diff --git a/win_toolchain/package_from_installed.py b/win_toolchain/package_from_installed.py index 276220128..19d3e706c 100644 --- a/win_toolchain/package_from_installed.py +++ b/win_toolchain/package_from_installed.py @@ -21,7 +21,9 @@ useful as the resulting zip can't be redistributed, and most will presumably have a Pro license anyway). """ +import optparse import os +import platform import shutil import sys import tempfile @@ -31,6 +33,7 @@ import get_toolchain_if_necessary VS_VERSION = None +WIN_VERSION = None def BuildFileList(): @@ -112,24 +115,24 @@ def BuildFileList(): combined = os.path.normpath(os.path.join(root, f)) # Some of the files in this directory are exceedingly long (and exceed #_MAX_PATH for any moderately long root), so exclude them. We don't need - # them anyway. + # them anyway. Exclude the Windows Performance Toolkit just to save space. tail = combined[len(sdk_path) + 1:] - if tail.startswith('References\\'): + if (tail.startswith('References\\') or + tail.startswith('Windows Performance Toolkit\\')): continue + if VS_VERSION == '2015': + # There may be many Include\Lib\Source directories for many different + # versions of Windows and packaging them all wastes ~450 MB + # (uncompressed) per version and wastes time. Only copy the specified + # version. + if (tail.startswith('Include\\') or tail.startswith('Lib\\') or + tail.startswith('Source\\')): + if tail.count(WIN_VERSION) == 0: + continue to = os.path.join('win_sdk', tail) result.append((combined, to)) if VS_VERSION == '2015': - for ucrt_path in ( - (r'C:\Program Files (x86)\Windows Kits\10\Include', 'Include'), - (r'C:\Program Files (x86)\Windows Kits\10\Lib', 'Lib'), - (r'C:\Program Files (x86)\Windows Kits\10\Source', 'Source')): - src, target = ucrt_path - for root, _, files in os.walk(src): - for f in files: - combined = os.path.normpath(os.path.join(root, f)) - to = os.path.join('ucrt', target, combined[len(src) + 1:]) - result.append((combined, to)) system_crt_files = [ 'api-ms-win-core-file-l1-2-0.dll', @@ -158,15 +161,21 @@ def BuildFileList(): 'ucrtbase.dll', 'ucrtbased.dll', ] + bitness = platform.architecture()[0] + # When running 64-bit python the x64 DLLs will be in System32 + x64_path = 'System32' if bitness == '64bit' else 'Sysnative' + x64_path = os.path.join(r'C:\Windows', x64_path) for system_crt_file in system_crt_files: result.append((os.path.join(r'C:\Windows\SysWOW64', system_crt_file), os.path.join('sys32', system_crt_file))) - result.append((os.path.join(r'C:\Windows\Sysnative', system_crt_file), + result.append((os.path.join(x64_path, system_crt_file), os.path.join('sys64', system_crt_file))) - # Generically drop all arm stuff that we don't need. + # Generically drop all arm stuff that we don't need, and + # drop .msi files because we don't need installers. return [(f, t) for f, t in result if 'arm\\' not in f.lower() and - 'arm64\\' not in f.lower()] + 'arm64\\' not in f.lower() and + not f.lower().endswith('.msi')] def GenerateSetEnvCmd(target_dir): @@ -181,12 +190,13 @@ def GenerateSetEnvCmd(target_dir): ':: Generated by win_toolchain\\package_from_installed.py.\n' # Common to x86 and x64 'set PATH=%~dp0..\\..\\Common7\\IDE;%PATH%\n' - 'set INCLUDE=%~dp0..\\..\\win_sdk\\Include\\10.0.10240.0\\um;' - '%~dp0..\\..\\win_sdk\\Include\\10.0.10240.0\\shared;' - '%~dp0..\\..\\win_sdk\\Include\\10.0.10240.0\\winrt;' + 'set INCLUDE=%~dp0..\\..\\win_sdk\\Include\\WINVERSION\\um;' + '%~dp0..\\..\\win_sdk\\Include\\WINVERSION\\shared;' + '%~dp0..\\..\\win_sdk\\Include\\WINVERSION\\winrt;' + '%~dp0..\\..\\win_sdk\\Include\\WINVERSION\\ucrt;' # VS 2015 '%~dp0..\\..\\VC\\include;' '%~dp0..\\..\\VC\\atlmfc\\include\n' - 'if "%1"=="/x64" goto x64\n') + 'if "%1"=="/x64" goto x64\n'.replace('WINVERSION', WIN_VERSION)) # x86. Always use amd64_x86 cross, not x86 on x86. f.write('set PATH=%~dp0..\\..\\win_sdk\\bin\\x86;' @@ -194,9 +204,10 @@ def GenerateSetEnvCmd(target_dir): '%~dp0..\\..\\VC\\bin\\amd64;' # Needed for mspdb1x0.dll. '%PATH%\n') f.write('set LIB=%~dp0..\\..\\VC\\lib;' - '%~dp0..\\..\\win_sdk\\Lib\\10.0.10240.0\\um\\x86;' + '%~dp0..\\..\\win_sdk\\Lib\\WINVERSION\\um\\x86;' + '%~dp0..\\..\\win_sdk\\Lib\\WINVERSION\\ucrt\\x86;' # VS 2015 '%~dp0..\\..\\VC\\atlmfc\\lib\n' - 'goto :EOF\n') + 'goto :EOF\n'.replace('WINVERSION', WIN_VERSION)) # x64. f.write(':x64\n' @@ -204,8 +215,10 @@ def GenerateSetEnvCmd(target_dir): '%~dp0..\\..\\VC\\bin\\amd64;' '%PATH%\n') f.write('set LIB=%~dp0..\\..\\VC\\lib\\amd64;' - '%~dp0..\\..\\win_sdk\\Lib\\10.0.10240.0\\um\\x64;' - '%~dp0..\\..\\VC\\atlmfc\\lib\\amd64\n') + '%~dp0..\\..\\win_sdk\\Lib\\WINVERSION\\um\\x64;' + '%~dp0..\\..\\win_sdk\\Lib\\WINVERSION\\ucrt\\x64;' # VS 2015 + '%~dp0..\\..\\VC\\atlmfc\\lib\\amd64\n' + .replace('WINVERSION', WIN_VERSION)) def AddEnvSetup(files): @@ -246,14 +259,27 @@ def RenameToSha1(output): def main(): - if len(sys.argv) != 2 or sys.argv[1] not in ('2013', '2015'): - print 'Usage: package_from_installed.py 2013|2015' + usage = 'usage: %prog [options] 2013|2015' + parser = optparse.OptionParser(usage) + parser.add_option('-w', '--winver', action='store', type='string', + dest='winver', default='10.0.10586.0', + help='Windows SDK version, such as 10.0.10586.0') + parser.add_option('-d', '--dryrun', action='store_true', dest='dryrun', + default=False, + help='scan for file existence and prints statistics') + (options, args) = parser.parse_args() + + if len(args) != 1 or args[0] not in ('2013', '2015'): + print 'Must specify 2013 or 2015' + parser.print_help(); return 1 global VS_VERSION - VS_VERSION = sys.argv[1] + VS_VERSION = args[0] + global WIN_VERSION + WIN_VERSION = options.winver - print 'Building file list...' + print 'Building file list for VS %s Windows %s...' % (VS_VERSION, WIN_VERSION) files = BuildFileList() AddEnvSetup(files) @@ -267,12 +293,33 @@ def main(): if os.path.exists(output): os.unlink(output) count = 0 + version_match_count = 0 + total_size = 0 + missing_files = False with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED, True) as zf: for disk_name, archive_name in files: sys.stdout.write('\r%d/%d ...%s' % (count, len(files), disk_name[-40:])) sys.stdout.flush() count += 1 - zf.write(disk_name, archive_name) + if disk_name.count(WIN_VERSION) > 0: + version_match_count += 1 + if os.path.exists(disk_name): + if options.dryrun: + total_size += os.path.getsize(disk_name) + else: + zf.write(disk_name, archive_name) + else: + missing_files = True + sys.stdout.write('\r%s does not exist.\n\n' % disk_name) + sys.stdout.flush() + if options.dryrun: + sys.stdout.write('\r%1.3f GB of data in %d files, %d files for %s.%s\n' % + (total_size / 1e9, count, version_match_count, WIN_VERSION, ' '*50)) + return 0 + if missing_files: + raise 'One or more files were missing - aborting' + if version_match_count == 0: + raise 'No files found that match the specified winversion' sys.stdout.write('\rWrote to %s.%s\n' % (output, ' '*50)) sys.stdout.flush()