contrib/packaging/hgpackaging/wix.py
changeset 41952 b83de9150c1c
parent 41924 9d4ae5044b4c
child 41956 39f65c506899
--- a/contrib/packaging/hgpackaging/wix.py	Wed Mar 13 10:51:40 2019 -0700
+++ b/contrib/packaging/hgpackaging/wix.py	Thu Mar 14 18:14:33 2019 -0700
@@ -1,239 +1,239 @@
-# wix.py - WiX installer functionality
-#
-# 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 re
-import subprocess
-
-from .downloads import (
-    download_entry,
-)
-from .py2exe import (
-    build_py2exe,
-)
-from .util import (
-    extract_zip_to_directory,
-    sign_with_signtool,
-)
-
-
-SUPPORT_WXS = [
-    ('contrib.wxs', r'contrib'),
-    ('dist.wxs', r'dist'),
-    ('doc.wxs', r'doc'),
-    ('help.wxs', r'mercurial\help'),
-    ('i18n.wxs', r'i18n'),
-    ('locale.wxs', r'mercurial\locale'),
-    ('templates.wxs', r'mercurial\templates'),
-]
-
-
-EXTRA_PACKAGES = {
-    'distutils',
-    'pygments',
-}
-
-
-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 normalize_version(version):
-    """Normalize Mercurial version string so WiX accepts it.
-
-    Version strings have to be numeric X.Y.Z.
-    """
-
-    if '+' in version:
-        version, extra = version.split('+', 1)
-    else:
-        extra = None
-
-    # 4.9rc0
-    if version[:-1].endswith('rc'):
-        version = version[:-3]
-
-    versions = [int(v) for v in version.split('.')]
-    while len(versions) < 3:
-        versions.append(0)
-
-    major, minor, build = versions[:3]
-
-    if extra:
-        # <commit count>-<hash>+<date>
-        build = int(extra.split('-')[0])
-
-    return '.'.join('%d' % x for x in (major, minor, build))
-
-
-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_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 build_installer(source_dir: pathlib.Path, python_exe: pathlib.Path,
-                    msi_name='mercurial', version=None, post_build_fn=None):
-    """Build a WiX MSI installer.
-
-    ``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.
-    """
-    arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86'
-
-    hg_build_dir = source_dir / 'build'
-    dist_dir = source_dir / 'dist'
-
-    requirements_txt = (source_dir / 'contrib' / 'packaging' /
-                        'wix' / 'requirements.txt')
-
-    build_py2exe(source_dir, hg_build_dir,
-                 python_exe, 'wix', requirements_txt,
-                 extra_packages=EXTRA_PACKAGES)
-
-    version = version or normalize_version(find_version(source_dir))
-    print('using version string: %s' % version)
-
-    if post_build_fn:
-        post_build_fn(source_dir, hg_build_dir, dist_dir, version)
-
-    build_dir = hg_build_dir / ('wix-%s' % arch)
-
-    build_dir.mkdir(exist_ok=True)
-
-    wix_pkg, wix_entry = download_entry('wix', hg_build_dir)
-    wix_path = hg_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)
-
-    source_build_rel = pathlib.Path(os.path.relpath(source_dir, build_dir))
-
-    defines = {'Platform': arch}
-
-    for wxs, rel_path in SUPPORT_WXS:
-        wxs = source_dir / 'contrib' / 'packaging' / 'wix' / wxs
-        wxs_source_dir = source_dir / rel_path
-        run_candle(wix_path, build_dir, wxs, wxs_source_dir, defines=defines)
-
-    source = source_dir / 'contrib' / 'packaging' / 'wix' / 'mercurial.wxs'
-    defines['Version'] = version
-    defines['Comments'] = 'Installs Mercurial version %s' % version
-    defines['VCRedistSrcDir'] = str(hg_build_dir)
-
-    run_candle(wix_path, build_dir, source, source_build_rel, defines=defines)
-
-    msi_path = source_dir / 'dist' / (
-        '%s-%s-%s.msi' % (msi_name, version, arch))
-
-    args = [
-        str(wix_path / 'light.exe'),
-        '-nologo',
-        '-ext', 'WixUIExtension',
-        '-sw1076',
-        '-spdb',
-        '-o', str(msi_path),
-    ]
-
-    for source, rel_path in SUPPORT_WXS:
-        assert source.endswith('.wxs')
-        args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
-
-    args.append(str(build_dir / 'mercurial.wixobj'))
-
-    subprocess.run(args, cwd=str(source_dir), check=True)
-
-    print('%s created' % msi_path)
-
-    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):
-    """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)
-
-    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)
+# wix.py - WiX installer functionality
+#
+# 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 re
+import subprocess
+
+from .downloads import (
+    download_entry,
+)
+from .py2exe import (
+    build_py2exe,
+)
+from .util import (
+    extract_zip_to_directory,
+    sign_with_signtool,
+)
+
+
+SUPPORT_WXS = [
+    ('contrib.wxs', r'contrib'),
+    ('dist.wxs', r'dist'),
+    ('doc.wxs', r'doc'),
+    ('help.wxs', r'mercurial\help'),
+    ('i18n.wxs', r'i18n'),
+    ('locale.wxs', r'mercurial\locale'),
+    ('templates.wxs', r'mercurial\templates'),
+]
+
+
+EXTRA_PACKAGES = {
+    'distutils',
+    'pygments',
+}
+
+
+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 normalize_version(version):
+    """Normalize Mercurial version string so WiX accepts it.
+
+    Version strings have to be numeric X.Y.Z.
+    """
+
+    if '+' in version:
+        version, extra = version.split('+', 1)
+    else:
+        extra = None
+
+    # 4.9rc0
+    if version[:-1].endswith('rc'):
+        version = version[:-3]
+
+    versions = [int(v) for v in version.split('.')]
+    while len(versions) < 3:
+        versions.append(0)
+
+    major, minor, build = versions[:3]
+
+    if extra:
+        # <commit count>-<hash>+<date>
+        build = int(extra.split('-')[0])
+
+    return '.'.join('%d' % x for x in (major, minor, build))
+
+
+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_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 build_installer(source_dir: pathlib.Path, python_exe: pathlib.Path,
+                    msi_name='mercurial', version=None, post_build_fn=None):
+    """Build a WiX MSI installer.
+
+    ``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.
+    """
+    arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86'
+
+    hg_build_dir = source_dir / 'build'
+    dist_dir = source_dir / 'dist'
+
+    requirements_txt = (source_dir / 'contrib' / 'packaging' /
+                        'wix' / 'requirements.txt')
+
+    build_py2exe(source_dir, hg_build_dir,
+                 python_exe, 'wix', requirements_txt,
+                 extra_packages=EXTRA_PACKAGES)
+
+    version = version or normalize_version(find_version(source_dir))
+    print('using version string: %s' % version)
+
+    if post_build_fn:
+        post_build_fn(source_dir, hg_build_dir, dist_dir, version)
+
+    build_dir = hg_build_dir / ('wix-%s' % arch)
+
+    build_dir.mkdir(exist_ok=True)
+
+    wix_pkg, wix_entry = download_entry('wix', hg_build_dir)
+    wix_path = hg_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)
+
+    source_build_rel = pathlib.Path(os.path.relpath(source_dir, build_dir))
+
+    defines = {'Platform': arch}
+
+    for wxs, rel_path in SUPPORT_WXS:
+        wxs = source_dir / 'contrib' / 'packaging' / 'wix' / wxs
+        wxs_source_dir = source_dir / rel_path
+        run_candle(wix_path, build_dir, wxs, wxs_source_dir, defines=defines)
+
+    source = source_dir / 'contrib' / 'packaging' / 'wix' / 'mercurial.wxs'
+    defines['Version'] = version
+    defines['Comments'] = 'Installs Mercurial version %s' % version
+    defines['VCRedistSrcDir'] = str(hg_build_dir)
+
+    run_candle(wix_path, build_dir, source, source_build_rel, defines=defines)
+
+    msi_path = source_dir / 'dist' / (
+        '%s-%s-%s.msi' % (msi_name, version, arch))
+
+    args = [
+        str(wix_path / 'light.exe'),
+        '-nologo',
+        '-ext', 'WixUIExtension',
+        '-sw1076',
+        '-spdb',
+        '-o', str(msi_path),
+    ]
+
+    for source, rel_path in SUPPORT_WXS:
+        assert source.endswith('.wxs')
+        args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
+
+    args.append(str(build_dir / 'mercurial.wixobj'))
+
+    subprocess.run(args, cwd=str(source_dir), check=True)
+
+    print('%s created' % msi_path)
+
+    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):
+    """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)
+
+    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)