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 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
19 import pynacl.download_utils
20 import pynacl.file_tools
21 import pynacl.platform
23 import toolchainbinaries
26 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
27 PARENT_DIR = os.path.dirname(SCRIPT_DIR)
29 SHA1_IN_FILENAME = re.compile('.*_[0-9a-f]{40}$')
32 def LoadVersions(filepath):
33 """Load version data from specified filepath into a dictionary.
36 filepath: path to the file which will be loaded.
38 A dictionary of KEY:VALUE pairs.
41 version_lines = open(filepath, 'r').readlines()
42 for line_num, line in enumerate(version_lines, start=1):
44 if line.startswith('#'):
51 raise RuntimeError('Expecting KEY=VALUE in line %d:\n\t>>%s<<' %
54 key, val = line.split('=', 1)
59 def VersionSelect(versions, flavor):
60 """Determine svn revision based on version data and flavor.
63 versions: version data loaded from file.
66 An svn version number (or other version string).
69 if isinstance(flavor, tuple):
70 ids = [versions[i] for i in flavor[1:]]
72 if toolchainbinaries.IsBionicFlavor(flavor):
73 return versions['BIONIC_VERSION']
74 if toolchainbinaries.IsPnaclFlavor(flavor):
75 return versions['PNACL_VERSION']
76 if toolchainbinaries.IsX86Flavor(flavor):
77 if toolchainbinaries.IsNotNaClNewlibFlavor(flavor):
78 return versions['GLIBC_VERSION']
80 return versions['NEWLIB_VERSION']
81 if toolchainbinaries.IsArmTrustedFlavor(flavor):
82 return versions['ARM_TRUSTED_VERSION']
83 raise Exception('Unknown flavor "%s"' % flavor)
87 """Generate the name of the key for this flavor's hash.
92 The string key for the hash.
94 return 'NACL_TOOL_%s_HASH' % flavor.upper()
97 def HashSelect(versions, flavor):
98 """Determine expected hash value based on version data and flavor.
101 versions: version data loaded from file.
102 flavor: kind of tool.
106 return versions[HashKey(flavor)]
109 def IsFlavorNeeded(options, flavor):
110 if isinstance(flavor, tuple):
112 if options.filter_out_predicates:
113 for predicate in options.filter_out_predicates:
114 if predicate(flavor):
119 def FlavorOutDir(options, flavor):
120 """Given a flavor, decide where it should be extracted."""
121 if isinstance(flavor, tuple):
122 return toolchainbinaries.GetStandardToolchainFlavorDir(
123 options.toolchain_dir,
126 return toolchainbinaries.GetStandardToolchainFlavorDir(
127 options.toolchain_dir,
131 def FlavorName(flavor):
132 """Given a flavor, get a string name for it."""
133 if isinstance(flavor, tuple):
139 def FlavorComponentNames(flavor):
140 if isinstance(flavor, tuple):
146 def FlavorUrls(options, versions, flavor):
147 """Given a flavor, get a list of the URLs of its components."""
148 if isinstance(flavor, tuple):
149 ids = [versions[i] for i in flavor[1:]]
150 return [toolchainbinaries.EncodeToolchainUrl(
151 options.base_once_url, i, 'new') for i in ids]
153 return [toolchainbinaries.EncodeToolchainUrl(
154 options.base_url, VersionSelect(versions, flavor), flavor)]
157 def FlavorHashes(versions, flavor):
158 """Given a flavor, get the list of hashes of its components."""
159 if isinstance(flavor, tuple):
160 return [HashSelect(versions, i) for i in flavor[1:]]
162 return [HashSelect(versions, flavor)]
165 def GetUpdatedDEPS(options, versions):
166 """Return a suggested DEPS toolchain hash update for all platforms.
169 options: options from the command line.
172 for pm in toolchainbinaries.PLATFORM_MAPPING.itervalues():
173 for flavorlist in pm.itervalues():
174 for flavor in flavorlist:
175 if IsFlavorNeeded(options, flavor):
178 for flavor in flavors:
179 names = FlavorComponentNames(flavor)
180 urls = FlavorUrls(options, versions, flavor)
181 for name, url in zip(names, urls):
182 new_deps[name] = pynacl.download_utils.HashUrl(url)
186 def ShowUpdatedDEPS(options, versions):
187 """Print a suggested DEPS toolchain hash update for all platforms.
190 options: options from the command line.
192 for flavor, value in sorted(GetUpdatedDEPS(options, versions).iteritems()):
193 keyname = HashKey(flavor)
194 print '%s=%s' % (keyname, value)
198 def SyncFlavor(flavor, urls, dst, hashes, min_time, keep=False, force=False,
200 """Sync a flavor of the nacl toolchain
203 flavor: short directory name of the toolchain flavor.
204 urls: urls to download the toolchain flavor from.
205 dst: destination directory for the toolchain.
206 hashes: expected hashes of the toolchain.
208 if isinstance(flavor, tuple):
209 flavor_name = flavor[0]
213 toolchain_dir = os.path.join(PARENT_DIR, 'toolchain')
214 if not os.path.exists(toolchain_dir):
215 os.makedirs(toolchain_dir)
216 download_dir = os.path.join(toolchain_dir, '.tars')
218 prefix = 'tmp_unpacked_toolchain_'
221 # Attempt to cleanup.
222 for path in os.listdir(toolchain_dir):
223 if path.startswith(prefix) and path.endswith(suffix):
224 full_path = os.path.join(toolchain_dir, path)
226 print 'Cleaning up %s...' % full_path
227 pynacl.file_tools.RemoveDir(full_path)
229 print 'Failed cleanup with: ' + str(e)
231 # If we are forcing a sync, then ignore stamp
241 for url, hash_val in zip(urls, hashes):
242 # Build the tarfile name from the url
243 # http://foo..../bar.tar.gz -> bar
244 filepath, ext = url.split('/')[-1].split('.', 1)
245 # For filenames containing _SHA1s, drop the sha1 part.
246 if SHA1_IN_FILENAME.match(filepath) is not None:
247 filepath = filepath.rsplit('_', 1)[0]
248 # Put it in the download dir and add back extension.
249 filepath = os.path.join(download_dir, '.'.join([filepath, ext]))
250 filepaths.append(filepath)
251 # If we did not need to synchronize, then we are done
252 if pynacl.download_utils.SyncURL(url, filepath, stamp_dir=stamp_dir,
253 min_time=min_time, hash_val=hash_val,
255 keep=keep, verbose=verbose):
262 # Compute the new hashes for each file.
264 for filepath in filepaths:
265 new_hashes.append(pynacl.download_utils.HashFile(filepath))
267 untar_dir = tempfile.mkdtemp(
268 suffix=suffix, prefix=prefix, dir=toolchain_dir)
271 tar_file = os.path.basename(filepath)
272 rel_dest = os.path.relpath(dst, toolchain_dir)
273 print '%s: Extracting "%s" -> "%s"...' % (flavor_name, tar_file, rel_dest)
275 for filepath in filepaths:
276 tar = cygtar.CygTar(filepath, 'r:*', verbose=verbose)
288 # TODO(bradnelson_): get rid of this when toolchain tarballs flattened.
289 if 'bionic' in flavor:
290 src = os.path.join(untar_dir, flavor)
291 elif isinstance(flavor, tuple) or 'arm' in flavor or 'pnacl' in flavor:
292 src = os.path.join(untar_dir)
293 elif 'newlib' in flavor:
294 src = os.path.join(untar_dir, 'sdk', 'nacl-sdk')
296 src = os.path.join(untar_dir, 'toolchain', flavor)
297 pynacl.file_tools.MoveDirCleanly(src, dst)
300 pynacl.file_tools.RemoveDir(untar_dir)
302 print 'Failed cleanup with: ' + str(e)
303 print 'Continuing on original exception...'
304 pynacl.download_utils.WriteSourceStamp(dst, '\n'.join(urls))
305 pynacl.download_utils.WriteHashStamp(dst, '\n'.join(new_hashes))
310 parser = optparse.OptionParser()
312 '-b', '--base-url', dest='base_url',
313 default=toolchainbinaries.BASE_DOWNLOAD_URL,
314 help='base url to download from')
316 '--base-once-url', dest='base_once_url',
317 default=toolchainbinaries.BASE_ONCE_DOWNLOAD_URL,
318 help='base url to download new toolchain artifacts from')
320 '-c', '--hashes', dest='hashes',
323 help='Calculate hashes.')
325 '-k', '--keep', dest='keep',
328 help='Keep the downloaded tarballs.')
330 '-q', '--quiet', dest='verbose',
332 action='store_false',
333 help='Produce less output.')
335 '--toolchain-dir', dest='toolchain_dir',
336 default=os.path.join(PARENT_DIR, 'toolchain'),
337 help='(optional) location of toolchain directory')
339 '--nacl-newlib-only', dest='filter_out_predicates',
340 action='append_const', const=toolchainbinaries.IsNotNaClNewlibFlavor,
341 help='download only the non-pnacl newlib toolchain')
343 '--allow-bionic', dest='allow_bionic', action='store_true',
345 help='Allow download of bionic toolchain.')
347 '--no-pnacl', dest='filter_out_predicates', action='append_const',
348 const=toolchainbinaries.IsPnaclFlavor,
349 help='Filter out PNaCl toolchains.')
351 '--no-x86', dest='filter_out_predicates', action='append_const',
352 const=toolchainbinaries.IsX86Flavor,
353 help='Filter out x86 toolchains.')
355 '--arm-untrusted', dest='arm_untrusted', action='store_true',
356 default=False, help='Add arm untrusted toolchains.')
358 '--no-pnacl-translator', dest='filter_out_predicates',
359 action='append_const',
360 const=toolchainbinaries.IsSandboxedTranslatorFlavor,
361 help='Filter out PNaCl sandboxed translator.')
363 '--no-arm-trusted', dest='filter_out_predicates', action='append_const',
364 const=toolchainbinaries.IsArmTrustedFlavor,
365 help='Filter out trusted arm toolchains.')
366 options, args = parser.parse_args(args)
368 if not options.arm_untrusted:
369 if options.filter_out_predicates is None:
370 options.filter_out_predicates = []
371 options.filter_out_predicates.append(toolchainbinaries.IsArmUntrustedFlavor)
373 if not options.allow_bionic:
374 if options.filter_out_predicates is None:
375 options.filter_out_predicates = []
376 options.filter_out_predicates.append(toolchainbinaries.IsBionicFlavor)
379 parser.error('Expecting only one version file.')
383 def ScriptDependencyTimestamp():
384 """Determine the timestamp for the most recently changed script."""
385 src_list = ['download_toolchains.py', '../pynacl/download_utils.py',
386 '../pynacl/file_tools.py', '../pynacl/platform.py',
387 'cygtar.py', '../pynacl/http_download.py']
388 srcs = [os.path.join(SCRIPT_DIR, src) for src in src_list]
392 src_times.append(os.stat(src).st_mtime)
393 return sorted(src_times)[-1]
397 script_time = ScriptDependencyTimestamp()
398 options, version_files = ParseArgs(args)
400 # If not provided, default to native_client/toolchain_versions.txt
401 if not version_files:
402 version_files = [os.path.join(PARENT_DIR, 'TOOL_REVISIONS')]
403 versions = LoadVersions(version_files[0])
406 print ' (Calculating, may take a second...)'
409 ShowUpdatedDEPS(options, versions)
413 host_os = pynacl.platform.GetOS()
414 arch = pynacl.platform.GetArch()
415 platform_mapping = toolchainbinaries.PLATFORM_MAPPING
417 for flavor in platform_mapping[host_os][arch]
418 if IsFlavorNeeded(options, flavor)]
420 for flavor in flavors:
421 version = VersionSelect(versions, flavor)
422 urls = FlavorUrls(options, versions, flavor)
423 dst = FlavorOutDir(options, flavor)
424 hashes = FlavorHashes(versions, flavor)
425 flavor_name = FlavorName(flavor)
427 if version == 'latest':
428 print flavor + ': downloading latest version...'
434 if SyncFlavor(flavor, urls, dst, hashes, script_time, force=force,
435 keep=options.keep, verbose=options.verbose):
436 print flavor_name + ': updated to version ' + version + '.'
438 print flavor_name + ': already up to date.'
439 except pynacl.download_utils.HashError, e:
442 print 'You probably want to update the %s hashes to:' % version_files[0]
443 print ' (Calculating, may take a second...)'
446 ShowUpdatedDEPS(options, versions)
452 if __name__ == '__main__':
453 sys.exit(main(sys.argv[1:]))