automation: support building Python 3 Inno installers stable
authorGregory Szorc <gregory.szorc@gmail.com>
Fri, 24 Apr 2020 12:11:08 -0700
branchstable
changeset 44771 802ee93c205d
parent 44770 47609da15379
child 44772 5e788dc7fb5d
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
contrib/automation/hgautomation/cli.py
contrib/automation/hgautomation/windows.py
--- a/contrib/automation/hgautomation/cli.py	Fri Apr 24 11:48:07 2020 -0700
+++ b/contrib/automation/hgautomation/cli.py	Fri Apr 24 12:11:08 2020 -0700
@@ -63,7 +63,13 @@
 
 
 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,10 +80,15 @@
 
         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(
@@ -146,12 +157,15 @@
                     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
+                )
+
         for arch in ('x86', 'x64'):
             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
             )
@@ -309,6 +323,14 @@
         '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'},
--- 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',