blob: a65737fdf84b586e04791b8c9e8b5c507c68effe [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassc05694f2013-04-03 11:07:16 +00002# Copyright (c) 2012 The Chromium OS Authors.
3#
Simon Glassc05694f2013-04-03 11:07:16 +00004
Simon Glasscc246fb2013-09-23 17:35:17 -06005import re
Simon Glassc05694f2013-04-03 11:07:16 +00006import glob
Simon Glass7e803e12014-12-01 17:34:06 -07007from HTMLParser import HTMLParser
Simon Glassc05694f2013-04-03 11:07:16 +00008import os
Simon Glass7e803e12014-12-01 17:34:06 -07009import sys
10import tempfile
11import urllib2
Simon Glassc05694f2013-04-03 11:07:16 +000012
13import bsettings
14import command
Simon Glass9f1ba0f2016-07-27 20:33:02 -060015import terminal
Simon Glassc05694f2013-04-03 11:07:16 +000016
Simon Glassf5902732016-03-12 18:50:32 -070017(PRIORITY_FULL_PREFIX, PRIORITY_PREFIX_GCC, PRIORITY_PREFIX_GCC_PATH,
18 PRIORITY_CALC) = range(4)
Simon Glass7b971292016-03-06 19:45:37 -070019
Simon Glass7e803e12014-12-01 17:34:06 -070020# Simple class to collect links from a page
21class MyHTMLParser(HTMLParser):
22 def __init__(self, arch):
23 """Create a new parser
24
25 After the parser runs, self.links will be set to a list of the links
26 to .xz archives found in the page, and self.arch_link will be set to
27 the one for the given architecture (or None if not found).
28
29 Args:
30 arch: Architecture to search for
31 """
32 HTMLParser.__init__(self)
33 self.arch_link = None
34 self.links = []
Daniel Schwierzeck40261f92018-05-10 07:15:53 -040035 self.re_arch = re.compile('[-_]%s-' % arch)
Simon Glass7e803e12014-12-01 17:34:06 -070036
37 def handle_starttag(self, tag, attrs):
38 if tag == 'a':
39 for tag, value in attrs:
40 if tag == 'href':
41 if value and value.endswith('.xz'):
42 self.links.append(value)
Daniel Schwierzeck40261f92018-05-10 07:15:53 -040043 if self.re_arch.search(value):
Simon Glass7e803e12014-12-01 17:34:06 -070044 self.arch_link = value
45
46
Simon Glassc05694f2013-04-03 11:07:16 +000047class Toolchain:
48 """A single toolchain
49
50 Public members:
51 gcc: Full path to C compiler
52 path: Directory path containing C compiler
53 cross: Cross compile string, e.g. 'arm-linux-'
54 arch: Architecture of toolchain as determined from the first
55 component of the filename. E.g. arm-linux-gcc becomes arm
Simon Glass7b971292016-03-06 19:45:37 -070056 priority: Toolchain priority (0=highest, 20=lowest)
Simon Glassf77ca5b2019-01-07 16:44:20 -070057 override_toolchain: Toolchain to use for sandbox, overriding the normal
58 one
Simon Glassc05694f2013-04-03 11:07:16 +000059 """
Simon Glassc6cdd3e2016-03-06 19:45:38 -070060 def __init__(self, fname, test, verbose=False, priority=PRIORITY_CALC,
Simon Glassf77ca5b2019-01-07 16:44:20 -070061 arch=None, override_toolchain=None):
Simon Glassc05694f2013-04-03 11:07:16 +000062 """Create a new toolchain object.
63
64 Args:
65 fname: Filename of the gcc component
66 test: True to run the toolchain to test it
Simon Glassd6ece322016-03-06 19:45:35 -070067 verbose: True to print out the information
Simon Glass7b971292016-03-06 19:45:37 -070068 priority: Priority to use for this toolchain, or PRIORITY_CALC to
69 calculate it
Simon Glassc05694f2013-04-03 11:07:16 +000070 """
71 self.gcc = fname
72 self.path = os.path.dirname(fname)
Simon Glassf77ca5b2019-01-07 16:44:20 -070073 self.override_toolchain = override_toolchain
Simon Glass28ed0062014-12-01 17:33:58 -070074
75 # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
76 # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
77 basename = os.path.basename(fname)
78 pos = basename.rfind('-')
79 self.cross = basename[:pos + 1] if pos != -1 else ''
80
81 # The architecture is the first part of the name
Simon Glassc05694f2013-04-03 11:07:16 +000082 pos = self.cross.find('-')
Simon Glassc6cdd3e2016-03-06 19:45:38 -070083 if arch:
84 self.arch = arch
85 else:
86 self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
Simon Glassf77ca5b2019-01-07 16:44:20 -070087 if self.arch == 'sandbox' and override_toolchain:
88 self.gcc = override_toolchain
Simon Glassc05694f2013-04-03 11:07:16 +000089
Simon Glassd48a46c2014-12-01 17:34:00 -070090 env = self.MakeEnvironment(False)
Simon Glassc05694f2013-04-03 11:07:16 +000091
92 # As a basic sanity check, run the C compiler with --version
93 cmd = [fname, '--version']
Simon Glass7b971292016-03-06 19:45:37 -070094 if priority == PRIORITY_CALC:
95 self.priority = self.GetPriority(fname)
96 else:
97 self.priority = priority
Simon Glassc05694f2013-04-03 11:07:16 +000098 if test:
Stephen Warren288d7672013-10-09 14:28:09 -060099 result = command.RunPipe([cmd], capture=True, env=env,
100 raise_on_error=False)
Simon Glassc05694f2013-04-03 11:07:16 +0000101 self.ok = result.return_code == 0
102 if verbose:
103 print 'Tool chain test: ',
104 if self.ok:
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700105 print "OK, arch='%s', priority %d" % (self.arch,
106 self.priority)
Simon Glassc05694f2013-04-03 11:07:16 +0000107 else:
108 print 'BAD'
109 print 'Command: ', cmd
110 print result.stdout
111 print result.stderr
112 else:
113 self.ok = True
Simon Glassc05694f2013-04-03 11:07:16 +0000114
115 def GetPriority(self, fname):
116 """Return the priority of the toolchain.
117
118 Toolchains are ranked according to their suitability by their
119 filename prefix.
120
121 Args:
122 fname: Filename of toolchain
123 Returns:
Simon Glass7b971292016-03-06 19:45:37 -0700124 Priority of toolchain, PRIORITY_CALC=highest, 20=lowest.
Simon Glassc05694f2013-04-03 11:07:16 +0000125 """
Masahiro Yamadafa25e1f2014-07-07 09:47:45 +0900126 priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
Tom Rini6abc9c82017-04-14 19:47:50 -0400127 '-none-linux-gnueabi', '-none-linux-gnueabihf', '-uclinux',
128 '-none-eabi', '-gentoo-linux-gnu', '-linux-gnueabi',
129 '-linux-gnueabihf', '-le-linux', '-uclinux']
Simon Glassc05694f2013-04-03 11:07:16 +0000130 for prio in range(len(priority_list)):
131 if priority_list[prio] in fname:
Simon Glass7b971292016-03-06 19:45:37 -0700132 return PRIORITY_CALC + prio
133 return PRIORITY_CALC + prio
Simon Glassc05694f2013-04-03 11:07:16 +0000134
York Sunfb197a82016-10-04 14:33:51 -0700135 def GetWrapper(self, show_warning=True):
136 """Get toolchain wrapper from the setting file.
137 """
Simon Glass45572632019-01-07 16:44:24 -0700138 value = ''
139 for name, value in bsettings.GetItems('toolchain-wrapper'):
York Sunfb197a82016-10-04 14:33:51 -0700140 if not value:
141 print "Warning: Wrapper not found"
142 if value:
143 value = value + ' '
144
145 return value
146
Simon Glassd48a46c2014-12-01 17:34:00 -0700147 def MakeEnvironment(self, full_path):
Simon Glassc05694f2013-04-03 11:07:16 +0000148 """Returns an environment for using the toolchain.
149
Simon Glassd48a46c2014-12-01 17:34:00 -0700150 Thie takes the current environment and adds CROSS_COMPILE so that
Daniel Schwierzeckc9e5f0c2017-06-08 03:07:08 +0200151 the tool chain will operate correctly. This also disables localized
152 output and possibly unicode encoded output of all build tools by
153 adding LC_ALL=C.
Simon Glassd48a46c2014-12-01 17:34:00 -0700154
155 Args:
156 full_path: Return the full path in CROSS_COMPILE and don't set
157 PATH
Simon Glassf77ca5b2019-01-07 16:44:20 -0700158 Returns:
159 Dict containing the environemnt to use. This is based on the current
160 environment, with changes as needed to CROSS_COMPILE, PATH and
161 LC_ALL.
Simon Glassc05694f2013-04-03 11:07:16 +0000162 """
163 env = dict(os.environ)
York Sunfb197a82016-10-04 14:33:51 -0700164 wrapper = self.GetWrapper()
165
Simon Glassf77ca5b2019-01-07 16:44:20 -0700166 if self.override_toolchain:
167 # We'll use MakeArgs() to provide this
168 pass
169 elif full_path:
York Sunfb197a82016-10-04 14:33:51 -0700170 env['CROSS_COMPILE'] = wrapper + os.path.join(self.path, self.cross)
Simon Glassd48a46c2014-12-01 17:34:00 -0700171 else:
York Sunfb197a82016-10-04 14:33:51 -0700172 env['CROSS_COMPILE'] = wrapper + self.cross
Simon Glassd48a46c2014-12-01 17:34:00 -0700173 env['PATH'] = self.path + ':' + env['PATH']
174
Daniel Schwierzeckc9e5f0c2017-06-08 03:07:08 +0200175 env['LC_ALL'] = 'C'
176
Simon Glassc05694f2013-04-03 11:07:16 +0000177 return env
178
Simon Glassf77ca5b2019-01-07 16:44:20 -0700179 def MakeArgs(self):
180 """Create the 'make' arguments for a toolchain
181
182 This is only used when the toolchain is being overridden. Since the
183 U-Boot Makefile sets CC and HOSTCC explicitly we cannot rely on the
184 environment (and MakeEnvironment()) to override these values. This
185 function returns the arguments to accomplish this.
186
187 Returns:
188 List of arguments to pass to 'make'
189 """
190 if self.override_toolchain:
191 return ['HOSTCC=%s' % self.override_toolchain,
192 'CC=%s' % self.override_toolchain]
193 return []
194
Simon Glassc05694f2013-04-03 11:07:16 +0000195
196class Toolchains:
197 """Manage a list of toolchains for building U-Boot
198
199 We select one toolchain for each architecture type
200
201 Public members:
202 toolchains: Dict of Toolchain objects, keyed by architecture name
Simon Glassf5902732016-03-12 18:50:32 -0700203 prefixes: Dict of prefixes to check, keyed by architecture. This can
204 be a full path and toolchain prefix, for example
205 {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
206 something on the search path, for example
207 {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
Simon Glassc05694f2013-04-03 11:07:16 +0000208 paths: List of paths to check for toolchains (may contain wildcards)
209 """
210
Simon Glassf77ca5b2019-01-07 16:44:20 -0700211 def __init__(self, override_toolchain=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000212 self.toolchains = {}
Simon Glassf5902732016-03-12 18:50:32 -0700213 self.prefixes = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000214 self.paths = []
Simon Glassf77ca5b2019-01-07 16:44:20 -0700215 self.override_toolchain = override_toolchain
Simon Glassed098bb2014-09-05 19:00:13 -0600216 self._make_flags = dict(bsettings.GetItems('make-flags'))
217
Simon Glassf38a6422016-07-27 20:33:01 -0600218 def GetPathList(self, show_warning=True):
Simon Glass7e803e12014-12-01 17:34:06 -0700219 """Get a list of available toolchain paths
220
Simon Glassf38a6422016-07-27 20:33:01 -0600221 Args:
222 show_warning: True to show a warning if there are no tool chains.
223
Simon Glass7e803e12014-12-01 17:34:06 -0700224 Returns:
225 List of strings, each a path to a toolchain mentioned in the
226 [toolchain] section of the settings file.
227 """
Simon Glasscc246fb2013-09-23 17:35:17 -0600228 toolchains = bsettings.GetItems('toolchain')
Simon Glassf38a6422016-07-27 20:33:01 -0600229 if show_warning and not toolchains:
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600230 print ("Warning: No tool chains. Please run 'buildman "
231 "--fetch-arch all' to download all available toolchains, or "
232 "add a [toolchain] section to your buildman config file "
233 "%s. See README for details" %
234 bsettings.config_fname)
Simon Glasscc246fb2013-09-23 17:35:17 -0600235
Simon Glass7e803e12014-12-01 17:34:06 -0700236 paths = []
Simon Glasscc246fb2013-09-23 17:35:17 -0600237 for name, value in toolchains:
Simon Glassc05694f2013-04-03 11:07:16 +0000238 if '*' in value:
Simon Glass7e803e12014-12-01 17:34:06 -0700239 paths += glob.glob(value)
Simon Glassc05694f2013-04-03 11:07:16 +0000240 else:
Simon Glass7e803e12014-12-01 17:34:06 -0700241 paths.append(value)
242 return paths
243
Simon Glassf38a6422016-07-27 20:33:01 -0600244 def GetSettings(self, show_warning=True):
245 """Get toolchain settings from the settings file.
246
247 Args:
248 show_warning: True to show a warning if there are no tool chains.
249 """
250 self.prefixes = bsettings.GetItems('toolchain-prefix')
251 self.paths += self.GetPathList(show_warning)
Simon Glassc05694f2013-04-03 11:07:16 +0000252
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700253 def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
254 arch=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000255 """Add a toolchain to our list
256
257 We select the given toolchain as our preferred one for its
258 architecture if it is a higher priority than the others.
259
260 Args:
261 fname: Filename of toolchain's gcc driver
262 test: True to run the toolchain to test it
Simon Glass7b971292016-03-06 19:45:37 -0700263 priority: Priority to use for this toolchain
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700264 arch: Toolchain architecture, or None if not known
Simon Glassc05694f2013-04-03 11:07:16 +0000265 """
Simon Glassf77ca5b2019-01-07 16:44:20 -0700266 toolchain = Toolchain(fname, test, verbose, priority, arch,
267 self.override_toolchain)
Simon Glassc05694f2013-04-03 11:07:16 +0000268 add_it = toolchain.ok
269 if toolchain.arch in self.toolchains:
270 add_it = (toolchain.priority <
271 self.toolchains[toolchain.arch].priority)
272 if add_it:
273 self.toolchains[toolchain.arch] = toolchain
Simon Glass7b971292016-03-06 19:45:37 -0700274 elif verbose:
275 print ("Toolchain '%s' at priority %d will be ignored because "
276 "another toolchain for arch '%s' has priority %d" %
277 (toolchain.gcc, toolchain.priority, toolchain.arch,
278 self.toolchains[toolchain.arch].priority))
Simon Glassc05694f2013-04-03 11:07:16 +0000279
Simon Glass7e803e12014-12-01 17:34:06 -0700280 def ScanPath(self, path, verbose):
281 """Scan a path for a valid toolchain
282
283 Args:
284 path: Path to scan
285 verbose: True to print out progress information
286 Returns:
287 Filename of C compiler if found, else None
288 """
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100289 fnames = []
Simon Glass7e803e12014-12-01 17:34:06 -0700290 for subdir in ['.', 'bin', 'usr/bin']:
291 dirname = os.path.join(path, subdir)
292 if verbose: print " - looking in '%s'" % dirname
293 for fname in glob.glob(dirname + '/*gcc'):
294 if verbose: print " - found '%s'" % fname
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100295 fnames.append(fname)
296 return fnames
Simon Glass7e803e12014-12-01 17:34:06 -0700297
Simon Glassf5902732016-03-12 18:50:32 -0700298 def ScanPathEnv(self, fname):
299 """Scan the PATH environment variable for a given filename.
300
301 Args:
302 fname: Filename to scan for
303 Returns:
304 List of matching pathanames, or [] if none
305 """
306 pathname_list = []
307 for path in os.environ["PATH"].split(os.pathsep):
308 path = path.strip('"')
309 pathname = os.path.join(path, fname)
310 if os.path.exists(pathname):
311 pathname_list.append(pathname)
312 return pathname_list
Simon Glass7e803e12014-12-01 17:34:06 -0700313
Simon Glassc05694f2013-04-03 11:07:16 +0000314 def Scan(self, verbose):
315 """Scan for available toolchains and select the best for each arch.
316
317 We look for all the toolchains we can file, figure out the
318 architecture for each, and whether it works. Then we select the
319 highest priority toolchain for each arch.
320
321 Args:
322 verbose: True to print out progress information
323 """
324 if verbose: print 'Scanning for tool chains'
Simon Glassf5902732016-03-12 18:50:32 -0700325 for name, value in self.prefixes:
326 if verbose: print " - scanning prefix '%s'" % value
327 if os.path.exists(value):
328 self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
329 continue
330 fname = value + 'gcc'
331 if os.path.exists(fname):
332 self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
333 continue
334 fname_list = self.ScanPathEnv(fname)
335 for f in fname_list:
336 self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
337 if not fname_list:
338 raise ValueError, ("No tool chain found for prefix '%s'" %
339 value)
Simon Glassc05694f2013-04-03 11:07:16 +0000340 for path in self.paths:
341 if verbose: print " - scanning path '%s'" % path
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100342 fnames = self.ScanPath(path, verbose)
343 for fname in fnames:
Simon Glass7e803e12014-12-01 17:34:06 -0700344 self.Add(fname, True, verbose)
Simon Glassc05694f2013-04-03 11:07:16 +0000345
346 def List(self):
347 """List out the selected toolchains for each architecture"""
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600348 col = terminal.Color()
349 print col.Color(col.BLUE, 'List of available toolchains (%d):' %
350 len(self.toolchains))
Simon Glassc05694f2013-04-03 11:07:16 +0000351 if len(self.toolchains):
352 for key, value in sorted(self.toolchains.iteritems()):
353 print '%-10s: %s' % (key, value.gcc)
354 else:
355 print 'None'
356
357 def Select(self, arch):
358 """Returns the toolchain for a given architecture
359
360 Args:
361 args: Name of architecture (e.g. 'arm', 'ppc_8xx')
362
363 returns:
364 toolchain object, or None if none found
365 """
Simon Glassc1528c12014-12-01 17:34:05 -0700366 for tag, value in bsettings.GetItems('toolchain-alias'):
367 if arch == tag:
368 for alias in value.split():
369 if alias in self.toolchains:
370 return self.toolchains[alias]
Simon Glassc05694f2013-04-03 11:07:16 +0000371
372 if not arch in self.toolchains:
373 raise ValueError, ("No tool chain found for arch '%s'" % arch)
374 return self.toolchains[arch]
Simon Glasscc246fb2013-09-23 17:35:17 -0600375
376 def ResolveReferences(self, var_dict, args):
377 """Resolve variable references in a string
378
379 This converts ${blah} within the string to the value of blah.
380 This function works recursively.
381
382 Args:
383 var_dict: Dictionary containing variables and their values
384 args: String containing make arguments
385 Returns:
386 Resolved string
387
388 >>> bsettings.Setup()
389 >>> tcs = Toolchains()
390 >>> tcs.Add('fred', False)
391 >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
392 'second' : '2nd'}
393 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
394 'this=OBLIQUE_set'
395 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
396 'this=OBLIQUE_setfi2ndrstnd'
397 """
Simon Glass53e189d2014-08-28 09:43:40 -0600398 re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
Simon Glasscc246fb2013-09-23 17:35:17 -0600399
400 while True:
401 m = re_var.search(args)
402 if not m:
403 break
404 lookup = m.group(0)[2:-1]
405 value = var_dict.get(lookup, '')
406 args = args[:m.start(0)] + value + args[m.end(0):]
407 return args
408
409 def GetMakeArguments(self, board):
410 """Returns 'make' arguments for a given board
411
412 The flags are in a section called 'make-flags'. Flags are named
413 after the target they represent, for example snapper9260=TESTING=1
414 will pass TESTING=1 to make when building the snapper9260 board.
415
416 References to other boards can be added in the string also. For
417 example:
418
419 [make-flags]
420 at91-boards=ENABLE_AT91_TEST=1
421 snapper9260=${at91-boards} BUILD_TAG=442
422 snapper9g45=${at91-boards} BUILD_TAG=443
423
424 This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
425 and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
426
427 A special 'target' variable is set to the board target.
428
429 Args:
430 board: Board object for the board to check.
431 Returns:
432 'make' flags for that board, or '' if none
433 """
434 self._make_flags['target'] = board.target
435 arg_str = self.ResolveReferences(self._make_flags,
436 self._make_flags.get(board.target, ''))
437 args = arg_str.split(' ')
438 i = 0
439 while i < len(args):
440 if not args[i]:
441 del args[i]
442 else:
443 i += 1
444 return args
Simon Glass7e803e12014-12-01 17:34:06 -0700445
446 def LocateArchUrl(self, fetch_arch):
447 """Find a toolchain available online
448
449 Look in standard places for available toolchains. At present the
450 only standard place is at kernel.org.
451
452 Args:
453 arch: Architecture to look for, or 'list' for all
454 Returns:
455 If fetch_arch is 'list', a tuple:
456 Machine architecture (e.g. x86_64)
457 List of toolchains
458 else
459 URL containing this toolchain, if avaialble, else None
460 """
461 arch = command.OutputOneLine('uname', '-m')
462 base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
Daniel Schwierzeck40261f92018-05-10 07:15:53 -0400463 versions = ['7.3.0', '6.4.0', '4.9.4']
Simon Glass7e803e12014-12-01 17:34:06 -0700464 links = []
465 for version in versions:
466 url = '%s/%s/%s/' % (base, arch, version)
467 print 'Checking: %s' % url
468 response = urllib2.urlopen(url)
469 html = response.read()
470 parser = MyHTMLParser(fetch_arch)
471 parser.feed(html)
472 if fetch_arch == 'list':
473 links += parser.links
474 elif parser.arch_link:
475 return url + parser.arch_link
476 if fetch_arch == 'list':
477 return arch, links
478 return None
479
480 def Download(self, url):
481 """Download a file to a temporary directory
482
483 Args:
484 url: URL to download
485 Returns:
486 Tuple:
487 Temporary directory name
488 Full path to the downloaded archive file in that directory,
489 or None if there was an error while downloading
490 """
Simon Glassd6ece322016-03-06 19:45:35 -0700491 print 'Downloading: %s' % url
Simon Glass7e803e12014-12-01 17:34:06 -0700492 leaf = url.split('/')[-1]
493 tmpdir = tempfile.mkdtemp('.buildman')
494 response = urllib2.urlopen(url)
495 fname = os.path.join(tmpdir, leaf)
496 fd = open(fname, 'wb')
497 meta = response.info()
Simon Glassd6ece322016-03-06 19:45:35 -0700498 size = int(meta.getheaders('Content-Length')[0])
Simon Glass7e803e12014-12-01 17:34:06 -0700499 done = 0
500 block_size = 1 << 16
501 status = ''
502
503 # Read the file in chunks and show progress as we go
504 while True:
505 buffer = response.read(block_size)
506 if not buffer:
507 print chr(8) * (len(status) + 1), '\r',
508 break
509
510 done += len(buffer)
511 fd.write(buffer)
Simon Glassd6ece322016-03-06 19:45:35 -0700512 status = r'%10d MiB [%3d%%]' % (done / 1024 / 1024,
Simon Glass7e803e12014-12-01 17:34:06 -0700513 done * 100 / size)
514 status = status + chr(8) * (len(status) + 1)
515 print status,
516 sys.stdout.flush()
517 fd.close()
518 if done != size:
519 print 'Error, failed to download'
520 os.remove(fname)
521 fname = None
522 return tmpdir, fname
523
524 def Unpack(self, fname, dest):
525 """Unpack a tar file
526
527 Args:
528 fname: Filename to unpack
529 dest: Destination directory
530 Returns:
531 Directory name of the first entry in the archive, without the
532 trailing /
533 """
534 stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
Trevor Woernere0557a72018-11-21 03:31:12 -0500535 dirs = stdout.splitlines()[1].split('/')[:2]
536 return '/'.join(dirs)
Simon Glass7e803e12014-12-01 17:34:06 -0700537
538 def TestSettingsHasPath(self, path):
Simon Glass43ea1282016-07-27 20:33:03 -0600539 """Check if buildman will find this toolchain
Simon Glass7e803e12014-12-01 17:34:06 -0700540
541 Returns:
542 True if the path is in settings, False if not
543 """
Simon Glassf38a6422016-07-27 20:33:01 -0600544 paths = self.GetPathList(False)
Simon Glass7e803e12014-12-01 17:34:06 -0700545 return path in paths
546
547 def ListArchs(self):
548 """List architectures with available toolchains to download"""
549 host_arch, archives = self.LocateArchUrl('list')
Trevor Woerner10a51622018-11-21 03:31:13 -0500550 re_arch = re.compile('[-a-z0-9.]*[-_]([^-]*)-.*')
Simon Glass7e803e12014-12-01 17:34:06 -0700551 arch_set = set()
552 for archive in archives:
553 # Remove the host architecture from the start
554 arch = re_arch.match(archive[len(host_arch):])
555 if arch:
Trevor Woerner10a51622018-11-21 03:31:13 -0500556 if arch.group(1) != '2.0' and arch.group(1) != '64':
557 arch_set.add(arch.group(1))
Simon Glass7e803e12014-12-01 17:34:06 -0700558 return sorted(arch_set)
559
560 def FetchAndInstall(self, arch):
561 """Fetch and install a new toolchain
562
563 arch:
564 Architecture to fetch, or 'list' to list
565 """
566 # Fist get the URL for this architecture
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600567 col = terminal.Color()
568 print col.Color(col.BLUE, "Downloading toolchain for arch '%s'" % arch)
Simon Glass7e803e12014-12-01 17:34:06 -0700569 url = self.LocateArchUrl(arch)
570 if not url:
571 print ("Cannot find toolchain for arch '%s' - use 'list' to list" %
572 arch)
573 return 2
574 home = os.environ['HOME']
575 dest = os.path.join(home, '.buildman-toolchains')
576 if not os.path.exists(dest):
577 os.mkdir(dest)
578
579 # Download the tar file for this toolchain and unpack it
580 tmpdir, tarfile = self.Download(url)
581 if not tarfile:
582 return 1
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600583 print col.Color(col.GREEN, 'Unpacking to: %s' % dest),
Simon Glass7e803e12014-12-01 17:34:06 -0700584 sys.stdout.flush()
585 path = self.Unpack(tarfile, dest)
586 os.remove(tarfile)
587 os.rmdir(tmpdir)
588 print
589
590 # Check that the toolchain works
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600591 print col.Color(col.GREEN, 'Testing')
Simon Glass7e803e12014-12-01 17:34:06 -0700592 dirpath = os.path.join(dest, path)
Simon Glassf6757502015-03-02 17:05:15 -0700593 compiler_fname_list = self.ScanPath(dirpath, True)
594 if not compiler_fname_list:
Simon Glass7e803e12014-12-01 17:34:06 -0700595 print 'Could not locate C compiler - fetch failed.'
596 return 1
Simon Glassf6757502015-03-02 17:05:15 -0700597 if len(compiler_fname_list) != 1:
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600598 print col.Color(col.RED, 'Warning, ambiguous toolchains: %s' %
599 ', '.join(compiler_fname_list))
Simon Glassf6757502015-03-02 17:05:15 -0700600 toolchain = Toolchain(compiler_fname_list[0], True, True)
Simon Glass7e803e12014-12-01 17:34:06 -0700601
602 # Make sure that it will be found by buildman
603 if not self.TestSettingsHasPath(dirpath):
604 print ("Adding 'download' to config file '%s'" %
605 bsettings.config_fname)
Simon Glassf90e2912016-07-27 20:33:05 -0600606 bsettings.SetItem('toolchain', 'download', '%s/*/*' % dest)
Simon Glass7e803e12014-12-01 17:34:06 -0700607 return 0