diff --git a/contrib/automation/hgautomation/cli.py b/contrib/automation/hgautomation/cli.py --- a/contrib/automation/hgautomation/cli.py +++ b/contrib/automation/hgautomation/cli.py @@ -63,7 +63,13 @@ def bootstrap_windows_dev(hga: HGAutomat def build_inno( - hga: HGAutomation, aws_region, arch, revision, version, base_image_name + hga: HGAutomation, + aws_region, + python_version, + arch, + revision, + version, + base_image_name, ): c = hga.aws_connection(aws_region) image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) @@ -74,14 +80,25 @@ def build_inno( windows.synchronize_hg(SOURCE_ROOT, revision, instance) - for a in arch: - windows.build_inno_installer( - instance.winrm_client, a, DIST_PATH, version=version - ) + for py_version in python_version: + for a in arch: + windows.build_inno_installer( + instance.winrm_client, + py_version, + a, + DIST_PATH, + version=version, + ) def build_wix( - hga: HGAutomation, aws_region, arch, revision, version, base_image_name + hga: HGAutomation, + aws_region, + python_version, + arch, + revision, + version, + base_image_name, ): c = hga.aws_connection(aws_region) image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) @@ -92,14 +109,24 @@ def build_wix( windows.synchronize_hg(SOURCE_ROOT, revision, instance) - for a in arch: - windows.build_wix_installer( - instance.winrm_client, a, DIST_PATH, version=version - ) + for py_version in python_version: + for a in arch: + windows.build_wix_installer( + instance.winrm_client, + py_version, + a, + DIST_PATH, + version=version, + ) def build_windows_wheel( - hga: HGAutomation, aws_region, arch, revision, base_image_name + hga: HGAutomation, + aws_region, + python_version, + arch, + revision, + base_image_name, ): c = hga.aws_connection(aws_region) image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) @@ -110,8 +137,11 @@ def build_windows_wheel( windows.synchronize_hg(SOURCE_ROOT, revision, instance) - for a in arch: - windows.build_wheel(instance.winrm_client, a, DIST_PATH) + for py_version in python_version: + for a in arch: + windows.build_wheel( + instance.winrm_client, py_version, a, DIST_PATH + ) def build_all_windows_packages( @@ -128,17 +158,25 @@ def build_all_windows_packages( windows.synchronize_hg(SOURCE_ROOT, revision, instance) - for arch in ('x86', 'x64'): - windows.purge_hg(winrm_client) - windows.build_wheel(winrm_client, arch, DIST_PATH) - windows.purge_hg(winrm_client) - windows.build_inno_installer( - winrm_client, arch, DIST_PATH, version=version - ) - windows.purge_hg(winrm_client) - windows.build_wix_installer( - winrm_client, arch, DIST_PATH, version=version - ) + for py_version in ("2.7", "3.7", "3.8"): + for arch in ("x86", "x64"): + windows.purge_hg(winrm_client) + windows.build_wheel( + winrm_client, + python_version=py_version, + arch=arch, + dest_path=DIST_PATH, + ) + + for py_version in (2, 3): + for arch in ('x86', 'x64'): + windows.purge_hg(winrm_client) + windows.build_inno_installer( + winrm_client, py_version, arch, DIST_PATH, version=version + ) + windows.build_wix_installer( + winrm_client, py_version, arch, DIST_PATH, version=version + ) def terminate_ec2_instances(hga: HGAutomation, aws_region): @@ -293,6 +331,14 @@ def get_parser(): 'build-inno', help='Build Inno Setup installer(s)', ) sp.add_argument( + '--python-version', + help='Which version of Python to target', + choices={2, 3}, + type=int, + nargs='*', + default=[3], + ) + sp.add_argument( '--arch', help='Architecture to build for', choices={'x86', 'x64'}, @@ -316,6 +362,13 @@ def get_parser(): 'build-windows-wheel', help='Build Windows wheel(s)', ) sp.add_argument( + '--python-version', + help='Python version to build for', + choices={'2.7', '3.7', '3.8'}, + nargs='*', + default=['3.8'], + ) + sp.add_argument( '--arch', help='Architecture to build for', choices={'x86', 'x64'}, @@ -334,6 +387,14 @@ def get_parser(): sp = subparsers.add_parser('build-wix', help='Build WiX installer(s)') sp.add_argument( + '--python-version', + help='Which version of Python to target', + choices={2, 3}, + type=int, + nargs='*', + default=[3], + ) + sp.add_argument( '--arch', help='Architecture to build for', choices={'x86', 'x64'}, diff --git a/contrib/automation/hgautomation/linux.py b/contrib/automation/hgautomation/linux.py --- a/contrib/automation/hgautomation/linux.py +++ b/contrib/automation/hgautomation/linux.py @@ -72,8 +72,10 @@ echo "${RUSTUP_INIT_SHA256} rustup-init" chmod +x rustup-init sudo -H -u hg -g hg ./rustup-init -y -sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup install 1.31.1 1.34.2 +sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup install 1.31.1 1.42.0 sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup component add clippy + +sudo -H -u hg -g hg /home/hg/.cargo/bin/cargo install --version 0.7.0 pyoxidizer ''' diff --git a/contrib/automation/hgautomation/windows.py b/contrib/automation/hgautomation/windows.py --- a/contrib/automation/hgautomation/windows.py +++ b/contrib/automation/hgautomation/windows.py @@ -68,10 +68,20 @@ hg.exe log -r . Write-Output "updated Mercurial working directory to {revision}" '''.lstrip() -BUILD_INNO = r''' +BUILD_INNO_PYTHON3 = r''' +$Env:RUSTUP_HOME = "C:\hgdev\rustup" +$Env:CARGO_HOME = "C:\hgdev\cargo" +Set-Location C:\hgdev\src +C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py inno --pyoxidizer-target {pyoxidizer_target} --version {version} +if ($LASTEXITCODE -ne 0) {{ + throw "process exited non-0: $LASTEXITCODE" +}} +''' + +BUILD_INNO_PYTHON2 = r''' Set-Location C:\hgdev\src $python = "C:\hgdev\python27-{arch}\python.exe" -C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py inno --python $python +C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py inno --python $python {extra_args} if ($LASTEXITCODE -ne 0) {{ throw "process exited non-0: $LASTEXITCODE" }} @@ -79,13 +89,23 @@ if ($LASTEXITCODE -ne 0) {{ BUILD_WHEEL = r''' Set-Location C:\hgdev\src -C:\hgdev\python27-{arch}\Scripts\pip.exe wheel --wheel-dir dist . +C:\hgdev\python{python_version}-{arch}\python.exe -m pip wheel --wheel-dir dist . if ($LASTEXITCODE -ne 0) {{ throw "process exited non-0: $LASTEXITCODE" }} ''' -BUILD_WIX = r''' +BUILD_WIX_PYTHON3 = r''' +$Env:RUSTUP_HOME = "C:\hgdev\rustup" +$Env:CARGO_HOME = "C:\hgdev\cargo" +Set-Location C:\hgdev\src +C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py wix --pyoxidizer-target {pyoxidizer_target} --version {version} +if ($LASTEXITCODE -ne 0) {{ + throw "process exited non-0: $LASTEXITCODE" +}} +''' + +BUILD_WIX_PYTHON2 = r''' Set-Location C:\hgdev\src $python = "C:\hgdev\python27-{arch}\python.exe" C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py wix --python $python {extra_args} @@ -101,31 +121,60 @@ if ($LASTEXITCODE -ne 0) {{ }} ''' -X86_WHEEL_FILENAME = 'mercurial-{version}-cp27-cp27m-win32.whl' -X64_WHEEL_FILENAME = 'mercurial-{version}-cp27-cp27m-win_amd64.whl' -X86_EXE_FILENAME = 'Mercurial-{version}.exe' -X64_EXE_FILENAME = 'Mercurial-{version}-x64.exe' -X86_MSI_FILENAME = 'mercurial-{version}-x86.msi' -X64_MSI_FILENAME = 'mercurial-{version}-x64.msi' +WHEEL_FILENAME_PYTHON27_X86 = 'mercurial-{version}-cp27-cp27m-win32.whl' +WHEEL_FILENAME_PYTHON27_X64 = 'mercurial-{version}-cp27-cp27m-win_amd64.whl' +WHEEL_FILENAME_PYTHON37_X86 = 'mercurial-{version}-cp37-cp37m-win32.whl' +WHEEL_FILENAME_PYTHON37_X64 = 'mercurial-{version}-cp37-cp37m-win_amd64.whl' +WHEEL_FILENAME_PYTHON38_X86 = 'mercurial-{version}-cp38-cp38-win32.whl' +WHEEL_FILENAME_PYTHON38_X64 = 'mercurial-{version}-cp38-cp38-win_amd64.whl' + +EXE_FILENAME_PYTHON2_X86 = 'Mercurial-{version}-x86-python2.exe' +EXE_FILENAME_PYTHON2_X64 = 'Mercurial-{version}-x64-python2.exe' +EXE_FILENAME_PYTHON3_X86 = 'Mercurial-{version}-x86.exe' +EXE_FILENAME_PYTHON3_X64 = 'Mercurial-{version}-x64.exe' + +MSI_FILENAME_PYTHON2_X86 = 'mercurial-{version}-x86-python2.msi' +MSI_FILENAME_PYTHON2_X64 = 'mercurial-{version}-x64-python2.msi' +MSI_FILENAME_PYTHON3_X86 = 'mercurial-{version}-x86.msi' +MSI_FILENAME_PYTHON3_X64 = 'mercurial-{version}-x64.msi' MERCURIAL_SCM_BASE_URL = 'https://mercurial-scm.org/release/windows' X86_USER_AGENT_PATTERN = '.*Windows.*' X64_USER_AGENT_PATTERN = '.*Windows.*(WOW|x)64.*' -X86_EXE_DESCRIPTION = ( - 'Mercurial {version} Inno Setup installer - x86 Windows ' +EXE_PYTHON2_X86_DESCRIPTION = ( + 'Mercurial {version} Inno Setup installer - x86 Windows (Python 2) ' + '- does not require admin rights' +) +EXE_PYTHON2_X64_DESCRIPTION = ( + 'Mercurial {version} Inno Setup installer - x64 Windows (Python 2) ' + '- does not require admin rights' +) +# TODO remove Python version once Python 2 is dropped. +EXE_PYTHON3_X86_DESCRIPTION = ( + 'Mercurial {version} Inno Setup installer - x86 Windows (Python 3) ' '- does not require admin rights' ) -X64_EXE_DESCRIPTION = ( - 'Mercurial {version} Inno Setup installer - x64 Windows ' +EXE_PYTHON3_X64_DESCRIPTION = ( + 'Mercurial {version} Inno Setup installer - x64 Windows (Python 3) ' '- does not require admin rights' ) -X86_MSI_DESCRIPTION = ( - 'Mercurial {version} MSI installer - x86 Windows ' '- requires admin rights' +MSI_PYTHON2_X86_DESCRIPTION = ( + 'Mercurial {version} MSI installer - x86 Windows (Python 2) ' + '- requires admin rights' +) +MSI_PYTHON2_X64_DESCRIPTION = ( + 'Mercurial {version} MSI installer - x64 Windows (Python 2) ' + '- requires admin rights' ) -X64_MSI_DESCRIPTION = ( - 'Mercurial {version} MSI installer - x64 Windows ' '- requires admin rights' +MSI_PYTHON3_X86_DESCRIPTION = ( + 'Mercurial {version} MSI installer - x86 Windows (Python 3) ' + '- requires admin rights' +) +MSI_PYTHON3_X64_DESCRIPTION = ( + 'Mercurial {version} MSI installer - x64 Windows (Python 3) ' + '- requires admin rights' ) @@ -280,53 +329,113 @@ def copy_latest_dist(winrm_client, patte def build_inno_installer( - winrm_client, arch: str, dest_path: pathlib.Path, version=None + winrm_client, + python_version: int, + arch: str, + dest_path: pathlib.Path, + version=None, ): """Build the Inno Setup installer on a remote machine. Using a WinRM client, remote commands are executed to build a Mercurial Inno Setup installer. """ - print('building Inno Setup installer for %s' % arch) + print( + 'building Inno Setup installer for Python %d %s' + % (python_version, arch) + ) + + if python_version == 3: + # TODO fix this limitation in packaging code + if not version: + raise Exception( + "version string is required when building for Python 3" + ) - extra_args = [] - if version: - extra_args.extend(['--version', version]) + if arch == "x86": + target_triple = "i686-pc-windows-msvc" + elif arch == "x64": + target_triple = "x86_64-pc-windows-msvc" + else: + raise Exception("unhandled arch: %s" % arch) - ps = get_vc_prefix(arch) + BUILD_INNO.format( - arch=arch, extra_args=' '.join(extra_args) - ) + ps = BUILD_INNO_PYTHON3.format( + pyoxidizer_target=target_triple, version=version, + ) + else: + extra_args = [] + if version: + extra_args.extend(['--version', version]) + + ps = get_vc_prefix(arch) + BUILD_INNO_PYTHON2.format( + arch=arch, extra_args=' '.join(extra_args) + ) + run_powershell(winrm_client, ps) copy_latest_dist(winrm_client, '*.exe', dest_path) -def build_wheel(winrm_client, arch: str, dest_path: pathlib.Path): +def build_wheel( + winrm_client, python_version: str, arch: str, dest_path: pathlib.Path +): """Build Python wheels on a remote machine. Using a WinRM client, remote commands are executed to build a Python wheel for Mercurial. """ - print('Building Windows wheel for %s' % arch) - ps = get_vc_prefix(arch) + BUILD_WHEEL.format(arch=arch) + print('Building Windows wheel for Python %s %s' % (python_version, arch)) + + ps = BUILD_WHEEL.format( + python_version=python_version.replace(".", ""), arch=arch + ) + + # Python 2.7 requires an activated environment. + if python_version == "2.7": + ps = get_vc_prefix(arch) + ps + run_powershell(winrm_client, ps) copy_latest_dist(winrm_client, '*.whl', dest_path) def build_wix_installer( - winrm_client, arch: str, dest_path: pathlib.Path, version=None + winrm_client, + python_version: int, + arch: str, + dest_path: pathlib.Path, + version=None, ): """Build the WiX installer on a remote machine. Using a WinRM client, remote commands are executed to build a WiX installer. """ - print('Building WiX installer for %s' % arch) - extra_args = [] - if version: - extra_args.extend(['--version', version]) + print('Building WiX installer for Python %d %s' % (python_version, arch)) + + if python_version == 3: + # TODO fix this limitation in packaging code + if not version: + raise Exception( + "version string is required when building for Python 3" + ) - ps = get_vc_prefix(arch) + BUILD_WIX.format( - arch=arch, extra_args=' '.join(extra_args) - ) + if arch == "x86": + target_triple = "i686-pc-windows-msvc" + elif arch == "x64": + target_triple = "x86_64-pc-windows-msvc" + else: + raise Exception("unhandled arch: %s" % arch) + + ps = BUILD_WIX_PYTHON3.format( + pyoxidizer_target=target_triple, version=version, + ) + else: + extra_args = [] + if version: + extra_args.extend(['--version', version]) + + ps = get_vc_prefix(arch) + BUILD_WIX_PYTHON2.format( + arch=arch, extra_args=' '.join(extra_args) + ) + run_powershell(winrm_client, ps) copy_latest_dist(winrm_client, '*.msi', dest_path) @@ -356,56 +465,100 @@ def run_tests(winrm_client, python_versi def resolve_wheel_artifacts(dist_path: pathlib.Path, version: str): return ( - dist_path / X86_WHEEL_FILENAME.format(version=version), - dist_path / X64_WHEEL_FILENAME.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON27_X86.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON27_X64.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON37_X86.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON37_X64.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON38_X86.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON38_X64.format(version=version), ) def resolve_all_artifacts(dist_path: pathlib.Path, version: str): return ( - dist_path / X86_WHEEL_FILENAME.format(version=version), - dist_path / X64_WHEEL_FILENAME.format(version=version), - dist_path / X86_EXE_FILENAME.format(version=version), - dist_path / X64_EXE_FILENAME.format(version=version), - dist_path / X86_MSI_FILENAME.format(version=version), - dist_path / X64_MSI_FILENAME.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON27_X86.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON27_X64.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON37_X86.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON37_X64.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON38_X86.format(version=version), + dist_path / WHEEL_FILENAME_PYTHON38_X64.format(version=version), + dist_path / EXE_FILENAME_PYTHON2_X86.format(version=version), + dist_path / EXE_FILENAME_PYTHON2_X64.format(version=version), + dist_path / EXE_FILENAME_PYTHON3_X86.format(version=version), + dist_path / EXE_FILENAME_PYTHON3_X64.format(version=version), + dist_path / MSI_FILENAME_PYTHON2_X86.format(version=version), + dist_path / MSI_FILENAME_PYTHON2_X64.format(version=version), + dist_path / MSI_FILENAME_PYTHON3_X86.format(version=version), + dist_path / MSI_FILENAME_PYTHON3_X64.format(version=version), ) def generate_latest_dat(version: str): - x86_exe_filename = X86_EXE_FILENAME.format(version=version) - x64_exe_filename = X64_EXE_FILENAME.format(version=version) - x86_msi_filename = X86_MSI_FILENAME.format(version=version) - x64_msi_filename = X64_MSI_FILENAME.format(version=version) + python2_x86_exe_filename = EXE_FILENAME_PYTHON2_X86.format(version=version) + python2_x64_exe_filename = EXE_FILENAME_PYTHON2_X64.format(version=version) + python3_x86_exe_filename = EXE_FILENAME_PYTHON3_X86.format(version=version) + python3_x64_exe_filename = EXE_FILENAME_PYTHON3_X64.format(version=version) + python2_x86_msi_filename = MSI_FILENAME_PYTHON2_X86.format(version=version) + python2_x64_msi_filename = MSI_FILENAME_PYTHON2_X64.format(version=version) + python3_x86_msi_filename = MSI_FILENAME_PYTHON3_X86.format(version=version) + python3_x64_msi_filename = MSI_FILENAME_PYTHON3_X64.format(version=version) entries = ( ( '10', version, X86_USER_AGENT_PATTERN, - '%s/%s' % (MERCURIAL_SCM_BASE_URL, x86_exe_filename), - X86_EXE_DESCRIPTION.format(version=version), + '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x86_exe_filename), + EXE_PYTHON3_X86_DESCRIPTION.format(version=version), ), ( '10', version, X64_USER_AGENT_PATTERN, - '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_exe_filename), - X64_EXE_DESCRIPTION.format(version=version), + '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x64_exe_filename), + EXE_PYTHON3_X64_DESCRIPTION.format(version=version), + ), + ( + '9', + version, + X86_USER_AGENT_PATTERN, + '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x86_exe_filename), + EXE_PYTHON2_X86_DESCRIPTION.format(version=version), + ), + ( + '9', + version, + X64_USER_AGENT_PATTERN, + '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x64_exe_filename), + EXE_PYTHON2_X64_DESCRIPTION.format(version=version), ), ( '10', version, X86_USER_AGENT_PATTERN, - '%s/%s' % (MERCURIAL_SCM_BASE_URL, x86_msi_filename), - X86_MSI_DESCRIPTION.format(version=version), + '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x86_msi_filename), + MSI_PYTHON3_X86_DESCRIPTION.format(version=version), ), ( '10', version, X64_USER_AGENT_PATTERN, - '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_msi_filename), - X64_MSI_DESCRIPTION.format(version=version), + '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x64_msi_filename), + MSI_PYTHON3_X64_DESCRIPTION.format(version=version), + ), + ( + '9', + version, + X86_USER_AGENT_PATTERN, + '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x86_msi_filename), + MSI_PYTHON2_X86_DESCRIPTION.format(version=version), + ), + ( + '9', + version, + X64_USER_AGENT_PATTERN, + '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x64_msi_filename), + MSI_PYTHON2_X64_DESCRIPTION.format(version=version), ), ) diff --git a/contrib/install-windows-dependencies.ps1 b/contrib/install-windows-dependencies.ps1 --- a/contrib/install-windows-dependencies.ps1 +++ b/contrib/install-windows-dependencies.ps1 @@ -64,6 +64,9 @@ $MERCURIAL_WHEEL_URL = "https://files.pythonhosted.org/packages/6d/47/e031e47f7fe9b16e4e3383da47e2b0a7eae6e603996bc67a03ec4fa1b3f4/$MERCURIAL_WHEEL_FILENAME" $MERCURIAL_WHEEL_SHA256 = "1d18c7f6ca1456f0f62ee65c9a50c14cbba48ce6e924930cdb10537f5c9eaf5f" +$RUSTUP_INIT_URL = "https://static.rust-lang.org/rustup/archive/1.21.1/x86_64-pc-windows-gnu/rustup-init.exe" +$RUSTUP_INIT_SHA256 = "d17df34ba974b9b19cf5c75883a95475aa22ddc364591d75d174090d55711c72" + # Writing progress slows down downloads substantially. So disable it. $progressPreference = 'silentlyContinue' @@ -116,6 +119,20 @@ function Install-Python3($name, $install Invoke-Process ${dest}\python.exe $pip } +function Install-Rust($prefix) { + Write-Output "installing Rust" + $Env:RUSTUP_HOME = "${prefix}\rustup" + $Env:CARGO_HOME = "${prefix}\cargo" + + Invoke-Process "${prefix}\assets\rustup-init.exe" "-y --default-host x86_64-pc-windows-msvc" + Invoke-Process "${prefix}\cargo\bin\rustup.exe" "target add i686-pc-windows-msvc" + Invoke-Process "${prefix}\cargo\bin\rustup.exe" "install 1.42.0" + Invoke-Process "${prefix}\cargo\bin\rustup.exe" "component add clippy" + + # Install PyOxidizer for packaging. + Invoke-Process "${prefix}\cargo\bin\cargo.exe" "install --version 0.7.0 pyoxidizer" +} + function Install-Dependencies($prefix) { if (!(Test-Path -Path $prefix\assets)) { New-Item -Path $prefix\assets -ItemType Directory @@ -140,6 +157,7 @@ function Install-Dependencies($prefix) { Secure-Download $INNO_SETUP_URL ${prefix}\assets\InnoSetup.exe $INNO_SETUP_SHA256 Secure-Download $MINGW_BIN_URL ${prefix}\assets\mingw-get-bin.zip $MINGW_BIN_SHA256 Secure-Download $MERCURIAL_WHEEL_URL ${prefix}\assets\${MERCURIAL_WHEEL_FILENAME} $MERCURIAL_WHEEL_SHA256 + Secure-Download $RUSTUP_INIT_URL ${prefix}\assets\rustup-init.exe $RUSTUP_INIT_SHA256 Write-Output "installing Python 2.7 32-bit" Invoke-Process msiexec.exe "/i ${prefix}\assets\python27-x86.msi /l* ${prefix}\assets\python27-x86.log /q TARGETDIR=${prefix}\python27-x86 ALLUSERS=" @@ -163,6 +181,8 @@ function Install-Dependencies($prefix) { Write-Output "installing Visual Studio 2017 Build Tools and SDKs" Invoke-Process ${prefix}\assets\vs_buildtools.exe "--quiet --wait --norestart --nocache --channelUri https://aka.ms/vs/15/release/channel --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.Windows10SDK.17763 --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows10SDK --add Microsoft.VisualStudio.Component.VC.140" + Install-Rust ${prefix} + Write-Output "installing Visual C++ 9.0 for Python 2.7" Invoke-Process msiexec.exe "/i ${prefix}\assets\VCForPython27.msi /l* ${prefix}\assets\VCForPython27.log /q" diff --git a/contrib/packaging/hgpackaging/cli.py b/contrib/packaging/hgpackaging/cli.py --- a/contrib/packaging/hgpackaging/cli.py +++ b/contrib/packaging/hgpackaging/cli.py @@ -20,8 +20,11 @@ HERE = pathlib.Path(os.path.abspath(os.p SOURCE_DIR = HERE.parent.parent.parent -def build_inno(python=None, iscc=None, version=None): - if not os.path.isabs(python): +def build_inno(pyoxidizer_target=None, python=None, iscc=None, version=None): + if not pyoxidizer_target and not python: + raise Exception("--python required unless building with PyOxidizer") + + if python and not os.path.isabs(python): raise Exception("--python arg must be an absolute path") if iscc: @@ -35,13 +38,19 @@ def build_inno(python=None, iscc=None, v build_dir = SOURCE_DIR / "build" - inno.build( - SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version, - ) + if pyoxidizer_target: + inno.build_with_pyoxidizer( + SOURCE_DIR, build_dir, pyoxidizer_target, iscc, version=version + ) + else: + inno.build_with_py2exe( + SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version, + ) def build_wix( name=None, + pyoxidizer_target=None, python=None, version=None, sign_sn=None, @@ -52,17 +61,29 @@ def build_wix( extra_wxs=None, extra_features=None, ): - fn = wix.build_installer + if not pyoxidizer_target and not python: + raise Exception("--python required unless building with PyOxidizer") + + if python and not os.path.isabs(python): + raise Exception("--python arg must be an absolute path") + kwargs = { "source_dir": SOURCE_DIR, - "python_exe": pathlib.Path(python), "version": version, } - if not os.path.isabs(python): - raise Exception("--python arg must be an absolute path") + if pyoxidizer_target: + fn = wix.build_installer_pyoxidizer + kwargs["target_triple"] = pyoxidizer_target + else: + fn = wix.build_installer_py2exe + kwargs["python_exe"] = pathlib.Path(python) if extra_packages_script: + if pyoxidizer_target: + raise Exception( + "pyoxidizer does not support --extra-packages-script" + ) kwargs["extra_packages_script"] = extra_packages_script if extra_wxs: kwargs["extra_wxs"] = dict( @@ -72,12 +93,13 @@ def build_wix( kwargs["extra_features"] = extra_features.split(",") if sign_sn or sign_cert: - fn = wix.build_signed_installer - kwargs["name"] = name - kwargs["subject_name"] = sign_sn - kwargs["cert_path"] = sign_cert - kwargs["cert_password"] = sign_password - kwargs["timestamp_url"] = sign_timestamp_url + kwargs["signing_info"] = { + "name": name, + "subject_name": sign_sn, + "cert_path": sign_cert, + "cert_password": sign_password, + "timestamp_url": sign_timestamp_url, + } fn(**kwargs) @@ -88,7 +110,12 @@ def get_parser(): subparsers = parser.add_subparsers() sp = subparsers.add_parser("inno", help="Build Inno Setup installer") - sp.add_argument("--python", required=True, help="path to python.exe to use") + sp.add_argument( + "--pyoxidizer-target", + choices={"i686-pc-windows-msvc", "x86_64-pc-windows-msvc"}, + help="Build with PyOxidizer targeting this host triple", + ) + sp.add_argument("--python", help="path to python.exe to use") sp.add_argument("--iscc", help="path to iscc.exe to use") sp.add_argument( "--version", @@ -102,8 +129,11 @@ def get_parser(): ) sp.add_argument("--name", help="Application name", default="Mercurial") sp.add_argument( - "--python", help="Path to Python executable to use", required=True + "--pyoxidizer-target", + choices={"i686-pc-windows-msvc", "x86_64-pc-windows-msvc"}, + help="Build with PyOxidizer targeting this host triple", ) + sp.add_argument("--python", help="Path to Python executable to use") sp.add_argument( "--sign-sn", help="Subject name (or fragment thereof) of certificate " diff --git a/contrib/packaging/hgpackaging/inno.py b/contrib/packaging/hgpackaging/inno.py --- a/contrib/packaging/hgpackaging/inno.py +++ b/contrib/packaging/hgpackaging/inno.py @@ -18,8 +18,9 @@ from .py2exe import ( build_py2exe, stage_install, ) +from .pyoxidizer import run_pyoxidizer from .util import ( - find_vc_runtime_files, + find_legacy_vc_runtime_files, normalize_windows_version, process_install_rules, read_version_py, @@ -41,14 +42,14 @@ PACKAGE_FILES_METADATA = { } -def build( +def build_with_py2exe( source_dir: pathlib.Path, build_dir: pathlib.Path, python_exe: pathlib.Path, iscc_exe: pathlib.Path, version=None, ): - """Build the Inno installer. + """Build the Inno installer using py2exe. Build files will be placed in ``build_dir``. @@ -61,8 +62,7 @@ def build( vc_x64 = r'\x64' in os.environ.get('LIB', '') arch = 'x64' if vc_x64 else 'x86' - inno_source_dir = source_dir / 'contrib' / 'packaging' / 'inno' - inno_build_dir = build_dir / ('inno-%s' % arch) + inno_build_dir = build_dir / ('inno-py2exe-%s' % arch) staging_dir = inno_build_dir / 'stage' requirements_txt = ( @@ -93,7 +93,7 @@ def build( process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir) # hg.exe depends on VC9 runtime DLLs. Copy those into place. - for f in find_vc_runtime_files(vc_x64): + for f in find_legacy_vc_runtime_files(vc_x64): if f.name.endswith('.manifest'): basename = 'Microsoft.VC90.CRT.manifest' else: @@ -104,6 +104,62 @@ def build( print('copying %s to %s' % (f, dest_path)) shutil.copyfile(f, dest_path) + build_installer( + source_dir, + inno_build_dir, + staging_dir, + iscc_exe, + version, + arch="x64" if vc_x64 else None, + suffix="-python2", + ) + + +def build_with_pyoxidizer( + source_dir: pathlib.Path, + build_dir: pathlib.Path, + target_triple: str, + iscc_exe: pathlib.Path, + version=None, +): + """Build the Inno installer using PyOxidizer.""" + if not iscc_exe.exists(): + raise Exception("%s does not exist" % iscc_exe) + + inno_build_dir = build_dir / ("inno-pyoxidizer-%s" % target_triple) + staging_dir = inno_build_dir / "stage" + + inno_build_dir.mkdir(parents=True, exist_ok=True) + run_pyoxidizer(source_dir, inno_build_dir, staging_dir, target_triple) + + process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir) + + build_installer( + source_dir, + inno_build_dir, + staging_dir, + iscc_exe, + version, + arch="x64" if "x86_64" in target_triple else None, + ) + + +def build_installer( + source_dir: pathlib.Path, + inno_build_dir: pathlib.Path, + staging_dir: pathlib.Path, + iscc_exe: pathlib.Path, + version, + arch=None, + suffix="", +): + """Build an Inno installer from staged Mercurial files. + + This function is agnostic about how to build Mercurial. It just + cares that Mercurial files are in ``staging_dir``. + """ + inno_source_dir = source_dir / "contrib" / "packaging" / "inno" + # The final package layout is simply a mirror of the staging directory. package_files = [] for root, dirs, files in os.walk(staging_dir): @@ -158,8 +214,11 @@ def build( args = [str(iscc_exe)] - if vc_x64: - args.append('/dARCH=x64') + if arch: + args.append('/dARCH=%s' % arch) + args.append('/dSUFFIX=-%s%s' % (arch, suffix)) + else: + args.append('/dSUFFIX=-x86%s' % suffix) if not version: version = read_version_py(source_dir) diff --git a/contrib/packaging/hgpackaging/pyoxidizer.py b/contrib/packaging/hgpackaging/pyoxidizer.py new file mode 100644 --- /dev/null +++ b/contrib/packaging/hgpackaging/pyoxidizer.py @@ -0,0 +1,145 @@ +# pyoxidizer.py - Packaging support for PyOxidizer +# +# Copyright 2020 Gregory Szorc +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +# no-check-code because Python 3 native. + +import os +import pathlib +import shutil +import subprocess +import sys + +from .downloads import download_entry +from .util import ( + extract_zip_to_directory, + process_install_rules, + find_vc_runtime_dll, +) + + +STAGING_RULES_WINDOWS = [ + ('contrib/bash_completion', 'contrib/'), + ('contrib/hgk', 'contrib/hgk.tcl'), + ('contrib/hgweb.fcgi', 'contrib/'), + ('contrib/hgweb.wsgi', 'contrib/'), + ('contrib/logo-droplets.svg', 'contrib/'), + ('contrib/mercurial.el', 'contrib/'), + ('contrib/mq.el', 'contrib/'), + ('contrib/tcsh_completion', 'contrib/'), + ('contrib/tcsh_completion_build.sh', 'contrib/'), + ('contrib/vim/*', 'contrib/vim/'), + ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'), + ('contrib/win32/ReadMe.html', 'ReadMe.html'), + ('contrib/xml.rnc', 'contrib/'), + ('contrib/zsh_completion', 'contrib/'), + ('doc/*.html', 'doc/'), + ('doc/style.css', 'doc/'), + ('COPYING', 'Copying.txt'), +] + +STAGING_RULES_APP = [ + ('mercurial/helptext/**/*.txt', 'helptext/'), + ('mercurial/defaultrc/*.rc', 'defaultrc/'), + ('mercurial/locale/**/*', 'locale/'), + ('mercurial/templates/**/*', 'templates/'), +] + +STAGING_EXCLUDES_WINDOWS = [ + "doc/hg-ssh.8.html", +] + + +def run_pyoxidizer( + source_dir: pathlib.Path, + build_dir: pathlib.Path, + out_dir: pathlib.Path, + target_triple: str, +): + """Build Mercurial with PyOxidizer and copy additional files into place. + + After successful completion, ``out_dir`` contains files constituting a + Mercurial install. + """ + # We need to make gettext binaries available for compiling i18n files. + gettext_pkg, gettext_entry = download_entry('gettext', build_dir) + gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0] + + gettext_root = build_dir / ('gettext-win-%s' % gettext_entry['version']) + + if not gettext_root.exists(): + extract_zip_to_directory(gettext_pkg, gettext_root) + extract_zip_to_directory(gettext_dep_pkg, gettext_root) + + env = dict(os.environ) + env["PATH"] = "%s%s%s" % ( + env["PATH"], + os.pathsep, + str(gettext_root / "bin"), + ) + + args = [ + "pyoxidizer", + "build", + "--path", + str(source_dir / "rust" / "hgcli"), + "--release", + "--target-triple", + target_triple, + ] + + subprocess.run(args, env=env, check=True) + + if "windows" in target_triple: + target = "app_windows" + else: + target = "app_posix" + + build_dir = ( + source_dir / "build" / "pyoxidizer" / target_triple / "release" / target + ) + + if out_dir.exists(): + print("purging %s" % out_dir) + shutil.rmtree(out_dir) + + # Now assemble all the files from PyOxidizer into the staging directory. + shutil.copytree(build_dir, out_dir) + + # Move some of those files around. + process_install_rules(STAGING_RULES_APP, build_dir, out_dir) + # Nuke the mercurial/* directory, as we copied resources + # to an appropriate location just above. + shutil.rmtree(out_dir / "mercurial") + + # We also need to run setup.py build_doc to produce html files, + # as they aren't built as part of ``pip install``. + # This will fail if docutils isn't installed. + subprocess.run( + [sys.executable, str(source_dir / "setup.py"), "build_doc", "--html"], + cwd=str(source_dir), + check=True, + ) + + if "windows" in target_triple: + process_install_rules(STAGING_RULES_WINDOWS, source_dir, out_dir) + + # Write out a default editor.rc file to configure notepad as the + # default editor. + with (out_dir / "defaultrc" / "editor.rc").open( + "w", encoding="utf-8" + ) as fh: + fh.write("[ui]\neditor = notepad\n") + + for f in STAGING_EXCLUDES_WINDOWS: + p = out_dir / f + if p.exists(): + print("removing %s" % p) + p.unlink() + + # Add vcruntimeXXX.dll next to executable. + vc_runtime_dll = find_vc_runtime_dll(x64="x86_64" in target_triple) + shutil.copy(vc_runtime_dll, out_dir / vc_runtime_dll.name) diff --git a/contrib/packaging/hgpackaging/util.py b/contrib/packaging/hgpackaging/util.py --- a/contrib/packaging/hgpackaging/util.py +++ b/contrib/packaging/hgpackaging/util.py @@ -29,7 +29,59 @@ def extract_zip_to_directory(source: pat zf.extractall(dest) -def find_vc_runtime_files(x64=False): +def find_vc_runtime_dll(x64=False): + """Finds Visual C++ Runtime DLL to include in distribution.""" + # We invoke vswhere to find the latest Visual Studio install. + vswhere = ( + pathlib.Path(os.environ["ProgramFiles(x86)"]) + / "Microsoft Visual Studio" + / "Installer" + / "vswhere.exe" + ) + + if not vswhere.exists(): + raise Exception( + "could not find vswhere.exe: %s does not exist" % vswhere + ) + + args = [ + str(vswhere), + # -products * is necessary to return results from Build Tools + # (as opposed to full IDE installs). + "-products", + "*", + "-requires", + "Microsoft.VisualCpp.Redist.14.Latest", + "-latest", + "-property", + "installationPath", + ] + + vs_install_path = pathlib.Path( + os.fsdecode(subprocess.check_output(args).strip()) + ) + + # This just gets us a path like + # C:\Program Files (x86)\Microsoft Visual Studio\2019\Community + # Actually vcruntime140.dll is under a path like: + # VC\Redist\MSVC\\\Microsoft.VC14.CRT\vcruntime140.dll. + + arch = "x64" if x64 else "x86" + + search_glob = ( + r"%s\VC\Redist\MSVC\*\%s\Microsoft.VC14*.CRT\vcruntime140.dll" + % (vs_install_path, arch) + ) + + candidates = glob.glob(search_glob, recursive=True) + + for candidate in reversed(candidates): + return pathlib.Path(candidate) + + raise Exception("could not find vcruntime140.dll") + + +def find_legacy_vc_runtime_files(x64=False): """Finds Visual C++ Runtime DLLs to include in distribution.""" winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS' diff --git a/contrib/packaging/hgpackaging/wix.py b/contrib/packaging/hgpackaging/wix.py --- a/contrib/packaging/hgpackaging/wix.py +++ b/contrib/packaging/hgpackaging/wix.py @@ -22,6 +22,7 @@ from .py2exe import ( build_py2exe, stage_install, ) +from .pyoxidizer import run_pyoxidizer from .util import ( extract_zip_to_directory, normalize_windows_version, @@ -121,30 +122,6 @@ def run_candle(wix, cwd, wxs, source_dir subprocess.run(args, cwd=str(cwd), check=True) -def make_post_build_signing_fn( - name, - subject_name=None, - cert_path=None, - cert_password=None, - timestamp_url=None, -): - """Create a callable that will use signtool to sign hg.exe.""" - - def post_build_sign(source_dir, build_dir, dist_dir, version): - description = '%s %s' % (name, version) - - sign_with_signtool( - dist_dir / 'hg.exe', - description, - subject_name=subject_name, - cert_path=cert_path, - cert_password=cert_password, - timestamp_url=timestamp_url, - ) - - return post_build_sign - - def make_files_xml(staging_dir: pathlib.Path, is_x64) -> str: """Create XML string listing every file to be installed.""" @@ -308,27 +285,23 @@ def make_files_xml(staging_dir: pathlib. return doc.toprettyxml() -def build_installer( +def build_installer_py2exe( source_dir: pathlib.Path, python_exe: pathlib.Path, msi_name='mercurial', version=None, - post_build_fn=None, extra_packages_script=None, extra_wxs: typing.Optional[typing.Dict[str, str]] = None, extra_features: typing.Optional[typing.List[str]] = None, + signing_info: typing.Optional[typing.Dict[str, str]] = None, ): - """Build a WiX MSI installer. + """Build a WiX MSI installer using py2exe. ``source_dir`` is the path to the Mercurial source tree to use. ``arch`` is the target architecture. either ``x86`` or ``x64``. ``python_exe`` is the path to the Python executable to use/bundle. ``version`` is the Mercurial version string. If not defined, ``mercurial/__version__.py`` will be consulted. - ``post_build_fn`` is a callable that will be called after building - Mercurial but before invoking WiX. It can be used to e.g. facilitate - signing. It is passed the paths to the Mercurial source, build, and - dist directories and the resolved Mercurial version. ``extra_packages_script`` is a command to be run to inject extra packages into the py2exe binary. It should stage packages into the virtualenv and print a null byte followed by a newline-separated list of packages that @@ -340,8 +313,6 @@ def build_installer( arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86' hg_build_dir = source_dir / 'build' - dist_dir = source_dir / 'dist' - wix_dir = source_dir / 'contrib' / 'packaging' / 'wix' requirements_txt = ( source_dir / 'contrib' / 'packaging' / 'requirements_win32.txt' @@ -357,15 +328,6 @@ def build_installer( extra_packages_script=extra_packages_script, ) - orig_version = version or find_version(source_dir) - version = normalize_windows_version(orig_version) - print('using version string: %s' % version) - if version != orig_version: - print('(normalized from: %s)' % orig_version) - - if post_build_fn: - post_build_fn(source_dir, hg_build_dir, dist_dir, version) - build_dir = hg_build_dir / ('wix-%s' % arch) staging_dir = build_dir / 'stage' @@ -388,13 +350,112 @@ def build_installer( print('removing %s' % p) p.unlink() - wix_pkg, wix_entry = download_entry('wix', hg_build_dir) - wix_path = hg_build_dir / ('wix-%s' % wix_entry['version']) + return run_wix_packaging( + source_dir, + build_dir, + staging_dir, + arch, + version=version, + python2=True, + msi_name=msi_name, + suffix="-python2", + extra_wxs=extra_wxs, + extra_features=extra_features, + signing_info=signing_info, + ) + + +def build_installer_pyoxidizer( + source_dir: pathlib.Path, + target_triple: str, + msi_name='mercurial', + version=None, + extra_wxs: typing.Optional[typing.Dict[str, str]] = None, + extra_features: typing.Optional[typing.List[str]] = None, + signing_info: typing.Optional[typing.Dict[str, str]] = None, +): + """Build a WiX MSI installer using PyOxidizer.""" + hg_build_dir = source_dir / "build" + build_dir = hg_build_dir / ("wix-%s" % target_triple) + staging_dir = build_dir / "stage" + + arch = "x64" if "x86_64" in target_triple else "x86" + + build_dir.mkdir(parents=True, exist_ok=True) + run_pyoxidizer(source_dir, build_dir, staging_dir, target_triple) + + # We also install some extra files. + process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir) + + # And remove some files we don't want. + for f in STAGING_REMOVE_FILES: + p = staging_dir / f + if p.exists(): + print('removing %s' % p) + p.unlink() + + return run_wix_packaging( + source_dir, + build_dir, + staging_dir, + arch, + version, + python2=False, + msi_name=msi_name, + extra_wxs=extra_wxs, + extra_features=extra_features, + signing_info=signing_info, + ) + + +def run_wix_packaging( + source_dir: pathlib.Path, + build_dir: pathlib.Path, + staging_dir: pathlib.Path, + arch: str, + version: str, + python2: bool, + msi_name: typing.Optional[str] = "mercurial", + suffix: str = "", + extra_wxs: typing.Optional[typing.Dict[str, str]] = None, + extra_features: typing.Optional[typing.List[str]] = None, + signing_info: typing.Optional[typing.Dict[str, str]] = None, +): + """Invokes WiX to package up a built Mercurial. + + ``signing_info`` is a dict defining properties to facilitate signing the + installer. Recognized keys include ``name``, ``subject_name``, + ``cert_path``, ``cert_password``, and ``timestamp_url``. If populated, + we will sign both the hg.exe and the .msi using the signing credentials + specified. + """ + + orig_version = version or find_version(source_dir) + version = normalize_windows_version(orig_version) + print('using version string: %s' % version) + if version != orig_version: + print('(normalized from: %s)' % orig_version) + + if signing_info: + sign_with_signtool( + staging_dir / "hg.exe", + "%s %s" % (signing_info["name"], version), + subject_name=signing_info["subject_name"], + cert_path=signing_info["cert_path"], + cert_password=signing_info["cert_password"], + timestamp_url=signing_info["timestamp_url"], + ) + + wix_dir = source_dir / 'contrib' / 'packaging' / 'wix' + + wix_pkg, wix_entry = download_entry('wix', build_dir) + wix_path = build_dir / ('wix-%s' % wix_entry['version']) if not wix_path.exists(): extract_zip_to_directory(wix_pkg, wix_path) - ensure_vc90_merge_modules(hg_build_dir) + if python2: + ensure_vc90_merge_modules(build_dir) source_build_rel = pathlib.Path(os.path.relpath(source_dir, build_dir)) @@ -413,7 +474,16 @@ def build_installer( source = wix_dir / 'mercurial.wxs' defines['Version'] = version defines['Comments'] = 'Installs Mercurial version %s' % version - defines['VCRedistSrcDir'] = str(hg_build_dir) + + if python2: + defines["PythonVersion"] = "2" + defines['VCRedistSrcDir'] = str(build_dir) + else: + defines["PythonVersion"] = "3" + + if (staging_dir / "lib").exists(): + defines["MercurialHasLib"] = "1" + if extra_features: assert all(';' not in f for f in extra_features) defines['MercurialExtraFeatures'] = ';'.join(extra_features) @@ -421,7 +491,9 @@ def build_installer( run_candle(wix_path, build_dir, source, source_build_rel, defines=defines) msi_path = ( - source_dir / 'dist' / ('%s-%s-%s.msi' % (msi_name, orig_version, arch)) + source_dir + / 'dist' + / ('%s-%s-%s%s.msi' % (msi_name, orig_version, arch, suffix)) ) args = [ @@ -448,52 +520,16 @@ def build_installer( print('%s created' % msi_path) + if signing_info: + sign_with_signtool( + msi_path, + "%s %s" % (signing_info["name"], version), + subject_name=signing_info["subject_name"], + cert_path=signing_info["cert_path"], + cert_password=signing_info["cert_password"], + timestamp_url=signing_info["timestamp_url"], + ) + return { 'msi_path': msi_path, } - - -def build_signed_installer( - source_dir: pathlib.Path, - python_exe: pathlib.Path, - name: str, - version=None, - subject_name=None, - cert_path=None, - cert_password=None, - timestamp_url=None, - extra_packages_script=None, - extra_wxs=None, - extra_features=None, -): - """Build an installer with signed executables.""" - - post_build_fn = make_post_build_signing_fn( - name, - subject_name=subject_name, - cert_path=cert_path, - cert_password=cert_password, - timestamp_url=timestamp_url, - ) - - info = build_installer( - source_dir, - python_exe=python_exe, - msi_name=name.lower(), - version=version, - post_build_fn=post_build_fn, - extra_packages_script=extra_packages_script, - extra_wxs=extra_wxs, - extra_features=extra_features, - ) - - description = '%s %s' % (name, version) - - sign_with_signtool( - info['msi_path'], - description, - subject_name=subject_name, - cert_path=cert_path, - cert_password=cert_password, - timestamp_url=timestamp_url, - ) diff --git a/contrib/packaging/inno/mercurial.iss b/contrib/packaging/inno/mercurial.iss --- a/contrib/packaging/inno/mercurial.iss +++ b/contrib/packaging/inno/mercurial.iss @@ -9,14 +9,13 @@ AppCopyright=Copyright 2005-2020 Matt Mackall and others AppName=Mercurial AppVersion={#VERSION} +OutputBaseFilename=Mercurial-{#VERSION}{#SUFFIX} #if ARCH == "x64" AppVerName=Mercurial {#VERSION} (64-bit) -OutputBaseFilename=Mercurial-{#VERSION}-x64 ArchitecturesAllowed=x64 ArchitecturesInstallIn64BitMode=x64 #else AppVerName=Mercurial {#VERSION} -OutputBaseFilename=Mercurial-{#VERSION} #endif InfoAfterFile=../postinstall.txt LicenseFile=Copying.txt diff --git a/contrib/packaging/pyoxidizer.bzl b/contrib/packaging/pyoxidizer.bzl deleted file mode 100644 --- a/contrib/packaging/pyoxidizer.bzl +++ /dev/null @@ -1,60 +0,0 @@ -# Instructions: -# -# 1. cargo install --version 0.5.0 pyoxidizer -# 2. cd /path/to/hg -# 3. pyoxidizer build --path contrib/packaging [--release] -# 4. Run build/pyoxidizer///app/hg -# -# If you need to build again, you need to remove the build/lib.* and -# build/temp.* directories, otherwise PyOxidizer fails to pick up C -# extensions. This is a bug in PyOxidizer. - -ROOT = CWD + "/../.." - -set_build_path(ROOT + "/build/pyoxidizer") - -def make_exe(): - dist = default_python_distribution() - - code = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()" - - config = PythonInterpreterConfig( - raw_allocator = "system", - run_eval = code, - # We want to let the user load extensions from the file system - filesystem_importer = True, - # We need this to make resourceutil happy, since it looks for sys.frozen. - sys_frozen = True, - legacy_windows_stdio = True, - ) - - exe = dist.to_python_executable( - name = "hg", - config = config, - ) - - # Use setup.py install to build Mercurial and collect Python resources to - # embed in the executable. - resources = dist.setup_py_install(ROOT) - exe.add_python_resources(resources) - - return exe - -def make_install(exe): - m = FileManifest() - - # `hg` goes in root directory. - m.add_python_resource(".", exe) - - templates = glob( - include=[ROOT + "/mercurial/templates/**/*"], - strip_prefix = ROOT + "/mercurial/", - ) - m.add_manifest(templates) - - return m - -register_target("exe", make_exe) -register_target("app", make_install, depends = ["exe"], default = True) - -resolve_targets() diff --git a/contrib/packaging/wix/mercurial.wxs b/contrib/packaging/wix/mercurial.wxs --- a/contrib/packaging/wix/mercurial.wxs +++ b/contrib/packaging/wix/mercurial.wxs @@ -79,16 +79,21 @@ - - - - - - + + + + + + + + + @@ -101,10 +106,14 @@ - + + + - - + + + + diff --git a/mercurial/patch.py b/mercurial/patch.py --- a/mercurial/patch.py +++ b/mercurial/patch.py @@ -2558,7 +2558,7 @@ def diff( fctx2 is not None ), b'fctx2 unexpectly None in diff hunks filtering' hunks = hunksfilterfn(fctx2, hunks) - text = b''.join(sum((list(hlines) for hrange, hlines in hunks), [])) + text = b''.join(b''.join(hlines) for hrange, hlines in hunks) if hdr and (text or len(hdr) > 1): yield b'\n'.join(hdr) + b'\n' if text: diff --git a/rust/hgcli/pyoxidizer.bzl b/rust/hgcli/pyoxidizer.bzl --- a/rust/hgcli/pyoxidizer.bzl +++ b/rust/hgcli/pyoxidizer.bzl @@ -1,13 +1,24 @@ ROOT = CWD + "/../.." -def make_exe(): - dist = default_python_distribution() +# Code to run in Python interpreter. +RUN_CODE = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()" + + +set_build_path(ROOT + "/build/pyoxidizer") + - code = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()" +def make_distribution(): + return default_python_distribution() + +def make_distribution_windows(): + return default_python_distribution(flavor="standalone_dynamic") + + +def make_exe(dist): config = PythonInterpreterConfig( raw_allocator = "system", - run_eval = code, + run_eval = RUN_CODE, # We want to let the user load extensions from the file system filesystem_importer = True, # We need this to make resourceutil happy, since it looks for sys.frozen. @@ -24,30 +35,65 @@ def make_exe(): extension_module_filter = "all", ) - exe.add_python_resources(dist.pip_install([ROOT])) + # Add Mercurial to resources. + for resource in dist.pip_install(["--verbose", ROOT]): + # This is a bit wonky and worth explaining. + # + # Various parts of Mercurial don't yet support loading package + # resources via the ResourceReader interface. Or, not having + # file-based resources would be too inconvenient for users. + # + # So, for package resources, we package them both in the + # filesystem as well as in memory. If both are defined, + # PyOxidizer will prefer the in-memory location. So even + # if the filesystem file isn't packaged in the location + # specified here, we should never encounter an errors as the + # resource will always be available in memory. + if type(resource) == "PythonPackageResource": + exe.add_filesystem_relative_python_resource(".", resource) + exe.add_in_memory_python_resource(resource) + else: + exe.add_python_resource(resource) + + # On Windows, we install extra packages for convenience. + if "windows" in BUILD_TARGET_TRIPLE: + exe.add_python_resources( + dist.pip_install(["-r", ROOT + "/contrib/packaging/requirements_win32.txt"]) + ) return exe -def make_install(exe): + +def make_manifest(dist, exe): m = FileManifest() - - # `hg` goes in root directory. m.add_python_resource(".", exe) - templates = glob( - include = [ROOT + "/mercurial/templates/**/*"], - strip_prefix = ROOT + "/mercurial/", - ) - m.add_manifest(templates) + return m - return m def make_embedded_resources(exe): return exe.to_embedded_resources() -register_target("exe", make_exe) -register_target("app", make_install, depends = ["exe"], default = True) -register_target("embedded", make_embedded_resources, depends = ["exe"], default_build_script = True) + +register_target("distribution_posix", make_distribution) +register_target("distribution_windows", make_distribution_windows) + +register_target("exe_posix", make_exe, depends = ["distribution_posix"]) +register_target("exe_windows", make_exe, depends = ["distribution_windows"]) + +register_target( + "app_posix", + make_manifest, + depends = ["distribution_posix", "exe_posix"], + default = "windows" not in BUILD_TARGET_TRIPLE, +) +register_target( + "app_windows", + make_manifest, + depends = ["distribution_windows", "exe_windows"], + default = "windows" in BUILD_TARGET_TRIPLE, +) + resolve_targets() # END OF COMMON USER-ADJUSTED SETTINGS. @@ -55,5 +101,4 @@ resolve_targets() # Everything below this is typically managed by PyOxidizer and doesn't need # to be updated by people. -PYOXIDIZER_VERSION = "0.7.0-pre" -PYOXIDIZER_COMMIT = "c772a1379c3026314eda1c8ea244b86c0658951d" +PYOXIDIZER_VERSION = "0.7.0" diff --git a/tests/test-check-code.t b/tests/test-check-code.t --- a/tests/test-check-code.t +++ b/tests/test-check-code.t @@ -27,6 +27,7 @@ New errors are not allowed. Warnings are Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob) Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob) Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob) + Skipping contrib/packaging/hgpackaging/pyoxidizer.py it has no-che?k-code (glob) Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob) Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob) Skipping i18n/polib.py it has no-che?k-code (glob)