blob: a7d7883b85108b0676bce73a317d7784d71505f9 [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
Simon Glass0ede00f2020-04-17 18:09:02 -060013from buildman import bsettings
Simon Glass4583c002023-02-23 18:18:04 -070014from u_boot_pylib import command
15from u_boot_pylib import terminal
16from u_boot_pylib import 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 Glass57cb9d52019-12-05 15:59:14 -070021(VAR_CROSS_COMPILE, VAR_PATH, VAR_ARCH, VAR_MAKE_ARGS) = range(4)
22
Simon Glass827e37b2014-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 Schwierzeck4c58d272018-05-10 07:15:53 -040038 self.re_arch = re.compile('[-_]%s-' % arch)
Simon Glass827e37b2014-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 Schwierzeck4c58d272018-05-10 07:15:53 -040046 if self.re_arch.search(value):
Simon Glass827e37b2014-12-01 17:34:06 -070047 self.arch_link = value
48
49
Simon Glassfc3fe1c2013-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 Glassff690df2016-03-06 19:45:37 -070059 priority: Toolchain priority (0=highest, 20=lowest)
Simon Glass00beb242019-01-07 16:44:20 -070060 override_toolchain: Toolchain to use for sandbox, overriding the normal
61 one
Simon Glassfc3fe1c2013-04-03 11:07:16 +000062 """
Simon Glass608e3992016-03-06 19:45:38 -070063 def __init__(self, fname, test, verbose=False, priority=PRIORITY_CALC,
Simon Glass00beb242019-01-07 16:44:20 -070064 arch=None, override_toolchain=None):
Simon Glassfc3fe1c2013-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 Glassad24eba2016-03-06 19:45:35 -070070 verbose: True to print out the information
Simon Glassff690df2016-03-06 19:45:37 -070071 priority: Priority to use for this toolchain, or PRIORITY_CALC to
72 calculate it
Simon Glassfc3fe1c2013-04-03 11:07:16 +000073 """
74 self.gcc = fname
75 self.path = os.path.dirname(fname)
Simon Glass00beb242019-01-07 16:44:20 -070076 self.override_toolchain = override_toolchain
Simon Glassb5324122014-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 Glassfc3fe1c2013-04-03 11:07:16 +000085 pos = self.cross.find('-')
Simon Glass608e3992016-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 Glass00beb242019-01-07 16:44:20 -070090 if self.arch == 'sandbox' and override_toolchain:
91 self.gcc = override_toolchain
Simon Glassfc3fe1c2013-04-03 11:07:16 +000092
Tom Rinie13fcae2024-07-05 14:34:07 -060093 env = self.MakeEnvironment(False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +000094
95 # As a basic sanity check, run the C compiler with --version
96 cmd = [fname, '--version']
Simon Glassff690df2016-03-06 19:45:37 -070097 if priority == PRIORITY_CALC:
98 self.priority = self.GetPriority(fname)
99 else:
100 self.priority = priority
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000101 if test:
Simon Glassd9800692022-01-29 14:14:05 -0700102 result = command.run_pipe([cmd], capture=True, env=env,
Stephen Warren8bb2bdd2013-10-09 14:28:09 -0600103 raise_on_error=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000104 self.ok = result.return_code == 0
105 if verbose:
Simon Glassc05aa032019-10-31 07:42:53 -0600106 print('Tool chain test: ', end=' ')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000107 if self.ok:
Simon Glassc05aa032019-10-31 07:42:53 -0600108 print("OK, arch='%s', priority %d" % (self.arch,
109 self.priority))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000110 else:
Simon Glassc05aa032019-10-31 07:42:53 -0600111 print('BAD')
112 print('Command: ', cmd)
113 print(result.stdout)
114 print(result.stderr)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000115 else:
116 self.ok = True
Simon Glassfc3fe1c2013-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 Glassff690df2016-03-06 19:45:37 -0700127 Priority of toolchain, PRIORITY_CALC=highest, 20=lowest.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000128 """
Masahiro Yamada87082672014-07-07 09:47:45 +0900129 priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
Tom Rini546a6f32017-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 Glassfc3fe1c2013-04-03 11:07:16 +0000133 for prio in range(len(priority_list)):
134 if priority_list[prio] in fname:
Simon Glassff690df2016-03-06 19:45:37 -0700135 return PRIORITY_CALC + prio
136 return PRIORITY_CALC + prio
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000137
York Sund5fe0132016-10-04 14:33:51 -0700138 def GetWrapper(self, show_warning=True):
139 """Get toolchain wrapper from the setting file.
140 """
Simon Glassccd29792019-01-07 16:44:24 -0700141 value = ''
Simon Glass42d42cf2023-07-19 17:49:05 -0600142 for name, value in bsettings.get_items('toolchain-wrapper'):
York Sund5fe0132016-10-04 14:33:51 -0700143 if not value:
Simon Glassc05aa032019-10-31 07:42:53 -0600144 print("Warning: Wrapper not found")
York Sund5fe0132016-10-04 14:33:51 -0700145 if value:
146 value = value + ' '
147
148 return value
149
Simon Glass57cb9d52019-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 Glass57cb9d52019-12-05 15:59:14 -0700159 if which == VAR_CROSS_COMPILE:
Simon Glassc3cea952023-03-10 12:48:51 -0800160 wrapper = self.GetWrapper()
161 base = '' if self.arch == 'sandbox' else self.path
Jerome Forissier0c2d7ad2024-09-11 11:58:14 +0200162 if (base == '' and self.cross == ''):
163 return ''
Simon Glassc3cea952023-03-10 12:48:51 -0800164 return wrapper + os.path.join(base, self.cross)
Simon Glass57cb9d52019-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 Glassd0f74bd2024-08-15 13:57:44 -0600177 def MakeEnvironment(self, full_path, env=None):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000178 """Returns an environment for using the toolchain.
179
Simon Glass57abd7c2024-06-23 11:56:19 -0600180 This takes the current environment and adds CROSS_COMPILE so that
Daniel Schwierzeckb0e994c2017-06-08 03:07:08 +0200181 the tool chain will operate correctly. This also disables localized
Simon Glass57abd7c2024-06-23 11:56:19 -0600182 output and possibly Unicode encoded output of all build tools by
Simon Glassd0f74bd2024-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 Glassbb1501f2014-12-01 17:34:00 -0700185
Simon Glassf1a83ab2021-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 Glassd0f74bd2024-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 Glassbb1501f2014-12-01 17:34:00 -0700197 Args:
Tom Rinie13fcae2024-07-05 14:34:07 -0600198 full_path: Return the full path in CROSS_COMPILE and don't set
199 PATH
Simon Glassd0f74bd2024-08-15 13:57:44 -0600200 env (dict of bytes): Original environment, used for testing
Simon Glass00beb242019-01-07 16:44:20 -0700201 Returns:
Simon Glassf1a83ab2021-04-11 16:27:28 +1200202 Dict containing the (bytes) environment to use. This is based on the
Tom Rinie13fcae2024-07-05 14:34:07 -0600203 current environment, with changes as needed to CROSS_COMPILE, PATH
204 and LC_ALL.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000205 """
Simon Glassd0f74bd2024-08-15 13:57:44 -0600206 env = dict(env or os.environb)
207
York Sund5fe0132016-10-04 14:33:51 -0700208 wrapper = self.GetWrapper()
209
Simon Glass00beb242019-01-07 16:44:20 -0700210 if self.override_toolchain:
211 # We'll use MakeArgs() to provide this
212 pass
Jerome Forissier0c2d7ad2024-09-11 11:58:14 +0200213 elif full_path and self.cross:
Simon Glassc1aa66e2022-01-29 14:14:04 -0700214 env[b'CROSS_COMPILE'] = tools.to_bytes(
Simon Glassf1a83ab2021-04-11 16:27:28 +1200215 wrapper + os.path.join(self.path, self.cross))
Jerome Forissier0c2d7ad2024-09-11 11:58:14 +0200216 elif self.cross:
Tom Rinie13fcae2024-07-05 14:34:07 -0600217 env[b'CROSS_COMPILE'] = tools.to_bytes(wrapper + self.cross)
Simon Glassd0f74bd2024-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 Glassbb1501f2014-12-01 17:34:00 -0700235
Simon Glassf1a83ab2021-04-11 16:27:28 +1200236 env[b'LC_ALL'] = b'C'
Daniel Schwierzeckb0e994c2017-06-08 03:07:08 +0200237
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000238 return env
239
Simon Glass00beb242019-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 Glassfc3fe1c2013-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 Glass17bce662016-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 Glassfc3fe1c2013-04-03 11:07:16 +0000269 paths: List of paths to check for toolchains (may contain wildcards)
270 """
271
Simon Glass00beb242019-01-07 16:44:20 -0700272 def __init__(self, override_toolchain=None):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000273 self.toolchains = {}
Simon Glass17bce662016-03-12 18:50:32 -0700274 self.prefixes = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000275 self.paths = []
Simon Glass00beb242019-01-07 16:44:20 -0700276 self.override_toolchain = override_toolchain
Simon Glass42d42cf2023-07-19 17:49:05 -0600277 self._make_flags = dict(bsettings.get_items('make-flags'))
Simon Glassd4144e42014-09-05 19:00:13 -0600278
Simon Glass80e6a482016-07-27 20:33:01 -0600279 def GetPathList(self, show_warning=True):
Simon Glass827e37b2014-12-01 17:34:06 -0700280 """Get a list of available toolchain paths
281
Simon Glass80e6a482016-07-27 20:33:01 -0600282 Args:
283 show_warning: True to show a warning if there are no tool chains.
284
Simon Glass827e37b2014-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 Glass42d42cf2023-07-19 17:49:05 -0600289 toolchains = bsettings.get_items('toolchain')
Simon Glass80e6a482016-07-27 20:33:01 -0600290 if show_warning and not toolchains:
Simon Glassc05aa032019-10-31 07:42:53 -0600291 print(("Warning: No tool chains. Please run 'buildman "
Simon Glass713bea32016-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 Schuchardteeb55252023-01-19 16:22:10 +0100294 "%s. See buildman.rst for details" %
Simon Glassc05aa032019-10-31 07:42:53 -0600295 bsettings.config_fname))
Simon Glass4281ad82013-09-23 17:35:17 -0600296
Simon Glass827e37b2014-12-01 17:34:06 -0700297 paths = []
Simon Glass4281ad82013-09-23 17:35:17 -0600298 for name, value in toolchains:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000299 if '*' in value:
Simon Glass827e37b2014-12-01 17:34:06 -0700300 paths += glob.glob(value)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000301 else:
Simon Glass827e37b2014-12-01 17:34:06 -0700302 paths.append(value)
303 return paths
304
Simon Glass80e6a482016-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 Glass42d42cf2023-07-19 17:49:05 -0600311 self.prefixes = bsettings.get_items('toolchain-prefix')
Simon Glass80e6a482016-07-27 20:33:01 -0600312 self.paths += self.GetPathList(show_warning)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000313
Simon Glass608e3992016-03-06 19:45:38 -0700314 def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
315 arch=None):
Simon Glassfc3fe1c2013-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 Glassff690df2016-03-06 19:45:37 -0700324 priority: Priority to use for this toolchain
Simon Glass608e3992016-03-06 19:45:38 -0700325 arch: Toolchain architecture, or None if not known
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000326 """
Simon Glass00beb242019-01-07 16:44:20 -0700327 toolchain = Toolchain(fname, test, verbose, priority, arch,
328 self.override_toolchain)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000329 add_it = toolchain.ok
330 if toolchain.arch in self.toolchains:
331 add_it = (toolchain.priority <
332 self.toolchains[toolchain.arch].priority)
333 if add_it:
334 self.toolchains[toolchain.arch] = toolchain
Simon Glassff690df2016-03-06 19:45:37 -0700335 elif verbose:
Simon Glassc05aa032019-10-31 07:42:53 -0600336 print(("Toolchain '%s' at priority %d will be ignored because "
Simon Glassff690df2016-03-06 19:45:37 -0700337 "another toolchain for arch '%s' has priority %d" %
338 (toolchain.gcc, toolchain.priority, toolchain.arch,
Simon Glassc05aa032019-10-31 07:42:53 -0600339 self.toolchains[toolchain.arch].priority)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000340
Simon Glass827e37b2014-12-01 17:34:06 -0700341 def ScanPath(self, path, verbose):
342 """Scan a path for a valid toolchain
343
344 Args:
345 path: Path to scan
346 verbose: True to print out progress information
347 Returns:
348 Filename of C compiler if found, else None
349 """
Albert ARIBAUDd9088982015-02-01 00:12:44 +0100350 fnames = []
Simon Glass827e37b2014-12-01 17:34:06 -0700351 for subdir in ['.', 'bin', 'usr/bin']:
352 dirname = os.path.join(path, subdir)
Simon Glassc05aa032019-10-31 07:42:53 -0600353 if verbose: print(" - looking in '%s'" % dirname)
Simon Glass827e37b2014-12-01 17:34:06 -0700354 for fname in glob.glob(dirname + '/*gcc'):
Simon Glassc05aa032019-10-31 07:42:53 -0600355 if verbose: print(" - found '%s'" % fname)
Albert ARIBAUDd9088982015-02-01 00:12:44 +0100356 fnames.append(fname)
357 return fnames
Simon Glass827e37b2014-12-01 17:34:06 -0700358
Simon Glass17bce662016-03-12 18:50:32 -0700359 def ScanPathEnv(self, fname):
360 """Scan the PATH environment variable for a given filename.
361
362 Args:
363 fname: Filename to scan for
364 Returns:
365 List of matching pathanames, or [] if none
366 """
367 pathname_list = []
368 for path in os.environ["PATH"].split(os.pathsep):
369 path = path.strip('"')
370 pathname = os.path.join(path, fname)
371 if os.path.exists(pathname):
372 pathname_list.append(pathname)
373 return pathname_list
Simon Glass827e37b2014-12-01 17:34:06 -0700374
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000375 def Scan(self, verbose):
376 """Scan for available toolchains and select the best for each arch.
377
378 We look for all the toolchains we can file, figure out the
379 architecture for each, and whether it works. Then we select the
380 highest priority toolchain for each arch.
381
382 Args:
383 verbose: True to print out progress information
384 """
Simon Glassc05aa032019-10-31 07:42:53 -0600385 if verbose: print('Scanning for tool chains')
Simon Glass17bce662016-03-12 18:50:32 -0700386 for name, value in self.prefixes:
Simon Glassc05aa032019-10-31 07:42:53 -0600387 if verbose: print(" - scanning prefix '%s'" % value)
Simon Glass17bce662016-03-12 18:50:32 -0700388 if os.path.exists(value):
389 self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
390 continue
391 fname = value + 'gcc'
392 if os.path.exists(fname):
393 self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
394 continue
395 fname_list = self.ScanPathEnv(fname)
396 for f in fname_list:
397 self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
398 if not fname_list:
Simon Glassc05aa032019-10-31 07:42:53 -0600399 raise ValueError("No tool chain found for prefix '%s'" %
Simon Glass17bce662016-03-12 18:50:32 -0700400 value)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000401 for path in self.paths:
Simon Glassc05aa032019-10-31 07:42:53 -0600402 if verbose: print(" - scanning path '%s'" % path)
Albert ARIBAUDd9088982015-02-01 00:12:44 +0100403 fnames = self.ScanPath(path, verbose)
404 for fname in fnames:
Simon Glass827e37b2014-12-01 17:34:06 -0700405 self.Add(fname, True, verbose)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000406
407 def List(self):
408 """List out the selected toolchains for each architecture"""
Simon Glass713bea32016-07-27 20:33:02 -0600409 col = terminal.Color()
Simon Glass252ac582022-01-29 14:14:17 -0700410 print(col.build(col.BLUE, 'List of available toolchains (%d):' %
Simon Glassc05aa032019-10-31 07:42:53 -0600411 len(self.toolchains)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000412 if len(self.toolchains):
Simon Glassc05aa032019-10-31 07:42:53 -0600413 for key, value in sorted(self.toolchains.items()):
414 print('%-10s: %s' % (key, value.gcc))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000415 else:
Simon Glassc05aa032019-10-31 07:42:53 -0600416 print('None')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000417
418 def Select(self, arch):
419 """Returns the toolchain for a given architecture
420
421 Args:
422 args: Name of architecture (e.g. 'arm', 'ppc_8xx')
423
424 returns:
425 toolchain object, or None if none found
426 """
Simon Glass42d42cf2023-07-19 17:49:05 -0600427 for tag, value in bsettings.get_items('toolchain-alias'):
Simon Glass9b83bfd2014-12-01 17:34:05 -0700428 if arch == tag:
429 for alias in value.split():
430 if alias in self.toolchains:
431 return self.toolchains[alias]
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000432
433 if not arch in self.toolchains:
Simon Glassc05aa032019-10-31 07:42:53 -0600434 raise ValueError("No tool chain found for arch '%s'" % arch)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000435 return self.toolchains[arch]
Simon Glass4281ad82013-09-23 17:35:17 -0600436
437 def ResolveReferences(self, var_dict, args):
438 """Resolve variable references in a string
439
440 This converts ${blah} within the string to the value of blah.
441 This function works recursively.
442
443 Args:
444 var_dict: Dictionary containing variables and their values
445 args: String containing make arguments
446 Returns:
447 Resolved string
448
Simon Glass42d42cf2023-07-19 17:49:05 -0600449 >>> bsettings.setup(None)
Simon Glass4281ad82013-09-23 17:35:17 -0600450 >>> tcs = Toolchains()
451 >>> tcs.Add('fred', False)
452 >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
453 'second' : '2nd'}
454 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
455 'this=OBLIQUE_set'
456 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
457 'this=OBLIQUE_setfi2ndrstnd'
458 """
Simon Glassf60c9d42014-08-28 09:43:40 -0600459 re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
Simon Glass4281ad82013-09-23 17:35:17 -0600460
461 while True:
462 m = re_var.search(args)
463 if not m:
464 break
465 lookup = m.group(0)[2:-1]
466 value = var_dict.get(lookup, '')
467 args = args[:m.start(0)] + value + args[m.end(0):]
468 return args
469
Simon Glassf4ed4702022-07-11 19:03:57 -0600470 def GetMakeArguments(self, brd):
Simon Glass4281ad82013-09-23 17:35:17 -0600471 """Returns 'make' arguments for a given board
472
473 The flags are in a section called 'make-flags'. Flags are named
474 after the target they represent, for example snapper9260=TESTING=1
475 will pass TESTING=1 to make when building the snapper9260 board.
476
477 References to other boards can be added in the string also. For
478 example:
479
480 [make-flags]
481 at91-boards=ENABLE_AT91_TEST=1
482 snapper9260=${at91-boards} BUILD_TAG=442
483 snapper9g45=${at91-boards} BUILD_TAG=443
484
485 This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
486 and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
487
488 A special 'target' variable is set to the board target.
489
490 Args:
Simon Glassf4ed4702022-07-11 19:03:57 -0600491 brd: Board object for the board to check.
Simon Glass4281ad82013-09-23 17:35:17 -0600492 Returns:
493 'make' flags for that board, or '' if none
494 """
Simon Glassf4ed4702022-07-11 19:03:57 -0600495 self._make_flags['target'] = brd.target
Simon Glass4281ad82013-09-23 17:35:17 -0600496 arg_str = self.ResolveReferences(self._make_flags,
Simon Glassf4ed4702022-07-11 19:03:57 -0600497 self._make_flags.get(brd.target, ''))
Cristian Ciocaltea4251fbc2019-11-24 22:30:26 +0200498 args = re.findall("(?:\".*?\"|\S)+", arg_str)
Simon Glass4281ad82013-09-23 17:35:17 -0600499 i = 0
500 while i < len(args):
Cristian Ciocaltea4251fbc2019-11-24 22:30:26 +0200501 args[i] = args[i].replace('"', '')
Simon Glass4281ad82013-09-23 17:35:17 -0600502 if not args[i]:
503 del args[i]
504 else:
505 i += 1
506 return args
Simon Glass827e37b2014-12-01 17:34:06 -0700507
508 def LocateArchUrl(self, fetch_arch):
509 """Find a toolchain available online
510
511 Look in standard places for available toolchains. At present the
512 only standard place is at kernel.org.
513
514 Args:
515 arch: Architecture to look for, or 'list' for all
516 Returns:
517 If fetch_arch is 'list', a tuple:
518 Machine architecture (e.g. x86_64)
519 List of toolchains
520 else
521 URL containing this toolchain, if avaialble, else None
522 """
Simon Glassd9800692022-01-29 14:14:05 -0700523 arch = command.output_one_line('uname', '-m')
Matthias Brugger2f7c53c2020-01-17 10:53:37 +0100524 if arch == 'aarch64':
525 arch = 'arm64'
Simon Glass827e37b2014-12-01 17:34:06 -0700526 base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
Tom Rini11934282023-08-25 13:21:26 -0400527 versions = ['13.2.0', '12.2.0']
Simon Glass827e37b2014-12-01 17:34:06 -0700528 links = []
529 for version in versions:
530 url = '%s/%s/%s/' % (base, arch, version)
Simon Glassc05aa032019-10-31 07:42:53 -0600531 print('Checking: %s' % url)
532 response = urllib.request.urlopen(url)
Simon Glassc1aa66e2022-01-29 14:14:04 -0700533 html = tools.to_string(response.read())
Simon Glass827e37b2014-12-01 17:34:06 -0700534 parser = MyHTMLParser(fetch_arch)
535 parser.feed(html)
536 if fetch_arch == 'list':
537 links += parser.links
538 elif parser.arch_link:
539 return url + parser.arch_link
540 if fetch_arch == 'list':
541 return arch, links
542 return None
543
Simon Glass827e37b2014-12-01 17:34:06 -0700544 def Unpack(self, fname, dest):
545 """Unpack a tar file
546
547 Args:
548 fname: Filename to unpack
549 dest: Destination directory
550 Returns:
551 Directory name of the first entry in the archive, without the
552 trailing /
553 """
Simon Glassd9800692022-01-29 14:14:05 -0700554 stdout = command.output('tar', 'xvfJ', fname, '-C', dest)
Trevor Woernerd82f5392018-11-21 03:31:12 -0500555 dirs = stdout.splitlines()[1].split('/')[:2]
556 return '/'.join(dirs)
Simon Glass827e37b2014-12-01 17:34:06 -0700557
558 def TestSettingsHasPath(self, path):
Simon Glass2289b272016-07-27 20:33:03 -0600559 """Check if buildman will find this toolchain
Simon Glass827e37b2014-12-01 17:34:06 -0700560
561 Returns:
562 True if the path is in settings, False if not
563 """
Simon Glass80e6a482016-07-27 20:33:01 -0600564 paths = self.GetPathList(False)
Simon Glass827e37b2014-12-01 17:34:06 -0700565 return path in paths
566
567 def ListArchs(self):
568 """List architectures with available toolchains to download"""
569 host_arch, archives = self.LocateArchUrl('list')
Trevor Woernerb11f1262018-11-21 03:31:13 -0500570 re_arch = re.compile('[-a-z0-9.]*[-_]([^-]*)-.*')
Simon Glass827e37b2014-12-01 17:34:06 -0700571 arch_set = set()
572 for archive in archives:
573 # Remove the host architecture from the start
574 arch = re_arch.match(archive[len(host_arch):])
575 if arch:
Trevor Woernerb11f1262018-11-21 03:31:13 -0500576 if arch.group(1) != '2.0' and arch.group(1) != '64':
577 arch_set.add(arch.group(1))
Simon Glass827e37b2014-12-01 17:34:06 -0700578 return sorted(arch_set)
579
580 def FetchAndInstall(self, arch):
581 """Fetch and install a new toolchain
582
583 arch:
584 Architecture to fetch, or 'list' to list
585 """
586 # Fist get the URL for this architecture
Simon Glass713bea32016-07-27 20:33:02 -0600587 col = terminal.Color()
Simon Glass252ac582022-01-29 14:14:17 -0700588 print(col.build(col.BLUE, "Downloading toolchain for arch '%s'" % arch))
Simon Glass827e37b2014-12-01 17:34:06 -0700589 url = self.LocateArchUrl(arch)
590 if not url:
Simon Glassc05aa032019-10-31 07:42:53 -0600591 print(("Cannot find toolchain for arch '%s' - use 'list' to list" %
592 arch))
Simon Glass827e37b2014-12-01 17:34:06 -0700593 return 2
594 home = os.environ['HOME']
595 dest = os.path.join(home, '.buildman-toolchains')
596 if not os.path.exists(dest):
597 os.mkdir(dest)
598
599 # Download the tar file for this toolchain and unpack it
Simon Glassc1aa66e2022-01-29 14:14:04 -0700600 tarfile, tmpdir = tools.download(url, '.buildman')
Simon Glass827e37b2014-12-01 17:34:06 -0700601 if not tarfile:
602 return 1
Simon Glass252ac582022-01-29 14:14:17 -0700603 print(col.build(col.GREEN, 'Unpacking to: %s' % dest), end=' ')
Simon Glass827e37b2014-12-01 17:34:06 -0700604 sys.stdout.flush()
605 path = self.Unpack(tarfile, dest)
606 os.remove(tarfile)
607 os.rmdir(tmpdir)
Simon Glassc05aa032019-10-31 07:42:53 -0600608 print()
Simon Glass827e37b2014-12-01 17:34:06 -0700609
610 # Check that the toolchain works
Simon Glass252ac582022-01-29 14:14:17 -0700611 print(col.build(col.GREEN, 'Testing'))
Simon Glass827e37b2014-12-01 17:34:06 -0700612 dirpath = os.path.join(dest, path)
Simon Glass2a76a642015-03-02 17:05:15 -0700613 compiler_fname_list = self.ScanPath(dirpath, True)
614 if not compiler_fname_list:
Simon Glassc05aa032019-10-31 07:42:53 -0600615 print('Could not locate C compiler - fetch failed.')
Simon Glass827e37b2014-12-01 17:34:06 -0700616 return 1
Simon Glass2a76a642015-03-02 17:05:15 -0700617 if len(compiler_fname_list) != 1:
Simon Glass252ac582022-01-29 14:14:17 -0700618 print(col.build(col.RED, 'Warning, ambiguous toolchains: %s' %
Simon Glassc05aa032019-10-31 07:42:53 -0600619 ', '.join(compiler_fname_list)))
Simon Glass2a76a642015-03-02 17:05:15 -0700620 toolchain = Toolchain(compiler_fname_list[0], True, True)
Simon Glass827e37b2014-12-01 17:34:06 -0700621
622 # Make sure that it will be found by buildman
623 if not self.TestSettingsHasPath(dirpath):
Simon Glassc05aa032019-10-31 07:42:53 -0600624 print(("Adding 'download' to config file '%s'" %
625 bsettings.config_fname))
Simon Glass42d42cf2023-07-19 17:49:05 -0600626 bsettings.set_item('toolchain', 'download', '%s/*/*' % dest)
Simon Glass827e37b2014-12-01 17:34:06 -0700627 return 0