xref: /openbmc/u-boot/tools/buildman/toolchain.py (revision bb1501f2c22c979961b735db775605cccedd98f6)
1# Copyright (c) 2012 The Chromium OS Authors.
2#
3# SPDX-License-Identifier:	GPL-2.0+
4#
5
6import re
7import glob
8import os
9
10import bsettings
11import command
12
13class 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(False)
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, full_path):
85        """Returns an environment for using the toolchain.
86
87        Thie takes the current environment and adds CROSS_COMPILE so that
88        the tool chain will operate correctly.
89
90        Args:
91            full_path: Return the full path in CROSS_COMPILE and don't set
92                PATH
93        """
94        env = dict(os.environ)
95        if full_path:
96            env['CROSS_COMPILE'] = os.path.join(self.path, self.cross)
97        else:
98            env['CROSS_COMPILE'] = self.cross
99            env['PATH'] = self.path + ':' + env['PATH']
100
101        return env
102
103
104class Toolchains:
105    """Manage a list of toolchains for building U-Boot
106
107    We select one toolchain for each architecture type
108
109    Public members:
110        toolchains: Dict of Toolchain objects, keyed by architecture name
111        paths: List of paths to check for toolchains (may contain wildcards)
112    """
113
114    def __init__(self):
115        self.toolchains = {}
116        self.paths = []
117        self._make_flags = dict(bsettings.GetItems('make-flags'))
118
119    def GetSettings(self):
120        toolchains = bsettings.GetItems('toolchain')
121        if not toolchains:
122            print ("Warning: No tool chains - please add a [toolchain] section"
123                 " to your buildman config file %s. See README for details" %
124                 bsettings.config_fname)
125
126        for name, value in toolchains:
127            if '*' in value:
128                self.paths += glob.glob(value)
129            else:
130                self.paths.append(value)
131
132    def Add(self, fname, test=True, verbose=False):
133        """Add a toolchain to our list
134
135        We select the given toolchain as our preferred one for its
136        architecture if it is a higher priority than the others.
137
138        Args:
139            fname: Filename of toolchain's gcc driver
140            test: True to run the toolchain to test it
141        """
142        toolchain = Toolchain(fname, test, verbose)
143        add_it = toolchain.ok
144        if toolchain.arch in self.toolchains:
145            add_it = (toolchain.priority <
146                        self.toolchains[toolchain.arch].priority)
147        if add_it:
148            self.toolchains[toolchain.arch] = toolchain
149
150    def Scan(self, verbose):
151        """Scan for available toolchains and select the best for each arch.
152
153        We look for all the toolchains we can file, figure out the
154        architecture for each, and whether it works. Then we select the
155        highest priority toolchain for each arch.
156
157        Args:
158            verbose: True to print out progress information
159        """
160        if verbose: print 'Scanning for tool chains'
161        for path in self.paths:
162            if verbose: print "   - scanning path '%s'" % path
163            for subdir in ['.', 'bin', 'usr/bin']:
164                dirname = os.path.join(path, subdir)
165                if verbose: print "      - looking in '%s'" % dirname
166                for fname in glob.glob(dirname + '/*gcc'):
167                    if verbose: print "         - found '%s'" % fname
168                    self.Add(fname, True, verbose)
169
170    def List(self):
171        """List out the selected toolchains for each architecture"""
172        print 'List of available toolchains (%d):' % len(self.toolchains)
173        if len(self.toolchains):
174            for key, value in sorted(self.toolchains.iteritems()):
175                print '%-10s: %s' % (key, value.gcc)
176        else:
177            print 'None'
178
179    def Select(self, arch):
180        """Returns the toolchain for a given architecture
181
182        Args:
183            args: Name of architecture (e.g. 'arm', 'ppc_8xx')
184
185        returns:
186            toolchain object, or None if none found
187        """
188        for name, value in bsettings.GetItems('toolchain-alias'):
189            if arch == name:
190                arch = value
191
192        if not arch in self.toolchains:
193            raise ValueError, ("No tool chain found for arch '%s'" % arch)
194        return self.toolchains[arch]
195
196    def ResolveReferences(self, var_dict, args):
197        """Resolve variable references in a string
198
199        This converts ${blah} within the string to the value of blah.
200        This function works recursively.
201
202        Args:
203            var_dict: Dictionary containing variables and their values
204            args: String containing make arguments
205        Returns:
206            Resolved string
207
208        >>> bsettings.Setup()
209        >>> tcs = Toolchains()
210        >>> tcs.Add('fred', False)
211        >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
212                        'second' : '2nd'}
213        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
214        'this=OBLIQUE_set'
215        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
216        'this=OBLIQUE_setfi2ndrstnd'
217        """
218        re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
219
220        while True:
221            m = re_var.search(args)
222            if not m:
223                break
224            lookup = m.group(0)[2:-1]
225            value = var_dict.get(lookup, '')
226            args = args[:m.start(0)] + value + args[m.end(0):]
227        return args
228
229    def GetMakeArguments(self, board):
230        """Returns 'make' arguments for a given board
231
232        The flags are in a section called 'make-flags'. Flags are named
233        after the target they represent, for example snapper9260=TESTING=1
234        will pass TESTING=1 to make when building the snapper9260 board.
235
236        References to other boards can be added in the string also. For
237        example:
238
239        [make-flags]
240        at91-boards=ENABLE_AT91_TEST=1
241        snapper9260=${at91-boards} BUILD_TAG=442
242        snapper9g45=${at91-boards} BUILD_TAG=443
243
244        This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
245        and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
246
247        A special 'target' variable is set to the board target.
248
249        Args:
250            board: Board object for the board to check.
251        Returns:
252            'make' flags for that board, or '' if none
253        """
254        self._make_flags['target'] = board.target
255        arg_str = self.ResolveReferences(self._make_flags,
256                           self._make_flags.get(board.target, ''))
257        args = arg_str.split(' ')
258        i = 0
259        while i < len(args):
260            if not args[i]:
261                del args[i]
262            else:
263                i += 1
264        return args
265