OLD | NEW |
(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 optparse |
| 11 import os |
| 12 import shutil |
| 13 import subprocess |
| 14 import sys |
| 15 import tempfile |
| 16 import urllib2 |
| 17 |
| 18 |
| 19 BASEDIR = os.path.dirname(os.path.abspath(__file__)) |
| 20 g_temp_dirs = [] |
| 21 |
| 22 |
| 23 def GetLongPathName(path): |
| 24 """Converts any 8dot3 names in the path to the full name.""" |
| 25 buf = ctypes.create_unicode_buffer(260) |
| 26 size = ctypes.windll.kernel32.GetLongPathNameW(unicode(path), buf, 260) |
| 27 if (size > 260): |
| 28 sys.exit('Long form of path longer than 260 chars: %s' % path) |
| 29 return buf.value |
| 30 |
| 31 |
| 32 def RunOrDie(command): |
| 33 subprocess.check_call(command, shell=True) |
| 34 |
| 35 |
| 36 def TempDir(): |
| 37 """Generates a temporary directory (for downloading or extracting to) and keep |
| 38 track of the directory that's created for cleaning up later. |
| 39 """ |
| 40 global g_temp_dirs |
| 41 temp = tempfile.mkdtemp() |
| 42 g_temp_dirs.append(temp) |
| 43 return temp |
| 44 |
| 45 |
| 46 def DeleteAllTempDirs(): |
| 47 """Removes all temporary directories created by |TempDir()|.""" |
| 48 global g_temp_dirs |
| 49 if g_temp_dirs: |
| 50 sys.stdout.write('Cleaning up temporaries...\n') |
| 51 for temp in g_temp_dirs: |
| 52 # shutil.rmtree errors out on read only attributes. |
| 53 RunOrDie('rmdir /s/q "%s"' % temp) |
| 54 g_temp_dirs = [] |
| 55 |
| 56 |
| 57 def GetIsoUrl(pro): |
| 58 """Gets the .iso URL. |
| 59 |
| 60 If |pro| is False, downloads the Express edition. |
| 61 """ |
| 62 prefix = 'http://download.microsoft.com/download/' |
| 63 if pro: |
| 64 return (prefix + |
| 65 'A/F/1/AF128362-A6A8-4DB3-A39A-C348086472CC/VS2013_RTM_PRO_ENU.iso') |
| 66 else: |
| 67 return (prefix + |
| 68 '7/2/E/72E0F986-D247-4289-B9DC-C4FB07374894/VS2013_RTM_DskExp_ENU.iso') |
| 69 |
| 70 |
| 71 def Download(url, local_path): |
| 72 """Downloads a large-ish binary file and print some status information while |
| 73 doing so. |
| 74 """ |
| 75 sys.stdout.write('Downloading %s...\n' % url) |
| 76 req = urllib2.urlopen(url) |
| 77 content_length = int(req.headers.get('Content-Length', 0)) |
| 78 bytes_read = 0L |
| 79 terminator = '\r' if sys.stdout.isatty() else '\n' |
| 80 with open(local_path, 'wb') as file: |
| 81 while True: |
| 82 chunk = req.read(1024 * 1024) |
| 83 if not chunk: |
| 84 break |
| 85 bytes_read += len(chunk) |
| 86 file.write(chunk) |
| 87 sys.stdout.write('... %d/%d%s' % (bytes_read, content_length, terminator)) |
| 88 sys.stdout.flush() |
| 89 sys.stdout.write('\n') |
| 90 if content_length and content_length != bytes_read: |
| 91 raise SystemExit('Got incorrect number of bytes downloading %s' % url) |
| 92 |
| 93 |
| 94 def ExtractIso(iso_path): |
| 95 """Uses 7zip to extract the contents of the given .iso (or self-extracting |
| 96 .exe). |
| 97 """ |
| 98 target_path = TempDir() |
| 99 sys.stdout.write('Extracting %s...\n' % iso_path) |
| 100 sys.stdout.flush() |
| 101 # TODO(scottmg): Do this (and exe) manually with python code. |
| 102 # Note that at the beginning of main() we set the working directory to 7z's |
| 103 # location so that 7z can find its codec dll. |
| 104 RunOrDie('7z x "%s" -y "-o%s" >nul' % (iso_path, target_path)) |
| 105 return target_path |
| 106 |
| 107 |
| 108 def ExtractMsi(msi_path): |
| 109 """Uses msiexec to extract the contents of the given .msi file.""" |
| 110 sys.stdout.write('Extracting %s...\n' % msi_path) |
| 111 target_path = TempDir() |
| 112 RunOrDie('msiexec /a "%s" /qn TARGETDIR="%s"' % (msi_path, target_path)) |
| 113 return target_path |
| 114 |
| 115 |
| 116 def DownloadMainIso(url): |
| 117 temp_dir = TempDir() |
| 118 target_path = os.path.join(temp_dir, os.path.basename(url)) |
| 119 Download(url, target_path) |
| 120 return target_path |
| 121 |
| 122 |
| 123 def GetSourceImage(local_dir, pro): |
| 124 url = GetIsoUrl(pro) |
| 125 if local_dir: |
| 126 return os.path.join(local_dir, os.path.basename(url)) |
| 127 else: |
| 128 return DownloadMainIso(url) |
| 129 |
| 130 |
| 131 def ExtractMsiList(iso_dir, packages): |
| 132 """Extracts the contents of a list of .msi files from an already extracted |
| 133 .iso file. |
| 134 |
| 135 |packages| is a list of pairs (msi, required). If required is not True, the |
| 136 msi is optional (this is set for packages that are in Pro but not Express). |
| 137 """ |
| 138 results = [] |
| 139 for (package, required) in packages: |
| 140 path_to_package = os.path.join(iso_dir, 'packages', package) |
| 141 if not os.path.exists(path_to_package) and not required: |
| 142 continue |
| 143 results.append(ExtractMsi(path_to_package)) |
| 144 return results |
| 145 |
| 146 |
| 147 def ExtractComponents(image): |
| 148 packages = [ |
| 149 (r'vcRuntimeAdditional_amd64\vc_runtimeAdditional_x64.msi', True), |
| 150 (r'vcRuntimeAdditional_x86\vc_runtimeAdditional_x86.msi', True), |
| 151 (r'vcRuntimeDebug_amd64\vc_runtimeDebug_x64.msi', True), |
| 152 (r'vcRuntimeDebug_x86\vc_runtimeDebug_x86.msi', True), |
| 153 (r'vcRuntimeMinimum_amd64\vc_runtimeMinimum_x64.msi', True), |
| 154 (r'vcRuntimeMinimum_x86\vc_runtimeMinimum_x86.msi', True), |
| 155 (r'vc_compilerCore86\vc_compilerCore86.msi', True), |
| 156 (r'vc_compilerCore86res\vc_compilerCore86res.msi', True), |
| 157 (r'vc_compilerx64nat\vc_compilerx64nat.msi', False), |
| 158 (r'vc_compilerx64natres\vc_compilerx64natres.msi', False), |
| 159 (r'vc_compilerx64x86\vc_compilerx64x86.msi', False), |
| 160 (r'vc_compilerx64x86res\vc_compilerx64x86res.msi', False), |
| 161 (r'vc_librarycore86\vc_librarycore86.msi', True), |
| 162 (r'vc_libraryDesktop\x64\vc_LibraryDesktopX64.msi', True), |
| 163 (r'vc_libraryDesktop\x86\vc_LibraryDesktopX86.msi', True), |
| 164 (r'vc_libraryextended\vc_libraryextended.msi', False), |
| 165 (r'Windows_SDK\Windows Software Development Kit-x86_en-us.msi', True), |
| 166 ('Windows_SDK\\' |
| 167 r'Windows Software Development Kit for Metro style Apps-x86_en-us.msi', |
| 168 True), |
| 169 ] |
| 170 extracted_iso = ExtractIso(image) |
| 171 return ExtractMsiList(extracted_iso, packages) |
| 172 |
| 173 |
| 174 def CopyToFinalLocation(extracted_dirs, target_dir): |
| 175 sys.stdout.write('Copying to final location...\n') |
| 176 mappings = { |
| 177 'Program Files\\Microsoft Visual Studio 12.0\\': '.\\', |
| 178 'System64\\': 'sys64\\', |
| 179 'System\\': 'sys32\\', |
| 180 'Windows Kits\\8.0\\': 'win8sdk\\', |
| 181 } |
| 182 matches = [] |
| 183 for extracted_dir in extracted_dirs: |
| 184 for root, dirnames, filenames in os.walk(extracted_dir): |
| 185 for filename in filenames: |
| 186 matches.append((extracted_dir, os.path.join(root, filename))) |
| 187 |
| 188 copies = [] |
| 189 for prefix, full_path in matches: |
| 190 # +1 for trailing \. |
| 191 partial_path = full_path[len(prefix) + 1:] |
| 192 for map_from, map_to in mappings.iteritems(): |
| 193 if partial_path.startswith(map_from): |
| 194 target_path = os.path.join(map_to, partial_path[len(map_from):]) |
| 195 copies.append((full_path, os.path.join(target_dir, target_path))) |
| 196 |
| 197 for full_source, full_target in copies: |
| 198 target_dir = os.path.dirname(full_target) |
| 199 if not os.path.isdir(target_dir): |
| 200 os.makedirs(target_dir) |
| 201 shutil.copy2(full_source, full_target) |
| 202 |
| 203 |
| 204 def GenerateSetEnvCmd(target_dir, pro): |
| 205 """Generate a batch file that gyp expects to exist to set up the compiler |
| 206 environment. |
| 207 |
| 208 This is normally generated by a full install of the SDK, but we |
| 209 do it here manually since we do not do a full install.""" |
| 210 with open(os.path.join( |
| 211 target_dir, r'win8sdk\bin\SetEnv.cmd'), 'w') as f: |
| 212 f.write('@echo off\n' |
| 213 ':: Generated by win_toolchain\\toolchain2013.py.\n' |
| 214 # Common to x86 and x64 |
| 215 'set PATH=%~dp0..\\..\\Common7\\IDE;%PATH%\n' |
| 216 'set INCLUDE=%~dp0..\\..\\win8sdk\\Include\\um;' |
| 217 '%~dp0..\\..\\win8sdk\\Include\\shared;' |
| 218 '%~dp0..\\..\\VC\\include;' |
| 219 '%~dp0..\\..\\VC\\atlmfc\\include\n' |
| 220 'if "%1"=="/x64" goto x64\n') |
| 221 |
| 222 # x86. If we're Pro, then use the amd64_x86 cross (we don't support x86 |
| 223 # host at all). |
| 224 if pro: |
| 225 f.write('set PATH=%~dp0..\\..\\win8sdk\\bin\\x86;' |
| 226 '%~dp0..\\..\\VC\\bin\\amd64_x86;' |
| 227 '%~dp0..\\..\\VC\\bin\\amd64;' # Needed for mspdb120.dll. |
| 228 '%PATH%\n') |
| 229 else: |
| 230 f.write('set PATH=%~dp0..\\..\\win8sdk\\bin\\x86;' |
| 231 '%~dp0..\\..\\VC\\bin;%PATH%\n') |
| 232 f.write('set LIB=%~dp0..\\..\\VC\\lib;' |
| 233 '%~dp0..\\..\\win8sdk\\Lib\\win8\\um\\x86;' |
| 234 '%~dp0..\\..\\VC\\atlmfc\\lib\n' |
| 235 'goto :EOF\n') |
| 236 |
| 237 # Express does not include a native 64 bit compiler, so we have to use |
| 238 # the x86->x64 cross. |
| 239 if not pro: |
| 240 # x86->x64 cross. |
| 241 f.write(':x64\n' |
| 242 'set PATH=%~dp0..\\..\\win8sdk\\bin\\x64;' |
| 243 '%~dp0..\\..\\VC\\bin\\x86_amd64;' |
| 244 '%PATH%\n') |
| 245 else: |
| 246 # x64 native. |
| 247 f.write(':x64\n' |
| 248 'set PATH=%~dp0..\\..\\win8sdk\\bin\\x64;' |
| 249 '%~dp0..\\..\\VC\\bin\\amd64;' |
| 250 '%PATH%\n') |
| 251 f.write('set LIB=%~dp0..\\..\\VC\\lib\\amd64;' |
| 252 '%~dp0..\\..\\win8sdk\\Lib\\win8\\um\\x64;' |
| 253 '%~dp0..\\..\\VC\\atlmfc\\lib\\amd64\n') |
| 254 |
| 255 |
| 256 def main(): |
| 257 parser = optparse.OptionParser(description=sys.modules[__name__].__doc__) |
| 258 parser.add_option('--targetdir', metavar='DIR', |
| 259 help='put toolchain into DIR', |
| 260 default=os.path.join(BASEDIR, 'win_toolchain_2013')) |
| 261 parser.add_option('--noclean', action='store_false', dest='clean', |
| 262 help='do not remove temp files', |
| 263 default=True) |
| 264 parser.add_option('--local', metavar='DIR', |
| 265 help='use downloaded files from DIR') |
| 266 parser.add_option('--express', |
| 267 help='use VS Express instead of Pro', action='store_true') |
| 268 options, args = parser.parse_args() |
| 269 try: |
| 270 target_dir = os.path.abspath(options.targetdir) |
| 271 if os.path.exists(target_dir): |
| 272 parser.error('%s already exists. Please [re]move it or use ' |
| 273 '--targetdir to select a different target.\n' % |
| 274 target_dir) |
| 275 # Set the working directory to 7z subdirectory. 7-zip doesn't find its |
| 276 # codec dll very well, so this is the simplest way to make sure it runs |
| 277 # correctly, as we don't otherwise care about working directory. |
| 278 os.chdir(os.path.join(BASEDIR, '7z')) |
| 279 image = GetSourceImage(options.local, not options.express) |
| 280 extracted = ExtractComponents(image) |
| 281 CopyToFinalLocation(extracted, target_dir) |
| 282 |
| 283 GenerateSetEnvCmd(target_dir, not options.express) |
| 284 finally: |
| 285 if options.clean: |
| 286 DeleteAllTempDirs() |
| 287 |
| 288 |
| 289 if __name__ == '__main__': |
| 290 sys.exit(main()) |
OLD | NEW |