package_from_installed.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. #!/usr/bin/env python3
  2. # Copyright 2014 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """
  6. From a system-installed copy of the toolchain, packages all the required bits
  7. into a .zip file.
  8. It assumes default install locations for tools, on the C: drive.
  9. 1. Start from a fresh Windows VM image.
  10. 2. Download the VS installer. Run the installer with these parameters:
  11. --add Microsoft.VisualStudio.Workload.NativeDesktop
  12. --add Microsoft.VisualStudio.Component.VC.ATLMFC
  13. --add Microsoft.VisualStudio.Component.VC.Tools.ARM64
  14. --add Microsoft.VisualStudio.Component.VC.MFC.ARM64
  15. --includeRecommended --passive
  16. These are equivalent to selecting the Desktop development with C++ workload,
  17. within that the Visual C++ MFC for x86 and x64 component, and then Individual
  18. Components-> Compilers, build tools, and runtimes-> Visual C++ compilers and
  19. libraries for ARM64, and Individual Components-> SDKs, libraries, and
  20. frameworks-> Visual C++ MFC for ARM64 (which also brings in ATL for ARM64).
  21. 3. Use Add or Remove Programs to find the Windows SDK installed with VS
  22. and modify it to include the debuggers.
  23. 4. Run this script, which will build a <sha1>.zip, something like this:
  24. python package_from_installed.py 2022 -w 10.0.22621.0|<SDK version>
  25. Express is not yet supported by this script, but patches welcome (it's not too
  26. useful as the resulting zip can't be redistributed, and most will presumably
  27. have a Pro license anyway).
  28. """
  29. import collections
  30. import glob
  31. import json
  32. import optparse
  33. import os
  34. import platform
  35. import shutil
  36. import subprocess
  37. import sys
  38. import tempfile
  39. import zipfile
  40. import get_toolchain_if_necessary
  41. _vs_version = None
  42. _win_version = None
  43. _vc_tools = None
  44. SUPPORTED_VS_VERSIONS = ['2022']
  45. _allow_multiple_vs_installs = False
  46. def GetVSPath():
  47. # Use vswhere to find the VS installation. This will find prerelease
  48. # versions because -prerelease is specified. This assumes that only one
  49. # version is installed.
  50. command = (r'C:\Program Files (x86)\Microsoft Visual Studio\Installer'
  51. r'\vswhere.exe -prerelease')
  52. vs_version_marker = 'catalog_productLineVersion: '
  53. vs_path_marker = 'installationPath: '
  54. output = subprocess.check_output(command, universal_newlines=True)
  55. vs_path = None
  56. vs_installs_count = 0
  57. matching_vs_path = ""
  58. for line in output.splitlines():
  59. if line.startswith(vs_path_marker):
  60. # The path information comes first
  61. vs_path = line[len(vs_path_marker):]
  62. vs_installs_count += 1
  63. if line.startswith(vs_version_marker):
  64. # The version for that path comes later
  65. if line[len(vs_version_marker):] == _vs_version:
  66. matching_vs_path = vs_path
  67. if vs_installs_count == 0:
  68. raise Exception('VS %s path not found in vswhere output' %
  69. (_vs_version))
  70. if vs_installs_count > 1:
  71. if not _allow_multiple_vs_installs:
  72. raise Exception(
  73. 'Multiple VS installs detected. This is unsupported. '
  74. 'It is recommended that packaging be done on a clean VM '
  75. 'with just one version installed. To proceed anyway add '
  76. 'the --allow_multiple_vs_installs flag to this script')
  77. else:
  78. print('Multiple VS installs were detected. This is unsupported. '
  79. 'Proceeding anyway')
  80. return matching_vs_path
  81. def ExpandWildcards(root, sub_dir):
  82. # normpath is needed to change '/' to '\\' characters.
  83. path = os.path.normpath(os.path.join(root, sub_dir))
  84. matches = glob.glob(path)
  85. if len(matches) != 1:
  86. raise Exception('%s had %d matches - should be one' %
  87. (path, len(matches)))
  88. return matches[0]
  89. def BuildRepackageFileList(src_dir):
  90. # Strip off a trailing separator if present
  91. if src_dir.endswith(os.path.sep):
  92. src_dir = src_dir[:-len(os.path.sep)]
  93. # Ensure .\Windows Kits\10\Debuggers exists and fail to repackage if it
  94. # doesn't.
  95. debuggers_path = os.path.join(src_dir, 'Windows Kits', '10', 'Debuggers')
  96. if not os.path.exists(debuggers_path):
  97. raise Exception('Repacking failed. Missing %s.' % (debuggers_path))
  98. result = []
  99. for root, _, files in os.walk(src_dir):
  100. for f in files:
  101. final_from = os.path.normpath(os.path.join(root, f))
  102. dest = final_from[len(src_dir) + 1:]
  103. result.append((final_from, dest))
  104. return result
  105. def BuildFileList(override_dir, include_arm, vs_path):
  106. result = []
  107. # Subset of VS corresponding roughly to VC.
  108. paths = [
  109. 'DIA SDK/bin',
  110. 'DIA SDK/idl',
  111. 'DIA SDK/include',
  112. 'DIA SDK/lib',
  113. _vc_tools + '/atlmfc',
  114. _vc_tools + '/crt',
  115. 'VC/redist',
  116. ]
  117. if override_dir:
  118. paths += [
  119. (os.path.join(override_dir, 'bin'), _vc_tools + '/bin'),
  120. (os.path.join(override_dir, 'include'), _vc_tools + '/include'),
  121. (os.path.join(override_dir, 'lib'), _vc_tools + '/lib'),
  122. ]
  123. else:
  124. paths += [
  125. _vc_tools + '/bin',
  126. _vc_tools + '/include',
  127. _vc_tools + '/lib',
  128. ]
  129. paths += [
  130. ('VC/redist/MSVC/14.*.*/x86/Microsoft.VC*.CRT', 'sys32'),
  131. ('VC/redist/MSVC/14.*.*/x86/Microsoft.VC*.CRT',
  132. 'Windows Kits/10//bin/x86'),
  133. ('VC/redist/MSVC/14.*.*/debug_nonredist/x86/Microsoft.VC*.DebugCRT',
  134. 'sys32'),
  135. ('VC/redist/MSVC/14.*.*/x64/Microsoft.VC*.CRT', 'sys64'),
  136. ('VC/redist/MSVC/14.*.*/x64/Microsoft.VC*.CRT', 'VC/bin/amd64_x86'),
  137. ('VC/redist/MSVC/14.*.*/x64/Microsoft.VC*.CRT', 'VC/bin/amd64'),
  138. ('VC/redist/MSVC/14.*.*/x64/Microsoft.VC*.CRT',
  139. 'Windows Kits/10/bin/x64'),
  140. ('VC/redist/MSVC/14.*.*/debug_nonredist/x64/Microsoft.VC*.DebugCRT',
  141. 'sys64'),
  142. ]
  143. if include_arm:
  144. paths += [
  145. ('VC/redist/MSVC/14.*.*/arm64/Microsoft.VC*.CRT', 'sysarm64'),
  146. ('VC/redist/MSVC/14.*.*/arm64/Microsoft.VC*.CRT',
  147. 'VC/bin/amd64_arm64'),
  148. ('VC/redist/MSVC/14.*.*/arm64/Microsoft.VC*.CRT', 'VC/bin/arm64'),
  149. ('VC/redist/MSVC/14.*.*/arm64/Microsoft.VC*.CRT',
  150. 'Windows Kits/10/bin/arm64'),
  151. ('VC/redist/MSVC/14.*.*/debug_nonredist/arm64/Microsoft.VC*.DebugCRT',
  152. 'sysarm64'),
  153. ]
  154. for path in paths:
  155. src = path[0] if isinstance(path, tuple) else path
  156. # Note that vs_path is ignored if src is an absolute path.
  157. combined = ExpandWildcards(vs_path, src)
  158. if not os.path.exists(combined):
  159. raise Exception('%s missing.' % combined)
  160. if not os.path.isdir(combined):
  161. raise Exception('%s not a directory.' % combined)
  162. for root, _, files in os.walk(combined):
  163. for f in files:
  164. # vctip.exe doesn't shutdown, leaving locks on directories. It's
  165. # optional so let's avoid this problem by not packaging it.
  166. # https://crbug.com/735226
  167. if f.lower() == 'vctip.exe':
  168. continue
  169. final_from = os.path.normpath(os.path.join(root, f))
  170. if isinstance(path, tuple):
  171. assert final_from.startswith(combined)
  172. dest = final_from[len(combined) + 1:]
  173. result.append((final_from,
  174. os.path.normpath(os.path.join(path[1],
  175. dest))))
  176. else:
  177. assert final_from.startswith(vs_path)
  178. dest = final_from[len(vs_path) + 1:]
  179. result.append((final_from, dest))
  180. command = (
  181. r'reg query "HKLM\SOFTWARE\Microsoft\Windows Kits\Installed Roots"'
  182. r' /v KitsRoot10')
  183. marker = " KitsRoot10 REG_SZ "
  184. sdk_path = None
  185. output = subprocess.check_output(command, universal_newlines=True)
  186. for line in output.splitlines():
  187. if line.startswith(marker):
  188. sdk_path = line[len(marker):]
  189. # Strip off a trailing slash if present
  190. if sdk_path.endswith(os.path.sep):
  191. sdk_path = sdk_path[:-len(os.path.sep)]
  192. debuggers_path = os.path.join(sdk_path, 'Debuggers')
  193. if not os.path.exists(debuggers_path):
  194. raise Exception('Packaging failed. Missing %s.' % (debuggers_path))
  195. for root, _, files in os.walk(sdk_path):
  196. for f in files:
  197. combined = os.path.normpath(os.path.join(root, f))
  198. # Some of the files in this directory are exceedingly long (and
  199. # exceed _MAX_PATH for any moderately long root), so exclude them.
  200. # We don't need them anyway. Exclude/filter/skip others just to save
  201. # space.
  202. tail = combined[len(sdk_path) + 1:]
  203. skip_dir = False
  204. for dir in [
  205. 'References\\', 'Windows Performance Toolkit\\',
  206. 'Testing\\', 'App Certification Kit\\', 'Extension SDKs\\',
  207. 'Assessment and Deployment Kit\\'
  208. ]:
  209. if tail.startswith(dir):
  210. skip_dir = True
  211. if skip_dir:
  212. continue
  213. # There may be many Include\Lib\Source\bin directories for many
  214. # different versions of Windows and packaging them all wastes ~450
  215. # MB (uncompressed) per version and wastes time. Only copy the
  216. # specified version. Note that the SDK version number started being
  217. # part of the bin path with 10.0.15063.0.
  218. if (tail.startswith('Include\\') or tail.startswith('Lib\\')
  219. or tail.startswith('Source\\') or tail.startswith('bin\\')):
  220. if tail.count(_win_version) == 0:
  221. continue
  222. to = os.path.join('Windows Kits', '10', tail)
  223. result.append((combined, to))
  224. # Copy the x86 ucrt DLLs to all directories with x86 binaries that are
  225. # added to the path by SetEnv.cmd, and to sys32. Starting with the 17763
  226. # SDK the ucrt files are in _win_version\ucrt instead of just ucrt.
  227. ucrt_dir = os.path.join(sdk_path, 'redist', _win_version, r'ucrt\dlls\x86')
  228. if not os.path.exists(ucrt_dir):
  229. ucrt_dir = os.path.join(sdk_path, r'redist\ucrt\dlls\x86')
  230. ucrt_paths = glob.glob(ucrt_dir + r'\*')
  231. assert (len(ucrt_paths) > 0)
  232. for ucrt_path in ucrt_paths:
  233. ucrt_file = os.path.split(ucrt_path)[1]
  234. for dest_dir in [r'Windows Kits\10\bin\x86', 'sys32']:
  235. result.append((ucrt_path, os.path.join(dest_dir, ucrt_file)))
  236. # Copy the x64 ucrt DLLs to all directories with x64 binaries that are
  237. # added to the path by SetEnv.cmd, and to sys64.
  238. ucrt_dir = os.path.join(sdk_path, 'redist', _win_version, r'ucrt\dlls\x64')
  239. if not os.path.exists(ucrt_dir):
  240. ucrt_dir = os.path.join(sdk_path, r'redist\ucrt\dlls\x64')
  241. ucrt_paths = glob.glob(ucrt_dir + r'\*')
  242. assert (len(ucrt_paths) > 0)
  243. for ucrt_path in ucrt_paths:
  244. ucrt_file = os.path.split(ucrt_path)[1]
  245. for dest_dir in [
  246. r'VC\bin\amd64_x86', r'VC\bin\amd64',
  247. r'Windows Kits\10\bin\x64', 'sys64'
  248. ]:
  249. result.append((ucrt_path, os.path.join(dest_dir, ucrt_file)))
  250. system_crt_files = [
  251. # Needed to let debug binaries run.
  252. 'ucrtbased.dll',
  253. ]
  254. cpu_pairs = [
  255. ('x86', 'sys32'),
  256. ('x64', 'sys64'),
  257. ]
  258. if include_arm:
  259. cpu_pairs += [
  260. ('arm64', 'sysarm64'),
  261. ]
  262. for system_crt_file in system_crt_files:
  263. for cpu_pair in cpu_pairs:
  264. target_cpu, dest_dir = cpu_pair
  265. src_path = os.path.join(sdk_path, 'bin', _win_version, target_cpu,
  266. 'ucrt')
  267. result.append((os.path.join(src_path, system_crt_file),
  268. os.path.join(dest_dir, system_crt_file)))
  269. # Generically drop all arm stuff that we don't need, and
  270. # drop .msi files because we don't need installers and drop
  271. # samples since those are not used by any tools.
  272. def is_skippable(f):
  273. return ('arm\\' in f.lower()
  274. or (not include_arm and 'arm64\\' in f.lower())
  275. or 'samples\\' in f.lower() or f.lower().endswith(
  276. ('.msi', '.msm')))
  277. return [(f, t) for f, t in result if not is_skippable(f)]
  278. def GenerateSetEnvCmd(target_dir):
  279. """Generate a batch file that gyp expects to exist to set up the compiler
  280. environment.
  281. This is normally generated by a full install of the SDK, but we
  282. do it here manually since we do not do a full install."""
  283. vc_tools_parts = _vc_tools.split('/')
  284. # All these paths are relative to the root of the toolchain package.
  285. include_dirs = [
  286. ['Windows Kits', '10', 'Include', _win_version, 'um'],
  287. ['Windows Kits', '10', 'Include', _win_version, 'shared'],
  288. ['Windows Kits', '10', 'Include', _win_version, 'winrt'],
  289. ]
  290. include_dirs.append(['Windows Kits', '10', 'Include', _win_version, 'ucrt'])
  291. include_dirs.extend([
  292. vc_tools_parts + ['include'],
  293. vc_tools_parts + ['atlmfc', 'include'],
  294. ])
  295. libpath_dirs = [
  296. vc_tools_parts + ['lib', 'x86', 'store', 'references'],
  297. ['Windows Kits', '10', 'UnionMetadata', _win_version],
  298. ]
  299. # Common to x86, x64, and arm64
  300. env = collections.OrderedDict([
  301. # Yuck: These have a trailing \ character. No good way to represent this
  302. # in an OS-independent way.
  303. ('VSINSTALLDIR', [['.\\']]),
  304. ('VCINSTALLDIR', [['VC\\']]),
  305. ('INCLUDE', include_dirs),
  306. ('LIBPATH', libpath_dirs),
  307. ])
  308. # x86. Always use amd64_x86 cross, not x86 on x86.
  309. env['VCToolsInstallDir'] = [vc_tools_parts[:]]
  310. # Yuck: This one ends in a path separator as well.
  311. env['VCToolsInstallDir'][0][-1] += os.path.sep
  312. env_x86 = collections.OrderedDict([
  313. (
  314. 'PATH',
  315. [
  316. ['Windows Kits', '10', 'bin', _win_version, 'x64'],
  317. vc_tools_parts + ['bin', 'HostX64', 'x86'],
  318. vc_tools_parts +
  319. ['bin', 'HostX64', 'x64'], # Needed for mspdb1x0.dll.
  320. ]),
  321. ('LIB', [
  322. vc_tools_parts + ['lib', 'x86'],
  323. ['Windows Kits', '10', 'Lib', _win_version, 'um', 'x86'],
  324. ['Windows Kits', '10', 'Lib', _win_version, 'ucrt', 'x86'],
  325. vc_tools_parts + ['atlmfc', 'lib', 'x86'],
  326. ]),
  327. ])
  328. # x64.
  329. env_x64 = collections.OrderedDict([
  330. ('PATH', [
  331. ['Windows Kits', '10', 'bin', _win_version, 'x64'],
  332. vc_tools_parts + ['bin', 'HostX64', 'x64'],
  333. ]),
  334. ('LIB', [
  335. vc_tools_parts + ['lib', 'x64'],
  336. ['Windows Kits', '10', 'Lib', _win_version, 'um', 'x64'],
  337. ['Windows Kits', '10', 'Lib', _win_version, 'ucrt', 'x64'],
  338. vc_tools_parts + ['atlmfc', 'lib', 'x64'],
  339. ]),
  340. ])
  341. # arm64.
  342. env_arm64 = collections.OrderedDict([
  343. ('PATH', [
  344. ['Windows Kits', '10', 'bin', _win_version, 'x64'],
  345. vc_tools_parts + ['bin', 'HostX64', 'arm64'],
  346. vc_tools_parts + ['bin', 'HostX64', 'x64'],
  347. ]),
  348. ('LIB', [
  349. vc_tools_parts + ['lib', 'arm64'],
  350. ['Windows Kits', '10', 'Lib', _win_version, 'um', 'arm64'],
  351. ['Windows Kits', '10', 'Lib', _win_version, 'ucrt', 'arm64'],
  352. vc_tools_parts + ['atlmfc', 'lib', 'arm64'],
  353. ]),
  354. ])
  355. def BatDirs(dirs):
  356. return ';'.join(['%cd%\\' + os.path.join(*d) for d in dirs])
  357. set_env_prefix = os.path.join(target_dir, r'Windows Kits\10\bin\SetEnv')
  358. with open(set_env_prefix + '.cmd', 'w') as f:
  359. # The prologue changes the current directory to the root of the
  360. # toolchain package, so that path entries can be set up without needing
  361. # ..\..\..\ components.
  362. f.write('@echo off\n'
  363. ':: Generated by win_toolchain\\package_from_installed.py.\n'
  364. 'pushd %~dp0..\..\..\n')
  365. for var, dirs in env.items():
  366. f.write('set %s=%s\n' % (var, BatDirs(dirs)))
  367. f.write('if "%1"=="/x64" goto x64\n')
  368. f.write('if "%1"=="/arm64" goto arm64\n')
  369. for var, dirs in env_x86.items():
  370. f.write('set %s=%s%s\n' %
  371. (var, BatDirs(dirs), ';%PATH%' if var == 'PATH' else ''))
  372. f.write('goto :END\n')
  373. f.write(':x64\n')
  374. for var, dirs in env_x64.items():
  375. f.write('set %s=%s%s\n' %
  376. (var, BatDirs(dirs), ';%PATH%' if var == 'PATH' else ''))
  377. f.write('goto :END\n')
  378. f.write(':arm64\n')
  379. for var, dirs in env_arm64.items():
  380. f.write('set %s=%s%s\n' %
  381. (var, BatDirs(dirs), ';%PATH%' if var == 'PATH' else ''))
  382. f.write('goto :END\n')
  383. f.write(':END\n')
  384. # Restore the original directory.
  385. f.write('popd\n')
  386. with open(set_env_prefix + '.x86.json', 'wt', newline='') as f:
  387. assert not set(env.keys()) & set(env_x86.keys()), 'dupe keys'
  388. json.dump(
  389. {
  390. 'env':
  391. collections.OrderedDict(
  392. list(env.items()) + list(env_x86.items()))
  393. }, f)
  394. with open(set_env_prefix + '.x64.json', 'wt', newline='') as f:
  395. assert not set(env.keys()) & set(env_x64.keys()), 'dupe keys'
  396. json.dump(
  397. {
  398. 'env':
  399. collections.OrderedDict(
  400. list(env.items()) + list(env_x64.items()))
  401. }, f)
  402. with open(set_env_prefix + '.arm64.json', 'wt', newline='') as f:
  403. assert not set(env.keys()) & set(env_arm64.keys()), 'dupe keys'
  404. json.dump(
  405. {
  406. 'env':
  407. collections.OrderedDict(
  408. list(env.items()) + list(env_arm64.items()))
  409. }, f)
  410. def AddEnvSetup(files, include_arm):
  411. """We need to generate this file in the same way that the "from pieces"
  412. script does, so pull that in here."""
  413. tempdir = tempfile.mkdtemp()
  414. os.makedirs(os.path.join(tempdir, 'Windows Kits', '10', 'bin'))
  415. GenerateSetEnvCmd(tempdir)
  416. files.append(
  417. (os.path.join(tempdir, 'Windows Kits', '10', 'bin',
  418. 'SetEnv.cmd'), 'Windows Kits\\10\\bin\\SetEnv.cmd'))
  419. files.append((os.path.join(tempdir, 'Windows Kits', '10', 'bin',
  420. 'SetEnv.x86.json'),
  421. 'Windows Kits\\10\\bin\\SetEnv.x86.json'))
  422. files.append((os.path.join(tempdir, 'Windows Kits', '10', 'bin',
  423. 'SetEnv.x64.json'),
  424. 'Windows Kits\\10\\bin\\SetEnv.x64.json'))
  425. if include_arm:
  426. files.append((os.path.join(tempdir, 'Windows Kits', '10', 'bin',
  427. 'SetEnv.arm64.json'),
  428. 'Windows Kits\\10\\bin\\SetEnv.arm64.json'))
  429. vs_version_file = os.path.join(tempdir, 'VS_VERSION')
  430. with open(vs_version_file, 'wt', newline='') as version:
  431. print(_vs_version, file=version)
  432. files.append((vs_version_file, 'VS_VERSION'))
  433. def RenameToSha1(output):
  434. """Determine the hash in the same way that the unzipper does to rename the
  435. # .zip file."""
  436. print('Extracting to determine hash...')
  437. tempdir = tempfile.mkdtemp()
  438. old_dir = os.getcwd()
  439. os.chdir(tempdir)
  440. rel_dir = 'vs_files'
  441. with zipfile.ZipFile(os.path.join(old_dir, output), 'r',
  442. zipfile.ZIP_DEFLATED, True) as zf:
  443. zf.extractall(rel_dir)
  444. print('Hashing...')
  445. sha1 = get_toolchain_if_necessary.CalculateHash(rel_dir, None)
  446. # Shorten from forty characters to ten. This is still enough to avoid
  447. # collisions, while being less unwieldy and reducing the risk of MAX_PATH
  448. # failures.
  449. sha1 = sha1[:10]
  450. os.chdir(old_dir)
  451. shutil.rmtree(tempdir)
  452. final_name = sha1 + '.zip'
  453. os.rename(output, final_name)
  454. print('Renamed %s to %s.' % (output, final_name))
  455. def main():
  456. if sys.version_info[0] < 3:
  457. print('This script requires Python 3')
  458. sys.exit(10)
  459. usage = 'usage: %prog [options] 2022'
  460. parser = optparse.OptionParser(usage)
  461. parser.add_option('-w',
  462. '--winver',
  463. action='store',
  464. type='string',
  465. dest='winver',
  466. default='10.0.22621.0',
  467. help='Windows SDK version, such as 10.0.22621.0')
  468. parser.add_option('-d',
  469. '--dryrun',
  470. action='store_true',
  471. dest='dryrun',
  472. default=False,
  473. help='scan for file existence and prints statistics')
  474. parser.add_option('--noarm',
  475. action='store_false',
  476. dest='arm',
  477. default=True,
  478. help='Avoids arm parts of the SDK')
  479. parser.add_option('--override',
  480. action='store',
  481. type='string',
  482. dest='override_dir',
  483. default=None,
  484. help='Specify alternate bin/include/lib directory')
  485. parser.add_option(
  486. '--repackage',
  487. action='store',
  488. type='string',
  489. dest='repackage_dir',
  490. default=None,
  491. help='Specify raw directory to be packaged, for hot fixes.')
  492. parser.add_option('--allow_multiple_vs_installs',
  493. action='store_true',
  494. default=False,
  495. dest='allow_multiple_vs_installs',
  496. help='Specify if multiple VS installs are allowed.')
  497. (options, args) = parser.parse_args()
  498. if options.repackage_dir:
  499. files = BuildRepackageFileList(options.repackage_dir)
  500. else:
  501. if len(args) != 1 or args[0] not in SUPPORTED_VS_VERSIONS:
  502. print('Must specify 2022')
  503. parser.print_help()
  504. return 1
  505. if options.override_dir:
  506. if (not os.path.exists(os.path.join(options.override_dir, 'bin'))
  507. or not os.path.exists(
  508. os.path.join(options.override_dir, 'include'))
  509. or not os.path.exists(
  510. os.path.join(options.override_dir, 'lib'))):
  511. print(
  512. 'Invalid override directory - must contain bin/include/lib dirs'
  513. )
  514. return 1
  515. global _vs_version
  516. _vs_version = args[0]
  517. global _win_version
  518. _win_version = options.winver
  519. global _vc_tools
  520. global _allow_multiple_vs_installs
  521. _allow_multiple_vs_installs = options.allow_multiple_vs_installs
  522. vs_path = GetVSPath()
  523. temp_tools_path = ExpandWildcards(vs_path, 'VC/Tools/MSVC/14.*.*')
  524. # Strip off the leading vs_path characters and switch back to /
  525. # separators.
  526. _vc_tools = temp_tools_path[len(vs_path) + 1:].replace('\\', '/')
  527. print('Building file list for VS %s Windows %s...' %
  528. (_vs_version, _win_version))
  529. files = BuildFileList(options.override_dir, options.arm, vs_path)
  530. AddEnvSetup(files, options.arm)
  531. if False:
  532. for f in files:
  533. print(f[0], '->', f[1])
  534. return 0
  535. output = 'out.zip'
  536. if os.path.exists(output):
  537. os.unlink(output)
  538. count = 0
  539. version_match_count = 0
  540. total_size = 0
  541. missing_files = False
  542. with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED, True) as zf:
  543. for disk_name, archive_name in files:
  544. sys.stdout.write('\r%d/%d ...%s' %
  545. (count, len(files), disk_name[-40:]))
  546. sys.stdout.flush()
  547. count += 1
  548. if not options.repackage_dir and disk_name.count(_win_version) > 0:
  549. version_match_count += 1
  550. if os.path.exists(disk_name):
  551. total_size += os.path.getsize(disk_name)
  552. if not options.dryrun:
  553. zf.write(disk_name, archive_name)
  554. else:
  555. missing_files = True
  556. sys.stdout.write('\r%s does not exist.\n\n' % disk_name)
  557. sys.stdout.flush()
  558. sys.stdout.write(
  559. '\r%1.3f GB of data in %d files, %d files for %s.%s\n' %
  560. (total_size / 1e9, count, version_match_count, _win_version, ' ' * 50))
  561. if options.dryrun:
  562. return 0
  563. if missing_files:
  564. raise Exception('One or more files were missing - aborting')
  565. if not options.repackage_dir and version_match_count == 0:
  566. raise Exception('No files found that match the specified winversion')
  567. sys.stdout.write('\rWrote to %s.%s\n' % (output, ' ' * 50))
  568. sys.stdout.flush()
  569. RenameToSha1(output)
  570. return 0
  571. if __name__ == '__main__':
  572. sys.exit(main())