diff contrib/automation/hgautomation/windows.py @ 44771:802ee93c205d stable

automation: support building Python 3 Inno installers The core packaging code now supports building Python 3 installers using PyOxidizer. Let's teach the automation code to invoke it so that we produce both Python 2 and Python 3 based exe installers. When publishing the artifacts, the Python 3 versions are preferred over the Python 2 versions given their higher weight (10 versus 9). This may be a controversial change. But I think making Python 3 the default is warranted, as it is the future. The Python 2 installers are still fully supported and can be installed should issues with Python 3 arise. Differential Revision: https://phab.mercurial-scm.org/D8483
author Gregory Szorc <gregory.szorc@gmail.com>
date Fri, 24 Apr 2020 12:11:08 -0700
parents 47609da15379
children 5e788dc7fb5d
line wrap: on
line diff
--- a/contrib/automation/hgautomation/windows.py	Fri Apr 24 11:48:07 2020 -0700
+++ b/contrib/automation/hgautomation/windows.py	Fri Apr 24 12:11:08 2020 -0700
@@ -68,7 +68,17 @@
 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 {extra_args}
@@ -108,8 +118,10 @@
 WHEEL_FILENAME_PYTHON38_X86 = 'mercurial-{version}-cp38-cp38-win32.whl'
 WHEEL_FILENAME_PYTHON38_X64 = 'mercurial-{version}-cp38-cp38-win_amd64.whl'
 
-X86_EXE_FILENAME = 'Mercurial-{version}-x86-python2.exe'
-X64_EXE_FILENAME = 'Mercurial-{version}-x64-python2.exe'
+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'
 X86_MSI_FILENAME = 'mercurial-{version}-x86-python2.msi'
 X64_MSI_FILENAME = 'mercurial-{version}-x64-python2.msi'
 
@@ -118,12 +130,21 @@
 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'
 )
-X64_EXE_DESCRIPTION = (
-    'Mercurial {version} Inno Setup installer - x64 Windows '
+# 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'
+)
+EXE_PYTHON3_X64_DESCRIPTION = (
+    'Mercurial {version} Inno Setup installer - x64 Windows (Python 3) '
     '- does not require admin rights'
 )
 X86_MSI_DESCRIPTION = (
@@ -285,22 +306,48 @@
 
 
 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)
 
@@ -388,16 +435,20 @@
         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 / X86_EXE_FILENAME.format(version=version),
-        dist_path / X64_EXE_FILENAME.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 / X86_MSI_FILENAME.format(version=version),
         dist_path / X64_MSI_FILENAME.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)
+    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)
     x86_msi_filename = X86_MSI_FILENAME.format(version=version)
     x64_msi_filename = X64_MSI_FILENAME.format(version=version)
 
@@ -406,15 +457,29 @@
             '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',