blob: dab350fec313fc1357a59c7d35dbfdb3e9efdb27 [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:
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
Tom Rini3f6f9b22024-07-05 14:34:07 -060093 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:
Simon Glass840be732022-01-29 14:14:05 -0700102 result = command.run_pipe([cmd], capture=True, env=env,
Stephen Warren288d7672013-10-09 14:28:09 -0600103 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 = ''
Simon Glass06b83a52023-07-19 17:49:05 -0600142 for name, value in bsettings.get_items('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 """
Simon Glass48ac42e2019-12-05 15:59:14 -0700159 if which == VAR_CROSS_COMPILE:
Simon Glass09753b32023-03-10 12:48:51 -0800160 wrapper = self.GetWrapper()
161 base = '' if self.arch == 'sandbox' else self.path
Jerome Forissieraa276952024-09-11 11:58:14 +0200162 if (base == '' and self.cross == ''):
163 return ''
Simon Glass09753b32023-03-10 12:48:51 -0800164 return wrapper + os.path.join(base, self.cross)
Simon Glass48ac42e2019-12-05 15:59:14 -0700165 elif which == VAR_PATH:
166 return self.path
167 elif which == VAR_ARCH:
168 return self.arch
169 elif which == VAR_MAKE_ARGS:
170 args = self.MakeArgs()
171 if args:
172 return ' '.join(args)
173 return ''
174 else:
175 raise ValueError('Unknown arg to GetEnvArgs (%d)' % which)
176
Simon Glass038a3dd2024-08-15 13:57:44 -0600177 def MakeEnvironment(self, full_path, env=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000178 """Returns an environment for using the toolchain.
179
Simon Glass58225462024-06-23 11:56:19 -0600180 This takes the current environment and adds CROSS_COMPILE so that
Daniel Schwierzeckc9e5f0c2017-06-08 03:07:08 +0200181 the tool chain will operate correctly. This also disables localized
Simon Glass58225462024-06-23 11:56:19 -0600182 output and possibly Unicode encoded output of all build tools by
Simon Glass038a3dd2024-08-15 13:57:44 -0600183 adding LC_ALL=C. For the case where full_path is False, it prepends
184 the toolchain to PATH
Simon Glassd48a46c2014-12-01 17:34:00 -0700185
Simon Glass93008e22021-04-11 16:27:28 +1200186 Note that os.environb is used to obtain the environment, since in some
187 cases the environment many contain non-ASCII characters and we see
188 errors like:
189
190 UnicodeEncodeError: 'utf-8' codec can't encode characters in position
191 569-570: surrogates not allowed
192
Simon Glass038a3dd2024-08-15 13:57:44 -0600193 When running inside a Python venv, care is taken not to put the
194 toolchain path before the venv path, so that builds initiated by
195 buildman will still respect the venv.
196
Simon Glassd48a46c2014-12-01 17:34:00 -0700197 Args:
Tom Rini3f6f9b22024-07-05 14:34:07 -0600198 full_path: Return the full path in CROSS_COMPILE and don't set
199 PATH
Simon Glass038a3dd2024-08-15 13:57:44 -0600200 env (dict of bytes): Original environment, used for testing
Simon Glassf77ca5b2019-01-07 16:44:20 -0700201 Returns:
Simon Glass93008e22021-04-11 16:27:28 +1200202 Dict containing the (bytes) environment to use. This is based on the
Tom Rini3f6f9b22024-07-05 14:34:07 -0600203 current environment, with changes as needed to CROSS_COMPILE, PATH
204 and LC_ALL.
Simon Glassc05694f2013-04-03 11:07:16 +0000205 """
Simon Glass038a3dd2024-08-15 13:57:44 -0600206 env = dict(env or os.environb)
207
York Sunfb197a82016-10-04 14:33:51 -0700208 wrapper = self.GetWrapper()
209
Simon Glassf77ca5b2019-01-07 16:44:20 -0700210 if self.override_toolchain:
211 # We'll use MakeArgs() to provide this
212 pass
Jerome Forissieraa276952024-09-11 11:58:14 +0200213 elif full_path and self.cross:
Simon Glass80025522022-01-29 14:14:04 -0700214 env[b'CROSS_COMPILE'] = tools.to_bytes(
Simon Glass93008e22021-04-11 16:27:28 +1200215 wrapper + os.path.join(self.path, self.cross))
Jerome Forissieraa276952024-09-11 11:58:14 +0200216 elif self.cross:
Tom Rini3f6f9b22024-07-05 14:34:07 -0600217 env[b'CROSS_COMPILE'] = tools.to_bytes(wrapper + self.cross)
Simon Glass038a3dd2024-08-15 13:57:44 -0600218
219 # Detect a Python virtualenv and avoid defeating it
220 if sys.prefix != sys.base_prefix:
221 paths = env[b'PATH'].split(b':')
222 new_paths = []
223 to_insert = tools.to_bytes(self.path)
224 insert_after = tools.to_bytes(sys.prefix)
225 for path in paths:
226 new_paths.append(path)
227 if to_insert and path.startswith(insert_after):
228 new_paths.append(to_insert)
229 to_insert = None
230 if to_insert:
231 new_paths.append(to_insert)
232 env[b'PATH'] = b':'.join(new_paths)
233 else:
234 env[b'PATH'] = tools.to_bytes(self.path) + b':' + env[b'PATH']
Simon Glassd48a46c2014-12-01 17:34:00 -0700235
Simon Glass93008e22021-04-11 16:27:28 +1200236 env[b'LC_ALL'] = b'C'
Daniel Schwierzeckc9e5f0c2017-06-08 03:07:08 +0200237
Simon Glassc05694f2013-04-03 11:07:16 +0000238 return env
239
Simon Glassf77ca5b2019-01-07 16:44:20 -0700240 def MakeArgs(self):
241 """Create the 'make' arguments for a toolchain
242
243 This is only used when the toolchain is being overridden. Since the
244 U-Boot Makefile sets CC and HOSTCC explicitly we cannot rely on the
245 environment (and MakeEnvironment()) to override these values. This
246 function returns the arguments to accomplish this.
247
248 Returns:
249 List of arguments to pass to 'make'
250 """
251 if self.override_toolchain:
252 return ['HOSTCC=%s' % self.override_toolchain,
253 'CC=%s' % self.override_toolchain]
254 return []
255
Simon Glassc05694f2013-04-03 11:07:16 +0000256
257class Toolchains:
258 """Manage a list of toolchains for building U-Boot
259
260 We select one toolchain for each architecture type
261
262 Public members:
263 toolchains: Dict of Toolchain objects, keyed by architecture name
Simon Glassf5902732016-03-12 18:50:32 -0700264 prefixes: Dict of prefixes to check, keyed by architecture. This can
265 be a full path and toolchain prefix, for example
266 {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
267 something on the search path, for example
268 {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
Simon Glassc05694f2013-04-03 11:07:16 +0000269 paths: List of paths to check for toolchains (may contain wildcards)
270 """
271
Simon Glassf77ca5b2019-01-07 16:44:20 -0700272 def __init__(self, override_toolchain=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000273 self.toolchains = {}
Simon Glassf5902732016-03-12 18:50:32 -0700274 self.prefixes = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000275 self.paths = []
Simon Glassf77ca5b2019-01-07 16:44:20 -0700276 self.override_toolchain = override_toolchain
Simon Glass06b83a52023-07-19 17:49:05 -0600277 self._make_flags = dict(bsettings.get_items('make-flags'))
Simon Glassed098bb2014-09-05 19:00:13 -0600278
Simon Glassf38a6422016-07-27 20:33:01 -0600279 def GetPathList(self, show_warning=True):
Simon Glass7e803e12014-12-01 17:34:06 -0700280 """Get a list of available toolchain paths
281
Simon Glassf38a6422016-07-27 20:33:01 -0600282 Args:
283 show_warning: True to show a warning if there are no tool chains.
284
Simon Glass7e803e12014-12-01 17:34:06 -0700285 Returns:
286 List of strings, each a path to a toolchain mentioned in the
287 [toolchain] section of the settings file.
288 """
Simon Glass06b83a52023-07-19 17:49:05 -0600289 toolchains = bsettings.get_items('toolchain')
Simon Glassf38a6422016-07-27 20:33:01 -0600290 if show_warning and not toolchains:
Simon Glassc78ed662019-10-31 07:42:53 -0600291 print(("Warning: No tool chains. Please run 'buildman "
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600292 "--fetch-arch all' to download all available toolchains, or "
293 "add a [toolchain] section to your buildman config file "
Heinrich Schuchardtf04e8002023-01-19 16:22:10 +0100294 "%s. See buildman.rst for details" %
Simon Glassc78ed662019-10-31 07:42:53 -0600295 bsettings.config_fname))
Simon Glasscc246fb2013-09-23 17:35:17 -0600296
Simon Glass7e803e12014-12-01 17:34:06 -0700297 paths = []
Simon Glasscc246fb2013-09-23 17:35:17 -0600298 for name, value in toolchains:
Simon Glassc05694f2013-04-03 11:07:16 +0000299 if '*' in value:
Simon Glass7e803e12014-12-01 17:34:06 -0700300 paths += glob.glob(value)
Simon Glassc05694f2013-04-03 11:07:16 +0000301 else:
Simon Glass7e803e12014-12-01 17:34:06 -0700302 paths.append(value)
303 return paths
304
Simon Glassf38a6422016-07-27 20:33:01 -0600305 def GetSettings(self, show_warning=True):
306 """Get toolchain settings from the settings file.
307
308 Args:
309 show_warning: True to show a warning if there are no tool chains.
310 """
Simon Glass06b83a52023-07-19 17:49:05 -0600311 self.prefixes = bsettings.get_items('toolchain-prefix')
Simon Glassf38a6422016-07-27 20:33:01 -0600312 self.paths += self.GetPathList(show_warning)
Simon Glassc05694f2013-04-03 11:07:16 +0000313
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700314 def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
315 arch=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000316 """Add a toolchain to our list
317
318 We select the given toolchain as our preferred one for its
319 architecture if it is a higher priority than the others.
320
321 Args:
322 fname: Filename of toolchain's gcc driver
323 test: True to run the toolchain to test it
Simon Glass7b971292016-03-06 19:45:37 -0700324 priority: Priority to use for this toolchain
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700325 arch: Toolchain architecture, or None if not known
Simon Glassc05694f2013-04-03 11:07:16 +0000326 """
Simon Glassf77ca5b2019-01-07 16:44:20 -0700327 toolchain = Toolchain(fname, test, verbose, priority, arch,
328 self.override_toolchain)
Simon Glassc05694f2013-04-03 11:07:16 +0000329 add_it = toolchain.ok
Simon Glassc05694f2013-04-03 11:07:16 +0000330 if add_it:
Simon Glasse2c35a02024-11-08 08:23:47 -0700331 if toolchain.arch in self.toolchains:
332 add_it = (toolchain.priority <
333 self.toolchains[toolchain.arch].priority)
334 if add_it:
335 self.toolchains[toolchain.arch] = toolchain
336 elif verbose:
337 print(("Toolchain '%s' at priority %d will be ignored because "
338 "another toolchain for arch '%s' has priority %d" %
339 (toolchain.gcc, toolchain.priority, toolchain.arch,
340 self.toolchains[toolchain.arch].priority)))
Simon Glassc05694f2013-04-03 11:07:16 +0000341
Simon Glass7e803e12014-12-01 17:34:06 -0700342 def ScanPath(self, path, verbose):
343 """Scan a path for a valid toolchain
344
345 Args:
346 path: Path to scan
347 verbose: True to print out progress information
348 Returns:
349 Filename of C compiler if found, else None
350 """
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100351 fnames = []
Simon Glass7e803e12014-12-01 17:34:06 -0700352 for subdir in ['.', 'bin', 'usr/bin']:
353 dirname = os.path.join(path, subdir)
Simon Glassc78ed662019-10-31 07:42:53 -0600354 if verbose: print(" - looking in '%s'" % dirname)
Simon Glass7e803e12014-12-01 17:34:06 -0700355 for fname in glob.glob(dirname + '/*gcc'):
Simon Glassc78ed662019-10-31 07:42:53 -0600356 if verbose: print(" - found '%s'" % fname)
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100357 fnames.append(fname)
358 return fnames
Simon Glass7e803e12014-12-01 17:34:06 -0700359
Simon Glassf5902732016-03-12 18:50:32 -0700360 def ScanPathEnv(self, fname):
361 """Scan the PATH environment variable for a given filename.
362
363 Args:
364 fname: Filename to scan for
365 Returns:
366 List of matching pathanames, or [] if none
367 """
368 pathname_list = []
369 for path in os.environ["PATH"].split(os.pathsep):
370 path = path.strip('"')
371 pathname = os.path.join(path, fname)
372 if os.path.exists(pathname):
373 pathname_list.append(pathname)
374 return pathname_list
Simon Glass7e803e12014-12-01 17:34:06 -0700375
Simon Glassc05694f2013-04-03 11:07:16 +0000376 def Scan(self, verbose):
377 """Scan for available toolchains and select the best for each arch.
378
379 We look for all the toolchains we can file, figure out the
380 architecture for each, and whether it works. Then we select the
381 highest priority toolchain for each arch.
382
383 Args:
384 verbose: True to print out progress information
385 """
Simon Glassc78ed662019-10-31 07:42:53 -0600386 if verbose: print('Scanning for tool chains')
Simon Glassf5902732016-03-12 18:50:32 -0700387 for name, value in self.prefixes:
Simon Glassc78ed662019-10-31 07:42:53 -0600388 if verbose: print(" - scanning prefix '%s'" % value)
Simon Glassf5902732016-03-12 18:50:32 -0700389 if os.path.exists(value):
390 self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
391 continue
392 fname = value + 'gcc'
393 if os.path.exists(fname):
394 self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
395 continue
396 fname_list = self.ScanPathEnv(fname)
397 for f in fname_list:
398 self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
399 if not fname_list:
Simon Glassc78ed662019-10-31 07:42:53 -0600400 raise ValueError("No tool chain found for prefix '%s'" %
Simon Glassf5902732016-03-12 18:50:32 -0700401 value)
Simon Glassc05694f2013-04-03 11:07:16 +0000402 for path in self.paths:
Simon Glassc78ed662019-10-31 07:42:53 -0600403 if verbose: print(" - scanning path '%s'" % path)
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100404 fnames = self.ScanPath(path, verbose)
405 for fname in fnames:
Simon Glass7e803e12014-12-01 17:34:06 -0700406 self.Add(fname, True, verbose)
Simon Glassc05694f2013-04-03 11:07:16 +0000407
408 def List(self):
409 """List out the selected toolchains for each architecture"""
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600410 col = terminal.Color()
Simon Glassf45d3742022-01-29 14:14:17 -0700411 print(col.build(col.BLUE, 'List of available toolchains (%d):' %
Simon Glassc78ed662019-10-31 07:42:53 -0600412 len(self.toolchains)))
Simon Glassc05694f2013-04-03 11:07:16 +0000413 if len(self.toolchains):
Simon Glassc78ed662019-10-31 07:42:53 -0600414 for key, value in sorted(self.toolchains.items()):
415 print('%-10s: %s' % (key, value.gcc))
Simon Glassc05694f2013-04-03 11:07:16 +0000416 else:
Simon Glassc78ed662019-10-31 07:42:53 -0600417 print('None')
Simon Glassc05694f2013-04-03 11:07:16 +0000418
419 def Select(self, arch):
420 """Returns the toolchain for a given architecture
421
422 Args:
423 args: Name of architecture (e.g. 'arm', 'ppc_8xx')
424
425 returns:
426 toolchain object, or None if none found
427 """
Simon Glass06b83a52023-07-19 17:49:05 -0600428 for tag, value in bsettings.get_items('toolchain-alias'):
Simon Glassc1528c12014-12-01 17:34:05 -0700429 if arch == tag:
430 for alias in value.split():
431 if alias in self.toolchains:
432 return self.toolchains[alias]
Simon Glassc05694f2013-04-03 11:07:16 +0000433
434 if not arch in self.toolchains:
Simon Glassc78ed662019-10-31 07:42:53 -0600435 raise ValueError("No tool chain found for arch '%s'" % arch)
Simon Glassc05694f2013-04-03 11:07:16 +0000436 return self.toolchains[arch]
Simon Glasscc246fb2013-09-23 17:35:17 -0600437
438 def ResolveReferences(self, var_dict, args):
439 """Resolve variable references in a string
440
441 This converts ${blah} within the string to the value of blah.
442 This function works recursively.
443
Simon Glass1f3a1222024-09-21 19:57:57 +0200444 Resolved string
445
Simon Glasscc246fb2013-09-23 17:35:17 -0600446 Args:
447 var_dict: Dictionary containing variables and their values
448 args: String containing make arguments
449 Returns:
Simon Glass06b83a52023-07-19 17:49:05 -0600450 >>> bsettings.setup(None)
Simon Glasscc246fb2013-09-23 17:35:17 -0600451 >>> tcs = Toolchains()
452 >>> tcs.Add('fred', False)
453 >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
454 'second' : '2nd'}
455 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
456 'this=OBLIQUE_set'
457 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
458 'this=OBLIQUE_setfi2ndrstnd'
459 """
Simon Glass1f3a1222024-09-21 19:57:57 +0200460 re_var = re.compile(r'(\$\{[-_a-z0-9A-Z]{1,}\})')
Simon Glasscc246fb2013-09-23 17:35:17 -0600461
462 while True:
463 m = re_var.search(args)
464 if not m:
465 break
466 lookup = m.group(0)[2:-1]
467 value = var_dict.get(lookup, '')
468 args = args[:m.start(0)] + value + args[m.end(0):]
469 return args
470
Simon Glass8132f982022-07-11 19:03:57 -0600471 def GetMakeArguments(self, brd):
Simon Glasscc246fb2013-09-23 17:35:17 -0600472 """Returns 'make' arguments for a given board
473
474 The flags are in a section called 'make-flags'. Flags are named
475 after the target they represent, for example snapper9260=TESTING=1
476 will pass TESTING=1 to make when building the snapper9260 board.
477
478 References to other boards can be added in the string also. For
479 example:
480
481 [make-flags]
482 at91-boards=ENABLE_AT91_TEST=1
483 snapper9260=${at91-boards} BUILD_TAG=442
484 snapper9g45=${at91-boards} BUILD_TAG=443
485
486 This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
487 and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
488
489 A special 'target' variable is set to the board target.
490
491 Args:
Simon Glass8132f982022-07-11 19:03:57 -0600492 brd: Board object for the board to check.
Simon Glasscc246fb2013-09-23 17:35:17 -0600493 Returns:
494 'make' flags for that board, or '' if none
495 """
Simon Glass8132f982022-07-11 19:03:57 -0600496 self._make_flags['target'] = brd.target
Simon Glasscc246fb2013-09-23 17:35:17 -0600497 arg_str = self.ResolveReferences(self._make_flags,
Simon Glass8132f982022-07-11 19:03:57 -0600498 self._make_flags.get(brd.target, ''))
Simon Glass1f3a1222024-09-21 19:57:57 +0200499 args = re.findall(r"(?:\".*?\"|\S)+", arg_str)
Simon Glasscc246fb2013-09-23 17:35:17 -0600500 i = 0
501 while i < len(args):
Cristian Ciocaltea9da17fb2019-11-24 22:30:26 +0200502 args[i] = args[i].replace('"', '')
Simon Glasscc246fb2013-09-23 17:35:17 -0600503 if not args[i]:
504 del args[i]
505 else:
506 i += 1
507 return args
Simon Glass7e803e12014-12-01 17:34:06 -0700508
509 def LocateArchUrl(self, fetch_arch):
510 """Find a toolchain available online
511
512 Look in standard places for available toolchains. At present the
513 only standard place is at kernel.org.
514
515 Args:
516 arch: Architecture to look for, or 'list' for all
517 Returns:
518 If fetch_arch is 'list', a tuple:
519 Machine architecture (e.g. x86_64)
520 List of toolchains
521 else
522 URL containing this toolchain, if avaialble, else None
523 """
Simon Glass840be732022-01-29 14:14:05 -0700524 arch = command.output_one_line('uname', '-m')
Matthias Bruggerfb50a262020-01-17 10:53:37 +0100525 if arch == 'aarch64':
526 arch = 'arm64'
Simon Glass7e803e12014-12-01 17:34:06 -0700527 base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
Tom Rini3bd5ed72023-08-25 13:21:26 -0400528 versions = ['13.2.0', '12.2.0']
Simon Glass7e803e12014-12-01 17:34:06 -0700529 links = []
530 for version in versions:
531 url = '%s/%s/%s/' % (base, arch, version)
Simon Glassc78ed662019-10-31 07:42:53 -0600532 print('Checking: %s' % url)
533 response = urllib.request.urlopen(url)
Simon Glass80025522022-01-29 14:14:04 -0700534 html = tools.to_string(response.read())
Simon Glass7e803e12014-12-01 17:34:06 -0700535 parser = MyHTMLParser(fetch_arch)
536 parser.feed(html)
537 if fetch_arch == 'list':
538 links += parser.links
539 elif parser.arch_link:
540 return url + parser.arch_link
541 if fetch_arch == 'list':
542 return arch, links
543 return None
544
Simon Glass7e803e12014-12-01 17:34:06 -0700545 def Unpack(self, fname, dest):
546 """Unpack a tar file
547
548 Args:
549 fname: Filename to unpack
550 dest: Destination directory
551 Returns:
552 Directory name of the first entry in the archive, without the
553 trailing /
554 """
Simon Glass840be732022-01-29 14:14:05 -0700555 stdout = command.output('tar', 'xvfJ', fname, '-C', dest)
Trevor Woernere0557a72018-11-21 03:31:12 -0500556 dirs = stdout.splitlines()[1].split('/')[:2]
557 return '/'.join(dirs)
Simon Glass7e803e12014-12-01 17:34:06 -0700558
559 def TestSettingsHasPath(self, path):
Simon Glass43ea1282016-07-27 20:33:03 -0600560 """Check if buildman will find this toolchain
Simon Glass7e803e12014-12-01 17:34:06 -0700561
562 Returns:
563 True if the path is in settings, False if not
564 """
Simon Glassf38a6422016-07-27 20:33:01 -0600565 paths = self.GetPathList(False)
Simon Glass7e803e12014-12-01 17:34:06 -0700566 return path in paths
567
568 def ListArchs(self):
569 """List architectures with available toolchains to download"""
570 host_arch, archives = self.LocateArchUrl('list')
Trevor Woerner10a51622018-11-21 03:31:13 -0500571 re_arch = re.compile('[-a-z0-9.]*[-_]([^-]*)-.*')
Simon Glass7e803e12014-12-01 17:34:06 -0700572 arch_set = set()
573 for archive in archives:
574 # Remove the host architecture from the start
575 arch = re_arch.match(archive[len(host_arch):])
576 if arch:
Trevor Woerner10a51622018-11-21 03:31:13 -0500577 if arch.group(1) != '2.0' and arch.group(1) != '64':
578 arch_set.add(arch.group(1))
Simon Glass7e803e12014-12-01 17:34:06 -0700579 return sorted(arch_set)
580
581 def FetchAndInstall(self, arch):
582 """Fetch and install a new toolchain
583
584 arch:
585 Architecture to fetch, or 'list' to list
586 """
587 # Fist get the URL for this architecture
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600588 col = terminal.Color()
Simon Glassf45d3742022-01-29 14:14:17 -0700589 print(col.build(col.BLUE, "Downloading toolchain for arch '%s'" % arch))
Simon Glass7e803e12014-12-01 17:34:06 -0700590 url = self.LocateArchUrl(arch)
591 if not url:
Simon Glassc78ed662019-10-31 07:42:53 -0600592 print(("Cannot find toolchain for arch '%s' - use 'list' to list" %
593 arch))
Simon Glass7e803e12014-12-01 17:34:06 -0700594 return 2
595 home = os.environ['HOME']
596 dest = os.path.join(home, '.buildman-toolchains')
597 if not os.path.exists(dest):
598 os.mkdir(dest)
599
600 # Download the tar file for this toolchain and unpack it
Simon Glass80025522022-01-29 14:14:04 -0700601 tarfile, tmpdir = tools.download(url, '.buildman')
Simon Glass7e803e12014-12-01 17:34:06 -0700602 if not tarfile:
603 return 1
Simon Glassf45d3742022-01-29 14:14:17 -0700604 print(col.build(col.GREEN, 'Unpacking to: %s' % dest), end=' ')
Simon Glass7e803e12014-12-01 17:34:06 -0700605 sys.stdout.flush()
606 path = self.Unpack(tarfile, dest)
607 os.remove(tarfile)
608 os.rmdir(tmpdir)
Simon Glassc78ed662019-10-31 07:42:53 -0600609 print()
Simon Glass7e803e12014-12-01 17:34:06 -0700610
611 # Check that the toolchain works
Simon Glassf45d3742022-01-29 14:14:17 -0700612 print(col.build(col.GREEN, 'Testing'))
Simon Glass7e803e12014-12-01 17:34:06 -0700613 dirpath = os.path.join(dest, path)
Simon Glassf6757502015-03-02 17:05:15 -0700614 compiler_fname_list = self.ScanPath(dirpath, True)
615 if not compiler_fname_list:
Simon Glassc78ed662019-10-31 07:42:53 -0600616 print('Could not locate C compiler - fetch failed.')
Simon Glass7e803e12014-12-01 17:34:06 -0700617 return 1
Simon Glassf6757502015-03-02 17:05:15 -0700618 if len(compiler_fname_list) != 1:
Simon Glassf45d3742022-01-29 14:14:17 -0700619 print(col.build(col.RED, 'Warning, ambiguous toolchains: %s' %
Simon Glassc78ed662019-10-31 07:42:53 -0600620 ', '.join(compiler_fname_list)))
Simon Glassf6757502015-03-02 17:05:15 -0700621 toolchain = Toolchain(compiler_fname_list[0], True, True)
Simon Glass7e803e12014-12-01 17:34:06 -0700622
623 # Make sure that it will be found by buildman
624 if not self.TestSettingsHasPath(dirpath):
Simon Glassc78ed662019-10-31 07:42:53 -0600625 print(("Adding 'download' to config file '%s'" %
626 bsettings.config_fname))
Simon Glass06b83a52023-07-19 17:49:05 -0600627 bsettings.set_item('toolchain', 'download', '%s/*/*' % dest)
Simon Glass7e803e12014-12-01 17:34:06 -0700628 return 0