mirror of
				https://github.com/python/cpython.git
				synced 2025-10-29 17:38:56 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			127 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			127 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])
 | 
