blob: 3993db3a8d65f43738c52c1ed8c23f215ef33c75 [file] [log] [blame]
Simon Glassc05694f2013-04-03 11:07:16 +00001# Copyright (c) 2012 The Chromium OS Authors.
2#
Wolfgang Denkd79de1d2013-07-08 09:37:19 +02003# SPDX-License-Identifier: GPL-2.0+
Simon Glassc05694f2013-04-03 11:07:16 +00004#
5
Simon Glasscc246fb2013-09-23 17:35:17 -06006import re
Simon Glassc05694f2013-04-03 11:07:16 +00007import glob
Simon Glass7e803e12014-12-01 17:34:06 -07008from HTMLParser import HTMLParser
Simon Glassc05694f2013-04-03 11:07:16 +00009import os
Simon Glass7e803e12014-12-01 17:34:06 -070010import sys
11import tempfile
12import urllib2
Simon Glassc05694f2013-04-03 11:07:16 +000013
14import bsettings
15import command
16
Simon Glassf5902732016-03-12 18:50:32 -070017(PRIORITY_FULL_PREFIX, PRIORITY_PREFIX_GCC, PRIORITY_PREFIX_GCC_PATH,
18 PRIORITY_CALC) = range(4)
Simon Glass7b971292016-03-06 19:45:37 -070019
Simon Glass7e803e12014-12-01 17:34:06 -070020# Simple class to collect links from a page
21class MyHTMLParser(HTMLParser):
22 def __init__(self, arch):
23 """Create a new parser
24
25 After the parser runs, self.links will be set to a list of the links
26 to .xz archives found in the page, and self.arch_link will be set to
27 the one for the given architecture (or None if not found).
28
29 Args:
30 arch: Architecture to search for
31 """
32 HTMLParser.__init__(self)
33 self.arch_link = None
34 self.links = []
35 self._match = '_%s-' % arch
36
37 def handle_starttag(self, tag, attrs):
38 if tag == 'a':
39 for tag, value in attrs:
40 if tag == 'href':
41 if value and value.endswith('.xz'):
42 self.links.append(value)
43 if self._match in value:
44 self.arch_link = value
45
46
Simon Glassc05694f2013-04-03 11:07:16 +000047class Toolchain:
48 """A single toolchain
49
50 Public members:
51 gcc: Full path to C compiler
52 path: Directory path containing C compiler
53 cross: Cross compile string, e.g. 'arm-linux-'
54 arch: Architecture of toolchain as determined from the first
55 component of the filename. E.g. arm-linux-gcc becomes arm
Simon Glass7b971292016-03-06 19:45:37 -070056 priority: Toolchain priority (0=highest, 20=lowest)
Simon Glassc05694f2013-04-03 11:07:16 +000057 """
Simon Glassc6cdd3e2016-03-06 19:45:38 -070058 def __init__(self, fname, test, verbose=False, priority=PRIORITY_CALC,
59 arch=None):
Simon Glassc05694f2013-04-03 11:07:16 +000060 """Create a new toolchain object.
61
62 Args:
63 fname: Filename of the gcc component
64 test: True to run the toolchain to test it
Simon Glassd6ece322016-03-06 19:45:35 -070065 verbose: True to print out the information
Simon Glass7b971292016-03-06 19:45:37 -070066 priority: Priority to use for this toolchain, or PRIORITY_CALC to
67 calculate it
Simon Glassc05694f2013-04-03 11:07:16 +000068 """
69 self.gcc = fname
70 self.path = os.path.dirname(fname)
Simon Glass28ed0062014-12-01 17:33:58 -070071
72 # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
73 # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
74 basename = os.path.basename(fname)
75 pos = basename.rfind('-')
76 self.cross = basename[:pos + 1] if pos != -1 else ''
77
78 # The architecture is the first part of the name
Simon Glassc05694f2013-04-03 11:07:16 +000079 pos = self.cross.find('-')
Simon Glassc6cdd3e2016-03-06 19:45:38 -070080 if arch:
81 self.arch = arch
82 else:
83 self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
Simon Glassc05694f2013-04-03 11:07:16 +000084
Simon Glassd48a46c2014-12-01 17:34:00 -070085 env = self.MakeEnvironment(False)
Simon Glassc05694f2013-04-03 11:07:16 +000086
87 # As a basic sanity check, run the C compiler with --version
88 cmd = [fname, '--version']
Simon Glass7b971292016-03-06 19:45:37 -070089 if priority == PRIORITY_CALC:
90 self.priority = self.GetPriority(fname)
91 else:
92 self.priority = priority
Simon Glassc05694f2013-04-03 11:07:16 +000093 if test:
Stephen Warren288d7672013-10-09 14:28:09 -060094 result = command.RunPipe([cmd], capture=True, env=env,
95 raise_on_error=False)
Simon Glassc05694f2013-04-03 11:07:16 +000096 self.ok = result.return_code == 0
97 if verbose:
98 print 'Tool chain test: ',
99 if self.ok:
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700100 print "OK, arch='%s', priority %d" % (self.arch,
101 self.priority)
Simon Glassc05694f2013-04-03 11:07:16 +0000102 else:
103 print 'BAD'
104 print 'Command: ', cmd
105 print result.stdout
106 print result.stderr
107 else:
108 self.ok = True
Simon Glassc05694f2013-04-03 11:07:16 +0000109
110 def GetPriority(self, fname):
111 """Return the priority of the toolchain.
112
113 Toolchains are ranked according to their suitability by their
114 filename prefix.
115
116 Args:
117 fname: Filename of toolchain
118 Returns:
Simon Glass7b971292016-03-06 19:45:37 -0700119 Priority of toolchain, PRIORITY_CALC=highest, 20=lowest.
Simon Glassc05694f2013-04-03 11:07:16 +0000120 """
Masahiro Yamadafa25e1f2014-07-07 09:47:45 +0900121 priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
Simon Glassc05694f2013-04-03 11:07:16 +0000122 '-none-linux-gnueabi', '-uclinux', '-none-eabi',
123 '-gentoo-linux-gnu', '-linux-gnueabi', '-le-linux', '-uclinux']
124 for prio in range(len(priority_list)):
125 if priority_list[prio] in fname:
Simon Glass7b971292016-03-06 19:45:37 -0700126 return PRIORITY_CALC + prio
127 return PRIORITY_CALC + prio
Simon Glassc05694f2013-04-03 11:07:16 +0000128
Simon Glassd48a46c2014-12-01 17:34:00 -0700129 def MakeEnvironment(self, full_path):
Simon Glassc05694f2013-04-03 11:07:16 +0000130 """Returns an environment for using the toolchain.
131
Simon Glassd48a46c2014-12-01 17:34:00 -0700132 Thie takes the current environment and adds CROSS_COMPILE so that
133 the tool chain will operate correctly.
134
135 Args:
136 full_path: Return the full path in CROSS_COMPILE and don't set
137 PATH
Simon Glassc05694f2013-04-03 11:07:16 +0000138 """
139 env = dict(os.environ)
Simon Glassd48a46c2014-12-01 17:34:00 -0700140 if full_path:
141 env['CROSS_COMPILE'] = os.path.join(self.path, self.cross)
142 else:
143 env['CROSS_COMPILE'] = self.cross
144 env['PATH'] = self.path + ':' + env['PATH']
145
Simon Glassc05694f2013-04-03 11:07:16 +0000146 return env
147
148
149class Toolchains:
150 """Manage a list of toolchains for building U-Boot
151
152 We select one toolchain for each architecture type
153
154 Public members:
155 toolchains: Dict of Toolchain objects, keyed by architecture name
Simon Glassf5902732016-03-12 18:50:32 -0700156 prefixes: Dict of prefixes to check, keyed by architecture. This can
157 be a full path and toolchain prefix, for example
158 {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
159 something on the search path, for example
160 {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
Simon Glassc05694f2013-04-03 11:07:16 +0000161 paths: List of paths to check for toolchains (may contain wildcards)
162 """
163
164 def __init__(self):
165 self.toolchains = {}
Simon Glassf5902732016-03-12 18:50:32 -0700166 self.prefixes = {}
Simon Glassc05694f2013-04-03 11:07:16 +0000167 self.paths = []
Simon Glassed098bb2014-09-05 19:00:13 -0600168 self._make_flags = dict(bsettings.GetItems('make-flags'))
169
Simon Glass7e803e12014-12-01 17:34:06 -0700170 def GetPathList(self):
171 """Get a list of available toolchain paths
172
173 Returns:
174 List of strings, each a path to a toolchain mentioned in the
175 [toolchain] section of the settings file.
176 """
Simon Glasscc246fb2013-09-23 17:35:17 -0600177 toolchains = bsettings.GetItems('toolchain')
178 if not toolchains:
Simon Glassd6ece322016-03-06 19:45:35 -0700179 print ('Warning: No tool chains - please add a [toolchain] section'
180 ' to your buildman config file %s. See README for details' %
Masahiro Yamada73c12672014-07-07 09:46:36 +0900181 bsettings.config_fname)
Simon Glasscc246fb2013-09-23 17:35:17 -0600182
Simon Glass7e803e12014-12-01 17:34:06 -0700183 paths = []
Simon Glasscc246fb2013-09-23 17:35:17 -0600184 for name, value in toolchains:
Simon Glassc05694f2013-04-03 11:07:16 +0000185 if '*' in value:
Simon Glass7e803e12014-12-01 17:34:06 -0700186 paths += glob.glob(value)
Simon Glassc05694f2013-04-03 11:07:16 +0000187 else:
Simon Glass7e803e12014-12-01 17:34:06 -0700188 paths.append(value)
189 return paths
190
191 def GetSettings(self):
Simon Glassf5902732016-03-12 18:50:32 -0700192 self.prefixes = bsettings.GetItems('toolchain-prefix')
Simon Glass7e803e12014-12-01 17:34:06 -0700193 self.paths += self.GetPathList()
Simon Glassc05694f2013-04-03 11:07:16 +0000194
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700195 def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
196 arch=None):
Simon Glassc05694f2013-04-03 11:07:16 +0000197 """Add a toolchain to our list
198
199 We select the given toolchain as our preferred one for its
200 architecture if it is a higher priority than the others.
201
202 Args:
203 fname: Filename of toolchain's gcc driver
204 test: True to run the toolchain to test it
Simon Glass7b971292016-03-06 19:45:37 -0700205 priority: Priority to use for this toolchain
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700206 arch: Toolchain architecture, or None if not known
Simon Glassc05694f2013-04-03 11:07:16 +0000207 """
Simon Glassc6cdd3e2016-03-06 19:45:38 -0700208 toolchain = Toolchain(fname, test, verbose, priority, arch)
Simon Glassc05694f2013-04-03 11:07:16 +0000209 add_it = toolchain.ok
210 if toolchain.arch in self.toolchains:
211 add_it = (toolchain.priority <
212 self.toolchains[toolchain.arch].priority)
213 if add_it:
214 self.toolchains[toolchain.arch] = toolchain
Simon Glass7b971292016-03-06 19:45:37 -0700215 elif verbose:
216 print ("Toolchain '%s' at priority %d will be ignored because "
217 "another toolchain for arch '%s' has priority %d" %
218 (toolchain.gcc, toolchain.priority, toolchain.arch,
219 self.toolchains[toolchain.arch].priority))
Simon Glassc05694f2013-04-03 11:07:16 +0000220
Simon Glass7e803e12014-12-01 17:34:06 -0700221 def ScanPath(self, path, verbose):
222 """Scan a path for a valid toolchain
223
224 Args:
225 path: Path to scan
226 verbose: True to print out progress information
227 Returns:
228 Filename of C compiler if found, else None
229 """
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100230 fnames = []
Simon Glass7e803e12014-12-01 17:34:06 -0700231 for subdir in ['.', 'bin', 'usr/bin']:
232 dirname = os.path.join(path, subdir)
233 if verbose: print " - looking in '%s'" % dirname
234 for fname in glob.glob(dirname + '/*gcc'):
235 if verbose: print " - found '%s'" % fname
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100236 fnames.append(fname)
237 return fnames
Simon Glass7e803e12014-12-01 17:34:06 -0700238
Simon Glassf5902732016-03-12 18:50:32 -0700239 def ScanPathEnv(self, fname):
240 """Scan the PATH environment variable for a given filename.
241
242 Args:
243 fname: Filename to scan for
244 Returns:
245 List of matching pathanames, or [] if none
246 """
247 pathname_list = []
248 for path in os.environ["PATH"].split(os.pathsep):
249 path = path.strip('"')
250 pathname = os.path.join(path, fname)
251 if os.path.exists(pathname):
252 pathname_list.append(pathname)
253 return pathname_list
Simon Glass7e803e12014-12-01 17:34:06 -0700254
Simon Glassc05694f2013-04-03 11:07:16 +0000255 def Scan(self, verbose):
256 """Scan for available toolchains and select the best for each arch.
257
258 We look for all the toolchains we can file, figure out the
259 architecture for each, and whether it works. Then we select the
260 highest priority toolchain for each arch.
261
262 Args:
263 verbose: True to print out progress information
264 """
265 if verbose: print 'Scanning for tool chains'
Simon Glassf5902732016-03-12 18:50:32 -0700266 for name, value in self.prefixes:
267 if verbose: print " - scanning prefix '%s'" % value
268 if os.path.exists(value):
269 self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
270 continue
271 fname = value + 'gcc'
272 if os.path.exists(fname):
273 self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
274 continue
275 fname_list = self.ScanPathEnv(fname)
276 for f in fname_list:
277 self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
278 if not fname_list:
279 raise ValueError, ("No tool chain found for prefix '%s'" %
280 value)
Simon Glassc05694f2013-04-03 11:07:16 +0000281 for path in self.paths:
282 if verbose: print " - scanning path '%s'" % path
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100283 fnames = self.ScanPath(path, verbose)
284 for fname in fnames:
Simon Glass7e803e12014-12-01 17:34:06 -0700285 self.Add(fname, True, verbose)
Simon Glassc05694f2013-04-03 11:07:16 +0000286
287 def List(self):
288 """List out the selected toolchains for each architecture"""
289 print 'List of available toolchains (%d):' % len(self.toolchains)
290 if len(self.toolchains):
291 for key, value in sorted(self.toolchains.iteritems()):
292 print '%-10s: %s' % (key, value.gcc)
293 else:
294 print 'None'
295
296 def Select(self, arch):
297 """Returns the toolchain for a given architecture
298
299 Args:
300 args: Name of architecture (e.g. 'arm', 'ppc_8xx')
301
302 returns:
303 toolchain object, or None if none found
304 """
Simon Glassc1528c12014-12-01 17:34:05 -0700305 for tag, value in bsettings.GetItems('toolchain-alias'):
306 if arch == tag:
307 for alias in value.split():
308 if alias in self.toolchains:
309 return self.toolchains[alias]
Simon Glassc05694f2013-04-03 11:07:16 +0000310
311 if not arch in self.toolchains:
312 raise ValueError, ("No tool chain found for arch '%s'" % arch)
313 return self.toolchains[arch]
Simon Glasscc246fb2013-09-23 17:35:17 -0600314
315 def ResolveReferences(self, var_dict, args):
316 """Resolve variable references in a string
317
318 This converts ${blah} within the string to the value of blah.
319 This function works recursively.
320
321 Args:
322 var_dict: Dictionary containing variables and their values
323 args: String containing make arguments
324 Returns:
325 Resolved string
326
327 >>> bsettings.Setup()
328 >>> tcs = Toolchains()
329 >>> tcs.Add('fred', False)
330 >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
331 'second' : '2nd'}
332 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
333 'this=OBLIQUE_set'
334 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
335 'this=OBLIQUE_setfi2ndrstnd'
336 """
Simon Glass53e189d2014-08-28 09:43:40 -0600337 re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
Simon Glasscc246fb2013-09-23 17:35:17 -0600338
339 while True:
340 m = re_var.search(args)
341 if not m:
342 break
343 lookup = m.group(0)[2:-1]
344 value = var_dict.get(lookup, '')
345 args = args[:m.start(0)] + value + args[m.end(0):]
346 return args
347
348 def GetMakeArguments(self, board):
349 """Returns 'make' arguments for a given board
350
351 The flags are in a section called 'make-flags'. Flags are named
352 after the target they represent, for example snapper9260=TESTING=1
353 will pass TESTING=1 to make when building the snapper9260 board.
354
355 References to other boards can be added in the string also. For
356 example:
357
358 [make-flags]
359 at91-boards=ENABLE_AT91_TEST=1
360 snapper9260=${at91-boards} BUILD_TAG=442
361 snapper9g45=${at91-boards} BUILD_TAG=443
362
363 This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
364 and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
365
366 A special 'target' variable is set to the board target.
367
368 Args:
369 board: Board object for the board to check.
370 Returns:
371 'make' flags for that board, or '' if none
372 """
373 self._make_flags['target'] = board.target
374 arg_str = self.ResolveReferences(self._make_flags,
375 self._make_flags.get(board.target, ''))
376 args = arg_str.split(' ')
377 i = 0
378 while i < len(args):
379 if not args[i]:
380 del args[i]
381 else:
382 i += 1
383 return args
Simon Glass7e803e12014-12-01 17:34:06 -0700384
385 def LocateArchUrl(self, fetch_arch):
386 """Find a toolchain available online
387
388 Look in standard places for available toolchains. At present the
389 only standard place is at kernel.org.
390
391 Args:
392 arch: Architecture to look for, or 'list' for all
393 Returns:
394 If fetch_arch is 'list', a tuple:
395 Machine architecture (e.g. x86_64)
396 List of toolchains
397 else
398 URL containing this toolchain, if avaialble, else None
399 """
400 arch = command.OutputOneLine('uname', '-m')
401 base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
Michal Simeke0e31f32015-04-20 11:46:24 +0200402 versions = ['4.9.0', '4.6.3', '4.6.2', '4.5.1', '4.2.4']
Simon Glass7e803e12014-12-01 17:34:06 -0700403 links = []
404 for version in versions:
405 url = '%s/%s/%s/' % (base, arch, version)
406 print 'Checking: %s' % url
407 response = urllib2.urlopen(url)
408 html = response.read()
409 parser = MyHTMLParser(fetch_arch)
410 parser.feed(html)
411 if fetch_arch == 'list':
412 links += parser.links
413 elif parser.arch_link:
414 return url + parser.arch_link
415 if fetch_arch == 'list':
416 return arch, links
417 return None
418
419 def Download(self, url):
420 """Download a file to a temporary directory
421
422 Args:
423 url: URL to download
424 Returns:
425 Tuple:
426 Temporary directory name
427 Full path to the downloaded archive file in that directory,
428 or None if there was an error while downloading
429 """
Simon Glassd6ece322016-03-06 19:45:35 -0700430 print 'Downloading: %s' % url
Simon Glass7e803e12014-12-01 17:34:06 -0700431 leaf = url.split('/')[-1]
432 tmpdir = tempfile.mkdtemp('.buildman')
433 response = urllib2.urlopen(url)
434 fname = os.path.join(tmpdir, leaf)
435 fd = open(fname, 'wb')
436 meta = response.info()
Simon Glassd6ece322016-03-06 19:45:35 -0700437 size = int(meta.getheaders('Content-Length')[0])
Simon Glass7e803e12014-12-01 17:34:06 -0700438 done = 0
439 block_size = 1 << 16
440 status = ''
441
442 # Read the file in chunks and show progress as we go
443 while True:
444 buffer = response.read(block_size)
445 if not buffer:
446 print chr(8) * (len(status) + 1), '\r',
447 break
448
449 done += len(buffer)
450 fd.write(buffer)
Simon Glassd6ece322016-03-06 19:45:35 -0700451 status = r'%10d MiB [%3d%%]' % (done / 1024 / 1024,
Simon Glass7e803e12014-12-01 17:34:06 -0700452 done * 100 / size)
453 status = status + chr(8) * (len(status) + 1)
454 print status,
455 sys.stdout.flush()
456 fd.close()
457 if done != size:
458 print 'Error, failed to download'
459 os.remove(fname)
460 fname = None
461 return tmpdir, fname
462
463 def Unpack(self, fname, dest):
464 """Unpack a tar file
465
466 Args:
467 fname: Filename to unpack
468 dest: Destination directory
469 Returns:
470 Directory name of the first entry in the archive, without the
471 trailing /
472 """
473 stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
474 return stdout.splitlines()[0][:-1]
475
476 def TestSettingsHasPath(self, path):
477 """Check if builmand will find this toolchain
478
479 Returns:
480 True if the path is in settings, False if not
481 """
482 paths = self.GetPathList()
483 return path in paths
484
485 def ListArchs(self):
486 """List architectures with available toolchains to download"""
487 host_arch, archives = self.LocateArchUrl('list')
488 re_arch = re.compile('[-a-z0-9.]*_([^-]*)-.*')
489 arch_set = set()
490 for archive in archives:
491 # Remove the host architecture from the start
492 arch = re_arch.match(archive[len(host_arch):])
493 if arch:
494 arch_set.add(arch.group(1))
495 return sorted(arch_set)
496
497 def FetchAndInstall(self, arch):
498 """Fetch and install a new toolchain
499
500 arch:
501 Architecture to fetch, or 'list' to list
502 """
503 # Fist get the URL for this architecture
504 url = self.LocateArchUrl(arch)
505 if not url:
506 print ("Cannot find toolchain for arch '%s' - use 'list' to list" %
507 arch)
508 return 2
509 home = os.environ['HOME']
510 dest = os.path.join(home, '.buildman-toolchains')
511 if not os.path.exists(dest):
512 os.mkdir(dest)
513
514 # Download the tar file for this toolchain and unpack it
515 tmpdir, tarfile = self.Download(url)
516 if not tarfile:
517 return 1
518 print 'Unpacking to: %s' % dest,
519 sys.stdout.flush()
520 path = self.Unpack(tarfile, dest)
521 os.remove(tarfile)
522 os.rmdir(tmpdir)
523 print
524
525 # Check that the toolchain works
526 print 'Testing'
527 dirpath = os.path.join(dest, path)
Simon Glassf6757502015-03-02 17:05:15 -0700528 compiler_fname_list = self.ScanPath(dirpath, True)
529 if not compiler_fname_list:
Simon Glass7e803e12014-12-01 17:34:06 -0700530 print 'Could not locate C compiler - fetch failed.'
531 return 1
Simon Glassf6757502015-03-02 17:05:15 -0700532 if len(compiler_fname_list) != 1:
533 print ('Internal error, ambiguous toolchains: %s' %
534 (', '.join(compiler_fname)))
535 return 1
536 toolchain = Toolchain(compiler_fname_list[0], True, True)
Simon Glass7e803e12014-12-01 17:34:06 -0700537
538 # Make sure that it will be found by buildman
539 if not self.TestSettingsHasPath(dirpath):
540 print ("Adding 'download' to config file '%s'" %
541 bsettings.config_fname)
542 tools_dir = os.path.dirname(dirpath)
543 bsettings.SetItem('toolchain', 'download', '%s/*' % tools_dir)
544 return 0