blob: 1874d730b2fd133ae256953a5ba7fb4d6bfaf225 [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 Glass7b971292016-03-06 19:45:37 -070017PRIORITY_CALC = 0
18
Simon Glass7e803e12014-12-01 17:34:06 -070019# Simple class to collect links from a page
20class MyHTMLParser(HTMLParser):
21 def __init__(self, arch):
22 """Create a new parser
23
24 After the parser runs, self.links will be set to a list of the links
25 to .xz archives found in the page, and self.arch_link will be set to
26 the one for the given architecture (or None if not found).
27
28 Args:
29 arch: Architecture to search for
30 """
31 HTMLParser.__init__(self)
32 self.arch_link = None
33 self.links = []
34 self._match = '_%s-' % arch
35
36 def handle_starttag(self, tag, attrs):
37 if tag == 'a':
38 for tag, value in attrs:
39 if tag == 'href':
40 if value and value.endswith('.xz'):
41 self.links.append(value)
42 if self._match in value:
43 self.arch_link = value
44
45
Simon Glassc05694f2013-04-03 11:07:16 +000046class Toolchain:
47 """A single toolchain
48
49 Public members:
50 gcc: Full path to C compiler
51 path: Directory path containing C compiler
52 cross: Cross compile string, e.g. 'arm-linux-'
53 arch: Architecture of toolchain as determined from the first
54 component of the filename. E.g. arm-linux-gcc becomes arm
Simon Glass7b971292016-03-06 19:45:37 -070055 priority: Toolchain priority (0=highest, 20=lowest)
Simon Glassc05694f2013-04-03 11:07:16 +000056 """
Simon Glass7b971292016-03-06 19:45:37 -070057 def __init__(self, fname, test, verbose=False, priority=PRIORITY_CALC):
Simon Glassc05694f2013-04-03 11:07:16 +000058 """Create a new toolchain object.
59
60 Args:
61 fname: Filename of the gcc component
62 test: True to run the toolchain to test it
Simon Glassd6ece322016-03-06 19:45:35 -070063 verbose: True to print out the information
Simon Glass7b971292016-03-06 19:45:37 -070064 priority: Priority to use for this toolchain, or PRIORITY_CALC to
65 calculate it
Simon Glassc05694f2013-04-03 11:07:16 +000066 """
67 self.gcc = fname
68 self.path = os.path.dirname(fname)
Simon Glass28ed0062014-12-01 17:33:58 -070069
70 # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
71 # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
72 basename = os.path.basename(fname)
73 pos = basename.rfind('-')
74 self.cross = basename[:pos + 1] if pos != -1 else ''
75
76 # The architecture is the first part of the name
Simon Glassc05694f2013-04-03 11:07:16 +000077 pos = self.cross.find('-')
78 self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
79
Simon Glassd48a46c2014-12-01 17:34:00 -070080 env = self.MakeEnvironment(False)
Simon Glassc05694f2013-04-03 11:07:16 +000081
82 # As a basic sanity check, run the C compiler with --version
83 cmd = [fname, '--version']
Simon Glass7b971292016-03-06 19:45:37 -070084 if priority == PRIORITY_CALC:
85 self.priority = self.GetPriority(fname)
86 else:
87 self.priority = priority
Simon Glassc05694f2013-04-03 11:07:16 +000088 if test:
Stephen Warren288d7672013-10-09 14:28:09 -060089 result = command.RunPipe([cmd], capture=True, env=env,
90 raise_on_error=False)
Simon Glassc05694f2013-04-03 11:07:16 +000091 self.ok = result.return_code == 0
92 if verbose:
93 print 'Tool chain test: ',
94 if self.ok:
Simon Glass7b971292016-03-06 19:45:37 -070095 print 'OK, priority %d' % self.priority
Simon Glassc05694f2013-04-03 11:07:16 +000096 else:
97 print 'BAD'
98 print 'Command: ', cmd
99 print result.stdout
100 print result.stderr
101 else:
102 self.ok = True
Simon Glassc05694f2013-04-03 11:07:16 +0000103
104 def GetPriority(self, fname):
105 """Return the priority of the toolchain.
106
107 Toolchains are ranked according to their suitability by their
108 filename prefix.
109
110 Args:
111 fname: Filename of toolchain
112 Returns:
Simon Glass7b971292016-03-06 19:45:37 -0700113 Priority of toolchain, PRIORITY_CALC=highest, 20=lowest.
Simon Glassc05694f2013-04-03 11:07:16 +0000114 """
Masahiro Yamadafa25e1f2014-07-07 09:47:45 +0900115 priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
Simon Glassc05694f2013-04-03 11:07:16 +0000116 '-none-linux-gnueabi', '-uclinux', '-none-eabi',
117 '-gentoo-linux-gnu', '-linux-gnueabi', '-le-linux', '-uclinux']
118 for prio in range(len(priority_list)):
119 if priority_list[prio] in fname:
Simon Glass7b971292016-03-06 19:45:37 -0700120 return PRIORITY_CALC + prio
121 return PRIORITY_CALC + prio
Simon Glassc05694f2013-04-03 11:07:16 +0000122
Simon Glassd48a46c2014-12-01 17:34:00 -0700123 def MakeEnvironment(self, full_path):
Simon Glassc05694f2013-04-03 11:07:16 +0000124 """Returns an environment for using the toolchain.
125
Simon Glassd48a46c2014-12-01 17:34:00 -0700126 Thie takes the current environment and adds CROSS_COMPILE so that
127 the tool chain will operate correctly.
128
129 Args:
130 full_path: Return the full path in CROSS_COMPILE and don't set
131 PATH
Simon Glassc05694f2013-04-03 11:07:16 +0000132 """
133 env = dict(os.environ)
Simon Glassd48a46c2014-12-01 17:34:00 -0700134 if full_path:
135 env['CROSS_COMPILE'] = os.path.join(self.path, self.cross)
136 else:
137 env['CROSS_COMPILE'] = self.cross
138 env['PATH'] = self.path + ':' + env['PATH']
139
Simon Glassc05694f2013-04-03 11:07:16 +0000140 return env
141
142
143class Toolchains:
144 """Manage a list of toolchains for building U-Boot
145
146 We select one toolchain for each architecture type
147
148 Public members:
149 toolchains: Dict of Toolchain objects, keyed by architecture name
150 paths: List of paths to check for toolchains (may contain wildcards)
151 """
152
153 def __init__(self):
154 self.toolchains = {}
155 self.paths = []
Simon Glassed098bb2014-09-05 19:00:13 -0600156 self._make_flags = dict(bsettings.GetItems('make-flags'))
157
Simon Glass7e803e12014-12-01 17:34:06 -0700158 def GetPathList(self):
159 """Get a list of available toolchain paths
160
161 Returns:
162 List of strings, each a path to a toolchain mentioned in the
163 [toolchain] section of the settings file.
164 """
Simon Glasscc246fb2013-09-23 17:35:17 -0600165 toolchains = bsettings.GetItems('toolchain')
166 if not toolchains:
Simon Glassd6ece322016-03-06 19:45:35 -0700167 print ('Warning: No tool chains - please add a [toolchain] section'
168 ' to your buildman config file %s. See README for details' %
Masahiro Yamada73c12672014-07-07 09:46:36 +0900169 bsettings.config_fname)
Simon Glasscc246fb2013-09-23 17:35:17 -0600170
Simon Glass7e803e12014-12-01 17:34:06 -0700171 paths = []
Simon Glasscc246fb2013-09-23 17:35:17 -0600172 for name, value in toolchains:
Simon Glassc05694f2013-04-03 11:07:16 +0000173 if '*' in value:
Simon Glass7e803e12014-12-01 17:34:06 -0700174 paths += glob.glob(value)
Simon Glassc05694f2013-04-03 11:07:16 +0000175 else:
Simon Glass7e803e12014-12-01 17:34:06 -0700176 paths.append(value)
177 return paths
178
179 def GetSettings(self):
180 self.paths += self.GetPathList()
Simon Glassc05694f2013-04-03 11:07:16 +0000181
Simon Glass7b971292016-03-06 19:45:37 -0700182 def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC):
Simon Glassc05694f2013-04-03 11:07:16 +0000183 """Add a toolchain to our list
184
185 We select the given toolchain as our preferred one for its
186 architecture if it is a higher priority than the others.
187
188 Args:
189 fname: Filename of toolchain's gcc driver
190 test: True to run the toolchain to test it
Simon Glass7b971292016-03-06 19:45:37 -0700191 priority: Priority to use for this toolchain
Simon Glassc05694f2013-04-03 11:07:16 +0000192 """
Simon Glass7b971292016-03-06 19:45:37 -0700193 toolchain = Toolchain(fname, test, verbose, priority)
Simon Glassc05694f2013-04-03 11:07:16 +0000194 add_it = toolchain.ok
195 if toolchain.arch in self.toolchains:
196 add_it = (toolchain.priority <
197 self.toolchains[toolchain.arch].priority)
198 if add_it:
199 self.toolchains[toolchain.arch] = toolchain
Simon Glass7b971292016-03-06 19:45:37 -0700200 elif verbose:
201 print ("Toolchain '%s' at priority %d will be ignored because "
202 "another toolchain for arch '%s' has priority %d" %
203 (toolchain.gcc, toolchain.priority, toolchain.arch,
204 self.toolchains[toolchain.arch].priority))
Simon Glassc05694f2013-04-03 11:07:16 +0000205
Simon Glass7e803e12014-12-01 17:34:06 -0700206 def ScanPath(self, path, verbose):
207 """Scan a path for a valid toolchain
208
209 Args:
210 path: Path to scan
211 verbose: True to print out progress information
212 Returns:
213 Filename of C compiler if found, else None
214 """
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100215 fnames = []
Simon Glass7e803e12014-12-01 17:34:06 -0700216 for subdir in ['.', 'bin', 'usr/bin']:
217 dirname = os.path.join(path, subdir)
218 if verbose: print " - looking in '%s'" % dirname
219 for fname in glob.glob(dirname + '/*gcc'):
220 if verbose: print " - found '%s'" % fname
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100221 fnames.append(fname)
222 return fnames
Simon Glass7e803e12014-12-01 17:34:06 -0700223
224
Simon Glassc05694f2013-04-03 11:07:16 +0000225 def Scan(self, verbose):
226 """Scan for available toolchains and select the best for each arch.
227
228 We look for all the toolchains we can file, figure out the
229 architecture for each, and whether it works. Then we select the
230 highest priority toolchain for each arch.
231
232 Args:
233 verbose: True to print out progress information
234 """
235 if verbose: print 'Scanning for tool chains'
236 for path in self.paths:
237 if verbose: print " - scanning path '%s'" % path
Albert ARIBAUDd4f22cf2015-02-01 00:12:44 +0100238 fnames = self.ScanPath(path, verbose)
239 for fname in fnames:
Simon Glass7e803e12014-12-01 17:34:06 -0700240 self.Add(fname, True, verbose)
Simon Glassc05694f2013-04-03 11:07:16 +0000241
242 def List(self):
243 """List out the selected toolchains for each architecture"""
244 print 'List of available toolchains (%d):' % len(self.toolchains)
245 if len(self.toolchains):
246 for key, value in sorted(self.toolchains.iteritems()):
247 print '%-10s: %s' % (key, value.gcc)
248 else:
249 print 'None'
250
251 def Select(self, arch):
252 """Returns the toolchain for a given architecture
253
254 Args:
255 args: Name of architecture (e.g. 'arm', 'ppc_8xx')
256
257 returns:
258 toolchain object, or None if none found
259 """
Simon Glassc1528c12014-12-01 17:34:05 -0700260 for tag, value in bsettings.GetItems('toolchain-alias'):
261 if arch == tag:
262 for alias in value.split():
263 if alias in self.toolchains:
264 return self.toolchains[alias]
Simon Glassc05694f2013-04-03 11:07:16 +0000265
266 if not arch in self.toolchains:
267 raise ValueError, ("No tool chain found for arch '%s'" % arch)
268 return self.toolchains[arch]
Simon Glasscc246fb2013-09-23 17:35:17 -0600269
270 def ResolveReferences(self, var_dict, args):
271 """Resolve variable references in a string
272
273 This converts ${blah} within the string to the value of blah.
274 This function works recursively.
275
276 Args:
277 var_dict: Dictionary containing variables and their values
278 args: String containing make arguments
279 Returns:
280 Resolved string
281
282 >>> bsettings.Setup()
283 >>> tcs = Toolchains()
284 >>> tcs.Add('fred', False)
285 >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
286 'second' : '2nd'}
287 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
288 'this=OBLIQUE_set'
289 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
290 'this=OBLIQUE_setfi2ndrstnd'
291 """
Simon Glass53e189d2014-08-28 09:43:40 -0600292 re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
Simon Glasscc246fb2013-09-23 17:35:17 -0600293
294 while True:
295 m = re_var.search(args)
296 if not m:
297 break
298 lookup = m.group(0)[2:-1]
299 value = var_dict.get(lookup, '')
300 args = args[:m.start(0)] + value + args[m.end(0):]
301 return args
302
303 def GetMakeArguments(self, board):
304 """Returns 'make' arguments for a given board
305
306 The flags are in a section called 'make-flags'. Flags are named
307 after the target they represent, for example snapper9260=TESTING=1
308 will pass TESTING=1 to make when building the snapper9260 board.
309
310 References to other boards can be added in the string also. For
311 example:
312
313 [make-flags]
314 at91-boards=ENABLE_AT91_TEST=1
315 snapper9260=${at91-boards} BUILD_TAG=442
316 snapper9g45=${at91-boards} BUILD_TAG=443
317
318 This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
319 and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
320
321 A special 'target' variable is set to the board target.
322
323 Args:
324 board: Board object for the board to check.
325 Returns:
326 'make' flags for that board, or '' if none
327 """
328 self._make_flags['target'] = board.target
329 arg_str = self.ResolveReferences(self._make_flags,
330 self._make_flags.get(board.target, ''))
331 args = arg_str.split(' ')
332 i = 0
333 while i < len(args):
334 if not args[i]:
335 del args[i]
336 else:
337 i += 1
338 return args
Simon Glass7e803e12014-12-01 17:34:06 -0700339
340 def LocateArchUrl(self, fetch_arch):
341 """Find a toolchain available online
342
343 Look in standard places for available toolchains. At present the
344 only standard place is at kernel.org.
345
346 Args:
347 arch: Architecture to look for, or 'list' for all
348 Returns:
349 If fetch_arch is 'list', a tuple:
350 Machine architecture (e.g. x86_64)
351 List of toolchains
352 else
353 URL containing this toolchain, if avaialble, else None
354 """
355 arch = command.OutputOneLine('uname', '-m')
356 base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
Michal Simeke0e31f32015-04-20 11:46:24 +0200357 versions = ['4.9.0', '4.6.3', '4.6.2', '4.5.1', '4.2.4']
Simon Glass7e803e12014-12-01 17:34:06 -0700358 links = []
359 for version in versions:
360 url = '%s/%s/%s/' % (base, arch, version)
361 print 'Checking: %s' % url
362 response = urllib2.urlopen(url)
363 html = response.read()
364 parser = MyHTMLParser(fetch_arch)
365 parser.feed(html)
366 if fetch_arch == 'list':
367 links += parser.links
368 elif parser.arch_link:
369 return url + parser.arch_link
370 if fetch_arch == 'list':
371 return arch, links
372 return None
373
374 def Download(self, url):
375 """Download a file to a temporary directory
376
377 Args:
378 url: URL to download
379 Returns:
380 Tuple:
381 Temporary directory name
382 Full path to the downloaded archive file in that directory,
383 or None if there was an error while downloading
384 """
Simon Glassd6ece322016-03-06 19:45:35 -0700385 print 'Downloading: %s' % url
Simon Glass7e803e12014-12-01 17:34:06 -0700386 leaf = url.split('/')[-1]
387 tmpdir = tempfile.mkdtemp('.buildman')
388 response = urllib2.urlopen(url)
389 fname = os.path.join(tmpdir, leaf)
390 fd = open(fname, 'wb')
391 meta = response.info()
Simon Glassd6ece322016-03-06 19:45:35 -0700392 size = int(meta.getheaders('Content-Length')[0])
Simon Glass7e803e12014-12-01 17:34:06 -0700393 done = 0
394 block_size = 1 << 16
395 status = ''
396
397 # Read the file in chunks and show progress as we go
398 while True:
399 buffer = response.read(block_size)
400 if not buffer:
401 print chr(8) * (len(status) + 1), '\r',
402 break
403
404 done += len(buffer)
405 fd.write(buffer)
Simon Glassd6ece322016-03-06 19:45:35 -0700406 status = r'%10d MiB [%3d%%]' % (done / 1024 / 1024,
Simon Glass7e803e12014-12-01 17:34:06 -0700407 done * 100 / size)
408 status = status + chr(8) * (len(status) + 1)
409 print status,
410 sys.stdout.flush()
411 fd.close()
412 if done != size:
413 print 'Error, failed to download'
414 os.remove(fname)
415 fname = None
416 return tmpdir, fname
417
418 def Unpack(self, fname, dest):
419 """Unpack a tar file
420
421 Args:
422 fname: Filename to unpack
423 dest: Destination directory
424 Returns:
425 Directory name of the first entry in the archive, without the
426 trailing /
427 """
428 stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
429 return stdout.splitlines()[0][:-1]
430
431 def TestSettingsHasPath(self, path):
432 """Check if builmand will find this toolchain
433
434 Returns:
435 True if the path is in settings, False if not
436 """
437 paths = self.GetPathList()
438 return path in paths
439
440 def ListArchs(self):
441 """List architectures with available toolchains to download"""
442 host_arch, archives = self.LocateArchUrl('list')
443 re_arch = re.compile('[-a-z0-9.]*_([^-]*)-.*')
444 arch_set = set()
445 for archive in archives:
446 # Remove the host architecture from the start
447 arch = re_arch.match(archive[len(host_arch):])
448 if arch:
449 arch_set.add(arch.group(1))
450 return sorted(arch_set)
451
452 def FetchAndInstall(self, arch):
453 """Fetch and install a new toolchain
454
455 arch:
456 Architecture to fetch, or 'list' to list
457 """
458 # Fist get the URL for this architecture
459 url = self.LocateArchUrl(arch)
460 if not url:
461 print ("Cannot find toolchain for arch '%s' - use 'list' to list" %
462 arch)
463 return 2
464 home = os.environ['HOME']
465 dest = os.path.join(home, '.buildman-toolchains')
466 if not os.path.exists(dest):
467 os.mkdir(dest)
468
469 # Download the tar file for this toolchain and unpack it
470 tmpdir, tarfile = self.Download(url)
471 if not tarfile:
472 return 1
473 print 'Unpacking to: %s' % dest,
474 sys.stdout.flush()
475 path = self.Unpack(tarfile, dest)
476 os.remove(tarfile)
477 os.rmdir(tmpdir)
478 print
479
480 # Check that the toolchain works
481 print 'Testing'
482 dirpath = os.path.join(dest, path)
Simon Glassf6757502015-03-02 17:05:15 -0700483 compiler_fname_list = self.ScanPath(dirpath, True)
484 if not compiler_fname_list:
Simon Glass7e803e12014-12-01 17:34:06 -0700485 print 'Could not locate C compiler - fetch failed.'
486 return 1
Simon Glassf6757502015-03-02 17:05:15 -0700487 if len(compiler_fname_list) != 1:
488 print ('Internal error, ambiguous toolchains: %s' %
489 (', '.join(compiler_fname)))
490 return 1
491 toolchain = Toolchain(compiler_fname_list[0], True, True)
Simon Glass7e803e12014-12-01 17:34:06 -0700492
493 # Make sure that it will be found by buildman
494 if not self.TestSettingsHasPath(dirpath):
495 print ("Adding 'download' to config file '%s'" %
496 bsettings.config_fname)
497 tools_dir = os.path.dirname(dirpath)
498 bsettings.SetItem('toolchain', 'download', '%s/*' % tools_dir)
499 return 0