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