Changeset View
Changeset View
Standalone View
Standalone View
contrib/packaging/hgpackaging/wix.py
# wix.py - WiX installer functionality | # wix.py - WiX installer functionality | ||||
# | # | ||||
# Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | ||||
# | # | ||||
# This software may be used and distributed according to the terms of the | # This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | # GNU General Public License version 2 or any later version. | ||||
# no-check-code because Python 3 native. | # no-check-code because Python 3 native. | ||||
import os | import os | ||||
import pathlib | import pathlib | ||||
import re | import re | ||||
import subprocess | import subprocess | ||||
import tempfile | import tempfile | ||||
import typing | |||||
import xml.dom.minidom | import xml.dom.minidom | ||||
from .downloads import ( | from .downloads import ( | ||||
download_entry, | download_entry, | ||||
) | ) | ||||
from .py2exe import ( | from .py2exe import ( | ||||
build_py2exe, | build_py2exe, | ||||
) | ) | ||||
▲ Show 20 Lines • Show All 150 Lines • ▼ Show 20 Line(s) | for p in sorted(lib_dir.iterdir()): | ||||
f.setAttribute('Name', p.name) | f.setAttribute('Name', p.name) | ||||
component.appendChild(f) | component.appendChild(f) | ||||
return doc.toprettyxml() | return doc.toprettyxml() | ||||
def build_installer(source_dir: pathlib.Path, python_exe: pathlib.Path, | def build_installer(source_dir: pathlib.Path, python_exe: pathlib.Path, | ||||
msi_name='mercurial', version=None, post_build_fn=None, | msi_name='mercurial', version=None, post_build_fn=None, | ||||
extra_prebuild_script=None): | extra_prebuild_script=None, | ||||
extra_wxs:typing.Optional[typing.Dict[str,str]]=None): | |||||
"""Build a WiX MSI installer. | """Build a WiX MSI installer. | ||||
``source_dir`` is the path to the Mercurial source tree to use. | ``source_dir`` is the path to the Mercurial source tree to use. | ||||
``arch`` is the target architecture. either ``x86`` or ``x64``. | ``arch`` is the target architecture. either ``x86`` or ``x64``. | ||||
``python_exe`` is the path to the Python executable to use/bundle. | ``python_exe`` is the path to the Python executable to use/bundle. | ||||
``version`` is the Mercurial version string. If not defined, | ``version`` is the Mercurial version string. If not defined, | ||||
``mercurial/__version__.py`` will be consulted. | ``mercurial/__version__.py`` will be consulted. | ||||
``post_build_fn`` is a callable that will be called after building | ``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 | 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 | signing. It is passed the paths to the Mercurial source, build, and | ||||
dist directories and the resolved Mercurial version. | dist directories and the resolved Mercurial version. | ||||
``extra_wxs`` is a dict of {wxs_name: working_dir_for_wxs_build}. | |||||
""" | """ | ||||
arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86' | arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86' | ||||
hg_build_dir = source_dir / 'build' | hg_build_dir = source_dir / 'build' | ||||
dist_dir = source_dir / 'dist' | dist_dir = source_dir / 'dist' | ||||
wix_dir = source_dir / 'contrib' / 'packaging' / 'wix' | wix_dir = source_dir / 'contrib' / 'packaging' / 'wix' | ||||
requirements_txt = wix_dir / 'requirements.txt' | requirements_txt = wix_dir / 'requirements.txt' | ||||
Show All 25 Lines | def build_installer(source_dir: pathlib.Path, python_exe: pathlib.Path, | ||||
defines = {'Platform': arch} | defines = {'Platform': arch} | ||||
for wxs, rel_path in SUPPORT_WXS: | for wxs, rel_path in SUPPORT_WXS: | ||||
wxs = wix_dir / wxs | wxs = wix_dir / wxs | ||||
wxs_source_dir = source_dir / rel_path | wxs_source_dir = source_dir / rel_path | ||||
run_candle(wix_path, build_dir, wxs, wxs_source_dir, defines=defines) | run_candle(wix_path, build_dir, wxs, wxs_source_dir, defines=defines) | ||||
for source, rel_path in sorted((extra_wxs or {}).items()): | |||||
run_candle(wix_path, build_dir, source, rel_path, defines=defines) | |||||
# candle.exe doesn't like when we have an open handle on the file. | # candle.exe doesn't like when we have an open handle on the file. | ||||
# So use TemporaryDirectory() instead of NamedTemporaryFile(). | # So use TemporaryDirectory() instead of NamedTemporaryFile(). | ||||
with tempfile.TemporaryDirectory() as td: | with tempfile.TemporaryDirectory() as td: | ||||
td = pathlib.Path(td) | td = pathlib.Path(td) | ||||
tf = td / 'library.wxs' | tf = td / 'library.wxs' | ||||
with tf.open('w') as fh: | with tf.open('w') as fh: | ||||
fh.write(make_libraries_xml(wix_dir, dist_dir)) | fh.write(make_libraries_xml(wix_dir, dist_dir)) | ||||
Show All 18 Lines | args = [ | ||||
'-spdb', | '-spdb', | ||||
'-o', str(msi_path), | '-o', str(msi_path), | ||||
] | ] | ||||
for source, rel_path in SUPPORT_WXS: | for source, rel_path in SUPPORT_WXS: | ||||
assert source.endswith('.wxs') | assert source.endswith('.wxs') | ||||
args.append(str(build_dir / ('%s.wixobj' % source[:-4]))) | args.append(str(build_dir / ('%s.wixobj' % source[:-4]))) | ||||
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([ | args.extend([ | ||||
str(build_dir / 'library.wixobj'), | str(build_dir / 'library.wixobj'), | ||||
str(build_dir / 'mercurial.wixobj'), | str(build_dir / 'mercurial.wixobj'), | ||||
]) | ]) | ||||
subprocess.run(args, cwd=str(source_dir), check=True) | subprocess.run(args, cwd=str(source_dir), check=True) | ||||
print('%s created' % msi_path) | print('%s created' % msi_path) | ||||
return { | return { | ||||
'msi_path': msi_path, | 'msi_path': msi_path, | ||||
} | } | ||||
def build_signed_installer(source_dir: pathlib.Path, python_exe: pathlib.Path, | def build_signed_installer(source_dir: pathlib.Path, python_exe: pathlib.Path, | ||||
name: str, version=None, subject_name=None, | name: str, version=None, subject_name=None, | ||||
cert_path=None, cert_password=None, | cert_path=None, cert_password=None, | ||||
timestamp_url=None, extra_prebuild_script=None): | timestamp_url=None, extra_prebuild_script=None, | ||||
extra_wxs=None): | |||||
"""Build an installer with signed executables.""" | """Build an installer with signed executables.""" | ||||
post_build_fn = make_post_build_signing_fn( | post_build_fn = make_post_build_signing_fn( | ||||
name, | name, | ||||
subject_name=subject_name, | subject_name=subject_name, | ||||
cert_path=cert_path, | cert_path=cert_path, | ||||
cert_password=cert_password, | cert_password=cert_password, | ||||
timestamp_url=timestamp_url) | timestamp_url=timestamp_url) | ||||
info = build_installer(source_dir, python_exe=python_exe, | info = build_installer(source_dir, python_exe=python_exe, | ||||
msi_name=name.lower(), version=version, | msi_name=name.lower(), version=version, | ||||
post_build_fn=post_build_fn, | post_build_fn=post_build_fn, | ||||
extra_prebuild_script=extra_prebuild_script) | extra_prebuild_script=extra_prebuild_script, | ||||
extra_wxs=extra_wxs) | |||||
description = '%s %s' % (name, version) | description = '%s %s' % (name, version) | ||||
sign_with_signtool(info['msi_path'], description, | sign_with_signtool(info['msi_path'], description, | ||||
subject_name=subject_name, cert_path=cert_path, | subject_name=subject_name, cert_path=cert_path, | ||||
cert_password=cert_password, timestamp_url=timestamp_url) | cert_password=cert_password, timestamp_url=timestamp_url) |