2 # Copyright (c) 2012 The Native Client 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.
6 """Download all Native Client toolchains for this platform.
8 This module downloads multiple tgz's and expands them.
18 import toolchainbinaries
21 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
22 PARENT_DIR = os.path.dirname(SCRIPT_DIR)
24 SHA1_IN_FILENAME = re.compile('.*_[0-9a-f]{40}$')
27 def LoadVersions(filepath):
28 """Load version data from specified filepath into a dictionary.
31 filepath: path to the file which will be loaded.
33 A dictionary of KEY:VALUE pairs.
36 version_lines = open(filepath, 'r').readlines()
37 for line_num, line in enumerate(version_lines, start=1):
39 if line.startswith('#'):
46 raise RuntimeError('Expecting KEY=VALUE in line %d:\n\t>>%s<<' %
49 key, val = line.split('=', 1)
54 def VersionSelect(versions, flavor):
55 """Determine svn revision based on version data and flavor.
58 versions: version data loaded from file.
61 An svn version number (or other version string).
64 if isinstance(flavor, tuple):
65 ids = [versions[i] for i in flavor[1:]]
67 if toolchainbinaries.IsBionicFlavor(flavor):
68 return versions['BIONIC_VERSION']
69 if toolchainbinaries.IsPnaclFlavor(flavor):
70 return versions['PNACL_VERSION']
71 if toolchainbinaries.IsX86Flavor(flavor):
72 if toolchainbinaries.IsNotNaClNewlibFlavor(flavor):
73 return versions['GLIBC_VERSION']
75 return versions['NEWLIB_VERSION']
76 if toolchainbinaries.IsArmTrustedFlavor(flavor):
77 return versions['ARM_TRUSTED_VERSION']
78 raise Exception('Unknown flavor "%s"' % flavor)
82 """Generate the name of the key for this flavor's hash.
87 The string key for the hash.
89 return 'NACL_TOOL_%s_HASH' % flavor.upper()
92 def HashSelect(versions, flavor):
93 """Determine expected hash value based on version data and flavor.
96 versions: version data loaded from file.
101 return versions[HashKey(flavor)]
104 def IsFlavorNeeded(options, flavor):
105 if isinstance(flavor, tuple):
107 if options.filter_out_predicates:
108 for predicate in options.filter_out_predicates:
109 if predicate(flavor):
114 def FlavorOutDir(options, flavor):
115 """Given a flavor, decide where it should be extracted."""
116 if isinstance(flavor, tuple):
117 return os.path.join(options.toolchain_dir, flavor[0])
119 return os.path.join(options.toolchain_dir, flavor)
122 def FlavorName(flavor):
123 """Given a flavor, get a string name for it."""
124 if isinstance(flavor, tuple):
130 def FlavorComponentNames(flavor):
131 if isinstance(flavor, tuple):
137 def FlavorUrls(options, versions, flavor):
138 """Given a flavor, get a list of the URLs of its components."""
139 if isinstance(flavor, tuple):
140 ids = [versions[i] for i in flavor[1:]]
141 return [toolchainbinaries.EncodeToolchainUrl(
142 options.base_once_url, i, 'new') for i in ids]
144 return [toolchainbinaries.EncodeToolchainUrl(
145 options.base_url, VersionSelect(versions, flavor), flavor)]
148 def FlavorHashes(versions, flavor):
149 """Given a flavor, get the list of hashes of its components."""
150 if isinstance(flavor, tuple):
151 return [HashSelect(versions, i) for i in flavor[1:]]
153 return [HashSelect(versions, flavor)]
156 def GetUpdatedDEPS(options, versions):
157 """Return a suggested DEPS toolchain hash update for all platforms.
160 options: options from the command line.
163 for platform in toolchainbinaries.PLATFORM_MAPPING:
164 pm = toolchainbinaries.PLATFORM_MAPPING[platform]
166 for flavor in pm[arch]:
167 if IsFlavorNeeded(options, flavor):
170 for flavor in flavors:
171 names = FlavorComponentNames(flavor)
172 urls = FlavorUrls(options, versions, flavor)
173 for name, url in zip(names, urls):
174 new_deps[name] = download_utils.HashUrl(url)
178 def ShowUpdatedDEPS(options, versions):
179 """Print a suggested DEPS toolchain hash update for all platforms.
182 options: options from the command line.
184 for flavor, value in sorted(GetUpdatedDEPS(options, versions).iteritems()):
185 keyname = HashKey(flavor)
186 print '%s=%s' % (keyname, value)
190 def SyncFlavor(flavor, urls, dst, hashes, min_time, keep=False, force=False,
192 """Sync a flavor of the nacl toolchain
195 flavor: short directory name of the toolchain flavor.
196 urls: urls to download the toolchain flavor from.
197 dst: destination directory for the toolchain.
198 hashes: expected hashes of the toolchain.
201 toolchain_dir = os.path.join(PARENT_DIR, 'toolchain')
202 if not os.path.exists(toolchain_dir):
203 os.makedirs(toolchain_dir)
204 download_dir = os.path.join(toolchain_dir, '.tars')
206 prefix = 'tmp_unpacked_toolchain_'
209 # Attempt to cleanup.
210 for path in os.listdir(toolchain_dir):
211 if path.startswith(prefix) and path.endswith(suffix):
212 full_path = os.path.join(toolchain_dir, path)
214 print 'Cleaning up %s...' % full_path
215 download_utils.RemoveDir(full_path)
217 print 'Failed cleanup with: ' + str(e)
219 # If we are forcing a sync, then ignore stamp
229 for url, hash_val in zip(urls, hashes):
230 # Build the tarfile name from the url
231 # http://foo..../bar.tar.gz -> bar
232 filepath, ext = url.split('/')[-1].split('.', 1)
233 # For filenames containing _SHA1s, drop the sha1 part.
234 if SHA1_IN_FILENAME.match(filepath) is not None:
235 filepath = filepath.rsplit('_', 1)[0]
236 # Put it in the download dir and add back extension.
237 filepath = os.path.join(download_dir, '.'.join([filepath, ext]))
238 filepaths.append(filepath)
239 # If we did not need to synchronize, then we are done
240 if download_utils.SyncURL(url, filepath, stamp_dir=stamp_dir,
241 min_time=min_time, hash_val=hash_val,
243 keep=keep, verbose=verbose):
250 # Compute the new hashes for each file.
252 for filepath in filepaths:
253 new_hashes.append(download_utils.HashFile(filepath))
255 untar_dir = tempfile.mkdtemp(
256 suffix=suffix, prefix=prefix, dir=toolchain_dir)
258 for filepath in filepaths:
259 tar = cygtar.CygTar(filepath, 'r:*', verbose=verbose)
271 # TODO(bradnelson_): get rid of this when toolchain tarballs flattened.
272 if isinstance(flavor, tuple) or 'arm' in flavor or 'pnacl' in flavor:
273 src = os.path.join(untar_dir)
274 elif 'newlib' in flavor:
275 src = os.path.join(untar_dir, 'sdk', 'nacl-sdk')
277 src = os.path.join(untar_dir, 'toolchain', flavor)
278 download_utils.MoveDirCleanly(src, dst)
281 download_utils.RemoveDir(untar_dir)
283 print 'Failed cleanup with: ' + str(e)
284 print 'Continuing on original exception...'
285 download_utils.WriteSourceStamp(dst, '\n'.join(urls))
286 download_utils.WriteHashStamp(dst, '\n'.join(new_hashes))
291 parser = optparse.OptionParser()
293 '-b', '--base-url', dest='base_url',
294 default=toolchainbinaries.BASE_DOWNLOAD_URL,
295 help='base url to download from')
297 '--base-once-url', dest='base_once_url',
298 default=toolchainbinaries.BASE_ONCE_DOWNLOAD_URL,
299 help='base url to download new toolchain artifacts from')
301 '-c', '--hashes', dest='hashes',
304 help='Calculate hashes.')
306 '-k', '--keep', dest='keep',
309 help='Keep the downloaded tarballs.')
311 '-q', '--quiet', dest='verbose',
313 action='store_false',
314 help='Produce less output.')
316 '--toolchain-dir', dest='toolchain_dir',
317 default=os.path.join(PARENT_DIR, 'toolchain'),
318 help='(optional) location of toolchain directory')
320 '--nacl-newlib-only', dest='filter_out_predicates',
321 action='append_const', const=toolchainbinaries.IsNotNaClNewlibFlavor,
322 help='download only the non-pnacl newlib toolchain')
324 '--allow-bionic', dest='allow_bionic', action='store_true',
326 help='Allow download of bionic toolchain.')
328 '--no-pnacl', dest='filter_out_predicates', action='append_const',
329 const=toolchainbinaries.IsPnaclFlavor,
330 help='Filter out PNaCl toolchains.')
332 '--no-x86', dest='filter_out_predicates', action='append_const',
333 const=toolchainbinaries.IsX86Flavor,
334 help='Filter out x86 toolchains.')
336 '--arm-untrusted', dest='arm_untrusted', action='store_true',
337 default=False, help='Add arm untrusted toolchains.')
339 '--no-pnacl-translator', dest='filter_out_predicates',
340 action='append_const',
341 const=toolchainbinaries.IsSandboxedTranslatorFlavor,
342 help='Filter out PNaCl sandboxed translator.')
344 '--no-arm-trusted', dest='filter_out_predicates', action='append_const',
345 const=toolchainbinaries.IsArmTrustedFlavor,
346 help='Filter out trusted arm toolchains.')
347 options, args = parser.parse_args(args)
349 if not options.arm_untrusted:
350 if options.filter_out_predicates is None:
351 options.filter_out_predicates = []
352 options.filter_out_predicates.append(toolchainbinaries.IsArmUntrustedFlavor)
354 if not options.allow_bionic:
355 if options.filter_out_predicates is None:
356 options.filter_out_predicates = []
357 options.filter_out_predicates.append(toolchainbinaries.IsBionicFlavor)
360 parser.error('Expecting only one version file.')
364 def ScriptDependencyTimestamp():
365 """Determine the timestamp for the most recently changed script."""
366 src_list = ['download_toolchains.py', 'download_utils.py',
367 'cygtar.py', 'http_download.py']
368 srcs = [os.path.join(SCRIPT_DIR, src) for src in src_list]
372 src_times.append(os.stat(src).st_mtime)
373 return sorted(src_times)[-1]
377 script_time = ScriptDependencyTimestamp()
378 options, version_files = ParseArgs(args)
380 # If not provided, default to native_client/toolchain_versions.txt
381 if not version_files:
382 version_files = [os.path.join(PARENT_DIR, 'TOOL_REVISIONS')]
383 versions = LoadVersions(version_files[0])
386 print ' (Calculating, may take a second...)'
389 ShowUpdatedDEPS(options, versions)
393 platform = download_utils.PlatformName()
394 arch = download_utils.ArchName()
396 for flavor in toolchainbinaries.PLATFORM_MAPPING[platform][arch]
397 if IsFlavorNeeded(options, flavor)]
399 for flavor in flavors:
400 version = VersionSelect(versions, flavor)
401 urls = FlavorUrls(options, versions, flavor)
402 dst = FlavorOutDir(options, flavor)
403 hashes = FlavorHashes(versions, flavor)
404 flavor_name = FlavorName(flavor)
406 if version == 'latest':
407 print flavor + ': downloading latest version...'
413 if SyncFlavor(flavor, urls, dst, hashes, script_time, force=force,
414 keep=options.keep, verbose=options.verbose):
415 print flavor_name + ': updated to version ' + version + '.'
417 print flavor_name + ': already up to date.'
418 except download_utils.HashError, e:
421 print 'You probably want to update the %s hashes to:' % version_files[0]
422 print ' (Calculating, may take a second...)'
425 ShowUpdatedDEPS(options, versions)
431 if __name__ == '__main__':
432 sys.exit(main(sys.argv[1:]))