##// END OF EJS Templates
wix: functionality to automate building WiX installers...
Gregory Szorc -
r42087:4371f543 default
parent child Browse files
Show More
@@ -0,0 +1,248 b''
1 # wix.py - WiX installer functionality
2 #
3 # Copyright 2019 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 re
13 import subprocess
14
15 from .downloads import (
16 download_entry,
17 )
18 from .py2exe import (
19 build_py2exe,
20 )
21 from .util import (
22 extract_zip_to_directory,
23 sign_with_signtool,
24 )
25
26
27 SUPPORT_WXS = [
28 ('contrib.wxs', r'contrib'),
29 ('dist.wxs', r'dist'),
30 ('doc.wxs', r'doc'),
31 ('help.wxs', r'mercurial\help'),
32 ('i18n.wxs', r'i18n'),
33 ('locale.wxs', r'mercurial\locale'),
34 ('templates.wxs', r'mercurial\templates'),
35 ]
36
37
38 EXTRA_PACKAGES = {
39 'distutils',
40 'enum',
41 'imagesize',
42 'pygments',
43 'sphinx',
44 }
45
46
47 EXCLUDES = {
48 # Python 3 only.
49 'jinja2.asyncsupport',
50 }
51
52
53 def find_version(source_dir: pathlib.Path):
54 version_py = source_dir / 'mercurial' / '__version__.py'
55
56 with version_py.open('r', encoding='utf-8') as fh:
57 source = fh.read().strip()
58
59 m = re.search('version = b"(.*)"', source)
60 return m.group(1)
61
62
63 def normalize_version(version):
64 """Normalize Mercurial version string so WiX accepts it.
65
66 Version strings have to be numeric X.Y.Z.
67 """
68
69 if '+' in version:
70 version, extra = version.split('+', 1)
71 else:
72 extra = None
73
74 # 4.9rc0
75 if version[:-1].endswith('rc'):
76 version = version[:-3]
77
78 versions = [int(v) for v in version.split('.')]
79 while len(versions) < 3:
80 versions.append(0)
81
82 major, minor, build = versions[:3]
83
84 if extra:
85 # <commit count>-<hash>+<date>
86 build = int(extra.split('-')[0])
87
88 return '.'.join('%d' % x for x in (major, minor, build))
89
90
91 def ensure_vc90_merge_modules(build_dir):
92 x86 = (
93 download_entry('vc9-crt-x86-msm', build_dir,
94 local_name='microsoft.vcxx.crt.x86_msm.msm')[0],
95 download_entry('vc9-crt-x86-msm-policy', build_dir,
96 local_name='policy.x.xx.microsoft.vcxx.crt.x86_msm.msm')[0]
97 )
98
99 x64 = (
100 download_entry('vc9-crt-x64-msm', build_dir,
101 local_name='microsoft.vcxx.crt.x64_msm.msm')[0],
102 download_entry('vc9-crt-x64-msm-policy', build_dir,
103 local_name='policy.x.xx.microsoft.vcxx.crt.x64_msm.msm')[0]
104 )
105 return {
106 'x86': x86,
107 'x64': x64,
108 }
109
110
111 def run_candle(wix, cwd, wxs, source_dir, defines=None):
112 args = [
113 str(wix / 'candle.exe'),
114 '-nologo',
115 str(wxs),
116 '-dSourceDir=%s' % source_dir,
117 ]
118
119 if defines:
120 args.extend('-d%s=%s' % define for define in sorted(defines.items()))
121
122 subprocess.run(args, cwd=str(cwd), check=True)
123
124
125 def make_post_build_signing_fn(name, subject_name=None, cert_path=None,
126 cert_password=None, timestamp_url=None):
127 """Create a callable that will use signtool to sign hg.exe."""
128
129 def post_build_sign(source_dir, build_dir, dist_dir, version):
130 description = '%s %s' % (name, version)
131
132 sign_with_signtool(dist_dir / 'hg.exe', description,
133 subject_name=subject_name, cert_path=cert_path,
134 cert_password=cert_password,
135 timestamp_url=timestamp_url)
136
137 return post_build_sign
138
139
140 def build_installer(source_dir: pathlib.Path, python_exe: pathlib.Path,
141 msi_name='mercurial', version=None, post_build_fn=None):
142 """Build a WiX MSI installer.
143
144 ``source_dir`` is the path to the Mercurial source tree to use.
145 ``arch`` is the target architecture. either ``x86`` or ``x64``.
146 ``python_exe`` is the path to the Python executable to use/bundle.
147 ``version`` is the Mercurial version string. If not defined,
148 ``mercurial/__version__.py`` will be consulted.
149 ``post_build_fn`` is a callable that will be called after building
150 Mercurial but before invoking WiX. It can be used to e.g. facilitate
151 signing. It is passed the paths to the Mercurial source, build, and
152 dist directories and the resolved Mercurial version.
153 """
154 arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86'
155
156 hg_build_dir = source_dir / 'build'
157 dist_dir = source_dir / 'dist'
158
159 requirements_txt = (source_dir / 'contrib' / 'packaging' /
160 'wix' / 'requirements.txt')
161
162 build_py2exe(source_dir, hg_build_dir,
163 python_exe, 'wix', requirements_txt,
164 extra_packages=EXTRA_PACKAGES, extra_excludes=EXCLUDES)
165
166 version = version or normalize_version(find_version(source_dir))
167 print('using version string: %s' % version)
168
169 if post_build_fn:
170 post_build_fn(source_dir, hg_build_dir, dist_dir, version)
171
172 build_dir = hg_build_dir / ('wix-%s' % arch)
173
174 build_dir.mkdir(exist_ok=True)
175
176 wix_pkg, wix_entry = download_entry('wix', hg_build_dir)
177 wix_path = hg_build_dir / ('wix-%s' % wix_entry['version'])
178
179 if not wix_path.exists():
180 extract_zip_to_directory(wix_pkg, wix_path)
181
182 ensure_vc90_merge_modules(hg_build_dir)
183
184 source_build_rel = pathlib.Path(os.path.relpath(source_dir, build_dir))
185
186 defines = {'Platform': arch}
187
188 for wxs, rel_path in SUPPORT_WXS:
189 wxs = source_dir / 'contrib' / 'packaging' / 'wix' / wxs
190 wxs_source_dir = source_dir / rel_path
191 run_candle(wix_path, build_dir, wxs, wxs_source_dir, defines=defines)
192
193 source = source_dir / 'contrib' / 'packaging' / 'wix' / 'mercurial.wxs'
194 defines['Version'] = version
195 defines['Comments'] = 'Installs Mercurial version %s' % version
196 defines['VCRedistSrcDir'] = str(hg_build_dir)
197
198 run_candle(wix_path, build_dir, source, source_build_rel, defines=defines)
199
200 msi_path = source_dir / 'dist' / (
201 '%s-%s-%s.msi' % (msi_name, version, arch))
202
203 args = [
204 str(wix_path / 'light.exe'),
205 '-nologo',
206 '-ext', 'WixUIExtension',
207 '-sw1076',
208 '-spdb',
209 '-o', str(msi_path),
210 ]
211
212 for source, rel_path in SUPPORT_WXS:
213 assert source.endswith('.wxs')
214 args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
215
216 args.append(str(build_dir / 'mercurial.wixobj'))
217
218 subprocess.run(args, cwd=str(source_dir), check=True)
219
220 print('%s created' % msi_path)
221
222 return {
223 'msi_path': msi_path,
224 }
225
226
227 def build_signed_installer(source_dir: pathlib.Path, python_exe: pathlib.Path,
228 name: str, version=None, subject_name=None,
229 cert_path=None, cert_password=None,
230 timestamp_url=None):
231 """Build an installer with signed executables."""
232
233 post_build_fn = make_post_build_signing_fn(
234 name,
235 subject_name=subject_name,
236 cert_path=cert_path,
237 cert_password=cert_password,
238 timestamp_url=timestamp_url)
239
240 info = build_installer(source_dir, python_exe=python_exe,
241 msi_name=name.lower(), version=version,
242 post_build_fn=post_build_fn)
243
244 description = '%s %s' % (name, version)
245
246 sign_with_signtool(info['msi_path'], description,
247 subject_name=subject_name, cert_path=cert_path,
248 cert_password=cert_password, timestamp_url=timestamp_url)
@@ -0,0 +1,65 b''
1 #!/usr/bin/env python3
2 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
3 #
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
6
7 # no-check-code because Python 3 native.
8
9 """Code to build Mercurial WiX installer."""
10
11 import argparse
12 import os
13 import pathlib
14 import sys
15
16
17 if __name__ == '__main__':
18 parser = argparse.ArgumentParser()
19
20 parser.add_argument('--name',
21 help='Application name',
22 default='Mercurial')
23 parser.add_argument('--python',
24 help='Path to Python executable to use',
25 required=True)
26 parser.add_argument('--sign-sn',
27 help='Subject name (or fragment thereof) of certificate '
28 'to use for signing')
29 parser.add_argument('--sign-cert',
30 help='Path to certificate to use for signing')
31 parser.add_argument('--sign-password',
32 help='Password for signing certificate')
33 parser.add_argument('--sign-timestamp-url',
34 help='URL of timestamp server to use for signing')
35 parser.add_argument('--version',
36 help='Version string to use')
37
38 args = parser.parse_args()
39
40 here = pathlib.Path(os.path.abspath(os.path.dirname(__file__)))
41 source_dir = here.parent.parent.parent
42
43 sys.path.insert(0, str(source_dir / 'contrib' / 'packaging'))
44
45 from hgpackaging.wix import (
46 build_installer,
47 build_signed_installer,
48 )
49
50 fn = build_installer
51 kwargs = {
52 'source_dir': source_dir,
53 'python_exe': pathlib.Path(args.python),
54 'version': args.version,
55 }
56
57 if args.sign_sn or args.sign_cert:
58 fn = build_signed_installer
59 kwargs['name'] = args.name
60 kwargs['subject_name'] = args.sign_sn
61 kwargs['cert_path'] = args.sign_cert
62 kwargs['cert_password'] = args.sign_password
63 kwargs['timestamp_url'] = args.sign_timestamp_url
64
65 fn(**kwargs)
@@ -0,0 +1,132 b''
1 #
2 # This file is autogenerated by pip-compile
3 # To update, run:
4 #
5 # pip-compile --generate-hashes contrib/packaging/wix/requirements.txt.in -o contrib/packaging/wix/requirements.txt -U
6 #
7 alabaster==0.7.12 \
8 --hash=sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359 \
9 --hash=sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02 \
10 # via sphinx
11 babel==2.6.0 \
12 --hash=sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669 \
13 --hash=sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23 \
14 # via sphinx
15 certifi==2018.11.29 \
16 --hash=sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7 \
17 --hash=sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033 \
18 # via requests
19 chardet==3.0.4 \
20 --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \
21 --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \
22 # via requests
23 colorama==0.4.1 \
24 --hash=sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d \
25 --hash=sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48 \
26 # via sphinx
27 docutils==0.14 \
28 --hash=sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6 \
29 --hash=sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274 \
30 --hash=sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6
31 enum==0.4.7 \
32 --hash=sha256:8c7cf3587eda51008bcc1eed99ea2c331ccd265c231dbaa95ec5258d3dc03100
33 future==0.17.1 \
34 --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8
35 idna==2.8 \
36 --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \
37 --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c \
38 # via requests
39 imagesize==1.1.0 \
40 --hash=sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8 \
41 --hash=sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5 \
42 # via sphinx
43 jinja2==2.10 \
44 --hash=sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd \
45 --hash=sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4 \
46 # via sphinx
47 markupsafe==1.1.1 \
48 --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \
49 --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \
50 --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \
51 --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \
52 --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \
53 --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \
54 --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \
55 --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \
56 --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \
57 --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \
58 --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \
59 --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \
60 --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \
61 --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \
62 --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \
63 --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \
64 --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \
65 --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \
66 --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \
67 --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \
68 --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \
69 --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \
70 --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \
71 --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \
72 --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \
73 --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \
74 --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \
75 --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \
76 # via jinja2
77 packaging==19.0 \
78 --hash=sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af \
79 --hash=sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3 \
80 # via sphinx
81 pygments==2.3.1 \
82 --hash=sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a \
83 --hash=sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d
84 pyparsing==2.3.1 \
85 --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \
86 --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 \
87 # via packaging
88 pypiwin32==223 \
89 --hash=sha256:67adf399debc1d5d14dffc1ab5acacb800da569754fafdc576b2a039485aa775 \
90 --hash=sha256:71be40c1fbd28594214ecaecb58e7aa8b708eabfa0125c8a109ebd51edbd776a
91 pytz==2018.9 \
92 --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \
93 --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c \
94 # via babel
95 pywin32==224 \
96 --hash=sha256:22e218832a54ed206452c8f3ca9eff07ef327f8e597569a4c2828be5eaa09a77 \
97 --hash=sha256:32b37abafbfeddb0fe718008d6aada5a71efa2874f068bee1f9e703983dcc49a \
98 --hash=sha256:35451edb44162d2f603b5b18bd427bc88fcbc74849eaa7a7e7cfe0f507e5c0c8 \
99 --hash=sha256:4eda2e1e50faa706ff8226195b84fbcbd542b08c842a9b15e303589f85bfb41c \
100 --hash=sha256:5f265d72588806e134c8e1ede8561739071626ea4cc25c12d526aa7b82416ae5 \
101 --hash=sha256:6852ceac5fdd7a146b570655c37d9eacd520ed1eaeec051ff41c6fc94243d8bf \
102 --hash=sha256:6dbc4219fe45ece6a0cc6baafe0105604fdee551b5e876dc475d3955b77190ec \
103 --hash=sha256:9bd07746ce7f2198021a9fa187fa80df7b221ec5e4c234ab6f00ea355a3baf99 \
104 # via pypiwin32
105 requests==2.21.0 \
106 --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \
107 --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b \
108 # via sphinx
109 six==1.12.0 \
110 --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \
111 --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 \
112 # via packaging, sphinx
113 snowballstemmer==1.2.1 \
114 --hash=sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128 \
115 --hash=sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89 \
116 # via sphinx
117 sphinx==1.8.4 \
118 --hash=sha256:b53904fa7cb4b06a39409a492b949193a1b68cc7241a1a8ce9974f86f0d24287 \
119 --hash=sha256:c1c00fc4f6e8b101a0d037065043460dffc2d507257f2f11acaed71fd2b0c83c
120 sphinxcontrib-websupport==1.1.0 \
121 --hash=sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd \
122 --hash=sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9 \
123 # via sphinx
124 typing==3.6.6 \
125 --hash=sha256:4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d \
126 --hash=sha256:57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4 \
127 --hash=sha256:a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a \
128 # via sphinx
129 urllib3==1.24.1 \
130 --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \
131 --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 \
132 # via requests
@@ -0,0 +1,6 b''
1 docutils
2 enum
3 future
4 pygments
5 pypiwin32
6 sphinx
@@ -31,12 +31,42 b' DOWNLOADS = {'
31 'sha256': '6bd383312e7d33eef2e43a5f236f9445e4f3e0f6b16333c6f183ed445c44ddbd',
31 'sha256': '6bd383312e7d33eef2e43a5f236f9445e4f3e0f6b16333c6f183ed445c44ddbd',
32 'version': '0.6.9',
32 'version': '0.6.9',
33 },
33 },
34 # The VC9 CRT merge modules aren't readily available on most systems because
35 # they are only installed as part of a full Visual Studio 2008 install.
36 # While we could potentially extract them from a Visual Studio 2008
37 # installer, it is easier to just fetch them from a known URL.
38 'vc9-crt-x86-msm': {
39 'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/Microsoft_VC90_CRT_x86.msm',
40 'size': 615424,
41 'sha256': '837e887ef31b332feb58156f429389de345cb94504228bb9a523c25a9dd3d75e',
42 },
43 'vc9-crt-x86-msm-policy': {
44 'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/policy_9_0_Microsoft_VC90_CRT_x86.msm',
45 'size': 71168,
46 'sha256': '3fbcf92e3801a0757f36c5e8d304e134a68d5cafd197a6df7734ae3e8825c940',
47 },
48 'vc9-crt-x64-msm': {
49 'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/Microsoft_VC90_CRT_x86_x64.msm',
50 'size': 662528,
51 'sha256': '50d9639b5ad4844a2285269c7551bf5157ec636e32396ddcc6f7ec5bce487a7c',
52 },
53 'vc9-crt-x64-msm-policy': {
54 'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/policy_9_0_Microsoft_VC90_CRT_x86_x64.msm',
55 'size': 71168,
56 'sha256': '0550ea1929b21239134ad3a678c944ba0f05f11087117b6cf0833e7110686486',
57 },
34 'virtualenv': {
58 'virtualenv': {
35 'url': 'https://files.pythonhosted.org/packages/37/db/89d6b043b22052109da35416abc3c397655e4bd3cff031446ba02b9654fa/virtualenv-16.4.3.tar.gz',
59 'url': 'https://files.pythonhosted.org/packages/37/db/89d6b043b22052109da35416abc3c397655e4bd3cff031446ba02b9654fa/virtualenv-16.4.3.tar.gz',
36 'size': 3713208,
60 'size': 3713208,
37 'sha256': '984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39',
61 'sha256': '984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39',
38 'version': '16.4.3',
62 'version': '16.4.3',
39 },
63 },
64 'wix': {
65 'url': 'https://github.com/wixtoolset/wix3/releases/download/wix3111rtm/wix311-binaries.zip',
66 'size': 34358269,
67 'sha256': '37f0a533b0978a454efb5dc3bd3598becf9660aaf4287e55bf68ca6b527d051d',
68 'version': '3.11.1',
69 },
40 }
70 }
41
71
42
72
@@ -8,6 +8,7 b''
8 # no-check-code because Python 3 native.
8 # no-check-code because Python 3 native.
9
9
10 import distutils.version
10 import distutils.version
11 import getpass
11 import os
12 import os
12 import pathlib
13 import pathlib
13 import subprocess
14 import subprocess
@@ -50,6 +51,89 b' def find_vc_runtime_files(x64=False):'
50 ]
51 ]
51
52
52
53
54 def windows_10_sdk_info():
55 """Resolves information about the Windows 10 SDK."""
56
57 base = pathlib.Path(os.environ['ProgramFiles(x86)']) / 'Windows Kits' / '10'
58
59 if not base.is_dir():
60 raise Exception('unable to find Windows 10 SDK at %s' % base)
61
62 # Find the latest version.
63 bin_base = base / 'bin'
64
65 versions = [v for v in os.listdir(bin_base) if v.startswith('10.')]
66 version = sorted(versions, reverse=True)[0]
67
68 bin_version = bin_base / version
69
70 return {
71 'root': base,
72 'version': version,
73 'bin_root': bin_version,
74 'bin_x86': bin_version / 'x86',
75 'bin_x64': bin_version / 'x64'
76 }
77
78
79 def find_signtool():
80 """Find signtool.exe from the Windows SDK."""
81 sdk = windows_10_sdk_info()
82
83 for key in ('bin_x64', 'bin_x86'):
84 p = sdk[key] / 'signtool.exe'
85
86 if p.exists():
87 return p
88
89 raise Exception('could not find signtool.exe in Windows 10 SDK')
90
91
92 def sign_with_signtool(file_path, description, subject_name=None,
93 cert_path=None, cert_password=None,
94 timestamp_url=None):
95 """Digitally sign a file with signtool.exe.
96
97 ``file_path`` is file to sign.
98 ``description`` is text that goes in the signature.
99
100 The signing certificate can be specified by ``cert_path`` or
101 ``subject_name``. These correspond to the ``/f`` and ``/n`` arguments
102 to signtool.exe, respectively.
103
104 The certificate password can be specified via ``cert_password``. If
105 not provided, you will be prompted for the password.
106
107 ``timestamp_url`` is the URL of a RFC 3161 timestamp server (``/tr``
108 argument to signtool.exe).
109 """
110 if cert_path and subject_name:
111 raise ValueError('cannot specify both cert_path and subject_name')
112
113 while cert_path and not cert_password:
114 cert_password = getpass.getpass('password for %s: ' % cert_path)
115
116 args = [
117 str(find_signtool()), 'sign',
118 '/v',
119 '/fd', 'sha256',
120 '/d', description,
121 ]
122
123 if cert_path:
124 args.extend(['/f', str(cert_path), '/p', cert_password])
125 elif subject_name:
126 args.extend(['/n', subject_name])
127
128 if timestamp_url:
129 args.extend(['/tr', timestamp_url, '/td', 'sha256'])
130
131 args.append(str(file_path))
132
133 print('signing %s' % file_path)
134 subprocess.run(args, check=True)
135
136
53 PRINT_PYTHON_INFO = '''
137 PRINT_PYTHON_INFO = '''
54 import platform; print("%s:%s" % (platform.architecture()[0], platform.python_version()))
138 import platform; print("%s:%s" % (platform.architecture()[0], platform.python_version()))
55 '''.strip()
139 '''.strip()
@@ -1,29 +1,71 b''
1 WiX installer source files
1 WiX Installer
2 =============
3
4 The files in this directory are used to produce an MSI installer using
5 the WiX Toolset (http://wixtoolset.org/).
6
7 The MSI installers require elevated (admin) privileges due to the
8 installation of MSVC CRT libraries into the Windows system store. See
9 the Inno Setup installers in the ``inno`` sibling directory for installers
10 that do not have this requirement.
11
12 Requirements
13 ============
14
15 Building the WiX installers requires a Windows machine. The following
16 dependencies must be installed:
17
18 * Python 2.7 (download from https://www.python.org/downloads/)
19 * Microsoft Visual C++ Compiler for Python 2.7
20 (https://www.microsoft.com/en-us/download/details.aspx?id=44266)
21 * Python 3.5+ (to run the ``build.py`` script)
22
23 Building
24 ========
25
26 The ``build.py`` script automates the process of producing an MSI
27 installer. It manages fetching and configuring non-system dependencies
28 (such as py2exe, gettext, and various Python packages).
29
30 The script requires an activated ``Visual C++ 2008`` command prompt.
31 A shortcut to such a prompt was installed with ``Microsoft Visual
32 C++ Compiler for Python 2.7``. From your Start Menu, look for
33 ``Microsoft Visual C++ Compiler Package for Python 2.7`` then
34 launch either ``Visual C++ 2008 32-bit Command Prompt`` or
35 ``Visual C++ 2008 64-bit Command Prompt``.
36
37 From the prompt, change to the Mercurial source directory. e.g.
38 ``cd c:\src\hg``.
39
40 Next, invoke ``build.py`` to produce an MSI installer. You will need
41 to supply the path to the Python interpreter to use.::
42
43 $ python3 contrib\packaging\wix\build.py \
44 --python c:\python27\python.exe
45
46 .. note::
47
48 The script validates that the Visual C++ environment is active and
49 that the architecture of the specified Python interpreter matches the
50 Visual C++ environment. An error is raised otherwise.
51
52 If everything runs as intended, dependencies will be fetched and
53 configured into the ``build`` sub-directory, Mercurial will be built,
54 and an installer placed in the ``dist`` sub-directory. The final line
55 of output should print the name of the generated installer.
56
57 Additional options may be configured. Run ``build.py --help`` to see
58 a list of program flags.
59
60 Relationship to TortoiseHG
2 ==========================
61 ==========================
3
62
4 The files in this folder are used by the thg-winbuild [1] package
63 TortoiseHG uses the WiX files in this directory.
5 building architecture to create a Mercurial MSI installer. These files
6 are versioned within the Mercurial source tree because the WXS files
7 must kept up to date with distribution changes within their branch. In
8 other words, the default branch WXS files are expected to diverge from
9 the stable branch WXS files. Storing them within the same repository is
10 the only sane way to keep the source tree and the installer in sync.
11
12 The MSI installer builder uses only the mercurial.ini file from the
13 contrib/win32 folder.
14
64
15 The MSI packages built by thg-winbuild require elevated (admin)
65 The code for building TortoiseHG installers lives at
16 privileges to be installed due to the installation of MSVC CRT libraries
66 https://bitbucket.org/tortoisehg/thg-winbuild and is maintained by
17 under the C:\WINDOWS\WinSxS folder. Thus the InnoSetup installers may
67 Steve Borho (steve@borho.org).
18 still be useful to some users.
19
68
20 To build your own MSI packages, clone the thg-winbuild [1] repository
69 When changing behavior of the WiX installer, be sure to notify
21 and follow the README.txt [2] instructions closely. There are fewer
70 the TortoiseHG Project of the changes so they have ample time
22 prerequisites for a WiX [3] installer than an InnoSetup installer, but
71 provide feedback and react to those changes.
23 they are more specific.
24
25 Direct questions or comments to Steve Borho <steve@borho.org>
26
27 [1] http://bitbucket.org/tortoisehg/thg-winbuild
28 [2] http://bitbucket.org/tortoisehg/thg-winbuild/src/tip/README.txt
29 [3] http://wix.sourceforge.net/
@@ -16,7 +16,9 b' New errors are not allowed. Warnings are'
16 Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob)
16 Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob)
17 Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob)
17 Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob)
18 Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob)
18 Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob)
19 Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob)
19 Skipping contrib/packaging/inno/build.py it has no-che?k-code (glob)
20 Skipping contrib/packaging/inno/build.py it has no-che?k-code (glob)
21 Skipping contrib/packaging/wix/build.py it has no-che?k-code (glob)
20 Skipping i18n/polib.py it has no-che?k-code (glob)
22 Skipping i18n/polib.py it has no-che?k-code (glob)
21 Skipping mercurial/statprof.py it has no-che?k-code (glob)
23 Skipping mercurial/statprof.py it has no-che?k-code (glob)
22 Skipping tests/badserverext.py it has no-che?k-code (glob)
24 Skipping tests/badserverext.py it has no-che?k-code (glob)
@@ -7,6 +7,7 b''
7 $ testrepohg files 'set:(**.py)' \
7 $ testrepohg files 'set:(**.py)' \
8 > -X contrib/packaging/hgpackaging/ \
8 > -X contrib/packaging/hgpackaging/ \
9 > -X contrib/packaging/inno/ \
9 > -X contrib/packaging/inno/ \
10 > -X contrib/packaging/wix/ \
10 > -X hgdemandimport/demandimportpy2.py \
11 > -X hgdemandimport/demandimportpy2.py \
11 > -X mercurial/thirdparty/cbor \
12 > -X mercurial/thirdparty/cbor \
12 > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py
13 > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py
General Comments 0
You need to be logged in to leave comments. Login now