mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	
		
			
	
	
		
			128 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			128 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | '''
 | ||
|  | Processes a CSV file containing a list of files into a WXS file with | ||
|  | components for each listed file. | ||
|  | 
 | ||
|  | The CSV columns are: | ||
|  |     source of file, target for file, group name | ||
|  | 
 | ||
|  | Usage:: | ||
|  |     py txt_to_wxs.py [path to file list .csv] [path to destination .wxs] | ||
|  | 
 | ||
|  | This is necessary to handle structures where some directories only | ||
|  | contain other directories. MSBuild is not able to generate the | ||
|  | Directory entries in the WXS file correctly, as it operates on files. | ||
|  | Python, however, can easily fill in the gap. | ||
|  | '''
 | ||
|  | 
 | ||
|  | __author__ = "Steve Dower <steve.dower@microsoft.com>" | ||
|  | 
 | ||
|  | import csv | ||
|  | import re | ||
|  | import sys | ||
|  | 
 | ||
|  | from collections import defaultdict | ||
|  | from itertools import chain, zip_longest | ||
|  | from pathlib import PureWindowsPath | ||
|  | from uuid import uuid1 | ||
|  | 
 | ||
|  | ID_CHAR_SUBS = { | ||
|  |     '-': '_', | ||
|  |     '+': '_P', | ||
|  | } | ||
|  | 
 | ||
|  | def make_id(path): | ||
|  |     return re.sub( | ||
|  |         r'[^A-Za-z0-9_.]', | ||
|  |         lambda m: ID_CHAR_SUBS.get(m.group(0), '_'), | ||
|  |         str(path).rstrip('/\\'), | ||
|  |         flags=re.I | ||
|  |     ) | ||
|  | 
 | ||
|  | DIRECTORIES = set() | ||
|  | 
 | ||
|  | def main(file_source, install_target): | ||
|  |     with open(file_source, 'r', newline='') as f: | ||
|  |         files = list(csv.reader(f)) | ||
|  | 
 | ||
|  |     assert len(files) == len(set(make_id(f[1]) for f in files)), "Duplicate file IDs exist" | ||
|  | 
 | ||
|  |     directories = defaultdict(set) | ||
|  |     cache_directories = defaultdict(set) | ||
|  |     groups = defaultdict(list) | ||
|  |     for source, target, group, disk_id, condition in files: | ||
|  |         target = PureWindowsPath(target) | ||
|  |         groups[group].append((source, target, disk_id, condition)) | ||
|  | 
 | ||
|  |         if target.suffix.lower() in {".py", ".pyw"}: | ||
|  |             cache_directories[group].add(target.parent) | ||
|  | 
 | ||
|  |         for dirname in target.parents: | ||
|  |             parent = make_id(dirname.parent) | ||
|  |             if parent and parent != '.': | ||
|  |                 directories[parent].add(dirname.name) | ||
|  | 
 | ||
|  |     lines = [ | ||
|  |         '<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">', | ||
|  |         '    <Fragment>', | ||
|  |     ] | ||
|  |     for dir_parent in sorted(directories): | ||
|  |         lines.append('        <DirectoryRef Id="{}">'.format(dir_parent)) | ||
|  |         for dir_name in sorted(directories[dir_parent]): | ||
|  |             lines.append('            <Directory Id="{}_{}" Name="{}" />'.format(dir_parent, make_id(dir_name), dir_name)) | ||
|  |         lines.append('        </DirectoryRef>') | ||
|  |     for dir_parent in (make_id(d) for group in cache_directories.values() for d in group): | ||
|  |         lines.append('        <DirectoryRef Id="{}">'.format(dir_parent)) | ||
|  |         lines.append('            <Directory Id="{}___pycache__" Name="__pycache__" />'.format(dir_parent)) | ||
|  |         lines.append('        </DirectoryRef>') | ||
|  |     lines.append('    </Fragment>') | ||
|  | 
 | ||
|  |     for group in sorted(groups): | ||
|  |         lines.extend([ | ||
|  |             '    <Fragment>', | ||
|  |             '        <ComponentGroup Id="{}">'.format(group), | ||
|  |         ]) | ||
|  |         for source, target, disk_id, condition in groups[group]: | ||
|  |             lines.append('            <Component Id="{}" Directory="{}" Guid="*">'.format(make_id(target), make_id(target.parent))) | ||
|  |             if condition: | ||
|  |                 lines.append('                <Condition>{}</Condition>'.format(condition)) | ||
|  | 
 | ||
|  |             if disk_id: | ||
|  |                 lines.append('                <File Id="{}" Name="{}" Source="{}" DiskId="{}" />'.format(make_id(target), target.name, source, disk_id)) | ||
|  |             else: | ||
|  |                 lines.append('                <File Id="{}" Name="{}" Source="{}" />'.format(make_id(target), target.name, source)) | ||
|  |             lines.append('            </Component>') | ||
|  | 
 | ||
|  |         create_folders = {make_id(p) + "___pycache__" for p in cache_directories[group]} | ||
|  |         remove_folders = {make_id(p2) for p1 in cache_directories[group] for p2 in chain((p1,), p1.parents)} | ||
|  |         create_folders.discard(".") | ||
|  |         remove_folders.discard(".") | ||
|  |         if create_folders or remove_folders: | ||
|  |             lines.append('            <Component Id="{}__pycache__folders" Directory="TARGETDIR" Guid="{}">'.format(group, uuid1())) | ||
|  |             lines.extend('                <CreateFolder Directory="{}" />'.format(p) for p in create_folders) | ||
|  |             lines.extend('                <RemoveFile Id="Remove_{0}_files" Name="*" On="uninstall" Directory="{0}" />'.format(p) for p in create_folders) | ||
|  |             lines.extend('                <RemoveFolder Id="Remove_{0}_folder" On="uninstall" Directory="{0}" />'.format(p) for p in create_folders | remove_folders) | ||
|  |             lines.append('            </Component>') | ||
|  | 
 | ||
|  |         lines.extend([ | ||
|  |             '        </ComponentGroup>', | ||
|  |             '    </Fragment>', | ||
|  |         ]) | ||
|  |     lines.append('</Wix>') | ||
|  | 
 | ||
|  |     # Check if the file matches. If so, we don't want to touch it so | ||
|  |     # that we can skip rebuilding. | ||
|  |     try: | ||
|  |         with open(install_target, 'r') as f: | ||
|  |             if all(x.rstrip('\r\n') == y for x, y in zip_longest(f, lines)): | ||
|  |                 print('File is up to date') | ||
|  |                 return | ||
|  |     except IOError: | ||
|  |         pass | ||
|  | 
 | ||
|  |     with open(install_target, 'w') as f: | ||
|  |         f.writelines(line + '\n' for line in lines) | ||
|  |     print('Wrote {} lines to {}'.format(len(lines), install_target)) | ||
|  | 
 | ||
|  | if __name__ == '__main__': | ||
|  |     main(sys.argv[1], sys.argv[2]) |