##// END OF EJS Templates
merge with stable
Martin von Zweigbergk -
r45280:16cba0ad merge default
parent child Browse files
Show More
@@ -0,0 +1,145 b''
1 # pyoxidizer.py - Packaging support for PyOxidizer
2 #
3 # Copyright 2020 Gregory Szorc <gregory.szorc@gmail.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7
8 # no-check-code because Python 3 native.
9
10 import os
11 import pathlib
12 import shutil
13 import subprocess
14 import sys
15
16 from .downloads import download_entry
17 from .util import (
18 extract_zip_to_directory,
19 process_install_rules,
20 find_vc_runtime_dll,
21 )
22
23
24 STAGING_RULES_WINDOWS = [
25 ('contrib/bash_completion', 'contrib/'),
26 ('contrib/hgk', 'contrib/hgk.tcl'),
27 ('contrib/hgweb.fcgi', 'contrib/'),
28 ('contrib/hgweb.wsgi', 'contrib/'),
29 ('contrib/logo-droplets.svg', 'contrib/'),
30 ('contrib/mercurial.el', 'contrib/'),
31 ('contrib/mq.el', 'contrib/'),
32 ('contrib/tcsh_completion', 'contrib/'),
33 ('contrib/tcsh_completion_build.sh', 'contrib/'),
34 ('contrib/vim/*', 'contrib/vim/'),
35 ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'),
36 ('contrib/win32/ReadMe.html', 'ReadMe.html'),
37 ('contrib/xml.rnc', 'contrib/'),
38 ('contrib/zsh_completion', 'contrib/'),
39 ('doc/*.html', 'doc/'),
40 ('doc/style.css', 'doc/'),
41 ('COPYING', 'Copying.txt'),
42 ]
43
44 STAGING_RULES_APP = [
45 ('mercurial/helptext/**/*.txt', 'helptext/'),
46 ('mercurial/defaultrc/*.rc', 'defaultrc/'),
47 ('mercurial/locale/**/*', 'locale/'),
48 ('mercurial/templates/**/*', 'templates/'),
49 ]
50
51 STAGING_EXCLUDES_WINDOWS = [
52 "doc/hg-ssh.8.html",
53 ]
54
55
56 def run_pyoxidizer(
57 source_dir: pathlib.Path,
58 build_dir: pathlib.Path,
59 out_dir: pathlib.Path,
60 target_triple: str,
61 ):
62 """Build Mercurial with PyOxidizer and copy additional files into place.
63
64 After successful completion, ``out_dir`` contains files constituting a
65 Mercurial install.
66 """
67 # We need to make gettext binaries available for compiling i18n files.
68 gettext_pkg, gettext_entry = download_entry('gettext', build_dir)
69 gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0]
70
71 gettext_root = build_dir / ('gettext-win-%s' % gettext_entry['version'])
72
73 if not gettext_root.exists():
74 extract_zip_to_directory(gettext_pkg, gettext_root)
75 extract_zip_to_directory(gettext_dep_pkg, gettext_root)
76
77 env = dict(os.environ)
78 env["PATH"] = "%s%s%s" % (
79 env["PATH"],
80 os.pathsep,
81 str(gettext_root / "bin"),
82 )
83
84 args = [
85 "pyoxidizer",
86 "build",
87 "--path",
88 str(source_dir / "rust" / "hgcli"),
89 "--release",
90 "--target-triple",
91 target_triple,
92 ]
93
94 subprocess.run(args, env=env, check=True)
95
96 if "windows" in target_triple:
97 target = "app_windows"
98 else:
99 target = "app_posix"
100
101 build_dir = (
102 source_dir / "build" / "pyoxidizer" / target_triple / "release" / target
103 )
104
105 if out_dir.exists():
106 print("purging %s" % out_dir)
107 shutil.rmtree(out_dir)
108
109 # Now assemble all the files from PyOxidizer into the staging directory.
110 shutil.copytree(build_dir, out_dir)
111
112 # Move some of those files around.
113 process_install_rules(STAGING_RULES_APP, build_dir, out_dir)
114 # Nuke the mercurial/* directory, as we copied resources
115 # to an appropriate location just above.
116 shutil.rmtree(out_dir / "mercurial")
117
118 # We also need to run setup.py build_doc to produce html files,
119 # as they aren't built as part of ``pip install``.
120 # This will fail if docutils isn't installed.
121 subprocess.run(
122 [sys.executable, str(source_dir / "setup.py"), "build_doc", "--html"],
123 cwd=str(source_dir),
124 check=True,
125 )
126
127 if "windows" in target_triple:
128 process_install_rules(STAGING_RULES_WINDOWS, source_dir, out_dir)
129
130 # Write out a default editor.rc file to configure notepad as the
131 # default editor.
132 with (out_dir / "defaultrc" / "editor.rc").open(
133 "w", encoding="utf-8"
134 ) as fh:
135 fh.write("[ui]\neditor = notepad\n")
136
137 for f in STAGING_EXCLUDES_WINDOWS:
138 p = out_dir / f
139 if p.exists():
140 print("removing %s" % p)
141 p.unlink()
142
143 # Add vcruntimeXXX.dll next to executable.
144 vc_runtime_dll = find_vc_runtime_dll(x64="x86_64" in target_triple)
145 shutil.copy(vc_runtime_dll, out_dir / vc_runtime_dll.name)
@@ -63,7 +63,13 b' def bootstrap_windows_dev(hga: HGAutomat'
63 63
64 64
65 65 def build_inno(
66 hga: HGAutomation, aws_region, arch, revision, version, base_image_name
66 hga: HGAutomation,
67 aws_region,
68 python_version,
69 arch,
70 revision,
71 version,
72 base_image_name,
67 73 ):
68 74 c = hga.aws_connection(aws_region)
69 75 image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name)
@@ -74,14 +80,25 b' def build_inno('
74 80
75 81 windows.synchronize_hg(SOURCE_ROOT, revision, instance)
76 82
77 for a in arch:
78 windows.build_inno_installer(
79 instance.winrm_client, a, DIST_PATH, version=version
80 )
83 for py_version in python_version:
84 for a in arch:
85 windows.build_inno_installer(
86 instance.winrm_client,
87 py_version,
88 a,
89 DIST_PATH,
90 version=version,
91 )
81 92
82 93
83 94 def build_wix(
84 hga: HGAutomation, aws_region, arch, revision, version, base_image_name
95 hga: HGAutomation,
96 aws_region,
97 python_version,
98 arch,
99 revision,
100 version,
101 base_image_name,
85 102 ):
86 103 c = hga.aws_connection(aws_region)
87 104 image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name)
@@ -92,14 +109,24 b' def build_wix('
92 109
93 110 windows.synchronize_hg(SOURCE_ROOT, revision, instance)
94 111
95 for a in arch:
96 windows.build_wix_installer(
97 instance.winrm_client, a, DIST_PATH, version=version
98 )
112 for py_version in python_version:
113 for a in arch:
114 windows.build_wix_installer(
115 instance.winrm_client,
116 py_version,
117 a,
118 DIST_PATH,
119 version=version,
120 )
99 121
100 122
101 123 def build_windows_wheel(
102 hga: HGAutomation, aws_region, arch, revision, base_image_name
124 hga: HGAutomation,
125 aws_region,
126 python_version,
127 arch,
128 revision,
129 base_image_name,
103 130 ):
104 131 c = hga.aws_connection(aws_region)
105 132 image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name)
@@ -110,8 +137,11 b' def build_windows_wheel('
110 137
111 138 windows.synchronize_hg(SOURCE_ROOT, revision, instance)
112 139
113 for a in arch:
114 windows.build_wheel(instance.winrm_client, a, DIST_PATH)
140 for py_version in python_version:
141 for a in arch:
142 windows.build_wheel(
143 instance.winrm_client, py_version, a, DIST_PATH
144 )
115 145
116 146
117 147 def build_all_windows_packages(
@@ -128,17 +158,25 b' def build_all_windows_packages('
128 158
129 159 windows.synchronize_hg(SOURCE_ROOT, revision, instance)
130 160
131 for arch in ('x86', 'x64'):
132 windows.purge_hg(winrm_client)
133 windows.build_wheel(winrm_client, arch, DIST_PATH)
134 windows.purge_hg(winrm_client)
135 windows.build_inno_installer(
136 winrm_client, arch, DIST_PATH, version=version
137 )
138 windows.purge_hg(winrm_client)
139 windows.build_wix_installer(
140 winrm_client, arch, DIST_PATH, version=version
141 )
161 for py_version in ("2.7", "3.7", "3.8"):
162 for arch in ("x86", "x64"):
163 windows.purge_hg(winrm_client)
164 windows.build_wheel(
165 winrm_client,
166 python_version=py_version,
167 arch=arch,
168 dest_path=DIST_PATH,
169 )
170
171 for py_version in (2, 3):
172 for arch in ('x86', 'x64'):
173 windows.purge_hg(winrm_client)
174 windows.build_inno_installer(
175 winrm_client, py_version, arch, DIST_PATH, version=version
176 )
177 windows.build_wix_installer(
178 winrm_client, py_version, arch, DIST_PATH, version=version
179 )
142 180
143 181
144 182 def terminate_ec2_instances(hga: HGAutomation, aws_region):
@@ -293,6 +331,14 b' def get_parser():'
293 331 'build-inno', help='Build Inno Setup installer(s)',
294 332 )
295 333 sp.add_argument(
334 '--python-version',
335 help='Which version of Python to target',
336 choices={2, 3},
337 type=int,
338 nargs='*',
339 default=[3],
340 )
341 sp.add_argument(
296 342 '--arch',
297 343 help='Architecture to build for',
298 344 choices={'x86', 'x64'},
@@ -316,6 +362,13 b' def get_parser():'
316 362 'build-windows-wheel', help='Build Windows wheel(s)',
317 363 )
318 364 sp.add_argument(
365 '--python-version',
366 help='Python version to build for',
367 choices={'2.7', '3.7', '3.8'},
368 nargs='*',
369 default=['3.8'],
370 )
371 sp.add_argument(
319 372 '--arch',
320 373 help='Architecture to build for',
321 374 choices={'x86', 'x64'},
@@ -334,6 +387,14 b' def get_parser():'
334 387
335 388 sp = subparsers.add_parser('build-wix', help='Build WiX installer(s)')
336 389 sp.add_argument(
390 '--python-version',
391 help='Which version of Python to target',
392 choices={2, 3},
393 type=int,
394 nargs='*',
395 default=[3],
396 )
397 sp.add_argument(
337 398 '--arch',
338 399 help='Architecture to build for',
339 400 choices={'x86', 'x64'},
@@ -72,8 +72,10 b' echo "${RUSTUP_INIT_SHA256} rustup-init"'
72 72
73 73 chmod +x rustup-init
74 74 sudo -H -u hg -g hg ./rustup-init -y
75 sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup install 1.31.1 1.34.2
75 sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup install 1.31.1 1.42.0
76 76 sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup component add clippy
77
78 sudo -H -u hg -g hg /home/hg/.cargo/bin/cargo install --version 0.7.0 pyoxidizer
77 79 '''
78 80
79 81
@@ -68,10 +68,20 b' hg.exe log -r .'
68 68 Write-Output "updated Mercurial working directory to {revision}"
69 69 '''.lstrip()
70 70
71 BUILD_INNO = r'''
71 BUILD_INNO_PYTHON3 = r'''
72 $Env:RUSTUP_HOME = "C:\hgdev\rustup"
73 $Env:CARGO_HOME = "C:\hgdev\cargo"
74 Set-Location C:\hgdev\src
75 C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py inno --pyoxidizer-target {pyoxidizer_target} --version {version}
76 if ($LASTEXITCODE -ne 0) {{
77 throw "process exited non-0: $LASTEXITCODE"
78 }}
79 '''
80
81 BUILD_INNO_PYTHON2 = r'''
72 82 Set-Location C:\hgdev\src
73 83 $python = "C:\hgdev\python27-{arch}\python.exe"
74 C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py inno --python $python
84 C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py inno --python $python {extra_args}
75 85 if ($LASTEXITCODE -ne 0) {{
76 86 throw "process exited non-0: $LASTEXITCODE"
77 87 }}
@@ -79,13 +89,23 b' if ($LASTEXITCODE -ne 0) {{'
79 89
80 90 BUILD_WHEEL = r'''
81 91 Set-Location C:\hgdev\src
82 C:\hgdev\python27-{arch}\Scripts\pip.exe wheel --wheel-dir dist .
92 C:\hgdev\python{python_version}-{arch}\python.exe -m pip wheel --wheel-dir dist .
83 93 if ($LASTEXITCODE -ne 0) {{
84 94 throw "process exited non-0: $LASTEXITCODE"
85 95 }}
86 96 '''
87 97
88 BUILD_WIX = r'''
98 BUILD_WIX_PYTHON3 = r'''
99 $Env:RUSTUP_HOME = "C:\hgdev\rustup"
100 $Env:CARGO_HOME = "C:\hgdev\cargo"
101 Set-Location C:\hgdev\src
102 C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py wix --pyoxidizer-target {pyoxidizer_target} --version {version}
103 if ($LASTEXITCODE -ne 0) {{
104 throw "process exited non-0: $LASTEXITCODE"
105 }}
106 '''
107
108 BUILD_WIX_PYTHON2 = r'''
89 109 Set-Location C:\hgdev\src
90 110 $python = "C:\hgdev\python27-{arch}\python.exe"
91 111 C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py wix --python $python {extra_args}
@@ -101,31 +121,60 b' if ($LASTEXITCODE -ne 0) {{'
101 121 }}
102 122 '''
103 123
104 X86_WHEEL_FILENAME = 'mercurial-{version}-cp27-cp27m-win32.whl'
105 X64_WHEEL_FILENAME = 'mercurial-{version}-cp27-cp27m-win_amd64.whl'
106 X86_EXE_FILENAME = 'Mercurial-{version}.exe'
107 X64_EXE_FILENAME = 'Mercurial-{version}-x64.exe'
108 X86_MSI_FILENAME = 'mercurial-{version}-x86.msi'
109 X64_MSI_FILENAME = 'mercurial-{version}-x64.msi'
124 WHEEL_FILENAME_PYTHON27_X86 = 'mercurial-{version}-cp27-cp27m-win32.whl'
125 WHEEL_FILENAME_PYTHON27_X64 = 'mercurial-{version}-cp27-cp27m-win_amd64.whl'
126 WHEEL_FILENAME_PYTHON37_X86 = 'mercurial-{version}-cp37-cp37m-win32.whl'
127 WHEEL_FILENAME_PYTHON37_X64 = 'mercurial-{version}-cp37-cp37m-win_amd64.whl'
128 WHEEL_FILENAME_PYTHON38_X86 = 'mercurial-{version}-cp38-cp38-win32.whl'
129 WHEEL_FILENAME_PYTHON38_X64 = 'mercurial-{version}-cp38-cp38-win_amd64.whl'
130
131 EXE_FILENAME_PYTHON2_X86 = 'Mercurial-{version}-x86-python2.exe'
132 EXE_FILENAME_PYTHON2_X64 = 'Mercurial-{version}-x64-python2.exe'
133 EXE_FILENAME_PYTHON3_X86 = 'Mercurial-{version}-x86.exe'
134 EXE_FILENAME_PYTHON3_X64 = 'Mercurial-{version}-x64.exe'
135
136 MSI_FILENAME_PYTHON2_X86 = 'mercurial-{version}-x86-python2.msi'
137 MSI_FILENAME_PYTHON2_X64 = 'mercurial-{version}-x64-python2.msi'
138 MSI_FILENAME_PYTHON3_X86 = 'mercurial-{version}-x86.msi'
139 MSI_FILENAME_PYTHON3_X64 = 'mercurial-{version}-x64.msi'
110 140
111 141 MERCURIAL_SCM_BASE_URL = 'https://mercurial-scm.org/release/windows'
112 142
113 143 X86_USER_AGENT_PATTERN = '.*Windows.*'
114 144 X64_USER_AGENT_PATTERN = '.*Windows.*(WOW|x)64.*'
115 145
116 X86_EXE_DESCRIPTION = (
117 'Mercurial {version} Inno Setup installer - x86 Windows '
146 EXE_PYTHON2_X86_DESCRIPTION = (
147 'Mercurial {version} Inno Setup installer - x86 Windows (Python 2) '
148 '- does not require admin rights'
149 )
150 EXE_PYTHON2_X64_DESCRIPTION = (
151 'Mercurial {version} Inno Setup installer - x64 Windows (Python 2) '
152 '- does not require admin rights'
153 )
154 # TODO remove Python version once Python 2 is dropped.
155 EXE_PYTHON3_X86_DESCRIPTION = (
156 'Mercurial {version} Inno Setup installer - x86 Windows (Python 3) '
118 157 '- does not require admin rights'
119 158 )
120 X64_EXE_DESCRIPTION = (
121 'Mercurial {version} Inno Setup installer - x64 Windows '
159 EXE_PYTHON3_X64_DESCRIPTION = (
160 'Mercurial {version} Inno Setup installer - x64 Windows (Python 3) '
122 161 '- does not require admin rights'
123 162 )
124 X86_MSI_DESCRIPTION = (
125 'Mercurial {version} MSI installer - x86 Windows ' '- requires admin rights'
163 MSI_PYTHON2_X86_DESCRIPTION = (
164 'Mercurial {version} MSI installer - x86 Windows (Python 2) '
165 '- requires admin rights'
166 )
167 MSI_PYTHON2_X64_DESCRIPTION = (
168 'Mercurial {version} MSI installer - x64 Windows (Python 2) '
169 '- requires admin rights'
126 170 )
127 X64_MSI_DESCRIPTION = (
128 'Mercurial {version} MSI installer - x64 Windows ' '- requires admin rights'
171 MSI_PYTHON3_X86_DESCRIPTION = (
172 'Mercurial {version} MSI installer - x86 Windows (Python 3) '
173 '- requires admin rights'
174 )
175 MSI_PYTHON3_X64_DESCRIPTION = (
176 'Mercurial {version} MSI installer - x64 Windows (Python 3) '
177 '- requires admin rights'
129 178 )
130 179
131 180
@@ -280,53 +329,113 b' def copy_latest_dist(winrm_client, patte'
280 329
281 330
282 331 def build_inno_installer(
283 winrm_client, arch: str, dest_path: pathlib.Path, version=None
332 winrm_client,
333 python_version: int,
334 arch: str,
335 dest_path: pathlib.Path,
336 version=None,
284 337 ):
285 338 """Build the Inno Setup installer on a remote machine.
286 339
287 340 Using a WinRM client, remote commands are executed to build
288 341 a Mercurial Inno Setup installer.
289 342 """
290 print('building Inno Setup installer for %s' % arch)
343 print(
344 'building Inno Setup installer for Python %d %s'
345 % (python_version, arch)
346 )
347
348 if python_version == 3:
349 # TODO fix this limitation in packaging code
350 if not version:
351 raise Exception(
352 "version string is required when building for Python 3"
353 )
291 354
292 extra_args = []
293 if version:
294 extra_args.extend(['--version', version])
355 if arch == "x86":
356 target_triple = "i686-pc-windows-msvc"
357 elif arch == "x64":
358 target_triple = "x86_64-pc-windows-msvc"
359 else:
360 raise Exception("unhandled arch: %s" % arch)
295 361
296 ps = get_vc_prefix(arch) + BUILD_INNO.format(
297 arch=arch, extra_args=' '.join(extra_args)
298 )
362 ps = BUILD_INNO_PYTHON3.format(
363 pyoxidizer_target=target_triple, version=version,
364 )
365 else:
366 extra_args = []
367 if version:
368 extra_args.extend(['--version', version])
369
370 ps = get_vc_prefix(arch) + BUILD_INNO_PYTHON2.format(
371 arch=arch, extra_args=' '.join(extra_args)
372 )
373
299 374 run_powershell(winrm_client, ps)
300 375 copy_latest_dist(winrm_client, '*.exe', dest_path)
301 376
302 377
303 def build_wheel(winrm_client, arch: str, dest_path: pathlib.Path):
378 def build_wheel(
379 winrm_client, python_version: str, arch: str, dest_path: pathlib.Path
380 ):
304 381 """Build Python wheels on a remote machine.
305 382
306 383 Using a WinRM client, remote commands are executed to build a Python wheel
307 384 for Mercurial.
308 385 """
309 print('Building Windows wheel for %s' % arch)
310 ps = get_vc_prefix(arch) + BUILD_WHEEL.format(arch=arch)
386 print('Building Windows wheel for Python %s %s' % (python_version, arch))
387
388 ps = BUILD_WHEEL.format(
389 python_version=python_version.replace(".", ""), arch=arch
390 )
391
392 # Python 2.7 requires an activated environment.
393 if python_version == "2.7":
394 ps = get_vc_prefix(arch) + ps
395
311 396 run_powershell(winrm_client, ps)
312 397 copy_latest_dist(winrm_client, '*.whl', dest_path)
313 398
314 399
315 400 def build_wix_installer(
316 winrm_client, arch: str, dest_path: pathlib.Path, version=None
401 winrm_client,
402 python_version: int,
403 arch: str,
404 dest_path: pathlib.Path,
405 version=None,
317 406 ):
318 407 """Build the WiX installer on a remote machine.
319 408
320 409 Using a WinRM client, remote commands are executed to build a WiX installer.
321 410 """
322 print('Building WiX installer for %s' % arch)
323 extra_args = []
324 if version:
325 extra_args.extend(['--version', version])
411 print('Building WiX installer for Python %d %s' % (python_version, arch))
412
413 if python_version == 3:
414 # TODO fix this limitation in packaging code
415 if not version:
416 raise Exception(
417 "version string is required when building for Python 3"
418 )
326 419
327 ps = get_vc_prefix(arch) + BUILD_WIX.format(
328 arch=arch, extra_args=' '.join(extra_args)
329 )
420 if arch == "x86":
421 target_triple = "i686-pc-windows-msvc"
422 elif arch == "x64":
423 target_triple = "x86_64-pc-windows-msvc"
424 else:
425 raise Exception("unhandled arch: %s" % arch)
426
427 ps = BUILD_WIX_PYTHON3.format(
428 pyoxidizer_target=target_triple, version=version,
429 )
430 else:
431 extra_args = []
432 if version:
433 extra_args.extend(['--version', version])
434
435 ps = get_vc_prefix(arch) + BUILD_WIX_PYTHON2.format(
436 arch=arch, extra_args=' '.join(extra_args)
437 )
438
330 439 run_powershell(winrm_client, ps)
331 440 copy_latest_dist(winrm_client, '*.msi', dest_path)
332 441
@@ -356,56 +465,100 b' def run_tests(winrm_client, python_versi'
356 465
357 466 def resolve_wheel_artifacts(dist_path: pathlib.Path, version: str):
358 467 return (
359 dist_path / X86_WHEEL_FILENAME.format(version=version),
360 dist_path / X64_WHEEL_FILENAME.format(version=version),
468 dist_path / WHEEL_FILENAME_PYTHON27_X86.format(version=version),
469 dist_path / WHEEL_FILENAME_PYTHON27_X64.format(version=version),
470 dist_path / WHEEL_FILENAME_PYTHON37_X86.format(version=version),
471 dist_path / WHEEL_FILENAME_PYTHON37_X64.format(version=version),
472 dist_path / WHEEL_FILENAME_PYTHON38_X86.format(version=version),
473 dist_path / WHEEL_FILENAME_PYTHON38_X64.format(version=version),
361 474 )
362 475
363 476
364 477 def resolve_all_artifacts(dist_path: pathlib.Path, version: str):
365 478 return (
366 dist_path / X86_WHEEL_FILENAME.format(version=version),
367 dist_path / X64_WHEEL_FILENAME.format(version=version),
368 dist_path / X86_EXE_FILENAME.format(version=version),
369 dist_path / X64_EXE_FILENAME.format(version=version),
370 dist_path / X86_MSI_FILENAME.format(version=version),
371 dist_path / X64_MSI_FILENAME.format(version=version),
479 dist_path / WHEEL_FILENAME_PYTHON27_X86.format(version=version),
480 dist_path / WHEEL_FILENAME_PYTHON27_X64.format(version=version),
481 dist_path / WHEEL_FILENAME_PYTHON37_X86.format(version=version),
482 dist_path / WHEEL_FILENAME_PYTHON37_X64.format(version=version),
483 dist_path / WHEEL_FILENAME_PYTHON38_X86.format(version=version),
484 dist_path / WHEEL_FILENAME_PYTHON38_X64.format(version=version),
485 dist_path / EXE_FILENAME_PYTHON2_X86.format(version=version),
486 dist_path / EXE_FILENAME_PYTHON2_X64.format(version=version),
487 dist_path / EXE_FILENAME_PYTHON3_X86.format(version=version),
488 dist_path / EXE_FILENAME_PYTHON3_X64.format(version=version),
489 dist_path / MSI_FILENAME_PYTHON2_X86.format(version=version),
490 dist_path / MSI_FILENAME_PYTHON2_X64.format(version=version),
491 dist_path / MSI_FILENAME_PYTHON3_X86.format(version=version),
492 dist_path / MSI_FILENAME_PYTHON3_X64.format(version=version),
372 493 )
373 494
374 495
375 496 def generate_latest_dat(version: str):
376 x86_exe_filename = X86_EXE_FILENAME.format(version=version)
377 x64_exe_filename = X64_EXE_FILENAME.format(version=version)
378 x86_msi_filename = X86_MSI_FILENAME.format(version=version)
379 x64_msi_filename = X64_MSI_FILENAME.format(version=version)
497 python2_x86_exe_filename = EXE_FILENAME_PYTHON2_X86.format(version=version)
498 python2_x64_exe_filename = EXE_FILENAME_PYTHON2_X64.format(version=version)
499 python3_x86_exe_filename = EXE_FILENAME_PYTHON3_X86.format(version=version)
500 python3_x64_exe_filename = EXE_FILENAME_PYTHON3_X64.format(version=version)
501 python2_x86_msi_filename = MSI_FILENAME_PYTHON2_X86.format(version=version)
502 python2_x64_msi_filename = MSI_FILENAME_PYTHON2_X64.format(version=version)
503 python3_x86_msi_filename = MSI_FILENAME_PYTHON3_X86.format(version=version)
504 python3_x64_msi_filename = MSI_FILENAME_PYTHON3_X64.format(version=version)
380 505
381 506 entries = (
382 507 (
383 508 '10',
384 509 version,
385 510 X86_USER_AGENT_PATTERN,
386 '%s/%s' % (MERCURIAL_SCM_BASE_URL, x86_exe_filename),
387 X86_EXE_DESCRIPTION.format(version=version),
511 '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x86_exe_filename),
512 EXE_PYTHON3_X86_DESCRIPTION.format(version=version),
388 513 ),
389 514 (
390 515 '10',
391 516 version,
392 517 X64_USER_AGENT_PATTERN,
393 '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_exe_filename),
394 X64_EXE_DESCRIPTION.format(version=version),
518 '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x64_exe_filename),
519 EXE_PYTHON3_X64_DESCRIPTION.format(version=version),
520 ),
521 (
522 '9',
523 version,
524 X86_USER_AGENT_PATTERN,
525 '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x86_exe_filename),
526 EXE_PYTHON2_X86_DESCRIPTION.format(version=version),
527 ),
528 (
529 '9',
530 version,
531 X64_USER_AGENT_PATTERN,
532 '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x64_exe_filename),
533 EXE_PYTHON2_X64_DESCRIPTION.format(version=version),
395 534 ),
396 535 (
397 536 '10',
398 537 version,
399 538 X86_USER_AGENT_PATTERN,
400 '%s/%s' % (MERCURIAL_SCM_BASE_URL, x86_msi_filename),
401 X86_MSI_DESCRIPTION.format(version=version),
539 '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x86_msi_filename),
540 MSI_PYTHON3_X86_DESCRIPTION.format(version=version),
402 541 ),
403 542 (
404 543 '10',
405 544 version,
406 545 X64_USER_AGENT_PATTERN,
407 '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_msi_filename),
408 X64_MSI_DESCRIPTION.format(version=version),
546 '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x64_msi_filename),
547 MSI_PYTHON3_X64_DESCRIPTION.format(version=version),
548 ),
549 (
550 '9',
551 version,
552 X86_USER_AGENT_PATTERN,
553 '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x86_msi_filename),
554 MSI_PYTHON2_X86_DESCRIPTION.format(version=version),
555 ),
556 (
557 '9',
558 version,
559 X64_USER_AGENT_PATTERN,
560 '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x64_msi_filename),
561 MSI_PYTHON2_X64_DESCRIPTION.format(version=version),
409 562 ),
410 563 )
411 564
@@ -64,6 +64,9 b''
64 64 $MERCURIAL_WHEEL_URL = "https://files.pythonhosted.org/packages/6d/47/e031e47f7fe9b16e4e3383da47e2b0a7eae6e603996bc67a03ec4fa1b3f4/$MERCURIAL_WHEEL_FILENAME"
65 65 $MERCURIAL_WHEEL_SHA256 = "1d18c7f6ca1456f0f62ee65c9a50c14cbba48ce6e924930cdb10537f5c9eaf5f"
66 66
67 $RUSTUP_INIT_URL = "https://static.rust-lang.org/rustup/archive/1.21.1/x86_64-pc-windows-gnu/rustup-init.exe"
68 $RUSTUP_INIT_SHA256 = "d17df34ba974b9b19cf5c75883a95475aa22ddc364591d75d174090d55711c72"
69
67 70 # Writing progress slows down downloads substantially. So disable it.
68 71 $progressPreference = 'silentlyContinue'
69 72
@@ -116,6 +119,20 b' function Install-Python3($name, $install'
116 119 Invoke-Process ${dest}\python.exe $pip
117 120 }
118 121
122 function Install-Rust($prefix) {
123 Write-Output "installing Rust"
124 $Env:RUSTUP_HOME = "${prefix}\rustup"
125 $Env:CARGO_HOME = "${prefix}\cargo"
126
127 Invoke-Process "${prefix}\assets\rustup-init.exe" "-y --default-host x86_64-pc-windows-msvc"
128 Invoke-Process "${prefix}\cargo\bin\rustup.exe" "target add i686-pc-windows-msvc"
129 Invoke-Process "${prefix}\cargo\bin\rustup.exe" "install 1.42.0"
130 Invoke-Process "${prefix}\cargo\bin\rustup.exe" "component add clippy"
131
132 # Install PyOxidizer for packaging.
133 Invoke-Process "${prefix}\cargo\bin\cargo.exe" "install --version 0.7.0 pyoxidizer"
134 }
135
119 136 function Install-Dependencies($prefix) {
120 137 if (!(Test-Path -Path $prefix\assets)) {
121 138 New-Item -Path $prefix\assets -ItemType Directory
@@ -140,6 +157,7 b' function Install-Dependencies($prefix) {'
140 157 Secure-Download $INNO_SETUP_URL ${prefix}\assets\InnoSetup.exe $INNO_SETUP_SHA256
141 158 Secure-Download $MINGW_BIN_URL ${prefix}\assets\mingw-get-bin.zip $MINGW_BIN_SHA256
142 159 Secure-Download $MERCURIAL_WHEEL_URL ${prefix}\assets\${MERCURIAL_WHEEL_FILENAME} $MERCURIAL_WHEEL_SHA256
160 Secure-Download $RUSTUP_INIT_URL ${prefix}\assets\rustup-init.exe $RUSTUP_INIT_SHA256
143 161
144 162 Write-Output "installing Python 2.7 32-bit"
145 163 Invoke-Process msiexec.exe "/i ${prefix}\assets\python27-x86.msi /l* ${prefix}\assets\python27-x86.log /q TARGETDIR=${prefix}\python27-x86 ALLUSERS="
@@ -163,6 +181,8 b' function Install-Dependencies($prefix) {'
163 181 Write-Output "installing Visual Studio 2017 Build Tools and SDKs"
164 182 Invoke-Process ${prefix}\assets\vs_buildtools.exe "--quiet --wait --norestart --nocache --channelUri https://aka.ms/vs/15/release/channel --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.Windows10SDK.17763 --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows10SDK --add Microsoft.VisualStudio.Component.VC.140"
165 183
184 Install-Rust ${prefix}
185
166 186 Write-Output "installing Visual C++ 9.0 for Python 2.7"
167 187 Invoke-Process msiexec.exe "/i ${prefix}\assets\VCForPython27.msi /l* ${prefix}\assets\VCForPython27.log /q"
168 188
@@ -20,8 +20,11 b' HERE = pathlib.Path(os.path.abspath(os.p'
20 20 SOURCE_DIR = HERE.parent.parent.parent
21 21
22 22
23 def build_inno(python=None, iscc=None, version=None):
24 if not os.path.isabs(python):
23 def build_inno(pyoxidizer_target=None, python=None, iscc=None, version=None):
24 if not pyoxidizer_target and not python:
25 raise Exception("--python required unless building with PyOxidizer")
26
27 if python and not os.path.isabs(python):
25 28 raise Exception("--python arg must be an absolute path")
26 29
27 30 if iscc:
@@ -35,13 +38,19 b' def build_inno(python=None, iscc=None, v'
35 38
36 39 build_dir = SOURCE_DIR / "build"
37 40
38 inno.build(
39 SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version,
40 )
41 if pyoxidizer_target:
42 inno.build_with_pyoxidizer(
43 SOURCE_DIR, build_dir, pyoxidizer_target, iscc, version=version
44 )
45 else:
46 inno.build_with_py2exe(
47 SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version,
48 )
41 49
42 50
43 51 def build_wix(
44 52 name=None,
53 pyoxidizer_target=None,
45 54 python=None,
46 55 version=None,
47 56 sign_sn=None,
@@ -52,17 +61,29 b' def build_wix('
52 61 extra_wxs=None,
53 62 extra_features=None,
54 63 ):
55 fn = wix.build_installer
64 if not pyoxidizer_target and not python:
65 raise Exception("--python required unless building with PyOxidizer")
66
67 if python and not os.path.isabs(python):
68 raise Exception("--python arg must be an absolute path")
69
56 70 kwargs = {
57 71 "source_dir": SOURCE_DIR,
58 "python_exe": pathlib.Path(python),
59 72 "version": version,
60 73 }
61 74
62 if not os.path.isabs(python):
63 raise Exception("--python arg must be an absolute path")
75 if pyoxidizer_target:
76 fn = wix.build_installer_pyoxidizer
77 kwargs["target_triple"] = pyoxidizer_target
78 else:
79 fn = wix.build_installer_py2exe
80 kwargs["python_exe"] = pathlib.Path(python)
64 81
65 82 if extra_packages_script:
83 if pyoxidizer_target:
84 raise Exception(
85 "pyoxidizer does not support --extra-packages-script"
86 )
66 87 kwargs["extra_packages_script"] = extra_packages_script
67 88 if extra_wxs:
68 89 kwargs["extra_wxs"] = dict(
@@ -72,12 +93,13 b' def build_wix('
72 93 kwargs["extra_features"] = extra_features.split(",")
73 94
74 95 if sign_sn or sign_cert:
75 fn = wix.build_signed_installer
76 kwargs["name"] = name
77 kwargs["subject_name"] = sign_sn
78 kwargs["cert_path"] = sign_cert
79 kwargs["cert_password"] = sign_password
80 kwargs["timestamp_url"] = sign_timestamp_url
96 kwargs["signing_info"] = {
97 "name": name,
98 "subject_name": sign_sn,
99 "cert_path": sign_cert,
100 "cert_password": sign_password,
101 "timestamp_url": sign_timestamp_url,
102 }
81 103
82 104 fn(**kwargs)
83 105
@@ -88,7 +110,12 b' def get_parser():'
88 110 subparsers = parser.add_subparsers()
89 111
90 112 sp = subparsers.add_parser("inno", help="Build Inno Setup installer")
91 sp.add_argument("--python", required=True, help="path to python.exe to use")
113 sp.add_argument(
114 "--pyoxidizer-target",
115 choices={"i686-pc-windows-msvc", "x86_64-pc-windows-msvc"},
116 help="Build with PyOxidizer targeting this host triple",
117 )
118 sp.add_argument("--python", help="path to python.exe to use")
92 119 sp.add_argument("--iscc", help="path to iscc.exe to use")
93 120 sp.add_argument(
94 121 "--version",
@@ -102,8 +129,11 b' def get_parser():'
102 129 )
103 130 sp.add_argument("--name", help="Application name", default="Mercurial")
104 131 sp.add_argument(
105 "--python", help="Path to Python executable to use", required=True
132 "--pyoxidizer-target",
133 choices={"i686-pc-windows-msvc", "x86_64-pc-windows-msvc"},
134 help="Build with PyOxidizer targeting this host triple",
106 135 )
136 sp.add_argument("--python", help="Path to Python executable to use")
107 137 sp.add_argument(
108 138 "--sign-sn",
109 139 help="Subject name (or fragment thereof) of certificate "
@@ -18,8 +18,9 b' from .py2exe import ('
18 18 build_py2exe,
19 19 stage_install,
20 20 )
21 from .pyoxidizer import run_pyoxidizer
21 22 from .util import (
22 find_vc_runtime_files,
23 find_legacy_vc_runtime_files,
23 24 normalize_windows_version,
24 25 process_install_rules,
25 26 read_version_py,
@@ -41,14 +42,14 b' PACKAGE_FILES_METADATA = {'
41 42 }
42 43
43 44
44 def build(
45 def build_with_py2exe(
45 46 source_dir: pathlib.Path,
46 47 build_dir: pathlib.Path,
47 48 python_exe: pathlib.Path,
48 49 iscc_exe: pathlib.Path,
49 50 version=None,
50 51 ):
51 """Build the Inno installer.
52 """Build the Inno installer using py2exe.
52 53
53 54 Build files will be placed in ``build_dir``.
54 55
@@ -61,8 +62,7 b' def build('
61 62
62 63 vc_x64 = r'\x64' in os.environ.get('LIB', '')
63 64 arch = 'x64' if vc_x64 else 'x86'
64 inno_source_dir = source_dir / 'contrib' / 'packaging' / 'inno'
65 inno_build_dir = build_dir / ('inno-%s' % arch)
65 inno_build_dir = build_dir / ('inno-py2exe-%s' % arch)
66 66 staging_dir = inno_build_dir / 'stage'
67 67
68 68 requirements_txt = (
@@ -93,7 +93,7 b' def build('
93 93 process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
94 94
95 95 # hg.exe depends on VC9 runtime DLLs. Copy those into place.
96 for f in find_vc_runtime_files(vc_x64):
96 for f in find_legacy_vc_runtime_files(vc_x64):
97 97 if f.name.endswith('.manifest'):
98 98 basename = 'Microsoft.VC90.CRT.manifest'
99 99 else:
@@ -104,6 +104,62 b' def build('
104 104 print('copying %s to %s' % (f, dest_path))
105 105 shutil.copyfile(f, dest_path)
106 106
107 build_installer(
108 source_dir,
109 inno_build_dir,
110 staging_dir,
111 iscc_exe,
112 version,
113 arch="x64" if vc_x64 else None,
114 suffix="-python2",
115 )
116
117
118 def build_with_pyoxidizer(
119 source_dir: pathlib.Path,
120 build_dir: pathlib.Path,
121 target_triple: str,
122 iscc_exe: pathlib.Path,
123 version=None,
124 ):
125 """Build the Inno installer using PyOxidizer."""
126 if not iscc_exe.exists():
127 raise Exception("%s does not exist" % iscc_exe)
128
129 inno_build_dir = build_dir / ("inno-pyoxidizer-%s" % target_triple)
130 staging_dir = inno_build_dir / "stage"
131
132 inno_build_dir.mkdir(parents=True, exist_ok=True)
133 run_pyoxidizer(source_dir, inno_build_dir, staging_dir, target_triple)
134
135 process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
136
137 build_installer(
138 source_dir,
139 inno_build_dir,
140 staging_dir,
141 iscc_exe,
142 version,
143 arch="x64" if "x86_64" in target_triple else None,
144 )
145
146
147 def build_installer(
148 source_dir: pathlib.Path,
149 inno_build_dir: pathlib.Path,
150 staging_dir: pathlib.Path,
151 iscc_exe: pathlib.Path,
152 version,
153 arch=None,
154 suffix="",
155 ):
156 """Build an Inno installer from staged Mercurial files.
157
158 This function is agnostic about how to build Mercurial. It just
159 cares that Mercurial files are in ``staging_dir``.
160 """
161 inno_source_dir = source_dir / "contrib" / "packaging" / "inno"
162
107 163 # The final package layout is simply a mirror of the staging directory.
108 164 package_files = []
109 165 for root, dirs, files in os.walk(staging_dir):
@@ -158,8 +214,11 b' def build('
158 214
159 215 args = [str(iscc_exe)]
160 216
161 if vc_x64:
162 args.append('/dARCH=x64')
217 if arch:
218 args.append('/dARCH=%s' % arch)
219 args.append('/dSUFFIX=-%s%s' % (arch, suffix))
220 else:
221 args.append('/dSUFFIX=-x86%s' % suffix)
163 222
164 223 if not version:
165 224 version = read_version_py(source_dir)
@@ -29,7 +29,59 b' def extract_zip_to_directory(source: pat'
29 29 zf.extractall(dest)
30 30
31 31
32 def find_vc_runtime_files(x64=False):
32 def find_vc_runtime_dll(x64=False):
33 """Finds Visual C++ Runtime DLL to include in distribution."""
34 # We invoke vswhere to find the latest Visual Studio install.
35 vswhere = (
36 pathlib.Path(os.environ["ProgramFiles(x86)"])
37 / "Microsoft Visual Studio"
38 / "Installer"
39 / "vswhere.exe"
40 )
41
42 if not vswhere.exists():
43 raise Exception(
44 "could not find vswhere.exe: %s does not exist" % vswhere
45 )
46
47 args = [
48 str(vswhere),
49 # -products * is necessary to return results from Build Tools
50 # (as opposed to full IDE installs).
51 "-products",
52 "*",
53 "-requires",
54 "Microsoft.VisualCpp.Redist.14.Latest",
55 "-latest",
56 "-property",
57 "installationPath",
58 ]
59
60 vs_install_path = pathlib.Path(
61 os.fsdecode(subprocess.check_output(args).strip())
62 )
63
64 # This just gets us a path like
65 # C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
66 # Actually vcruntime140.dll is under a path like:
67 # VC\Redist\MSVC\<version>\<arch>\Microsoft.VC14<X>.CRT\vcruntime140.dll.
68
69 arch = "x64" if x64 else "x86"
70
71 search_glob = (
72 r"%s\VC\Redist\MSVC\*\%s\Microsoft.VC14*.CRT\vcruntime140.dll"
73 % (vs_install_path, arch)
74 )
75
76 candidates = glob.glob(search_glob, recursive=True)
77
78 for candidate in reversed(candidates):
79 return pathlib.Path(candidate)
80
81 raise Exception("could not find vcruntime140.dll")
82
83
84 def find_legacy_vc_runtime_files(x64=False):
33 85 """Finds Visual C++ Runtime DLLs to include in distribution."""
34 86 winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS'
35 87
@@ -22,6 +22,7 b' from .py2exe import ('
22 22 build_py2exe,
23 23 stage_install,
24 24 )
25 from .pyoxidizer import run_pyoxidizer
25 26 from .util import (
26 27 extract_zip_to_directory,
27 28 normalize_windows_version,
@@ -121,30 +122,6 b' def run_candle(wix, cwd, wxs, source_dir'
121 122 subprocess.run(args, cwd=str(cwd), check=True)
122 123
123 124
124 def make_post_build_signing_fn(
125 name,
126 subject_name=None,
127 cert_path=None,
128 cert_password=None,
129 timestamp_url=None,
130 ):
131 """Create a callable that will use signtool to sign hg.exe."""
132
133 def post_build_sign(source_dir, build_dir, dist_dir, version):
134 description = '%s %s' % (name, version)
135
136 sign_with_signtool(
137 dist_dir / 'hg.exe',
138 description,
139 subject_name=subject_name,
140 cert_path=cert_path,
141 cert_password=cert_password,
142 timestamp_url=timestamp_url,
143 )
144
145 return post_build_sign
146
147
148 125 def make_files_xml(staging_dir: pathlib.Path, is_x64) -> str:
149 126 """Create XML string listing every file to be installed."""
150 127
@@ -308,27 +285,23 b' def make_files_xml(staging_dir: pathlib.'
308 285 return doc.toprettyxml()
309 286
310 287
311 def build_installer(
288 def build_installer_py2exe(
312 289 source_dir: pathlib.Path,
313 290 python_exe: pathlib.Path,
314 291 msi_name='mercurial',
315 292 version=None,
316 post_build_fn=None,
317 293 extra_packages_script=None,
318 294 extra_wxs: typing.Optional[typing.Dict[str, str]] = None,
319 295 extra_features: typing.Optional[typing.List[str]] = None,
296 signing_info: typing.Optional[typing.Dict[str, str]] = None,
320 297 ):
321 """Build a WiX MSI installer.
298 """Build a WiX MSI installer using py2exe.
322 299
323 300 ``source_dir`` is the path to the Mercurial source tree to use.
324 301 ``arch`` is the target architecture. either ``x86`` or ``x64``.
325 302 ``python_exe`` is the path to the Python executable to use/bundle.
326 303 ``version`` is the Mercurial version string. If not defined,
327 304 ``mercurial/__version__.py`` will be consulted.
328 ``post_build_fn`` is a callable that will be called after building
329 Mercurial but before invoking WiX. It can be used to e.g. facilitate
330 signing. It is passed the paths to the Mercurial source, build, and
331 dist directories and the resolved Mercurial version.
332 305 ``extra_packages_script`` is a command to be run to inject extra packages
333 306 into the py2exe binary. It should stage packages into the virtualenv and
334 307 print a null byte followed by a newline-separated list of packages that
@@ -340,8 +313,6 b' def build_installer('
340 313 arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86'
341 314
342 315 hg_build_dir = source_dir / 'build'
343 dist_dir = source_dir / 'dist'
344 wix_dir = source_dir / 'contrib' / 'packaging' / 'wix'
345 316
346 317 requirements_txt = (
347 318 source_dir / 'contrib' / 'packaging' / 'requirements_win32.txt'
@@ -357,15 +328,6 b' def build_installer('
357 328 extra_packages_script=extra_packages_script,
358 329 )
359 330
360 orig_version = version or find_version(source_dir)
361 version = normalize_windows_version(orig_version)
362 print('using version string: %s' % version)
363 if version != orig_version:
364 print('(normalized from: %s)' % orig_version)
365
366 if post_build_fn:
367 post_build_fn(source_dir, hg_build_dir, dist_dir, version)
368
369 331 build_dir = hg_build_dir / ('wix-%s' % arch)
370 332 staging_dir = build_dir / 'stage'
371 333
@@ -388,13 +350,112 b' def build_installer('
388 350 print('removing %s' % p)
389 351 p.unlink()
390 352
391 wix_pkg, wix_entry = download_entry('wix', hg_build_dir)
392 wix_path = hg_build_dir / ('wix-%s' % wix_entry['version'])
353 return run_wix_packaging(
354 source_dir,
355 build_dir,
356 staging_dir,
357 arch,
358 version=version,
359 python2=True,
360 msi_name=msi_name,
361 suffix="-python2",
362 extra_wxs=extra_wxs,
363 extra_features=extra_features,
364 signing_info=signing_info,
365 )
366
367
368 def build_installer_pyoxidizer(
369 source_dir: pathlib.Path,
370 target_triple: str,
371 msi_name='mercurial',
372 version=None,
373 extra_wxs: typing.Optional[typing.Dict[str, str]] = None,
374 extra_features: typing.Optional[typing.List[str]] = None,
375 signing_info: typing.Optional[typing.Dict[str, str]] = None,
376 ):
377 """Build a WiX MSI installer using PyOxidizer."""
378 hg_build_dir = source_dir / "build"
379 build_dir = hg_build_dir / ("wix-%s" % target_triple)
380 staging_dir = build_dir / "stage"
381
382 arch = "x64" if "x86_64" in target_triple else "x86"
383
384 build_dir.mkdir(parents=True, exist_ok=True)
385 run_pyoxidizer(source_dir, build_dir, staging_dir, target_triple)
386
387 # We also install some extra files.
388 process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
389
390 # And remove some files we don't want.
391 for f in STAGING_REMOVE_FILES:
392 p = staging_dir / f
393 if p.exists():
394 print('removing %s' % p)
395 p.unlink()
396
397 return run_wix_packaging(
398 source_dir,
399 build_dir,
400 staging_dir,
401 arch,
402 version,
403 python2=False,
404 msi_name=msi_name,
405 extra_wxs=extra_wxs,
406 extra_features=extra_features,
407 signing_info=signing_info,
408 )
409
410
411 def run_wix_packaging(
412 source_dir: pathlib.Path,
413 build_dir: pathlib.Path,
414 staging_dir: pathlib.Path,
415 arch: str,
416 version: str,
417 python2: bool,
418 msi_name: typing.Optional[str] = "mercurial",
419 suffix: str = "",
420 extra_wxs: typing.Optional[typing.Dict[str, str]] = None,
421 extra_features: typing.Optional[typing.List[str]] = None,
422 signing_info: typing.Optional[typing.Dict[str, str]] = None,
423 ):
424 """Invokes WiX to package up a built Mercurial.
425
426 ``signing_info`` is a dict defining properties to facilitate signing the
427 installer. Recognized keys include ``name``, ``subject_name``,
428 ``cert_path``, ``cert_password``, and ``timestamp_url``. If populated,
429 we will sign both the hg.exe and the .msi using the signing credentials
430 specified.
431 """
432
433 orig_version = version or find_version(source_dir)
434 version = normalize_windows_version(orig_version)
435 print('using version string: %s' % version)
436 if version != orig_version:
437 print('(normalized from: %s)' % orig_version)
438
439 if signing_info:
440 sign_with_signtool(
441 staging_dir / "hg.exe",
442 "%s %s" % (signing_info["name"], version),
443 subject_name=signing_info["subject_name"],
444 cert_path=signing_info["cert_path"],
445 cert_password=signing_info["cert_password"],
446 timestamp_url=signing_info["timestamp_url"],
447 )
448
449 wix_dir = source_dir / 'contrib' / 'packaging' / 'wix'
450
451 wix_pkg, wix_entry = download_entry('wix', build_dir)
452 wix_path = build_dir / ('wix-%s' % wix_entry['version'])
393 453
394 454 if not wix_path.exists():
395 455 extract_zip_to_directory(wix_pkg, wix_path)
396 456
397 ensure_vc90_merge_modules(hg_build_dir)
457 if python2:
458 ensure_vc90_merge_modules(build_dir)
398 459
399 460 source_build_rel = pathlib.Path(os.path.relpath(source_dir, build_dir))
400 461
@@ -413,7 +474,16 b' def build_installer('
413 474 source = wix_dir / 'mercurial.wxs'
414 475 defines['Version'] = version
415 476 defines['Comments'] = 'Installs Mercurial version %s' % version
416 defines['VCRedistSrcDir'] = str(hg_build_dir)
477
478 if python2:
479 defines["PythonVersion"] = "2"
480 defines['VCRedistSrcDir'] = str(build_dir)
481 else:
482 defines["PythonVersion"] = "3"
483
484 if (staging_dir / "lib").exists():
485 defines["MercurialHasLib"] = "1"
486
417 487 if extra_features:
418 488 assert all(';' not in f for f in extra_features)
419 489 defines['MercurialExtraFeatures'] = ';'.join(extra_features)
@@ -421,7 +491,9 b' def build_installer('
421 491 run_candle(wix_path, build_dir, source, source_build_rel, defines=defines)
422 492
423 493 msi_path = (
424 source_dir / 'dist' / ('%s-%s-%s.msi' % (msi_name, orig_version, arch))
494 source_dir
495 / 'dist'
496 / ('%s-%s-%s%s.msi' % (msi_name, orig_version, arch, suffix))
425 497 )
426 498
427 499 args = [
@@ -448,52 +520,16 b' def build_installer('
448 520
449 521 print('%s created' % msi_path)
450 522
523 if signing_info:
524 sign_with_signtool(
525 msi_path,
526 "%s %s" % (signing_info["name"], version),
527 subject_name=signing_info["subject_name"],
528 cert_path=signing_info["cert_path"],
529 cert_password=signing_info["cert_password"],
530 timestamp_url=signing_info["timestamp_url"],
531 )
532
451 533 return {
452 534 'msi_path': msi_path,
453 535 }
454
455
456 def build_signed_installer(
457 source_dir: pathlib.Path,
458 python_exe: pathlib.Path,
459 name: str,
460 version=None,
461 subject_name=None,
462 cert_path=None,
463 cert_password=None,
464 timestamp_url=None,
465 extra_packages_script=None,
466 extra_wxs=None,
467 extra_features=None,
468 ):
469 """Build an installer with signed executables."""
470
471 post_build_fn = make_post_build_signing_fn(
472 name,
473 subject_name=subject_name,
474 cert_path=cert_path,
475 cert_password=cert_password,
476 timestamp_url=timestamp_url,
477 )
478
479 info = build_installer(
480 source_dir,
481 python_exe=python_exe,
482 msi_name=name.lower(),
483 version=version,
484 post_build_fn=post_build_fn,
485 extra_packages_script=extra_packages_script,
486 extra_wxs=extra_wxs,
487 extra_features=extra_features,
488 )
489
490 description = '%s %s' % (name, version)
491
492 sign_with_signtool(
493 info['msi_path'],
494 description,
495 subject_name=subject_name,
496 cert_path=cert_path,
497 cert_password=cert_password,
498 timestamp_url=timestamp_url,
499 )
@@ -9,14 +9,13 b''
9 9 AppCopyright=Copyright 2005-2020 Matt Mackall and others
10 10 AppName=Mercurial
11 11 AppVersion={#VERSION}
12 OutputBaseFilename=Mercurial-{#VERSION}{#SUFFIX}
12 13 #if ARCH == "x64"
13 14 AppVerName=Mercurial {#VERSION} (64-bit)
14 OutputBaseFilename=Mercurial-{#VERSION}-x64
15 15 ArchitecturesAllowed=x64
16 16 ArchitecturesInstallIn64BitMode=x64
17 17 #else
18 18 AppVerName=Mercurial {#VERSION}
19 OutputBaseFilename=Mercurial-{#VERSION}
20 19 #endif
21 20 InfoAfterFile=../postinstall.txt
22 21 LicenseFile=Copying.txt
@@ -79,16 +79,21 b''
79 79 </Directory>
80 80 </Directory>
81 81
82 <?if $(var.Platform) = "x86" ?>
83 <Merge Id='VCRuntime' DiskId='1' Language='1033'
84 SourceFile='$(var.VCRedistSrcDir)\microsoft.vcxx.crt.x86_msm.msm' />
85 <Merge Id='VCRuntimePolicy' DiskId='1' Language='1033'
86 SourceFile='$(var.VCRedistSrcDir)\policy.x.xx.microsoft.vcxx.crt.x86_msm.msm' />
87 <?else?>
88 <Merge Id='VCRuntime' DiskId='1' Language='1033'
89 SourceFile='$(var.VCRedistSrcDir)\microsoft.vcxx.crt.x64_msm.msm' />
90 <Merge Id='VCRuntimePolicy' DiskId='1' Language='1033'
91 SourceFile='$(var.VCRedistSrcDir)\policy.x.xx.microsoft.vcxx.crt.x64_msm.msm' />
82 <!-- Install VCRedist merge modules on Python 2. On Python 3,
83 vcruntimeXXX.dll is part of the install layout and gets picked up
84 as a regular file. -->
85 <?if $(var.PythonVersion) = "2" ?>
86 <?if $(var.Platform) = "x86" ?>
87 <Merge Id='VCRuntime' DiskId='1' Language='1033'
88 SourceFile='$(var.VCRedistSrcDir)\microsoft.vcxx.crt.x86_msm.msm' />
89 <Merge Id='VCRuntimePolicy' DiskId='1' Language='1033'
90 SourceFile='$(var.VCRedistSrcDir)\policy.x.xx.microsoft.vcxx.crt.x86_msm.msm' />
91 <?else?>
92 <Merge Id='VCRuntime' DiskId='1' Language='1033'
93 SourceFile='$(var.VCRedistSrcDir)\microsoft.vcxx.crt.x64_msm.msm' />
94 <Merge Id='VCRuntimePolicy' DiskId='1' Language='1033'
95 SourceFile='$(var.VCRedistSrcDir)\policy.x.xx.microsoft.vcxx.crt.x64_msm.msm' />
96 <?endif?>
92 97 <?endif?>
93 98 </Directory>
94 99
@@ -101,10 +106,14 b''
101 106 <ComponentGroupRef Id="hg.group.ROOT" />
102 107 <ComponentGroupRef Id="hg.group.defaultrc" />
103 108 <ComponentGroupRef Id="hg.group.helptext" />
104 <ComponentGroupRef Id="hg.group.lib" />
109 <?ifdef MercurialHasLib?>
110 <ComponentGroupRef Id="hg.group.lib" />
111 <?endif?>
105 112 <ComponentGroupRef Id="hg.group.templates" />
106 <MergeRef Id='VCRuntime' />
107 <MergeRef Id='VCRuntimePolicy' />
113 <?if $(var.PythonVersion) = "2" ?>
114 <MergeRef Id='VCRuntime' />
115 <MergeRef Id='VCRuntimePolicy' />
116 <?endif?>
108 117 </Feature>
109 118 <?ifdef MercurialExtraFeatures?>
110 119 <?foreach EXTRAFEAT in $(var.MercurialExtraFeatures)?>
@@ -2558,7 +2558,7 b' def diff('
2558 2558 fctx2 is not None
2559 2559 ), b'fctx2 unexpectly None in diff hunks filtering'
2560 2560 hunks = hunksfilterfn(fctx2, hunks)
2561 text = b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
2561 text = b''.join(b''.join(hlines) for hrange, hlines in hunks)
2562 2562 if hdr and (text or len(hdr) > 1):
2563 2563 yield b'\n'.join(hdr) + b'\n'
2564 2564 if text:
@@ -1,13 +1,24 b''
1 1 ROOT = CWD + "/../.."
2 2
3 def make_exe():
4 dist = default_python_distribution()
3 # Code to run in Python interpreter.
4 RUN_CODE = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()"
5
6
7 set_build_path(ROOT + "/build/pyoxidizer")
8
5 9
6 code = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()"
10 def make_distribution():
11 return default_python_distribution()
12
7 13
14 def make_distribution_windows():
15 return default_python_distribution(flavor="standalone_dynamic")
16
17
18 def make_exe(dist):
8 19 config = PythonInterpreterConfig(
9 20 raw_allocator = "system",
10 run_eval = code,
21 run_eval = RUN_CODE,
11 22 # We want to let the user load extensions from the file system
12 23 filesystem_importer = True,
13 24 # We need this to make resourceutil happy, since it looks for sys.frozen.
@@ -24,30 +35,65 b' def make_exe():'
24 35 extension_module_filter = "all",
25 36 )
26 37
27 exe.add_python_resources(dist.pip_install([ROOT]))
38 # Add Mercurial to resources.
39 for resource in dist.pip_install(["--verbose", ROOT]):
40 # This is a bit wonky and worth explaining.
41 #
42 # Various parts of Mercurial don't yet support loading package
43 # resources via the ResourceReader interface. Or, not having
44 # file-based resources would be too inconvenient for users.
45 #
46 # So, for package resources, we package them both in the
47 # filesystem as well as in memory. If both are defined,
48 # PyOxidizer will prefer the in-memory location. So even
49 # if the filesystem file isn't packaged in the location
50 # specified here, we should never encounter an errors as the
51 # resource will always be available in memory.
52 if type(resource) == "PythonPackageResource":
53 exe.add_filesystem_relative_python_resource(".", resource)
54 exe.add_in_memory_python_resource(resource)
55 else:
56 exe.add_python_resource(resource)
57
58 # On Windows, we install extra packages for convenience.
59 if "windows" in BUILD_TARGET_TRIPLE:
60 exe.add_python_resources(
61 dist.pip_install(["-r", ROOT + "/contrib/packaging/requirements_win32.txt"])
62 )
28 63
29 64 return exe
30 65
31 def make_install(exe):
66
67 def make_manifest(dist, exe):
32 68 m = FileManifest()
33
34 # `hg` goes in root directory.
35 69 m.add_python_resource(".", exe)
36 70
37 templates = glob(
38 include = [ROOT + "/mercurial/templates/**/*"],
39 strip_prefix = ROOT + "/mercurial/",
40 )
41 m.add_manifest(templates)
71 return m
42 72
43 return m
44 73
45 74 def make_embedded_resources(exe):
46 75 return exe.to_embedded_resources()
47 76
48 register_target("exe", make_exe)
49 register_target("app", make_install, depends = ["exe"], default = True)
50 register_target("embedded", make_embedded_resources, depends = ["exe"], default_build_script = True)
77
78 register_target("distribution_posix", make_distribution)
79 register_target("distribution_windows", make_distribution_windows)
80
81 register_target("exe_posix", make_exe, depends = ["distribution_posix"])
82 register_target("exe_windows", make_exe, depends = ["distribution_windows"])
83
84 register_target(
85 "app_posix",
86 make_manifest,
87 depends = ["distribution_posix", "exe_posix"],
88 default = "windows" not in BUILD_TARGET_TRIPLE,
89 )
90 register_target(
91 "app_windows",
92 make_manifest,
93 depends = ["distribution_windows", "exe_windows"],
94 default = "windows" in BUILD_TARGET_TRIPLE,
95 )
96
51 97 resolve_targets()
52 98
53 99 # END OF COMMON USER-ADJUSTED SETTINGS.
@@ -55,5 +101,4 b' resolve_targets()'
55 101 # Everything below this is typically managed by PyOxidizer and doesn't need
56 102 # to be updated by people.
57 103
58 PYOXIDIZER_VERSION = "0.7.0-pre"
59 PYOXIDIZER_COMMIT = "c772a1379c3026314eda1c8ea244b86c0658951d"
104 PYOXIDIZER_VERSION = "0.7.0"
@@ -27,6 +27,7 b' New errors are not allowed. Warnings are'
27 27 Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob)
28 28 Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob)
29 29 Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob)
30 Skipping contrib/packaging/hgpackaging/pyoxidizer.py it has no-che?k-code (glob)
30 31 Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob)
31 32 Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob)
32 33 Skipping i18n/polib.py it has no-che?k-code (glob)
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now