blob: 958f36f9f612790c4732bd01fd1e82ce3185a023 [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 Glass131444f2023-02-23 18:18:04 -070014from u_boot_pylib import command
15from u_boot_pylib import terminal
16from u_boot_pylib 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:
Simon Glass6a8d6ef2024-11-08 08:23:48 -070068 fname: Filename of the gcc component, possibly with ~ or $HOME in it
Simon Glassc05694f2013-04-03 11:07:16 +000069 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 """
Simon Glass6a8d6ef2024-11-08 08:23:48 -070074 fname = os.path.expanduser(fname)
Simon Glassc05694f2013-04-03 11:07:16 +000075 self.gcc = fname
76 self.path = os.path.dirname(fname)
Simon Glassf77ca5b2019-01-07 16:44:20 -070077 self.override_toolchain = override_toolchain
Simon Glass28ed0062014-12-01 17:33:58 -070078
79 # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
80 # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
81 basename = os.path.basename(fname)
82 pos = basename.rfind('-')
83 self.cross = basename[:pos + 1] if pos != -1 else ''
84
85 # The architecture is the first part of the name
Simon Glassc05694f2013-04-03 11:07:16 +000086 pos = self.cross.find('-')
Simon Glassc6cdd3e2016-03-06 19:45:38 -070087 if arch:
88 self.arch = arch
89 else:
90 self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
Simon Glassf77ca5b2019-01-07 16:44:20 -070091 if self.arch == 'sandbox' and override_toolchain:
92 self.gcc = override_toolchain
Simon Glassc05694f2013-04-03 11:07:16 +000093
Tom Rini3f6f9b22024-07-05 14:34:07 -060094 env = self.MakeEnvironment(False)
Simon Glassc05694f2013-04-03 11:07:16 +000095
96 # As a basic sanity check, run the C compiler with --version
97 cmd = [fname, '--version']
Simon Glass7b971292016-03-06 19:45:37 -070098 if priority == PRIORITY_CALC:
99 self.priority = self.GetPriority(fname)
100 else:
101 self.priority = priority
Simon Glassc05694f2013-04-03 11:07:16 +0000102 if test:
Simon Glass840be732022-01-29 14:14:05 -0700103 result = command.run_pipe([cmd], capture=True, env=env,
Stephen Warren288d7672013-10-09 14:28:09 -0600104 raise_on_error=False)
Simon Glassc05694f2013-04-03 11:07:16 +0000105 self.ok = result.return_code == 0
106 if verbose:
Simon Glassc78ed662019-10-31 07:42:53 -0600107 print('Tool chain test: ', end=' ')
Simon Glassc05694f2013-04-03 11:07:16 +0000108 if self.ok:
Simon Glassc78ed662019-10-31 07:42:53 -0600109 print("OK, arch='%s', priority %d" % (self.arch,
110 self.priority))
Simon Glassc05694f2013-04-03 11:07:16 +0000111 else:
Simon Glassc78ed662019-10-31 07:42:53 -0600112 print('BAD')
Simon Glass6a8d6ef2024-11-08 08:23:48 -0700113 print(f"Command: {' '.join(cmd)}")
Simon Glassc78ed662019-10-31 07:42:53 -0600114 print(result.stdout)
115 print(result.stderr)
Simon Glassc05694f2013-04-03 11:07:16 +0000116 else:
117 self.ok = True
Simon Glassc05694f2013-04-03 11:07:16 +0000118
119 def GetPriority(self, fname):
120 """Return the priority of the toolchain.
121
122 Toolchains are ranked according to their suitability by their
123 filename prefix.
124
125 Args:
126 fname: Filename of toolchain
127 Returns:
Simon Glass7b971292016-03-06 19:45:37 -0700128 Priority of toolchain, PRIORITY_CALC=highest, 20=lowest.
Simon Glassc05694f2013-04-03 11:07:16 +0000129 """
Masahiro Yamadafa25e1f2014-07-07 09:47:45 +0900130 priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
Tom Rini6abc9c82017-04-14 19:47:50 -0400131 '-none-linux-gnueabi', '-none-linux-gnueabihf', '-uclinux',
132 '-none-eabi', '-gentoo-linux-gnu', '-linux-gnueabi',
133 '-linux-gnueabihf', '-le-linux', '-uclinux']
Simon Glassc05694f2013-04-03 11:07:16 +0000134 for prio in range(len(priority_list)):
135 if priority_list[prio] in fname:
Simon Glass7b971292016-03-06 19:45:37 -0700136 return PRIORITY_CALC + prio
137 return PRIORITY_CALC + prio
Simon Glassc05694f2013-04-03 11:07:16 +0000138
York Sunfb197a82016-10-04 14:33:51 -0700139 def GetWrapper(self, show_warning=True):
140 """Get toolchain wrapper from the setting file.
141 """
Simon Glass45572632019-01-07 16:44:24 -0700142 value = ''
Simon Glass06b83a52023-07-19 17:49:05 -0600143 for name, value in bsettings.get_items('toolchain-wrapper'):
York Sunfb197a82016-10-04 14:33:51 -0700144 if not value:
Simon Glassc78ed662019-10-31 07:42:53 -0600145 print("Warning: Wrapper not found")
York Sunfb197a82016-10-04 14:33:51 -0700146 if value:
147 value = value + ' '
148
149 return value
150
Simon Glass48ac42e2019-12-05 15:59:14 -0700151 def GetEnvArgs(self, which):
152 """Get an environment variable/args value based on the the toolchain
153
154 Args:
155 which: VAR_... value to get
156
157 Returns:
158 Value of that environment variable or arguments
159 """
Simon Glass48ac42e2019-12-05 15:59:14 -0700160 if which == VAR_CROSS_COMPILE:
Simon Glass09753b32023-03-10 12:48:51 -0800161 wrapper = self.GetWrapper()
162 base = '' if self.arch == 'sandbox' else self.path
Jerome Forissieraa276952024-09-11 11:58:14 +0200163 if (base == '' and self.cross == ''):
164 return ''
Simon Glass09753b32023-03-10 12:48:51 -0800165 return wrapper + os.path.join(base, self.cross)
Simon Glass48ac42e2019-12-05 15:59:14 -0700166 elif which == VAR_PATH:
167 return self.path
168 elif which == VAR_ARCH:
169 return self.arch
170 elif which == VAR_MAKE_ARGS:
171 args = self.MakeArgs()
172 if args:
173 return ' '.join(args)
174 return ''
175 else:
176 raise ValueError('Unknown arg to GetEnvArgs (%d)' % which)
177
Simon Glass038a3dd2024-08-15 13:57:44 -0600178 def MakeEnvironment(self, full_path, env=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000179 """Returns an environment for using the toolchain.
180
Simon Glass58225462024-06-23 11:56:19 -0600181 This takes the current environment and adds CROSS_COMPILE so that
Daniel Schwierzeckc9e5f0c2017-06-08 03:07:08 +0200182 the tool chain will operate correctly. This also disables localized
Simon Glass58225462024-06-23 11:56:19 -0600183 output and possibly Unicode encoded output of all build tools by
Simon Glass038a3dd2024-08-15 13:57:44 -0600184 adding LC_ALL=C. For the case where full_path is False, it prepends
185 the toolchain to PATH
Simon Glassd48a46c2014-12-01 17:34:00 -0700186
Simon Glass93008e22021-04-11 16:27:28 +1200187 Note that os.environb is used to obtain the environment, since in some
188 cases the environment many contain non-ASCII characters and we see
189 errors like:
190
191 UnicodeEncodeError: 'utf-8' codec can't encode characters in position
192 569-570: surrogates not allowed
193
Simon Glass038a3dd2024-08-15 13:57:44 -0600194 When running inside a Python venv, care is taken not to put the
195 toolchain path before the venv path, so that builds initiated by
196 buildman will still respect the venv.
197
Simon Glassd48a46c2014-12-01 17:34:00 -0700198 Args:
Tom Rini3f6f9b22024-07-05 14:34:07 -0600199 full_path: Return the full path in CROSS_COMPILE and don't set
200 PATH
Simon Glass038a3dd2024-08-15 13:57:44 -0600201 env (dict of bytes): Original environment, used for testing
Simon Glassf77ca5b2019-01-07 16:44:20 -0700202 Returns:
Simon Glass93008e22021-04-11 16:27:28 +1200203 Dict containing the (bytes) environment to use. This is based on the
Tom Rini3f6f9b22024-07-05 14:34:07 -0600204 current environment, with changes as needed to CROSS_COMPILE, PATH
205 and LC_ALL.
Simon Glassc05694f2013-04-03 11:07:16 +0000206 """
Simon Glass038a3dd2024-08-15 13:57:44 -0600207 env = dict(env or os.environb)
208
York Sunfb197a82016-10-04 14:33:51 -0700209 wrapper = self.GetWrapper()
210
Simon Glassf77ca5b2019-01-07 16:44:20 -0700211 if self.override_toolchain:
212 # We'll use MakeArgs() to provide this
213 pass
Jerome Forissieraa276952024-09-11 11:58:14 +0200214 elif full_path and self.cross:
Simon Glass80025522022-01-29 14:14:04 -0700215 env[b'CROSS_COMPILE'] = tools.to_bytes(
Simon Glass93008e22021-04-11 16:27:28 +1200216 wrapper + os.path.join(self.path, self.cross))
Jerome Forissieraa276952024-09-11 11:58:14 +0200217 elif self.cross:
Tom Rini3f6f9b22024-07-05 14:34:07 -0600218 env[b'CROSS_COMPILE'] = tools.to_bytes(wrapper + self.cross)
Simon Glass038a3dd2024-08-15 13:57:44 -0600219
220 # Detect a Python virtualenv and avoid defeating it
221 if sys.prefix != sys.base_prefix:
222 paths = env[b'PATH'].split(b':')
223 new_paths = []
224 to_insert = tools.to_bytes(self.path)
225 insert_after = tools.to_bytes(sys.prefix)
226 for path in paths:
227 new_paths.append(path)
228 if to_insert and path.startswith(insert_after):
229 new_paths.append(to_insert)
230 to_insert = None
231 if to_insert:
232 new_paths.append(to_insert)
233 env[b'PATH'] = b':'.join(new_paths)
234 else:
235 env[b'PATH'] = tools.to_bytes(self.path) + b':' + env[b'PATH']
Simon Glassd48a46c2014-12-01 17:34:00 -0700236
Simon Glass93008e22021-04-11 16:27:28 +1200237 env[b'LC_ALL'] = b'C'
Daniel Schwierzeckc9e5f0c2017-06-08 03:07:08 +0200238
Simon Glassc05694f2013-04-03 11:07:16 +0000239 return env
240
Simon Glassf77ca5b2019-01-07 16:44:20 -0700241 def MakeArgs(self):
242 """Create the 'make' arguments for a toolchain
243
244 This is only used when the toolchain is being overridden. Since the
245 U-Boot Makefile sets CC and HOSTCC explicitly we cannot rely on the
246 environment (and MakeEnvironment()) to override these values. This
247 function returns the arguments to accomplish this.
248
249 Returns:
250 List of arguments to pass to 'make'
251 """
252 if self.override_toolchain:
253 return ['HOSTCC=%s' % self.override_toolchain,
254 'CC=%s' % self.override_toolchain]
255 return []
256
Simon Glassc05694f2013-04-03 11:07:16 +0000257
258class Toolchains:
259 """Manage a list of toolchains for building U-Boot
260
261 We select one toolchain for each architecture type
262
263 Public members:
264 toolchains: Dict of Toolchain objects, keyed by architecture name
Simon Glassf5902732016-03-12 18:50:32 -0700265 prefixes: Dict of prefixes to check, keyed by architecture. This can
266 be a full path and toolchain prefix, for example
267 {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
268 something on the search path, for example
269 {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
Simon Glassc05694f2013-04-03 11:07:16 +0000270 paths: List of paths to check for toolchains (may contain wildcards)
271 """
272
Simon Glassf77ca5b2019-01-07 16:44:20 -0700273 def __init__(self, override_toolchain=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000274 self.toolchains = {}
Simon Glassf5902732016-03-12 18:50:32 -0700275 self.prefixes = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000276 self.paths = []
Simon Glassf77ca5b2019-01-07 16:44:20 -0700277 self.override_toolchain = override_toolchain
Simon Glass06b83a52023-07-19 17:49:05 -0600278 self._make_flags = dict(bsettings.get_items('make-flags'))
Simon Glassed098bb2014-09-05 19:00:13 -0600279
Simon Glassf38a6422016-07-27 20:33:01 -0600280 def GetPathList(self, show_warning=True):
Simon Glass7e803e12014-12-01 17:34:06 -0700281 """Get a list of available toolchain paths
282
Simon Glassf38a6422016-07-27 20:33:01 -0600283 Args:
284 show_warning: True to show a warning if there are no tool chains.
285
Simon Glass7e803e12014-12-01 17:34:06 -0700286 Returns:
287 List of strings, each a path to a toolchain mentioned in the
288 [toolchain] section of the settings file.
289 """
Simon Glass06b83a52023-07-19 17:49:05 -0600290 toolchains = bsettings.get_items('toolchain')
Simon Glassf38a6422016-07-27 20:33:01 -0600291 if show_warning and not toolchains:
Simon Glassc78ed662019-10-31 07:42:53 -0600292 print(("Warning: No tool chains. Please run 'buildman "
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600293 "--fetch-arch all' to download all available toolchains, or "
294 "add a [toolchain] section to your buildman config file "
Heinrich Schuchardtf04e8002023-01-19 16:22:10 +0100295 "%s. See buildman.rst for details" %
Simon Glassc78ed662019-10-31 07:42:53 -0600296 bsettings.config_fname))
Simon Glasscc246fb2013-09-23 17:35:17 -0600297
Simon Glass7e803e12014-12-01 17:34:06 -0700298 paths = []
Simon Glasscc246fb2013-09-23 17:35:17 -0600299 for name, value in toolchains:
Simon Glass6a8d6ef2024-11-08 08:23:48 -0700300 fname = os.path.expanduser(value)
Simon Glassc05694f2013-04-03 11:07:16 +0000301 if '*' in value:
Simon Glass6a8d6ef2024-11-08 08:23:48 -0700302 paths += glob.glob(fname)
Simon Glassc05694f2013-04-03 11:07:16 +0000303 else:
Simon Glass6a8d6ef2024-11-08 08:23:48 -0700304 paths.append(fname)
Simon Glass7e803e12014-12-01 17:34:06 -0700305 return paths
306
Simon Glassf38a6422016-07-27 20:33:01 -0600307 def GetSettings(self, show_warning=True):
308 """Get toolchain settings from the settings file.
309
310 Args:
311 show_warning: True to show a warning if there are no tool chains.
312 """
Simon Glass06b83a52023-07-19 17:49:05 -0600313 self.prefixes = bsettings.get_items('toolchain-prefix')
Simon Glassf38a6422016-07-27 20:33:01 -0600314 self.paths += self.GetPathList(show_warning)
Simon Glassc05694f2013-04-03 11:07:16 +0000315
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700316 def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
317 arch=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000318 """Add a toolchain to our list
319
320 We select the given toolchain as our preferred one for its
321 architecture if it is a higher priority than the others.
322
323 Args:
324 fname: Filename of toolchain's gcc driver
325 test: True to run the toolchain to test it
Simon Glass7b971292016-03-06 19:45:37 -0700326 priority: Priority to use for this toolchain
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700327 arch: Toolchain architecture, or None if not known
Simon Glassc05694f2013-04-03 11:07:16 +0000328 """
Simon Glassf77ca5b2019-01-07 16:44:20 -0700329 toolchain = Toolchain(fname, test, verbose, priority, arch,
330 self.override_toolchain)
Simon Glassc05694f2013-04-03 11:07:16 +0000331 add_it = toolchain.ok
Simon Glassc05694f2013-04-03 11:07:16 +0000332 if add_it:
Simon Glasse2c35a02024-11-08 08:23:47 -0700333 if toolchain.arch in self.toolchains:
334 add_it = (toolchain.priority <
335 self.toolchains[toolchain.arch].priority)
336 if add_it:
337 self.toolchains[toolchain.arch] = toolchain
338 elif verbose:
339 print(("Toolchain '%s' at priority %d will be ignored because "
340 "another toolchain for arch '%s' has priority %d" %
341 (toolchain.gcc, toolchain.priority, toolchain.arch,
342 self.toolchains[toolchain.arch].priority)))
Simon Glassc05694f2013-04-03 11:07:16 +0000343
Simon Glass7e803e12014-12-01 17:34:06 -0700344 def ScanPath(self, path, verbose):
345 """Scan a path for a valid toolchain
346
347 Args:
348 path: Path to scan
349 verbose: True to print out progress information
350 Returns:
351 Filename of C compiler if found, else None
352 """
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100353 fnames = []
Simon Glass7e803e12014-12-01 17:34:06 -0700354 for subdir in ['.', 'bin', 'usr/bin']:
355 dirname = os.path.join(path, subdir)
Simon Glassc78ed662019-10-31 07:42:53 -0600356 if verbose: print(" - looking in '%s'" % dirname)
Simon Glass7e803e12014-12-01 17:34:06 -0700357 for fname in glob.glob(dirname + '/*gcc'):
Simon Glassc78ed662019-10-31 07:42:53 -0600358 if verbose: print(" - found '%s'" % fname)
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100359 fnames.append(fname)
360 return fnames
Simon Glass7e803e12014-12-01 17:34:06 -0700361
Simon Glassf5902732016-03-12 18:50:32 -0700362 def ScanPathEnv(self, fname):
363 """Scan the PATH environment variable for a given filename.
364
365 Args:
366 fname: Filename to scan for
367 Returns:
368 List of matching pathanames, or [] if none
369 """
370 pathname_list = []
371 for path in os.environ["PATH"].split(os.pathsep):
372 path = path.strip('"')
373 pathname = os.path.join(path, fname)
374 if os.path.exists(pathname):
375 pathname_list.append(pathname)
376 return pathname_list
Simon Glass7e803e12014-12-01 17:34:06 -0700377
Simon Glass6a8d6ef2024-11-08 08:23:48 -0700378 def Scan(self, verbose, raise_on_error=True):
Simon Glassc05694f2013-04-03 11:07:16 +0000379 """Scan for available toolchains and select the best for each arch.
380
381 We look for all the toolchains we can file, figure out the
382 architecture for each, and whether it works. Then we select the
383 highest priority toolchain for each arch.
384
385 Args:
386 verbose: True to print out progress information
387 """
Simon Glassc78ed662019-10-31 07:42:53 -0600388 if verbose: print('Scanning for tool chains')
Simon Glassf5902732016-03-12 18:50:32 -0700389 for name, value in self.prefixes:
Simon Glass6a8d6ef2024-11-08 08:23:48 -0700390 fname = os.path.expanduser(value)
391 if verbose: print(" - scanning prefix '%s'" % fname)
392 if os.path.exists(fname):
393 self.Add(fname, True, verbose, PRIORITY_FULL_PREFIX, name)
Simon Glassf5902732016-03-12 18:50:32 -0700394 continue
Simon Glass6a8d6ef2024-11-08 08:23:48 -0700395 fname += 'gcc'
Simon Glassf5902732016-03-12 18:50:32 -0700396 if os.path.exists(fname):
397 self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
398 continue
399 fname_list = self.ScanPathEnv(fname)
400 for f in fname_list:
401 self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
402 if not fname_list:
Simon Glass6a8d6ef2024-11-08 08:23:48 -0700403 msg = f"No tool chain found for prefix '{fname}'"
404 if raise_on_error:
405 raise ValueError(msg)
406 else:
407 print(f'Error: {msg}')
Simon Glassc05694f2013-04-03 11:07:16 +0000408 for path in self.paths:
Simon Glassc78ed662019-10-31 07:42:53 -0600409 if verbose: print(" - scanning path '%s'" % path)
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100410 fnames = self.ScanPath(path, verbose)
411 for fname in fnames:
Simon Glass7e803e12014-12-01 17:34:06 -0700412 self.Add(fname, True, verbose)
Simon Glassc05694f2013-04-03 11:07:16 +0000413
414 def List(self):
415 """List out the selected toolchains for each architecture"""
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600416 col = terminal.Color()
Simon Glassf45d3742022-01-29 14:14:17 -0700417 print(col.build(col.BLUE, 'List of available toolchains (%d):' %
Simon Glassc78ed662019-10-31 07:42:53 -0600418 len(self.toolchains)))
Simon Glassc05694f2013-04-03 11:07:16 +0000419 if len(self.toolchains):
Simon Glassc78ed662019-10-31 07:42:53 -0600420 for key, value in sorted(self.toolchains.items()):
421 print('%-10s: %s' % (key, value.gcc))
Simon Glassc05694f2013-04-03 11:07:16 +0000422 else:
Simon Glassc78ed662019-10-31 07:42:53 -0600423 print('None')
Simon Glassc05694f2013-04-03 11:07:16 +0000424
425 def Select(self, arch):
426 """Returns the toolchain for a given architecture
427
428 Args:
429 args: Name of architecture (e.g. 'arm', 'ppc_8xx')
430
431 returns:
432 toolchain object, or None if none found
433 """
Simon Glass06b83a52023-07-19 17:49:05 -0600434 for tag, value in bsettings.get_items('toolchain-alias'):
Simon Glassc1528c12014-12-01 17:34:05 -0700435 if arch == tag:
436 for alias in value.split():
437 if alias in self.toolchains:
438 return self.toolchains[alias]
Simon Glassc05694f2013-04-03 11:07:16 +0000439
440 if not arch in self.toolchains:
Simon Glassc78ed662019-10-31 07:42:53 -0600441 raise ValueError("No tool chain found for arch '%s'" % arch)
Simon Glassc05694f2013-04-03 11:07:16 +0000442 return self.toolchains[arch]
Simon Glasscc246fb2013-09-23 17:35:17 -0600443
444 def ResolveReferences(self, var_dict, args):
445 """Resolve variable references in a string
446
447 This converts ${blah} within the string to the value of blah.
448 This function works recursively.
449
Simon Glass1f3a1222024-09-21 19:57:57 +0200450 Resolved string
451
Simon Glasscc246fb2013-09-23 17:35:17 -0600452 Args:
453 var_dict: Dictionary containing variables and their values
454 args: String containing make arguments
455 Returns:
Simon Glass06b83a52023-07-19 17:49:05 -0600456 >>> bsettings.setup(None)
Simon Glasscc246fb2013-09-23 17:35:17 -0600457 >>> tcs = Toolchains()
458 >>> tcs.Add('fred', False)
459 >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
460 'second' : '2nd'}
461 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
462 'this=OBLIQUE_set'
463 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
464 'this=OBLIQUE_setfi2ndrstnd'
465 """
Simon Glass1f3a1222024-09-21 19:57:57 +0200466 re_var = re.compile(r'(\$\{[-_a-z0-9A-Z]{1,}\})')
Simon Glasscc246fb2013-09-23 17:35:17 -0600467
468 while True:
469 m = re_var.search(args)
470 if not m:
471 break
472 lookup = m.group(0)[2:-1]
473 value = var_dict.get(lookup, '')
474 args = args[:m.start(0)] + value + args[m.end(0):]
475 return args
476
Simon Glass8132f982022-07-11 19:03:57 -0600477 def GetMakeArguments(self, brd):
Simon Glasscc246fb2013-09-23 17:35:17 -0600478 """Returns 'make' arguments for a given board
479
480 The flags are in a section called 'make-flags'. Flags are named
481 after the target they represent, for example snapper9260=TESTING=1
482 will pass TESTING=1 to make when building the snapper9260 board.
483
484 References to other boards can be added in the string also. For
485 example:
486
487 [make-flags]
488 at91-boards=ENABLE_AT91_TEST=1
489 snapper9260=${at91-boards} BUILD_TAG=442
490 snapper9g45=${at91-boards} BUILD_TAG=443
491
492 This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
493 and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
494
495 A special 'target' variable is set to the board target.
496
497 Args:
Simon Glass8132f982022-07-11 19:03:57 -0600498 brd: Board object for the board to check.
Simon Glasscc246fb2013-09-23 17:35:17 -0600499 Returns:
500 'make' flags for that board, or '' if none
501 """
Simon Glass8132f982022-07-11 19:03:57 -0600502 self._make_flags['target'] = brd.target
Simon Glasscc246fb2013-09-23 17:35:17 -0600503 arg_str = self.ResolveReferences(self._make_flags,
Simon Glass8132f982022-07-11 19:03:57 -0600504 self._make_flags.get(brd.target, ''))
Simon Glass1f3a1222024-09-21 19:57:57 +0200505 args = re.findall(r"(?:\".*?\"|\S)+", arg_str)
Simon Glasscc246fb2013-09-23 17:35:17 -0600506 i = 0
507 while i < len(args):
Cristian Ciocaltea9da17fb2019-11-24 22:30:26 +0200508 args[i] = args[i].replace('"', '')
Simon Glasscc246fb2013-09-23 17:35:17 -0600509 if not args[i]:
510 del args[i]
511 else:
512 i += 1
513 return args
Simon Glass7e803e12014-12-01 17:34:06 -0700514
515 def LocateArchUrl(self, fetch_arch):
516 """Find a toolchain available online
517
518 Look in standard places for available toolchains. At present the
519 only standard place is at kernel.org.
520
521 Args:
522 arch: Architecture to look for, or 'list' for all
523 Returns:
524 If fetch_arch is 'list', a tuple:
525 Machine architecture (e.g. x86_64)
526 List of toolchains
527 else
528 URL containing this toolchain, if avaialble, else None
529 """
Simon Glass840be732022-01-29 14:14:05 -0700530 arch = command.output_one_line('uname', '-m')
Matthias Bruggerfb50a262020-01-17 10:53:37 +0100531 if arch == 'aarch64':
532 arch = 'arm64'
Simon Glass7e803e12014-12-01 17:34:06 -0700533 base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
Tom Rini3bd5ed72023-08-25 13:21:26 -0400534 versions = ['13.2.0', '12.2.0']
Simon Glass7e803e12014-12-01 17:34:06 -0700535 links = []
536 for version in versions:
537 url = '%s/%s/%s/' % (base, arch, version)
Simon Glassc78ed662019-10-31 07:42:53 -0600538 print('Checking: %s' % url)
539 response = urllib.request.urlopen(url)
Simon Glass80025522022-01-29 14:14:04 -0700540 html = tools.to_string(response.read())
Simon Glass7e803e12014-12-01 17:34:06 -0700541 parser = MyHTMLParser(fetch_arch)
542 parser.feed(html)
543 if fetch_arch == 'list':
544 links += parser.links
545 elif parser.arch_link:
546 return url + parser.arch_link
547 if fetch_arch == 'list':
548 return arch, links
549 return None
550
Simon Glass7e803e12014-12-01 17:34:06 -0700551 def Unpack(self, fname, dest):
552 """Unpack a tar file
553
554 Args:
555 fname: Filename to unpack
556 dest: Destination directory
557 Returns:
558 Directory name of the first entry in the archive, without the
559 trailing /
560 """
Simon Glass840be732022-01-29 14:14:05 -0700561 stdout = command.output('tar', 'xvfJ', fname, '-C', dest)
Trevor Woernere0557a72018-11-21 03:31:12 -0500562 dirs = stdout.splitlines()[1].split('/')[:2]
563 return '/'.join(dirs)
Simon Glass7e803e12014-12-01 17:34:06 -0700564
565 def TestSettingsHasPath(self, path):
Simon Glass43ea1282016-07-27 20:33:03 -0600566 """Check if buildman will find this toolchain
Simon Glass7e803e12014-12-01 17:34:06 -0700567
568 Returns:
569 True if the path is in settings, False if not
570 """
Simon Glassf38a6422016-07-27 20:33:01 -0600571 paths = self.GetPathList(False)
Simon Glass7e803e12014-12-01 17:34:06 -0700572 return path in paths
573
574 def ListArchs(self):
575 """List architectures with available toolchains to download"""
576 host_arch, archives = self.LocateArchUrl('list')
Trevor Woerner10a51622018-11-21 03:31:13 -0500577 re_arch = re.compile('[-a-z0-9.]*[-_]([^-]*)-.*')
Simon Glass7e803e12014-12-01 17:34:06 -0700578 arch_set = set()
579 for archive in archives:
580 # Remove the host architecture from the start
581 arch = re_arch.match(archive[len(host_arch):])
582 if arch:
Trevor Woerner10a51622018-11-21 03:31:13 -0500583 if arch.group(1) != '2.0' and arch.group(1) != '64':
584 arch_set.add(arch.group(1))
Simon Glass7e803e12014-12-01 17:34:06 -0700585 return sorted(arch_set)
586
587 def FetchAndInstall(self, arch):
588 """Fetch and install a new toolchain
589
590 arch:
591 Architecture to fetch, or 'list' to list
592 """
593 # Fist get the URL for this architecture
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600594 col = terminal.Color()
Simon Glassf45d3742022-01-29 14:14:17 -0700595 print(col.build(col.BLUE, "Downloading toolchain for arch '%s'" % arch))
Simon Glass7e803e12014-12-01 17:34:06 -0700596 url = self.LocateArchUrl(arch)
597 if not url:
Simon Glassc78ed662019-10-31 07:42:53 -0600598 print(("Cannot find toolchain for arch '%s' - use 'list' to list" %
599 arch))
Simon Glass7e803e12014-12-01 17:34:06 -0700600 return 2
601 home = os.environ['HOME']
602 dest = os.path.join(home, '.buildman-toolchains')
603 if not os.path.exists(dest):
604 os.mkdir(dest)
605
606 # Download the tar file for this toolchain and unpack it
Simon Glass80025522022-01-29 14:14:04 -0700607 tarfile, tmpdir = tools.download(url, '.buildman')
Simon Glass7e803e12014-12-01 17:34:06 -0700608 if not tarfile:
609 return 1
Simon Glassf45d3742022-01-29 14:14:17 -0700610 print(col.build(col.GREEN, 'Unpacking to: %s' % dest), end=' ')
Simon Glass7e803e12014-12-01 17:34:06 -0700611 sys.stdout.flush()
612 path = self.Unpack(tarfile, dest)
613 os.remove(tarfile)
614 os.rmdir(tmpdir)
Simon Glassc78ed662019-10-31 07:42:53 -0600615 print()
Simon Glass7e803e12014-12-01 17:34:06 -0700616
617 # Check that the toolchain works
Simon Glassf45d3742022-01-29 14:14:17 -0700618 print(col.build(col.GREEN, 'Testing'))
Simon Glass7e803e12014-12-01 17:34:06 -0700619 dirpath = os.path.join(dest, path)
Simon Glassf6757502015-03-02 17:05:15 -0700620 compiler_fname_list = self.ScanPath(dirpath, True)
621 if not compiler_fname_list:
Simon Glassc78ed662019-10-31 07:42:53 -0600622 print('Could not locate C compiler - fetch failed.')
Simon Glass7e803e12014-12-01 17:34:06 -0700623 return 1
Simon Glassf6757502015-03-02 17:05:15 -0700624 if len(compiler_fname_list) != 1:
Simon Glassf45d3742022-01-29 14:14:17 -0700625 print(col.build(col.RED, 'Warning, ambiguous toolchains: %s' %
Simon Glassc78ed662019-10-31 07:42:53 -0600626 ', '.join(compiler_fname_list)))
Simon Glassf6757502015-03-02 17:05:15 -0700627 toolchain = Toolchain(compiler_fname_list[0], True, True)
Simon Glass7e803e12014-12-01 17:34:06 -0700628
629 # Make sure that it will be found by buildman
630 if not self.TestSettingsHasPath(dirpath):
Simon Glassc78ed662019-10-31 07:42:53 -0600631 print(("Adding 'download' to config file '%s'" %
632 bsettings.config_fname))
Simon Glass06b83a52023-07-19 17:49:05 -0600633 bsettings.set_item('toolchain', 'download', '%s/*/*' % dest)
Simon Glass7e803e12014-12-01 17:34:06 -0700634 return 0