xref: /openbmc/u-boot/tools/buildman/toolchain.py (revision f210b58734b750fcab3d4fef6477a6eaf39b5d51)
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()
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 + ':' + env['PATH']
93        return env
94
95
96class 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
257