buildman: Try to avoid hard-coded string parsing
[oweals/u-boot.git] / tools / buildman / toolchain.py
1 # Copyright (c) 2012 The Chromium OS Authors.
2 #
3 # SPDX-License-Identifier:      GPL-2.0+
4 #
5
6 import re
7 import glob
8 import os
9
10 import bsettings
11 import command
12
13 class Toolchain:
14     """A single toolchain
15
16     Public members:
17         gcc: Full path to C compiler
18         path: Directory path containing C compiler
19         cross: Cross compile string, e.g. 'arm-linux-'
20         arch: Architecture of toolchain as determined from the first
21                 component of the filename. E.g. arm-linux-gcc becomes arm
22     """
23
24     def __init__(self, fname, test, verbose=False):
25         """Create a new toolchain object.
26
27         Args:
28             fname: Filename of the gcc component
29             test: True to run the toolchain to test it
30         """
31         self.gcc = fname
32         self.path = os.path.dirname(fname)
33
34         # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
35         # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
36         basename = os.path.basename(fname)
37         pos = basename.rfind('-')
38         self.cross = basename[:pos + 1] if pos != -1 else ''
39
40         # The architecture is the first part of the name
41         pos = self.cross.find('-')
42         self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
43
44         env = self.MakeEnvironment()
45
46         # As a basic sanity check, run the C compiler with --version
47         cmd = [fname, '--version']
48         if test:
49             result = command.RunPipe([cmd], capture=True, env=env,
50                                      raise_on_error=False)
51             self.ok = result.return_code == 0
52             if verbose:
53                 print 'Tool chain test: ',
54                 if self.ok:
55                     print 'OK'
56                 else:
57                     print 'BAD'
58                     print 'Command: ', cmd
59                     print result.stdout
60                     print result.stderr
61         else:
62             self.ok = True
63         self.priority = self.GetPriority(fname)
64
65     def GetPriority(self, fname):
66         """Return the priority of the toolchain.
67
68         Toolchains are ranked according to their suitability by their
69         filename prefix.
70
71         Args:
72             fname: Filename of toolchain
73         Returns:
74             Priority of toolchain, 0=highest, 20=lowest.
75         """
76         priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
77             '-none-linux-gnueabi', '-uclinux', '-none-eabi',
78             '-gentoo-linux-gnu', '-linux-gnueabi', '-le-linux', '-uclinux']
79         for prio in range(len(priority_list)):
80             if priority_list[prio] in fname:
81                 return prio
82         return prio
83
84     def MakeEnvironment(self):
85         """Returns an environment for using the toolchain.
86
87         Thie takes the current environment, adds CROSS_COMPILE and
88         augments PATH so that the toolchain will operate correctly.
89         """
90         env = dict(os.environ)
91         env['CROSS_COMPILE'] = self.cross
92         env['PATH'] += (':' + self.path)
93         return env
94
95
96 class Toolchains:
97     """Manage a list of toolchains for building U-Boot
98
99     We select one toolchain for each architecture type
100
101     Public members:
102         toolchains: Dict of Toolchain objects, keyed by architecture name
103         paths: List of paths to check for toolchains (may contain wildcards)
104     """
105
106     def __init__(self):
107         self.toolchains = {}
108         self.paths = []
109         self._make_flags = dict(bsettings.GetItems('make-flags'))
110
111     def GetSettings(self):
112         toolchains = bsettings.GetItems('toolchain')
113         if not toolchains:
114             print ("Warning: No tool chains - please add a [toolchain] section"
115                  " to your buildman config file %s. See README for details" %
116                  bsettings.config_fname)
117
118         for name, value in toolchains:
119             if '*' in value:
120                 self.paths += glob.glob(value)
121             else:
122                 self.paths.append(value)
123
124     def Add(self, fname, test=True, verbose=False):
125         """Add a toolchain to our list
126
127         We select the given toolchain as our preferred one for its
128         architecture if it is a higher priority than the others.
129
130         Args:
131             fname: Filename of toolchain's gcc driver
132             test: True to run the toolchain to test it
133         """
134         toolchain = Toolchain(fname, test, verbose)
135         add_it = toolchain.ok
136         if toolchain.arch in self.toolchains:
137             add_it = (toolchain.priority <
138                         self.toolchains[toolchain.arch].priority)
139         if add_it:
140             self.toolchains[toolchain.arch] = toolchain
141
142     def Scan(self, verbose):
143         """Scan for available toolchains and select the best for each arch.
144
145         We look for all the toolchains we can file, figure out the
146         architecture for each, and whether it works. Then we select the
147         highest priority toolchain for each arch.
148
149         Args:
150             verbose: True to print out progress information
151         """
152         if verbose: print 'Scanning for tool chains'
153         for path in self.paths:
154             if verbose: print "   - scanning path '%s'" % path
155             for subdir in ['.', 'bin', 'usr/bin']:
156                 dirname = os.path.join(path, subdir)
157                 if verbose: print "      - looking in '%s'" % dirname
158                 for fname in glob.glob(dirname + '/*gcc'):
159                     if verbose: print "         - found '%s'" % fname
160                     self.Add(fname, True, verbose)
161
162     def List(self):
163         """List out the selected toolchains for each architecture"""
164         print 'List of available toolchains (%d):' % len(self.toolchains)
165         if len(self.toolchains):
166             for key, value in sorted(self.toolchains.iteritems()):
167                 print '%-10s: %s' % (key, value.gcc)
168         else:
169             print 'None'
170
171     def Select(self, arch):
172         """Returns the toolchain for a given architecture
173
174         Args:
175             args: Name of architecture (e.g. 'arm', 'ppc_8xx')
176
177         returns:
178             toolchain object, or None if none found
179         """
180         for name, value in bsettings.GetItems('toolchain-alias'):
181             if arch == name:
182                 arch = value
183
184         if not arch in self.toolchains:
185             raise ValueError, ("No tool chain found for arch '%s'" % arch)
186         return self.toolchains[arch]
187
188     def ResolveReferences(self, var_dict, args):
189         """Resolve variable references in a string
190
191         This converts ${blah} within the string to the value of blah.
192         This function works recursively.
193
194         Args:
195             var_dict: Dictionary containing variables and their values
196             args: String containing make arguments
197         Returns:
198             Resolved string
199
200         >>> bsettings.Setup()
201         >>> tcs = Toolchains()
202         >>> tcs.Add('fred', False)
203         >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
204                         'second' : '2nd'}
205         >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
206         'this=OBLIQUE_set'
207         >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
208         'this=OBLIQUE_setfi2ndrstnd'
209         """
210         re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
211
212         while True:
213             m = re_var.search(args)
214             if not m:
215                 break
216             lookup = m.group(0)[2:-1]
217             value = var_dict.get(lookup, '')
218             args = args[:m.start(0)] + value + args[m.end(0):]
219         return args
220
221     def GetMakeArguments(self, board):
222         """Returns 'make' arguments for a given board
223
224         The flags are in a section called 'make-flags'. Flags are named
225         after the target they represent, for example snapper9260=TESTING=1
226         will pass TESTING=1 to make when building the snapper9260 board.
227
228         References to other boards can be added in the string also. For
229         example:
230
231         [make-flags]
232         at91-boards=ENABLE_AT91_TEST=1
233         snapper9260=${at91-boards} BUILD_TAG=442
234         snapper9g45=${at91-boards} BUILD_TAG=443
235
236         This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
237         and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
238
239         A special 'target' variable is set to the board target.
240
241         Args:
242             board: Board object for the board to check.
243         Returns:
244             'make' flags for that board, or '' if none
245         """
246         self._make_flags['target'] = board.target
247         arg_str = self.ResolveReferences(self._make_flags,
248                            self._make_flags.get(board.target, ''))
249         args = arg_str.split(' ')
250         i = 0
251         while i < len(args):
252             if not args[i]:
253                 del args[i]
254             else:
255                 i += 1
256         return args