Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(386)

Side by Side Diff: win_toolchain/toolchain2013.py

Issue 1382873003: win_toolchain: Update packaging script to package win10 sdk (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: . Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « win_toolchain/package_from_installed.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2013 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 """Extracts a Windows VS2013 toolchain from various downloadable pieces."""
7
8
9 import ctypes
10 import json
11 import optparse
12 import os
13 import shutil
14 import subprocess
15 import sys
16 import tempfile
17 import urllib2
18
19
20 BASEDIR = os.path.dirname(os.path.abspath(__file__))
21 WDK_ISO_URL = (
22 'http://download.microsoft.com/download/'
23 '4/A/2/4A25C7D5-EFBE-4182-B6A9-AE6850409A78/GRMWDK_EN_7600_1.ISO')
24 g_temp_dirs = []
25
26
27 sys.path.append(os.path.join(BASEDIR, '..'))
28 import download_from_google_storage
29
30
31 def GetLongPathName(path):
32 """Converts any 8dot3 names in the path to the full name."""
33 buf = ctypes.create_unicode_buffer(260)
34 size = ctypes.windll.kernel32.GetLongPathNameW(unicode(path), buf, 260)
35 if (size > 260):
36 sys.exit('Long form of path longer than 260 chars: %s' % path)
37 return buf.value
38
39
40 def RunOrDie(command):
41 subprocess.check_call(command, shell=True)
42
43
44 class ScopedSubstTempDir(object):
45 """Creates a |TempDir()| and subst's a drive to the path.
46
47 This is done to avoid exceedingly long names in some .msi packages which
48 fail to extract because they exceed _MAX_PATH. Only the "subst" part of this
49 is scoped, not the temp dir, which is left for use and cleanup by the
50 caller.
51 """
52 DefineDosDevice = ctypes.windll.kernel32.DefineDosDeviceW
53 DefineDosDevice.argtypes = [ctypes.c_int, ctypes.c_wchar_p, ctypes.c_wchar_p]
54 DDD_NO_BROADCAST_SYSTEM = 0x08
55 DDD_REMOVE_DEFINITION = 0x02
56
57 def __init__(self):
58 self.real_path = TempDir()
59 self.subst_drive = None
60
61 def __enter__(self):
62 """Tries to find a subst that we can use for the temporary directory, and
63 aborts on failure."""
64 for drive in range(ord('Z'), ord('A') - 1, -1):
65 candidate = '%c:' % drive
66 if self.DefineDosDevice(
67 self.DDD_NO_BROADCAST_SYSTEM, candidate, self.real_path) != 0:
68 self.subst_drive = candidate
69 return self
70 raise RuntimeError('Unable to find a subst path')
71
72 def __exit__(self, typ, value, traceback):
73 if self.subst_drive:
74 if self.DefineDosDevice(int(self.DDD_REMOVE_DEFINITION),
75 self.subst_drive,
76 self.real_path) == 0:
77 raise RuntimeError('Unable to remove subst')
78
79 def ShortenedPath(self):
80 return self.subst_drive + '\\'
81
82 def RealPath(self):
83 return self.real_path
84
85
86 def TempDir():
87 """Generates a temporary directory (for downloading or extracting to) and keep
88 track of the directory that's created for cleaning up later.
89 """
90 temp = tempfile.mkdtemp()
91 g_temp_dirs.append(temp)
92 return temp
93
94
95 def DeleteAllTempDirs():
96 """Removes all temporary directories created by |TempDir()|."""
97 global g_temp_dirs
98 if g_temp_dirs:
99 sys.stdout.write('Cleaning up temporaries...\n')
100 for temp in g_temp_dirs:
101 # shutil.rmtree errors out on read only attributes.
102 RunOrDie('rmdir /s/q "%s"' % temp)
103 g_temp_dirs = []
104
105
106 def GetMainIsoUrl(pro):
107 """Gets the main .iso URL.
108
109 If |pro| is False, downloads the Express edition.
110 """
111 prefix = 'http://download.microsoft.com/download/'
112 if pro:
113 return (prefix +
114 'A/F/1/AF128362-A6A8-4DB3-A39A-C348086472CC/VS2013_RTM_PRO_ENU.iso')
115 else:
116 return (prefix +
117 '7/2/E/72E0F986-D247-4289-B9DC-C4FB07374894/VS2013_RTM_DskExp_ENU.iso')
118
119
120 def Download(url, local_path):
121 """Downloads a large-ish binary file and print some status information while
122 doing so.
123 """
124 sys.stdout.write('Downloading %s...\n' % url)
125 req = urllib2.urlopen(url)
126 content_length = int(req.headers.get('Content-Length', 0))
127 bytes_read = 0L
128 terminator = '\r' if sys.stdout.isatty() else '\n'
129 with open(local_path, 'wb') as file_handle:
130 while True:
131 chunk = req.read(1024 * 1024)
132 if not chunk:
133 break
134 bytes_read += len(chunk)
135 file_handle.write(chunk)
136 sys.stdout.write('... %d/%d%s' % (bytes_read, content_length, terminator))
137 sys.stdout.flush()
138 sys.stdout.write('\n')
139 if content_length and content_length != bytes_read:
140 sys.exit('Got incorrect number of bytes downloading %s' % url)
141
142
143 def ExtractIso(iso_path):
144 """Uses 7zip to extract the contents of the given .iso (or self-extracting
145 .exe).
146 """
147 target_path = TempDir()
148 sys.stdout.write('Extracting %s...\n' % iso_path)
149 sys.stdout.flush()
150 # TODO(scottmg): Do this (and exe) manually with python code.
151 # Note that at the beginning of main() we set the working directory to 7z's
152 # location so that 7z can find its codec dll.
153 RunOrDie('7z x "%s" -y "-o%s"' % (iso_path, target_path))
154 return target_path
155
156
157 def ExtractMsi(msi_path):
158 """Uses msiexec to extract the contents of the given .msi file."""
159 sys.stdout.write('Extracting %s...\n' % msi_path)
160 with ScopedSubstTempDir() as temp_dir:
161 RunOrDie('msiexec /a "%s" /qn TARGETDIR="%s"' % (
162 msi_path, temp_dir.ShortenedPath()))
163 return temp_dir.RealPath()
164
165
166 def DownloadMainIso(url):
167 temp_dir = TempDir()
168 target_path = os.path.join(temp_dir, os.path.basename(url))
169 Download(url, target_path)
170 return target_path
171
172
173 def DownloadSDK8():
174 """Downloads the Win8 SDK.
175
176 This one is slightly different than the simpler direct downloads. There is
177 no .ISO distribution for the Windows 8 SDK. Rather, a tool is provided that
178 is a download manager. This is used to download the various .msi files to a
179 target location. Unfortunately, this tool requires elevation for no obvious
180 reason even when only downloading, so this function will trigger a UAC
181 elevation if the script is not run from an elevated prompt. This is mostly
182 grabbed for windbg and cdb (See http://crbug.com/321187) as most of the SDK
183 is in VS2013, however we need a couple D3D related things from the SDK.
184 """
185 # Use the long path name here because because 8dot3 names don't seem to work.
186 sdk_temp_dir = GetLongPathName(TempDir())
187 target_path = os.path.join(sdk_temp_dir, 'sdksetup.exe')
188 standalone_path = os.path.join(sdk_temp_dir, 'Standalone')
189 Download(
190 ('http://download.microsoft.com/download/'
191 'F/1/3/F1300C9C-A120-4341-90DF-8A52509B23AC/standalonesdk/sdksetup.exe'),
192 target_path)
193 sys.stdout.write(
194 'Running sdksetup.exe to download Win8 SDK (may request elevation)...\n')
195 count = 0
196 while count < 5:
197 rc = subprocess.call([target_path,
198 '/quiet',
199 '/features', 'OptionId.WindowsDesktopDebuggers',
200 'OptionId.WindowsDesktopSoftwareDevelopmentKit',
201 '/layout', standalone_path])
202 if rc == 0:
203 return standalone_path
204 count += 1
205 sys.stdout.write('Windows 8 SDK failed to download, retrying.\n')
206 sys.exit('After multiple retries, couldn\'t download Win8 SDK')
207
208
209 def DownloadWDKIso():
210 wdk_temp_dir = TempDir()
211 target_path = os.path.join(wdk_temp_dir, 'GRMWDK_EN_7600_1.ISO')
212 Download(WDK_ISO_URL, target_path)
213 return target_path
214
215
216 def DownloadUsingGsutil(filename):
217 """Downloads the given file from Google Storage chrome-wintoolchain bucket."""
218 temp_dir = TempDir()
219 assert os.path.basename(filename) == filename
220 target_path = os.path.join(temp_dir, filename)
221 gsutil = download_from_google_storage.Gsutil(
222 download_from_google_storage.GSUTIL_DEFAULT_PATH, boto_path=None)
223 code = gsutil.call('cp', 'gs://chrome-wintoolchain/' + filename, target_path)
224 if code != 0:
225 sys.exit('gsutil failed')
226 return target_path
227
228
229 def GetVSInternal():
230 """Uses gsutil to pull the toolchain from internal Google Storage bucket."""
231 return DownloadUsingGsutil('VS2013_RTM_PRO_ENU.iso')
232
233
234 def GetSDKInternal():
235 """Downloads a zipped copy of the SDK from internal Google Storage bucket,
236 and extracts it."""
237 zip_file = DownloadUsingGsutil('Standalone.zip')
238 return ExtractIso(zip_file)
239
240
241 class SourceImages(object):
242 """Local paths for components. |wdk_path| may be None if it's unnecessary for
243 the given configuration."""
244 def __init__(self, vs_path, sdk8_path, wdk_path):
245 self.vs_path = vs_path
246 self.sdk8_path = sdk8_path
247 self.wdk_path = wdk_path
248
249
250 def GetSourceImages(local_dir, pro):
251 """Downloads the various sources that we need.
252
253 Of note: Because Express does not include ATL, there's an additional download
254 of the 7.1 WDK which is the latest publically accessible source for ATL. When
255 |pro| this is not necessary (and CHROME_HEADLESS always implies Pro).
256 """
257 if pro and not local_dir:
258 sys.exit('Non-Express must be used with --local')
259 url = GetMainIsoUrl(pro)
260 if local_dir:
261 wdk_path = (os.path.join(local_dir, os.path.basename(WDK_ISO_URL))
262 if not pro else None)
263 return SourceImages(os.path.join(local_dir, os.path.basename(url)),
264 os.path.join(local_dir, 'Standalone'),
265 wdk_path=wdk_path)
266 else:
267 # Note that we do the SDK first, as it might cause an elevation prompt.
268 sdk8_path = DownloadSDK8()
269 vs_path = DownloadMainIso(url)
270 wdk_path = DownloadWDKIso() if not pro else None
271 return SourceImages(vs_path, sdk8_path, wdk_path=wdk_path)
272
273
274 def ExtractMsiList(root_dir, packages):
275 """Extracts the contents of a list of .msi files from an already extracted
276 .iso file.
277
278 |packages| is a list of pairs (msi, required). If required is not True, the
279 msi is optional (this is set for packages that are in Pro but not Express).
280 """
281 results = []
282 for (package, required) in packages:
283 path_to_package = os.path.join(root_dir, package)
284 if not os.path.exists(path_to_package) and not required:
285 continue
286 results.append(ExtractMsi(path_to_package))
287 return results
288
289
290 def ExtractComponents(image):
291 vs_packages = [
292 (r'vcRuntimeAdditional_amd64\vc_runtimeAdditional_x64.msi', True),
293 (r'vcRuntimeAdditional_x86\vc_runtimeAdditional_x86.msi', True),
294 (r'vcRuntimeDebug_amd64\vc_runtimeDebug_x64.msi', True),
295 (r'vcRuntimeDebug_x86\vc_runtimeDebug_x86.msi', True),
296 (r'vcRuntimeMinimum_amd64\vc_runtimeMinimum_x64.msi', True),
297 (r'vcRuntimeMinimum_x86\vc_runtimeMinimum_x86.msi', True),
298 (r'vc_compilerCore86\vc_compilerCore86.msi', True),
299 (r'vc_compilerCore86res\vc_compilerCore86res.msi', True),
300 (r'vc_compilerx64nat\vc_compilerx64nat.msi', False),
301 (r'vc_compilerx64natres\vc_compilerx64natres.msi', False),
302 (r'vc_compilerx64x86\vc_compilerx64x86.msi', False),
303 (r'vc_compilerx64x86res\vc_compilerx64x86res.msi', False),
304 (r'vc_librarycore86\vc_librarycore86.msi', True),
305 (r'vc_libraryDesktop\x64\vc_LibraryDesktopX64.msi', True),
306 (r'vc_libraryDesktop\x86\vc_LibraryDesktopX86.msi', True),
307 (r'vc_libraryextended\vc_libraryextended.msi', False),
308 (r'professionalcore\Setup\vs_professionalcore.msi', False),
309 (r'vc_libraryselectablemfc\vc_libraryselectablemfc.msi', False),
310 ]
311 extracted_iso = ExtractIso(image.vs_path)
312 result = ExtractMsiList(os.path.join(extracted_iso, 'packages'), vs_packages)
313
314 sdk_packages = [
315 (r'X86 Debuggers And Tools-x86_en-us.msi', True),
316 (r'X64 Debuggers And Tools-x64_en-us.msi', True),
317 (r'SDK Debuggers-x86_en-us.msi', True),
318 (r'Windows Software Development Kit-x86_en-us.msi', True),
319 (r'Windows Software Development Kit for Metro style Apps-x86_en-us.msi',
320 True),
321 ]
322 result.extend(ExtractMsiList(os.path.join(image.sdk8_path, 'Installers'),
323 sdk_packages))
324
325 if image.wdk_path:
326 # This image will only be set when using Express, when we need the WDK
327 # headers and libs to supplement Express with ATL.
328 wdk_packages = [
329 (r'headers.msi', True),
330 (r'libs_x86fre.msi', True),
331 (r'libs_x64fre.msi', True),
332 ]
333 extracted_iso = ExtractIso(image.wdk_path)
334 result.extend(ExtractMsiList(os.path.join(extracted_iso, 'WDK'),
335 wdk_packages))
336
337 return result
338
339
340 def CopyToFinalLocation(extracted_dirs, target_dir):
341 sys.stdout.write('Copying to final location...\n')
342 mappings = {
343 'Program Files\\Microsoft Visual Studio 12.0\\VC\\': 'VC\\',
344 'Program Files\\Microsoft Visual Studio 12.0\\DIA SDK\\': 'DIA SDK\\',
345 'System64\\': 'sys64\\',
346 'System\\': 'sys32\\',
347 'Windows Kits\\8.1\\': 'win8sdk\\',
348 'WinDDK\\7600.16385.win7_wdk.100208-1538\\': 'wdk\\',
349 }
350 matches = []
351 for extracted_dir in extracted_dirs:
352 for root, _, filenames in os.walk(extracted_dir):
353 for filename in filenames:
354 matches.append((extracted_dir, os.path.join(root, filename)))
355
356 copies = []
357 for prefix, full_path in matches:
358 # +1 for trailing \.
359 partial_path = full_path[len(prefix) + 1:]
360 for map_from, map_to in mappings.iteritems():
361 if partial_path.startswith(map_from):
362 target_path = os.path.join(map_to, partial_path[len(map_from):])
363 copies.append((full_path, os.path.join(target_dir, target_path)))
364
365 for full_source, full_target in copies:
366 target_dir = os.path.dirname(full_target)
367 if not os.path.isdir(target_dir):
368 os.makedirs(target_dir)
369 shutil.copy2(full_source, full_target)
370
371
372 def GenerateSetEnvCmd(target_dir, pro):
373 """Generate a batch file that gyp expects to exist to set up the compiler
374 environment.
375
376 This is normally generated by a full install of the SDK, but we
377 do it here manually since we do not do a full install."""
378 with open(os.path.join(
379 target_dir, r'win8sdk\bin\SetEnv.cmd'), 'w') as f:
380 f.write('@echo off\n'
381 ':: Generated by win_toolchain\\toolchain2013.py.\n'
382 # Common to x86 and x64
383 'set PATH=%~dp0..\\..\\Common7\\IDE;%PATH%\n'
384 'set INCLUDE=%~dp0..\\..\\win8sdk\\Include\\um;'
385 '%~dp0..\\..\\win8sdk\\Include\\shared;'
386 '%~dp0..\\..\\VC\\include;'
387 '%~dp0..\\..\\VC\\atlmfc\\include\n'
388 'if "%1"=="/x64" goto x64\n')
389
390 # x86. If we're Pro, then use the amd64_x86 cross (we don't support x86
391 # host at all).
392 if pro:
393 f.write('set PATH=%~dp0..\\..\\win8sdk\\bin\\x86;'
394 '%~dp0..\\..\\VC\\bin\\amd64_x86;'
395 '%~dp0..\\..\\VC\\bin\\amd64;' # Needed for mspdb120.dll.
396 '%PATH%\n')
397 else:
398 f.write('set PATH=%~dp0..\\..\\win8sdk\\bin\\x86;'
399 '%~dp0..\\..\\VC\\bin;%PATH%\n')
400 f.write('set LIB=%~dp0..\\..\\VC\\lib;'
401 '%~dp0..\\..\\win8sdk\\Lib\\winv6.3\\um\\x86;'
402 '%~dp0..\\..\\VC\\atlmfc\\lib\n'
403 'goto :EOF\n')
404
405 # Express does not include a native 64 bit compiler, so we have to use
406 # the x86->x64 cross.
407 if not pro:
408 # x86->x64 cross.
409 f.write(':x64\n'
410 'set PATH=%~dp0..\\..\\win8sdk\\bin\\x64;'
411 '%~dp0..\\..\\VC\\bin\\x86_amd64;'
412 # Needed for mspdb120.dll. Must be after above though, so
413 # that cl.exe is the x86_amd64 one.
414 '%~dp0..\\..\\VC\\bin;'
415 '%PATH%\n')
416 else:
417 # x64 native.
418 f.write(':x64\n'
419 'set PATH=%~dp0..\\..\\win8sdk\\bin\\x64;'
420 '%~dp0..\\..\\VC\\bin\\amd64;'
421 '%PATH%\n')
422 f.write('set LIB=%~dp0..\\..\\VC\\lib\\amd64;'
423 '%~dp0..\\..\\win8sdk\\Lib\\winv6.3\\um\\x64;'
424 '%~dp0..\\..\\VC\\atlmfc\\lib\\amd64\n')
425
426
427 def DoTreeMirror(target_dir, tree_sha1):
428 """In order to save temporary space on bots that do not have enough space to
429 download ISOs, unpack them, and copy to the target location, the whole tree
430 is uploaded as a zip to internal storage, and then mirrored here."""
431 local_zip = DownloadUsingGsutil(tree_sha1 + '.zip')
432 sys.stdout.write('Extracting %s...\n' % local_zip)
433 sys.stdout.flush()
434 RunOrDie('7z x "%s" -y "-o%s"' % (local_zip, target_dir))
435
436
437 def main():
438 parser = optparse.OptionParser(description=sys.modules[__name__].__doc__)
439 parser.add_option('--targetdir', metavar='DIR',
440 help='put toolchain into DIR',
441 default=os.path.join(BASEDIR, 'win_toolchain_2013'))
442 parser.add_option('--noclean', action='store_false', dest='clean',
443 help='do not remove temp files',
444 default=True)
445 parser.add_option('--local', metavar='DIR',
446 help='use downloaded files from DIR')
447 parser.add_option('--express',
448 help='use VS Express instead of Pro', action='store_true')
449 parser.add_option('--sha1',
450 help='tree sha1 that can be used to mirror an internal '
451 'copy (used if --use-gs)')
452 parser.add_option('--use-gs',
453 help='Use internal servers to pull isos',
454 default=bool(int(os.environ.get('CHROME_HEADLESS', 0))),
455 action='store_true')
456 options, _ = parser.parse_args()
457 try:
458 target_dir = os.path.abspath(options.targetdir)
459 if os.path.exists(target_dir):
460 parser.error('%s already exists. Please [re]move it or use '
461 '--targetdir to select a different target.\n' %
462 target_dir)
463 # Set the working directory to 7z subdirectory. 7-zip doesn't find its
464 # codec dll very well, so this is the simplest way to make sure it runs
465 # correctly, as we don't otherwise care about working directory.
466 os.chdir(os.path.join(BASEDIR, '7z'))
467 if options.use_gs and options.sha1:
468 options.express = False
469 DoTreeMirror(target_dir, options.sha1)
470 else:
471 images = GetSourceImages(options.local, not options.express)
472 extracted = ExtractComponents(images)
473 CopyToFinalLocation(extracted, target_dir)
474 GenerateSetEnvCmd(target_dir, not options.express)
475
476 data = {
477 'path': target_dir,
478 'version': '2013e' if options.express else '2013',
479 'win8sdk': os.path.join(target_dir, 'win8sdk'),
480 'wdk': os.path.join(target_dir, 'wdk'),
481 'runtime_dirs': [
482 os.path.join(target_dir, 'sys64'),
483 os.path.join(target_dir, 'sys32'),
484 ],
485 }
486 with open(os.path.join(target_dir, '..', 'data.json'), 'w') as f:
487 json.dump(data, f)
488 finally:
489 if options.clean:
490 DeleteAllTempDirs()
491
492
493 if __name__ == '__main__':
494 sys.exit(main())
OLDNEW
« no previous file with comments | « win_toolchain/package_from_installed.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698