##// END OF EJS Templates
copies: move from a copy on branchpoint to a copy on write approach...
copies: move from a copy on branchpoint to a copy on write approach Before this changes, any branch points results in a copy of the dictionary containing the copy information. This can be very costly for branchy history with few rename information. Instead, we take a "copy on write" approach. Copying the input data only when we are about to update them. In practice we where already doing the copying in half of these case (because `_chain` makes a copy), so we don't add a significant cost here even in the linear case. However the speed up in branchy case is very significant. Here are some timing on the pypy repository. revision: large amount; added files: large amount; rename small amount; c3b14617fbd7 9ba6ab77fd29 before: ! wall 1.399863 comb 1.400000 user 1.370000 sys 0.030000 (median of 10) after: ! wall 0.766453 comb 0.770000 user 0.750000 sys 0.020000 (median of 11) revision: large amount; added files: small amount; rename small amount; c3b14617fbd7 f650a9b140d2 before: ! wall 1.876748 comb 1.890000 user 1.870000 sys 0.020000 (median of 10) after: ! wall 1.167223 comb 1.170000 user 1.150000 sys 0.020000 (median of 10) revision: large amount; added files: large amount; rename large amount; 08ea3258278e d9fa043f30c0 before: ! wall 0.242457 comb 0.240000 user 0.240000 sys 0.000000 (median of 39) after: ! wall 0.211476 comb 0.210000 user 0.210000 sys 0.000000 (median of 45) revision: small amount; added files: large amount; rename large amount; df6f7a526b60 a83dc6a2d56f before: ! wall 0.013193 comb 0.020000 user 0.020000 sys 0.000000 (median of 224) after: ! wall 0.013290 comb 0.010000 user 0.010000 sys 0.000000 (median of 222) revision: small amount; added files: large amount; rename small amount; 4aa4e1f8e19a 169138063d63 before: ! wall 0.001673 comb 0.000000 user 0.000000 sys 0.000000 (median of 1000) after: ! wall 0.001677 comb 0.000000 user 0.000000 sys 0.000000 (median of 1000) revision: small amount; added files: small amount; rename small amount; 4bc173b045a6 964879152e2e before: ! wall 0.000119 comb 0.000000 user 0.000000 sys 0.000000 (median of 8023) after: ! wall 0.000119 comb 0.000000 user 0.000000 sys 0.000000 (median of 7997) revision: medium amount; added files: large amount; rename medium amount; c95f1ced15f2 2c68e87c3efe before: ! wall 0.201898 comb 0.210000 user 0.200000 sys 0.010000 (median of 48) after: ! wall 0.167415 comb 0.170000 user 0.160000 sys 0.010000 (median of 58) revision: medium amount; added files: medium amount; rename small amount; d343da0c55a8 d7746d32bf9d before: ! wall 0.036820 comb 0.040000 user 0.040000 sys 0.000000 (median of 100) after: ! wall 0.035797 comb 0.040000 user 0.040000 sys 0.000000 (median of 100) The extra cost in the linear case can be reclaimed later with some extra logic. Differential Revision: https://phab.mercurial-scm.org/D7124

File last commit:

r43346:2372284d default
r43594:ffd04bc9 default
Show More
wix.py
379 lines | 10.3 KiB | text/x-python | PythonLexer
Gregory Szorc
packaging: convert files to LF...
r42118 # 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
Gregory Szorc
wix: autogenerate wxs file for library files...
r42123 import tempfile
Augie Fackler
wix: add support for additional wxs files...
r42215 import typing
Gregory Szorc
wix: autogenerate wxs file for library files...
r42123 import xml.dom.minidom
Gregory Szorc
packaging: convert files to LF...
r42118
Augie Fackler
formatting: blacken the codebase...
r43346 from .downloads import download_entry
from .py2exe import build_py2exe
Gregory Szorc
packaging: convert files to LF...
r42118 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 = (
Augie Fackler
formatting: blacken the codebase...
r43346 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],
Gregory Szorc
packaging: convert files to LF...
r42118 )
x64 = (
Augie Fackler
formatting: blacken the codebase...
r43346 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],
Gregory Szorc
packaging: convert files to LF...
r42118 )
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)
Augie Fackler
formatting: blacken the codebase...
r43346 def make_post_build_signing_fn(
name,
subject_name=None,
cert_path=None,
cert_password=None,
timestamp_url=None,
):
Gregory Szorc
packaging: convert files to LF...
r42118 """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)
Augie Fackler
formatting: blacken the codebase...
r43346 sign_with_signtool(
dist_dir / 'hg.exe',
description,
subject_name=subject_name,
cert_path=cert_path,
cert_password=cert_password,
timestamp_url=timestamp_url,
)
Gregory Szorc
packaging: convert files to LF...
r42118
return post_build_sign
Gregory Szorc
wix: autogenerate wxs file for library files...
r42123 LIBRARIES_XML = '''
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?include {wix_dir}/guids.wxi ?>
<?include {wix_dir}/defines.wxi ?>
<Fragment>
<DirectoryRef Id="INSTALLDIR" FileSource="$(var.SourceDir)">
<Directory Id="libdir" Name="lib" FileSource="$(var.SourceDir)/lib">
<Component Id="libOutput" Guid="$(var.lib.guid)" Win64='$(var.IsX64)'>
</Component>
</Directory>
</DirectoryRef>
</Fragment>
</Wix>
'''.lstrip()
def make_libraries_xml(wix_dir: pathlib.Path, dist_dir: pathlib.Path):
"""Make XML data for library components WXS."""
# We can't use ElementTree because it doesn't handle the
# <?include ?> directives.
doc = xml.dom.minidom.parseString(
Augie Fackler
formatting: blacken the codebase...
r43346 LIBRARIES_XML.format(wix_dir=str(wix_dir))
)
Gregory Szorc
wix: autogenerate wxs file for library files...
r42123
component = doc.getElementsByTagName('Component')[0]
f = doc.createElement('File')
f.setAttribute('Name', 'library.zip')
f.setAttribute('KeyPath', 'yes')
component.appendChild(f)
lib_dir = dist_dir / 'lib'
for p in sorted(lib_dir.iterdir()):
if not p.name.endswith(('.dll', '.pyd')):
continue
f = doc.createElement('File')
f.setAttribute('Name', p.name)
component.appendChild(f)
return doc.toprettyxml()
Augie Fackler
formatting: blacken the codebase...
r43346 def build_installer(
source_dir: pathlib.Path,
python_exe: pathlib.Path,
msi_name='mercurial',
version=None,
post_build_fn=None,
extra_packages_script=None,
extra_wxs: typing.Optional[typing.Dict[str, str]] = None,
extra_features: typing.Optional[typing.List[str]] = None,
):
Gregory Szorc
packaging: convert files to LF...
r42118 """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.
Augie Fackler
wix: add a hook for a prebuild script to inject extra libraries...
r42214 ``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.
Augie Fackler
wix: add support for additional wxs files...
r42215 ``extra_wxs`` is a dict of {wxs_name: working_dir_for_wxs_build}.
Augie Fackler
wix: add functionality to inject additional Features into installer...
r42216 ``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.
Gregory Szorc
packaging: convert files to LF...
r42118 """
arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86'
hg_build_dir = source_dir / 'build'
dist_dir = source_dir / 'dist'
Gregory Szorc
wix: introduce variable to hold path to wix packaging directory...
r42122 wix_dir = source_dir / 'contrib' / 'packaging' / 'wix'
Gregory Szorc
packaging: convert files to LF...
r42118
Gregory Szorc
wix: introduce variable to hold path to wix packaging directory...
r42122 requirements_txt = wix_dir / 'requirements.txt'
Gregory Szorc
packaging: convert files to LF...
r42118
Augie Fackler
formatting: blacken the codebase...
r43346 build_py2exe(
source_dir,
hg_build_dir,
python_exe,
'wix',
requirements_txt,
extra_packages=EXTRA_PACKAGES,
extra_packages_script=extra_packages_script,
)
Gregory Szorc
packaging: convert files to LF...
r42118
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:
Gregory Szorc
wix: introduce variable to hold path to wix packaging directory...
r42122 wxs = wix_dir / wxs
Gregory Szorc
packaging: convert files to LF...
r42118 wxs_source_dir = source_dir / rel_path
run_candle(wix_path, build_dir, wxs, wxs_source_dir, defines=defines)
Augie Fackler
wix: add support for additional wxs files...
r42215 for source, rel_path in sorted((extra_wxs or {}).items()):
run_candle(wix_path, build_dir, source, rel_path, defines=defines)
Gregory Szorc
wix: autogenerate wxs file for library files...
r42123 # candle.exe doesn't like when we have an open handle on the file.
# So use TemporaryDirectory() instead of NamedTemporaryFile().
with tempfile.TemporaryDirectory() as td:
td = pathlib.Path(td)
tf = td / 'library.wxs'
with tf.open('w') as fh:
fh.write(make_libraries_xml(wix_dir, dist_dir))
run_candle(wix_path, build_dir, tf, dist_dir, defines=defines)
Gregory Szorc
wix: introduce variable to hold path to wix packaging directory...
r42122 source = wix_dir / 'mercurial.wxs'
Gregory Szorc
packaging: convert files to LF...
r42118 defines['Version'] = version
defines['Comments'] = 'Installs Mercurial version %s' % version
defines['VCRedistSrcDir'] = str(hg_build_dir)
Augie Fackler
wix: add functionality to inject additional Features into installer...
r42216 if extra_features:
assert all(';' not in f for f in extra_features)
defines['MercurialExtraFeatures'] = ';'.join(extra_features)
Gregory Szorc
packaging: convert files to LF...
r42118
run_candle(wix_path, build_dir, source, source_build_rel, defines=defines)
Augie Fackler
formatting: blacken the codebase...
r43346 msi_path = (
source_dir / 'dist' / ('%s-%s-%s.msi' % (msi_name, version, arch))
)
Gregory Szorc
packaging: convert files to LF...
r42118
args = [
str(wix_path / 'light.exe'),
'-nologo',
Augie Fackler
formatting: blacken the codebase...
r43346 '-ext',
'WixUIExtension',
Gregory Szorc
packaging: convert files to LF...
r42118 '-sw1076',
'-spdb',
Augie Fackler
formatting: blacken the codebase...
r43346 '-o',
str(msi_path),
Gregory Szorc
packaging: convert files to LF...
r42118 ]
for source, rel_path in SUPPORT_WXS:
assert source.endswith('.wxs')
args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
Augie Fackler
wix: add support for additional wxs files...
r42215 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])))
Augie Fackler
formatting: blacken the codebase...
r43346 args.extend(
[
str(build_dir / 'library.wixobj'),
str(build_dir / 'mercurial.wixobj'),
]
)
Gregory Szorc
packaging: convert files to LF...
r42118
subprocess.run(args, cwd=str(source_dir), check=True)
print('%s created' % msi_path)
return {
'msi_path': msi_path,
}
Augie Fackler
formatting: blacken the codebase...
r43346 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,
extra_packages_script=None,
extra_wxs=None,
extra_features=None,
):
Gregory Szorc
packaging: convert files to LF...
r42118 """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,
Augie Fackler
formatting: blacken the codebase...
r43346 timestamp_url=timestamp_url,
)
Gregory Szorc
packaging: convert files to LF...
r42118
Augie Fackler
formatting: blacken the codebase...
r43346 info = build_installer(
source_dir,
python_exe=python_exe,
msi_name=name.lower(),
version=version,
post_build_fn=post_build_fn,
extra_packages_script=extra_packages_script,
extra_wxs=extra_wxs,
extra_features=extra_features,
)
Gregory Szorc
packaging: convert files to LF...
r42118
description = '%s %s' % (name, version)
Augie Fackler
formatting: blacken the codebase...
r43346 sign_with_signtool(
info['msi_path'],
description,
subject_name=subject_name,
cert_path=cert_path,
cert_password=cert_password,
timestamp_url=timestamp_url,
)