blob: 5e77fed5833435ca02e17a64b7075f8a95281b0e [file] [log] [blame]
Simon Glassfc3fe1c2013-04-03 11:07:16 +00001# Copyright (c) 2012 The Chromium OS Authors.
2#
Wolfgang Denk1a459662013-07-08 09:37:19 +02003# SPDX-License-Identifier: GPL-2.0+
Simon Glassfc3fe1c2013-04-03 11:07:16 +00004#
5
Simon Glass4281ad82013-09-23 17:35:17 -06006import re
Simon Glassfc3fe1c2013-04-03 11:07:16 +00007import glob
Simon Glass827e37b2014-12-01 17:34:06 -07008from HTMLParser import HTMLParser
Simon Glassfc3fe1c2013-04-03 11:07:16 +00009import os
Simon Glass827e37b2014-12-01 17:34:06 -070010import sys
11import tempfile
12import urllib2
Simon Glassfc3fe1c2013-04-03 11:07:16 +000013
14import bsettings
15import command
16
Simon Glass827e37b2014-12-01 17:34:06 -070017# Simple class to collect links from a page
18class MyHTMLParser(HTMLParser):
19 def __init__(self, arch):
20 """Create a new parser
21
22 After the parser runs, self.links will be set to a list of the links
23 to .xz archives found in the page, and self.arch_link will be set to
24 the one for the given architecture (or None if not found).
25
26 Args:
27 arch: Architecture to search for
28 """
29 HTMLParser.__init__(self)
30 self.arch_link = None
31 self.links = []
32 self._match = '_%s-' % arch
33
34 def handle_starttag(self, tag, attrs):
35 if tag == 'a':
36 for tag, value in attrs:
37 if tag == 'href':
38 if value and value.endswith('.xz'):
39 self.links.append(value)
40 if self._match in value:
41 self.arch_link = value
42
43
Simon Glassfc3fe1c2013-04-03 11:07:16 +000044class Toolchain:
45 """A single toolchain
46
47 Public members:
48 gcc: Full path to C compiler
49 path: Directory path containing C compiler
50 cross: Cross compile string, e.g. 'arm-linux-'
51 arch: Architecture of toolchain as determined from the first
52 component of the filename. E.g. arm-linux-gcc becomes arm
53 """
Simon Glassfc3fe1c2013-04-03 11:07:16 +000054 def __init__(self, fname, test, verbose=False):
55 """Create a new toolchain object.
56
57 Args:
58 fname: Filename of the gcc component
59 test: True to run the toolchain to test it
Simon Glassad24eba2016-03-06 19:45:35 -070060 verbose: True to print out the information
Simon Glassfc3fe1c2013-04-03 11:07:16 +000061 """
62 self.gcc = fname
63 self.path = os.path.dirname(fname)
Simon Glassb5324122014-12-01 17:33:58 -070064
65 # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
66 # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
67 basename = os.path.basename(fname)
68 pos = basename.rfind('-')
69 self.cross = basename[:pos + 1] if pos != -1 else ''
70
71 # The architecture is the first part of the name
Simon Glassfc3fe1c2013-04-03 11:07:16 +000072 pos = self.cross.find('-')
73 self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
74
Simon Glassbb1501f2014-12-01 17:34:00 -070075 env = self.MakeEnvironment(False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +000076
77 # As a basic sanity check, run the C compiler with --version
78 cmd = [fname, '--version']
79 if test:
Stephen Warren8bb2bdd2013-10-09 14:28:09 -060080 result = command.RunPipe([cmd], capture=True, env=env,
81 raise_on_error=False)
Simon Glassfc3fe1c2013-04-03 11:07:16 +000082 self.ok = result.return_code == 0
83 if verbose:
84 print 'Tool chain test: ',
85 if self.ok:
86 print 'OK'
87 else:
88 print 'BAD'
89 print 'Command: ', cmd
90 print result.stdout
91 print result.stderr
92 else:
93 self.ok = True
94 self.priority = self.GetPriority(fname)
95
96 def GetPriority(self, fname):
97 """Return the priority of the toolchain.
98
99 Toolchains are ranked according to their suitability by their
100 filename prefix.
101
102 Args:
103 fname: Filename of toolchain
104 Returns:
105 Priority of toolchain, 0=highest, 20=lowest.
106 """
Masahiro Yamada87082672014-07-07 09:47:45 +0900107 priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000108 '-none-linux-gnueabi', '-uclinux', '-none-eabi',
109 '-gentoo-linux-gnu', '-linux-gnueabi', '-le-linux', '-uclinux']
110 for prio in range(len(priority_list)):
111 if priority_list[prio] in fname:
112 return prio
113 return prio
114
Simon Glassbb1501f2014-12-01 17:34:00 -0700115 def MakeEnvironment(self, full_path):
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000116 """Returns an environment for using the toolchain.
117
Simon Glassbb1501f2014-12-01 17:34:00 -0700118 Thie takes the current environment and adds CROSS_COMPILE so that
119 the tool chain will operate correctly.
120
121 Args:
122 full_path: Return the full path in CROSS_COMPILE and don't set
123 PATH
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000124 """
125 env = dict(os.environ)
Simon Glassbb1501f2014-12-01 17:34:00 -0700126 if full_path:
127 env['CROSS_COMPILE'] = os.path.join(self.path, self.cross)
128 else:
129 env['CROSS_COMPILE'] = self.cross
130 env['PATH'] = self.path + ':' + env['PATH']
131
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000132 return env
133
134
135class Toolchains:
136 """Manage a list of toolchains for building U-Boot
137
138 We select one toolchain for each architecture type
139
140 Public members:
141 toolchains: Dict of Toolchain objects, keyed by architecture name
142 paths: List of paths to check for toolchains (may contain wildcards)
143 """
144
145 def __init__(self):
146 self.toolchains = {}
147 self.paths = []
Simon Glassd4144e42014-09-05 19:00:13 -0600148 self._make_flags = dict(bsettings.GetItems('make-flags'))
149
Simon Glass827e37b2014-12-01 17:34:06 -0700150 def GetPathList(self):
151 """Get a list of available toolchain paths
152
153 Returns:
154 List of strings, each a path to a toolchain mentioned in the
155 [toolchain] section of the settings file.
156 """
Simon Glass4281ad82013-09-23 17:35:17 -0600157 toolchains = bsettings.GetItems('toolchain')
158 if not toolchains:
Simon Glassad24eba2016-03-06 19:45:35 -0700159 print ('Warning: No tool chains - please add a [toolchain] section'
160 ' to your buildman config file %s. See README for details' %
Masahiro Yamada1826a182014-07-07 09:46:36 +0900161 bsettings.config_fname)
Simon Glass4281ad82013-09-23 17:35:17 -0600162
Simon Glass827e37b2014-12-01 17:34:06 -0700163 paths = []
Simon Glass4281ad82013-09-23 17:35:17 -0600164 for name, value in toolchains:
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000165 if '*' in value:
Simon Glass827e37b2014-12-01 17:34:06 -0700166 paths += glob.glob(value)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000167 else:
Simon Glass827e37b2014-12-01 17:34:06 -0700168 paths.append(value)
169 return paths
170
171 def GetSettings(self):
172 self.paths += self.GetPathList()
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000173
174 def Add(self, fname, test=True, verbose=False):
175 """Add a toolchain to our list
176
177 We select the given toolchain as our preferred one for its
178 architecture if it is a higher priority than the others.
179
180 Args:
181 fname: Filename of toolchain's gcc driver
182 test: True to run the toolchain to test it
183 """
184 toolchain = Toolchain(fname, test, verbose)
185 add_it = toolchain.ok
186 if toolchain.arch in self.toolchains:
187 add_it = (toolchain.priority <
188 self.toolchains[toolchain.arch].priority)
189 if add_it:
190 self.toolchains[toolchain.arch] = toolchain
191
Simon Glass827e37b2014-12-01 17:34:06 -0700192 def ScanPath(self, path, verbose):
193 """Scan a path for a valid toolchain
194
195 Args:
196 path: Path to scan
197 verbose: True to print out progress information
198 Returns:
199 Filename of C compiler if found, else None
200 """
Albert ARIBAUDd9088982015-02-01 00:12:44 +0100201 fnames = []
Simon Glass827e37b2014-12-01 17:34:06 -0700202 for subdir in ['.', 'bin', 'usr/bin']:
203 dirname = os.path.join(path, subdir)
204 if verbose: print " - looking in '%s'" % dirname
205 for fname in glob.glob(dirname + '/*gcc'):
206 if verbose: print " - found '%s'" % fname
Albert ARIBAUDd9088982015-02-01 00:12:44 +0100207 fnames.append(fname)
208 return fnames
Simon Glass827e37b2014-12-01 17:34:06 -0700209
210
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000211 def Scan(self, verbose):
212 """Scan for available toolchains and select the best for each arch.
213
214 We look for all the toolchains we can file, figure out the
215 architecture for each, and whether it works. Then we select the
216 highest priority toolchain for each arch.
217
218 Args:
219 verbose: True to print out progress information
220 """
221 if verbose: print 'Scanning for tool chains'
222 for path in self.paths:
223 if verbose: print " - scanning path '%s'" % path
Albert ARIBAUDd9088982015-02-01 00:12:44 +0100224 fnames = self.ScanPath(path, verbose)
225 for fname in fnames:
Simon Glass827e37b2014-12-01 17:34:06 -0700226 self.Add(fname, True, verbose)
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000227
228 def List(self):
229 """List out the selected toolchains for each architecture"""
230 print 'List of available toolchains (%d):' % len(self.toolchains)
231 if len(self.toolchains):
232 for key, value in sorted(self.toolchains.iteritems()):
233 print '%-10s: %s' % (key, value.gcc)
234 else:
235 print 'None'
236
237 def Select(self, arch):
238 """Returns the toolchain for a given architecture
239
240 Args:
241 args: Name of architecture (e.g. 'arm', 'ppc_8xx')
242
243 returns:
244 toolchain object, or None if none found
245 """
Simon Glass9b83bfd2014-12-01 17:34:05 -0700246 for tag, value in bsettings.GetItems('toolchain-alias'):
247 if arch == tag:
248 for alias in value.split():
249 if alias in self.toolchains:
250 return self.toolchains[alias]
Simon Glassfc3fe1c2013-04-03 11:07:16 +0000251
252 if not arch in self.toolchains:
253 raise ValueError, ("No tool chain found for arch '%s'" % arch)
254 return self.toolchains[arch]
Simon Glass4281ad82013-09-23 17:35:17 -0600255
256 def ResolveReferences(self, var_dict, args):
257 """Resolve variable references in a string
258
259 This converts ${blah} within the string to the value of blah.
260 This function works recursively.
261
262 Args:
263 var_dict: Dictionary containing variables and their values
264 args: String containing make arguments
265 Returns:
266 Resolved string
267
268 >>> bsettings.Setup()
269 >>> tcs = Toolchains()
270 >>> tcs.Add('fred', False)
271 >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
272 'second' : '2nd'}
273 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
274 'this=OBLIQUE_set'
275 >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
276 'this=OBLIQUE_setfi2ndrstnd'
277 """
Simon Glassf60c9d42014-08-28 09:43:40 -0600278 re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
Simon Glass4281ad82013-09-23 17:35:17 -0600279
280 while True:
281 m = re_var.search(args)
282 if not m:
283 break
284 lookup = m.group(0)[2:-1]
285 value = var_dict.get(lookup, '')
286 args = args[:m.start(0)] + value + args[m.end(0):]
287 return args
288
289 def GetMakeArguments(self, board):
290 """Returns 'make' arguments for a given board
291
292 The flags are in a section called 'make-flags'. Flags are named
293 after the target they represent, for example snapper9260=TESTING=1
294 will pass TESTING=1 to make when building the snapper9260 board.
295
296 References to other boards can be added in the string also. For
297 example:
298
299 [make-flags]
300 at91-boards=ENABLE_AT91_TEST=1
301 snapper9260=${at91-boards} BUILD_TAG=442
302 snapper9g45=${at91-boards} BUILD_TAG=443
303
304 This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
305 and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
306
307 A special 'target' variable is set to the board target.
308
309 Args:
310 board: Board object for the board to check.
311 Returns:
312 'make' flags for that board, or '' if none
313 """
314 self._make_flags['target'] = board.target
315 arg_str = self.ResolveReferences(self._make_flags,
316 self._make_flags.get(board.target, ''))
317 args = arg_str.split(' ')
318 i = 0
319 while i < len(args):
320 if not args[i]:
321 del args[i]
322 else:
323 i += 1
324 return args
Simon Glass827e37b2014-12-01 17:34:06 -0700325
326 def LocateArchUrl(self, fetch_arch):
327 """Find a toolchain available online
328
329 Look in standard places for available toolchains. At present the
330 only standard place is at kernel.org.
331
332 Args:
333 arch: Architecture to look for, or 'list' for all
334 Returns:
335 If fetch_arch is 'list', a tuple:
336 Machine architecture (e.g. x86_64)
337 List of toolchains
338 else
339 URL containing this toolchain, if avaialble, else None
340 """
341 arch = command.OutputOneLine('uname', '-m')
342 base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
Michal Simek12462312015-04-20 11:46:24 +0200343 versions = ['4.9.0', '4.6.3', '4.6.2', '4.5.1', '4.2.4']
Simon Glass827e37b2014-12-01 17:34:06 -0700344 links = []
345 for version in versions:
346 url = '%s/%s/%s/' % (base, arch, version)
347 print 'Checking: %s' % url
348 response = urllib2.urlopen(url)
349 html = response.read()
350 parser = MyHTMLParser(fetch_arch)
351 parser.feed(html)
352 if fetch_arch == 'list':
353 links += parser.links
354 elif parser.arch_link:
355 return url + parser.arch_link
356 if fetch_arch == 'list':
357 return arch, links
358 return None
359
360 def Download(self, url):
361 """Download a file to a temporary directory
362
363 Args:
364 url: URL to download
365 Returns:
366 Tuple:
367 Temporary directory name
368 Full path to the downloaded archive file in that directory,
369 or None if there was an error while downloading
370 """
Simon Glassad24eba2016-03-06 19:45:35 -0700371 print 'Downloading: %s' % url
Simon Glass827e37b2014-12-01 17:34:06 -0700372 leaf = url.split('/')[-1]
373 tmpdir = tempfile.mkdtemp('.buildman')
374 response = urllib2.urlopen(url)
375 fname = os.path.join(tmpdir, leaf)
376 fd = open(fname, 'wb')
377 meta = response.info()
Simon Glassad24eba2016-03-06 19:45:35 -0700378 size = int(meta.getheaders('Content-Length')[0])
Simon Glass827e37b2014-12-01 17:34:06 -0700379 done = 0
380 block_size = 1 << 16
381 status = ''
382
383 # Read the file in chunks and show progress as we go
384 while True:
385 buffer = response.read(block_size)
386 if not buffer:
387 print chr(8) * (len(status) + 1), '\r',
388 break
389
390 done += len(buffer)
391 fd.write(buffer)
Simon Glassad24eba2016-03-06 19:45:35 -0700392 status = r'%10d MiB [%3d%%]' % (done / 1024 / 1024,
Simon Glass827e37b2014-12-01 17:34:06 -0700393 done * 100 / size)
394 status = status + chr(8) * (len(status) + 1)
395 print status,
396 sys.stdout.flush()
397 fd.close()
398 if done != size:
399 print 'Error, failed to download'
400 os.remove(fname)
401 fname = None
402 return tmpdir, fname
403
404 def Unpack(self, fname, dest):
405 """Unpack a tar file
406
407 Args:
408 fname: Filename to unpack
409 dest: Destination directory
410 Returns:
411 Directory name of the first entry in the archive, without the
412 trailing /
413 """
414 stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
415 return stdout.splitlines()[0][:-1]
416
417 def TestSettingsHasPath(self, path):
418 """Check if builmand will find this toolchain
419
420 Returns:
421 True if the path is in settings, False if not
422 """
423 paths = self.GetPathList()
424 return path in paths
425
426 def ListArchs(self):
427 """List architectures with available toolchains to download"""
428 host_arch, archives = self.LocateArchUrl('list')
429 re_arch = re.compile('[-a-z0-9.]*_([^-]*)-.*')
430 arch_set = set()
431 for archive in archives:
432 # Remove the host architecture from the start
433 arch = re_arch.match(archive[len(host_arch):])
434 if arch:
435 arch_set.add(arch.group(1))
436 return sorted(arch_set)
437
438 def FetchAndInstall(self, arch):
439 """Fetch and install a new toolchain
440
441 arch:
442 Architecture to fetch, or 'list' to list
443 """
444 # Fist get the URL for this architecture
445 url = self.LocateArchUrl(arch)
446 if not url:
447 print ("Cannot find toolchain for arch '%s' - use 'list' to list" %
448 arch)
449 return 2
450 home = os.environ['HOME']
451 dest = os.path.join(home, '.buildman-toolchains')
452 if not os.path.exists(dest):
453 os.mkdir(dest)
454
455 # Download the tar file for this toolchain and unpack it
456 tmpdir, tarfile = self.Download(url)
457 if not tarfile:
458 return 1
459 print 'Unpacking to: %s' % dest,
460 sys.stdout.flush()
461 path = self.Unpack(tarfile, dest)
462 os.remove(tarfile)
463 os.rmdir(tmpdir)
464 print
465
466 # Check that the toolchain works
467 print 'Testing'
468 dirpath = os.path.join(dest, path)
Simon Glass2a76a642015-03-02 17:05:15 -0700469 compiler_fname_list = self.ScanPath(dirpath, True)
470 if not compiler_fname_list:
Simon Glass827e37b2014-12-01 17:34:06 -0700471 print 'Could not locate C compiler - fetch failed.'
472 return 1
Simon Glass2a76a642015-03-02 17:05:15 -0700473 if len(compiler_fname_list) != 1:
474 print ('Internal error, ambiguous toolchains: %s' %
475 (', '.join(compiler_fname)))
476 return 1
477 toolchain = Toolchain(compiler_fname_list[0], True, True)
Simon Glass827e37b2014-12-01 17:34:06 -0700478
479 # Make sure that it will be found by buildman
480 if not self.TestSettingsHasPath(dirpath):
481 print ("Adding 'download' to config file '%s'" %
482 bsettings.config_fname)
483 tools_dir = os.path.dirname(dirpath)
484 bsettings.SetItem('toolchain', 'download', '%s/*' % tools_dir)
485 return 0