82 return '.'.join('%d' % x for x in (major, minor, build)) |
78 return '.'.join('%d' % x for x in (major, minor, build)) |
83 |
79 |
84 |
80 |
85 def ensure_vc90_merge_modules(build_dir): |
81 def ensure_vc90_merge_modules(build_dir): |
86 x86 = ( |
82 x86 = ( |
87 download_entry('vc9-crt-x86-msm', build_dir, |
83 download_entry( |
88 local_name='microsoft.vcxx.crt.x86_msm.msm')[0], |
84 'vc9-crt-x86-msm', |
89 download_entry('vc9-crt-x86-msm-policy', build_dir, |
85 build_dir, |
90 local_name='policy.x.xx.microsoft.vcxx.crt.x86_msm.msm')[0] |
86 local_name='microsoft.vcxx.crt.x86_msm.msm', |
|
87 )[0], |
|
88 download_entry( |
|
89 'vc9-crt-x86-msm-policy', |
|
90 build_dir, |
|
91 local_name='policy.x.xx.microsoft.vcxx.crt.x86_msm.msm', |
|
92 )[0], |
91 ) |
93 ) |
92 |
94 |
93 x64 = ( |
95 x64 = ( |
94 download_entry('vc9-crt-x64-msm', build_dir, |
96 download_entry( |
95 local_name='microsoft.vcxx.crt.x64_msm.msm')[0], |
97 'vc9-crt-x64-msm', |
96 download_entry('vc9-crt-x64-msm-policy', build_dir, |
98 build_dir, |
97 local_name='policy.x.xx.microsoft.vcxx.crt.x64_msm.msm')[0] |
99 local_name='microsoft.vcxx.crt.x64_msm.msm', |
|
100 )[0], |
|
101 download_entry( |
|
102 'vc9-crt-x64-msm-policy', |
|
103 build_dir, |
|
104 local_name='policy.x.xx.microsoft.vcxx.crt.x64_msm.msm', |
|
105 )[0], |
98 ) |
106 ) |
99 return { |
107 return { |
100 'x86': x86, |
108 'x86': x86, |
101 'x64': x64, |
109 'x64': x64, |
102 } |
110 } |
114 args.extend('-d%s=%s' % define for define in sorted(defines.items())) |
122 args.extend('-d%s=%s' % define for define in sorted(defines.items())) |
115 |
123 |
116 subprocess.run(args, cwd=str(cwd), check=True) |
124 subprocess.run(args, cwd=str(cwd), check=True) |
117 |
125 |
118 |
126 |
119 def make_post_build_signing_fn(name, subject_name=None, cert_path=None, |
127 def make_post_build_signing_fn( |
120 cert_password=None, timestamp_url=None): |
128 name, |
|
129 subject_name=None, |
|
130 cert_path=None, |
|
131 cert_password=None, |
|
132 timestamp_url=None, |
|
133 ): |
121 """Create a callable that will use signtool to sign hg.exe.""" |
134 """Create a callable that will use signtool to sign hg.exe.""" |
122 |
135 |
123 def post_build_sign(source_dir, build_dir, dist_dir, version): |
136 def post_build_sign(source_dir, build_dir, dist_dir, version): |
124 description = '%s %s' % (name, version) |
137 description = '%s %s' % (name, version) |
125 |
138 |
126 sign_with_signtool(dist_dir / 'hg.exe', description, |
139 sign_with_signtool( |
127 subject_name=subject_name, cert_path=cert_path, |
140 dist_dir / 'hg.exe', |
128 cert_password=cert_password, |
141 description, |
129 timestamp_url=timestamp_url) |
142 subject_name=subject_name, |
|
143 cert_path=cert_path, |
|
144 cert_password=cert_password, |
|
145 timestamp_url=timestamp_url, |
|
146 ) |
130 |
147 |
131 return post_build_sign |
148 return post_build_sign |
132 |
149 |
133 |
150 |
134 LIBRARIES_XML = ''' |
151 LIBRARIES_XML = ''' |
153 def make_libraries_xml(wix_dir: pathlib.Path, dist_dir: pathlib.Path): |
170 def make_libraries_xml(wix_dir: pathlib.Path, dist_dir: pathlib.Path): |
154 """Make XML data for library components WXS.""" |
171 """Make XML data for library components WXS.""" |
155 # We can't use ElementTree because it doesn't handle the |
172 # We can't use ElementTree because it doesn't handle the |
156 # <?include ?> directives. |
173 # <?include ?> directives. |
157 doc = xml.dom.minidom.parseString( |
174 doc = xml.dom.minidom.parseString( |
158 LIBRARIES_XML.format(wix_dir=str(wix_dir))) |
175 LIBRARIES_XML.format(wix_dir=str(wix_dir)) |
|
176 ) |
159 |
177 |
160 component = doc.getElementsByTagName('Component')[0] |
178 component = doc.getElementsByTagName('Component')[0] |
161 |
179 |
162 f = doc.createElement('File') |
180 f = doc.createElement('File') |
163 f.setAttribute('Name', 'library.zip') |
181 f.setAttribute('Name', 'library.zip') |
175 component.appendChild(f) |
193 component.appendChild(f) |
176 |
194 |
177 return doc.toprettyxml() |
195 return doc.toprettyxml() |
178 |
196 |
179 |
197 |
180 def build_installer(source_dir: pathlib.Path, python_exe: pathlib.Path, |
198 def build_installer( |
181 msi_name='mercurial', version=None, post_build_fn=None, |
199 source_dir: pathlib.Path, |
182 extra_packages_script=None, |
200 python_exe: pathlib.Path, |
183 extra_wxs:typing.Optional[typing.Dict[str,str]]=None, |
201 msi_name='mercurial', |
184 extra_features:typing.Optional[typing.List[str]]=None): |
202 version=None, |
|
203 post_build_fn=None, |
|
204 extra_packages_script=None, |
|
205 extra_wxs: typing.Optional[typing.Dict[str, str]] = None, |
|
206 extra_features: typing.Optional[typing.List[str]] = None, |
|
207 ): |
185 """Build a WiX MSI installer. |
208 """Build a WiX MSI installer. |
186 |
209 |
187 ``source_dir`` is the path to the Mercurial source tree to use. |
210 ``source_dir`` is the path to the Mercurial source tree to use. |
188 ``arch`` is the target architecture. either ``x86`` or ``x64``. |
211 ``arch`` is the target architecture. either ``x86`` or ``x64``. |
189 ``python_exe`` is the path to the Python executable to use/bundle. |
212 ``python_exe`` is the path to the Python executable to use/bundle. |
207 dist_dir = source_dir / 'dist' |
230 dist_dir = source_dir / 'dist' |
208 wix_dir = source_dir / 'contrib' / 'packaging' / 'wix' |
231 wix_dir = source_dir / 'contrib' / 'packaging' / 'wix' |
209 |
232 |
210 requirements_txt = wix_dir / 'requirements.txt' |
233 requirements_txt = wix_dir / 'requirements.txt' |
211 |
234 |
212 build_py2exe(source_dir, hg_build_dir, |
235 build_py2exe( |
213 python_exe, 'wix', requirements_txt, |
236 source_dir, |
214 extra_packages=EXTRA_PACKAGES, |
237 hg_build_dir, |
215 extra_packages_script=extra_packages_script) |
238 python_exe, |
|
239 'wix', |
|
240 requirements_txt, |
|
241 extra_packages=EXTRA_PACKAGES, |
|
242 extra_packages_script=extra_packages_script, |
|
243 ) |
216 |
244 |
217 version = version or normalize_version(find_version(source_dir)) |
245 version = version or normalize_version(find_version(source_dir)) |
218 print('using version string: %s' % version) |
246 print('using version string: %s' % version) |
219 |
247 |
220 if post_build_fn: |
248 if post_build_fn: |
263 assert all(';' not in f for f in extra_features) |
291 assert all(';' not in f for f in extra_features) |
264 defines['MercurialExtraFeatures'] = ';'.join(extra_features) |
292 defines['MercurialExtraFeatures'] = ';'.join(extra_features) |
265 |
293 |
266 run_candle(wix_path, build_dir, source, source_build_rel, defines=defines) |
294 run_candle(wix_path, build_dir, source, source_build_rel, defines=defines) |
267 |
295 |
268 msi_path = source_dir / 'dist' / ( |
296 msi_path = ( |
269 '%s-%s-%s.msi' % (msi_name, version, arch)) |
297 source_dir / 'dist' / ('%s-%s-%s.msi' % (msi_name, version, arch)) |
|
298 ) |
270 |
299 |
271 args = [ |
300 args = [ |
272 str(wix_path / 'light.exe'), |
301 str(wix_path / 'light.exe'), |
273 '-nologo', |
302 '-nologo', |
274 '-ext', 'WixUIExtension', |
303 '-ext', |
|
304 'WixUIExtension', |
275 '-sw1076', |
305 '-sw1076', |
276 '-spdb', |
306 '-spdb', |
277 '-o', str(msi_path), |
307 '-o', |
|
308 str(msi_path), |
278 ] |
309 ] |
279 |
310 |
280 for source, rel_path in SUPPORT_WXS: |
311 for source, rel_path in SUPPORT_WXS: |
281 assert source.endswith('.wxs') |
312 assert source.endswith('.wxs') |
282 args.append(str(build_dir / ('%s.wixobj' % source[:-4]))) |
313 args.append(str(build_dir / ('%s.wixobj' % source[:-4]))) |
284 for source, rel_path in sorted((extra_wxs or {}).items()): |
315 for source, rel_path in sorted((extra_wxs or {}).items()): |
285 assert source.endswith('.wxs') |
316 assert source.endswith('.wxs') |
286 source = os.path.basename(source) |
317 source = os.path.basename(source) |
287 args.append(str(build_dir / ('%s.wixobj' % source[:-4]))) |
318 args.append(str(build_dir / ('%s.wixobj' % source[:-4]))) |
288 |
319 |
289 args.extend([ |
320 args.extend( |
290 str(build_dir / 'library.wixobj'), |
321 [ |
291 str(build_dir / 'mercurial.wixobj'), |
322 str(build_dir / 'library.wixobj'), |
292 ]) |
323 str(build_dir / 'mercurial.wixobj'), |
|
324 ] |
|
325 ) |
293 |
326 |
294 subprocess.run(args, cwd=str(source_dir), check=True) |
327 subprocess.run(args, cwd=str(source_dir), check=True) |
295 |
328 |
296 print('%s created' % msi_path) |
329 print('%s created' % msi_path) |
297 |
330 |
298 return { |
331 return { |
299 'msi_path': msi_path, |
332 'msi_path': msi_path, |
300 } |
333 } |
301 |
334 |
302 |
335 |
303 def build_signed_installer(source_dir: pathlib.Path, python_exe: pathlib.Path, |
336 def build_signed_installer( |
304 name: str, version=None, subject_name=None, |
337 source_dir: pathlib.Path, |
305 cert_path=None, cert_password=None, |
338 python_exe: pathlib.Path, |
306 timestamp_url=None, extra_packages_script=None, |
339 name: str, |
307 extra_wxs=None, extra_features=None): |
340 version=None, |
|
341 subject_name=None, |
|
342 cert_path=None, |
|
343 cert_password=None, |
|
344 timestamp_url=None, |
|
345 extra_packages_script=None, |
|
346 extra_wxs=None, |
|
347 extra_features=None, |
|
348 ): |
308 """Build an installer with signed executables.""" |
349 """Build an installer with signed executables.""" |
309 |
350 |
310 post_build_fn = make_post_build_signing_fn( |
351 post_build_fn = make_post_build_signing_fn( |
311 name, |
352 name, |
312 subject_name=subject_name, |
353 subject_name=subject_name, |
313 cert_path=cert_path, |
354 cert_path=cert_path, |
314 cert_password=cert_password, |
355 cert_password=cert_password, |
315 timestamp_url=timestamp_url) |
356 timestamp_url=timestamp_url, |
316 |
357 ) |
317 info = build_installer(source_dir, python_exe=python_exe, |
358 |
318 msi_name=name.lower(), version=version, |
359 info = build_installer( |
319 post_build_fn=post_build_fn, |
360 source_dir, |
320 extra_packages_script=extra_packages_script, |
361 python_exe=python_exe, |
321 extra_wxs=extra_wxs, extra_features=extra_features) |
362 msi_name=name.lower(), |
|
363 version=version, |
|
364 post_build_fn=post_build_fn, |
|
365 extra_packages_script=extra_packages_script, |
|
366 extra_wxs=extra_wxs, |
|
367 extra_features=extra_features, |
|
368 ) |
322 |
369 |
323 description = '%s %s' % (name, version) |
370 description = '%s %s' % (name, version) |
324 |
371 |
325 sign_with_signtool(info['msi_path'], description, |
372 sign_with_signtool( |
326 subject_name=subject_name, cert_path=cert_path, |
373 info['msi_path'], |
327 cert_password=cert_password, timestamp_url=timestamp_url) |
374 description, |
|
375 subject_name=subject_name, |
|
376 cert_path=cert_path, |
|
377 cert_password=cert_password, |
|
378 timestamp_url=timestamp_url, |
|
379 ) |