xref: /rk3399_rockchip-uboot/tools/buildman/toolchain.py (revision 17bce66cbfb3610a8e341fdb0766cd5586b0f4e9)
1fc3fe1c2SSimon Glass# Copyright (c) 2012 The Chromium OS Authors.
2fc3fe1c2SSimon Glass#
31a459660SWolfgang Denk# SPDX-License-Identifier:	GPL-2.0+
4fc3fe1c2SSimon Glass#
5fc3fe1c2SSimon Glass
64281ad8eSSimon Glassimport re
7fc3fe1c2SSimon Glassimport glob
8827e37b5SSimon Glassfrom HTMLParser import HTMLParser
9fc3fe1c2SSimon Glassimport os
10827e37b5SSimon Glassimport sys
11827e37b5SSimon Glassimport tempfile
12827e37b5SSimon Glassimport urllib2
13fc3fe1c2SSimon Glass
14fc3fe1c2SSimon Glassimport bsettings
15fc3fe1c2SSimon Glassimport command
16fc3fe1c2SSimon Glass
17*17bce66cSSimon Glass(PRIORITY_FULL_PREFIX, PRIORITY_PREFIX_GCC, PRIORITY_PREFIX_GCC_PATH,
18*17bce66cSSimon Glass    PRIORITY_CALC) = range(4)
19ff690df9SSimon Glass
20827e37b5SSimon Glass# Simple class to collect links from a page
21827e37b5SSimon Glassclass MyHTMLParser(HTMLParser):
22827e37b5SSimon Glass    def __init__(self, arch):
23827e37b5SSimon Glass        """Create a new parser
24827e37b5SSimon Glass
25827e37b5SSimon Glass        After the parser runs, self.links will be set to a list of the links
26827e37b5SSimon Glass        to .xz archives found in the page, and self.arch_link will be set to
27827e37b5SSimon Glass        the one for the given architecture (or None if not found).
28827e37b5SSimon Glass
29827e37b5SSimon Glass        Args:
30827e37b5SSimon Glass            arch: Architecture to search for
31827e37b5SSimon Glass        """
32827e37b5SSimon Glass        HTMLParser.__init__(self)
33827e37b5SSimon Glass        self.arch_link = None
34827e37b5SSimon Glass        self.links = []
35827e37b5SSimon Glass        self._match = '_%s-' % arch
36827e37b5SSimon Glass
37827e37b5SSimon Glass    def handle_starttag(self, tag, attrs):
38827e37b5SSimon Glass        if tag == 'a':
39827e37b5SSimon Glass            for tag, value in attrs:
40827e37b5SSimon Glass                if tag == 'href':
41827e37b5SSimon Glass                    if value and value.endswith('.xz'):
42827e37b5SSimon Glass                        self.links.append(value)
43827e37b5SSimon Glass                        if self._match in value:
44827e37b5SSimon Glass                            self.arch_link = value
45827e37b5SSimon Glass
46827e37b5SSimon Glass
47fc3fe1c2SSimon Glassclass Toolchain:
48fc3fe1c2SSimon Glass    """A single toolchain
49fc3fe1c2SSimon Glass
50fc3fe1c2SSimon Glass    Public members:
51fc3fe1c2SSimon Glass        gcc: Full path to C compiler
52fc3fe1c2SSimon Glass        path: Directory path containing C compiler
53fc3fe1c2SSimon Glass        cross: Cross compile string, e.g. 'arm-linux-'
54fc3fe1c2SSimon Glass        arch: Architecture of toolchain as determined from the first
55fc3fe1c2SSimon Glass                component of the filename. E.g. arm-linux-gcc becomes arm
56ff690df9SSimon Glass        priority: Toolchain priority (0=highest, 20=lowest)
57fc3fe1c2SSimon Glass    """
58608e399fSSimon Glass    def __init__(self, fname, test, verbose=False, priority=PRIORITY_CALC,
59608e399fSSimon Glass                 arch=None):
60fc3fe1c2SSimon Glass        """Create a new toolchain object.
61fc3fe1c2SSimon Glass
62fc3fe1c2SSimon Glass        Args:
63fc3fe1c2SSimon Glass            fname: Filename of the gcc component
64fc3fe1c2SSimon Glass            test: True to run the toolchain to test it
65ad24ebacSSimon Glass            verbose: True to print out the information
66ff690df9SSimon Glass            priority: Priority to use for this toolchain, or PRIORITY_CALC to
67ff690df9SSimon Glass                calculate it
68fc3fe1c2SSimon Glass        """
69fc3fe1c2SSimon Glass        self.gcc = fname
70fc3fe1c2SSimon Glass        self.path = os.path.dirname(fname)
71b5324123SSimon Glass
72b5324123SSimon Glass        # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
73b5324123SSimon Glass        # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
74b5324123SSimon Glass        basename = os.path.basename(fname)
75b5324123SSimon Glass        pos = basename.rfind('-')
76b5324123SSimon Glass        self.cross = basename[:pos + 1] if pos != -1 else ''
77b5324123SSimon Glass
78b5324123SSimon Glass        # The architecture is the first part of the name
79fc3fe1c2SSimon Glass        pos = self.cross.find('-')
80608e399fSSimon Glass        if arch:
81608e399fSSimon Glass            self.arch = arch
82608e399fSSimon Glass        else:
83fc3fe1c2SSimon Glass            self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
84fc3fe1c2SSimon Glass
85bb1501f2SSimon Glass        env = self.MakeEnvironment(False)
86fc3fe1c2SSimon Glass
87fc3fe1c2SSimon Glass        # As a basic sanity check, run the C compiler with --version
88fc3fe1c2SSimon Glass        cmd = [fname, '--version']
89ff690df9SSimon Glass        if priority == PRIORITY_CALC:
90ff690df9SSimon Glass            self.priority = self.GetPriority(fname)
91ff690df9SSimon Glass        else:
92ff690df9SSimon Glass            self.priority = priority
93fc3fe1c2SSimon Glass        if test:
948bb2bddcSStephen Warren            result = command.RunPipe([cmd], capture=True, env=env,
958bb2bddcSStephen Warren                                     raise_on_error=False)
96fc3fe1c2SSimon Glass            self.ok = result.return_code == 0
97fc3fe1c2SSimon Glass            if verbose:
98fc3fe1c2SSimon Glass                print 'Tool chain test: ',
99fc3fe1c2SSimon Glass                if self.ok:
100608e399fSSimon Glass                    print "OK, arch='%s', priority %d" % (self.arch,
101608e399fSSimon Glass                                                          self.priority)
102fc3fe1c2SSimon Glass                else:
103fc3fe1c2SSimon Glass                    print 'BAD'
104fc3fe1c2SSimon Glass                    print 'Command: ', cmd
105fc3fe1c2SSimon Glass                    print result.stdout
106fc3fe1c2SSimon Glass                    print result.stderr
107fc3fe1c2SSimon Glass        else:
108fc3fe1c2SSimon Glass            self.ok = True
109fc3fe1c2SSimon Glass
110fc3fe1c2SSimon Glass    def GetPriority(self, fname):
111fc3fe1c2SSimon Glass        """Return the priority of the toolchain.
112fc3fe1c2SSimon Glass
113fc3fe1c2SSimon Glass        Toolchains are ranked according to their suitability by their
114fc3fe1c2SSimon Glass        filename prefix.
115fc3fe1c2SSimon Glass
116fc3fe1c2SSimon Glass        Args:
117fc3fe1c2SSimon Glass            fname: Filename of toolchain
118fc3fe1c2SSimon Glass        Returns:
119ff690df9SSimon Glass            Priority of toolchain, PRIORITY_CALC=highest, 20=lowest.
120fc3fe1c2SSimon Glass        """
1218708267fSMasahiro Yamada        priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
122fc3fe1c2SSimon Glass            '-none-linux-gnueabi', '-uclinux', '-none-eabi',
123fc3fe1c2SSimon Glass            '-gentoo-linux-gnu', '-linux-gnueabi', '-le-linux', '-uclinux']
124fc3fe1c2SSimon Glass        for prio in range(len(priority_list)):
125fc3fe1c2SSimon Glass            if priority_list[prio] in fname:
126ff690df9SSimon Glass                return PRIORITY_CALC + prio
127ff690df9SSimon Glass        return PRIORITY_CALC + prio
128fc3fe1c2SSimon Glass
129bb1501f2SSimon Glass    def MakeEnvironment(self, full_path):
130fc3fe1c2SSimon Glass        """Returns an environment for using the toolchain.
131fc3fe1c2SSimon Glass
132bb1501f2SSimon Glass        Thie takes the current environment and adds CROSS_COMPILE so that
133bb1501f2SSimon Glass        the tool chain will operate correctly.
134bb1501f2SSimon Glass
135bb1501f2SSimon Glass        Args:
136bb1501f2SSimon Glass            full_path: Return the full path in CROSS_COMPILE and don't set
137bb1501f2SSimon Glass                PATH
138fc3fe1c2SSimon Glass        """
139fc3fe1c2SSimon Glass        env = dict(os.environ)
140bb1501f2SSimon Glass        if full_path:
141bb1501f2SSimon Glass            env['CROSS_COMPILE'] = os.path.join(self.path, self.cross)
142bb1501f2SSimon Glass        else:
143fc3fe1c2SSimon Glass            env['CROSS_COMPILE'] = self.cross
144f210b587SSimon Glass            env['PATH'] = self.path + ':' + env['PATH']
145bb1501f2SSimon Glass
146fc3fe1c2SSimon Glass        return env
147fc3fe1c2SSimon Glass
148fc3fe1c2SSimon Glass
149fc3fe1c2SSimon Glassclass Toolchains:
150fc3fe1c2SSimon Glass    """Manage a list of toolchains for building U-Boot
151fc3fe1c2SSimon Glass
152fc3fe1c2SSimon Glass    We select one toolchain for each architecture type
153fc3fe1c2SSimon Glass
154fc3fe1c2SSimon Glass    Public members:
155fc3fe1c2SSimon Glass        toolchains: Dict of Toolchain objects, keyed by architecture name
156*17bce66cSSimon Glass        prefixes: Dict of prefixes to check, keyed by architecture. This can
157*17bce66cSSimon Glass            be a full path and toolchain prefix, for example
158*17bce66cSSimon Glass            {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
159*17bce66cSSimon Glass            something on the search path, for example
160*17bce66cSSimon Glass            {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
161fc3fe1c2SSimon Glass        paths: List of paths to check for toolchains (may contain wildcards)
162fc3fe1c2SSimon Glass    """
163fc3fe1c2SSimon Glass
164fc3fe1c2SSimon Glass    def __init__(self):
165fc3fe1c2SSimon Glass        self.toolchains = {}
166*17bce66cSSimon Glass        self.prefixes = {}
167fc3fe1c2SSimon Glass        self.paths = []
168d4144e45SSimon Glass        self._make_flags = dict(bsettings.GetItems('make-flags'))
169d4144e45SSimon Glass
170827e37b5SSimon Glass    def GetPathList(self):
171827e37b5SSimon Glass        """Get a list of available toolchain paths
172827e37b5SSimon Glass
173827e37b5SSimon Glass        Returns:
174827e37b5SSimon Glass            List of strings, each a path to a toolchain mentioned in the
175827e37b5SSimon Glass            [toolchain] section of the settings file.
176827e37b5SSimon Glass        """
1774281ad8eSSimon Glass        toolchains = bsettings.GetItems('toolchain')
1784281ad8eSSimon Glass        if not toolchains:
179ad24ebacSSimon Glass            print ('Warning: No tool chains - please add a [toolchain] section'
180ad24ebacSSimon Glass                 ' to your buildman config file %s. See README for details' %
1811826a18dSMasahiro Yamada                 bsettings.config_fname)
1824281ad8eSSimon Glass
183827e37b5SSimon Glass        paths = []
1844281ad8eSSimon Glass        for name, value in toolchains:
185fc3fe1c2SSimon Glass            if '*' in value:
186827e37b5SSimon Glass                paths += glob.glob(value)
187fc3fe1c2SSimon Glass            else:
188827e37b5SSimon Glass                paths.append(value)
189827e37b5SSimon Glass        return paths
190827e37b5SSimon Glass
191827e37b5SSimon Glass    def GetSettings(self):
192*17bce66cSSimon Glass      self.prefixes = bsettings.GetItems('toolchain-prefix')
193827e37b5SSimon Glass      self.paths += self.GetPathList()
194fc3fe1c2SSimon Glass
195608e399fSSimon Glass    def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
196608e399fSSimon Glass            arch=None):
197fc3fe1c2SSimon Glass        """Add a toolchain to our list
198fc3fe1c2SSimon Glass
199fc3fe1c2SSimon Glass        We select the given toolchain as our preferred one for its
200fc3fe1c2SSimon Glass        architecture if it is a higher priority than the others.
201fc3fe1c2SSimon Glass
202fc3fe1c2SSimon Glass        Args:
203fc3fe1c2SSimon Glass            fname: Filename of toolchain's gcc driver
204fc3fe1c2SSimon Glass            test: True to run the toolchain to test it
205ff690df9SSimon Glass            priority: Priority to use for this toolchain
206608e399fSSimon Glass            arch: Toolchain architecture, or None if not known
207fc3fe1c2SSimon Glass        """
208608e399fSSimon Glass        toolchain = Toolchain(fname, test, verbose, priority, arch)
209fc3fe1c2SSimon Glass        add_it = toolchain.ok
210fc3fe1c2SSimon Glass        if toolchain.arch in self.toolchains:
211fc3fe1c2SSimon Glass            add_it = (toolchain.priority <
212fc3fe1c2SSimon Glass                        self.toolchains[toolchain.arch].priority)
213fc3fe1c2SSimon Glass        if add_it:
214fc3fe1c2SSimon Glass            self.toolchains[toolchain.arch] = toolchain
215ff690df9SSimon Glass        elif verbose:
216ff690df9SSimon Glass            print ("Toolchain '%s' at priority %d will be ignored because "
217ff690df9SSimon Glass                   "another toolchain for arch '%s' has priority %d" %
218ff690df9SSimon Glass                   (toolchain.gcc, toolchain.priority, toolchain.arch,
219ff690df9SSimon Glass                    self.toolchains[toolchain.arch].priority))
220fc3fe1c2SSimon Glass
221827e37b5SSimon Glass    def ScanPath(self, path, verbose):
222827e37b5SSimon Glass        """Scan a path for a valid toolchain
223827e37b5SSimon Glass
224827e37b5SSimon Glass        Args:
225827e37b5SSimon Glass            path: Path to scan
226827e37b5SSimon Glass            verbose: True to print out progress information
227827e37b5SSimon Glass        Returns:
228827e37b5SSimon Glass            Filename of C compiler if found, else None
229827e37b5SSimon Glass        """
230d9088983SAlbert ARIBAUD        fnames = []
231827e37b5SSimon Glass        for subdir in ['.', 'bin', 'usr/bin']:
232827e37b5SSimon Glass            dirname = os.path.join(path, subdir)
233827e37b5SSimon Glass            if verbose: print "      - looking in '%s'" % dirname
234827e37b5SSimon Glass            for fname in glob.glob(dirname + '/*gcc'):
235827e37b5SSimon Glass                if verbose: print "         - found '%s'" % fname
236d9088983SAlbert ARIBAUD                fnames.append(fname)
237d9088983SAlbert ARIBAUD        return fnames
238827e37b5SSimon Glass
239*17bce66cSSimon Glass    def ScanPathEnv(self, fname):
240*17bce66cSSimon Glass        """Scan the PATH environment variable for a given filename.
241*17bce66cSSimon Glass
242*17bce66cSSimon Glass        Args:
243*17bce66cSSimon Glass            fname: Filename to scan for
244*17bce66cSSimon Glass        Returns:
245*17bce66cSSimon Glass            List of matching pathanames, or [] if none
246*17bce66cSSimon Glass        """
247*17bce66cSSimon Glass        pathname_list = []
248*17bce66cSSimon Glass        for path in os.environ["PATH"].split(os.pathsep):
249*17bce66cSSimon Glass            path = path.strip('"')
250*17bce66cSSimon Glass            pathname = os.path.join(path, fname)
251*17bce66cSSimon Glass            if os.path.exists(pathname):
252*17bce66cSSimon Glass                pathname_list.append(pathname)
253*17bce66cSSimon Glass        return pathname_list
254827e37b5SSimon Glass
255fc3fe1c2SSimon Glass    def Scan(self, verbose):
256fc3fe1c2SSimon Glass        """Scan for available toolchains and select the best for each arch.
257fc3fe1c2SSimon Glass
258fc3fe1c2SSimon Glass        We look for all the toolchains we can file, figure out the
259fc3fe1c2SSimon Glass        architecture for each, and whether it works. Then we select the
260fc3fe1c2SSimon Glass        highest priority toolchain for each arch.
261fc3fe1c2SSimon Glass
262fc3fe1c2SSimon Glass        Args:
263fc3fe1c2SSimon Glass            verbose: True to print out progress information
264fc3fe1c2SSimon Glass        """
265fc3fe1c2SSimon Glass        if verbose: print 'Scanning for tool chains'
266*17bce66cSSimon Glass        for name, value in self.prefixes:
267*17bce66cSSimon Glass            if verbose: print "   - scanning prefix '%s'" % value
268*17bce66cSSimon Glass            if os.path.exists(value):
269*17bce66cSSimon Glass                self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
270*17bce66cSSimon Glass                continue
271*17bce66cSSimon Glass            fname = value + 'gcc'
272*17bce66cSSimon Glass            if os.path.exists(fname):
273*17bce66cSSimon Glass                self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
274*17bce66cSSimon Glass                continue
275*17bce66cSSimon Glass            fname_list = self.ScanPathEnv(fname)
276*17bce66cSSimon Glass            for f in fname_list:
277*17bce66cSSimon Glass                self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
278*17bce66cSSimon Glass            if not fname_list:
279*17bce66cSSimon Glass                raise ValueError, ("No tool chain found for prefix '%s'" %
280*17bce66cSSimon Glass                                   value)
281fc3fe1c2SSimon Glass        for path in self.paths:
282fc3fe1c2SSimon Glass            if verbose: print "   - scanning path '%s'" % path
283d9088983SAlbert ARIBAUD            fnames = self.ScanPath(path, verbose)
284d9088983SAlbert ARIBAUD            for fname in fnames:
285fc3fe1c2SSimon Glass                self.Add(fname, True, verbose)
286fc3fe1c2SSimon Glass
287fc3fe1c2SSimon Glass    def List(self):
288fc3fe1c2SSimon Glass        """List out the selected toolchains for each architecture"""
289fc3fe1c2SSimon Glass        print 'List of available toolchains (%d):' % len(self.toolchains)
290fc3fe1c2SSimon Glass        if len(self.toolchains):
291fc3fe1c2SSimon Glass            for key, value in sorted(self.toolchains.iteritems()):
292fc3fe1c2SSimon Glass                print '%-10s: %s' % (key, value.gcc)
293fc3fe1c2SSimon Glass        else:
294fc3fe1c2SSimon Glass            print 'None'
295fc3fe1c2SSimon Glass
296fc3fe1c2SSimon Glass    def Select(self, arch):
297fc3fe1c2SSimon Glass        """Returns the toolchain for a given architecture
298fc3fe1c2SSimon Glass
299fc3fe1c2SSimon Glass        Args:
300fc3fe1c2SSimon Glass            args: Name of architecture (e.g. 'arm', 'ppc_8xx')
301fc3fe1c2SSimon Glass
302fc3fe1c2SSimon Glass        returns:
303fc3fe1c2SSimon Glass            toolchain object, or None if none found
304fc3fe1c2SSimon Glass        """
3059b83bfdcSSimon Glass        for tag, value in bsettings.GetItems('toolchain-alias'):
3069b83bfdcSSimon Glass            if arch == tag:
3079b83bfdcSSimon Glass                for alias in value.split():
3089b83bfdcSSimon Glass                    if alias in self.toolchains:
3099b83bfdcSSimon Glass                        return self.toolchains[alias]
310fc3fe1c2SSimon Glass
311fc3fe1c2SSimon Glass        if not arch in self.toolchains:
312fc3fe1c2SSimon Glass            raise ValueError, ("No tool chain found for arch '%s'" % arch)
313fc3fe1c2SSimon Glass        return self.toolchains[arch]
3144281ad8eSSimon Glass
3154281ad8eSSimon Glass    def ResolveReferences(self, var_dict, args):
3164281ad8eSSimon Glass        """Resolve variable references in a string
3174281ad8eSSimon Glass
3184281ad8eSSimon Glass        This converts ${blah} within the string to the value of blah.
3194281ad8eSSimon Glass        This function works recursively.
3204281ad8eSSimon Glass
3214281ad8eSSimon Glass        Args:
3224281ad8eSSimon Glass            var_dict: Dictionary containing variables and their values
3234281ad8eSSimon Glass            args: String containing make arguments
3244281ad8eSSimon Glass        Returns:
3254281ad8eSSimon Glass            Resolved string
3264281ad8eSSimon Glass
3274281ad8eSSimon Glass        >>> bsettings.Setup()
3284281ad8eSSimon Glass        >>> tcs = Toolchains()
3294281ad8eSSimon Glass        >>> tcs.Add('fred', False)
3304281ad8eSSimon Glass        >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
3314281ad8eSSimon Glass                        'second' : '2nd'}
3324281ad8eSSimon Glass        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
3334281ad8eSSimon Glass        'this=OBLIQUE_set'
3344281ad8eSSimon Glass        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
3354281ad8eSSimon Glass        'this=OBLIQUE_setfi2ndrstnd'
3364281ad8eSSimon Glass        """
337f60c9d4fSSimon Glass        re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
3384281ad8eSSimon Glass
3394281ad8eSSimon Glass        while True:
3404281ad8eSSimon Glass            m = re_var.search(args)
3414281ad8eSSimon Glass            if not m:
3424281ad8eSSimon Glass                break
3434281ad8eSSimon Glass            lookup = m.group(0)[2:-1]
3444281ad8eSSimon Glass            value = var_dict.get(lookup, '')
3454281ad8eSSimon Glass            args = args[:m.start(0)] + value + args[m.end(0):]
3464281ad8eSSimon Glass        return args
3474281ad8eSSimon Glass
3484281ad8eSSimon Glass    def GetMakeArguments(self, board):
3494281ad8eSSimon Glass        """Returns 'make' arguments for a given board
3504281ad8eSSimon Glass
3514281ad8eSSimon Glass        The flags are in a section called 'make-flags'. Flags are named
3524281ad8eSSimon Glass        after the target they represent, for example snapper9260=TESTING=1
3534281ad8eSSimon Glass        will pass TESTING=1 to make when building the snapper9260 board.
3544281ad8eSSimon Glass
3554281ad8eSSimon Glass        References to other boards can be added in the string also. For
3564281ad8eSSimon Glass        example:
3574281ad8eSSimon Glass
3584281ad8eSSimon Glass        [make-flags]
3594281ad8eSSimon Glass        at91-boards=ENABLE_AT91_TEST=1
3604281ad8eSSimon Glass        snapper9260=${at91-boards} BUILD_TAG=442
3614281ad8eSSimon Glass        snapper9g45=${at91-boards} BUILD_TAG=443
3624281ad8eSSimon Glass
3634281ad8eSSimon Glass        This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
3644281ad8eSSimon Glass        and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
3654281ad8eSSimon Glass
3664281ad8eSSimon Glass        A special 'target' variable is set to the board target.
3674281ad8eSSimon Glass
3684281ad8eSSimon Glass        Args:
3694281ad8eSSimon Glass            board: Board object for the board to check.
3704281ad8eSSimon Glass        Returns:
3714281ad8eSSimon Glass            'make' flags for that board, or '' if none
3724281ad8eSSimon Glass        """
3734281ad8eSSimon Glass        self._make_flags['target'] = board.target
3744281ad8eSSimon Glass        arg_str = self.ResolveReferences(self._make_flags,
3754281ad8eSSimon Glass                           self._make_flags.get(board.target, ''))
3764281ad8eSSimon Glass        args = arg_str.split(' ')
3774281ad8eSSimon Glass        i = 0
3784281ad8eSSimon Glass        while i < len(args):
3794281ad8eSSimon Glass            if not args[i]:
3804281ad8eSSimon Glass                del args[i]
3814281ad8eSSimon Glass            else:
3824281ad8eSSimon Glass                i += 1
3834281ad8eSSimon Glass        return args
384827e37b5SSimon Glass
385827e37b5SSimon Glass    def LocateArchUrl(self, fetch_arch):
386827e37b5SSimon Glass        """Find a toolchain available online
387827e37b5SSimon Glass
388827e37b5SSimon Glass        Look in standard places for available toolchains. At present the
389827e37b5SSimon Glass        only standard place is at kernel.org.
390827e37b5SSimon Glass
391827e37b5SSimon Glass        Args:
392827e37b5SSimon Glass            arch: Architecture to look for, or 'list' for all
393827e37b5SSimon Glass        Returns:
394827e37b5SSimon Glass            If fetch_arch is 'list', a tuple:
395827e37b5SSimon Glass                Machine architecture (e.g. x86_64)
396827e37b5SSimon Glass                List of toolchains
397827e37b5SSimon Glass            else
398827e37b5SSimon Glass                URL containing this toolchain, if avaialble, else None
399827e37b5SSimon Glass        """
400827e37b5SSimon Glass        arch = command.OutputOneLine('uname', '-m')
401827e37b5SSimon Glass        base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
4021246231cSMichal Simek        versions = ['4.9.0', '4.6.3', '4.6.2', '4.5.1', '4.2.4']
403827e37b5SSimon Glass        links = []
404827e37b5SSimon Glass        for version in versions:
405827e37b5SSimon Glass            url = '%s/%s/%s/' % (base, arch, version)
406827e37b5SSimon Glass            print 'Checking: %s' % url
407827e37b5SSimon Glass            response = urllib2.urlopen(url)
408827e37b5SSimon Glass            html = response.read()
409827e37b5SSimon Glass            parser = MyHTMLParser(fetch_arch)
410827e37b5SSimon Glass            parser.feed(html)
411827e37b5SSimon Glass            if fetch_arch == 'list':
412827e37b5SSimon Glass                links += parser.links
413827e37b5SSimon Glass            elif parser.arch_link:
414827e37b5SSimon Glass                return url + parser.arch_link
415827e37b5SSimon Glass        if fetch_arch == 'list':
416827e37b5SSimon Glass            return arch, links
417827e37b5SSimon Glass        return None
418827e37b5SSimon Glass
419827e37b5SSimon Glass    def Download(self, url):
420827e37b5SSimon Glass        """Download a file to a temporary directory
421827e37b5SSimon Glass
422827e37b5SSimon Glass        Args:
423827e37b5SSimon Glass            url: URL to download
424827e37b5SSimon Glass        Returns:
425827e37b5SSimon Glass            Tuple:
426827e37b5SSimon Glass                Temporary directory name
427827e37b5SSimon Glass                Full path to the downloaded archive file in that directory,
428827e37b5SSimon Glass                    or None if there was an error while downloading
429827e37b5SSimon Glass        """
430ad24ebacSSimon Glass        print 'Downloading: %s' % url
431827e37b5SSimon Glass        leaf = url.split('/')[-1]
432827e37b5SSimon Glass        tmpdir = tempfile.mkdtemp('.buildman')
433827e37b5SSimon Glass        response = urllib2.urlopen(url)
434827e37b5SSimon Glass        fname = os.path.join(tmpdir, leaf)
435827e37b5SSimon Glass        fd = open(fname, 'wb')
436827e37b5SSimon Glass        meta = response.info()
437ad24ebacSSimon Glass        size = int(meta.getheaders('Content-Length')[0])
438827e37b5SSimon Glass        done = 0
439827e37b5SSimon Glass        block_size = 1 << 16
440827e37b5SSimon Glass        status = ''
441827e37b5SSimon Glass
442827e37b5SSimon Glass        # Read the file in chunks and show progress as we go
443827e37b5SSimon Glass        while True:
444827e37b5SSimon Glass            buffer = response.read(block_size)
445827e37b5SSimon Glass            if not buffer:
446827e37b5SSimon Glass                print chr(8) * (len(status) + 1), '\r',
447827e37b5SSimon Glass                break
448827e37b5SSimon Glass
449827e37b5SSimon Glass            done += len(buffer)
450827e37b5SSimon Glass            fd.write(buffer)
451ad24ebacSSimon Glass            status = r'%10d MiB  [%3d%%]' % (done / 1024 / 1024,
452827e37b5SSimon Glass                                             done * 100 / size)
453827e37b5SSimon Glass            status = status + chr(8) * (len(status) + 1)
454827e37b5SSimon Glass            print status,
455827e37b5SSimon Glass            sys.stdout.flush()
456827e37b5SSimon Glass        fd.close()
457827e37b5SSimon Glass        if done != size:
458827e37b5SSimon Glass            print 'Error, failed to download'
459827e37b5SSimon Glass            os.remove(fname)
460827e37b5SSimon Glass            fname = None
461827e37b5SSimon Glass        return tmpdir, fname
462827e37b5SSimon Glass
463827e37b5SSimon Glass    def Unpack(self, fname, dest):
464827e37b5SSimon Glass        """Unpack a tar file
465827e37b5SSimon Glass
466827e37b5SSimon Glass        Args:
467827e37b5SSimon Glass            fname: Filename to unpack
468827e37b5SSimon Glass            dest: Destination directory
469827e37b5SSimon Glass        Returns:
470827e37b5SSimon Glass            Directory name of the first entry in the archive, without the
471827e37b5SSimon Glass            trailing /
472827e37b5SSimon Glass        """
473827e37b5SSimon Glass        stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
474827e37b5SSimon Glass        return stdout.splitlines()[0][:-1]
475827e37b5SSimon Glass
476827e37b5SSimon Glass    def TestSettingsHasPath(self, path):
477827e37b5SSimon Glass        """Check if builmand will find this toolchain
478827e37b5SSimon Glass
479827e37b5SSimon Glass        Returns:
480827e37b5SSimon Glass            True if the path is in settings, False if not
481827e37b5SSimon Glass        """
482827e37b5SSimon Glass        paths = self.GetPathList()
483827e37b5SSimon Glass        return path in paths
484827e37b5SSimon Glass
485827e37b5SSimon Glass    def ListArchs(self):
486827e37b5SSimon Glass        """List architectures with available toolchains to download"""
487827e37b5SSimon Glass        host_arch, archives = self.LocateArchUrl('list')
488827e37b5SSimon Glass        re_arch = re.compile('[-a-z0-9.]*_([^-]*)-.*')
489827e37b5SSimon Glass        arch_set = set()
490827e37b5SSimon Glass        for archive in archives:
491827e37b5SSimon Glass            # Remove the host architecture from the start
492827e37b5SSimon Glass            arch = re_arch.match(archive[len(host_arch):])
493827e37b5SSimon Glass            if arch:
494827e37b5SSimon Glass                arch_set.add(arch.group(1))
495827e37b5SSimon Glass        return sorted(arch_set)
496827e37b5SSimon Glass
497827e37b5SSimon Glass    def FetchAndInstall(self, arch):
498827e37b5SSimon Glass        """Fetch and install a new toolchain
499827e37b5SSimon Glass
500827e37b5SSimon Glass        arch:
501827e37b5SSimon Glass            Architecture to fetch, or 'list' to list
502827e37b5SSimon Glass        """
503827e37b5SSimon Glass        # Fist get the URL for this architecture
504827e37b5SSimon Glass        url = self.LocateArchUrl(arch)
505827e37b5SSimon Glass        if not url:
506827e37b5SSimon Glass            print ("Cannot find toolchain for arch '%s' - use 'list' to list" %
507827e37b5SSimon Glass                   arch)
508827e37b5SSimon Glass            return 2
509827e37b5SSimon Glass        home = os.environ['HOME']
510827e37b5SSimon Glass        dest = os.path.join(home, '.buildman-toolchains')
511827e37b5SSimon Glass        if not os.path.exists(dest):
512827e37b5SSimon Glass            os.mkdir(dest)
513827e37b5SSimon Glass
514827e37b5SSimon Glass        # Download the tar file for this toolchain and unpack it
515827e37b5SSimon Glass        tmpdir, tarfile = self.Download(url)
516827e37b5SSimon Glass        if not tarfile:
517827e37b5SSimon Glass            return 1
518827e37b5SSimon Glass        print 'Unpacking to: %s' % dest,
519827e37b5SSimon Glass        sys.stdout.flush()
520827e37b5SSimon Glass        path = self.Unpack(tarfile, dest)
521827e37b5SSimon Glass        os.remove(tarfile)
522827e37b5SSimon Glass        os.rmdir(tmpdir)
523827e37b5SSimon Glass        print
524827e37b5SSimon Glass
525827e37b5SSimon Glass        # Check that the toolchain works
526827e37b5SSimon Glass        print 'Testing'
527827e37b5SSimon Glass        dirpath = os.path.join(dest, path)
5282a76a649SSimon Glass        compiler_fname_list = self.ScanPath(dirpath, True)
5292a76a649SSimon Glass        if not compiler_fname_list:
530827e37b5SSimon Glass            print 'Could not locate C compiler - fetch failed.'
531827e37b5SSimon Glass            return 1
5322a76a649SSimon Glass        if len(compiler_fname_list) != 1:
5332a76a649SSimon Glass            print ('Internal error, ambiguous toolchains: %s' %
5342a76a649SSimon Glass                   (', '.join(compiler_fname)))
5352a76a649SSimon Glass            return 1
5362a76a649SSimon Glass        toolchain = Toolchain(compiler_fname_list[0], True, True)
537827e37b5SSimon Glass
538827e37b5SSimon Glass        # Make sure that it will be found by buildman
539827e37b5SSimon Glass        if not self.TestSettingsHasPath(dirpath):
540827e37b5SSimon Glass            print ("Adding 'download' to config file '%s'" %
541827e37b5SSimon Glass                   bsettings.config_fname)
542827e37b5SSimon Glass            tools_dir = os.path.dirname(dirpath)
543827e37b5SSimon Glass            bsettings.SetItem('toolchain', 'download', '%s/*' % tools_dir)
544827e37b5SSimon Glass        return 0
545