ยปCore Development>Code coverage>Tools/msi/csv_to_wxs.py

Python code coverage for Tools/msi/csv_to_wxs.py

#countcontent
1n/a'''
2n/aProcesses a CSV file containing a list of files into a WXS file with
3n/acomponents for each listed file.
4n/a
5n/aThe CSV columns are:
6n/a source of file, target for file, group name
7n/a
8n/aUsage::
9n/a py txt_to_wxs.py [path to file list .csv] [path to destination .wxs]
10n/a
11n/aThis is necessary to handle structures where some directories only
12n/acontain other directories. MSBuild is not able to generate the
13n/aDirectory entries in the WXS file correctly, as it operates on files.
14n/aPython, however, can easily fill in the gap.
15n/a'''
16n/a
17n/a__author__ = "Steve Dower <steve.dower@microsoft.com>"
18n/a
19n/aimport csv
20n/aimport re
21n/aimport sys
22n/a
23n/afrom collections import defaultdict
24n/afrom itertools import chain, zip_longest
25n/afrom pathlib import PureWindowsPath
26n/afrom uuid import uuid1
27n/a
28n/aID_CHAR_SUBS = {
29n/a '-': '_',
30n/a '+': '_P',
31n/a}
32n/a
33n/adef make_id(path):
34n/a return re.sub(
35n/a r'[^A-Za-z0-9_.]',
36n/a lambda m: ID_CHAR_SUBS.get(m.group(0), '_'),
37n/a str(path).rstrip('/\\'),
38n/a flags=re.I
39n/a )
40n/a
41n/aDIRECTORIES = set()
42n/a
43n/adef main(file_source, install_target):
44n/a with open(file_source, 'r', newline='') as f:
45n/a files = list(csv.reader(f))
46n/a
47n/a assert len(files) == len(set(make_id(f[1]) for f in files)), "Duplicate file IDs exist"
48n/a
49n/a directories = defaultdict(set)
50n/a cache_directories = defaultdict(set)
51n/a groups = defaultdict(list)
52n/a for source, target, group, disk_id, condition in files:
53n/a target = PureWindowsPath(target)
54n/a groups[group].append((source, target, disk_id, condition))
55n/a
56n/a if target.suffix.lower() in {".py", ".pyw"}:
57n/a cache_directories[group].add(target.parent)
58n/a
59n/a for dirname in target.parents:
60n/a parent = make_id(dirname.parent)
61n/a if parent and parent != '.':
62n/a directories[parent].add(dirname.name)
63n/a
64n/a lines = [
65n/a '<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">',
66n/a ' <Fragment>',
67n/a ]
68n/a for dir_parent in sorted(directories):
69n/a lines.append(' <DirectoryRef Id="{}">'.format(dir_parent))
70n/a for dir_name in sorted(directories[dir_parent]):
71n/a lines.append(' <Directory Id="{}_{}" Name="{}" />'.format(dir_parent, make_id(dir_name), dir_name))
72n/a lines.append(' </DirectoryRef>')
73n/a for dir_parent in (make_id(d) for group in cache_directories.values() for d in group):
74n/a lines.append(' <DirectoryRef Id="{}">'.format(dir_parent))
75n/a lines.append(' <Directory Id="{}___pycache__" Name="__pycache__" />'.format(dir_parent))
76n/a lines.append(' </DirectoryRef>')
77n/a lines.append(' </Fragment>')
78n/a
79n/a for group in sorted(groups):
80n/a lines.extend([
81n/a ' <Fragment>',
82n/a ' <ComponentGroup Id="{}">'.format(group),
83n/a ])
84n/a for source, target, disk_id, condition in groups[group]:
85n/a lines.append(' <Component Id="{}" Directory="{}" Guid="*">'.format(make_id(target), make_id(target.parent)))
86n/a if condition:
87n/a lines.append(' <Condition>{}</Condition>'.format(condition))
88n/a
89n/a if disk_id:
90n/a lines.append(' <File Id="{}" Name="{}" Source="{}" DiskId="{}" />'.format(make_id(target), target.name, source, disk_id))
91n/a else:
92n/a lines.append(' <File Id="{}" Name="{}" Source="{}" />'.format(make_id(target), target.name, source))
93n/a lines.append(' </Component>')
94n/a
95n/a create_folders = {make_id(p) + "___pycache__" for p in cache_directories[group]}
96n/a remove_folders = {make_id(p2) for p1 in cache_directories[group] for p2 in chain((p1,), p1.parents)}
97n/a create_folders.discard(".")
98n/a remove_folders.discard(".")
99n/a if create_folders or remove_folders:
100n/a lines.append(' <Component Id="{}__pycache__folders" Directory="TARGETDIR" Guid="{}">'.format(group, uuid1()))
101n/a lines.extend(' <CreateFolder Directory="{}" />'.format(p) for p in create_folders)
102n/a lines.extend(' <RemoveFile Id="Remove_{0}_files" Name="*" On="uninstall" Directory="{0}" />'.format(p) for p in create_folders)
103n/a lines.extend(' <RemoveFolder Id="Remove_{0}_folder" On="uninstall" Directory="{0}" />'.format(p) for p in create_folders | remove_folders)
104n/a lines.append(' </Component>')
105n/a
106n/a lines.extend([
107n/a ' </ComponentGroup>',
108n/a ' </Fragment>',
109n/a ])
110n/a lines.append('</Wix>')
111n/a
112n/a # Check if the file matches. If so, we don't want to touch it so
113n/a # that we can skip rebuilding.
114n/a try:
115n/a with open(install_target, 'r') as f:
116n/a if all(x.rstrip('\r\n') == y for x, y in zip_longest(f, lines)):
117n/a print('File is up to date')
118n/a return
119n/a except IOError:
120n/a pass
121n/a
122n/a with open(install_target, 'w') as f:
123n/a f.writelines(line + '\n' for line in lines)
124n/a print('Wrote {} lines to {}'.format(len(lines), install_target))
125n/a
126n/aif __name__ == '__main__':
127n/a main(sys.argv[1], sys.argv[2])