PageRenderTime 2717ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/buildman/toolchain.py

https://gitlab.com/gmbnomis/u-boot
Python | 576 lines | 536 code | 7 blank | 33 comment | 11 complexity | 31180a54a8f9e8bba803cf0c8df8581d MD5 | raw file
  1. # Copyright (c) 2012 The Chromium OS Authors.
  2. #
  3. # SPDX-License-Identifier: GPL-2.0+
  4. #
  5. import re
  6. import glob
  7. from HTMLParser import HTMLParser
  8. import os
  9. import sys
  10. import tempfile
  11. import urllib2
  12. import bsettings
  13. import command
  14. import terminal
  15. (PRIORITY_FULL_PREFIX, PRIORITY_PREFIX_GCC, PRIORITY_PREFIX_GCC_PATH,
  16. PRIORITY_CALC) = range(4)
  17. # Simple class to collect links from a page
  18. class MyHTMLParser(HTMLParser):
  19. def __init__(self, arch):
  20. """Create a new parser
  21. After the parser runs, self.links will be set to a list of the links
  22. to .xz archives found in the page, and self.arch_link will be set to
  23. the one for the given architecture (or None if not found).
  24. Args:
  25. arch: Architecture to search for
  26. """
  27. HTMLParser.__init__(self)
  28. self.arch_link = None
  29. self.links = []
  30. self._match = '_%s-' % arch
  31. def handle_starttag(self, tag, attrs):
  32. if tag == 'a':
  33. for tag, value in attrs:
  34. if tag == 'href':
  35. if value and value.endswith('.xz'):
  36. self.links.append(value)
  37. if self._match in value:
  38. self.arch_link = value
  39. class Toolchain:
  40. """A single toolchain
  41. Public members:
  42. gcc: Full path to C compiler
  43. path: Directory path containing C compiler
  44. cross: Cross compile string, e.g. 'arm-linux-'
  45. arch: Architecture of toolchain as determined from the first
  46. component of the filename. E.g. arm-linux-gcc becomes arm
  47. priority: Toolchain priority (0=highest, 20=lowest)
  48. """
  49. def __init__(self, fname, test, verbose=False, priority=PRIORITY_CALC,
  50. arch=None):
  51. """Create a new toolchain object.
  52. Args:
  53. fname: Filename of the gcc component
  54. test: True to run the toolchain to test it
  55. verbose: True to print out the information
  56. priority: Priority to use for this toolchain, or PRIORITY_CALC to
  57. calculate it
  58. """
  59. self.gcc = fname
  60. self.path = os.path.dirname(fname)
  61. # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
  62. # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
  63. basename = os.path.basename(fname)
  64. pos = basename.rfind('-')
  65. self.cross = basename[:pos + 1] if pos != -1 else ''
  66. # The architecture is the first part of the name
  67. pos = self.cross.find('-')
  68. if arch:
  69. self.arch = arch
  70. else:
  71. self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
  72. env = self.MakeEnvironment(False)
  73. # As a basic sanity check, run the C compiler with --version
  74. cmd = [fname, '--version']
  75. if priority == PRIORITY_CALC:
  76. self.priority = self.GetPriority(fname)
  77. else:
  78. self.priority = priority
  79. if test:
  80. result = command.RunPipe([cmd], capture=True, env=env,
  81. raise_on_error=False)
  82. self.ok = result.return_code == 0
  83. if verbose:
  84. print 'Tool chain test: ',
  85. if self.ok:
  86. print "OK, arch='%s', priority %d" % (self.arch,
  87. self.priority)
  88. else:
  89. print 'BAD'
  90. print 'Command: ', cmd
  91. print result.stdout
  92. print result.stderr
  93. else:
  94. self.ok = True
  95. def GetPriority(self, fname):
  96. """Return the priority of the toolchain.
  97. Toolchains are ranked according to their suitability by their
  98. filename prefix.
  99. Args:
  100. fname: Filename of toolchain
  101. Returns:
  102. Priority of toolchain, PRIORITY_CALC=highest, 20=lowest.
  103. """
  104. priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
  105. '-none-linux-gnueabi', '-none-linux-gnueabihf', '-uclinux',
  106. '-none-eabi', '-gentoo-linux-gnu', '-linux-gnueabi',
  107. '-linux-gnueabihf', '-le-linux', '-uclinux']
  108. for prio in range(len(priority_list)):
  109. if priority_list[prio] in fname:
  110. return PRIORITY_CALC + prio
  111. return PRIORITY_CALC + prio
  112. def GetWrapper(self, show_warning=True):
  113. """Get toolchain wrapper from the setting file.
  114. """
  115. value = ''
  116. for name, value in bsettings.GetItems('toolchain-wrapper'):
  117. if not value:
  118. print "Warning: Wrapper not found"
  119. if value:
  120. value = value + ' '
  121. return value
  122. def MakeEnvironment(self, full_path):
  123. """Returns an environment for using the toolchain.
  124. Thie takes the current environment and adds CROSS_COMPILE so that
  125. the tool chain will operate correctly. This also disables localized
  126. output and possibly unicode encoded output of all build tools by
  127. adding LC_ALL=C.
  128. Args:
  129. full_path: Return the full path in CROSS_COMPILE and don't set
  130. PATH
  131. """
  132. env = dict(os.environ)
  133. wrapper = self.GetWrapper()
  134. if full_path:
  135. env['CROSS_COMPILE'] = wrapper + os.path.join(self.path, self.cross)
  136. else:
  137. env['CROSS_COMPILE'] = wrapper + self.cross
  138. env['PATH'] = self.path + ':' + env['PATH']
  139. env['LC_ALL'] = 'C'
  140. return env
  141. class Toolchains:
  142. """Manage a list of toolchains for building U-Boot
  143. We select one toolchain for each architecture type
  144. Public members:
  145. toolchains: Dict of Toolchain objects, keyed by architecture name
  146. prefixes: Dict of prefixes to check, keyed by architecture. This can
  147. be a full path and toolchain prefix, for example
  148. {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
  149. something on the search path, for example
  150. {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
  151. paths: List of paths to check for toolchains (may contain wildcards)
  152. """
  153. def __init__(self):
  154. self.toolchains = {}
  155. self.prefixes = {}
  156. self.paths = []
  157. self._make_flags = dict(bsettings.GetItems('make-flags'))
  158. def GetPathList(self, show_warning=True):
  159. """Get a list of available toolchain paths
  160. Args:
  161. show_warning: True to show a warning if there are no tool chains.
  162. Returns:
  163. List of strings, each a path to a toolchain mentioned in the
  164. [toolchain] section of the settings file.
  165. """
  166. toolchains = bsettings.GetItems('toolchain')
  167. if show_warning and not toolchains:
  168. print ("Warning: No tool chains. Please run 'buildman "
  169. "--fetch-arch all' to download all available toolchains, or "
  170. "add a [toolchain] section to your buildman config file "
  171. "%s. See README for details" %
  172. bsettings.config_fname)
  173. paths = []
  174. for name, value in toolchains:
  175. if '*' in value:
  176. paths += glob.glob(value)
  177. else:
  178. paths.append(value)
  179. return paths
  180. def GetSettings(self, show_warning=True):
  181. """Get toolchain settings from the settings file.
  182. Args:
  183. show_warning: True to show a warning if there are no tool chains.
  184. """
  185. self.prefixes = bsettings.GetItems('toolchain-prefix')
  186. self.paths += self.GetPathList(show_warning)
  187. def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
  188. arch=None):
  189. """Add a toolchain to our list
  190. We select the given toolchain as our preferred one for its
  191. architecture if it is a higher priority than the others.
  192. Args:
  193. fname: Filename of toolchain's gcc driver
  194. test: True to run the toolchain to test it
  195. priority: Priority to use for this toolchain
  196. arch: Toolchain architecture, or None if not known
  197. """
  198. toolchain = Toolchain(fname, test, verbose, priority, arch)
  199. add_it = toolchain.ok
  200. if toolchain.arch in self.toolchains:
  201. add_it = (toolchain.priority <
  202. self.toolchains[toolchain.arch].priority)
  203. if add_it:
  204. self.toolchains[toolchain.arch] = toolchain
  205. elif verbose:
  206. print ("Toolchain '%s' at priority %d will be ignored because "
  207. "another toolchain for arch '%s' has priority %d" %
  208. (toolchain.gcc, toolchain.priority, toolchain.arch,
  209. self.toolchains[toolchain.arch].priority))
  210. def ScanPath(self, path, verbose):
  211. """Scan a path for a valid toolchain
  212. Args:
  213. path: Path to scan
  214. verbose: True to print out progress information
  215. Returns:
  216. Filename of C compiler if found, else None
  217. """
  218. fnames = []
  219. for subdir in ['.', 'bin', 'usr/bin']:
  220. dirname = os.path.join(path, subdir)
  221. if verbose: print " - looking in '%s'" % dirname
  222. for fname in glob.glob(dirname + '/*gcc'):
  223. if verbose: print " - found '%s'" % fname
  224. fnames.append(fname)
  225. return fnames
  226. def ScanPathEnv(self, fname):
  227. """Scan the PATH environment variable for a given filename.
  228. Args:
  229. fname: Filename to scan for
  230. Returns:
  231. List of matching pathanames, or [] if none
  232. """
  233. pathname_list = []
  234. for path in os.environ["PATH"].split(os.pathsep):
  235. path = path.strip('"')
  236. pathname = os.path.join(path, fname)
  237. if os.path.exists(pathname):
  238. pathname_list.append(pathname)
  239. return pathname_list
  240. def Scan(self, verbose):
  241. """Scan for available toolchains and select the best for each arch.
  242. We look for all the toolchains we can file, figure out the
  243. architecture for each, and whether it works. Then we select the
  244. highest priority toolchain for each arch.
  245. Args:
  246. verbose: True to print out progress information
  247. """
  248. if verbose: print 'Scanning for tool chains'
  249. for name, value in self.prefixes:
  250. if verbose: print " - scanning prefix '%s'" % value
  251. if os.path.exists(value):
  252. self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
  253. continue
  254. fname = value + 'gcc'
  255. if os.path.exists(fname):
  256. self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
  257. continue
  258. fname_list = self.ScanPathEnv(fname)
  259. for f in fname_list:
  260. self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
  261. if not fname_list:
  262. raise ValueError, ("No tool chain found for prefix '%s'" %
  263. value)
  264. for path in self.paths:
  265. if verbose: print " - scanning path '%s'" % path
  266. fnames = self.ScanPath(path, verbose)
  267. for fname in fnames:
  268. self.Add(fname, True, verbose)
  269. def List(self):
  270. """List out the selected toolchains for each architecture"""
  271. col = terminal.Color()
  272. print col.Color(col.BLUE, 'List of available toolchains (%d):' %
  273. len(self.toolchains))
  274. if len(self.toolchains):
  275. for key, value in sorted(self.toolchains.iteritems()):
  276. print '%-10s: %s' % (key, value.gcc)
  277. else:
  278. print 'None'
  279. def Select(self, arch):
  280. """Returns the toolchain for a given architecture
  281. Args:
  282. args: Name of architecture (e.g. 'arm', 'ppc_8xx')
  283. returns:
  284. toolchain object, or None if none found
  285. """
  286. for tag, value in bsettings.GetItems('toolchain-alias'):
  287. if arch == tag:
  288. for alias in value.split():
  289. if alias in self.toolchains:
  290. return self.toolchains[alias]
  291. if not arch in self.toolchains:
  292. raise ValueError, ("No tool chain found for arch '%s'" % arch)
  293. return self.toolchains[arch]
  294. def ResolveReferences(self, var_dict, args):
  295. """Resolve variable references in a string
  296. This converts ${blah} within the string to the value of blah.
  297. This function works recursively.
  298. Args:
  299. var_dict: Dictionary containing variables and their values
  300. args: String containing make arguments
  301. Returns:
  302. Resolved string
  303. >>> bsettings.Setup()
  304. >>> tcs = Toolchains()
  305. >>> tcs.Add('fred', False)
  306. >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
  307. 'second' : '2nd'}
  308. >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
  309. 'this=OBLIQUE_set'
  310. >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
  311. 'this=OBLIQUE_setfi2ndrstnd'
  312. """
  313. re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
  314. while True:
  315. m = re_var.search(args)
  316. if not m:
  317. break
  318. lookup = m.group(0)[2:-1]
  319. value = var_dict.get(lookup, '')
  320. args = args[:m.start(0)] + value + args[m.end(0):]
  321. return args
  322. def GetMakeArguments(self, board):
  323. """Returns 'make' arguments for a given board
  324. The flags are in a section called 'make-flags'. Flags are named
  325. after the target they represent, for example snapper9260=TESTING=1
  326. will pass TESTING=1 to make when building the snapper9260 board.
  327. References to other boards can be added in the string also. For
  328. example:
  329. [make-flags]
  330. at91-boards=ENABLE_AT91_TEST=1
  331. snapper9260=${at91-boards} BUILD_TAG=442
  332. snapper9g45=${at91-boards} BUILD_TAG=443
  333. This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
  334. and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
  335. A special 'target' variable is set to the board target.
  336. Args:
  337. board: Board object for the board to check.
  338. Returns:
  339. 'make' flags for that board, or '' if none
  340. """
  341. self._make_flags['target'] = board.target
  342. arg_str = self.ResolveReferences(self._make_flags,
  343. self._make_flags.get(board.target, ''))
  344. args = arg_str.split(' ')
  345. i = 0
  346. while i < len(args):
  347. if not args[i]:
  348. del args[i]
  349. else:
  350. i += 1
  351. return args
  352. def LocateArchUrl(self, fetch_arch):
  353. """Find a toolchain available online
  354. Look in standard places for available toolchains. At present the
  355. only standard place is at kernel.org.
  356. Args:
  357. arch: Architecture to look for, or 'list' for all
  358. Returns:
  359. If fetch_arch is 'list', a tuple:
  360. Machine architecture (e.g. x86_64)
  361. List of toolchains
  362. else
  363. URL containing this toolchain, if avaialble, else None
  364. """
  365. arch = command.OutputOneLine('uname', '-m')
  366. base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
  367. versions = ['4.9.0', '4.6.3', '4.6.2', '4.5.1', '4.2.4']
  368. links = []
  369. for version in versions:
  370. url = '%s/%s/%s/' % (base, arch, version)
  371. print 'Checking: %s' % url
  372. response = urllib2.urlopen(url)
  373. html = response.read()
  374. parser = MyHTMLParser(fetch_arch)
  375. parser.feed(html)
  376. if fetch_arch == 'list':
  377. links += parser.links
  378. elif parser.arch_link:
  379. return url + parser.arch_link
  380. if fetch_arch == 'list':
  381. return arch, links
  382. return None
  383. def Download(self, url):
  384. """Download a file to a temporary directory
  385. Args:
  386. url: URL to download
  387. Returns:
  388. Tuple:
  389. Temporary directory name
  390. Full path to the downloaded archive file in that directory,
  391. or None if there was an error while downloading
  392. """
  393. print 'Downloading: %s' % url
  394. leaf = url.split('/')[-1]
  395. tmpdir = tempfile.mkdtemp('.buildman')
  396. response = urllib2.urlopen(url)
  397. fname = os.path.join(tmpdir, leaf)
  398. fd = open(fname, 'wb')
  399. meta = response.info()
  400. size = int(meta.getheaders('Content-Length')[0])
  401. done = 0
  402. block_size = 1 << 16
  403. status = ''
  404. # Read the file in chunks and show progress as we go
  405. while True:
  406. buffer = response.read(block_size)
  407. if not buffer:
  408. print chr(8) * (len(status) + 1), '\r',
  409. break
  410. done += len(buffer)
  411. fd.write(buffer)
  412. status = r'%10d MiB [%3d%%]' % (done / 1024 / 1024,
  413. done * 100 / size)
  414. status = status + chr(8) * (len(status) + 1)
  415. print status,
  416. sys.stdout.flush()
  417. fd.close()
  418. if done != size:
  419. print 'Error, failed to download'
  420. os.remove(fname)
  421. fname = None
  422. return tmpdir, fname
  423. def Unpack(self, fname, dest):
  424. """Unpack a tar file
  425. Args:
  426. fname: Filename to unpack
  427. dest: Destination directory
  428. Returns:
  429. Directory name of the first entry in the archive, without the
  430. trailing /
  431. """
  432. stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
  433. return stdout.splitlines()[0][:-1]
  434. def TestSettingsHasPath(self, path):
  435. """Check if buildman will find this toolchain
  436. Returns:
  437. True if the path is in settings, False if not
  438. """
  439. paths = self.GetPathList(False)
  440. return path in paths
  441. def ListArchs(self):
  442. """List architectures with available toolchains to download"""
  443. host_arch, archives = self.LocateArchUrl('list')
  444. re_arch = re.compile('[-a-z0-9.]*_([^-]*)-.*')
  445. arch_set = set()
  446. for archive in archives:
  447. # Remove the host architecture from the start
  448. arch = re_arch.match(archive[len(host_arch):])
  449. if arch:
  450. arch_set.add(arch.group(1))
  451. return sorted(arch_set)
  452. def FetchAndInstall(self, arch):
  453. """Fetch and install a new toolchain
  454. arch:
  455. Architecture to fetch, or 'list' to list
  456. """
  457. # Fist get the URL for this architecture
  458. col = terminal.Color()
  459. print col.Color(col.BLUE, "Downloading toolchain for arch '%s'" % arch)
  460. url = self.LocateArchUrl(arch)
  461. if not url:
  462. print ("Cannot find toolchain for arch '%s' - use 'list' to list" %
  463. arch)
  464. return 2
  465. home = os.environ['HOME']
  466. dest = os.path.join(home, '.buildman-toolchains')
  467. if not os.path.exists(dest):
  468. os.mkdir(dest)
  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 col.Color(col.GREEN, '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. # Check that the toolchain works
  480. print col.Color(col.GREEN, 'Testing')
  481. dirpath = os.path.join(dest, path)
  482. compiler_fname_list = self.ScanPath(dirpath, True)
  483. if not compiler_fname_list:
  484. print 'Could not locate C compiler - fetch failed.'
  485. return 1
  486. if len(compiler_fname_list) != 1:
  487. print col.Color(col.RED, 'Warning, ambiguous toolchains: %s' %
  488. ', '.join(compiler_fname_list))
  489. toolchain = Toolchain(compiler_fname_list[0], True, True)
  490. # Make sure that it will be found by buildman
  491. if not self.TestSettingsHasPath(dirpath):
  492. print ("Adding 'download' to config file '%s'" %
  493. bsettings.config_fname)
  494. bsettings.SetItem('toolchain', 'download', '%s/*/*' % dest)
  495. return 0