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