##// END OF EJS Templates
packaging: integrate signing into run_wix_packaging()...
Gregory Szorc -
r45258:08423196 default draft
parent child Browse files
Show More
@@ -1,166 +1,166 b''
1 1 # cli.py - Command line interface for automation
2 2 #
3 3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # no-check-code because Python 3 native.
9 9
10 10 import argparse
11 11 import os
12 12 import pathlib
13 13
14 14 from . import (
15 15 inno,
16 16 wix,
17 17 )
18 18
19 19 HERE = pathlib.Path(os.path.abspath(os.path.dirname(__file__)))
20 20 SOURCE_DIR = HERE.parent.parent.parent
21 21
22 22
23 23 def build_inno(pyoxidizer_target=None, python=None, iscc=None, version=None):
24 24 if not pyoxidizer_target and not python:
25 25 raise Exception("--python required unless building with PyOxidizer")
26 26
27 27 if python and not os.path.isabs(python):
28 28 raise Exception("--python arg must be an absolute path")
29 29
30 30 if iscc:
31 31 iscc = pathlib.Path(iscc)
32 32 else:
33 33 iscc = (
34 34 pathlib.Path(os.environ["ProgramFiles(x86)"])
35 35 / "Inno Setup 5"
36 36 / "ISCC.exe"
37 37 )
38 38
39 39 build_dir = SOURCE_DIR / "build"
40 40
41 41 if pyoxidizer_target:
42 42 inno.build_with_pyoxidizer(
43 43 SOURCE_DIR, build_dir, pyoxidizer_target, iscc, version=version
44 44 )
45 45 else:
46 46 inno.build_with_py2exe(
47 47 SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version,
48 48 )
49 49
50 50
51 51 def build_wix(
52 52 name=None,
53 53 python=None,
54 54 version=None,
55 55 sign_sn=None,
56 56 sign_cert=None,
57 57 sign_password=None,
58 58 sign_timestamp_url=None,
59 59 extra_packages_script=None,
60 60 extra_wxs=None,
61 61 extra_features=None,
62 62 ):
63 fn = wix.build_installer
64 63 kwargs = {
65 64 "source_dir": SOURCE_DIR,
66 65 "python_exe": pathlib.Path(python),
67 66 "version": version,
68 67 }
69 68
70 69 if not os.path.isabs(python):
71 70 raise Exception("--python arg must be an absolute path")
72 71
73 72 if extra_packages_script:
74 73 kwargs["extra_packages_script"] = extra_packages_script
75 74 if extra_wxs:
76 75 kwargs["extra_wxs"] = dict(
77 76 thing.split("=") for thing in extra_wxs.split(",")
78 77 )
79 78 if extra_features:
80 79 kwargs["extra_features"] = extra_features.split(",")
81 80
82 81 if sign_sn or sign_cert:
83 fn = wix.build_signed_installer
84 kwargs["name"] = name
85 kwargs["subject_name"] = sign_sn
86 kwargs["cert_path"] = sign_cert
87 kwargs["cert_password"] = sign_password
88 kwargs["timestamp_url"] = sign_timestamp_url
82 kwargs["signing_info"] = {
83 "name": name,
84 "subject_name": sign_sn,
85 "cert_path": sign_cert,
86 "cert_password": sign_password,
87 "timestamp_url": sign_timestamp_url,
88 }
89 89
90 fn(**kwargs)
90 wix.build_installer(**kwargs)
91 91
92 92
93 93 def get_parser():
94 94 parser = argparse.ArgumentParser()
95 95
96 96 subparsers = parser.add_subparsers()
97 97
98 98 sp = subparsers.add_parser("inno", help="Build Inno Setup installer")
99 99 sp.add_argument(
100 100 "--pyoxidizer-target",
101 101 choices={"i686-pc-windows-msvc", "x86_64-pc-windows-msvc"},
102 102 help="Build with PyOxidizer targeting this host triple",
103 103 )
104 104 sp.add_argument("--python", help="path to python.exe to use")
105 105 sp.add_argument("--iscc", help="path to iscc.exe to use")
106 106 sp.add_argument(
107 107 "--version",
108 108 help="Mercurial version string to use "
109 109 "(detected from __version__.py if not defined",
110 110 )
111 111 sp.set_defaults(func=build_inno)
112 112
113 113 sp = subparsers.add_parser(
114 114 "wix", help="Build Windows installer with WiX Toolset"
115 115 )
116 116 sp.add_argument("--name", help="Application name", default="Mercurial")
117 117 sp.add_argument(
118 118 "--python", help="Path to Python executable to use", required=True
119 119 )
120 120 sp.add_argument(
121 121 "--sign-sn",
122 122 help="Subject name (or fragment thereof) of certificate "
123 123 "to use for signing",
124 124 )
125 125 sp.add_argument(
126 126 "--sign-cert", help="Path to certificate to use for signing"
127 127 )
128 128 sp.add_argument("--sign-password", help="Password for signing certificate")
129 129 sp.add_argument(
130 130 "--sign-timestamp-url",
131 131 help="URL of timestamp server to use for signing",
132 132 )
133 133 sp.add_argument("--version", help="Version string to use")
134 134 sp.add_argument(
135 135 "--extra-packages-script",
136 136 help=(
137 137 "Script to execute to include extra packages in " "py2exe binary."
138 138 ),
139 139 )
140 140 sp.add_argument(
141 141 "--extra-wxs", help="CSV of path_to_wxs_file=working_dir_for_wxs_file"
142 142 )
143 143 sp.add_argument(
144 144 "--extra-features",
145 145 help=(
146 146 "CSV of extra feature names to include "
147 147 "in the installer from the extra wxs files"
148 148 ),
149 149 )
150 150 sp.set_defaults(func=build_wix)
151 151
152 152 return parser
153 153
154 154
155 155 def main():
156 156 parser = get_parser()
157 157 args = parser.parse_args()
158 158
159 159 if not hasattr(args, "func"):
160 160 parser.print_help()
161 161 return
162 162
163 163 kwargs = dict(vars(args))
164 164 del kwargs["func"]
165 165
166 166 args.func(**kwargs)
@@ -1,526 +1,476 b''
1 1 # wix.py - WiX installer functionality
2 2 #
3 3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # no-check-code because Python 3 native.
9 9
10 10 import collections
11 11 import os
12 12 import pathlib
13 13 import re
14 14 import shutil
15 15 import subprocess
16 16 import typing
17 17 import uuid
18 18 import xml.dom.minidom
19 19
20 20 from .downloads import download_entry
21 21 from .py2exe import (
22 22 build_py2exe,
23 23 stage_install,
24 24 )
25 25 from .util import (
26 26 extract_zip_to_directory,
27 27 normalize_windows_version,
28 28 process_install_rules,
29 29 sign_with_signtool,
30 30 )
31 31
32 32
33 33 EXTRA_PACKAGES = {
34 34 'dulwich',
35 35 'distutils',
36 36 'keyring',
37 37 'pygments',
38 38 'win32ctypes',
39 39 }
40 40
41 41
42 42 EXTRA_INSTALL_RULES = [
43 43 ('contrib/packaging/wix/COPYING.rtf', 'COPYING.rtf'),
44 44 ('contrib/win32/mercurial.ini', 'defaultrc/mercurial.rc'),
45 45 ]
46 46
47 47 STAGING_REMOVE_FILES = [
48 48 # We use the RTF variant.
49 49 'copying.txt',
50 50 ]
51 51
52 52 SHORTCUTS = {
53 53 # hg.1.html'
54 54 'hg.file.5d3e441c_28d9_5542_afd0_cdd4234f12d5': {
55 55 'Name': 'Mercurial Command Reference',
56 56 },
57 57 # hgignore.5.html
58 58 'hg.file.5757d8e0_f207_5e10_a2ec_3ba0a062f431': {
59 59 'Name': 'Mercurial Ignore Files',
60 60 },
61 61 # hgrc.5.html
62 62 'hg.file.92e605fd_1d1a_5dc6_9fc0_5d2998eb8f5e': {
63 63 'Name': 'Mercurial Configuration Files',
64 64 },
65 65 }
66 66
67 67
68 68 def find_version(source_dir: pathlib.Path):
69 69 version_py = source_dir / 'mercurial' / '__version__.py'
70 70
71 71 with version_py.open('r', encoding='utf-8') as fh:
72 72 source = fh.read().strip()
73 73
74 74 m = re.search('version = b"(.*)"', source)
75 75 return m.group(1)
76 76
77 77
78 78 def ensure_vc90_merge_modules(build_dir):
79 79 x86 = (
80 80 download_entry(
81 81 'vc9-crt-x86-msm',
82 82 build_dir,
83 83 local_name='microsoft.vcxx.crt.x86_msm.msm',
84 84 )[0],
85 85 download_entry(
86 86 'vc9-crt-x86-msm-policy',
87 87 build_dir,
88 88 local_name='policy.x.xx.microsoft.vcxx.crt.x86_msm.msm',
89 89 )[0],
90 90 )
91 91
92 92 x64 = (
93 93 download_entry(
94 94 'vc9-crt-x64-msm',
95 95 build_dir,
96 96 local_name='microsoft.vcxx.crt.x64_msm.msm',
97 97 )[0],
98 98 download_entry(
99 99 'vc9-crt-x64-msm-policy',
100 100 build_dir,
101 101 local_name='policy.x.xx.microsoft.vcxx.crt.x64_msm.msm',
102 102 )[0],
103 103 )
104 104 return {
105 105 'x86': x86,
106 106 'x64': x64,
107 107 }
108 108
109 109
110 110 def run_candle(wix, cwd, wxs, source_dir, defines=None):
111 111 args = [
112 112 str(wix / 'candle.exe'),
113 113 '-nologo',
114 114 str(wxs),
115 115 '-dSourceDir=%s' % source_dir,
116 116 ]
117 117
118 118 if defines:
119 119 args.extend('-d%s=%s' % define for define in sorted(defines.items()))
120 120
121 121 subprocess.run(args, cwd=str(cwd), check=True)
122 122
123 123
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 124 def make_files_xml(staging_dir: pathlib.Path, is_x64) -> str:
149 125 """Create XML string listing every file to be installed."""
150 126
151 127 # We derive GUIDs from a deterministic file path identifier.
152 128 # We shoehorn the name into something that looks like a URL because
153 129 # the UUID namespaces are supposed to work that way (even though
154 130 # the input data probably is never validated).
155 131
156 132 doc = xml.dom.minidom.parseString(
157 133 '<?xml version="1.0" encoding="utf-8"?>'
158 134 '<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">'
159 135 '</Wix>'
160 136 )
161 137
162 138 # Assemble the install layout by directory. This makes it easier to
163 139 # emit XML, since each directory has separate entities.
164 140 manifest = collections.defaultdict(dict)
165 141
166 142 for root, dirs, files in os.walk(staging_dir):
167 143 dirs.sort()
168 144
169 145 root = pathlib.Path(root)
170 146 rel_dir = root.relative_to(staging_dir)
171 147
172 148 for i in range(len(rel_dir.parts)):
173 149 parent = '/'.join(rel_dir.parts[0 : i + 1])
174 150 manifest.setdefault(parent, {})
175 151
176 152 for f in sorted(files):
177 153 full = root / f
178 154 manifest[str(rel_dir).replace('\\', '/')][full.name] = full
179 155
180 156 component_groups = collections.defaultdict(list)
181 157
182 158 # Now emit a <Fragment> for each directory.
183 159 # Each directory is composed of a <DirectoryRef> pointing to its parent
184 160 # and defines child <Directory>'s and a <Component> with all the files.
185 161 for dir_name, entries in sorted(manifest.items()):
186 162 # The directory id is derived from the path. But the root directory
187 163 # is special.
188 164 if dir_name == '.':
189 165 parent_directory_id = 'INSTALLDIR'
190 166 else:
191 167 parent_directory_id = 'hg.dir.%s' % dir_name.replace('/', '.')
192 168
193 169 fragment = doc.createElement('Fragment')
194 170 directory_ref = doc.createElement('DirectoryRef')
195 171 directory_ref.setAttribute('Id', parent_directory_id)
196 172
197 173 # Add <Directory> entries for immediate children directories.
198 174 for possible_child in sorted(manifest.keys()):
199 175 if (
200 176 dir_name == '.'
201 177 and '/' not in possible_child
202 178 and possible_child != '.'
203 179 ):
204 180 child_directory_id = 'hg.dir.%s' % possible_child
205 181 name = possible_child
206 182 else:
207 183 if not possible_child.startswith('%s/' % dir_name):
208 184 continue
209 185 name = possible_child[len(dir_name) + 1 :]
210 186 if '/' in name:
211 187 continue
212 188
213 189 child_directory_id = 'hg.dir.%s' % possible_child.replace(
214 190 '/', '.'
215 191 )
216 192
217 193 directory = doc.createElement('Directory')
218 194 directory.setAttribute('Id', child_directory_id)
219 195 directory.setAttribute('Name', name)
220 196 directory_ref.appendChild(directory)
221 197
222 198 # Add <Component>s for files in this directory.
223 199 for rel, source_path in sorted(entries.items()):
224 200 if dir_name == '.':
225 201 full_rel = rel
226 202 else:
227 203 full_rel = '%s/%s' % (dir_name, rel)
228 204
229 205 component_unique_id = (
230 206 'https://www.mercurial-scm.org/wix-installer/0/component/%s'
231 207 % full_rel
232 208 )
233 209 component_guid = uuid.uuid5(uuid.NAMESPACE_URL, component_unique_id)
234 210 component_id = 'hg.component.%s' % str(component_guid).replace(
235 211 '-', '_'
236 212 )
237 213
238 214 component = doc.createElement('Component')
239 215
240 216 component.setAttribute('Id', component_id)
241 217 component.setAttribute('Guid', str(component_guid).upper())
242 218 component.setAttribute('Win64', 'yes' if is_x64 else 'no')
243 219
244 220 # Assign this component to a top-level group.
245 221 if dir_name == '.':
246 222 component_groups['ROOT'].append(component_id)
247 223 elif '/' in dir_name:
248 224 component_groups[dir_name[0 : dir_name.index('/')]].append(
249 225 component_id
250 226 )
251 227 else:
252 228 component_groups[dir_name].append(component_id)
253 229
254 230 unique_id = (
255 231 'https://www.mercurial-scm.org/wix-installer/0/%s' % full_rel
256 232 )
257 233 file_guid = uuid.uuid5(uuid.NAMESPACE_URL, unique_id)
258 234
259 235 # IDs have length limits. So use GUID to derive them.
260 236 file_guid_normalized = str(file_guid).replace('-', '_')
261 237 file_id = 'hg.file.%s' % file_guid_normalized
262 238
263 239 file_element = doc.createElement('File')
264 240 file_element.setAttribute('Id', file_id)
265 241 file_element.setAttribute('Source', str(source_path))
266 242 file_element.setAttribute('KeyPath', 'yes')
267 243 file_element.setAttribute('ReadOnly', 'yes')
268 244
269 245 component.appendChild(file_element)
270 246 directory_ref.appendChild(component)
271 247
272 248 fragment.appendChild(directory_ref)
273 249 doc.documentElement.appendChild(fragment)
274 250
275 251 for group, component_ids in sorted(component_groups.items()):
276 252 fragment = doc.createElement('Fragment')
277 253 component_group = doc.createElement('ComponentGroup')
278 254 component_group.setAttribute('Id', 'hg.group.%s' % group)
279 255
280 256 for component_id in component_ids:
281 257 component_ref = doc.createElement('ComponentRef')
282 258 component_ref.setAttribute('Id', component_id)
283 259 component_group.appendChild(component_ref)
284 260
285 261 fragment.appendChild(component_group)
286 262 doc.documentElement.appendChild(fragment)
287 263
288 264 # Add <Shortcut> to files that have it defined.
289 265 for file_id, metadata in sorted(SHORTCUTS.items()):
290 266 els = doc.getElementsByTagName('File')
291 267 els = [el for el in els if el.getAttribute('Id') == file_id]
292 268
293 269 if not els:
294 270 raise Exception('could not find File[Id=%s]' % file_id)
295 271
296 272 for el in els:
297 273 shortcut = doc.createElement('Shortcut')
298 274 shortcut.setAttribute('Id', 'hg.shortcut.%s' % file_id)
299 275 shortcut.setAttribute('Directory', 'ProgramMenuDir')
300 276 shortcut.setAttribute('Icon', 'hgIcon.ico')
301 277 shortcut.setAttribute('IconIndex', '0')
302 278 shortcut.setAttribute('Advertise', 'yes')
303 279 for k, v in sorted(metadata.items()):
304 280 shortcut.setAttribute(k, v)
305 281
306 282 el.appendChild(shortcut)
307 283
308 284 return doc.toprettyxml()
309 285
310 286
311 287 def build_installer(
312 288 source_dir: pathlib.Path,
313 289 python_exe: pathlib.Path,
314 290 msi_name='mercurial',
315 291 version=None,
316 post_build_fn=None,
317 292 extra_packages_script=None,
318 293 extra_wxs: typing.Optional[typing.Dict[str, str]] = None,
319 294 extra_features: typing.Optional[typing.List[str]] = None,
295 signing_info: typing.Optional[typing.Dict[str, str]] = None,
320 296 ):
321 297 """Build a WiX MSI installer.
322 298
323 299 ``source_dir`` is the path to the Mercurial source tree to use.
324 300 ``arch`` is the target architecture. either ``x86`` or ``x64``.
325 301 ``python_exe`` is the path to the Python executable to use/bundle.
326 302 ``version`` is the Mercurial version string. If not defined,
327 303 ``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 304 ``extra_packages_script`` is a command to be run to inject extra packages
333 305 into the py2exe binary. It should stage packages into the virtualenv and
334 306 print a null byte followed by a newline-separated list of packages that
335 307 should be included in the exe.
336 308 ``extra_wxs`` is a dict of {wxs_name: working_dir_for_wxs_build}.
337 309 ``extra_features`` is a list of additional named Features to include in
338 310 the build. These must match Feature names in one of the wxs scripts.
339 311 """
340 312 arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86'
341 313
342 314 hg_build_dir = source_dir / 'build'
343 dist_dir = source_dir / 'dist'
344 315
345 316 requirements_txt = (
346 317 source_dir / 'contrib' / 'packaging' / 'requirements_win32.txt'
347 318 )
348 319
349 320 build_py2exe(
350 321 source_dir,
351 322 hg_build_dir,
352 323 python_exe,
353 324 'wix',
354 325 requirements_txt,
355 326 extra_packages=EXTRA_PACKAGES,
356 327 extra_packages_script=extra_packages_script,
357 328 )
358 329
359 330 orig_version = version or find_version(source_dir)
360 331 version = normalize_windows_version(orig_version)
361 332 print('using version string: %s' % version)
362 333 if version != orig_version:
363 334 print('(normalized from: %s)' % orig_version)
364 335
365 if post_build_fn:
366 post_build_fn(source_dir, hg_build_dir, dist_dir, version)
367
368 336 build_dir = hg_build_dir / ('wix-%s' % arch)
369 337 staging_dir = build_dir / 'stage'
370 338
371 339 build_dir.mkdir(exist_ok=True)
372 340
373 341 # Purge the staging directory for every build so packaging is pristine.
374 342 if staging_dir.exists():
375 343 print('purging %s' % staging_dir)
376 344 shutil.rmtree(staging_dir)
377 345
378 346 stage_install(source_dir, staging_dir, lower_case=True)
379 347
380 348 # We also install some extra files.
381 349 process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
382 350
383 351 # And remove some files we don't want.
384 352 for f in STAGING_REMOVE_FILES:
385 353 p = staging_dir / f
386 354 if p.exists():
387 355 print('removing %s' % p)
388 356 p.unlink()
389 357
390 358 return run_wix_packaging(
391 359 source_dir,
392 360 build_dir,
393 361 staging_dir,
394 362 arch,
395 363 version=version,
396 364 orig_version=orig_version,
397 365 msi_name=msi_name,
398 366 extra_wxs=extra_wxs,
399 367 extra_features=extra_features,
368 signing_info=signing_info,
400 369 )
401 370
402 371
403 372 def run_wix_packaging(
404 373 source_dir: pathlib.Path,
405 374 build_dir: pathlib.Path,
406 375 staging_dir: pathlib.Path,
407 376 arch: str,
408 377 version: str,
409 378 orig_version: str,
410 379 msi_name: typing.Optional[str] = "mercurial",
411 380 extra_wxs: typing.Optional[typing.Dict[str, str]] = None,
412 381 extra_features: typing.Optional[typing.List[str]] = None,
382 signing_info: typing.Optional[typing.Dict[str, str]] = None,
413 383 ):
414 """Invokes WiX to package up a built Mercurial."""
384 """Invokes WiX to package up a built Mercurial.
385
386 ``signing_info`` is a dict defining properties to facilitate signing the
387 installer. Recognized keys include ``name``, ``subject_name``,
388 ``cert_path``, ``cert_password``, and ``timestamp_url``. If populated,
389 we will sign both the hg.exe and the .msi using the signing credentials
390 specified.
391 """
392 if signing_info:
393 sign_with_signtool(
394 staging_dir / "hg.exe",
395 "%s %s" % (signing_info["name"], version),
396 subject_name=signing_info["subject_name"],
397 cert_path=signing_info["cert_path"],
398 cert_password=signing_info["cert_password"],
399 timestamp_url=signing_info["timestamp_url"],
400 )
415 401
416 402 wix_dir = source_dir / 'contrib' / 'packaging' / 'wix'
417 403
418 404 wix_pkg, wix_entry = download_entry('wix', build_dir)
419 405 wix_path = build_dir / ('wix-%s' % wix_entry['version'])
420 406
421 407 if not wix_path.exists():
422 408 extract_zip_to_directory(wix_pkg, wix_path)
423 409
424 410 ensure_vc90_merge_modules(build_dir)
425 411
426 412 source_build_rel = pathlib.Path(os.path.relpath(source_dir, build_dir))
427 413
428 414 defines = {'Platform': arch}
429 415
430 416 # Derive a .wxs file with the staged files.
431 417 manifest_wxs = build_dir / 'stage.wxs'
432 418 with manifest_wxs.open('w', encoding='utf-8') as fh:
433 419 fh.write(make_files_xml(staging_dir, is_x64=arch == 'x64'))
434 420
435 421 run_candle(wix_path, build_dir, manifest_wxs, staging_dir, defines=defines)
436 422
437 423 for source, rel_path in sorted((extra_wxs or {}).items()):
438 424 run_candle(wix_path, build_dir, source, rel_path, defines=defines)
439 425
440 426 source = wix_dir / 'mercurial.wxs'
441 427 defines['Version'] = version
442 428 defines['Comments'] = 'Installs Mercurial version %s' % version
443 429 defines['VCRedistSrcDir'] = str(build_dir)
444 430 if extra_features:
445 431 assert all(';' not in f for f in extra_features)
446 432 defines['MercurialExtraFeatures'] = ';'.join(extra_features)
447 433
448 434 run_candle(wix_path, build_dir, source, source_build_rel, defines=defines)
449 435
450 436 msi_path = (
451 437 source_dir / 'dist' / ('%s-%s-%s.msi' % (msi_name, orig_version, arch))
452 438 )
453 439
454 440 args = [
455 441 str(wix_path / 'light.exe'),
456 442 '-nologo',
457 443 '-ext',
458 444 'WixUIExtension',
459 445 '-sw1076',
460 446 '-spdb',
461 447 '-o',
462 448 str(msi_path),
463 449 ]
464 450
465 451 for source, rel_path in sorted((extra_wxs or {}).items()):
466 452 assert source.endswith('.wxs')
467 453 source = os.path.basename(source)
468 454 args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
469 455
470 456 args.extend(
471 457 [str(build_dir / 'stage.wixobj'), str(build_dir / 'mercurial.wixobj'),]
472 458 )
473 459
474 460 subprocess.run(args, cwd=str(source_dir), check=True)
475 461
476 462 print('%s created' % msi_path)
477 463
464 if signing_info:
465 sign_with_signtool(
466 msi_path,
467 "%s %s" % (signing_info["name"], version),
468 subject_name=signing_info["subject_name"],
469 cert_path=signing_info["cert_path"],
470 cert_password=signing_info["cert_password"],
471 timestamp_url=signing_info["timestamp_url"],
472 )
473
478 474 return {
479 475 'msi_path': msi_path,
480 476 }
481
482
483 def build_signed_installer(
484 source_dir: pathlib.Path,
485 python_exe: pathlib.Path,
486 name: str,
487 version=None,
488 subject_name=None,
489 cert_path=None,
490 cert_password=None,
491 timestamp_url=None,
492 extra_packages_script=None,
493 extra_wxs=None,
494 extra_features=None,
495 ):
496 """Build an installer with signed executables."""
497
498 post_build_fn = make_post_build_signing_fn(
499 name,
500 subject_name=subject_name,
501 cert_path=cert_path,
502 cert_password=cert_password,
503 timestamp_url=timestamp_url,
504 )
505
506 info = build_installer(
507 source_dir,
508 python_exe=python_exe,
509 msi_name=name.lower(),
510 version=version,
511 post_build_fn=post_build_fn,
512 extra_packages_script=extra_packages_script,
513 extra_wxs=extra_wxs,
514 extra_features=extra_features,
515 )
516
517 description = '%s %s' % (name, version)
518
519 sign_with_signtool(
520 info['msi_path'],
521 description,
522 subject_name=subject_name,
523 cert_path=cert_path,
524 cert_password=cert_password,
525 timestamp_url=timestamp_url,
526 )
General Comments 0
You need to be logged in to leave comments. Login now