##// END OF EJS Templates
packaging: stage files and dynamically generate WiX installer...
Gregory Szorc -
r44022:94eac340 default
parent child Browse files
Show More
@@ -209,13 +209,26 b' def build_py2exe('
209 )
209 )
210
210
211
211
212 def stage_install(source_dir: pathlib.Path, staging_dir: pathlib.Path):
212 def stage_install(
213 source_dir: pathlib.Path, staging_dir: pathlib.Path, lower_case=False
214 ):
213 """Copy all files to be installed to a directory.
215 """Copy all files to be installed to a directory.
214
216
215 This allows packaging to simply walk a directory tree to find source
217 This allows packaging to simply walk a directory tree to find source
216 files.
218 files.
217 """
219 """
218 process_install_rules(STAGING_RULES, source_dir, staging_dir)
220 if lower_case:
221 rules = []
222 for source, dest in STAGING_RULES:
223 # Only lower directory names.
224 if '/' in dest:
225 parent, leaf = dest.rsplit('/', 1)
226 dest = '%s/%s' % (parent.lower(), leaf)
227 rules.append((source, dest))
228 else:
229 rules = STAGING_RULES
230
231 process_install_rules(rules, source_dir, staging_dir)
219
232
220 # Write out a default editor.rc file to configure notepad as the
233 # Write out a default editor.rc file to configure notepad as the
221 # default editor.
234 # default editor.
@@ -7,38 +7,60 b''
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 os
11 import os
11 import pathlib
12 import pathlib
12 import re
13 import re
14 import shutil
13 import subprocess
15 import subprocess
14 import tempfile
15 import typing
16 import typing
17 import uuid
16 import xml.dom.minidom
18 import xml.dom.minidom
17
19
18 from .downloads import download_entry
20 from .downloads import download_entry
19 from .py2exe import build_py2exe
21 from .py2exe import (
22 build_py2exe,
23 stage_install,
24 )
20 from .util import (
25 from .util import (
21 extract_zip_to_directory,
26 extract_zip_to_directory,
27 process_install_rules,
22 sign_with_signtool,
28 sign_with_signtool,
23 )
29 )
24
30
25
31
26 SUPPORT_WXS = [
27 ('contrib.wxs', r'contrib'),
28 ('dist.wxs', r'dist'),
29 ('doc.wxs', r'doc'),
30 ('help.wxs', r'mercurial\help'),
31 ('locale.wxs', r'mercurial\locale'),
32 ('templates.wxs', r'mercurial\templates'),
33 ]
34
35
36 EXTRA_PACKAGES = {
32 EXTRA_PACKAGES = {
37 'distutils',
33 'distutils',
38 'pygments',
34 'pygments',
39 }
35 }
40
36
41
37
38 EXTRA_INSTALL_RULES = [
39 ('contrib/packaging/wix/COPYING.rtf', 'COPYING.rtf'),
40 ('contrib/win32/mercurial.ini', 'hgrc.d/mercurial.rc'),
41 ]
42
43 STAGING_REMOVE_FILES = [
44 # We use the RTF variant.
45 'copying.txt',
46 ]
47
48 SHORTCUTS = {
49 # hg.1.html'
50 'hg.file.5d3e441c_28d9_5542_afd0_cdd4234f12d5': {
51 'Name': 'Mercurial Command Reference',
52 },
53 # hgignore.5.html
54 'hg.file.5757d8e0_f207_5e10_a2ec_3ba0a062f431': {
55 'Name': 'Mercurial Ignore Files',
56 },
57 # hgrc.5.html
58 'hg.file.92e605fd_1d1a_5dc6_9fc0_5d2998eb8f5e': {
59 'Name': 'Mercurial Configuration Files',
60 },
61 }
62
63
42 def find_version(source_dir: pathlib.Path):
64 def find_version(source_dir: pathlib.Path):
43 version_py = source_dir / 'mercurial' / '__version__.py'
65 version_py = source_dir / 'mercurial' / '__version__.py'
44
66
@@ -147,49 +169,165 b' def make_post_build_signing_fn('
147 return post_build_sign
169 return post_build_sign
148
170
149
171
150 LIBRARIES_XML = '''
172 def make_files_xml(staging_dir: pathlib.Path, is_x64) -> str:
151 <?xml version="1.0" encoding="utf-8"?>
173 """Create XML string listing every file to be installed."""
152 <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
153
154 <?include {wix_dir}/guids.wxi ?>
155 <?include {wix_dir}/defines.wxi ?>
156
174
157 <Fragment>
175 # We derive GUIDs from a deterministic file path identifier.
158 <DirectoryRef Id="INSTALLDIR" FileSource="$(var.SourceDir)">
176 # We shoehorn the name into something that looks like a URL because
159 <Directory Id="libdir" Name="lib" FileSource="$(var.SourceDir)/lib">
177 # the UUID namespaces are supposed to work that way (even though
160 <Component Id="libOutput" Guid="$(var.lib.guid)" Win64='$(var.IsX64)'>
178 # the input data probably is never validated).
161 </Component>
162 </Directory>
163 </DirectoryRef>
164 </Fragment>
165 </Wix>
166 '''.lstrip()
167
179
168
169 def make_libraries_xml(wix_dir: pathlib.Path, dist_dir: pathlib.Path):
170 """Make XML data for library components WXS."""
171 # We can't use ElementTree because it doesn't handle the
172 # <?include ?> directives.
173 doc = xml.dom.minidom.parseString(
180 doc = xml.dom.minidom.parseString(
174 LIBRARIES_XML.format(wix_dir=str(wix_dir))
181 '<?xml version="1.0" encoding="utf-8"?>'
182 '<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">'
183 '</Wix>'
175 )
184 )
176
185
177 component = doc.getElementsByTagName('Component')[0]
186 # Assemble the install layout by directory. This makes it easier to
187 # emit XML, since each directory has separate entities.
188 manifest = collections.defaultdict(dict)
189
190 for root, dirs, files in os.walk(staging_dir):
191 dirs.sort()
192
193 root = pathlib.Path(root)
194 rel_dir = root.relative_to(staging_dir)
195
196 for i in range(len(rel_dir.parts)):
197 parent = '/'.join(rel_dir.parts[0 : i + 1])
198 manifest.setdefault(parent, {})
199
200 for f in sorted(files):
201 full = root / f
202 manifest[str(rel_dir).replace('\\', '/')][full.name] = full
203
204 component_groups = collections.defaultdict(list)
205
206 # Now emit a <Fragment> for each directory.
207 # Each directory is composed of a <DirectoryRef> pointing to its parent
208 # and defines child <Directory>'s and a <Component> with all the files.
209 for dir_name, entries in sorted(manifest.items()):
210 # The directory id is derived from the path. But the root directory
211 # is special.
212 if dir_name == '.':
213 parent_directory_id = 'INSTALLDIR'
214 else:
215 parent_directory_id = 'hg.dir.%s' % dir_name.replace('/', '.')
178
216
179 f = doc.createElement('File')
217 fragment = doc.createElement('Fragment')
180 f.setAttribute('Name', 'library.zip')
218 directory_ref = doc.createElement('DirectoryRef')
181 f.setAttribute('KeyPath', 'yes')
219 directory_ref.setAttribute('Id', parent_directory_id)
182 component.appendChild(f)
220
221 # Add <Directory> entries for immediate children directories.
222 for possible_child in sorted(manifest.keys()):
223 if (
224 dir_name == '.'
225 and '/' not in possible_child
226 and possible_child != '.'
227 ):
228 child_directory_id = 'hg.dir.%s' % possible_child
229 name = possible_child
230 else:
231 if not possible_child.startswith('%s/' % dir_name):
232 continue
233 name = possible_child[len(dir_name) + 1 :]
234 if '/' in name:
235 continue
236
237 child_directory_id = 'hg.dir.%s' % possible_child.replace(
238 '/', '.'
239 )
240
241 directory = doc.createElement('Directory')
242 directory.setAttribute('Id', child_directory_id)
243 directory.setAttribute('Name', name)
244 directory_ref.appendChild(directory)
245
246 # Add <Component>s for files in this directory.
247 for rel, source_path in sorted(entries.items()):
248 if dir_name == '.':
249 full_rel = rel
250 else:
251 full_rel = '%s/%s' % (dir_name, rel)
183
252
184 lib_dir = dist_dir / 'lib'
253 component_unique_id = (
254 'https://www.mercurial-scm.org/wix-installer/0/component/%s'
255 % full_rel
256 )
257 component_guid = uuid.uuid5(uuid.NAMESPACE_URL, component_unique_id)
258 component_id = 'hg.component.%s' % str(component_guid).replace(
259 '-', '_'
260 )
261
262 component = doc.createElement('Component')
263
264 component.setAttribute('Id', component_id)
265 component.setAttribute('Guid', str(component_guid).upper())
266 component.setAttribute('Win64', 'yes' if is_x64 else 'no')
267
268 # Assign this component to a top-level group.
269 if dir_name == '.':
270 component_groups['ROOT'].append(component_id)
271 elif '/' in dir_name:
272 component_groups[dir_name[0 : dir_name.index('/')]].append(
273 component_id
274 )
275 else:
276 component_groups[dir_name].append(component_id)
277
278 unique_id = (
279 'https://www.mercurial-scm.org/wix-installer/0/%s' % full_rel
280 )
281 file_guid = uuid.uuid5(uuid.NAMESPACE_URL, unique_id)
282
283 # IDs have length limits. So use GUID to derive them.
284 file_guid_normalized = str(file_guid).replace('-', '_')
285 file_id = 'hg.file.%s' % file_guid_normalized
185
286
186 for p in sorted(lib_dir.iterdir()):
287 file_element = doc.createElement('File')
187 if not p.name.endswith(('.dll', '.pyd')):
288 file_element.setAttribute('Id', file_id)
188 continue
289 file_element.setAttribute('Source', str(source_path))
290 file_element.setAttribute('KeyPath', 'yes')
291 file_element.setAttribute('ReadOnly', 'yes')
292
293 component.appendChild(file_element)
294 directory_ref.appendChild(component)
295
296 fragment.appendChild(directory_ref)
297 doc.documentElement.appendChild(fragment)
298
299 for group, component_ids in sorted(component_groups.items()):
300 fragment = doc.createElement('Fragment')
301 component_group = doc.createElement('ComponentGroup')
302 component_group.setAttribute('Id', 'hg.group.%s' % group)
303
304 for component_id in component_ids:
305 component_ref = doc.createElement('ComponentRef')
306 component_ref.setAttribute('Id', component_id)
307 component_group.appendChild(component_ref)
189
308
190 f = doc.createElement('File')
309 fragment.appendChild(component_group)
191 f.setAttribute('Name', p.name)
310 doc.documentElement.appendChild(fragment)
192 component.appendChild(f)
311
312 # Add <Shortcut> to files that have it defined.
313 for file_id, metadata in sorted(SHORTCUTS.items()):
314 els = doc.getElementsByTagName('File')
315 els = [el for el in els if el.getAttribute('Id') == file_id]
316
317 if not els:
318 raise Exception('could not find File[Id=%s]' % file_id)
319
320 for el in els:
321 shortcut = doc.createElement('Shortcut')
322 shortcut.setAttribute('Id', 'hg.shortcut.%s' % file_id)
323 shortcut.setAttribute('Directory', 'ProgramMenuDir')
324 shortcut.setAttribute('Icon', 'hgIcon.ico')
325 shortcut.setAttribute('IconIndex', '0')
326 shortcut.setAttribute('Advertise', 'yes')
327 for k, v in sorted(metadata.items()):
328 shortcut.setAttribute(k, v)
329
330 el.appendChild(shortcut)
193
331
194 return doc.toprettyxml()
332 return doc.toprettyxml()
195
333
@@ -248,9 +386,27 b' def build_installer('
248 post_build_fn(source_dir, hg_build_dir, dist_dir, version)
386 post_build_fn(source_dir, hg_build_dir, dist_dir, version)
249
387
250 build_dir = hg_build_dir / ('wix-%s' % arch)
388 build_dir = hg_build_dir / ('wix-%s' % arch)
389 staging_dir = build_dir / 'stage'
251
390
252 build_dir.mkdir(exist_ok=True)
391 build_dir.mkdir(exist_ok=True)
253
392
393 # Purge the staging directory for every build so packaging is pristine.
394 if staging_dir.exists():
395 print('purging %s' % staging_dir)
396 shutil.rmtree(staging_dir)
397
398 stage_install(source_dir, staging_dir, lower_case=True)
399
400 # We also install some extra files.
401 process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
402
403 # And remove some files we don't want.
404 for f in STAGING_REMOVE_FILES:
405 p = staging_dir / f
406 if p.exists():
407 print('removing %s' % p)
408 p.unlink()
409
254 wix_pkg, wix_entry = download_entry('wix', hg_build_dir)
410 wix_pkg, wix_entry = download_entry('wix', hg_build_dir)
255 wix_path = hg_build_dir / ('wix-%s' % wix_entry['version'])
411 wix_path = hg_build_dir / ('wix-%s' % wix_entry['version'])
256
412
@@ -263,25 +419,16 b' def build_installer('
263
419
264 defines = {'Platform': arch}
420 defines = {'Platform': arch}
265
421
266 for wxs, rel_path in SUPPORT_WXS:
422 # Derive a .wxs file with the staged files.
267 wxs = wix_dir / wxs
423 manifest_wxs = build_dir / 'stage.wxs'
268 wxs_source_dir = source_dir / rel_path
424 with manifest_wxs.open('w', encoding='utf-8') as fh:
269 run_candle(wix_path, build_dir, wxs, wxs_source_dir, defines=defines)
425 fh.write(make_files_xml(staging_dir, is_x64=arch == 'x64'))
426
427 run_candle(wix_path, build_dir, manifest_wxs, staging_dir, defines=defines)
270
428
271 for source, rel_path in sorted((extra_wxs or {}).items()):
429 for source, rel_path in sorted((extra_wxs or {}).items()):
272 run_candle(wix_path, build_dir, source, rel_path, defines=defines)
430 run_candle(wix_path, build_dir, source, rel_path, defines=defines)
273
431
274 # candle.exe doesn't like when we have an open handle on the file.
275 # So use TemporaryDirectory() instead of NamedTemporaryFile().
276 with tempfile.TemporaryDirectory() as td:
277 td = pathlib.Path(td)
278
279 tf = td / 'library.wxs'
280 with tf.open('w') as fh:
281 fh.write(make_libraries_xml(wix_dir, dist_dir))
282
283 run_candle(wix_path, build_dir, tf, dist_dir, defines=defines)
284
285 source = wix_dir / 'mercurial.wxs'
432 source = wix_dir / 'mercurial.wxs'
286 defines['Version'] = version
433 defines['Version'] = version
287 defines['Comments'] = 'Installs Mercurial version %s' % version
434 defines['Comments'] = 'Installs Mercurial version %s' % version
@@ -307,20 +454,13 b' def build_installer('
307 str(msi_path),
454 str(msi_path),
308 ]
455 ]
309
456
310 for source, rel_path in SUPPORT_WXS:
311 assert source.endswith('.wxs')
312 args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
313
314 for source, rel_path in sorted((extra_wxs or {}).items()):
457 for source, rel_path in sorted((extra_wxs or {}).items()):
315 assert source.endswith('.wxs')
458 assert source.endswith('.wxs')
316 source = os.path.basename(source)
459 source = os.path.basename(source)
317 args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
460 args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
318
461
319 args.extend(
462 args.extend(
320 [
463 [str(build_dir / 'stage.wixobj'), str(build_dir / 'mercurial.wixobj'),]
321 str(build_dir / 'library.wixobj'),
322 str(build_dir / 'mercurial.wixobj'),
323 ]
324 )
464 )
325
465
326 subprocess.run(args, cwd=str(source_dir), check=True)
466 subprocess.run(args, cwd=str(source_dir), check=True)
@@ -4,46 +4,9 b''
4 and replace 'Mercurial' in this notice with the name of
4 and replace 'Mercurial' in this notice with the name of
5 your project. Component GUIDs have global namespace! -->
5 your project. Component GUIDs have global namespace! -->
6
6
7 <!-- contrib.wxs -->
8 <?define contrib.guid = {4E11FFC2-E2F7-482A-8460-9394B5489F02} ?>
9 <?define contrib.vim.guid = {BB04903A-652D-4C4F-9590-2BD07A2304F2} ?>
10
11 <!-- dist.wxs -->
12 <?define dist.guid = {CE405FE6-CD1E-4873-9C9A-7683AE5A3D90} ?>
13 <?define lib.guid = {877633b5-0b7e-4b46-8f1c-224a61733297} ?>
14
15 <!-- doc.wxs -->
16 <?define doc.hg.1.html.guid = {AAAA3FDA-EDC5-4220-B59D-D342722358A2} ?>
17 <?define doc.hgignore.5.html.guid = {AA9118C4-F3A0-4429-A5F4-5A1906B2D67F} ?>
18 <?define doc.hgrc.5.html = {E0CEA1EB-FA01-408c-844B-EE5965165BAE} ?>
19 <?define doc.style.css = {172F8262-98E0-4711-BD39-4DAE0D77EF05} ?>
20
21 <!-- help.wxs -->
22 <?define help.root.guid = {9FA957DB-6DFE-44f2-AD03-293B2791CF17} ?>
23 <?define help.internals.guid = {2DD7669D-0DB8-4C39-9806-78E6475E7ACC} ?>
24
25 <!-- templates.wxs -->
26 <?define templates.root.guid = {437FD55C-7756-4EA0-87E5-FDBE75DC8595} ?>
27 <?define templates.atom.guid = {D30E14A5-8AF0-4268-8B00-00BEE9E09E39} ?>
28 <?define templates.coal.guid = {B63CCAAB-4EAF-43b4-901E-4BD13F5B78FC} ?>
29 <?define templates.gitweb.guid = {827334AF-1EFD-421B-962C-5660A068F612} ?>
30 <?define templates.json.guid = {F535BE7A-EC34-46E0-B9BE-013F3DBAFB19} ?>
31 <?define templates.monoblue.guid = {8060A1E4-BD4C-453E-92CB-9536DC44A9E3} ?>
32 <?define templates.paper.guid = {61AB1DE9-645F-46ED-8AF8-0CF02267FFBB} ?>
33 <?define templates.raw.guid = {834DF8D7-9784-43A6-851D-A96CE1B3575B} ?>
34 <?define templates.rss.guid = {9338FA09-E128-4B1C-B723-1142DBD09E14} ?>
35 <?define templates.spartan.guid = {80222625-FA8F-44b1-86CE-1781EF375D09} ?>
36 <?define templates.static.guid = {6B3D7C24-98DA-4B67-9F18-35F77357B0B4} ?>
37
38 <!-- mercurial.wxs -->
7 <!-- mercurial.wxs -->
39 <?define ProductUpgradeCode = {A1CC6134-E945-4399-BE36-EB0017FDF7CF} ?>
8 <?define ProductUpgradeCode = {A1CC6134-E945-4399-BE36-EB0017FDF7CF} ?>
40
41 <?define ComponentMainExecutableGUID = {D102B8FA-059B-4ACC-9FA3-8C78C3B58EEF} ?>
9 <?define ComponentMainExecutableGUID = {D102B8FA-059B-4ACC-9FA3-8C78C3B58EEF} ?>
42
43 <?define ReadMe.guid = {56A8E372-991D-4DCA-B91D-93D775974CF5} ?>
44 <?define COPYING.guid = {B7801DBA-1C49-4BF4-91AD-33C65F5C7895} ?>
45 <?define mercurial.rc.guid = {52BBF223-58F6-47F5-9353-8FDEA88D9B8D} ?>
46 <?define mergetools.rc.guid = {5AF0430B-2B76-4BC6-91EE-E771B74A0214} ?>
47 <?define ProgramMenuDir.guid = {D5A63320-1238-489B-B68B-CF053E9577CA} ?>
10 <?define ProgramMenuDir.guid = {D5A63320-1238-489B-B68B-CF053E9577CA} ?>
48
11
49 </Include>
12 </Include>
@@ -60,30 +60,10 b''
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 <File Id='hgEXE' Name='hg.exe' Source='dist\hg.exe' KeyPath='yes' />
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 <Component Id='ReadMe' Guid='$(var.ReadMe.guid)' Win64='$(var.IsX64)'>
68 <File Id='ReadMe' Name='ReadMe.html' Source='contrib\win32\ReadMe.html'
69 KeyPath='yes'/>
70 </Component>
71 <Component Id='COPYING' Guid='$(var.COPYING.guid)' Win64='$(var.IsX64)'>
72 <File Id='COPYING' Name='COPYING.rtf' Source='contrib\packaging\wix\COPYING.rtf'
73 KeyPath='yes'/>
74 </Component>
75
76 <Directory Id='HGRCD' Name='hgrc.d'>
77 <Component Id='mercurial.rc' Guid='$(var.mercurial.rc.guid)' Win64='$(var.IsX64)'>
78 <File Id='mercurial.rc' Name='mercurial.rc' Source='contrib\win32\mercurial.ini'
79 ReadOnly='yes' KeyPath='yes'/>
80 </Component>
81 <Component Id='mergetools.rc' Guid='$(var.mergetools.rc.guid)' Win64='$(var.IsX64)'>
82 <File Id='mergetools.rc' Name='mergetools.rc' Source='mercurial\default.d\mergetools.rc'
83 ReadOnly='yes' KeyPath='yes'/>
84 </Component>
85 </Directory>
86
87 </Directory>
67 </Directory>
88 </Directory>
68 </Directory>
89
69
@@ -117,15 +97,12 b''
117 <Feature Id='MainProgram' Title='Program' Description='Mercurial command line app'
97 <Feature Id='MainProgram' Title='Program' Description='Mercurial command line app'
118 Level='1' Absent='disallow' >
98 Level='1' Absent='disallow' >
119 <ComponentRef Id='MainExecutable' />
99 <ComponentRef Id='MainExecutable' />
120 <ComponentRef Id='distOutput' />
121 <ComponentRef Id='libOutput' />
122 <ComponentRef Id='ProgramMenuDir' />
100 <ComponentRef Id='ProgramMenuDir' />
123 <ComponentRef Id='ReadMe' />
101 <ComponentGroupRef Id="hg.group.ROOT" />
124 <ComponentRef Id='COPYING' />
102 <ComponentGroupRef Id="hg.group.hgrc.d" />
125 <ComponentRef Id='mercurial.rc' />
103 <ComponentGroupRef Id="hg.group.help" />
126 <ComponentRef Id='mergetools.rc' />
104 <ComponentGroupRef Id="hg.group.lib" />
127 <ComponentGroupRef Id='helpFolder' />
105 <ComponentGroupRef Id="hg.group.templates" />
128 <ComponentGroupRef Id='templatesFolder' />
129 <MergeRef Id='VCRuntime' />
106 <MergeRef Id='VCRuntime' />
130 <MergeRef Id='VCRuntimePolicy' />
107 <MergeRef Id='VCRuntimePolicy' />
131 </Feature>
108 </Feature>
@@ -135,13 +112,13 b''
135 <?endforeach?>
112 <?endforeach?>
136 <?endif?>
113 <?endif?>
137 <Feature Id='Locales' Title='Translations' Description='Translations' Level='1'>
114 <Feature Id='Locales' Title='Translations' Description='Translations' Level='1'>
138 <ComponentGroupRef Id='localeFolder' />
115 <ComponentGroupRef Id="hg.group.locale" />
139 </Feature>
116 </Feature>
140 <Feature Id='Documentation' Title='Documentation' Description='HTML man pages' Level='1'>
117 <Feature Id='Documentation' Title='Documentation' Description='HTML man pages' Level='1'>
141 <ComponentGroupRef Id='docFolder' />
118 <ComponentGroupRef Id="hg.group.doc" />
142 </Feature>
119 </Feature>
143 <Feature Id='Misc' Title='Miscellaneous' Description='Contributed scripts' Level='1'>
120 <Feature Id='Misc' Title='Miscellaneous' Description='Contributed scripts' Level='1'>
144 <ComponentGroupRef Id='contribFolder' />
121 <ComponentGroupRef Id="hg.group.contrib" />
145 </Feature>
122 </Feature>
146 </Feature>
123 </Feature>
147
124
@@ -16,7 +16,7 b''
16
16
17 [ui]
17 [ui]
18 ; editor used to enter commit logs, etc. Most text editors will work.
18 ; editor used to enter commit logs, etc. Most text editors will work.
19 editor = notepad
19 ; editor = notepad
20 ; show changed files and be a bit more verbose if True
20 ; show changed files and be a bit more verbose if True
21 ; verbose = True
21 ; verbose = True
22 ; colorize commands output
22 ; colorize commands output
@@ -162,75 +162,6 b' Verify the json works too:'
162 "fsmonitor-watchman": "false",
162 "fsmonitor-watchman": "false",
163 "fsmonitor-watchman-error": "warning: Watchman unavailable: watchman exited with code 1",
163 "fsmonitor-watchman-error": "warning: Watchman unavailable: watchman exited with code 1",
164
164
165
166 #if test-repo
167 $ . "$TESTDIR/helpers-testrepo.sh"
168
169 $ cat >> wixxml.py << EOF
170 > import os
171 > import subprocess
172 > import sys
173 > import xml.etree.ElementTree as ET
174 > from mercurial import pycompat
175 >
176 > # MSYS mangles the path if it expands $TESTDIR
177 > testdir = os.environ['TESTDIR']
178 > ns = {'wix' : 'http://schemas.microsoft.com/wix/2006/wi'}
179 >
180 > def directory(node, relpath):
181 > '''generator of files in the xml node, rooted at relpath'''
182 > dirs = node.findall('./{%(wix)s}Directory' % ns)
183 >
184 > for d in dirs:
185 > for subfile in directory(d, relpath + d.attrib['Name'] + '/'):
186 > yield subfile
187 >
188 > files = node.findall('./{%(wix)s}Component/{%(wix)s}File' % ns)
189 >
190 > for f in files:
191 > yield pycompat.sysbytes(relpath + f.attrib['Name'])
192 >
193 > def hgdirectory(relpath):
194 > '''generator of tracked files, rooted at relpath'''
195 > hgdir = "%s/../mercurial" % (testdir)
196 > args = ['hg', '--cwd', hgdir, 'files', relpath]
197 > proc = subprocess.Popen(args, stdout=subprocess.PIPE,
198 > stderr=subprocess.PIPE)
199 > output = proc.communicate()[0]
200 >
201 > for line in output.splitlines():
202 > if os.name == 'nt':
203 > yield line.replace(pycompat.sysbytes(os.sep), b'/')
204 > else:
205 > yield line
206 >
207 > tracked = [f for f in hgdirectory(sys.argv[1])]
208 >
209 > xml = ET.parse("%s/../contrib/packaging/wix/%s.wxs" % (testdir, sys.argv[1]))
210 > root = xml.getroot()
211 > dir = root.find('.//{%(wix)s}DirectoryRef' % ns)
212 >
213 > installed = [f for f in directory(dir, '')]
214 >
215 > print('Not installed:')
216 > for f in sorted(set(tracked) - set(installed)):
217 > print(' %s' % pycompat.sysstr(f))
218 >
219 > print('Not tracked:')
220 > for f in sorted(set(installed) - set(tracked)):
221 > print(' %s' % pycompat.sysstr(f))
222 > EOF
223
224 $ ( testrepohgenv; "$PYTHON" wixxml.py help )
225 Not installed:
226 Not tracked:
227
228 $ ( testrepohgenv; "$PYTHON" wixxml.py templates )
229 Not installed:
230 Not tracked:
231
232 #endif
233
234 Verify that Mercurial is installable with pip. Note that this MUST be
165 Verify that Mercurial is installable with pip. Note that this MUST be
235 the last test in this file, because we do some nasty things to the
166 the last test in this file, because we do some nasty things to the
236 shell environment in order to make the virtualenv work reliably.
167 shell environment in order to make the virtualenv work reliably.
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now