Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 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 # vim: set ts=2 sw=2 et sts=2 ai: | |
| 6 | |
| 7 """Download files for your platfrom and extract its contents into a directory. | |
| 8 | |
| 9 Uses download_from_google_storage for the actual download process. | |
| 10 Uses tar to extract the files from the archive. | |
| 11 | |
| 12 The input is a list of sha1 files suitable for download_from_google_storage. | |
| 13 """ | |
| 14 | |
| 15 """ | |
| 16 TODO(mithro): Replace the other download/extract scripts that exist in the | |
| 17 chrome tree. | |
| 18 | |
| 19 Replace chrome/installer/linux/sysroot_scripts/install-debian.wheezy.sysroot.py | |
| 20 download_and_extract.py \\ | |
| 21 --default-file-arch Linux \\ | |
| 22 --bucket chrome-linux-sysroot \\ | |
| 23 --extract ????/debian_wheezy_???-sysroot \\ | |
| 24 <sha1 files??> | |
| 25 | |
| 26 Replace build/linux/install-arm-sysroot.py | |
| 27 download_and_extract.py \\ | |
| 28 --bucket nativeclient-archive2 \\ | |
| 29 --extract build/linux/arm-sys-root \\ | |
| 30 <sha1 files??> | |
| 31 | |
| 32 Replace download part of tools/clang/scripts/update.(py|sh) | |
| 33 download_and_extract.py \\ | |
| 34 --bucket chromium-browser-clang \\ | |
| 35 --extract third_party/llvm-build/Release+Asserts \\ | |
| 36 <sha1 files??> | |
| 37 """ | |
| 38 | |
| 39 | |
| 40 import optparse | |
| 41 import os | |
| 42 import re | |
| 43 import shutil | |
| 44 import subprocess | |
| 45 import sys | |
| 46 import types | |
| 47 | |
| 48 | |
| 49 def MatchPatterns(mapping, string, error): | |
| 50 if not isinstance(string, types.StringTypes): | |
| 51 raise TypeError("Can't match %r (a %s)" % (string, type(string))) | |
| 52 | |
| 53 for i, (pattern, result) in enumerate(mapping): | |
| 54 if not isinstance(pattern, types.StringTypes): | |
| 55 raise TypeError( | |
| 56 "%r (%s) is not valid regex pattern (pattern %i - %s)." % ( | |
| 57 pattern, type(pattern), i, (pattern, result))) | |
| 58 if re.search(pattern, string, re.I): | |
| 59 return result | |
| 60 raise ValueError(error % string) | |
| 61 | |
| 62 | |
| 63 def ExtractAndNormalizeArch(string): | |
| 64 """Extract and normalize an architecture string. | |
| 65 >>> # Linux arch / uname output | |
| 66 >>> ExtractAndNormalizeArch('x86_64') | |
| 67 'amd64' | |
| 68 >>> ExtractAndNormalizeArch('i686') | |
| 69 'i386' | |
| 70 >>> ExtractAndNormalizeArch('i386') | |
| 71 'i386' | |
| 72 | |
| 73 >>> # autoconf "platform" tuple | |
| 74 >>> ExtractAndNormalizeArch('i686-pc-linux-gnu') | |
| 75 'i386' | |
| 76 >>> ExtractAndNormalizeArch('x86_64-unknown-linux-gnu') | |
| 77 'amd64' | |
| 78 | |
| 79 >>> # platform.machine() | |
| 80 >>> ExtractAndNormalizeArch('i386') | |
| 81 'i386' | |
| 82 >>> ExtractAndNormalizeArch('x86_64') | |
| 83 'amd64' | |
| 84 | |
| 85 >>> # GYP Defines | |
| 86 >>> ExtractAndNormalizeArch('target_arch=x64') | |
| 87 'amd64' | |
| 88 >>> ExtractAndNormalizeArch('target_arch=ia32') | |
| 89 'i386' | |
| 90 >>> ExtractAndNormalizeArch('x64') | |
| 91 'amd64' | |
| 92 >>> ExtractAndNormalizeArch('ia32') | |
| 93 'i386' | |
| 94 | |
| 95 >>> # Empty Arch | |
| 96 >>> ExtractAndNormalizeArch('') | |
| 97 '' | |
| 98 """ | |
| 99 ARCH_MAPPING = [ | |
| 100 # Linux 'arch' outputs | |
| 101 ('i[3456]?86', 'i386'), | |
| 102 ('i86pc', 'i386'), | |
| 103 ('x86_64', 'amd64'), | |
| 104 ('amd64', 'amd64'), | |
| 105 ('mips64', 'mips64'), # Must be before mips | |
| 106 ('mips', 'mips'), | |
| 107 ('arm', 'arm'), | |
| 108 ('aarch', 'arm64'), | |
| 109 # Windows | |
| 110 ('win32', 'i386'), | |
| 111 ('win64', 'amd64'), | |
| 112 # GYP defines | |
| 113 ('ia32', 'i386'), | |
| 114 ('x64', 'amd64'), | |
| 115 # Empty arch | |
| 116 ('^$', ''), | |
| 117 ] | |
| 118 return MatchPatterns( | |
| 119 ARCH_MAPPING, string, | |
| 120 'Was not able to extract architecture from %s') | |
| 121 | |
| 122 | |
| 123 def ExtractAndNormalizeOS(string): | |
| 124 """Extract and normalize an OS string. | |
| 125 | |
| 126 >>> # Used by download_from_storage | |
| 127 >>> # sys.platform | |
| 128 >>> ExtractAndNormalizeOS('linux2') | |
| 129 'Linux' | |
| 130 >>> ExtractAndNormalizeOS('darwin') | |
| 131 'Mac' | |
| 132 >>> ExtractAndNormalizeOS('cygwin') | |
| 133 'Win' | |
| 134 >>> ExtractAndNormalizeOS('win32') | |
| 135 'Win' | |
| 136 >>> ExtractAndNormalizeOS('win64') | |
| 137 'Win' | |
| 138 | |
| 139 >>> # platform.system() | |
| 140 >>> ExtractAndNormalizeOS('Linux') | |
| 141 'Linux' | |
| 142 >>> ExtractAndNormalizeOS('Windows') | |
| 143 'Win' | |
| 144 | |
| 145 >>> # Used by tools/clang/scripts | |
| 146 >>> # uname -s | |
| 147 >>> ExtractAndNormalizeOS('Linux') | |
| 148 'Linux' | |
| 149 >>> ExtractAndNormalizeOS('Darwin') | |
| 150 'Mac' | |
| 151 | |
| 152 >>> # GYP defines | |
| 153 >>> ExtractAndNormalizeOS('win') | |
| 154 'Win' | |
| 155 >>> ExtractAndNormalizeOS('linux') | |
| 156 'Linux' | |
| 157 >>> ExtractAndNormalizeOS('mac') | |
| 158 'Mac' | |
| 159 | |
| 160 >>> # GNU triplets | |
| 161 >>> ExtractAndNormalizeOS('i686-pc-linux-gnu') | |
| 162 'Linux' | |
| 163 >>> ExtractAndNormalizeOS('x86_64-unknown-linux-gnu') | |
| 164 'Linux' | |
| 165 >>> ExtractAndNormalizeOS('i586-pc-mingw32') | |
| 166 'Win' | |
| 167 >>> ExtractAndNormalizeOS('i386-pc-cygwin') | |
| 168 'Win' | |
| 169 >>> ExtractAndNormalizeOS('i386-pc-win32') | |
| 170 'Win' | |
| 171 >>> ExtractAndNormalizeOS('x86_64-apple-darwin10') | |
| 172 'Mac' | |
| 173 """ | |
| 174 PLATFORM_MAPPING = [ | |
| 175 # Mac | |
| 176 ('darwin', 'Mac'), | |
| 177 ('mac', 'Mac'), | |
| 178 # Linux | |
| 179 ('linux.*', 'Linux'), | |
| 180 # Windows | |
| 181 ('cygwin', 'Win'), | |
| 182 ('mingw', 'Win'), | |
|
Lei Zhang
2014/04/02 08:32:22
Is this in use anywhere? download_from_google_stor
| |
| 183 ('win', 'Win'), | |
| 184 ] | |
| 185 return MatchPatterns( | |
| 186 PLATFORM_MAPPING, string, | |
| 187 'Was not able to extract operating system from %s') | |
| 188 | |
| 189 | |
| 190 def GetSystemArch(os_str): | |
| 191 if os_str == 'Linux': | |
| 192 # Try calling arch first, then fall back to uname | |
| 193 try: | |
| 194 return subprocess.check_output(['arch']).strip() | |
| 195 except subprocess.CalledProcessError, e: | |
| 196 # We want the architecture, which is roughly the machine hardware name | |
| 197 # in uname. | |
| 198 # -m, --machine; print the machine hardware name | |
| 199 # These other two are possibilities? | |
| 200 # -p, --processor; print the processor type or 'unknown' | |
| 201 # -i, --hardware-platform; print the hardware platform or 'unknown' | |
| 202 return subprocess.check_output(['uname', '-m']).strip() | |
| 203 else: | |
| 204 # TODO(mithro): Make this work under Mac / Windows | |
| 205 return '' | |
| 206 | |
| 207 | |
| 208 def FilterFilesByPlatform(files, target_os, target_arch, | |
|
Lei Zhang
2014/04/02 08:32:22
Design concern: I feel this is trying too hard to
| |
| 209 file_os_default=None, file_arch_default=''): | |
| 210 """Filter input files to given platform. | |
| 211 | |
| 212 We assume the target arch is the host arch if not overridden. | |
| 213 | |
| 214 >>> clang_style = [ | |
| 215 ... 'abc/Linux_ia32/xxx', | |
| 216 ... 'abc/Linux_x64/xxx', | |
| 217 ... 'abc/Mac/xxx', | |
| 218 ... 'abc/Win/xxx', | |
| 219 ... ] | |
| 220 >>> FilterFilesByPlatform(clang_style, 'Linux', 'i386') | |
| 221 ['abc/Linux_ia32/xxx'] | |
| 222 >>> FilterFilesByPlatform(clang_style, 'Linux', 'amd64') | |
| 223 ['abc/Linux_x64/xxx'] | |
| 224 >>> FilterFilesByPlatform(clang_style, 'Win', '') | |
| 225 ['abc/Win/xxx'] | |
| 226 >>> FilterFilesByPlatform(clang_style, 'Mac', '') | |
| 227 ['abc/Mac/xxx'] | |
| 228 | |
| 229 >>> gnu_style = [ | |
| 230 ... 'XXX-i686-pc-linux-gnu.XXX', | |
| 231 ... 'XXX-x86_64-unknown-linux-gnu.XXX', | |
| 232 ... 'XXX-i586-pc-mingw32.XXX', | |
| 233 ... 'XXX-i386-pc-win32.XXX', | |
| 234 ... 'XXX-x86_64-apple-darwin10.XXX', | |
| 235 ... ] | |
| 236 >>> FilterFilesByPlatform(gnu_style, 'Linux', 'i386') | |
| 237 ['XXX-i686-pc-linux-gnu.XXX'] | |
| 238 >>> FilterFilesByPlatform(gnu_style, 'Linux', 'amd64') | |
| 239 ['XXX-x86_64-unknown-linux-gnu.XXX'] | |
| 240 >>> FilterFilesByPlatform(gnu_style, 'Win', '') | |
| 241 ['XXX-i586-pc-mingw32.XXX', 'XXX-i386-pc-win32.XXX'] | |
| 242 >>> FilterFilesByPlatform(gnu_style, 'Mac', '') | |
| 243 ['XXX-x86_64-apple-darwin10.XXX'] | |
| 244 | |
| 245 >>> simple_no_os = [ | |
| 246 ... 'XXXX_amd64_XXX', | |
| 247 ... 'XXXX_i386_XXX', | |
| 248 ... ] | |
| 249 >>> FilterFilesByPlatform( | |
| 250 ... simple_no_os, 'Linux', 'i386', file_os_default='Linux') | |
| 251 ['XXXX_i386_XXX'] | |
| 252 >>> FilterFilesByPlatform( | |
| 253 ... simple_no_os, 'Linux', 'amd64', file_os_default='Linux') | |
| 254 ['XXXX_amd64_XXX'] | |
| 255 >>> FilterFilesByPlatform( | |
| 256 ... simple_no_os, 'Win', '', file_os_default='Linux') | |
| 257 [] | |
| 258 >>> # Fails when no default is provided and can't extract from filename. | |
| 259 >>> FilterFilesByPlatform(simple_no_os, 'Linux', 'i386') | |
| 260 Traceback (most recent call last): | |
| 261 ... | |
| 262 ValueError: Was not able to extract operating system from XXXX_amd64_XXX | |
| 263 """ | |
| 264 files_for_platform = [] | |
| 265 for filename in files: | |
| 266 try: | |
| 267 file_os = ExtractAndNormalizeOS(filename) | |
| 268 except ValueError, e: | |
| 269 if file_os_default is None: | |
| 270 raise | |
| 271 file_os = file_os_default | |
| 272 | |
| 273 try: | |
| 274 file_arch = ExtractAndNormalizeArch(filename) | |
| 275 except ValueError: | |
| 276 if file_arch_default is None: | |
| 277 raise | |
| 278 file_arch = file_arch_default | |
| 279 | |
| 280 match = True | |
| 281 if target_os != '' and file_os != '': | |
| 282 match = target_os == file_os | |
| 283 | |
| 284 if target_arch != '' and file_arch != '': | |
| 285 match = match and target_arch == file_arch | |
| 286 | |
| 287 if match: | |
| 288 files_for_platform.append(filename) | |
| 289 | |
| 290 return files_for_platform | |
| 291 | |
| 292 | |
| 293 class StampFile(object): | |
| 294 """Stores a stamp for when an action occured. | |
| 295 | |
| 296 This is normally a revision number or checksum of the input files to the | |
| 297 process. Proper usage of stamping will mean a partial action is never | |
| 298 considered successful. | |
| 299 | |
| 300 Proper usage is; | |
| 301 * /Check/ -- Check the stamp file matches your revision/checksum. | |
| 302 * /Delete/ -- Delete the existing stamp file. | |
| 303 * /Clean up/ -- Clean up any partially failed previous attempt. | |
| 304 * /Do action/ -- Do your work. | |
| 305 * /Set/ -- Set the stamp file to your revision/checksum. | |
| 306 | |
| 307 For example: | |
| 308 >>> stamp = 'mySHA1sum' | |
| 309 >>> action_stamp = StampFile('stamp.action') | |
| 310 >>> # Check stamp | |
| 311 >>> if stamp != action_stamp.get(): # doctest: +SKIP | |
| 312 ... # Delete stamp | |
| 313 ... action_stamp.delete() | |
| 314 ... # Clean up | |
| 315 ... if os.path.exists(filename): | |
| 316 ... os.unlink(filename) | |
| 317 ... # Action | |
| 318 ... do_download(filename) | |
| 319 ... # Set stamp | |
| 320 ... action_stamp.set(stamp) | |
| 321 """ | |
| 322 | |
| 323 def __init__(self, filename): | |
| 324 self.filename = filename | |
| 325 | |
| 326 def get(self): | |
| 327 try: | |
| 328 return file(self.filename, 'r').read() | |
| 329 except IOError, e: | |
| 330 # We use NaN because it is not equal even to itself, so equality checks | |
| 331 # will always fail. | |
| 332 return float('NaN') | |
| 333 | |
| 334 def delete(self): | |
| 335 if os.path.exists(self.filename): | |
| 336 os.unlink(self.filename) | |
| 337 | |
| 338 def set(self, stamp): | |
| 339 if os.path.exists(self.filename): | |
| 340 raise IOError('Stamp file %r currently exist!' % filename) | |
| 341 | |
| 342 f = file(self.filename, 'w') | |
| 343 f.write(stamp) | |
| 344 # Force a fsync so this ends up on disk and other processes can see it. | |
| 345 os.fsync(f) | |
| 346 f.close() | |
| 347 | |
| 348 | |
| 349 def main(args): | |
| 350 usage = ( | |
| 351 'usage: %prog [options] targets\n\n' | |
| 352 'Downloads and extracts targets which match the platform.\n' | |
| 353 'Targets must be a list of a .sha1 file, containing a sha1 sum' | |
| 354 'used by download_from_google_storage tool.') | |
| 355 | |
| 356 parser = optparse.OptionParser(usage) | |
| 357 parser.add_option( | |
| 358 '-b', '--bucket', | |
| 359 help='Google Storage bucket to fetch from.') | |
| 360 parser.add_option( | |
| 361 '-e', '--extract', | |
| 362 help='Directory to extract the downloaded files into.') | |
| 363 parser.add_option( | |
| 364 '-a', '--target-arch', | |
| 365 help='Override architecture to given value.') | |
| 366 parser.add_option( | |
| 367 '', '--default-file-arch', | |
| 368 help='Override input file architecture to given value.') | |
| 369 parser.add_option( | |
| 370 '', '--default-file-os', | |
| 371 help='Override input file operating system to given value.') | |
| 372 parser.add_option( | |
| 373 '', '--self-test', default=False, action="store_true", | |
| 374 help='Run the internal tests.') | |
| 375 | |
| 376 (options, files) = parser.parse_args() | |
| 377 | |
| 378 if options.self_test: | |
| 379 import doctest | |
| 380 return doctest.testmod() | |
| 381 | |
| 382 errors = [] | |
| 383 if not options.bucket: | |
| 384 errors.append('--bucket (-b) is required option.') | |
| 385 | |
| 386 if not options.extract: | |
| 387 errors.append('--extract (-e) is required option.') | |
| 388 | |
| 389 if not files: | |
| 390 errors.append('Need to specify files to download.') | |
| 391 | |
| 392 for filename in files: | |
| 393 if not os.path.exists(filename): | |
| 394 errors.append('File %s does not exist.' % filename) | |
| 395 | |
| 396 if errors: | |
| 397 parser.error('\n '.join(errors)) | |
| 398 | |
| 399 # Figure out which files we want to download. Filter by the current platform | |
| 400 # the tool is being run on. | |
| 401 target_os = ExtractAndNormalizeOS(sys.platform) | |
| 402 | |
| 403 target_arch = None | |
| 404 if options.target_arch: | |
| 405 target_arch = options.target_arch | |
| 406 else: | |
| 407 # Try to get target_arch out of GYP | |
| 408 gyp_target_arch = re.search( | |
| 409 '(target_arch=[^ ]*)', os.environ.get('GYP_DEFINES', '')) | |
| 410 if gyp_target_arch: | |
| 411 target_arch = gyp_target_arch.groups(1) | |
| 412 | |
| 413 if target_arch is None: # '' is a valid target_arch | |
| 414 target_arch = GetSystemArch(target_os) | |
| 415 | |
| 416 target_arch = ExtractAndNormalizeArch(target_arch) | |
| 417 | |
| 418 todo = FilterFilesByPlatform( | |
| 419 files, | |
| 420 target_os, | |
| 421 target_arch, | |
| 422 file_os_default=options.default_file_os, | |
| 423 file_arch_default=options.default_file_arch) | |
| 424 | |
| 425 if len(todo) == 0: | |
| 426 print 'No files to download.' | |
| 427 return 0 | |
| 428 | |
| 429 elif len(todo) > 1: | |
| 430 # TODO(mithro): Support downloading and extracting multiple files. | |
| 431 parser.error( | |
| 432 'Matched multiple files on this platform!\n' + '\n'.join(todo)) | |
| 433 return 1 | |
| 434 | |
| 435 # Process the files | |
| 436 for filename in todo: | |
| 437 filename = os.path.abspath(filename) | |
| 438 | |
| 439 basename, ext = os.path.splitext(filename) | |
| 440 assert ext == '.sha1', 'Input filename %s does not end in .sha1' % filename | |
| 441 sha1 = file(filename, 'r').read().strip() | |
| 442 | |
| 443 # Download the tarball | |
| 444 download_stamp = StampFile('%s.stamp.download' % basename) | |
| 445 if sha1 != download_stamp.get(): | |
| 446 print "Downloading", basename | |
| 447 download_stamp.delete() | |
| 448 if os.path.exists(basename): | |
| 449 os.unlink(basename) | |
| 450 | |
| 451 subprocess.check_call([ | |
| 452 'download_from_google_storage', | |
| 453 '--no_resume', | |
| 454 '--no_auth', | |
| 455 '--bucket', options.bucket, | |
| 456 '-s', filename]) | |
| 457 | |
| 458 download_stamp.set(sha1) | |
| 459 | |
| 460 # Extract the tarball | |
| 461 extract_stamp = StampFile('%s.stamp.untar' % basename) | |
| 462 if sha1 != extract_stamp.get(): | |
| 463 print "Extracting", basename | |
| 464 extract_stamp.delete() | |
| 465 if os.path.exists(options.extract): | |
| 466 shutil.rmtree(options.extract) | |
| 467 | |
| 468 os.makedirs(options.extract) | |
| 469 | |
| 470 # TODO(mithro): Maybe use https://docs.python.org/2/library/tarfile.html | |
| 471 # rather than tar cmdline. | |
| 472 subprocess.check_call( | |
| 473 ['tar', 'axf', basename], cwd=options.extract) | |
| 474 | |
| 475 extract_stamp.set(sha1) | |
| 476 | |
| 477 return 0 | |
| 478 | |
| 479 | |
| 480 if __name__ == '__main__': | |
| 481 sys.exit(main(sys.argv)) | |
| OLD | NEW |