blob: cc26e2ede5737bb0814fda9216cde42ee0faf73f [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassfc3fe1c2013-04-03 11:07:16 +00002# Copyright (c) 2012 The Chromium OS Authors.
3#
Simon Glassfc3fe1c2013-04-03 11:07:16 +00004
Simon Glass4281ad82013-09-23 17:35:17 -06005import re
Simon Glassfc3fe1c2013-04-03 11:07:16 +00006import glob
Simon Glassc05aa032019-10-31 07:42:53 -06007from html.parser import HTMLParser
Simon Glassfc3fe1c2013-04-03 11:07:16 +00008import os
Simon Glass827e37b2014-12-01 17:34:06 -07009import sys
10import tempfile
Simon Glassc05aa032019-10-31 07:42:53 -060011import urllib.request, urllib.error, urllib.parse
Simon Glassfc3fe1c2013-04-03 11:07:16 +000012
13import bsettings
14import command
Simon Glass713bea32016-07-27 20:33:02 -060015import terminal
Simon Glassc05aa032019-10-31 07:42:53 -060016import tools
Simon Glassfc3fe1c2013-04-03 11:07:16 +000017
Simon Glass17bce662016-03-12 18:50:32 -070018(PRIORITY_FULL_PREFIX, PRIORITY_PREFIX_GCC, PRIORITY_PREFIX_GCC_PATH,
Simon Glassc05aa032019-10-31 07:42:53 -060019 PRIORITY_CALC) = list(range(4))
Simon Glassff690df2016-03-06 19:45:37 -070020
Simon Glass827e37b2014-12-01 17:34:06 -070021# Simple class to collect links from a page
22class MyHTMLParser(HTMLParser):
23 def __init__(self, arch):
24 """Create a new parser
25
26 After the parser runs, self.links will be set to a list of the links
27 to .xz archives found in the page, and self.arch_link will be set to
28 the one for the given architecture (or None if not found).
29
30 Args:
31 arch: Architecture to search for
32 """
33 HTMLParser.__init__(self)
34 self.arch_link = None
35 self.links = []
Daniel Schwierzeck4c58d272018-05-10 07:15:53 -040036 self.re_arch = re.compile('[-_]%s-' % arch)
Simon Glass827e37b2014-12-01 17:34:06 -070037
38 def handle_starttag(self, tag, attrs):
39 if tag == 'a':
40 for tag, value in attrs:
41 if tag == 'href':
42 if value and value.endswith('.xz'):
43 self.links.append(value)
Daniel Schwierzeck4c58d272018-05-10 07:15:53 -040044 if self.re_arch.search(value):
Simon Glass827e37b2014-12-01 17:34:06 -070045 self.arch_link = value
46
47
Simon Glassfc3fe1c2013-04-03 11:07:16 +000048class Toolchain:
49 """A single toolchain
50
51 Public members:
52 gcc: Full path to C compiler
53 path: Directory path containing C compiler
54 cross: Cross compile string, e.g. 'arm-linux-'
55 arch: Architecture of toolchain as determined from the first
56 component of the filename. E.g. arm-linux-gcc becomes arm
Simon Glassff690df2016-03-06 19:45:37 -070057 priority: Toolchain priority (0=highest, 20=lowest)
Simon Glass00beb242019-01-07 16:44:20 -070058 override_toolchain: Toolchain to use for sandbox, overriding the normal
59 one
Simon Glassfc3fe1c2013-04-03 11:07:16 +000060 """
Simon Glass608e3992016-03-06 19:45:38 -070061 def __init__(self, fname, test, verbose=False, priority=PRIORITY_CALC,
Simon Glass00beb242019-01-07 16:44:20 -070062 arch=None, override_toolchain=None):
Simon Glassfc3fe1c2013-04-03 11:07:16 +000063 """Create a new toolchain object.
64
65 Args:
66 fname: Filename of the gcc component
67 test: True to run the toolchain to test it
Simon Glassad24eba2016-03-06 19:45:35 -070068 verbose: True to print out the information
Simon Glassff690df2016-03-06 19:45:37 -070069 priority: Priority to use for this toolchain, or PRIORITY_CALC to
70 calculate it
Simon Glassfc3fe1c2013-04-03 11:07:16 +000071 """
72 self.gcc = fname
73 self.path = os.path.dirname(fname)
Simon Glass00beb242019-01-07 16:44:20 -070074 self.override_toolchain = override_toolchain
Simon Glassb5324122014-12-01 17:33:58 -070075
76 # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
77 # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
78 basename = os.path.basename(fname)
79 pos = basename.rfind('-')
80 self.cross = basename[:pos + 1] if pos != -1 else ''
81
82 # The architecture is the first part of the name
Simon Glassfc3fe1c2013-04-03 11:07:16 +000083 pos = self.cross.find('-')
Simon Glass608e3992016-03-06 19:45:38 -070084 if arch:
85 self.arch = arch
86 else:
87 self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
Simon Glass00beb242019-01-07 16:44:20 -070088 if self.arch == 'sandbox' and override_toolchain:
89 self.gcc = override_toolchain
Simon Glassfc3fe1c2013-04-03 11:07:16 +000090
Simon Glassbb1501f2014-12-01 17:34:00 -070091 env = self.MakeEnvironment(False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +000092
93 # As a basic sanity check, run the C compiler with --version
94 cmd = [fname, '--version']
Simon Glassff690df2016-03-06 19:45:37 -070095 if priority == PRIORITY_CALC:
96 self.priority = self.GetPriority(fname)
97 else:
98 self.priority = priority
Simon Glassfc3fe1c2013-04-03 11:07:16 +000099 if test:
Stephen Warren8bb2bdd2013-10-09 14:28:09 -0600100 result = command.RunPipe([cmd], capture=True, env=env,
101 raise_on_error=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000102 self.ok = result.return_code == 0
103 if verbose:
Simon Glassc05aa032019-10-31 07:42:53 -0600104 print('Tool chain test: ', end=' ')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000105 if self.ok:
Simon Glassc05aa032019-10-31 07:42:53 -0600106 print("OK, arch='%s', priority %d" % (self.arch,
107 self.priority))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000108 else:
Simon Glassc05aa032019-10-31 07:42:53 -0600109 print('BAD')
110 print('Command: ', cmd)
111 print(result.stdout)
112 print(result.stderr)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000113 else:
114 self.ok = True
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000115
116 def GetPriority(self, fname):
117 """Return the priority of the toolchain.
118
119 Toolchains are ranked according to their suitability by their
120 filename prefix.
121
122 Args:
123 fname: Filename of toolchain
124 Returns:
Simon Glassff690df2016-03-06 19:45:37 -0700125 Priority of toolchain, PRIORITY_CALC=highest, 20=lowest.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000126 """
Masahiro Yamada87082672014-07-07 09:47:45 +0900127 priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
Tom Rini546a6f32017-04-14 19:47:50 -0400128 '-none-linux-gnueabi', '-none-linux-gnueabihf', '-uclinux',
129 '-none-eabi', '-gentoo-linux-gnu', '-linux-gnueabi',
130 '-linux-gnueabihf', '-le-linux', '-uclinux']
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000131 for prio in range(len(priority_list)):
132 if priority_list[prio] in fname:
Simon Glassff690df2016-03-06 19:45:37 -0700133 return PRIORITY_CALC + prio
134 return PRIORITY_CALC + prio
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000135
York Sund5fe0132016-10-04 14:33:51 -0700136 def GetWrapper(self, show_warning=True):
137 """Get toolchain wrapper from the setting file.
138 """
Simon Glassccd29792019-01-07 16:44:24 -0700139 value = ''
140 for name, value in bsettings.GetItems('toolchain-wrapper'):
York Sund5fe0132016-10-04 14:33:51 -0700141 if not value:
Simon Glassc05aa032019-10-31 07:42:53 -0600142 print("Warning: Wrapper not found")
York Sund5fe0132016-10-04 14:33:51 -0700143 if value:
144 value = value + ' '
145
146 return value
147
Simon Glassbb1501f2014-12-01 17:34:00 -0700148 def MakeEnvironment(self, full_path):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000149 """Returns an environment for using the toolchain.
150
Simon Glassbb1501f2014-12-01 17:34:00 -0700151 Thie takes the current environment and adds CROSS_COMPILE so that
Daniel Schwierzeckb0e994c2017-06-08 03:07:08 +0200152 the tool chain will operate correctly. This also disables localized
153 output and possibly unicode encoded output of all build tools by
154 adding LC_ALL=C.
Simon Glassbb1501f2014-12-01 17:34:00 -0700155
156 Args:
157 full_path: Return the full path in CROSS_COMPILE and don't set
158 PATH
Simon Glass00beb242019-01-07 16:44:20 -0700159 Returns:
160 Dict containing the environemnt to use. This is based on the current
161 environment, with changes as needed to CROSS_COMPILE, PATH and
162 LC_ALL.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000163 """
164 env = dict(os.environ)
York Sund5fe0132016-10-04 14:33:51 -0700165 wrapper = self.GetWrapper()
166
Simon Glass00beb242019-01-07 16:44:20 -0700167 if self.override_toolchain:
168 # We'll use MakeArgs() to provide this
169 pass
170 elif full_path:
York Sund5fe0132016-10-04 14:33:51 -0700171 env['CROSS_COMPILE'] = wrapper + os.path.join(self.path, self.cross)
Simon Glassbb1501f2014-12-01 17:34:00 -0700172 else:
York Sund5fe0132016-10-04 14:33:51 -0700173 env['CROSS_COMPILE'] = wrapper + self.cross
Simon Glassbb1501f2014-12-01 17:34:00 -0700174 env['PATH'] = self.path + ':' + env['PATH']
175
Daniel Schwierzeckb0e994c2017-06-08 03:07:08 +0200176 env['LC_ALL'] = 'C'
177
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000178 return env
179
Simon Glass00beb242019-01-07 16:44:20 -0700180 def MakeArgs(self):
181 """Create the 'make' arguments for a toolchain
182
183 This is only used when the toolchain is being overridden. Since the
184 U-Boot Makefile sets CC and HOSTCC explicitly we cannot rely on the
185 environment (and MakeEnvironment()) to override these values. This
186 function returns the arguments to accomplish this.
187
188 Returns:
189 List of arguments to pass to 'make'
190 """
191 if self.override_toolchain:
192 return ['HOSTCC=%s' % self.override_toolchain,
193 'CC=%s' % self.override_toolchain]
194 return []
195
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000196
197class Toolchains:
198 """Manage a list of toolchains for building U-Boot
199
200 We select one toolchain for each architecture type
201
202 Public members:
203 toolchains: Dict of Toolchain objects, keyed by architecture name
Simon Glass17bce662016-03-12 18:50:32 -0700204 prefixes: Dict of prefixes to check, keyed by architecture. This can
205 be a full path and toolchain prefix, for example
206 {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
207 something on the search path, for example
208 {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000209 paths: List of paths to check for toolchains (may contain wildcards)
210 """
211
Simon Glass00beb242019-01-07 16:44:20 -0700212 def __init__(self, override_toolchain=None):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000213 self.toolchains = {}
Simon Glass17bce662016-03-12 18:50:32 -0700214 self.prefixes = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000215 self.paths = []
Simon Glass00beb242019-01-07 16:44:20 -0700216 self.override_toolchain = override_toolchain
Simon Glassd4144e42014-09-05 19:00:13 -0600217 self._make_flags = dict(bsettings.GetItems('make-flags'))
218
Simon Glass80e6a482016-07-27 20:33:01 -0600219 def GetPathList(self, show_warning=True):
Simon Glass827e37b2014-12-01 17:34:06 -0700220 """Get a list of available toolchain paths
221
Simon Glass80e6a482016-07-27 20:33:01 -0600222 Args:
223 show_warning: True to show a warning if there are no tool chains.
224
Simon Glass827e37b2014-12-01 17:34:06 -0700225 Returns:
226 List of strings, each a path to a toolchain mentioned in the
227 [toolchain] section of the settings file.
228 """
Simon Glass4281ad82013-09-23 17:35:17 -0600229 toolchains = bsettings.GetItems('toolchain')
Simon Glass80e6a482016-07-27 20:33:01 -0600230 if show_warning and not toolchains:
Simon Glassc05aa032019-10-31 07:42:53 -0600231 print(("Warning: No tool chains. Please run 'buildman "
Simon Glass713bea32016-07-27 20:33:02 -0600232 "--fetch-arch all' to download all available toolchains, or "
233 "add a [toolchain] section to your buildman config file "
234 "%s. See README for details" %
Simon Glassc05aa032019-10-31 07:42:53 -0600235 bsettings.config_fname))
Simon Glass4281ad82013-09-23 17:35:17 -0600236
Simon Glass827e37b2014-12-01 17:34:06 -0700237 paths = []
Simon Glass4281ad82013-09-23 17:35:17 -0600238 for name, value in toolchains:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000239 if '*' in value:
Simon Glass827e37b2014-12-01 17:34:06 -0700240 paths += glob.glob(value)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000241 else:
Simon Glass827e37b2014-12-01 17:34:06 -0700242 paths.append(value)
243 return paths
244
Simon Glass80e6a482016-07-27 20:33:01 -0600245 def GetSettings(self, show_warning=True):
246 """Get toolchain settings from the settings file.
247
248 Args:
249 show_warning: True to show a warning if there are no tool chains.
250 """
251 self.prefixes = bsettings.GetItems('toolchain-prefix')
252 self.paths += self.GetPathList(show_warning)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000253
Simon Glass608e3992016-03-06 19:45:38 -0700254 def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
255 arch=None):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000256 """Add a toolchain to our list
257
258 We select the given toolchain as our preferred one for its
259 architecture if it is a higher priority than the others.
260
261 Args:
262 fname: Filename of toolchain's gcc driver
263 test: True to run the toolchain to test it
Simon Glassff690df2016-03-06 19:45:37 -0700264 priority: Priority to use for this toolchain
Simon Glass608e3992016-03-06 19:45:38 -0700265 arch: Toolchain architecture, or None if not known
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000266 """
Simon Glass00beb242019-01-07 16:44:20 -0700267 toolchain = Toolchain(fname, test, verbose, priority, arch,
268 self.override_toolchain)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000269 add_it = toolchain.ok
270 if toolchain.arch in self.toolchains:
271 add_it = (toolchain.priority <
272 self.toolchains[toolchain.arch].priority)
273 if add_it:
274 self.toolchains[toolchain.arch] = toolchain
Simon Glassff690df2016-03-06 19:45:37 -0700275 elif verbose:
Simon Glassc05aa032019-10-31 07:42:53 -0600276 print(("Toolchain '%s' at priority %d will be ignored because "
Simon Glassff690df2016-03-06 19:45:37 -0700277 "another toolchain for arch '%s' has priority %d" %
278 (toolchain.gcc, toolchain.priority, toolchain.arch,
Simon Glassc05aa032019-10-31 07:42:53 -0600279 self.toolchains[toolchain.arch].priority)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000280
Simon Glass827e37b2014-12-01 17:34:06 -0700281 def ScanPath(self, path, verbose):
282 """Scan a path for a valid toolchain
283
284 Args:
285 path: Path to scan
286 verbose: True to print out progress information
287 Returns:
288 Filename of C compiler if found, else None
289 """
Albert ARIBAUDd9088982015-02-01 00:12:44 +0100290 fnames = []
Simon Glass827e37b2014-12-01 17:34:06 -0700291 for subdir in ['.', 'bin', 'usr/bin']:
292 dirname = os.path.join(path, subdir)
Simon Glassc05aa032019-10-31 07:42:53 -0600293 if verbose: print(" - looking in '%s'" % dirname)
Simon Glass827e37b2014-12-01 17:34:06 -0700294 for fname in glob.glob(dirname + '/*gcc'):
Simon Glassc05aa032019-10-31 07:42:53 -0600295 if verbose: print(" - found '%s'" % fname)
Albert ARIBAUDd9088982015-02-01 00:12:44 +0100296 fnames.append(fname)
297 return fnames
Simon Glass827e37b2014-12-01 17:34:06 -0700298
Simon Glass17bce662016-03-12 18:50:32 -0700299 def ScanPathEnv(self, fname):
300 """Scan the PATH environment variable for a given filename.
301
302 Args:
303 fname: Filename to scan for
304 Returns:
305 List of matching pathanames, or [] if none
306 """
307 pathname_list = []
308 for path in os.environ["PATH"].split(os.pathsep):
309 path = path.strip('"')
310 pathname = os.path.join(path, fname)
311 if os.path.exists(pathname):
312 pathname_list.append(pathname)
313 return pathname_list
Simon Glass827e37b2014-12-01 17:34:06 -0700314
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000315 def Scan(self, verbose):
316 """Scan for available toolchains and select the best for each arch.
317
318 We look for all the toolchains we can file, figure out the
319 architecture for each, and whether it works. Then we select the
320 highest priority toolchain for each arch.
321
322 Args:
323 verbose: True to print out progress information
324 """
Simon Glassc05aa032019-10-31 07:42:53 -0600325 if verbose: print('Scanning for tool chains')
Simon Glass17bce662016-03-12 18:50:32 -0700326 for name, value in self.prefixes:
Simon Glassc05aa032019-10-31 07:42:53 -0600327 if verbose: print(" - scanning prefix '%s'" % value)
Simon Glass17bce662016-03-12 18:50:32 -0700328 if os.path.exists(value):
329 self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
330 continue
331 fname = value + 'gcc'
332 if os.path.exists(fname):
333 self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
334 continue
335 fname_list = self.ScanPathEnv(fname)
336 for f in fname_list:
337 self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
338 if not fname_list:
Simon Glassc05aa032019-10-31 07:42:53 -0600339 raise ValueError("No tool chain found for prefix '%s'" %
Simon Glass17bce662016-03-12 18:50:32 -0700340 value)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000341 for path in self.paths:
Simon Glassc05aa032019-10-31 07:42:53 -0600342 if verbose: print(" - scanning path '%s'" % path)
Albert ARIBAUDd9088982015-02-01 00:12:44 +0100343 fnames = self.ScanPath(path, verbose)
344 for fname in fnames:
Simon Glass827e37b2014-12-01 17:34:06 -0700345 self.Add(fname, True, verbose)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000346
347 def List(self):
348 """List out the selected toolchains for each architecture"""
Simon Glass713bea32016-07-27 20:33:02 -0600349 col = terminal.Color()
Simon Glassc05aa032019-10-31 07:42:53 -0600350 print(col.Color(col.BLUE, 'List of available toolchains (%d):' %
351 len(self.toolchains)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000352 if len(self.toolchains):
Simon Glassc05aa032019-10-31 07:42:53 -0600353 for key, value in sorted(self.toolchains.items()):
354 print('%-10s: %s' % (key, value.gcc))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000355 else:
Simon Glassc05aa032019-10-31 07:42:53 -0600356 print('None')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000357
358 def Select(self, arch):
359 """Returns the toolchain for a given architecture
360
361 Args:
362 args: Name of architecture (e.g. 'arm', 'ppc_8xx')
363
364 returns:
365 toolchain object, or None if none found
366 """
Simon Glass9b83bfd2014-12-01 17:34:05 -0700367 for tag, value in bsettings.GetItems('toolchain-alias'):
368 if arch == tag:
369 for alias in value.split():
370 if alias in self.toolchains:
371 return self.toolchains[alias]
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000372
373 if not arch in self.toolchains:
Simon Glassc05aa032019-10-31 07:42:53 -0600374 raise ValueError("No tool chain found for arch '%s'" % arch)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000375 return self.toolchains[arch]
Simon Glass4281ad82013-09-23 17:35:17 -0600376
377 def ResolveReferences(self, var_dict, args):
378 """Resolve variable references in a string
379
380 This converts ${blah} within the string to the value of blah.
381 This function works recursively.
382
383 Args:
384 var_dict: Dictionary containing variables and their values
385 args: String containing make arguments
386 Returns:
387 Resolved string
388
389 >>> bsettings.Setup()
390 >>> tcs = Toolchains()
391 >>> tcs.Add('fred', False)
392 >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
393 'second' : '2nd'}
394 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
395 'this=OBLIQUE_set'
396 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
397 'this=OBLIQUE_setfi2ndrstnd'
398 """
Simon Glassf60c9d42014-08-28 09:43:40 -0600399 re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
Simon Glass4281ad82013-09-23 17:35:17 -0600400
401 while True:
402 m = re_var.search(args)
403 if not m:
404 break
405 lookup = m.group(0)[2:-1]
406 value = var_dict.get(lookup, '')
407 args = args[:m.start(0)] + value + args[m.end(0):]
408 return args
409
410 def GetMakeArguments(self, board):
411 """Returns 'make' arguments for a given board
412
413 The flags are in a section called 'make-flags'. Flags are named
414 after the target they represent, for example snapper9260=TESTING=1
415 will pass TESTING=1 to make when building the snapper9260 board.
416
417 References to other boards can be added in the string also. For
418 example:
419
420 [make-flags]
421 at91-boards=ENABLE_AT91_TEST=1
422 snapper9260=${at91-boards} BUILD_TAG=442
423 snapper9g45=${at91-boards} BUILD_TAG=443
424
425 This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
426 and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
427
428 A special 'target' variable is set to the board target.
429
430 Args:
431 board: Board object for the board to check.
432 Returns:
433 'make' flags for that board, or '' if none
434 """
435 self._make_flags['target'] = board.target
436 arg_str = self.ResolveReferences(self._make_flags,
437 self._make_flags.get(board.target, ''))
438 args = arg_str.split(' ')
439 i = 0
440 while i < len(args):
441 if not args[i]:
442 del args[i]
443 else:
444 i += 1
445 return args
Simon Glass827e37b2014-12-01 17:34:06 -0700446
447 def LocateArchUrl(self, fetch_arch):
448 """Find a toolchain available online
449
450 Look in standard places for available toolchains. At present the
451 only standard place is at kernel.org.
452
453 Args:
454 arch: Architecture to look for, or 'list' for all
455 Returns:
456 If fetch_arch is 'list', a tuple:
457 Machine architecture (e.g. x86_64)
458 List of toolchains
459 else
460 URL containing this toolchain, if avaialble, else None
461 """
462 arch = command.OutputOneLine('uname', '-m')
463 base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
Daniel Schwierzeck4c58d272018-05-10 07:15:53 -0400464 versions = ['7.3.0', '6.4.0', '4.9.4']
Simon Glass827e37b2014-12-01 17:34:06 -0700465 links = []
466 for version in versions:
467 url = '%s/%s/%s/' % (base, arch, version)
Simon Glassc05aa032019-10-31 07:42:53 -0600468 print('Checking: %s' % url)
469 response = urllib.request.urlopen(url)
470 html = tools.ToString(response.read())
Simon Glass827e37b2014-12-01 17:34:06 -0700471 parser = MyHTMLParser(fetch_arch)
472 parser.feed(html)
473 if fetch_arch == 'list':
474 links += parser.links
475 elif parser.arch_link:
476 return url + parser.arch_link
477 if fetch_arch == 'list':
478 return arch, links
479 return None
480
481 def Download(self, url):
482 """Download a file to a temporary directory
483
484 Args:
485 url: URL to download
486 Returns:
487 Tuple:
488 Temporary directory name
489 Full path to the downloaded archive file in that directory,
490 or None if there was an error while downloading
491 """
Simon Glassc05aa032019-10-31 07:42:53 -0600492 print('Downloading: %s' % url)
Simon Glass827e37b2014-12-01 17:34:06 -0700493 leaf = url.split('/')[-1]
494 tmpdir = tempfile.mkdtemp('.buildman')
Simon Glassc05aa032019-10-31 07:42:53 -0600495 response = urllib.request.urlopen(url)
Simon Glass827e37b2014-12-01 17:34:06 -0700496 fname = os.path.join(tmpdir, leaf)
497 fd = open(fname, 'wb')
498 meta = response.info()
Simon Glassc05aa032019-10-31 07:42:53 -0600499 size = int(meta.get('Content-Length'))
Simon Glass827e37b2014-12-01 17:34:06 -0700500 done = 0
501 block_size = 1 << 16
502 status = ''
503
504 # Read the file in chunks and show progress as we go
505 while True:
506 buffer = response.read(block_size)
507 if not buffer:
Simon Glassc05aa032019-10-31 07:42:53 -0600508 print(chr(8) * (len(status) + 1), '\r', end=' ')
Simon Glass827e37b2014-12-01 17:34:06 -0700509 break
510
511 done += len(buffer)
512 fd.write(buffer)
Simon Glassc05aa032019-10-31 07:42:53 -0600513 status = r'%10d MiB [%3d%%]' % (done // 1024 // 1024,
514 done * 100 // size)
Simon Glass827e37b2014-12-01 17:34:06 -0700515 status = status + chr(8) * (len(status) + 1)
Simon Glassc05aa032019-10-31 07:42:53 -0600516 print(status, end=' ')
Simon Glass827e37b2014-12-01 17:34:06 -0700517 sys.stdout.flush()
518 fd.close()
519 if done != size:
Simon Glassc05aa032019-10-31 07:42:53 -0600520 print('Error, failed to download')
Simon Glass827e37b2014-12-01 17:34:06 -0700521 os.remove(fname)
522 fname = None
523 return tmpdir, fname
524
525 def Unpack(self, fname, dest):
526 """Unpack a tar file
527
528 Args:
529 fname: Filename to unpack
530 dest: Destination directory
531 Returns:
532 Directory name of the first entry in the archive, without the
533 trailing /
534 """
535 stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
Trevor Woernerd82f5392018-11-21 03:31:12 -0500536 dirs = stdout.splitlines()[1].split('/')[:2]
537 return '/'.join(dirs)
Simon Glass827e37b2014-12-01 17:34:06 -0700538
539 def TestSettingsHasPath(self, path):
Simon Glass2289b272016-07-27 20:33:03 -0600540 """Check if buildman will find this toolchain
Simon Glass827e37b2014-12-01 17:34:06 -0700541
542 Returns:
543 True if the path is in settings, False if not
544 """
Simon Glass80e6a482016-07-27 20:33:01 -0600545 paths = self.GetPathList(False)
Simon Glass827e37b2014-12-01 17:34:06 -0700546 return path in paths
547
548 def ListArchs(self):
549 """List architectures with available toolchains to download"""
550 host_arch, archives = self.LocateArchUrl('list')
Trevor Woernerb11f1262018-11-21 03:31:13 -0500551 re_arch = re.compile('[-a-z0-9.]*[-_]([^-]*)-.*')
Simon Glass827e37b2014-12-01 17:34:06 -0700552 arch_set = set()
553 for archive in archives:
554 # Remove the host architecture from the start
555 arch = re_arch.match(archive[len(host_arch):])
556 if arch:
Trevor Woernerb11f1262018-11-21 03:31:13 -0500557 if arch.group(1) != '2.0' and arch.group(1) != '64':
558 arch_set.add(arch.group(1))
Simon Glass827e37b2014-12-01 17:34:06 -0700559 return sorted(arch_set)
560
561 def FetchAndInstall(self, arch):
562 """Fetch and install a new toolchain
563
564 arch:
565 Architecture to fetch, or 'list' to list
566 """
567 # Fist get the URL for this architecture
Simon Glass713bea32016-07-27 20:33:02 -0600568 col = terminal.Color()
Simon Glassc05aa032019-10-31 07:42:53 -0600569 print(col.Color(col.BLUE, "Downloading toolchain for arch '%s'" % arch))
Simon Glass827e37b2014-12-01 17:34:06 -0700570 url = self.LocateArchUrl(arch)
571 if not url:
Simon Glassc05aa032019-10-31 07:42:53 -0600572 print(("Cannot find toolchain for arch '%s' - use 'list' to list" %
573 arch))
Simon Glass827e37b2014-12-01 17:34:06 -0700574 return 2
575 home = os.environ['HOME']
576 dest = os.path.join(home, '.buildman-toolchains')
577 if not os.path.exists(dest):
578 os.mkdir(dest)
579
580 # Download the tar file for this toolchain and unpack it
581 tmpdir, tarfile = self.Download(url)
582 if not tarfile:
583 return 1
Simon Glassc05aa032019-10-31 07:42:53 -0600584 print(col.Color(col.GREEN, 'Unpacking to: %s' % dest), end=' ')
Simon Glass827e37b2014-12-01 17:34:06 -0700585 sys.stdout.flush()
586 path = self.Unpack(tarfile, dest)
587 os.remove(tarfile)
588 os.rmdir(tmpdir)
Simon Glassc05aa032019-10-31 07:42:53 -0600589 print()
Simon Glass827e37b2014-12-01 17:34:06 -0700590
591 # Check that the toolchain works
Simon Glassc05aa032019-10-31 07:42:53 -0600592 print(col.Color(col.GREEN, 'Testing'))
Simon Glass827e37b2014-12-01 17:34:06 -0700593 dirpath = os.path.join(dest, path)
Simon Glass2a76a642015-03-02 17:05:15 -0700594 compiler_fname_list = self.ScanPath(dirpath, True)
595 if not compiler_fname_list:
Simon Glassc05aa032019-10-31 07:42:53 -0600596 print('Could not locate C compiler - fetch failed.')
Simon Glass827e37b2014-12-01 17:34:06 -0700597 return 1
Simon Glass2a76a642015-03-02 17:05:15 -0700598 if len(compiler_fname_list) != 1:
Simon Glassc05aa032019-10-31 07:42:53 -0600599 print(col.Color(col.RED, 'Warning, ambiguous toolchains: %s' %
600 ', '.join(compiler_fname_list)))
Simon Glass2a76a642015-03-02 17:05:15 -0700601 toolchain = Toolchain(compiler_fname_list[0], True, True)
Simon Glass827e37b2014-12-01 17:34:06 -0700602
603 # Make sure that it will be found by buildman
604 if not self.TestSettingsHasPath(dirpath):
Simon Glassc05aa032019-10-31 07:42:53 -0600605 print(("Adding 'download' to config file '%s'" %
606 bsettings.config_fname))
Simon Glassc8785c52016-07-27 20:33:05 -0600607 bsettings.SetItem('toolchain', 'download', '%s/*/*' % dest)
Simon Glass827e37b2014-12-01 17:34:06 -0700608 return 0