blob: fd137f7300e73ce52d1cd0248715200b50fed778 [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 Glassc78ed662019-10-31 07:42:53 -06007from html.parser import HTMLParser
Simon Glassc05694f2013-04-03 11:07:16 +00008import os
Simon Glass7e803e12014-12-01 17:34:06 -07009import sys
10import tempfile
Simon Glassc78ed662019-10-31 07:42:53 -060011import urllib.request, urllib.error, urllib.parse
Simon Glassc05694f2013-04-03 11:07:16 +000012
Simon Glassf0d9c102020-04-17 18:09:02 -060013from buildman import bsettings
Simon Glassa997ea52020-04-17 18:09:04 -060014from patman import command
15from patman import terminal
16from patman import tools
Simon Glassc05694f2013-04-03 11:07:16 +000017
Simon Glassf5902732016-03-12 18:50:32 -070018(PRIORITY_FULL_PREFIX, PRIORITY_PREFIX_GCC, PRIORITY_PREFIX_GCC_PATH,
Simon Glassc78ed662019-10-31 07:42:53 -060019 PRIORITY_CALC) = list(range(4))
Simon Glass7b971292016-03-06 19:45:37 -070020
Simon Glass48ac42e2019-12-05 15:59:14 -070021(VAR_CROSS_COMPILE, VAR_PATH, VAR_ARCH, VAR_MAKE_ARGS) = range(4)
22
Simon Glass7e803e12014-12-01 17:34:06 -070023# Simple class to collect links from a page
24class MyHTMLParser(HTMLParser):
25 def __init__(self, arch):
26 """Create a new parser
27
28 After the parser runs, self.links will be set to a list of the links
29 to .xz archives found in the page, and self.arch_link will be set to
30 the one for the given architecture (or None if not found).
31
32 Args:
33 arch: Architecture to search for
34 """
35 HTMLParser.__init__(self)
36 self.arch_link = None
37 self.links = []
Daniel Schwierzeck40261f92018-05-10 07:15:53 -040038 self.re_arch = re.compile('[-_]%s-' % arch)
Simon Glass7e803e12014-12-01 17:34:06 -070039
40 def handle_starttag(self, tag, attrs):
41 if tag == 'a':
42 for tag, value in attrs:
43 if tag == 'href':
44 if value and value.endswith('.xz'):
45 self.links.append(value)
Daniel Schwierzeck40261f92018-05-10 07:15:53 -040046 if self.re_arch.search(value):
Simon Glass7e803e12014-12-01 17:34:06 -070047 self.arch_link = value
48
49
Simon Glassc05694f2013-04-03 11:07:16 +000050class Toolchain:
51 """A single toolchain
52
53 Public members:
54 gcc: Full path to C compiler
55 path: Directory path containing C compiler
56 cross: Cross compile string, e.g. 'arm-linux-'
57 arch: Architecture of toolchain as determined from the first
58 component of the filename. E.g. arm-linux-gcc becomes arm
Simon Glass7b971292016-03-06 19:45:37 -070059 priority: Toolchain priority (0=highest, 20=lowest)
Simon Glassf77ca5b2019-01-07 16:44:20 -070060 override_toolchain: Toolchain to use for sandbox, overriding the normal
61 one
Simon Glassc05694f2013-04-03 11:07:16 +000062 """
Simon Glassc6cdd3e2016-03-06 19:45:38 -070063 def __init__(self, fname, test, verbose=False, priority=PRIORITY_CALC,
Simon Glassf77ca5b2019-01-07 16:44:20 -070064 arch=None, override_toolchain=None):
Simon Glassc05694f2013-04-03 11:07:16 +000065 """Create a new toolchain object.
66
67 Args:
68 fname: Filename of the gcc component
69 test: True to run the toolchain to test it
Simon Glassd6ece322016-03-06 19:45:35 -070070 verbose: True to print out the information
Simon Glass7b971292016-03-06 19:45:37 -070071 priority: Priority to use for this toolchain, or PRIORITY_CALC to
72 calculate it
Simon Glassc05694f2013-04-03 11:07:16 +000073 """
74 self.gcc = fname
75 self.path = os.path.dirname(fname)
Simon Glassf77ca5b2019-01-07 16:44:20 -070076 self.override_toolchain = override_toolchain
Simon Glass28ed0062014-12-01 17:33:58 -070077
78 # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
79 # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
80 basename = os.path.basename(fname)
81 pos = basename.rfind('-')
82 self.cross = basename[:pos + 1] if pos != -1 else ''
83
84 # The architecture is the first part of the name
Simon Glassc05694f2013-04-03 11:07:16 +000085 pos = self.cross.find('-')
Simon Glassc6cdd3e2016-03-06 19:45:38 -070086 if arch:
87 self.arch = arch
88 else:
89 self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
Simon Glassf77ca5b2019-01-07 16:44:20 -070090 if self.arch == 'sandbox' and override_toolchain:
91 self.gcc = override_toolchain
Simon Glassc05694f2013-04-03 11:07:16 +000092
Simon Glassd48a46c2014-12-01 17:34:00 -070093 env = self.MakeEnvironment(False)
Simon Glassc05694f2013-04-03 11:07:16 +000094
95 # As a basic sanity check, run the C compiler with --version
96 cmd = [fname, '--version']
Simon Glass7b971292016-03-06 19:45:37 -070097 if priority == PRIORITY_CALC:
98 self.priority = self.GetPriority(fname)
99 else:
100 self.priority = priority
Simon Glassc05694f2013-04-03 11:07:16 +0000101 if test:
Stephen Warren288d7672013-10-09 14:28:09 -0600102 result = command.RunPipe([cmd], capture=True, env=env,
103 raise_on_error=False)
Simon Glassc05694f2013-04-03 11:07:16 +0000104 self.ok = result.return_code == 0
105 if verbose:
Simon Glassc78ed662019-10-31 07:42:53 -0600106 print('Tool chain test: ', end=' ')
Simon Glassc05694f2013-04-03 11:07:16 +0000107 if self.ok:
Simon Glassc78ed662019-10-31 07:42:53 -0600108 print("OK, arch='%s', priority %d" % (self.arch,
109 self.priority))
Simon Glassc05694f2013-04-03 11:07:16 +0000110 else:
Simon Glassc78ed662019-10-31 07:42:53 -0600111 print('BAD')
112 print('Command: ', cmd)
113 print(result.stdout)
114 print(result.stderr)
Simon Glassc05694f2013-04-03 11:07:16 +0000115 else:
116 self.ok = True
Simon Glassc05694f2013-04-03 11:07:16 +0000117
118 def GetPriority(self, fname):
119 """Return the priority of the toolchain.
120
121 Toolchains are ranked according to their suitability by their
122 filename prefix.
123
124 Args:
125 fname: Filename of toolchain
126 Returns:
Simon Glass7b971292016-03-06 19:45:37 -0700127 Priority of toolchain, PRIORITY_CALC=highest, 20=lowest.
Simon Glassc05694f2013-04-03 11:07:16 +0000128 """
Masahiro Yamadafa25e1f2014-07-07 09:47:45 +0900129 priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
Tom Rini6abc9c82017-04-14 19:47:50 -0400130 '-none-linux-gnueabi', '-none-linux-gnueabihf', '-uclinux',
131 '-none-eabi', '-gentoo-linux-gnu', '-linux-gnueabi',
132 '-linux-gnueabihf', '-le-linux', '-uclinux']
Simon Glassc05694f2013-04-03 11:07:16 +0000133 for prio in range(len(priority_list)):
134 if priority_list[prio] in fname:
Simon Glass7b971292016-03-06 19:45:37 -0700135 return PRIORITY_CALC + prio
136 return PRIORITY_CALC + prio
Simon Glassc05694f2013-04-03 11:07:16 +0000137
York Sunfb197a82016-10-04 14:33:51 -0700138 def GetWrapper(self, show_warning=True):
139 """Get toolchain wrapper from the setting file.
140 """
Simon Glass45572632019-01-07 16:44:24 -0700141 value = ''
142 for name, value in bsettings.GetItems('toolchain-wrapper'):
York Sunfb197a82016-10-04 14:33:51 -0700143 if not value:
Simon Glassc78ed662019-10-31 07:42:53 -0600144 print("Warning: Wrapper not found")
York Sunfb197a82016-10-04 14:33:51 -0700145 if value:
146 value = value + ' '
147
148 return value
149
Simon Glass48ac42e2019-12-05 15:59:14 -0700150 def GetEnvArgs(self, which):
151 """Get an environment variable/args value based on the the toolchain
152
153 Args:
154 which: VAR_... value to get
155
156 Returns:
157 Value of that environment variable or arguments
158 """
159 wrapper = self.GetWrapper()
160 if which == VAR_CROSS_COMPILE:
161 return wrapper + os.path.join(self.path, self.cross)
162 elif which == VAR_PATH:
163 return self.path
164 elif which == VAR_ARCH:
165 return self.arch
166 elif which == VAR_MAKE_ARGS:
167 args = self.MakeArgs()
168 if args:
169 return ' '.join(args)
170 return ''
171 else:
172 raise ValueError('Unknown arg to GetEnvArgs (%d)' % which)
173
Simon Glassd48a46c2014-12-01 17:34:00 -0700174 def MakeEnvironment(self, full_path):
Simon Glassc05694f2013-04-03 11:07:16 +0000175 """Returns an environment for using the toolchain.
176
Simon Glassd48a46c2014-12-01 17:34:00 -0700177 Thie takes the current environment and adds CROSS_COMPILE so that
Daniel Schwierzeckc9e5f0c2017-06-08 03:07:08 +0200178 the tool chain will operate correctly. This also disables localized
179 output and possibly unicode encoded output of all build tools by
180 adding LC_ALL=C.
Simon Glassd48a46c2014-12-01 17:34:00 -0700181
Simon Glass93008e22021-04-11 16:27:28 +1200182 Note that os.environb is used to obtain the environment, since in some
183 cases the environment many contain non-ASCII characters and we see
184 errors like:
185
186 UnicodeEncodeError: 'utf-8' codec can't encode characters in position
187 569-570: surrogates not allowed
188
Simon Glassd48a46c2014-12-01 17:34:00 -0700189 Args:
190 full_path: Return the full path in CROSS_COMPILE and don't set
191 PATH
Simon Glassf77ca5b2019-01-07 16:44:20 -0700192 Returns:
Simon Glass93008e22021-04-11 16:27:28 +1200193 Dict containing the (bytes) environment to use. This is based on the
194 current environment, with changes as needed to CROSS_COMPILE, PATH
195 and LC_ALL.
Simon Glassc05694f2013-04-03 11:07:16 +0000196 """
Simon Glass93008e22021-04-11 16:27:28 +1200197 env = dict(os.environb)
York Sunfb197a82016-10-04 14:33:51 -0700198 wrapper = self.GetWrapper()
199
Simon Glassf77ca5b2019-01-07 16:44:20 -0700200 if self.override_toolchain:
201 # We'll use MakeArgs() to provide this
202 pass
203 elif full_path:
Simon Glass93008e22021-04-11 16:27:28 +1200204 env[b'CROSS_COMPILE'] = tools.ToBytes(
205 wrapper + os.path.join(self.path, self.cross))
Simon Glassd48a46c2014-12-01 17:34:00 -0700206 else:
Simon Glass93008e22021-04-11 16:27:28 +1200207 env[b'CROSS_COMPILE'] = tools.ToBytes(wrapper + self.cross)
208 env[b'PATH'] = tools.ToBytes(self.path) + b':' + env[b'PATH']
Simon Glassd48a46c2014-12-01 17:34:00 -0700209
Simon Glass93008e22021-04-11 16:27:28 +1200210 env[b'LC_ALL'] = b'C'
Daniel Schwierzeckc9e5f0c2017-06-08 03:07:08 +0200211
Simon Glassc05694f2013-04-03 11:07:16 +0000212 return env
213
Simon Glassf77ca5b2019-01-07 16:44:20 -0700214 def MakeArgs(self):
215 """Create the 'make' arguments for a toolchain
216
217 This is only used when the toolchain is being overridden. Since the
218 U-Boot Makefile sets CC and HOSTCC explicitly we cannot rely on the
219 environment (and MakeEnvironment()) to override these values. This
220 function returns the arguments to accomplish this.
221
222 Returns:
223 List of arguments to pass to 'make'
224 """
225 if self.override_toolchain:
226 return ['HOSTCC=%s' % self.override_toolchain,
227 'CC=%s' % self.override_toolchain]
228 return []
229
Simon Glassc05694f2013-04-03 11:07:16 +0000230
231class Toolchains:
232 """Manage a list of toolchains for building U-Boot
233
234 We select one toolchain for each architecture type
235
236 Public members:
237 toolchains: Dict of Toolchain objects, keyed by architecture name
Simon Glassf5902732016-03-12 18:50:32 -0700238 prefixes: Dict of prefixes to check, keyed by architecture. This can
239 be a full path and toolchain prefix, for example
240 {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
241 something on the search path, for example
242 {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
Simon Glassc05694f2013-04-03 11:07:16 +0000243 paths: List of paths to check for toolchains (may contain wildcards)
244 """
245
Simon Glassf77ca5b2019-01-07 16:44:20 -0700246 def __init__(self, override_toolchain=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000247 self.toolchains = {}
Simon Glassf5902732016-03-12 18:50:32 -0700248 self.prefixes = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000249 self.paths = []
Simon Glassf77ca5b2019-01-07 16:44:20 -0700250 self.override_toolchain = override_toolchain
Simon Glassed098bb2014-09-05 19:00:13 -0600251 self._make_flags = dict(bsettings.GetItems('make-flags'))
252
Simon Glassf38a6422016-07-27 20:33:01 -0600253 def GetPathList(self, show_warning=True):
Simon Glass7e803e12014-12-01 17:34:06 -0700254 """Get a list of available toolchain paths
255
Simon Glassf38a6422016-07-27 20:33:01 -0600256 Args:
257 show_warning: True to show a warning if there are no tool chains.
258
Simon Glass7e803e12014-12-01 17:34:06 -0700259 Returns:
260 List of strings, each a path to a toolchain mentioned in the
261 [toolchain] section of the settings file.
262 """
Simon Glasscc246fb2013-09-23 17:35:17 -0600263 toolchains = bsettings.GetItems('toolchain')
Simon Glassf38a6422016-07-27 20:33:01 -0600264 if show_warning and not toolchains:
Simon Glassc78ed662019-10-31 07:42:53 -0600265 print(("Warning: No tool chains. Please run 'buildman "
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600266 "--fetch-arch all' to download all available toolchains, or "
267 "add a [toolchain] section to your buildman config file "
268 "%s. See README for details" %
Simon Glassc78ed662019-10-31 07:42:53 -0600269 bsettings.config_fname))
Simon Glasscc246fb2013-09-23 17:35:17 -0600270
Simon Glass7e803e12014-12-01 17:34:06 -0700271 paths = []
Simon Glasscc246fb2013-09-23 17:35:17 -0600272 for name, value in toolchains:
Simon Glassc05694f2013-04-03 11:07:16 +0000273 if '*' in value:
Simon Glass7e803e12014-12-01 17:34:06 -0700274 paths += glob.glob(value)
Simon Glassc05694f2013-04-03 11:07:16 +0000275 else:
Simon Glass7e803e12014-12-01 17:34:06 -0700276 paths.append(value)
277 return paths
278
Simon Glassf38a6422016-07-27 20:33:01 -0600279 def GetSettings(self, show_warning=True):
280 """Get toolchain settings from the settings file.
281
282 Args:
283 show_warning: True to show a warning if there are no tool chains.
284 """
285 self.prefixes = bsettings.GetItems('toolchain-prefix')
286 self.paths += self.GetPathList(show_warning)
Simon Glassc05694f2013-04-03 11:07:16 +0000287
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700288 def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
289 arch=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000290 """Add a toolchain to our list
291
292 We select the given toolchain as our preferred one for its
293 architecture if it is a higher priority than the others.
294
295 Args:
296 fname: Filename of toolchain's gcc driver
297 test: True to run the toolchain to test it
Simon Glass7b971292016-03-06 19:45:37 -0700298 priority: Priority to use for this toolchain
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700299 arch: Toolchain architecture, or None if not known
Simon Glassc05694f2013-04-03 11:07:16 +0000300 """
Simon Glassf77ca5b2019-01-07 16:44:20 -0700301 toolchain = Toolchain(fname, test, verbose, priority, arch,
302 self.override_toolchain)
Simon Glassc05694f2013-04-03 11:07:16 +0000303 add_it = toolchain.ok
304 if toolchain.arch in self.toolchains:
305 add_it = (toolchain.priority <
306 self.toolchains[toolchain.arch].priority)
307 if add_it:
308 self.toolchains[toolchain.arch] = toolchain
Simon Glass7b971292016-03-06 19:45:37 -0700309 elif verbose:
Simon Glassc78ed662019-10-31 07:42:53 -0600310 print(("Toolchain '%s' at priority %d will be ignored because "
Simon Glass7b971292016-03-06 19:45:37 -0700311 "another toolchain for arch '%s' has priority %d" %
312 (toolchain.gcc, toolchain.priority, toolchain.arch,
Simon Glassc78ed662019-10-31 07:42:53 -0600313 self.toolchains[toolchain.arch].priority)))
Simon Glassc05694f2013-04-03 11:07:16 +0000314
Simon Glass7e803e12014-12-01 17:34:06 -0700315 def ScanPath(self, path, verbose):
316 """Scan a path for a valid toolchain
317
318 Args:
319 path: Path to scan
320 verbose: True to print out progress information
321 Returns:
322 Filename of C compiler if found, else None
323 """
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100324 fnames = []
Simon Glass7e803e12014-12-01 17:34:06 -0700325 for subdir in ['.', 'bin', 'usr/bin']:
326 dirname = os.path.join(path, subdir)
Simon Glassc78ed662019-10-31 07:42:53 -0600327 if verbose: print(" - looking in '%s'" % dirname)
Simon Glass7e803e12014-12-01 17:34:06 -0700328 for fname in glob.glob(dirname + '/*gcc'):
Simon Glassc78ed662019-10-31 07:42:53 -0600329 if verbose: print(" - found '%s'" % fname)
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100330 fnames.append(fname)
331 return fnames
Simon Glass7e803e12014-12-01 17:34:06 -0700332
Simon Glassf5902732016-03-12 18:50:32 -0700333 def ScanPathEnv(self, fname):
334 """Scan the PATH environment variable for a given filename.
335
336 Args:
337 fname: Filename to scan for
338 Returns:
339 List of matching pathanames, or [] if none
340 """
341 pathname_list = []
342 for path in os.environ["PATH"].split(os.pathsep):
343 path = path.strip('"')
344 pathname = os.path.join(path, fname)
345 if os.path.exists(pathname):
346 pathname_list.append(pathname)
347 return pathname_list
Simon Glass7e803e12014-12-01 17:34:06 -0700348
Simon Glassc05694f2013-04-03 11:07:16 +0000349 def Scan(self, verbose):
350 """Scan for available toolchains and select the best for each arch.
351
352 We look for all the toolchains we can file, figure out the
353 architecture for each, and whether it works. Then we select the
354 highest priority toolchain for each arch.
355
356 Args:
357 verbose: True to print out progress information
358 """
Simon Glassc78ed662019-10-31 07:42:53 -0600359 if verbose: print('Scanning for tool chains')
Simon Glassf5902732016-03-12 18:50:32 -0700360 for name, value in self.prefixes:
Simon Glassc78ed662019-10-31 07:42:53 -0600361 if verbose: print(" - scanning prefix '%s'" % value)
Simon Glassf5902732016-03-12 18:50:32 -0700362 if os.path.exists(value):
363 self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
364 continue
365 fname = value + 'gcc'
366 if os.path.exists(fname):
367 self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
368 continue
369 fname_list = self.ScanPathEnv(fname)
370 for f in fname_list:
371 self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
372 if not fname_list:
Simon Glassc78ed662019-10-31 07:42:53 -0600373 raise ValueError("No tool chain found for prefix '%s'" %
Simon Glassf5902732016-03-12 18:50:32 -0700374 value)
Simon Glassc05694f2013-04-03 11:07:16 +0000375 for path in self.paths:
Simon Glassc78ed662019-10-31 07:42:53 -0600376 if verbose: print(" - scanning path '%s'" % path)
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100377 fnames = self.ScanPath(path, verbose)
378 for fname in fnames:
Simon Glass7e803e12014-12-01 17:34:06 -0700379 self.Add(fname, True, verbose)
Simon Glassc05694f2013-04-03 11:07:16 +0000380
381 def List(self):
382 """List out the selected toolchains for each architecture"""
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600383 col = terminal.Color()
Simon Glassc78ed662019-10-31 07:42:53 -0600384 print(col.Color(col.BLUE, 'List of available toolchains (%d):' %
385 len(self.toolchains)))
Simon Glassc05694f2013-04-03 11:07:16 +0000386 if len(self.toolchains):
Simon Glassc78ed662019-10-31 07:42:53 -0600387 for key, value in sorted(self.toolchains.items()):
388 print('%-10s: %s' % (key, value.gcc))
Simon Glassc05694f2013-04-03 11:07:16 +0000389 else:
Simon Glassc78ed662019-10-31 07:42:53 -0600390 print('None')
Simon Glassc05694f2013-04-03 11:07:16 +0000391
392 def Select(self, arch):
393 """Returns the toolchain for a given architecture
394
395 Args:
396 args: Name of architecture (e.g. 'arm', 'ppc_8xx')
397
398 returns:
399 toolchain object, or None if none found
400 """
Simon Glassc1528c12014-12-01 17:34:05 -0700401 for tag, value in bsettings.GetItems('toolchain-alias'):
402 if arch == tag:
403 for alias in value.split():
404 if alias in self.toolchains:
405 return self.toolchains[alias]
Simon Glassc05694f2013-04-03 11:07:16 +0000406
407 if not arch in self.toolchains:
Simon Glassc78ed662019-10-31 07:42:53 -0600408 raise ValueError("No tool chain found for arch '%s'" % arch)
Simon Glassc05694f2013-04-03 11:07:16 +0000409 return self.toolchains[arch]
Simon Glasscc246fb2013-09-23 17:35:17 -0600410
411 def ResolveReferences(self, var_dict, args):
412 """Resolve variable references in a string
413
414 This converts ${blah} within the string to the value of blah.
415 This function works recursively.
416
417 Args:
418 var_dict: Dictionary containing variables and their values
419 args: String containing make arguments
420 Returns:
421 Resolved string
422
423 >>> bsettings.Setup()
424 >>> tcs = Toolchains()
425 >>> tcs.Add('fred', False)
426 >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
427 'second' : '2nd'}
428 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
429 'this=OBLIQUE_set'
430 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
431 'this=OBLIQUE_setfi2ndrstnd'
432 """
Simon Glass53e189d2014-08-28 09:43:40 -0600433 re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
Simon Glasscc246fb2013-09-23 17:35:17 -0600434
435 while True:
436 m = re_var.search(args)
437 if not m:
438 break
439 lookup = m.group(0)[2:-1]
440 value = var_dict.get(lookup, '')
441 args = args[:m.start(0)] + value + args[m.end(0):]
442 return args
443
444 def GetMakeArguments(self, board):
445 """Returns 'make' arguments for a given board
446
447 The flags are in a section called 'make-flags'. Flags are named
448 after the target they represent, for example snapper9260=TESTING=1
449 will pass TESTING=1 to make when building the snapper9260 board.
450
451 References to other boards can be added in the string also. For
452 example:
453
454 [make-flags]
455 at91-boards=ENABLE_AT91_TEST=1
456 snapper9260=${at91-boards} BUILD_TAG=442
457 snapper9g45=${at91-boards} BUILD_TAG=443
458
459 This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
460 and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
461
462 A special 'target' variable is set to the board target.
463
464 Args:
465 board: Board object for the board to check.
466 Returns:
467 'make' flags for that board, or '' if none
468 """
469 self._make_flags['target'] = board.target
470 arg_str = self.ResolveReferences(self._make_flags,
471 self._make_flags.get(board.target, ''))
Cristian Ciocaltea9da17fb2019-11-24 22:30:26 +0200472 args = re.findall("(?:\".*?\"|\S)+", arg_str)
Simon Glasscc246fb2013-09-23 17:35:17 -0600473 i = 0
474 while i < len(args):
Cristian Ciocaltea9da17fb2019-11-24 22:30:26 +0200475 args[i] = args[i].replace('"', '')
Simon Glasscc246fb2013-09-23 17:35:17 -0600476 if not args[i]:
477 del args[i]
478 else:
479 i += 1
480 return args
Simon Glass7e803e12014-12-01 17:34:06 -0700481
482 def LocateArchUrl(self, fetch_arch):
483 """Find a toolchain available online
484
485 Look in standard places for available toolchains. At present the
486 only standard place is at kernel.org.
487
488 Args:
489 arch: Architecture to look for, or 'list' for all
490 Returns:
491 If fetch_arch is 'list', a tuple:
492 Machine architecture (e.g. x86_64)
493 List of toolchains
494 else
495 URL containing this toolchain, if avaialble, else None
496 """
497 arch = command.OutputOneLine('uname', '-m')
Matthias Bruggerfb50a262020-01-17 10:53:37 +0100498 if arch == 'aarch64':
499 arch = 'arm64'
Simon Glass7e803e12014-12-01 17:34:06 -0700500 base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
Bin Meng6ad154b2020-04-06 06:06:59 -0700501 versions = ['9.2.0', '7.3.0', '6.4.0', '4.9.4']
Simon Glass7e803e12014-12-01 17:34:06 -0700502 links = []
503 for version in versions:
504 url = '%s/%s/%s/' % (base, arch, version)
Simon Glassc78ed662019-10-31 07:42:53 -0600505 print('Checking: %s' % url)
506 response = urllib.request.urlopen(url)
507 html = tools.ToString(response.read())
Simon Glass7e803e12014-12-01 17:34:06 -0700508 parser = MyHTMLParser(fetch_arch)
509 parser.feed(html)
510 if fetch_arch == 'list':
511 links += parser.links
512 elif parser.arch_link:
513 return url + parser.arch_link
514 if fetch_arch == 'list':
515 return arch, links
516 return None
517
518 def Download(self, url):
519 """Download a file to a temporary directory
520
521 Args:
522 url: URL to download
523 Returns:
524 Tuple:
525 Temporary directory name
526 Full path to the downloaded archive file in that directory,
527 or None if there was an error while downloading
528 """
Simon Glassc78ed662019-10-31 07:42:53 -0600529 print('Downloading: %s' % url)
Simon Glass7e803e12014-12-01 17:34:06 -0700530 leaf = url.split('/')[-1]
531 tmpdir = tempfile.mkdtemp('.buildman')
Simon Glassc78ed662019-10-31 07:42:53 -0600532 response = urllib.request.urlopen(url)
Simon Glass7e803e12014-12-01 17:34:06 -0700533 fname = os.path.join(tmpdir, leaf)
534 fd = open(fname, 'wb')
535 meta = response.info()
Simon Glassc78ed662019-10-31 07:42:53 -0600536 size = int(meta.get('Content-Length'))
Simon Glass7e803e12014-12-01 17:34:06 -0700537 done = 0
538 block_size = 1 << 16
539 status = ''
540
541 # Read the file in chunks and show progress as we go
542 while True:
543 buffer = response.read(block_size)
544 if not buffer:
Simon Glassc78ed662019-10-31 07:42:53 -0600545 print(chr(8) * (len(status) + 1), '\r', end=' ')
Simon Glass7e803e12014-12-01 17:34:06 -0700546 break
547
548 done += len(buffer)
549 fd.write(buffer)
Simon Glassc78ed662019-10-31 07:42:53 -0600550 status = r'%10d MiB [%3d%%]' % (done // 1024 // 1024,
551 done * 100 // size)
Simon Glass7e803e12014-12-01 17:34:06 -0700552 status = status + chr(8) * (len(status) + 1)
Simon Glassc78ed662019-10-31 07:42:53 -0600553 print(status, end=' ')
Simon Glass7e803e12014-12-01 17:34:06 -0700554 sys.stdout.flush()
555 fd.close()
556 if done != size:
Simon Glassc78ed662019-10-31 07:42:53 -0600557 print('Error, failed to download')
Simon Glass7e803e12014-12-01 17:34:06 -0700558 os.remove(fname)
559 fname = None
560 return tmpdir, fname
561
562 def Unpack(self, fname, dest):
563 """Unpack a tar file
564
565 Args:
566 fname: Filename to unpack
567 dest: Destination directory
568 Returns:
569 Directory name of the first entry in the archive, without the
570 trailing /
571 """
572 stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
Trevor Woernere0557a72018-11-21 03:31:12 -0500573 dirs = stdout.splitlines()[1].split('/')[:2]
574 return '/'.join(dirs)
Simon Glass7e803e12014-12-01 17:34:06 -0700575
576 def TestSettingsHasPath(self, path):
Simon Glass43ea1282016-07-27 20:33:03 -0600577 """Check if buildman will find this toolchain
Simon Glass7e803e12014-12-01 17:34:06 -0700578
579 Returns:
580 True if the path is in settings, False if not
581 """
Simon Glassf38a6422016-07-27 20:33:01 -0600582 paths = self.GetPathList(False)
Simon Glass7e803e12014-12-01 17:34:06 -0700583 return path in paths
584
585 def ListArchs(self):
586 """List architectures with available toolchains to download"""
587 host_arch, archives = self.LocateArchUrl('list')
Trevor Woerner10a51622018-11-21 03:31:13 -0500588 re_arch = re.compile('[-a-z0-9.]*[-_]([^-]*)-.*')
Simon Glass7e803e12014-12-01 17:34:06 -0700589 arch_set = set()
590 for archive in archives:
591 # Remove the host architecture from the start
592 arch = re_arch.match(archive[len(host_arch):])
593 if arch:
Trevor Woerner10a51622018-11-21 03:31:13 -0500594 if arch.group(1) != '2.0' and arch.group(1) != '64':
595 arch_set.add(arch.group(1))
Simon Glass7e803e12014-12-01 17:34:06 -0700596 return sorted(arch_set)
597
598 def FetchAndInstall(self, arch):
599 """Fetch and install a new toolchain
600
601 arch:
602 Architecture to fetch, or 'list' to list
603 """
604 # Fist get the URL for this architecture
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600605 col = terminal.Color()
Simon Glassc78ed662019-10-31 07:42:53 -0600606 print(col.Color(col.BLUE, "Downloading toolchain for arch '%s'" % arch))
Simon Glass7e803e12014-12-01 17:34:06 -0700607 url = self.LocateArchUrl(arch)
608 if not url:
Simon Glassc78ed662019-10-31 07:42:53 -0600609 print(("Cannot find toolchain for arch '%s' - use 'list' to list" %
610 arch))
Simon Glass7e803e12014-12-01 17:34:06 -0700611 return 2
612 home = os.environ['HOME']
613 dest = os.path.join(home, '.buildman-toolchains')
614 if not os.path.exists(dest):
615 os.mkdir(dest)
616
617 # Download the tar file for this toolchain and unpack it
618 tmpdir, tarfile = self.Download(url)
619 if not tarfile:
620 return 1
Simon Glassc78ed662019-10-31 07:42:53 -0600621 print(col.Color(col.GREEN, 'Unpacking to: %s' % dest), end=' ')
Simon Glass7e803e12014-12-01 17:34:06 -0700622 sys.stdout.flush()
623 path = self.Unpack(tarfile, dest)
624 os.remove(tarfile)
625 os.rmdir(tmpdir)
Simon Glassc78ed662019-10-31 07:42:53 -0600626 print()
Simon Glass7e803e12014-12-01 17:34:06 -0700627
628 # Check that the toolchain works
Simon Glassc78ed662019-10-31 07:42:53 -0600629 print(col.Color(col.GREEN, 'Testing'))
Simon Glass7e803e12014-12-01 17:34:06 -0700630 dirpath = os.path.join(dest, path)
Simon Glassf6757502015-03-02 17:05:15 -0700631 compiler_fname_list = self.ScanPath(dirpath, True)
632 if not compiler_fname_list:
Simon Glassc78ed662019-10-31 07:42:53 -0600633 print('Could not locate C compiler - fetch failed.')
Simon Glass7e803e12014-12-01 17:34:06 -0700634 return 1
Simon Glassf6757502015-03-02 17:05:15 -0700635 if len(compiler_fname_list) != 1:
Simon Glassc78ed662019-10-31 07:42:53 -0600636 print(col.Color(col.RED, 'Warning, ambiguous toolchains: %s' %
637 ', '.join(compiler_fname_list)))
Simon Glassf6757502015-03-02 17:05:15 -0700638 toolchain = Toolchain(compiler_fname_list[0], True, True)
Simon Glass7e803e12014-12-01 17:34:06 -0700639
640 # Make sure that it will be found by buildman
641 if not self.TestSettingsHasPath(dirpath):
Simon Glassc78ed662019-10-31 07:42:53 -0600642 print(("Adding 'download' to config file '%s'" %
643 bsettings.config_fname))
Simon Glassf90e2912016-07-27 20:33:05 -0600644 bsettings.SetItem('toolchain', 'download', '%s/*/*' % dest)
Simon Glass7e803e12014-12-01 17:34:06 -0700645 return 0