blob: 0ecd8458b91207ee8ce6b33f62db0f06755fb0c4 [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
Simon Glassbb1501f2014-12-01 17:34:00 -070093 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 = ''
142 for name, value in bsettings.GetItems('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
162 return wrapper + os.path.join(base, self.cross)
Simon Glass57cb9d52019-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 Glassbb1501f2014-12-01 17:34:00 -0700175 def MakeEnvironment(self, full_path):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000176 """Returns an environment for using the toolchain.
177
Simon Glassbb1501f2014-12-01 17:34:00 -0700178 Thie takes the current environment and adds CROSS_COMPILE so that
Daniel Schwierzeckb0e994c2017-06-08 03:07:08 +0200179 the tool chain will operate correctly. This also disables localized
180 output and possibly unicode encoded output of all build tools by
181 adding LC_ALL=C.
Simon Glassbb1501f2014-12-01 17:34:00 -0700182
Simon Glassf1a83ab2021-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 Glassbb1501f2014-12-01 17:34:00 -0700190 Args:
191 full_path: Return the full path in CROSS_COMPILE and don't set
192 PATH
Simon Glass00beb242019-01-07 16:44:20 -0700193 Returns:
Simon Glassf1a83ab2021-04-11 16:27:28 +1200194 Dict containing the (bytes) environment to use. This is based on the
195 current environment, with changes as needed to CROSS_COMPILE, PATH
196 and LC_ALL.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000197 """
Simon Glassf1a83ab2021-04-11 16:27:28 +1200198 env = dict(os.environb)
York Sund5fe0132016-10-04 14:33:51 -0700199 wrapper = self.GetWrapper()
200
Simon Glass00beb242019-01-07 16:44:20 -0700201 if self.override_toolchain:
202 # We'll use MakeArgs() to provide this
203 pass
204 elif full_path:
Simon Glassc1aa66e2022-01-29 14:14:04 -0700205 env[b'CROSS_COMPILE'] = tools.to_bytes(
Simon Glassf1a83ab2021-04-11 16:27:28 +1200206 wrapper + os.path.join(self.path, self.cross))
Simon Glassbb1501f2014-12-01 17:34:00 -0700207 else:
Simon Glassc1aa66e2022-01-29 14:14:04 -0700208 env[b'CROSS_COMPILE'] = tools.to_bytes(wrapper + self.cross)
209 env[b'PATH'] = tools.to_bytes(self.path) + b':' + env[b'PATH']
Simon Glassbb1501f2014-12-01 17:34:00 -0700210
Simon Glassf1a83ab2021-04-11 16:27:28 +1200211 env[b'LC_ALL'] = b'C'
Daniel Schwierzeckb0e994c2017-06-08 03:07:08 +0200212
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000213 return env
214
Simon Glass00beb242019-01-07 16:44:20 -0700215 def MakeArgs(self):
216 """Create the 'make' arguments for a toolchain
217
218 This is only used when the toolchain is being overridden. Since the
219 U-Boot Makefile sets CC and HOSTCC explicitly we cannot rely on the
220 environment (and MakeEnvironment()) to override these values. This
221 function returns the arguments to accomplish this.
222
223 Returns:
224 List of arguments to pass to 'make'
225 """
226 if self.override_toolchain:
227 return ['HOSTCC=%s' % self.override_toolchain,
228 'CC=%s' % self.override_toolchain]
229 return []
230
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000231
232class Toolchains:
233 """Manage a list of toolchains for building U-Boot
234
235 We select one toolchain for each architecture type
236
237 Public members:
238 toolchains: Dict of Toolchain objects, keyed by architecture name
Simon Glass17bce662016-03-12 18:50:32 -0700239 prefixes: Dict of prefixes to check, keyed by architecture. This can
240 be a full path and toolchain prefix, for example
241 {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
242 something on the search path, for example
243 {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000244 paths: List of paths to check for toolchains (may contain wildcards)
245 """
246
Simon Glass00beb242019-01-07 16:44:20 -0700247 def __init__(self, override_toolchain=None):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000248 self.toolchains = {}
Simon Glass17bce662016-03-12 18:50:32 -0700249 self.prefixes = {}
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000250 self.paths = []
Simon Glass00beb242019-01-07 16:44:20 -0700251 self.override_toolchain = override_toolchain
Simon Glassd4144e42014-09-05 19:00:13 -0600252 self._make_flags = dict(bsettings.GetItems('make-flags'))
253
Simon Glass80e6a482016-07-27 20:33:01 -0600254 def GetPathList(self, show_warning=True):
Simon Glass827e37b2014-12-01 17:34:06 -0700255 """Get a list of available toolchain paths
256
Simon Glass80e6a482016-07-27 20:33:01 -0600257 Args:
258 show_warning: True to show a warning if there are no tool chains.
259
Simon Glass827e37b2014-12-01 17:34:06 -0700260 Returns:
261 List of strings, each a path to a toolchain mentioned in the
262 [toolchain] section of the settings file.
263 """
Simon Glass4281ad82013-09-23 17:35:17 -0600264 toolchains = bsettings.GetItems('toolchain')
Simon Glass80e6a482016-07-27 20:33:01 -0600265 if show_warning and not toolchains:
Simon Glassc05aa032019-10-31 07:42:53 -0600266 print(("Warning: No tool chains. Please run 'buildman "
Simon Glass713bea32016-07-27 20:33:02 -0600267 "--fetch-arch all' to download all available toolchains, or "
268 "add a [toolchain] section to your buildman config file "
Heinrich Schuchardteeb55252023-01-19 16:22:10 +0100269 "%s. See buildman.rst for details" %
Simon Glassc05aa032019-10-31 07:42:53 -0600270 bsettings.config_fname))
Simon Glass4281ad82013-09-23 17:35:17 -0600271
Simon Glass827e37b2014-12-01 17:34:06 -0700272 paths = []
Simon Glass4281ad82013-09-23 17:35:17 -0600273 for name, value in toolchains:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000274 if '*' in value:
Simon Glass827e37b2014-12-01 17:34:06 -0700275 paths += glob.glob(value)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000276 else:
Simon Glass827e37b2014-12-01 17:34:06 -0700277 paths.append(value)
278 return paths
279
Simon Glass80e6a482016-07-27 20:33:01 -0600280 def GetSettings(self, show_warning=True):
281 """Get toolchain settings from the settings file.
282
283 Args:
284 show_warning: True to show a warning if there are no tool chains.
285 """
286 self.prefixes = bsettings.GetItems('toolchain-prefix')
287 self.paths += self.GetPathList(show_warning)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000288
Simon Glass608e3992016-03-06 19:45:38 -0700289 def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
290 arch=None):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000291 """Add a toolchain to our list
292
293 We select the given toolchain as our preferred one for its
294 architecture if it is a higher priority than the others.
295
296 Args:
297 fname: Filename of toolchain's gcc driver
298 test: True to run the toolchain to test it
Simon Glassff690df2016-03-06 19:45:37 -0700299 priority: Priority to use for this toolchain
Simon Glass608e3992016-03-06 19:45:38 -0700300 arch: Toolchain architecture, or None if not known
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000301 """
Simon Glass00beb242019-01-07 16:44:20 -0700302 toolchain = Toolchain(fname, test, verbose, priority, arch,
303 self.override_toolchain)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000304 add_it = toolchain.ok
305 if toolchain.arch in self.toolchains:
306 add_it = (toolchain.priority <
307 self.toolchains[toolchain.arch].priority)
308 if add_it:
309 self.toolchains[toolchain.arch] = toolchain
Simon Glassff690df2016-03-06 19:45:37 -0700310 elif verbose:
Simon Glassc05aa032019-10-31 07:42:53 -0600311 print(("Toolchain '%s' at priority %d will be ignored because "
Simon Glassff690df2016-03-06 19:45:37 -0700312 "another toolchain for arch '%s' has priority %d" %
313 (toolchain.gcc, toolchain.priority, toolchain.arch,
Simon Glassc05aa032019-10-31 07:42:53 -0600314 self.toolchains[toolchain.arch].priority)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000315
Simon Glass827e37b2014-12-01 17:34:06 -0700316 def ScanPath(self, path, verbose):
317 """Scan a path for a valid toolchain
318
319 Args:
320 path: Path to scan
321 verbose: True to print out progress information
322 Returns:
323 Filename of C compiler if found, else None
324 """
Albert ARIBAUDd9088982015-02-01 00:12:44 +0100325 fnames = []
Simon Glass827e37b2014-12-01 17:34:06 -0700326 for subdir in ['.', 'bin', 'usr/bin']:
327 dirname = os.path.join(path, subdir)
Simon Glassc05aa032019-10-31 07:42:53 -0600328 if verbose: print(" - looking in '%s'" % dirname)
Simon Glass827e37b2014-12-01 17:34:06 -0700329 for fname in glob.glob(dirname + '/*gcc'):
Simon Glassc05aa032019-10-31 07:42:53 -0600330 if verbose: print(" - found '%s'" % fname)
Albert ARIBAUDd9088982015-02-01 00:12:44 +0100331 fnames.append(fname)
332 return fnames
Simon Glass827e37b2014-12-01 17:34:06 -0700333
Simon Glass17bce662016-03-12 18:50:32 -0700334 def ScanPathEnv(self, fname):
335 """Scan the PATH environment variable for a given filename.
336
337 Args:
338 fname: Filename to scan for
339 Returns:
340 List of matching pathanames, or [] if none
341 """
342 pathname_list = []
343 for path in os.environ["PATH"].split(os.pathsep):
344 path = path.strip('"')
345 pathname = os.path.join(path, fname)
346 if os.path.exists(pathname):
347 pathname_list.append(pathname)
348 return pathname_list
Simon Glass827e37b2014-12-01 17:34:06 -0700349
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000350 def Scan(self, verbose):
351 """Scan for available toolchains and select the best for each arch.
352
353 We look for all the toolchains we can file, figure out the
354 architecture for each, and whether it works. Then we select the
355 highest priority toolchain for each arch.
356
357 Args:
358 verbose: True to print out progress information
359 """
Simon Glassc05aa032019-10-31 07:42:53 -0600360 if verbose: print('Scanning for tool chains')
Simon Glass17bce662016-03-12 18:50:32 -0700361 for name, value in self.prefixes:
Simon Glassc05aa032019-10-31 07:42:53 -0600362 if verbose: print(" - scanning prefix '%s'" % value)
Simon Glass17bce662016-03-12 18:50:32 -0700363 if os.path.exists(value):
364 self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
365 continue
366 fname = value + 'gcc'
367 if os.path.exists(fname):
368 self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
369 continue
370 fname_list = self.ScanPathEnv(fname)
371 for f in fname_list:
372 self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
373 if not fname_list:
Simon Glassc05aa032019-10-31 07:42:53 -0600374 raise ValueError("No tool chain found for prefix '%s'" %
Simon Glass17bce662016-03-12 18:50:32 -0700375 value)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000376 for path in self.paths:
Simon Glassc05aa032019-10-31 07:42:53 -0600377 if verbose: print(" - scanning path '%s'" % path)
Albert ARIBAUDd9088982015-02-01 00:12:44 +0100378 fnames = self.ScanPath(path, verbose)
379 for fname in fnames:
Simon Glass827e37b2014-12-01 17:34:06 -0700380 self.Add(fname, True, verbose)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000381
382 def List(self):
383 """List out the selected toolchains for each architecture"""
Simon Glass713bea32016-07-27 20:33:02 -0600384 col = terminal.Color()
Simon Glass252ac582022-01-29 14:14:17 -0700385 print(col.build(col.BLUE, 'List of available toolchains (%d):' %
Simon Glassc05aa032019-10-31 07:42:53 -0600386 len(self.toolchains)))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000387 if len(self.toolchains):
Simon Glassc05aa032019-10-31 07:42:53 -0600388 for key, value in sorted(self.toolchains.items()):
389 print('%-10s: %s' % (key, value.gcc))
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000390 else:
Simon Glassc05aa032019-10-31 07:42:53 -0600391 print('None')
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000392
393 def Select(self, arch):
394 """Returns the toolchain for a given architecture
395
396 Args:
397 args: Name of architecture (e.g. 'arm', 'ppc_8xx')
398
399 returns:
400 toolchain object, or None if none found
401 """
Simon Glass9b83bfd2014-12-01 17:34:05 -0700402 for tag, value in bsettings.GetItems('toolchain-alias'):
403 if arch == tag:
404 for alias in value.split():
405 if alias in self.toolchains:
406 return self.toolchains[alias]
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000407
408 if not arch in self.toolchains:
Simon Glassc05aa032019-10-31 07:42:53 -0600409 raise ValueError("No tool chain found for arch '%s'" % arch)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000410 return self.toolchains[arch]
Simon Glass4281ad82013-09-23 17:35:17 -0600411
412 def ResolveReferences(self, var_dict, args):
413 """Resolve variable references in a string
414
415 This converts ${blah} within the string to the value of blah.
416 This function works recursively.
417
418 Args:
419 var_dict: Dictionary containing variables and their values
420 args: String containing make arguments
421 Returns:
422 Resolved string
423
Simon Glass49d8cc42023-03-28 10:47:37 +1300424 >>> bsettings.Setup(None)
Simon Glass4281ad82013-09-23 17:35:17 -0600425 >>> tcs = Toolchains()
426 >>> tcs.Add('fred', False)
427 >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
428 'second' : '2nd'}
429 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
430 'this=OBLIQUE_set'
431 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
432 'this=OBLIQUE_setfi2ndrstnd'
433 """
Simon Glassf60c9d42014-08-28 09:43:40 -0600434 re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
Simon Glass4281ad82013-09-23 17:35:17 -0600435
436 while True:
437 m = re_var.search(args)
438 if not m:
439 break
440 lookup = m.group(0)[2:-1]
441 value = var_dict.get(lookup, '')
442 args = args[:m.start(0)] + value + args[m.end(0):]
443 return args
444
Simon Glassf4ed4702022-07-11 19:03:57 -0600445 def GetMakeArguments(self, brd):
Simon Glass4281ad82013-09-23 17:35:17 -0600446 """Returns 'make' arguments for a given board
447
448 The flags are in a section called 'make-flags'. Flags are named
449 after the target they represent, for example snapper9260=TESTING=1
450 will pass TESTING=1 to make when building the snapper9260 board.
451
452 References to other boards can be added in the string also. For
453 example:
454
455 [make-flags]
456 at91-boards=ENABLE_AT91_TEST=1
457 snapper9260=${at91-boards} BUILD_TAG=442
458 snapper9g45=${at91-boards} BUILD_TAG=443
459
460 This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
461 and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
462
463 A special 'target' variable is set to the board target.
464
465 Args:
Simon Glassf4ed4702022-07-11 19:03:57 -0600466 brd: Board object for the board to check.
Simon Glass4281ad82013-09-23 17:35:17 -0600467 Returns:
468 'make' flags for that board, or '' if none
469 """
Simon Glassf4ed4702022-07-11 19:03:57 -0600470 self._make_flags['target'] = brd.target
Simon Glass4281ad82013-09-23 17:35:17 -0600471 arg_str = self.ResolveReferences(self._make_flags,
Simon Glassf4ed4702022-07-11 19:03:57 -0600472 self._make_flags.get(brd.target, ''))
Cristian Ciocaltea4251fbc2019-11-24 22:30:26 +0200473 args = re.findall("(?:\".*?\"|\S)+", arg_str)
Simon Glass4281ad82013-09-23 17:35:17 -0600474 i = 0
475 while i < len(args):
Cristian Ciocaltea4251fbc2019-11-24 22:30:26 +0200476 args[i] = args[i].replace('"', '')
Simon Glass4281ad82013-09-23 17:35:17 -0600477 if not args[i]:
478 del args[i]
479 else:
480 i += 1
481 return args
Simon Glass827e37b2014-12-01 17:34:06 -0700482
483 def LocateArchUrl(self, fetch_arch):
484 """Find a toolchain available online
485
486 Look in standard places for available toolchains. At present the
487 only standard place is at kernel.org.
488
489 Args:
490 arch: Architecture to look for, or 'list' for all
491 Returns:
492 If fetch_arch is 'list', a tuple:
493 Machine architecture (e.g. x86_64)
494 List of toolchains
495 else
496 URL containing this toolchain, if avaialble, else None
497 """
Simon Glassd9800692022-01-29 14:14:05 -0700498 arch = command.output_one_line('uname', '-m')
Matthias Brugger2f7c53c2020-01-17 10:53:37 +0100499 if arch == 'aarch64':
500 arch = 'arm64'
Simon Glass827e37b2014-12-01 17:34:06 -0700501 base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
Tom Rini83e37a82022-11-22 12:31:53 -0500502 versions = ['12.2.0', '11.1.0']
Simon Glass827e37b2014-12-01 17:34:06 -0700503 links = []
504 for version in versions:
505 url = '%s/%s/%s/' % (base, arch, version)
Simon Glassc05aa032019-10-31 07:42:53 -0600506 print('Checking: %s' % url)
507 response = urllib.request.urlopen(url)
Simon Glassc1aa66e2022-01-29 14:14:04 -0700508 html = tools.to_string(response.read())
Simon Glass827e37b2014-12-01 17:34:06 -0700509 parser = MyHTMLParser(fetch_arch)
510 parser.feed(html)
511 if fetch_arch == 'list':
512 links += parser.links
513 elif parser.arch_link:
514 return url + parser.arch_link
515 if fetch_arch == 'list':
516 return arch, links
517 return None
518
Simon Glass827e37b2014-12-01 17:34:06 -0700519 def Unpack(self, fname, dest):
520 """Unpack a tar file
521
522 Args:
523 fname: Filename to unpack
524 dest: Destination directory
525 Returns:
526 Directory name of the first entry in the archive, without the
527 trailing /
528 """
Simon Glassd9800692022-01-29 14:14:05 -0700529 stdout = command.output('tar', 'xvfJ', fname, '-C', dest)
Trevor Woernerd82f5392018-11-21 03:31:12 -0500530 dirs = stdout.splitlines()[1].split('/')[:2]
531 return '/'.join(dirs)
Simon Glass827e37b2014-12-01 17:34:06 -0700532
533 def TestSettingsHasPath(self, path):
Simon Glass2289b272016-07-27 20:33:03 -0600534 """Check if buildman will find this toolchain
Simon Glass827e37b2014-12-01 17:34:06 -0700535
536 Returns:
537 True if the path is in settings, False if not
538 """
Simon Glass80e6a482016-07-27 20:33:01 -0600539 paths = self.GetPathList(False)
Simon Glass827e37b2014-12-01 17:34:06 -0700540 return path in paths
541
542 def ListArchs(self):
543 """List architectures with available toolchains to download"""
544 host_arch, archives = self.LocateArchUrl('list')
Trevor Woernerb11f1262018-11-21 03:31:13 -0500545 re_arch = re.compile('[-a-z0-9.]*[-_]([^-]*)-.*')
Simon Glass827e37b2014-12-01 17:34:06 -0700546 arch_set = set()
547 for archive in archives:
548 # Remove the host architecture from the start
549 arch = re_arch.match(archive[len(host_arch):])
550 if arch:
Trevor Woernerb11f1262018-11-21 03:31:13 -0500551 if arch.group(1) != '2.0' and arch.group(1) != '64':
552 arch_set.add(arch.group(1))
Simon Glass827e37b2014-12-01 17:34:06 -0700553 return sorted(arch_set)
554
555 def FetchAndInstall(self, arch):
556 """Fetch and install a new toolchain
557
558 arch:
559 Architecture to fetch, or 'list' to list
560 """
561 # Fist get the URL for this architecture
Simon Glass713bea32016-07-27 20:33:02 -0600562 col = terminal.Color()
Simon Glass252ac582022-01-29 14:14:17 -0700563 print(col.build(col.BLUE, "Downloading toolchain for arch '%s'" % arch))
Simon Glass827e37b2014-12-01 17:34:06 -0700564 url = self.LocateArchUrl(arch)
565 if not url:
Simon Glassc05aa032019-10-31 07:42:53 -0600566 print(("Cannot find toolchain for arch '%s' - use 'list' to list" %
567 arch))
Simon Glass827e37b2014-12-01 17:34:06 -0700568 return 2
569 home = os.environ['HOME']
570 dest = os.path.join(home, '.buildman-toolchains')
571 if not os.path.exists(dest):
572 os.mkdir(dest)
573
574 # Download the tar file for this toolchain and unpack it
Simon Glassc1aa66e2022-01-29 14:14:04 -0700575 tarfile, tmpdir = tools.download(url, '.buildman')
Simon Glass827e37b2014-12-01 17:34:06 -0700576 if not tarfile:
577 return 1
Simon Glass252ac582022-01-29 14:14:17 -0700578 print(col.build(col.GREEN, 'Unpacking to: %s' % dest), end=' ')
Simon Glass827e37b2014-12-01 17:34:06 -0700579 sys.stdout.flush()
580 path = self.Unpack(tarfile, dest)
581 os.remove(tarfile)
582 os.rmdir(tmpdir)
Simon Glassc05aa032019-10-31 07:42:53 -0600583 print()
Simon Glass827e37b2014-12-01 17:34:06 -0700584
585 # Check that the toolchain works
Simon Glass252ac582022-01-29 14:14:17 -0700586 print(col.build(col.GREEN, 'Testing'))
Simon Glass827e37b2014-12-01 17:34:06 -0700587 dirpath = os.path.join(dest, path)
Simon Glass2a76a642015-03-02 17:05:15 -0700588 compiler_fname_list = self.ScanPath(dirpath, True)
589 if not compiler_fname_list:
Simon Glassc05aa032019-10-31 07:42:53 -0600590 print('Could not locate C compiler - fetch failed.')
Simon Glass827e37b2014-12-01 17:34:06 -0700591 return 1
Simon Glass2a76a642015-03-02 17:05:15 -0700592 if len(compiler_fname_list) != 1:
Simon Glass252ac582022-01-29 14:14:17 -0700593 print(col.build(col.RED, 'Warning, ambiguous toolchains: %s' %
Simon Glassc05aa032019-10-31 07:42:53 -0600594 ', '.join(compiler_fname_list)))
Simon Glass2a76a642015-03-02 17:05:15 -0700595 toolchain = Toolchain(compiler_fname_list[0], True, True)
Simon Glass827e37b2014-12-01 17:34:06 -0700596
597 # Make sure that it will be found by buildman
598 if not self.TestSettingsHasPath(dirpath):
Simon Glassc05aa032019-10-31 07:42:53 -0600599 print(("Adding 'download' to config file '%s'" %
600 bsettings.config_fname))
Simon Glassc8785c52016-07-27 20:33:05 -0600601 bsettings.SetItem('toolchain', 'download', '%s/*/*' % dest)
Simon Glass827e37b2014-12-01 17:34:06 -0700602 return 0