changeset 48848:17d5e25b8e78

packaging: remove py2exe / Python 2.7 support This commit started by deleting references to py2exe (which is only used on Python 2). After pulling the thread, quite a lot of code was orphaned and was deleted. Differential Revision: https://phab.mercurial-scm.org/D12265
author Gregory Szorc <gregory.szorc@gmail.com>
date Sat, 19 Feb 2022 22:13:11 -0700
parents 4561ec90d3c1
children ec6a4021beb1
files contrib/packaging/hgpackaging/cli.py contrib/packaging/hgpackaging/downloads.py contrib/packaging/hgpackaging/inno.py contrib/packaging/hgpackaging/py2exe.py contrib/packaging/hgpackaging/util.py contrib/packaging/hgpackaging/wix.py contrib/packaging/requirements-windows-py2.txt tests/test-check-code.t
diffstat 8 files changed, 10 insertions(+), 1129 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/packaging/hgpackaging/cli.py	Sat Feb 19 18:42:12 2022 -0700
+++ b/contrib/packaging/hgpackaging/cli.py	Sat Feb 19 22:13:11 2022 -0700
@@ -20,13 +20,7 @@
 SOURCE_DIR = HERE.parent.parent.parent
 
 
-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")
-
+def build_inno(pyoxidizer_target, iscc=None, version=None):
     if iscc:
         iscc = pathlib.Path(iscc)
     else:
@@ -38,59 +32,30 @@
 
     build_dir = SOURCE_DIR / "build"
 
-    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,
-        )
+    inno.build_with_pyoxidizer(
+        SOURCE_DIR, build_dir, pyoxidizer_target, iscc, version=version
+    )
 
 
 def build_wix(
+    pyoxidizer_target,
     name=None,
-    pyoxidizer_target=None,
-    python=None,
     version=None,
     sign_sn=None,
     sign_cert=None,
     sign_password=None,
     sign_timestamp_url=None,
-    extra_packages_script=None,
     extra_wxs=None,
     extra_features=None,
     extra_pyoxidizer_vars=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")
-
     kwargs = {
         "source_dir": SOURCE_DIR,
         "version": version,
+        "target_triple": pyoxidizer_target,
+        "extra_pyoxidizer_vars": extra_pyoxidizer_vars,
     }
 
-    if pyoxidizer_target:
-        fn = wix.build_installer_pyoxidizer
-        kwargs["target_triple"] = pyoxidizer_target
-        kwargs["extra_pyoxidizer_vars"] = extra_pyoxidizer_vars
-    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(
             thing.split("=") for thing in extra_wxs.split(",")
@@ -107,7 +72,7 @@
             "timestamp_url": sign_timestamp_url,
         }
 
-    fn(**kwargs)
+    wix.build_installer_pyoxidizer(**kwargs)
 
 
 def get_parser():
@@ -119,9 +84,9 @@
     sp.add_argument(
         "--pyoxidizer-target",
         choices={"i686-pc-windows-msvc", "x86_64-pc-windows-msvc"},
+        required=True,
         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",
@@ -137,9 +102,9 @@
     sp.add_argument(
         "--pyoxidizer-target",
         choices={"i686-pc-windows-msvc", "x86_64-pc-windows-msvc"},
+        required=True,
         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 "
@@ -155,12 +120,6 @@
     )
     sp.add_argument("--version", help="Version string to use")
     sp.add_argument(
-        "--extra-packages-script",
-        help=(
-            "Script to execute to include extra packages in " "py2exe binary."
-        ),
-    )
-    sp.add_argument(
         "--extra-wxs", help="CSV of path_to_wxs_file=working_dir_for_wxs_file"
     )
     sp.add_argument(
--- a/contrib/packaging/hgpackaging/downloads.py	Sat Feb 19 18:42:12 2022 -0700
+++ b/contrib/packaging/hgpackaging/downloads.py	Sat Feb 19 22:13:11 2022 -0700
@@ -25,48 +25,6 @@
         'size': 715086,
         'sha256': '411f94974492fd2ecf52590cb05b1023530aec67e64154a88b1e4ebcd9c28588',
     },
-    'py2exe': {
-        'url': 'https://versaweb.dl.sourceforge.net/project/py2exe/py2exe/0.6.9/py2exe-0.6.9.zip',
-        'size': 149687,
-        'sha256': '6bd383312e7d33eef2e43a5f236f9445e4f3e0f6b16333c6f183ed445c44ddbd',
-        'version': '0.6.9',
-    },
-    # The VC9 CRT merge modules aren't readily available on most systems because
-    # they are only installed as part of a full Visual Studio 2008 install.
-    # While we could potentially extract them from a Visual Studio 2008
-    # installer, it is easier to just fetch them from a known URL.
-    'vc9-crt-x86-msm': {
-        'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/Microsoft_VC90_CRT_x86.msm',
-        'size': 615424,
-        'sha256': '837e887ef31b332feb58156f429389de345cb94504228bb9a523c25a9dd3d75e',
-    },
-    'vc9-crt-x86-msm-policy': {
-        'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/policy_9_0_Microsoft_VC90_CRT_x86.msm',
-        'size': 71168,
-        'sha256': '3fbcf92e3801a0757f36c5e8d304e134a68d5cafd197a6df7734ae3e8825c940',
-    },
-    'vc9-crt-x64-msm': {
-        'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/Microsoft_VC90_CRT_x86_x64.msm',
-        'size': 662528,
-        'sha256': '50d9639b5ad4844a2285269c7551bf5157ec636e32396ddcc6f7ec5bce487a7c',
-    },
-    'vc9-crt-x64-msm-policy': {
-        'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/policy_9_0_Microsoft_VC90_CRT_x86_x64.msm',
-        'size': 71168,
-        'sha256': '0550ea1929b21239134ad3a678c944ba0f05f11087117b6cf0833e7110686486',
-    },
-    'virtualenv': {
-        'url': 'https://files.pythonhosted.org/packages/37/db/89d6b043b22052109da35416abc3c397655e4bd3cff031446ba02b9654fa/virtualenv-16.4.3.tar.gz',
-        'size': 3713208,
-        'sha256': '984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39',
-        'version': '16.4.3',
-    },
-    'wix': {
-        'url': 'https://github.com/wixtoolset/wix3/releases/download/wix3111rtm/wix311-binaries.zip',
-        'size': 34358269,
-        'sha256': '37f0a533b0978a454efb5dc3bd3598becf9660aaf4287e55bf68ca6b527d051d',
-        'version': '3.11.1',
-    },
 }
 
 
--- a/contrib/packaging/hgpackaging/inno.py	Sat Feb 19 18:42:12 2022 -0700
+++ b/contrib/packaging/hgpackaging/inno.py	Sat Feb 19 22:13:11 2022 -0700
@@ -14,29 +14,13 @@
 
 import jinja2
 
-from .py2exe import (
-    build_py2exe,
-    stage_install,
-)
 from .pyoxidizer import create_pyoxidizer_install_layout
 from .util import (
-    find_legacy_vc_runtime_files,
     normalize_windows_version,
     process_install_rules,
     read_version_py,
 )
 
-EXTRA_PACKAGES = {
-    'dulwich',
-    'keyring',
-    'pygments',
-    'win32ctypes',
-}
-
-EXTRA_INCLUDES = {
-    '_curses',
-    '_curses_panel',
-}
 
 EXTRA_INSTALL_RULES = [
     ('contrib/win32/mercurial.ini', 'defaultrc/mercurial.rc'),
@@ -47,80 +31,6 @@
 }
 
 
-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 using py2exe.
-
-    Build files will be placed in ``build_dir``.
-
-    py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
-    for finding the Python 2.7 toolchain. So, we require the environment
-    to already be configured with an active toolchain.
-    """
-    if not iscc_exe.exists():
-        raise Exception('%s does not exist' % iscc_exe)
-
-    vc_x64 = r'\x64' in os.environ.get('LIB', '')
-    arch = 'x64' if vc_x64 else 'x86'
-    inno_build_dir = build_dir / ('inno-py2exe-%s' % arch)
-    staging_dir = inno_build_dir / 'stage'
-
-    requirements_txt = (
-        source_dir / 'contrib' / 'packaging' / 'requirements-windows-py2.txt'
-    )
-
-    inno_build_dir.mkdir(parents=True, exist_ok=True)
-
-    build_py2exe(
-        source_dir,
-        build_dir,
-        python_exe,
-        'inno',
-        requirements_txt,
-        extra_packages=EXTRA_PACKAGES,
-        extra_includes=EXTRA_INCLUDES,
-    )
-
-    # Purge the staging directory for every build so packaging is
-    # pristine.
-    if staging_dir.exists():
-        print('purging %s' % staging_dir)
-        shutil.rmtree(staging_dir)
-
-    # Now assemble all the packaged files into the staging directory.
-    stage_install(source_dir, staging_dir)
-
-    # We also install some extra files.
-    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_legacy_vc_runtime_files(vc_x64):
-        if f.name.endswith('.manifest'):
-            basename = 'Microsoft.VC90.CRT.manifest'
-        else:
-            basename = f.name
-
-        dest_path = staging_dir / basename
-
-        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,
--- a/contrib/packaging/hgpackaging/py2exe.py	Sat Feb 19 18:42:12 2022 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,248 +0,0 @@
-# py2exe.py - Functionality for performing py2exe builds.
-#
-# Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
-#
-# 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 subprocess
-
-from .downloads import download_entry
-from .util import (
-    extract_tar_to_directory,
-    extract_zip_to_directory,
-    process_install_rules,
-    python_exe_info,
-)
-
-
-STAGING_RULES = [
-    ('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/'),
-    ('dist/hg.exe', './'),
-    ('dist/lib/*.dll', 'lib/'),
-    ('dist/lib/*.pyd', 'lib/'),
-    ('dist/lib/library.zip', 'lib/'),
-    ('dist/Microsoft.VC*.CRT.manifest', './'),
-    ('dist/msvc*.dll', './'),
-    ('dist/python*.dll', './'),
-    ('doc/*.html', 'doc/'),
-    ('doc/style.css', 'doc/'),
-    ('mercurial/helptext/**/*.txt', 'helptext/'),
-    ('mercurial/defaultrc/*.rc', 'defaultrc/'),
-    ('mercurial/locale/**/*', 'locale/'),
-    ('mercurial/templates/**/*', 'templates/'),
-    ('COPYING', 'Copying.txt'),
-]
-
-# List of paths to exclude from the staging area.
-STAGING_EXCLUDES = [
-    'doc/hg-ssh.8.html',
-]
-
-
-def build_py2exe(
-    source_dir: pathlib.Path,
-    build_dir: pathlib.Path,
-    python_exe: pathlib.Path,
-    build_name: str,
-    venv_requirements_txt: pathlib.Path,
-    extra_packages=None,
-    extra_excludes=None,
-    extra_dll_excludes=None,
-    extra_packages_script=None,
-    extra_includes=None,
-):
-    """Build Mercurial with py2exe.
-
-    Build files will be placed in ``build_dir``.
-
-    py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
-    for finding the Python 2.7 toolchain. So, we require the environment
-    to already be configured with an active toolchain.
-    """
-    if 'VCINSTALLDIR' not in os.environ:
-        raise Exception(
-            'not running from a Visual C++ build environment; '
-            'execute the "Visual C++ <version> Command Prompt" '
-            'application shortcut or a vcsvarsall.bat file'
-        )
-
-    # Identity x86/x64 and validate the environment matches the Python
-    # architecture.
-    vc_x64 = r'\x64' in os.environ['LIB']
-
-    py_info = python_exe_info(python_exe)
-
-    if vc_x64:
-        if py_info['arch'] != '64bit':
-            raise Exception(
-                'architecture mismatch: Visual C++ environment '
-                'is configured for 64-bit but Python is 32-bit'
-            )
-    else:
-        if py_info['arch'] != '32bit':
-            raise Exception(
-                'architecture mismatch: Visual C++ environment '
-                'is configured for 32-bit but Python is 64-bit'
-            )
-
-    if py_info['py3']:
-        raise Exception('Only Python 2 is currently supported')
-
-    build_dir.mkdir(exist_ok=True)
-
-    gettext_pkg, gettext_entry = download_entry('gettext', build_dir)
-    gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0]
-    virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir)
-    py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir)
-
-    venv_path = build_dir / (
-        'venv-%s-%s' % (build_name, 'x64' if vc_x64 else 'x86')
-    )
-
-    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)
-
-    # This assumes Python 2. We don't need virtualenv on Python 3.
-    virtualenv_src_path = build_dir / (
-        'virtualenv-%s' % virtualenv_entry['version']
-    )
-    virtualenv_py = virtualenv_src_path / 'virtualenv.py'
-
-    if not virtualenv_src_path.exists():
-        extract_tar_to_directory(virtualenv_pkg, build_dir)
-
-    py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version'])
-
-    if not py2exe_source_path.exists():
-        extract_zip_to_directory(py2exe_pkg, build_dir)
-
-    if not venv_path.exists():
-        print('creating virtualenv with dependencies')
-        subprocess.run(
-            [str(python_exe), str(virtualenv_py), str(venv_path)], check=True
-        )
-
-    venv_python = venv_path / 'Scripts' / 'python.exe'
-    venv_pip = venv_path / 'Scripts' / 'pip.exe'
-
-    subprocess.run(
-        [str(venv_pip), 'install', '-r', str(venv_requirements_txt)], check=True
-    )
-
-    # Force distutils to use VC++ settings from environment, which was
-    # validated above.
-    env = dict(os.environ)
-    env['DISTUTILS_USE_SDK'] = '1'
-    env['MSSdk'] = '1'
-
-    if extra_packages_script:
-        more_packages = set(
-            subprocess.check_output(extra_packages_script, cwd=build_dir)
-            .split(b'\0')[-1]
-            .strip()
-            .decode('utf-8')
-            .splitlines()
-        )
-        if more_packages:
-            if not extra_packages:
-                extra_packages = more_packages
-            else:
-                extra_packages |= more_packages
-
-    if extra_packages:
-        env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages))
-        hgext3rd_extras = sorted(
-            e for e in extra_packages if e.startswith('hgext3rd.')
-        )
-        if hgext3rd_extras:
-            env['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'] = ' '.join(hgext3rd_extras)
-    if extra_includes:
-        env['HG_PY2EXE_EXTRA_INCLUDES'] = ' '.join(sorted(extra_includes))
-    if extra_excludes:
-        env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes))
-    if extra_dll_excludes:
-        env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join(
-            sorted(extra_dll_excludes)
-        )
-
-    py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe'
-    if not py2exe_py_path.exists():
-        print('building py2exe')
-        subprocess.run(
-            [str(venv_python), 'setup.py', 'install'],
-            cwd=py2exe_source_path,
-            env=env,
-            check=True,
-        )
-
-    # Register location of msgfmt and other binaries.
-    env['PATH'] = '%s%s%s' % (
-        env['PATH'],
-        os.pathsep,
-        str(gettext_root / 'bin'),
-    )
-
-    print('building Mercurial')
-    subprocess.run(
-        [str(venv_python), 'setup.py', 'py2exe', 'build_doc', '--html'],
-        cwd=str(source_dir),
-        env=env,
-        check=True,
-    )
-
-
-def stage_install(
-    source_dir: pathlib.Path, staging_dir: pathlib.Path, lower_case=False
-):
-    """Copy all files to be installed to a directory.
-
-    This allows packaging to simply walk a directory tree to find source
-    files.
-    """
-    if lower_case:
-        rules = []
-        for source, dest in STAGING_RULES:
-            # Only lower directory names.
-            if '/' in dest:
-                parent, leaf = dest.rsplit('/', 1)
-                dest = '%s/%s' % (parent.lower(), leaf)
-            rules.append((source, dest))
-    else:
-        rules = STAGING_RULES
-
-    process_install_rules(rules, source_dir, staging_dir)
-
-    # Write out a default editor.rc file to configure notepad as the
-    # default editor.
-    with (staging_dir / 'defaultrc' / 'editor.rc').open(
-        'w', encoding='utf-8'
-    ) as fh:
-        fh.write('[ui]\neditor = notepad\n')
-
-    # Purge any files we don't want to be there.
-    for f in STAGING_EXCLUDES:
-        p = staging_dir / f
-        if p.exists():
-            print('removing %s' % p)
-            p.unlink()
--- a/contrib/packaging/hgpackaging/util.py	Sat Feb 19 18:42:12 2022 -0700
+++ b/contrib/packaging/hgpackaging/util.py	Sat Feb 19 22:13:11 2022 -0700
@@ -7,23 +7,15 @@
 
 # no-check-code because Python 3 native.
 
-import distutils.version
-import getpass
 import glob
 import os
 import pathlib
 import re
 import shutil
 import subprocess
-import tarfile
 import zipfile
 
 
-def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path):
-    with tarfile.open(source, 'r') as tf:
-        tf.extractall(dest)
-
-
 def extract_zip_to_directory(source: pathlib.Path, dest: pathlib.Path):
     with zipfile.ZipFile(source, 'r') as zf:
         zf.extractall(dest)
@@ -81,59 +73,6 @@
     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'
-
-    prefix = 'amd64' if x64 else 'x86'
-
-    candidates = sorted(
-        p
-        for p in os.listdir(winsxs)
-        if p.lower().startswith('%s_microsoft.vc90.crt_' % prefix)
-    )
-
-    for p in candidates:
-        print('found candidate VC runtime: %s' % p)
-
-    # Take the newest version.
-    version = candidates[-1]
-
-    d = winsxs / version
-
-    return [
-        d / 'msvcm90.dll',
-        d / 'msvcp90.dll',
-        d / 'msvcr90.dll',
-        winsxs / 'Manifests' / ('%s.manifest' % version),
-    ]
-
-
-def windows_10_sdk_info():
-    """Resolves information about the Windows 10 SDK."""
-
-    base = pathlib.Path(os.environ['ProgramFiles(x86)']) / 'Windows Kits' / '10'
-
-    if not base.is_dir():
-        raise Exception('unable to find Windows 10 SDK at %s' % base)
-
-    # Find the latest version.
-    bin_base = base / 'bin'
-
-    versions = [v for v in os.listdir(bin_base) if v.startswith('10.')]
-    version = sorted(versions, reverse=True)[0]
-
-    bin_version = bin_base / version
-
-    return {
-        'root': base,
-        'version': version,
-        'bin_root': bin_version,
-        'bin_x86': bin_version / 'x86',
-        'bin_x64': bin_version / 'x64',
-    }
-
-
 def normalize_windows_version(version):
     """Normalize Mercurial version string so WiX/Inno accepts it.
 
@@ -194,93 +133,6 @@
     return '.'.join('%d' % x for x in versions[0:4])
 
 
-def find_signtool():
-    """Find signtool.exe from the Windows SDK."""
-    sdk = windows_10_sdk_info()
-
-    for key in ('bin_x64', 'bin_x86'):
-        p = sdk[key] / 'signtool.exe'
-
-        if p.exists():
-            return p
-
-    raise Exception('could not find signtool.exe in Windows 10 SDK')
-
-
-def sign_with_signtool(
-    file_path,
-    description,
-    subject_name=None,
-    cert_path=None,
-    cert_password=None,
-    timestamp_url=None,
-):
-    """Digitally sign a file with signtool.exe.
-
-    ``file_path`` is file to sign.
-    ``description`` is text that goes in the signature.
-
-    The signing certificate can be specified by ``cert_path`` or
-    ``subject_name``. These correspond to the ``/f`` and ``/n`` arguments
-    to signtool.exe, respectively.
-
-    The certificate password can be specified via ``cert_password``. If
-    not provided, you will be prompted for the password.
-
-    ``timestamp_url`` is the URL of a RFC 3161 timestamp server (``/tr``
-    argument to signtool.exe).
-    """
-    if cert_path and subject_name:
-        raise ValueError('cannot specify both cert_path and subject_name')
-
-    while cert_path and not cert_password:
-        cert_password = getpass.getpass('password for %s: ' % cert_path)
-
-    args = [
-        str(find_signtool()),
-        'sign',
-        '/v',
-        '/fd',
-        'sha256',
-        '/d',
-        description,
-    ]
-
-    if cert_path:
-        args.extend(['/f', str(cert_path), '/p', cert_password])
-    elif subject_name:
-        args.extend(['/n', subject_name])
-
-    if timestamp_url:
-        args.extend(['/tr', timestamp_url, '/td', 'sha256'])
-
-    args.append(str(file_path))
-
-    print('signing %s' % file_path)
-    subprocess.run(args, check=True)
-
-
-PRINT_PYTHON_INFO = '''
-import platform; print("%s:%s" % (platform.architecture()[0], platform.python_version()))
-'''.strip()
-
-
-def python_exe_info(python_exe: pathlib.Path):
-    """Obtain information about a Python executable."""
-
-    res = subprocess.check_output([str(python_exe), '-c', PRINT_PYTHON_INFO])
-
-    arch, version = res.decode('utf-8').split(':')
-
-    version = distutils.version.LooseVersion(version)
-
-    return {
-        'arch': arch,
-        'version': version,
-        'py3': version >= distutils.version.LooseVersion('3'),
-    }
-
-
 def process_install_rules(
     rules: list, source_dir: pathlib.Path, dest_dir: pathlib.Path
 ):
--- a/contrib/packaging/hgpackaging/wix.py	Sat Feb 19 18:42:12 2022 -0700
+++ b/contrib/packaging/hgpackaging/wix.py	Sat Feb 19 22:13:11 2022 -0700
@@ -7,376 +7,16 @@
 
 # no-check-code because Python 3 native.
 
-import collections
 import json
 import os
 import pathlib
-import re
 import shutil
-import subprocess
 import typing
-import uuid
-import xml.dom.minidom
 
-from .downloads import download_entry
-from .py2exe import (
-    build_py2exe,
-    stage_install,
-)
 from .pyoxidizer import (
     build_docs_html,
-    create_pyoxidizer_install_layout,
     run_pyoxidizer,
 )
-from .util import (
-    extract_zip_to_directory,
-    normalize_windows_version,
-    process_install_rules,
-    sign_with_signtool,
-)
-
-
-EXTRA_PACKAGES = {
-    'dulwich',
-    'distutils',
-    'keyring',
-    'pygments',
-    'win32ctypes',
-}
-
-EXTRA_INCLUDES = {
-    '_curses',
-    '_curses_panel',
-}
-
-EXTRA_INSTALL_RULES = [
-    ('contrib/packaging/wix/COPYING.rtf', 'COPYING.rtf'),
-    ('contrib/win32/mercurial.ini', 'defaultrc/mercurial.rc'),
-]
-
-STAGING_REMOVE_FILES = [
-    # We use the RTF variant.
-    'copying.txt',
-]
-
-SHORTCUTS = {
-    # hg.1.html'
-    'hg.file.5d3e441c_28d9_5542_afd0_cdd4234f12d5': {
-        'Name': 'Mercurial Command Reference',
-    },
-    # hgignore.5.html
-    'hg.file.5757d8e0_f207_5e10_a2ec_3ba0a062f431': {
-        'Name': 'Mercurial Ignore Files',
-    },
-    # hgrc.5.html
-    'hg.file.92e605fd_1d1a_5dc6_9fc0_5d2998eb8f5e': {
-        'Name': 'Mercurial Configuration Files',
-    },
-}
-
-
-def find_version(source_dir: pathlib.Path):
-    version_py = source_dir / 'mercurial' / '__version__.py'
-
-    with version_py.open('r', encoding='utf-8') as fh:
-        source = fh.read().strip()
-
-    m = re.search('version = b"(.*)"', source)
-    return m.group(1)
-
-
-def ensure_vc90_merge_modules(build_dir):
-    x86 = (
-        download_entry(
-            'vc9-crt-x86-msm',
-            build_dir,
-            local_name='microsoft.vcxx.crt.x86_msm.msm',
-        )[0],
-        download_entry(
-            'vc9-crt-x86-msm-policy',
-            build_dir,
-            local_name='policy.x.xx.microsoft.vcxx.crt.x86_msm.msm',
-        )[0],
-    )
-
-    x64 = (
-        download_entry(
-            'vc9-crt-x64-msm',
-            build_dir,
-            local_name='microsoft.vcxx.crt.x64_msm.msm',
-        )[0],
-        download_entry(
-            'vc9-crt-x64-msm-policy',
-            build_dir,
-            local_name='policy.x.xx.microsoft.vcxx.crt.x64_msm.msm',
-        )[0],
-    )
-    return {
-        'x86': x86,
-        'x64': x64,
-    }
-
-
-def run_candle(wix, cwd, wxs, source_dir, defines=None):
-    args = [
-        str(wix / 'candle.exe'),
-        '-nologo',
-        str(wxs),
-        '-dSourceDir=%s' % source_dir,
-    ]
-
-    if defines:
-        args.extend('-d%s=%s' % define for define in sorted(defines.items()))
-
-    subprocess.run(args, cwd=str(cwd), check=True)
-
-
-def make_files_xml(staging_dir: pathlib.Path, is_x64) -> str:
-    """Create XML string listing every file to be installed."""
-
-    # We derive GUIDs from a deterministic file path identifier.
-    # We shoehorn the name into something that looks like a URL because
-    # the UUID namespaces are supposed to work that way (even though
-    # the input data probably is never validated).
-
-    doc = xml.dom.minidom.parseString(
-        '<?xml version="1.0" encoding="utf-8"?>'
-        '<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">'
-        '</Wix>'
-    )
-
-    # Assemble the install layout by directory. This makes it easier to
-    # emit XML, since each directory has separate entities.
-    manifest = collections.defaultdict(dict)
-
-    for root, dirs, files in os.walk(staging_dir):
-        dirs.sort()
-
-        root = pathlib.Path(root)
-        rel_dir = root.relative_to(staging_dir)
-
-        for i in range(len(rel_dir.parts)):
-            parent = '/'.join(rel_dir.parts[0 : i + 1])
-            manifest.setdefault(parent, {})
-
-        for f in sorted(files):
-            full = root / f
-            manifest[str(rel_dir).replace('\\', '/')][full.name] = full
-
-    component_groups = collections.defaultdict(list)
-
-    # Now emit a <Fragment> for each directory.
-    # Each directory is composed of a <DirectoryRef> pointing to its parent
-    # and defines child <Directory>'s and a <Component> with all the files.
-    for dir_name, entries in sorted(manifest.items()):
-        # The directory id is derived from the path. But the root directory
-        # is special.
-        if dir_name == '.':
-            parent_directory_id = 'INSTALLDIR'
-        else:
-            parent_directory_id = 'hg.dir.%s' % dir_name.replace(
-                '/', '.'
-            ).replace('-', '_')
-
-        fragment = doc.createElement('Fragment')
-        directory_ref = doc.createElement('DirectoryRef')
-        directory_ref.setAttribute('Id', parent_directory_id)
-
-        # Add <Directory> entries for immediate children directories.
-        for possible_child in sorted(manifest.keys()):
-            if (
-                dir_name == '.'
-                and '/' not in possible_child
-                and possible_child != '.'
-            ):
-                child_directory_id = ('hg.dir.%s' % possible_child).replace(
-                    '-', '_'
-                )
-                name = possible_child
-            else:
-                if not possible_child.startswith('%s/' % dir_name):
-                    continue
-                name = possible_child[len(dir_name) + 1 :]
-                if '/' in name:
-                    continue
-
-                child_directory_id = 'hg.dir.%s' % possible_child.replace(
-                    '/', '.'
-                ).replace('-', '_')
-
-            directory = doc.createElement('Directory')
-            directory.setAttribute('Id', child_directory_id)
-            directory.setAttribute('Name', name)
-            directory_ref.appendChild(directory)
-
-        # Add <Component>s for files in this directory.
-        for rel, source_path in sorted(entries.items()):
-            if dir_name == '.':
-                full_rel = rel
-            else:
-                full_rel = '%s/%s' % (dir_name, rel)
-
-            component_unique_id = (
-                'https://www.mercurial-scm.org/wix-installer/0/component/%s'
-                % full_rel
-            )
-            component_guid = uuid.uuid5(uuid.NAMESPACE_URL, component_unique_id)
-            component_id = 'hg.component.%s' % str(component_guid).replace(
-                '-', '_'
-            )
-
-            component = doc.createElement('Component')
-
-            component.setAttribute('Id', component_id)
-            component.setAttribute('Guid', str(component_guid).upper())
-            component.setAttribute('Win64', 'yes' if is_x64 else 'no')
-
-            # Assign this component to a top-level group.
-            if dir_name == '.':
-                component_groups['ROOT'].append(component_id)
-            elif '/' in dir_name:
-                component_groups[dir_name[0 : dir_name.index('/')]].append(
-                    component_id
-                )
-            else:
-                component_groups[dir_name].append(component_id)
-
-            unique_id = (
-                'https://www.mercurial-scm.org/wix-installer/0/%s' % full_rel
-            )
-            file_guid = uuid.uuid5(uuid.NAMESPACE_URL, unique_id)
-
-            # IDs have length limits. So use GUID to derive them.
-            file_guid_normalized = str(file_guid).replace('-', '_')
-            file_id = 'hg.file.%s' % file_guid_normalized
-
-            file_element = doc.createElement('File')
-            file_element.setAttribute('Id', file_id)
-            file_element.setAttribute('Source', str(source_path))
-            file_element.setAttribute('KeyPath', 'yes')
-            file_element.setAttribute('ReadOnly', 'yes')
-
-            component.appendChild(file_element)
-            directory_ref.appendChild(component)
-
-        fragment.appendChild(directory_ref)
-        doc.documentElement.appendChild(fragment)
-
-    for group, component_ids in sorted(component_groups.items()):
-        fragment = doc.createElement('Fragment')
-        component_group = doc.createElement('ComponentGroup')
-        component_group.setAttribute('Id', 'hg.group.%s' % group)
-
-        for component_id in component_ids:
-            component_ref = doc.createElement('ComponentRef')
-            component_ref.setAttribute('Id', component_id)
-            component_group.appendChild(component_ref)
-
-        fragment.appendChild(component_group)
-        doc.documentElement.appendChild(fragment)
-
-    # Add <Shortcut> to files that have it defined.
-    for file_id, metadata in sorted(SHORTCUTS.items()):
-        els = doc.getElementsByTagName('File')
-        els = [el for el in els if el.getAttribute('Id') == file_id]
-
-        if not els:
-            raise Exception('could not find File[Id=%s]' % file_id)
-
-        for el in els:
-            shortcut = doc.createElement('Shortcut')
-            shortcut.setAttribute('Id', 'hg.shortcut.%s' % file_id)
-            shortcut.setAttribute('Directory', 'ProgramMenuDir')
-            shortcut.setAttribute('Icon', 'hgIcon.ico')
-            shortcut.setAttribute('IconIndex', '0')
-            shortcut.setAttribute('Advertise', 'yes')
-            for k, v in sorted(metadata.items()):
-                shortcut.setAttribute(k, v)
-
-            el.appendChild(shortcut)
-
-    return doc.toprettyxml()
-
-
-def build_installer_py2exe(
-    source_dir: pathlib.Path,
-    python_exe: pathlib.Path,
-    msi_name='mercurial',
-    version=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 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.
-    ``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
-    should be included in the exe.
-    ``extra_wxs`` is a dict of {wxs_name: working_dir_for_wxs_build}.
-    ``extra_features`` is a list of additional named Features to include in
-    the build. These must match Feature names in one of the wxs scripts.
-    """
-    arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86'
-
-    hg_build_dir = source_dir / 'build'
-
-    requirements_txt = (
-        source_dir / 'contrib' / 'packaging' / 'requirements-windows-py2.txt'
-    )
-
-    build_py2exe(
-        source_dir,
-        hg_build_dir,
-        python_exe,
-        'wix',
-        requirements_txt,
-        extra_packages=EXTRA_PACKAGES,
-        extra_packages_script=extra_packages_script,
-        extra_includes=EXTRA_INCLUDES,
-    )
-
-    build_dir = hg_build_dir / ('wix-%s' % arch)
-    staging_dir = build_dir / 'stage'
-
-    build_dir.mkdir(exist_ok=True)
-
-    # Purge the staging directory for every build so packaging is pristine.
-    if staging_dir.exists():
-        print('purging %s' % staging_dir)
-        shutil.rmtree(staging_dir)
-
-    stage_install(source_dir, staging_dir, lower_case=True)
-
-    # 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=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(
@@ -454,133 +94,3 @@
     return {
         "msi_path": dist_path,
     }
-
-
-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)
-
-    if python2:
-        ensure_vc90_merge_modules(build_dir)
-
-    source_build_rel = pathlib.Path(os.path.relpath(source_dir, build_dir))
-
-    defines = {'Platform': arch}
-
-    # Derive a .wxs file with the staged files.
-    manifest_wxs = build_dir / 'stage.wxs'
-    with manifest_wxs.open('w', encoding='utf-8') as fh:
-        fh.write(make_files_xml(staging_dir, is_x64=arch == 'x64'))
-
-    run_candle(wix_path, build_dir, manifest_wxs, staging_dir, defines=defines)
-
-    for source, rel_path in sorted((extra_wxs or {}).items()):
-        run_candle(wix_path, build_dir, source, rel_path, defines=defines)
-
-    source = wix_dir / 'mercurial.wxs'
-    defines['Version'] = version
-    defines['Comments'] = 'Installs Mercurial version %s' % version
-
-    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)
-
-    run_candle(wix_path, build_dir, source, source_build_rel, defines=defines)
-
-    msi_path = (
-        source_dir
-        / 'dist'
-        / ('%s-%s-%s%s.msi' % (msi_name, orig_version, arch, suffix))
-    )
-
-    args = [
-        str(wix_path / 'light.exe'),
-        '-nologo',
-        '-ext',
-        'WixUIExtension',
-        '-sw1076',
-        '-spdb',
-        '-o',
-        str(msi_path),
-    ]
-
-    for source, rel_path in sorted((extra_wxs or {}).items()):
-        assert source.endswith('.wxs')
-        source = os.path.basename(source)
-        args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
-
-    args.extend(
-        [
-            str(build_dir / 'stage.wixobj'),
-            str(build_dir / 'mercurial.wixobj'),
-        ]
-    )
-
-    subprocess.run(args, cwd=str(source_dir), check=True)
-
-    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,
-    }
--- a/contrib/packaging/requirements-windows-py2.txt	Sat Feb 19 18:42:12 2022 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-#
-# This file is autogenerated by pip-compile
-# To update, run:
-#
-#    pip-compile --generate-hashes --output-file=contrib/packaging/requirements-windows-py2.txt contrib/packaging/requirements-windows.txt.in
-#
-certifi==2021.5.30 \
-    --hash=sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee \
-    --hash=sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8 \
-    # via dulwich
-configparser==4.0.2 \
-    --hash=sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c \
-    --hash=sha256:c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df \
-    # via entrypoints
-docutils==0.16 \
-    --hash=sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af \
-    --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc \
-    # via -r contrib/packaging/requirements-windows.txt.in
-dulwich==0.19.16 ; python_version <= "2.7" \
-    --hash=sha256:10699277c6268d0c16febe141a5b1c1a6e9744f3144c2d2de1706f4b1adafe63 \
-    --hash=sha256:267160904e9a1cb6c248c5efc53597a35d038ecc6f60bdc4546b3053bed11982 \
-    --hash=sha256:4e3aba5e4844e7c700721c1fc696987ea820ee3528a03604dc4e74eff4196826 \
-    --hash=sha256:60bb2c2c92f5025c1b53a556304008f0f624c98ae36f22d870e056b2d4236c11 \
-    --hash=sha256:dddae02d372fc3b5cfb0046d0f62246ef281fa0c088df7601ab5916607add94b \
-    --hash=sha256:f00d132082b8fcc2eb0d722abc773d4aeb5558c1475d7edd1f0f571146c29db9 \
-    --hash=sha256:f74561c448bfb6f04c07de731c1181ae4280017f759b0bb04fa5770aa84ca850 \
-    # via -r contrib/packaging/requirements-windows.txt.in
-entrypoints==0.3 \
-    --hash=sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19 \
-    --hash=sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451 \
-    # via keyring
-keyring==18.0.1 \
-    --hash=sha256:67d6cc0132bd77922725fae9f18366bb314fd8f95ff4d323a4df41890a96a838 \
-    --hash=sha256:7b29ebfcf8678c4da531b2478a912eea01e80007e5ddca9ee0c7038cb3489ec6 \
-    # via -r contrib/packaging/requirements-windows.txt.in
-pygments==2.5.2 \
-    --hash=sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b \
-    --hash=sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe \
-    # via -r contrib/packaging/requirements-windows.txt.in
-pywin32-ctypes==0.2.0 \
-    --hash=sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942 \
-    --hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98 \
-    # via -r contrib/packaging/requirements-windows.txt.in, keyring
-urllib3==1.25.11 \
-    --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \
-    --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e \
-    # via dulwich
-windows-curses==2.1.0 \
-    --hash=sha256:261fde5680d1ce4ce116908996b9a3cfb0ffb03ea68d42240f62b56a9fa6af2c \
-    --hash=sha256:66034dc9a705d87308cc9ea90836f4ee60008a1d5e2c1d34ace627f60268158b \
-    --hash=sha256:669caad3ae16faf2d201d7ab3b8af418a2fd074d8a39d60ca26f3acb34b6afe5 \
-    --hash=sha256:73bd3eebccfda55330783f165151de115bfa238d1332f0b2e224b550d6187840 \
-    --hash=sha256:89a6d973f88cfe49b41ea80164dcbec209d296e0cec34a02002578b0bf464a64 \
-    --hash=sha256:8ba7c000d7ffa5452bbd0966b96e69261e4f117ebe510aeb8771a9650197b7f0 \
-    --hash=sha256:97084c6b37b1534f6a28a514d521dfae402f77dcbad42b14ee32e8d5bdc13648 \
-    --hash=sha256:9e474a181f96d60429a4766145628264e60b72e7715876f9135aeb2e842f9433 \
-    --hash=sha256:cfe64c30807c146ef8d094412f90f2a2c81ad6aefff3ebfe8e37aabe2f801303 \
-    --hash=sha256:ff8c67f74b88944d99fa9d22971c05c335bc74f149120f0a69340c2c3a595497 \
-    # via -r contrib/packaging/requirements-windows.txt.in
--- a/tests/test-check-code.t	Sat Feb 19 18:42:12 2022 -0700
+++ b/tests/test-check-code.t	Sat Feb 19 22:13:11 2022 -0700
@@ -27,7 +27,6 @@
   Skipping contrib/packaging/hgpackaging/cli.py it has no-che?k-code (glob)
   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)