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