##// END OF EJS Templates
copies: add test that makes both the merging csets dirty and fails...
copies: add test that makes both the merging csets dirty and fails This patch is a part of series which is about the case when both the merging csets are not descendant of merge base. The existing code assumes if c1 is dirty there shouldn't be any partial copies from c2 i.e both2['incomplete'] and same for c2, if c2 is dirty both1['incomplete'] should be empty, but this is not the right assumption. Now as we know we can have both c1 and c2 dirty at the same time, it is possible that c1 is dirty and both2['incomplete'] has some value. Or if c2 is dirty and both1['incomplete'] has some value. Added test shows that because of this assumption it could fail. Differential Revision: https://phab.mercurial-scm.org/D5962

File last commit:

r42090:9d4ae504 default
r42097:fc4b7a46 default
Show More
wix.py
239 lines | 7.4 KiB | text/x-python | PythonLexer
# 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)