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 | def build_inno( |
|
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 | c = hga.aws_connection(aws_region) |
|
74 | c = hga.aws_connection(aws_region) | |
69 | image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) |
|
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 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) |
|
81 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) | |
76 |
|
82 | |||
|
83 | for py_version in python_version: | |||
77 | for a in arch: |
|
84 | for a in arch: | |
78 | windows.build_inno_installer( |
|
85 | windows.build_inno_installer( | |
79 |
instance.winrm_client, |
|
86 | instance.winrm_client, | |
|
87 | py_version, | |||
|
88 | a, | |||
|
89 | DIST_PATH, | |||
|
90 | version=version, | |||
80 | ) |
|
91 | ) | |
81 |
|
92 | |||
82 |
|
93 | |||
83 | def build_wix( |
|
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 | c = hga.aws_connection(aws_region) |
|
103 | c = hga.aws_connection(aws_region) | |
87 | image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) |
|
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 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) |
|
110 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) | |
94 |
|
111 | |||
|
112 | for py_version in python_version: | |||
95 | for a in arch: |
|
113 | for a in arch: | |
96 | windows.build_wix_installer( |
|
114 | windows.build_wix_installer( | |
97 |
instance.winrm_client, |
|
115 | instance.winrm_client, | |
|
116 | py_version, | |||
|
117 | a, | |||
|
118 | DIST_PATH, | |||
|
119 | version=version, | |||
98 | ) |
|
120 | ) | |
99 |
|
121 | |||
100 |
|
122 | |||
101 | def build_windows_wheel( |
|
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 | c = hga.aws_connection(aws_region) |
|
131 | c = hga.aws_connection(aws_region) | |
105 | image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name) |
|
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 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) |
|
138 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) | |
112 |
|
139 | |||
|
140 | for py_version in python_version: | |||
113 | for a in arch: |
|
141 | for a in arch: | |
114 |
windows.build_wheel( |
|
142 | windows.build_wheel( | |
|
143 | instance.winrm_client, py_version, a, DIST_PATH | |||
|
144 | ) | |||
115 |
|
145 | |||
116 |
|
146 | |||
117 | def build_all_windows_packages( |
|
147 | def build_all_windows_packages( | |
@@ -128,16 +158,24 b' def build_all_windows_packages(' | |||||
128 |
|
158 | |||
129 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) |
|
159 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) | |
130 |
|
160 | |||
131 | for arch in ('x86', 'x64'): |
|
161 | for py_version in ("2.7", "3.7", "3.8"): | |
|
162 | for arch in ("x86", "x64"): | |||
132 | windows.purge_hg(winrm_client) |
|
163 | windows.purge_hg(winrm_client) | |
133 |
windows.build_wheel( |
|
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'): | |||
134 | windows.purge_hg(winrm_client) |
|
173 | windows.purge_hg(winrm_client) | |
135 | windows.build_inno_installer( |
|
174 | windows.build_inno_installer( | |
136 | winrm_client, arch, DIST_PATH, version=version |
|
175 | winrm_client, py_version, arch, DIST_PATH, version=version | |
137 | ) |
|
176 | ) | |
138 | windows.purge_hg(winrm_client) |
|
|||
139 | windows.build_wix_installer( |
|
177 | windows.build_wix_installer( | |
140 | winrm_client, arch, DIST_PATH, version=version |
|
178 | winrm_client, py_version, arch, DIST_PATH, version=version | |
141 | ) |
|
179 | ) | |
142 |
|
180 | |||
143 |
|
181 | |||
@@ -293,6 +331,14 b' def get_parser():' | |||||
293 | 'build-inno', help='Build Inno Setup installer(s)', |
|
331 | 'build-inno', help='Build Inno Setup installer(s)', | |
294 | ) |
|
332 | ) | |
295 | sp.add_argument( |
|
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 | '--arch', |
|
342 | '--arch', | |
297 | help='Architecture to build for', |
|
343 | help='Architecture to build for', | |
298 | choices={'x86', 'x64'}, |
|
344 | choices={'x86', 'x64'}, | |
@@ -316,6 +362,13 b' def get_parser():' | |||||
316 | 'build-windows-wheel', help='Build Windows wheel(s)', |
|
362 | 'build-windows-wheel', help='Build Windows wheel(s)', | |
317 | ) |
|
363 | ) | |
318 | sp.add_argument( |
|
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 | '--arch', |
|
372 | '--arch', | |
320 | help='Architecture to build for', |
|
373 | help='Architecture to build for', | |
321 | choices={'x86', 'x64'}, |
|
374 | choices={'x86', 'x64'}, | |
@@ -334,6 +387,14 b' def get_parser():' | |||||
334 |
|
387 | |||
335 | sp = subparsers.add_parser('build-wix', help='Build WiX installer(s)') |
|
388 | sp = subparsers.add_parser('build-wix', help='Build WiX installer(s)') | |
336 | sp.add_argument( |
|
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 | '--arch', |
|
398 | '--arch', | |
338 | help='Architecture to build for', |
|
399 | help='Architecture to build for', | |
339 | choices={'x86', 'x64'}, |
|
400 | choices={'x86', 'x64'}, |
@@ -72,8 +72,10 b' echo "${RUSTUP_INIT_SHA256} rustup-init"' | |||||
72 |
|
72 | |||
73 | chmod +x rustup-init |
|
73 | chmod +x rustup-init | |
74 | sudo -H -u hg -g hg ./rustup-init -y |
|
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. |
|
75 | sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup install 1.31.1 1.42.0 | |
76 | sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup component add clippy |
|
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 | Write-Output "updated Mercurial working directory to {revision}" |
|
68 | Write-Output "updated Mercurial working directory to {revision}" | |
69 | '''.lstrip() |
|
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 | Set-Location C:\hgdev\src |
|
82 | Set-Location C:\hgdev\src | |
73 | $python = "C:\hgdev\python27-{arch}\python.exe" |
|
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 | if ($LASTEXITCODE -ne 0) {{ |
|
85 | if ($LASTEXITCODE -ne 0) {{ | |
76 | throw "process exited non-0: $LASTEXITCODE" |
|
86 | throw "process exited non-0: $LASTEXITCODE" | |
77 | }} |
|
87 | }} | |
@@ -79,13 +89,23 b' if ($LASTEXITCODE -ne 0) {{' | |||||
79 |
|
89 | |||
80 | BUILD_WHEEL = r''' |
|
90 | BUILD_WHEEL = r''' | |
81 | Set-Location C:\hgdev\src |
|
91 | Set-Location C:\hgdev\src | |
82 |
C:\hgdev\python |
|
92 | C:\hgdev\python{python_version}-{arch}\python.exe -m pip wheel --wheel-dir dist . | |
83 | if ($LASTEXITCODE -ne 0) {{ |
|
93 | if ($LASTEXITCODE -ne 0) {{ | |
84 | throw "process exited non-0: $LASTEXITCODE" |
|
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 | Set-Location C:\hgdev\src |
|
109 | Set-Location C:\hgdev\src | |
90 | $python = "C:\hgdev\python27-{arch}\python.exe" |
|
110 | $python = "C:\hgdev\python27-{arch}\python.exe" | |
91 | C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py wix --python $python {extra_args} |
|
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 |
|
|
124 | WHEEL_FILENAME_PYTHON27_X86 = 'mercurial-{version}-cp27-cp27m-win32.whl' | |
105 |
|
|
125 | WHEEL_FILENAME_PYTHON27_X64 = 'mercurial-{version}-cp27-cp27m-win_amd64.whl' | |
106 |
|
|
126 | WHEEL_FILENAME_PYTHON37_X86 = 'mercurial-{version}-cp37-cp37m-win32.whl' | |
107 |
|
|
127 | WHEEL_FILENAME_PYTHON37_X64 = 'mercurial-{version}-cp37-cp37m-win_amd64.whl' | |
108 |
|
|
128 | WHEEL_FILENAME_PYTHON38_X86 = 'mercurial-{version}-cp38-cp38-win32.whl' | |
109 |
|
|
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 | MERCURIAL_SCM_BASE_URL = 'https://mercurial-scm.org/release/windows' |
|
141 | MERCURIAL_SCM_BASE_URL = 'https://mercurial-scm.org/release/windows' | |
112 |
|
142 | |||
113 | X86_USER_AGENT_PATTERN = '.*Windows.*' |
|
143 | X86_USER_AGENT_PATTERN = '.*Windows.*' | |
114 | X64_USER_AGENT_PATTERN = '.*Windows.*(WOW|x)64.*' |
|
144 | X64_USER_AGENT_PATTERN = '.*Windows.*(WOW|x)64.*' | |
115 |
|
145 | |||
116 |
|
|
146 | EXE_PYTHON2_X86_DESCRIPTION = ( | |
117 | 'Mercurial {version} Inno Setup installer - x86 Windows ' |
|
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 | '- does not require admin rights' |
|
157 | '- does not require admin rights' | |
119 | ) |
|
158 | ) | |
120 |
|
|
159 | EXE_PYTHON3_X64_DESCRIPTION = ( | |
121 | 'Mercurial {version} Inno Setup installer - x64 Windows ' |
|
160 | 'Mercurial {version} Inno Setup installer - x64 Windows (Python 3) ' | |
122 | '- does not require admin rights' |
|
161 | '- does not require admin rights' | |
123 | ) |
|
162 | ) | |
124 |
|
|
163 | MSI_PYTHON2_X86_DESCRIPTION = ( | |
125 |
'Mercurial {version} MSI installer - x86 Windows |
|
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 |
|
|
171 | MSI_PYTHON3_X86_DESCRIPTION = ( | |
128 |
'Mercurial {version} MSI installer - x6 |
|
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 | def build_inno_installer( |
|
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 | """Build the Inno Setup installer on a remote machine. |
|
338 | """Build the Inno Setup installer on a remote machine. | |
286 |
|
339 | |||
287 | Using a WinRM client, remote commands are executed to build |
|
340 | Using a WinRM client, remote commands are executed to build | |
288 | a Mercurial Inno Setup installer. |
|
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 | |||
|
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) | |||
|
361 | ||||
|
362 | ps = BUILD_INNO_PYTHON3.format( | |||
|
363 | pyoxidizer_target=target_triple, version=version, | |||
|
364 | ) | |||
|
365 | else: | |||
292 | extra_args = [] |
|
366 | extra_args = [] | |
293 | if version: |
|
367 | if version: | |
294 | extra_args.extend(['--version', version]) |
|
368 | extra_args.extend(['--version', version]) | |
295 |
|
369 | |||
296 | ps = get_vc_prefix(arch) + BUILD_INNO.format( |
|
370 | ps = get_vc_prefix(arch) + BUILD_INNO_PYTHON2.format( | |
297 | arch=arch, extra_args=' '.join(extra_args) |
|
371 | arch=arch, extra_args=' '.join(extra_args) | |
298 | ) |
|
372 | ) | |
|
373 | ||||
299 | run_powershell(winrm_client, ps) |
|
374 | run_powershell(winrm_client, ps) | |
300 | copy_latest_dist(winrm_client, '*.exe', dest_path) |
|
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 | """Build Python wheels on a remote machine. |
|
381 | """Build Python wheels on a remote machine. | |
305 |
|
382 | |||
306 | Using a WinRM client, remote commands are executed to build a Python wheel |
|
383 | Using a WinRM client, remote commands are executed to build a Python wheel | |
307 | for Mercurial. |
|
384 | for Mercurial. | |
308 | """ |
|
385 | """ | |
309 | print('Building Windows wheel for %s' % arch) |
|
386 | print('Building Windows wheel for Python %s %s' % (python_version, arch)) | |
310 | ps = get_vc_prefix(arch) + BUILD_WHEEL.format(arch=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 | run_powershell(winrm_client, ps) |
|
396 | run_powershell(winrm_client, ps) | |
312 | copy_latest_dist(winrm_client, '*.whl', dest_path) |
|
397 | copy_latest_dist(winrm_client, '*.whl', dest_path) | |
313 |
|
398 | |||
314 |
|
399 | |||
315 | def build_wix_installer( |
|
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 | """Build the WiX installer on a remote machine. |
|
407 | """Build the WiX installer on a remote machine. | |
319 |
|
408 | |||
320 | Using a WinRM client, remote commands are executed to build a WiX installer. |
|
409 | Using a WinRM client, remote commands are executed to build a WiX installer. | |
321 | """ |
|
410 | """ | |
322 | print('Building WiX installer for %s' % arch) |
|
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 | ) | |||
|
419 | ||||
|
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: | |||
323 | extra_args = [] |
|
431 | extra_args = [] | |
324 | if version: |
|
432 | if version: | |
325 | extra_args.extend(['--version', version]) |
|
433 | extra_args.extend(['--version', version]) | |
326 |
|
434 | |||
327 | ps = get_vc_prefix(arch) + BUILD_WIX.format( |
|
435 | ps = get_vc_prefix(arch) + BUILD_WIX_PYTHON2.format( | |
328 | arch=arch, extra_args=' '.join(extra_args) |
|
436 | arch=arch, extra_args=' '.join(extra_args) | |
329 | ) |
|
437 | ) | |
|
438 | ||||
330 | run_powershell(winrm_client, ps) |
|
439 | run_powershell(winrm_client, ps) | |
331 | copy_latest_dist(winrm_client, '*.msi', dest_path) |
|
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 | def resolve_wheel_artifacts(dist_path: pathlib.Path, version: str): |
|
466 | def resolve_wheel_artifacts(dist_path: pathlib.Path, version: str): | |
358 | return ( |
|
467 | return ( | |
359 |
dist_path / |
|
468 | dist_path / WHEEL_FILENAME_PYTHON27_X86.format(version=version), | |
360 |
dist_path / |
|
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 | def resolve_all_artifacts(dist_path: pathlib.Path, version: str): |
|
477 | def resolve_all_artifacts(dist_path: pathlib.Path, version: str): | |
365 | return ( |
|
478 | return ( | |
366 |
dist_path / |
|
479 | dist_path / WHEEL_FILENAME_PYTHON27_X86.format(version=version), | |
367 |
dist_path / |
|
480 | dist_path / WHEEL_FILENAME_PYTHON27_X64.format(version=version), | |
368 |
dist_path / |
|
481 | dist_path / WHEEL_FILENAME_PYTHON37_X86.format(version=version), | |
369 |
dist_path / |
|
482 | dist_path / WHEEL_FILENAME_PYTHON37_X64.format(version=version), | |
370 |
dist_path / |
|
483 | dist_path / WHEEL_FILENAME_PYTHON38_X86.format(version=version), | |
371 |
dist_path / |
|
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 | def generate_latest_dat(version: str): |
|
496 | def generate_latest_dat(version: str): | |
376 |
x86_exe_filename = |
|
497 | python2_x86_exe_filename = EXE_FILENAME_PYTHON2_X86.format(version=version) | |
377 |
x64_exe_filename = |
|
498 | python2_x64_exe_filename = EXE_FILENAME_PYTHON2_X64.format(version=version) | |
378 |
|
|
499 | python3_x86_exe_filename = EXE_FILENAME_PYTHON3_X86.format(version=version) | |
379 |
|
|
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 | entries = ( |
|
506 | entries = ( | |
382 | ( |
|
507 | ( | |
383 | '10', |
|
508 | '10', | |
384 | version, |
|
509 | version, | |
385 | X86_USER_AGENT_PATTERN, |
|
510 | X86_USER_AGENT_PATTERN, | |
386 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x86_exe_filename), |
|
511 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x86_exe_filename), | |
387 |
|
|
512 | EXE_PYTHON3_X86_DESCRIPTION.format(version=version), | |
388 | ), |
|
513 | ), | |
389 | ( |
|
514 | ( | |
390 | '10', |
|
515 | '10', | |
391 | version, |
|
516 | version, | |
392 | X64_USER_AGENT_PATTERN, |
|
517 | X64_USER_AGENT_PATTERN, | |
393 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_exe_filename), |
|
518 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x64_exe_filename), | |
394 |
|
|
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 | '10', |
|
536 | '10', | |
398 | version, |
|
537 | version, | |
399 | X86_USER_AGENT_PATTERN, |
|
538 | X86_USER_AGENT_PATTERN, | |
400 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x86_msi_filename), |
|
539 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x86_msi_filename), | |
401 |
|
|
540 | MSI_PYTHON3_X86_DESCRIPTION.format(version=version), | |
402 | ), |
|
541 | ), | |
403 | ( |
|
542 | ( | |
404 | '10', |
|
543 | '10', | |
405 | version, |
|
544 | version, | |
406 | X64_USER_AGENT_PATTERN, |
|
545 | X64_USER_AGENT_PATTERN, | |
407 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_msi_filename), |
|
546 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x64_msi_filename), | |
408 |
|
|
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 | $MERCURIAL_WHEEL_URL = "https://files.pythonhosted.org/packages/6d/47/e031e47f7fe9b16e4e3383da47e2b0a7eae6e603996bc67a03ec4fa1b3f4/$MERCURIAL_WHEEL_FILENAME" |
|
64 | $MERCURIAL_WHEEL_URL = "https://files.pythonhosted.org/packages/6d/47/e031e47f7fe9b16e4e3383da47e2b0a7eae6e603996bc67a03ec4fa1b3f4/$MERCURIAL_WHEEL_FILENAME" | |
65 | $MERCURIAL_WHEEL_SHA256 = "1d18c7f6ca1456f0f62ee65c9a50c14cbba48ce6e924930cdb10537f5c9eaf5f" |
|
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 | # Writing progress slows down downloads substantially. So disable it. |
|
70 | # Writing progress slows down downloads substantially. So disable it. | |
68 | $progressPreference = 'silentlyContinue' |
|
71 | $progressPreference = 'silentlyContinue' | |
69 |
|
72 | |||
@@ -116,6 +119,20 b' function Install-Python3($name, $install' | |||||
116 | Invoke-Process ${dest}\python.exe $pip |
|
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 | function Install-Dependencies($prefix) { |
|
136 | function Install-Dependencies($prefix) { | |
120 | if (!(Test-Path -Path $prefix\assets)) { |
|
137 | if (!(Test-Path -Path $prefix\assets)) { | |
121 | New-Item -Path $prefix\assets -ItemType Directory |
|
138 | New-Item -Path $prefix\assets -ItemType Directory | |
@@ -140,6 +157,7 b' function Install-Dependencies($prefix) {' | |||||
140 | Secure-Download $INNO_SETUP_URL ${prefix}\assets\InnoSetup.exe $INNO_SETUP_SHA256 |
|
157 | Secure-Download $INNO_SETUP_URL ${prefix}\assets\InnoSetup.exe $INNO_SETUP_SHA256 | |
141 | Secure-Download $MINGW_BIN_URL ${prefix}\assets\mingw-get-bin.zip $MINGW_BIN_SHA256 |
|
158 | Secure-Download $MINGW_BIN_URL ${prefix}\assets\mingw-get-bin.zip $MINGW_BIN_SHA256 | |
142 | Secure-Download $MERCURIAL_WHEEL_URL ${prefix}\assets\${MERCURIAL_WHEEL_FILENAME} $MERCURIAL_WHEEL_SHA256 |
|
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 | Write-Output "installing Python 2.7 32-bit" |
|
162 | Write-Output "installing Python 2.7 32-bit" | |
145 | Invoke-Process msiexec.exe "/i ${prefix}\assets\python27-x86.msi /l* ${prefix}\assets\python27-x86.log /q TARGETDIR=${prefix}\python27-x86 ALLUSERS=" |
|
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 | Write-Output "installing Visual Studio 2017 Build Tools and SDKs" |
|
181 | Write-Output "installing Visual Studio 2017 Build Tools and SDKs" | |
164 | 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" |
|
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 | Write-Output "installing Visual C++ 9.0 for Python 2.7" |
|
186 | Write-Output "installing Visual C++ 9.0 for Python 2.7" | |
167 | Invoke-Process msiexec.exe "/i ${prefix}\assets\VCForPython27.msi /l* ${prefix}\assets\VCForPython27.log /q" |
|
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 | SOURCE_DIR = HERE.parent.parent.parent |
|
20 | SOURCE_DIR = HERE.parent.parent.parent | |
21 |
|
21 | |||
22 |
|
22 | |||
23 | def build_inno(python=None, iscc=None, version=None): |
|
23 | def build_inno(pyoxidizer_target=None, python=None, iscc=None, version=None): | |
24 | if not os.path.isabs(python): |
|
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 | raise Exception("--python arg must be an absolute path") |
|
28 | raise Exception("--python arg must be an absolute path") | |
26 |
|
29 | |||
27 | if iscc: |
|
30 | if iscc: | |
@@ -35,13 +38,19 b' def build_inno(python=None, iscc=None, v' | |||||
35 |
|
38 | |||
36 | build_dir = SOURCE_DIR / "build" |
|
39 | build_dir = SOURCE_DIR / "build" | |
37 |
|
40 | |||
38 | inno.build( |
|
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( | |||
39 | SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version, |
|
47 | SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version, | |
40 | ) |
|
48 | ) | |
41 |
|
49 | |||
42 |
|
50 | |||
43 | def build_wix( |
|
51 | def build_wix( | |
44 | name=None, |
|
52 | name=None, | |
|
53 | pyoxidizer_target=None, | |||
45 | python=None, |
|
54 | python=None, | |
46 | version=None, |
|
55 | version=None, | |
47 | sign_sn=None, |
|
56 | sign_sn=None, | |
@@ -52,17 +61,29 b' def build_wix(' | |||||
52 | extra_wxs=None, |
|
61 | extra_wxs=None, | |
53 | extra_features=None, |
|
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 | kwargs = { |
|
70 | kwargs = { | |
57 | "source_dir": SOURCE_DIR, |
|
71 | "source_dir": SOURCE_DIR, | |
58 | "python_exe": pathlib.Path(python), |
|
|||
59 | "version": version, |
|
72 | "version": version, | |
60 | } |
|
73 | } | |
61 |
|
74 | |||
62 | if not os.path.isabs(python): |
|
75 | if pyoxidizer_target: | |
63 | raise Exception("--python arg must be an absolute path") |
|
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 | if extra_packages_script: |
|
82 | if extra_packages_script: | |
|
83 | if pyoxidizer_target: | |||
|
84 | raise Exception( | |||
|
85 | "pyoxidizer does not support --extra-packages-script" | |||
|
86 | ) | |||
66 | kwargs["extra_packages_script"] = extra_packages_script |
|
87 | kwargs["extra_packages_script"] = extra_packages_script | |
67 | if extra_wxs: |
|
88 | if extra_wxs: | |
68 | kwargs["extra_wxs"] = dict( |
|
89 | kwargs["extra_wxs"] = dict( | |
@@ -72,12 +93,13 b' def build_wix(' | |||||
72 | kwargs["extra_features"] = extra_features.split(",") |
|
93 | kwargs["extra_features"] = extra_features.split(",") | |
73 |
|
94 | |||
74 | if sign_sn or sign_cert: |
|
95 | if sign_sn or sign_cert: | |
75 | fn = wix.build_signed_installer |
|
96 | kwargs["signing_info"] = { | |
76 |
|
|
97 | "name": name, | |
77 |
|
|
98 | "subject_name": sign_sn, | |
78 |
|
|
99 | "cert_path": sign_cert, | |
79 |
|
|
100 | "cert_password": sign_password, | |
80 |
|
|
101 | "timestamp_url": sign_timestamp_url, | |
|
102 | } | |||
81 |
|
103 | |||
82 | fn(**kwargs) |
|
104 | fn(**kwargs) | |
83 |
|
105 | |||
@@ -88,7 +110,12 b' def get_parser():' | |||||
88 | subparsers = parser.add_subparsers() |
|
110 | subparsers = parser.add_subparsers() | |
89 |
|
111 | |||
90 | sp = subparsers.add_parser("inno", help="Build Inno Setup installer") |
|
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 | sp.add_argument("--iscc", help="path to iscc.exe to use") |
|
119 | sp.add_argument("--iscc", help="path to iscc.exe to use") | |
93 | sp.add_argument( |
|
120 | sp.add_argument( | |
94 | "--version", |
|
121 | "--version", | |
@@ -102,8 +129,11 b' def get_parser():' | |||||
102 | ) |
|
129 | ) | |
103 | sp.add_argument("--name", help="Application name", default="Mercurial") |
|
130 | sp.add_argument("--name", help="Application name", default="Mercurial") | |
104 | sp.add_argument( |
|
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 | sp.add_argument( |
|
137 | sp.add_argument( | |
108 | "--sign-sn", |
|
138 | "--sign-sn", | |
109 | help="Subject name (or fragment thereof) of certificate " |
|
139 | help="Subject name (or fragment thereof) of certificate " |
@@ -18,8 +18,9 b' from .py2exe import (' | |||||
18 | build_py2exe, |
|
18 | build_py2exe, | |
19 | stage_install, |
|
19 | stage_install, | |
20 | ) |
|
20 | ) | |
|
21 | from .pyoxidizer import run_pyoxidizer | |||
21 | from .util import ( |
|
22 | from .util import ( | |
22 | find_vc_runtime_files, |
|
23 | find_legacy_vc_runtime_files, | |
23 | normalize_windows_version, |
|
24 | normalize_windows_version, | |
24 | process_install_rules, |
|
25 | process_install_rules, | |
25 | read_version_py, |
|
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 | source_dir: pathlib.Path, |
|
46 | source_dir: pathlib.Path, | |
46 | build_dir: pathlib.Path, |
|
47 | build_dir: pathlib.Path, | |
47 | python_exe: pathlib.Path, |
|
48 | python_exe: pathlib.Path, | |
48 | iscc_exe: pathlib.Path, |
|
49 | iscc_exe: pathlib.Path, | |
49 | version=None, |
|
50 | version=None, | |
50 | ): |
|
51 | ): | |
51 | """Build the Inno installer. |
|
52 | """Build the Inno installer using py2exe. | |
52 |
|
53 | |||
53 | Build files will be placed in ``build_dir``. |
|
54 | Build files will be placed in ``build_dir``. | |
54 |
|
55 | |||
@@ -61,8 +62,7 b' def build(' | |||||
61 |
|
62 | |||
62 | vc_x64 = r'\x64' in os.environ.get('LIB', '') |
|
63 | vc_x64 = r'\x64' in os.environ.get('LIB', '') | |
63 | arch = 'x64' if vc_x64 else 'x86' |
|
64 | arch = 'x64' if vc_x64 else 'x86' | |
64 | inno_source_dir = source_dir / 'contrib' / 'packaging' / 'inno' |
|
65 | inno_build_dir = build_dir / ('inno-py2exe-%s' % arch) | |
65 | inno_build_dir = build_dir / ('inno-%s' % arch) |
|
|||
66 | staging_dir = inno_build_dir / 'stage' |
|
66 | staging_dir = inno_build_dir / 'stage' | |
67 |
|
67 | |||
68 | requirements_txt = ( |
|
68 | requirements_txt = ( | |
@@ -93,7 +93,7 b' def build(' | |||||
93 | process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir) |
|
93 | process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir) | |
94 |
|
94 | |||
95 | # hg.exe depends on VC9 runtime DLLs. Copy those into place. |
|
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 | if f.name.endswith('.manifest'): |
|
97 | if f.name.endswith('.manifest'): | |
98 | basename = 'Microsoft.VC90.CRT.manifest' |
|
98 | basename = 'Microsoft.VC90.CRT.manifest' | |
99 | else: |
|
99 | else: | |
@@ -104,6 +104,62 b' def build(' | |||||
104 | print('copying %s to %s' % (f, dest_path)) |
|
104 | print('copying %s to %s' % (f, dest_path)) | |
105 | shutil.copyfile(f, dest_path) |
|
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 | # The final package layout is simply a mirror of the staging directory. |
|
163 | # The final package layout is simply a mirror of the staging directory. | |
108 | package_files = [] |
|
164 | package_files = [] | |
109 | for root, dirs, files in os.walk(staging_dir): |
|
165 | for root, dirs, files in os.walk(staging_dir): | |
@@ -158,8 +214,11 b' def build(' | |||||
158 |
|
214 | |||
159 | args = [str(iscc_exe)] |
|
215 | args = [str(iscc_exe)] | |
160 |
|
216 | |||
161 |
if |
|
217 | if arch: | |
162 |
args.append('/dARCH= |
|
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 | if not version: |
|
223 | if not version: | |
165 | version = read_version_py(source_dir) |
|
224 | version = read_version_py(source_dir) |
@@ -29,7 +29,59 b' def extract_zip_to_directory(source: pat' | |||||
29 | zf.extractall(dest) |
|
29 | zf.extractall(dest) | |
30 |
|
30 | |||
31 |
|
31 | |||
32 |
def find_vc_runtime_ |
|
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 | """Finds Visual C++ Runtime DLLs to include in distribution.""" |
|
85 | """Finds Visual C++ Runtime DLLs to include in distribution.""" | |
34 | winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS' |
|
86 | winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS' | |
35 |
|
87 |
@@ -22,6 +22,7 b' from .py2exe import (' | |||||
22 | build_py2exe, |
|
22 | build_py2exe, | |
23 | stage_install, |
|
23 | stage_install, | |
24 | ) |
|
24 | ) | |
|
25 | from .pyoxidizer import run_pyoxidizer | |||
25 | from .util import ( |
|
26 | from .util import ( | |
26 | extract_zip_to_directory, |
|
27 | extract_zip_to_directory, | |
27 | normalize_windows_version, |
|
28 | normalize_windows_version, | |
@@ -121,30 +122,6 b' def run_candle(wix, cwd, wxs, source_dir' | |||||
121 | subprocess.run(args, cwd=str(cwd), check=True) |
|
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 | def make_files_xml(staging_dir: pathlib.Path, is_x64) -> str: |
|
125 | def make_files_xml(staging_dir: pathlib.Path, is_x64) -> str: | |
149 | """Create XML string listing every file to be installed.""" |
|
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 | return doc.toprettyxml() |
|
285 | return doc.toprettyxml() | |
309 |
|
286 | |||
310 |
|
287 | |||
311 | def build_installer( |
|
288 | def build_installer_py2exe( | |
312 | source_dir: pathlib.Path, |
|
289 | source_dir: pathlib.Path, | |
313 | python_exe: pathlib.Path, |
|
290 | python_exe: pathlib.Path, | |
314 | msi_name='mercurial', |
|
291 | msi_name='mercurial', | |
315 | version=None, |
|
292 | version=None, | |
316 | post_build_fn=None, |
|
|||
317 | extra_packages_script=None, |
|
293 | extra_packages_script=None, | |
318 | extra_wxs: typing.Optional[typing.Dict[str, str]] = None, |
|
294 | extra_wxs: typing.Optional[typing.Dict[str, str]] = None, | |
319 | extra_features: typing.Optional[typing.List[str]] = None, |
|
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 | ``source_dir`` is the path to the Mercurial source tree to use. |
|
300 | ``source_dir`` is the path to the Mercurial source tree to use. | |
324 | ``arch`` is the target architecture. either ``x86`` or ``x64``. |
|
301 | ``arch`` is the target architecture. either ``x86`` or ``x64``. | |
325 | ``python_exe`` is the path to the Python executable to use/bundle. |
|
302 | ``python_exe`` is the path to the Python executable to use/bundle. | |
326 | ``version`` is the Mercurial version string. If not defined, |
|
303 | ``version`` is the Mercurial version string. If not defined, | |
327 | ``mercurial/__version__.py`` will be consulted. |
|
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 | ``extra_packages_script`` is a command to be run to inject extra packages |
|
305 | ``extra_packages_script`` is a command to be run to inject extra packages | |
333 | into the py2exe binary. It should stage packages into the virtualenv and |
|
306 | into the py2exe binary. It should stage packages into the virtualenv and | |
334 | print a null byte followed by a newline-separated list of packages that |
|
307 | print a null byte followed by a newline-separated list of packages that | |
@@ -340,8 +313,6 b' def build_installer(' | |||||
340 | arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86' |
|
313 | arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86' | |
341 |
|
314 | |||
342 | hg_build_dir = source_dir / 'build' |
|
315 | hg_build_dir = source_dir / 'build' | |
343 | dist_dir = source_dir / 'dist' |
|
|||
344 | wix_dir = source_dir / 'contrib' / 'packaging' / 'wix' |
|
|||
345 |
|
316 | |||
346 | requirements_txt = ( |
|
317 | requirements_txt = ( | |
347 | source_dir / 'contrib' / 'packaging' / 'requirements_win32.txt' |
|
318 | source_dir / 'contrib' / 'packaging' / 'requirements_win32.txt' | |
@@ -357,15 +328,6 b' def build_installer(' | |||||
357 | extra_packages_script=extra_packages_script, |
|
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 | build_dir = hg_build_dir / ('wix-%s' % arch) |
|
331 | build_dir = hg_build_dir / ('wix-%s' % arch) | |
370 | staging_dir = build_dir / 'stage' |
|
332 | staging_dir = build_dir / 'stage' | |
371 |
|
333 | |||
@@ -388,13 +350,112 b' def build_installer(' | |||||
388 | print('removing %s' % p) |
|
350 | print('removing %s' % p) | |
389 | p.unlink() |
|
351 | p.unlink() | |
390 |
|
352 | |||
391 | wix_pkg, wix_entry = download_entry('wix', hg_build_dir) |
|
353 | return run_wix_packaging( | |
392 | wix_path = hg_build_dir / ('wix-%s' % wix_entry['version']) |
|
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 | if not wix_path.exists(): |
|
454 | if not wix_path.exists(): | |
395 | extract_zip_to_directory(wix_pkg, wix_path) |
|
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 | source_build_rel = pathlib.Path(os.path.relpath(source_dir, build_dir)) |
|
460 | source_build_rel = pathlib.Path(os.path.relpath(source_dir, build_dir)) | |
400 |
|
461 | |||
@@ -413,7 +474,16 b' def build_installer(' | |||||
413 | source = wix_dir / 'mercurial.wxs' |
|
474 | source = wix_dir / 'mercurial.wxs' | |
414 | defines['Version'] = version |
|
475 | defines['Version'] = version | |
415 | defines['Comments'] = 'Installs Mercurial version %s' % version |
|
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 | if extra_features: |
|
487 | if extra_features: | |
418 | assert all(';' not in f for f in extra_features) |
|
488 | assert all(';' not in f for f in extra_features) | |
419 | defines['MercurialExtraFeatures'] = ';'.join(extra_features) |
|
489 | defines['MercurialExtraFeatures'] = ';'.join(extra_features) | |
@@ -421,7 +491,9 b' def build_installer(' | |||||
421 | run_candle(wix_path, build_dir, source, source_build_rel, defines=defines) |
|
491 | run_candle(wix_path, build_dir, source, source_build_rel, defines=defines) | |
422 |
|
492 | |||
423 | msi_path = ( |
|
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 | args = [ |
|
499 | args = [ | |
@@ -448,52 +520,16 b' def build_installer(' | |||||
448 |
|
520 | |||
449 | print('%s created' % msi_path) |
|
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 | return { |
|
533 | return { | |
452 | 'msi_path': msi_path, |
|
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 | AppCopyright=Copyright 2005-2020 Matt Mackall and others |
|
9 | AppCopyright=Copyright 2005-2020 Matt Mackall and others | |
10 | AppName=Mercurial |
|
10 | AppName=Mercurial | |
11 | AppVersion={#VERSION} |
|
11 | AppVersion={#VERSION} | |
|
12 | OutputBaseFilename=Mercurial-{#VERSION}{#SUFFIX} | |||
12 | #if ARCH == "x64" |
|
13 | #if ARCH == "x64" | |
13 | AppVerName=Mercurial {#VERSION} (64-bit) |
|
14 | AppVerName=Mercurial {#VERSION} (64-bit) | |
14 | OutputBaseFilename=Mercurial-{#VERSION}-x64 |
|
|||
15 | ArchitecturesAllowed=x64 |
|
15 | ArchitecturesAllowed=x64 | |
16 | ArchitecturesInstallIn64BitMode=x64 |
|
16 | ArchitecturesInstallIn64BitMode=x64 | |
17 | #else |
|
17 | #else | |
18 | AppVerName=Mercurial {#VERSION} |
|
18 | AppVerName=Mercurial {#VERSION} | |
19 | OutputBaseFilename=Mercurial-{#VERSION} |
|
|||
20 | #endif |
|
19 | #endif | |
21 | InfoAfterFile=../postinstall.txt |
|
20 | InfoAfterFile=../postinstall.txt | |
22 | LicenseFile=Copying.txt |
|
21 | LicenseFile=Copying.txt |
@@ -79,6 +79,10 b'' | |||||
79 | </Directory> |
|
79 | </Directory> | |
80 | </Directory> |
|
80 | </Directory> | |
81 |
|
81 | |||
|
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" ?> | |||
82 | <?if $(var.Platform) = "x86" ?> |
|
86 | <?if $(var.Platform) = "x86" ?> | |
83 | <Merge Id='VCRuntime' DiskId='1' Language='1033' |
|
87 | <Merge Id='VCRuntime' DiskId='1' Language='1033' | |
84 | SourceFile='$(var.VCRedistSrcDir)\microsoft.vcxx.crt.x86_msm.msm' /> |
|
88 | SourceFile='$(var.VCRedistSrcDir)\microsoft.vcxx.crt.x86_msm.msm' /> | |
@@ -90,6 +94,7 b'' | |||||
90 | <Merge Id='VCRuntimePolicy' DiskId='1' Language='1033' |
|
94 | <Merge Id='VCRuntimePolicy' DiskId='1' Language='1033' | |
91 | SourceFile='$(var.VCRedistSrcDir)\policy.x.xx.microsoft.vcxx.crt.x64_msm.msm' /> |
|
95 | SourceFile='$(var.VCRedistSrcDir)\policy.x.xx.microsoft.vcxx.crt.x64_msm.msm' /> | |
92 | <?endif?> |
|
96 | <?endif?> | |
|
97 | <?endif?> | |||
93 | </Directory> |
|
98 | </Directory> | |
94 |
|
99 | |||
95 | <Feature Id='Complete' Title='Mercurial' Description='The complete package' |
|
100 | <Feature Id='Complete' Title='Mercurial' Description='The complete package' | |
@@ -101,10 +106,14 b'' | |||||
101 | <ComponentGroupRef Id="hg.group.ROOT" /> |
|
106 | <ComponentGroupRef Id="hg.group.ROOT" /> | |
102 | <ComponentGroupRef Id="hg.group.defaultrc" /> |
|
107 | <ComponentGroupRef Id="hg.group.defaultrc" /> | |
103 | <ComponentGroupRef Id="hg.group.helptext" /> |
|
108 | <ComponentGroupRef Id="hg.group.helptext" /> | |
|
109 | <?ifdef MercurialHasLib?> | |||
104 | <ComponentGroupRef Id="hg.group.lib" /> |
|
110 | <ComponentGroupRef Id="hg.group.lib" /> | |
|
111 | <?endif?> | |||
105 | <ComponentGroupRef Id="hg.group.templates" /> |
|
112 | <ComponentGroupRef Id="hg.group.templates" /> | |
|
113 | <?if $(var.PythonVersion) = "2" ?> | |||
106 | <MergeRef Id='VCRuntime' /> |
|
114 | <MergeRef Id='VCRuntime' /> | |
107 | <MergeRef Id='VCRuntimePolicy' /> |
|
115 | <MergeRef Id='VCRuntimePolicy' /> | |
|
116 | <?endif?> | |||
108 | </Feature> |
|
117 | </Feature> | |
109 | <?ifdef MercurialExtraFeatures?> |
|
118 | <?ifdef MercurialExtraFeatures?> | |
110 | <?foreach EXTRAFEAT in $(var.MercurialExtraFeatures)?> |
|
119 | <?foreach EXTRAFEAT in $(var.MercurialExtraFeatures)?> |
@@ -2558,7 +2558,7 b' def diff(' | |||||
2558 | fctx2 is not None |
|
2558 | fctx2 is not None | |
2559 | ), b'fctx2 unexpectly None in diff hunks filtering' |
|
2559 | ), b'fctx2 unexpectly None in diff hunks filtering' | |
2560 | hunks = hunksfilterfn(fctx2, hunks) |
|
2560 | hunks = hunksfilterfn(fctx2, hunks) | |
2561 |
text = b''.join( |
|
2561 | text = b''.join(b''.join(hlines) for hrange, hlines in hunks) | |
2562 | if hdr and (text or len(hdr) > 1): |
|
2562 | if hdr and (text or len(hdr) > 1): | |
2563 | yield b'\n'.join(hdr) + b'\n' |
|
2563 | yield b'\n'.join(hdr) + b'\n' | |
2564 | if text: |
|
2564 | if text: |
@@ -1,13 +1,24 b'' | |||||
1 | ROOT = CWD + "/../.." |
|
1 | ROOT = CWD + "/../.." | |
2 |
|
2 | |||
3 | def make_exe(): |
|
3 | # Code to run in Python interpreter. | |
4 | dist = default_python_distribution() |
|
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 | config = PythonInterpreterConfig( |
|
19 | config = PythonInterpreterConfig( | |
9 | raw_allocator = "system", |
|
20 | raw_allocator = "system", | |
10 |
run_eval = |
|
21 | run_eval = RUN_CODE, | |
11 | # We want to let the user load extensions from the file system |
|
22 | # We want to let the user load extensions from the file system | |
12 | filesystem_importer = True, |
|
23 | filesystem_importer = True, | |
13 | # We need this to make resourceutil happy, since it looks for sys.frozen. |
|
24 | # We need this to make resourceutil happy, since it looks for sys.frozen. | |
@@ -24,30 +35,65 b' def make_exe():' | |||||
24 | extension_module_filter = "all", |
|
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 | return exe |
|
64 | return exe | |
30 |
|
65 | |||
31 | def make_install(exe): |
|
66 | ||
|
67 | def make_manifest(dist, exe): | |||
32 | m = FileManifest() |
|
68 | m = FileManifest() | |
33 |
|
||||
34 | # `hg` goes in root directory. |
|
|||
35 | m.add_python_resource(".", exe) |
|
69 | m.add_python_resource(".", exe) | |
36 |
|
70 | |||
37 | templates = glob( |
|
71 | return m | |
38 | include = [ROOT + "/mercurial/templates/**/*"], |
|
|||
39 | strip_prefix = ROOT + "/mercurial/", |
|
|||
40 | ) |
|
|||
41 | m.add_manifest(templates) |
|
|||
42 |
|
72 | |||
43 | return m |
|
|||
44 |
|
73 | |||
45 | def make_embedded_resources(exe): |
|
74 | def make_embedded_resources(exe): | |
46 | return exe.to_embedded_resources() |
|
75 | return exe.to_embedded_resources() | |
47 |
|
76 | |||
48 | register_target("exe", make_exe) |
|
77 | ||
49 | register_target("app", make_install, depends = ["exe"], default = True) |
|
78 | register_target("distribution_posix", make_distribution) | |
50 | register_target("embedded", make_embedded_resources, depends = ["exe"], default_build_script = True) |
|
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 | resolve_targets() |
|
97 | resolve_targets() | |
52 |
|
98 | |||
53 | # END OF COMMON USER-ADJUSTED SETTINGS. |
|
99 | # END OF COMMON USER-ADJUSTED SETTINGS. | |
@@ -55,5 +101,4 b' resolve_targets()' | |||||
55 | # Everything below this is typically managed by PyOxidizer and doesn't need |
|
101 | # Everything below this is typically managed by PyOxidizer and doesn't need | |
56 | # to be updated by people. |
|
102 | # to be updated by people. | |
57 |
|
103 | |||
58 |
PYOXIDIZER_VERSION = "0.7.0 |
|
104 | PYOXIDIZER_VERSION = "0.7.0" | |
59 | PYOXIDIZER_COMMIT = "c772a1379c3026314eda1c8ea244b86c0658951d" |
|
@@ -27,6 +27,7 b' New errors are not allowed. Warnings are' | |||||
27 | Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob) |
|
27 | Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob) | |
28 | Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob) |
|
28 | Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob) | |
29 | Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob) |
|
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 | Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob) |
|
31 | Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob) | |
31 | Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob) |
|
32 | Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob) | |
32 | Skipping i18n/polib.py it has no-che?k-code (glob) |
|
33 | Skipping i18n/polib.py it has no-che?k-code (glob) |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now