blob: 739acf3ec53b9ede25d63b469701edce02cb6ecd [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
Simon Glassf498efc2024-06-23 11:56:20 -060093 env = self.MakeEnvironment()
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
162 return wrapper + os.path.join(base, self.cross)
Simon Glass48ac42e2019-12-05 15:59:14 -0700163 elif which == VAR_PATH:
164 return self.path
165 elif which == VAR_ARCH:
166 return self.arch
167 elif which == VAR_MAKE_ARGS:
168 args = self.MakeArgs()
169 if args:
170 return ' '.join(args)
171 return ''
172 else:
173 raise ValueError('Unknown arg to GetEnvArgs (%d)' % which)
174
Simon Glassf498efc2024-06-23 11:56:20 -0600175 def MakeEnvironment(self, env=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000176 """Returns an environment for using the toolchain.
177
Simon Glass58225462024-06-23 11:56:19 -0600178 This takes the current environment and adds CROSS_COMPILE so that
Daniel Schwierzeckc9e5f0c2017-06-08 03:07:08 +0200179 the tool chain will operate correctly. This also disables localized
Simon Glass58225462024-06-23 11:56:19 -0600180 output and possibly Unicode encoded output of all build tools by
Daniel Schwierzeckc9e5f0c2017-06-08 03:07:08 +0200181 adding LC_ALL=C.
Simon Glassd48a46c2014-12-01 17:34:00 -0700182
Simon Glass93008e22021-04-11 16:27:28 +1200183 Note that os.environb is used to obtain the environment, since in some
184 cases the environment many contain non-ASCII characters and we see
185 errors like:
186
187 UnicodeEncodeError: 'utf-8' codec can't encode characters in position
188 569-570: surrogates not allowed
189
Simon Glassd48a46c2014-12-01 17:34:00 -0700190 Args:
Simon Glassf498efc2024-06-23 11:56:20 -0600191 env (dict of bytes): Original environment, used for testing
192
Simon Glassf77ca5b2019-01-07 16:44:20 -0700193 Returns:
Simon Glass93008e22021-04-11 16:27:28 +1200194 Dict containing the (bytes) environment to use. This is based on the
Simon Glassf498efc2024-06-23 11:56:20 -0600195 current environment, with changes as needed to CROSS_COMPILE and
196 LC_ALL.
Simon Glassc05694f2013-04-03 11:07:16 +0000197 """
Simon Glassf498efc2024-06-23 11:56:20 -0600198 env = dict(env or os.environb)
199
York Sunfb197a82016-10-04 14:33:51 -0700200 wrapper = self.GetWrapper()
201
Simon Glassf77ca5b2019-01-07 16:44:20 -0700202 if self.override_toolchain:
203 # We'll use MakeArgs() to provide this
204 pass
Simon Glassf498efc2024-06-23 11:56:20 -0600205 else:
Simon Glass80025522022-01-29 14:14:04 -0700206 env[b'CROSS_COMPILE'] = tools.to_bytes(
Simon Glass93008e22021-04-11 16:27:28 +1200207 wrapper + os.path.join(self.path, self.cross))
Simon Glassd48a46c2014-12-01 17:34:00 -0700208
Simon Glass93008e22021-04-11 16:27:28 +1200209 env[b'LC_ALL'] = b'C'
Daniel Schwierzeckc9e5f0c2017-06-08 03:07:08 +0200210
Simon Glassc05694f2013-04-03 11:07:16 +0000211 return env
212
Simon Glassf77ca5b2019-01-07 16:44:20 -0700213 def MakeArgs(self):
214 """Create the 'make' arguments for a toolchain
215
216 This is only used when the toolchain is being overridden. Since the
217 U-Boot Makefile sets CC and HOSTCC explicitly we cannot rely on the
218 environment (and MakeEnvironment()) to override these values. This
219 function returns the arguments to accomplish this.
220
221 Returns:
222 List of arguments to pass to 'make'
223 """
224 if self.override_toolchain:
225 return ['HOSTCC=%s' % self.override_toolchain,
226 'CC=%s' % self.override_toolchain]
227 return []
228
Simon Glassc05694f2013-04-03 11:07:16 +0000229
230class Toolchains:
231 """Manage a list of toolchains for building U-Boot
232
233 We select one toolchain for each architecture type
234
235 Public members:
236 toolchains: Dict of Toolchain objects, keyed by architecture name
Simon Glassf5902732016-03-12 18:50:32 -0700237 prefixes: Dict of prefixes to check, keyed by architecture. This can
238 be a full path and toolchain prefix, for example
239 {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
240 something on the search path, for example
241 {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
Simon Glassc05694f2013-04-03 11:07:16 +0000242 paths: List of paths to check for toolchains (may contain wildcards)
243 """
244
Simon Glassf77ca5b2019-01-07 16:44:20 -0700245 def __init__(self, override_toolchain=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000246 self.toolchains = {}
Simon Glassf5902732016-03-12 18:50:32 -0700247 self.prefixes = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000248 self.paths = []
Simon Glassf77ca5b2019-01-07 16:44:20 -0700249 self.override_toolchain = override_toolchain
Simon Glass06b83a52023-07-19 17:49:05 -0600250 self._make_flags = dict(bsettings.get_items('make-flags'))
Simon Glassed098bb2014-09-05 19:00:13 -0600251
Simon Glassf38a6422016-07-27 20:33:01 -0600252 def GetPathList(self, show_warning=True):
Simon Glass7e803e12014-12-01 17:34:06 -0700253 """Get a list of available toolchain paths
254
Simon Glassf38a6422016-07-27 20:33:01 -0600255 Args:
256 show_warning: True to show a warning if there are no tool chains.
257
Simon Glass7e803e12014-12-01 17:34:06 -0700258 Returns:
259 List of strings, each a path to a toolchain mentioned in the
260 [toolchain] section of the settings file.
261 """
Simon Glass06b83a52023-07-19 17:49:05 -0600262 toolchains = bsettings.get_items('toolchain')
Simon Glassf38a6422016-07-27 20:33:01 -0600263 if show_warning and not toolchains:
Simon Glassc78ed662019-10-31 07:42:53 -0600264 print(("Warning: No tool chains. Please run 'buildman "
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600265 "--fetch-arch all' to download all available toolchains, or "
266 "add a [toolchain] section to your buildman config file "
Heinrich Schuchardtf04e8002023-01-19 16:22:10 +0100267 "%s. See buildman.rst for details" %
Simon Glassc78ed662019-10-31 07:42:53 -0600268 bsettings.config_fname))
Simon Glasscc246fb2013-09-23 17:35:17 -0600269
Simon Glass7e803e12014-12-01 17:34:06 -0700270 paths = []
Simon Glasscc246fb2013-09-23 17:35:17 -0600271 for name, value in toolchains:
Simon Glassc05694f2013-04-03 11:07:16 +0000272 if '*' in value:
Simon Glass7e803e12014-12-01 17:34:06 -0700273 paths += glob.glob(value)
Simon Glassc05694f2013-04-03 11:07:16 +0000274 else:
Simon Glass7e803e12014-12-01 17:34:06 -0700275 paths.append(value)
276 return paths
277
Simon Glassf38a6422016-07-27 20:33:01 -0600278 def GetSettings(self, show_warning=True):
279 """Get toolchain settings from the settings file.
280
281 Args:
282 show_warning: True to show a warning if there are no tool chains.
283 """
Simon Glass06b83a52023-07-19 17:49:05 -0600284 self.prefixes = bsettings.get_items('toolchain-prefix')
Simon Glassf38a6422016-07-27 20:33:01 -0600285 self.paths += self.GetPathList(show_warning)
Simon Glassc05694f2013-04-03 11:07:16 +0000286
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700287 def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
288 arch=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000289 """Add a toolchain to our list
290
291 We select the given toolchain as our preferred one for its
292 architecture if it is a higher priority than the others.
293
294 Args:
295 fname: Filename of toolchain's gcc driver
296 test: True to run the toolchain to test it
Simon Glass7b971292016-03-06 19:45:37 -0700297 priority: Priority to use for this toolchain
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700298 arch: Toolchain architecture, or None if not known
Simon Glassc05694f2013-04-03 11:07:16 +0000299 """
Simon Glassf77ca5b2019-01-07 16:44:20 -0700300 toolchain = Toolchain(fname, test, verbose, priority, arch,
301 self.override_toolchain)
Simon Glassc05694f2013-04-03 11:07:16 +0000302 add_it = toolchain.ok
303 if toolchain.arch in self.toolchains:
304 add_it = (toolchain.priority <
305 self.toolchains[toolchain.arch].priority)
306 if add_it:
307 self.toolchains[toolchain.arch] = toolchain
Simon Glass7b971292016-03-06 19:45:37 -0700308 elif verbose:
Simon Glassc78ed662019-10-31 07:42:53 -0600309 print(("Toolchain '%s' at priority %d will be ignored because "
Simon Glass7b971292016-03-06 19:45:37 -0700310 "another toolchain for arch '%s' has priority %d" %
311 (toolchain.gcc, toolchain.priority, toolchain.arch,
Simon Glassc78ed662019-10-31 07:42:53 -0600312 self.toolchains[toolchain.arch].priority)))
Simon Glassc05694f2013-04-03 11:07:16 +0000313
Simon Glass7e803e12014-12-01 17:34:06 -0700314 def ScanPath(self, path, verbose):
315 """Scan a path for a valid toolchain
316
317 Args:
318 path: Path to scan
319 verbose: True to print out progress information
320 Returns:
321 Filename of C compiler if found, else None
322 """
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100323 fnames = []
Simon Glass7e803e12014-12-01 17:34:06 -0700324 for subdir in ['.', 'bin', 'usr/bin']:
325 dirname = os.path.join(path, subdir)
Simon Glassc78ed662019-10-31 07:42:53 -0600326 if verbose: print(" - looking in '%s'" % dirname)
Simon Glass7e803e12014-12-01 17:34:06 -0700327 for fname in glob.glob(dirname + '/*gcc'):
Simon Glassc78ed662019-10-31 07:42:53 -0600328 if verbose: print(" - found '%s'" % fname)
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100329 fnames.append(fname)
330 return fnames
Simon Glass7e803e12014-12-01 17:34:06 -0700331
Simon Glassf5902732016-03-12 18:50:32 -0700332 def ScanPathEnv(self, fname):
333 """Scan the PATH environment variable for a given filename.
334
335 Args:
336 fname: Filename to scan for
337 Returns:
338 List of matching pathanames, or [] if none
339 """
340 pathname_list = []
341 for path in os.environ["PATH"].split(os.pathsep):
342 path = path.strip('"')
343 pathname = os.path.join(path, fname)
344 if os.path.exists(pathname):
345 pathname_list.append(pathname)
346 return pathname_list
Simon Glass7e803e12014-12-01 17:34:06 -0700347
Simon Glassc05694f2013-04-03 11:07:16 +0000348 def Scan(self, verbose):
349 """Scan for available toolchains and select the best for each arch.
350
351 We look for all the toolchains we can file, figure out the
352 architecture for each, and whether it works. Then we select the
353 highest priority toolchain for each arch.
354
355 Args:
356 verbose: True to print out progress information
357 """
Simon Glassc78ed662019-10-31 07:42:53 -0600358 if verbose: print('Scanning for tool chains')
Simon Glassf5902732016-03-12 18:50:32 -0700359 for name, value in self.prefixes:
Simon Glassc78ed662019-10-31 07:42:53 -0600360 if verbose: print(" - scanning prefix '%s'" % value)
Simon Glassf5902732016-03-12 18:50:32 -0700361 if os.path.exists(value):
362 self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
363 continue
364 fname = value + 'gcc'
365 if os.path.exists(fname):
366 self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
367 continue
368 fname_list = self.ScanPathEnv(fname)
369 for f in fname_list:
370 self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
371 if not fname_list:
Simon Glassc78ed662019-10-31 07:42:53 -0600372 raise ValueError("No tool chain found for prefix '%s'" %
Simon Glassf5902732016-03-12 18:50:32 -0700373 value)
Simon Glassc05694f2013-04-03 11:07:16 +0000374 for path in self.paths:
Simon Glassc78ed662019-10-31 07:42:53 -0600375 if verbose: print(" - scanning path '%s'" % path)
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100376 fnames = self.ScanPath(path, verbose)
377 for fname in fnames:
Simon Glass7e803e12014-12-01 17:34:06 -0700378 self.Add(fname, True, verbose)
Simon Glassc05694f2013-04-03 11:07:16 +0000379
380 def List(self):
381 """List out the selected toolchains for each architecture"""
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600382 col = terminal.Color()
Simon Glassf45d3742022-01-29 14:14:17 -0700383 print(col.build(col.BLUE, 'List of available toolchains (%d):' %
Simon Glassc78ed662019-10-31 07:42:53 -0600384 len(self.toolchains)))
Simon Glassc05694f2013-04-03 11:07:16 +0000385 if len(self.toolchains):
Simon Glassc78ed662019-10-31 07:42:53 -0600386 for key, value in sorted(self.toolchains.items()):
387 print('%-10s: %s' % (key, value.gcc))
Simon Glassc05694f2013-04-03 11:07:16 +0000388 else:
Simon Glassc78ed662019-10-31 07:42:53 -0600389 print('None')
Simon Glassc05694f2013-04-03 11:07:16 +0000390
391 def Select(self, arch):
392 """Returns the toolchain for a given architecture
393
394 Args:
395 args: Name of architecture (e.g. 'arm', 'ppc_8xx')
396
397 returns:
398 toolchain object, or None if none found
399 """
Simon Glass06b83a52023-07-19 17:49:05 -0600400 for tag, value in bsettings.get_items('toolchain-alias'):
Simon Glassc1528c12014-12-01 17:34:05 -0700401 if arch == tag:
402 for alias in value.split():
403 if alias in self.toolchains:
404 return self.toolchains[alias]
Simon Glassc05694f2013-04-03 11:07:16 +0000405
406 if not arch in self.toolchains:
Simon Glassc78ed662019-10-31 07:42:53 -0600407 raise ValueError("No tool chain found for arch '%s'" % arch)
Simon Glassc05694f2013-04-03 11:07:16 +0000408 return self.toolchains[arch]
Simon Glasscc246fb2013-09-23 17:35:17 -0600409
410 def ResolveReferences(self, var_dict, args):
411 """Resolve variable references in a string
412
413 This converts ${blah} within the string to the value of blah.
414 This function works recursively.
415
416 Args:
417 var_dict: Dictionary containing variables and their values
418 args: String containing make arguments
419 Returns:
420 Resolved string
421
Simon Glass06b83a52023-07-19 17:49:05 -0600422 >>> bsettings.setup(None)
Simon Glasscc246fb2013-09-23 17:35:17 -0600423 >>> tcs = Toolchains()
424 >>> tcs.Add('fred', False)
425 >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
426 'second' : '2nd'}
427 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
428 'this=OBLIQUE_set'
429 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
430 'this=OBLIQUE_setfi2ndrstnd'
431 """
Simon Glass53e189d2014-08-28 09:43:40 -0600432 re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
Simon Glasscc246fb2013-09-23 17:35:17 -0600433
434 while True:
435 m = re_var.search(args)
436 if not m:
437 break
438 lookup = m.group(0)[2:-1]
439 value = var_dict.get(lookup, '')
440 args = args[:m.start(0)] + value + args[m.end(0):]
441 return args
442
Simon Glass8132f982022-07-11 19:03:57 -0600443 def GetMakeArguments(self, brd):
Simon Glasscc246fb2013-09-23 17:35:17 -0600444 """Returns 'make' arguments for a given board
445
446 The flags are in a section called 'make-flags'. Flags are named
447 after the target they represent, for example snapper9260=TESTING=1
448 will pass TESTING=1 to make when building the snapper9260 board.
449
450 References to other boards can be added in the string also. For
451 example:
452
453 [make-flags]
454 at91-boards=ENABLE_AT91_TEST=1
455 snapper9260=${at91-boards} BUILD_TAG=442
456 snapper9g45=${at91-boards} BUILD_TAG=443
457
458 This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
459 and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
460
461 A special 'target' variable is set to the board target.
462
463 Args:
Simon Glass8132f982022-07-11 19:03:57 -0600464 brd: Board object for the board to check.
Simon Glasscc246fb2013-09-23 17:35:17 -0600465 Returns:
466 'make' flags for that board, or '' if none
467 """
Simon Glass8132f982022-07-11 19:03:57 -0600468 self._make_flags['target'] = brd.target
Simon Glasscc246fb2013-09-23 17:35:17 -0600469 arg_str = self.ResolveReferences(self._make_flags,
Simon Glass8132f982022-07-11 19:03:57 -0600470 self._make_flags.get(brd.target, ''))
Cristian Ciocaltea9da17fb2019-11-24 22:30:26 +0200471 args = re.findall("(?:\".*?\"|\S)+", arg_str)
Simon Glasscc246fb2013-09-23 17:35:17 -0600472 i = 0
473 while i < len(args):
Cristian Ciocaltea9da17fb2019-11-24 22:30:26 +0200474 args[i] = args[i].replace('"', '')
Simon Glasscc246fb2013-09-23 17:35:17 -0600475 if not args[i]:
476 del args[i]
477 else:
478 i += 1
479 return args
Simon Glass7e803e12014-12-01 17:34:06 -0700480
481 def LocateArchUrl(self, fetch_arch):
482 """Find a toolchain available online
483
484 Look in standard places for available toolchains. At present the
485 only standard place is at kernel.org.
486
487 Args:
488 arch: Architecture to look for, or 'list' for all
489 Returns:
490 If fetch_arch is 'list', a tuple:
491 Machine architecture (e.g. x86_64)
492 List of toolchains
493 else
494 URL containing this toolchain, if avaialble, else None
495 """
Simon Glass840be732022-01-29 14:14:05 -0700496 arch = command.output_one_line('uname', '-m')
Matthias Bruggerfb50a262020-01-17 10:53:37 +0100497 if arch == 'aarch64':
498 arch = 'arm64'
Simon Glass7e803e12014-12-01 17:34:06 -0700499 base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
Tom Rini3bd5ed72023-08-25 13:21:26 -0400500 versions = ['13.2.0', '12.2.0']
Simon Glass7e803e12014-12-01 17:34:06 -0700501 links = []
502 for version in versions:
503 url = '%s/%s/%s/' % (base, arch, version)
Simon Glassc78ed662019-10-31 07:42:53 -0600504 print('Checking: %s' % url)
505 response = urllib.request.urlopen(url)
Simon Glass80025522022-01-29 14:14:04 -0700506 html = tools.to_string(response.read())
Simon Glass7e803e12014-12-01 17:34:06 -0700507 parser = MyHTMLParser(fetch_arch)
508 parser.feed(html)
509 if fetch_arch == 'list':
510 links += parser.links
511 elif parser.arch_link:
512 return url + parser.arch_link
513 if fetch_arch == 'list':
514 return arch, links
515 return None
516
Simon Glass7e803e12014-12-01 17:34:06 -0700517 def Unpack(self, fname, dest):
518 """Unpack a tar file
519
520 Args:
521 fname: Filename to unpack
522 dest: Destination directory
523 Returns:
524 Directory name of the first entry in the archive, without the
525 trailing /
526 """
Simon Glass840be732022-01-29 14:14:05 -0700527 stdout = command.output('tar', 'xvfJ', fname, '-C', dest)
Trevor Woernere0557a72018-11-21 03:31:12 -0500528 dirs = stdout.splitlines()[1].split('/')[:2]
529 return '/'.join(dirs)
Simon Glass7e803e12014-12-01 17:34:06 -0700530
531 def TestSettingsHasPath(self, path):
Simon Glass43ea1282016-07-27 20:33:03 -0600532 """Check if buildman will find this toolchain
Simon Glass7e803e12014-12-01 17:34:06 -0700533
534 Returns:
535 True if the path is in settings, False if not
536 """
Simon Glassf38a6422016-07-27 20:33:01 -0600537 paths = self.GetPathList(False)
Simon Glass7e803e12014-12-01 17:34:06 -0700538 return path in paths
539
540 def ListArchs(self):
541 """List architectures with available toolchains to download"""
542 host_arch, archives = self.LocateArchUrl('list')
Trevor Woerner10a51622018-11-21 03:31:13 -0500543 re_arch = re.compile('[-a-z0-9.]*[-_]([^-]*)-.*')
Simon Glass7e803e12014-12-01 17:34:06 -0700544 arch_set = set()
545 for archive in archives:
546 # Remove the host architecture from the start
547 arch = re_arch.match(archive[len(host_arch):])
548 if arch:
Trevor Woerner10a51622018-11-21 03:31:13 -0500549 if arch.group(1) != '2.0' and arch.group(1) != '64':
550 arch_set.add(arch.group(1))
Simon Glass7e803e12014-12-01 17:34:06 -0700551 return sorted(arch_set)
552
553 def FetchAndInstall(self, arch):
554 """Fetch and install a new toolchain
555
556 arch:
557 Architecture to fetch, or 'list' to list
558 """
559 # Fist get the URL for this architecture
Simon Glass9f1ba0f2016-07-27 20:33:02 -0600560 col = terminal.Color()
Simon Glassf45d3742022-01-29 14:14:17 -0700561 print(col.build(col.BLUE, "Downloading toolchain for arch '%s'" % arch))
Simon Glass7e803e12014-12-01 17:34:06 -0700562 url = self.LocateArchUrl(arch)
563 if not url:
Simon Glassc78ed662019-10-31 07:42:53 -0600564 print(("Cannot find toolchain for arch '%s' - use 'list' to list" %
565 arch))
Simon Glass7e803e12014-12-01 17:34:06 -0700566 return 2
567 home = os.environ['HOME']
568 dest = os.path.join(home, '.buildman-toolchains')
569 if not os.path.exists(dest):
570 os.mkdir(dest)
571
572 # Download the tar file for this toolchain and unpack it
Simon Glass80025522022-01-29 14:14:04 -0700573 tarfile, tmpdir = tools.download(url, '.buildman')
Simon Glass7e803e12014-12-01 17:34:06 -0700574 if not tarfile:
575 return 1
Simon Glassf45d3742022-01-29 14:14:17 -0700576 print(col.build(col.GREEN, 'Unpacking to: %s' % dest), end=' ')
Simon Glass7e803e12014-12-01 17:34:06 -0700577 sys.stdout.flush()
578 path = self.Unpack(tarfile, dest)
579 os.remove(tarfile)
580 os.rmdir(tmpdir)
Simon Glassc78ed662019-10-31 07:42:53 -0600581 print()
Simon Glass7e803e12014-12-01 17:34:06 -0700582
583 # Check that the toolchain works
Simon Glassf45d3742022-01-29 14:14:17 -0700584 print(col.build(col.GREEN, 'Testing'))
Simon Glass7e803e12014-12-01 17:34:06 -0700585 dirpath = os.path.join(dest, path)
Simon Glassf6757502015-03-02 17:05:15 -0700586 compiler_fname_list = self.ScanPath(dirpath, True)
587 if not compiler_fname_list:
Simon Glassc78ed662019-10-31 07:42:53 -0600588 print('Could not locate C compiler - fetch failed.')
Simon Glass7e803e12014-12-01 17:34:06 -0700589 return 1
Simon Glassf6757502015-03-02 17:05:15 -0700590 if len(compiler_fname_list) != 1:
Simon Glassf45d3742022-01-29 14:14:17 -0700591 print(col.build(col.RED, 'Warning, ambiguous toolchains: %s' %
Simon Glassc78ed662019-10-31 07:42:53 -0600592 ', '.join(compiler_fname_list)))
Simon Glassf6757502015-03-02 17:05:15 -0700593 toolchain = Toolchain(compiler_fname_list[0], True, True)
Simon Glass7e803e12014-12-01 17:34:06 -0700594
595 # Make sure that it will be found by buildman
596 if not self.TestSettingsHasPath(dirpath):
Simon Glassc78ed662019-10-31 07:42:53 -0600597 print(("Adding 'download' to config file '%s'" %
598 bsettings.config_fname))
Simon Glass06b83a52023-07-19 17:49:05 -0600599 bsettings.set_item('toolchain', 'download', '%s/*/*' % dest)
Simon Glass7e803e12014-12-01 17:34:06 -0700600 return 0