xref: /openbmc/u-boot/tools/buildman/toolchain.py (revision 4c58d273)
11a459660SWolfgang Denk# SPDX-License-Identifier: GPL-2.0+
283d290c5STom Rini# Copyright (c) 2012 The Chromium OS Authors.
3fc3fe1c2SSimon Glass#
4fc3fe1c2SSimon Glass
54281ad8eSSimon Glassimport re
6fc3fe1c2SSimon Glassimport glob
7827e37b5SSimon Glassfrom HTMLParser import HTMLParser
8fc3fe1c2SSimon Glassimport os
9827e37b5SSimon Glassimport sys
10827e37b5SSimon Glassimport tempfile
11827e37b5SSimon Glassimport urllib2
12fc3fe1c2SSimon Glass
13fc3fe1c2SSimon Glassimport bsettings
14fc3fe1c2SSimon Glassimport command
15713bea38SSimon Glassimport terminal
16fc3fe1c2SSimon Glass
1717bce66cSSimon Glass(PRIORITY_FULL_PREFIX, PRIORITY_PREFIX_GCC, PRIORITY_PREFIX_GCC_PATH,
1817bce66cSSimon 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 = []
35*4c58d273SDaniel Schwierzeck        self.re_arch = re.compile('[-_]%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)
43*4c58d273SDaniel Schwierzeck                        if self.re_arch.search(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',
122546a6f3aSTom Rini            '-none-linux-gnueabi', '-none-linux-gnueabihf', '-uclinux',
123546a6f3aSTom Rini            '-none-eabi', '-gentoo-linux-gnu', '-linux-gnueabi',
124546a6f3aSTom Rini            '-linux-gnueabihf', '-le-linux', '-uclinux']
125fc3fe1c2SSimon Glass        for prio in range(len(priority_list)):
126fc3fe1c2SSimon Glass            if priority_list[prio] in fname:
127ff690df9SSimon Glass                return PRIORITY_CALC + prio
128ff690df9SSimon Glass        return PRIORITY_CALC + prio
129fc3fe1c2SSimon Glass
130d5fe013cSYork Sun    def GetWrapper(self, show_warning=True):
131d5fe013cSYork Sun        """Get toolchain wrapper from the setting file.
132d5fe013cSYork Sun        """
133d5fe013cSYork Sun	value = ''
134d5fe013cSYork Sun	for name, value in bsettings.GetItems('toolchain-wrapper'):
135d5fe013cSYork Sun            if not value:
136d5fe013cSYork Sun                print "Warning: Wrapper not found"
137d5fe013cSYork Sun        if value:
138d5fe013cSYork Sun            value = value + ' '
139d5fe013cSYork Sun
140d5fe013cSYork Sun        return value
141d5fe013cSYork Sun
142bb1501f2SSimon Glass    def MakeEnvironment(self, full_path):
143fc3fe1c2SSimon Glass        """Returns an environment for using the toolchain.
144fc3fe1c2SSimon Glass
145bb1501f2SSimon Glass        Thie takes the current environment and adds CROSS_COMPILE so that
146b0e994c2SDaniel Schwierzeck        the tool chain will operate correctly. This also disables localized
147b0e994c2SDaniel Schwierzeck        output and possibly unicode encoded output of all build tools by
148b0e994c2SDaniel Schwierzeck        adding LC_ALL=C.
149bb1501f2SSimon Glass
150bb1501f2SSimon Glass        Args:
151bb1501f2SSimon Glass            full_path: Return the full path in CROSS_COMPILE and don't set
152bb1501f2SSimon Glass                PATH
153fc3fe1c2SSimon Glass        """
154fc3fe1c2SSimon Glass        env = dict(os.environ)
155d5fe013cSYork Sun        wrapper = self.GetWrapper()
156d5fe013cSYork Sun
157bb1501f2SSimon Glass        if full_path:
158d5fe013cSYork Sun            env['CROSS_COMPILE'] = wrapper + os.path.join(self.path, self.cross)
159bb1501f2SSimon Glass        else:
160d5fe013cSYork Sun            env['CROSS_COMPILE'] = wrapper + self.cross
161f210b587SSimon Glass            env['PATH'] = self.path + ':' + env['PATH']
162bb1501f2SSimon Glass
163b0e994c2SDaniel Schwierzeck        env['LC_ALL'] = 'C'
164b0e994c2SDaniel Schwierzeck
165fc3fe1c2SSimon Glass        return env
166fc3fe1c2SSimon Glass
167fc3fe1c2SSimon Glass
168fc3fe1c2SSimon Glassclass Toolchains:
169fc3fe1c2SSimon Glass    """Manage a list of toolchains for building U-Boot
170fc3fe1c2SSimon Glass
171fc3fe1c2SSimon Glass    We select one toolchain for each architecture type
172fc3fe1c2SSimon Glass
173fc3fe1c2SSimon Glass    Public members:
174fc3fe1c2SSimon Glass        toolchains: Dict of Toolchain objects, keyed by architecture name
17517bce66cSSimon Glass        prefixes: Dict of prefixes to check, keyed by architecture. This can
17617bce66cSSimon Glass            be a full path and toolchain prefix, for example
17717bce66cSSimon Glass            {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
17817bce66cSSimon Glass            something on the search path, for example
17917bce66cSSimon Glass            {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
180fc3fe1c2SSimon Glass        paths: List of paths to check for toolchains (may contain wildcards)
181fc3fe1c2SSimon Glass    """
182fc3fe1c2SSimon Glass
183fc3fe1c2SSimon Glass    def __init__(self):
184fc3fe1c2SSimon Glass        self.toolchains = {}
18517bce66cSSimon Glass        self.prefixes = {}
186fc3fe1c2SSimon Glass        self.paths = []
187d4144e45SSimon Glass        self._make_flags = dict(bsettings.GetItems('make-flags'))
188d4144e45SSimon Glass
18980e6a487SSimon Glass    def GetPathList(self, show_warning=True):
190827e37b5SSimon Glass        """Get a list of available toolchain paths
191827e37b5SSimon Glass
19280e6a487SSimon Glass        Args:
19380e6a487SSimon Glass            show_warning: True to show a warning if there are no tool chains.
19480e6a487SSimon Glass
195827e37b5SSimon Glass        Returns:
196827e37b5SSimon Glass            List of strings, each a path to a toolchain mentioned in the
197827e37b5SSimon Glass            [toolchain] section of the settings file.
198827e37b5SSimon Glass        """
1994281ad8eSSimon Glass        toolchains = bsettings.GetItems('toolchain')
20080e6a487SSimon Glass        if show_warning and not toolchains:
201713bea38SSimon Glass            print ("Warning: No tool chains. Please run 'buildman "
202713bea38SSimon Glass                   "--fetch-arch all' to download all available toolchains, or "
203713bea38SSimon Glass                   "add a [toolchain] section to your buildman config file "
204713bea38SSimon Glass                   "%s. See README for details" %
2051826a18dSMasahiro Yamada                   bsettings.config_fname)
2064281ad8eSSimon Glass
207827e37b5SSimon Glass        paths = []
2084281ad8eSSimon Glass        for name, value in toolchains:
209fc3fe1c2SSimon Glass            if '*' in value:
210827e37b5SSimon Glass                paths += glob.glob(value)
211fc3fe1c2SSimon Glass            else:
212827e37b5SSimon Glass                paths.append(value)
213827e37b5SSimon Glass        return paths
214827e37b5SSimon Glass
21580e6a487SSimon Glass    def GetSettings(self, show_warning=True):
21680e6a487SSimon Glass        """Get toolchain settings from the settings file.
21780e6a487SSimon Glass
21880e6a487SSimon Glass        Args:
21980e6a487SSimon Glass            show_warning: True to show a warning if there are no tool chains.
22080e6a487SSimon Glass        """
22117bce66cSSimon Glass        self.prefixes = bsettings.GetItems('toolchain-prefix')
22280e6a487SSimon Glass        self.paths += self.GetPathList(show_warning)
223fc3fe1c2SSimon Glass
224608e399fSSimon Glass    def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
225608e399fSSimon Glass            arch=None):
226fc3fe1c2SSimon Glass        """Add a toolchain to our list
227fc3fe1c2SSimon Glass
228fc3fe1c2SSimon Glass        We select the given toolchain as our preferred one for its
229fc3fe1c2SSimon Glass        architecture if it is a higher priority than the others.
230fc3fe1c2SSimon Glass
231fc3fe1c2SSimon Glass        Args:
232fc3fe1c2SSimon Glass            fname: Filename of toolchain's gcc driver
233fc3fe1c2SSimon Glass            test: True to run the toolchain to test it
234ff690df9SSimon Glass            priority: Priority to use for this toolchain
235608e399fSSimon Glass            arch: Toolchain architecture, or None if not known
236fc3fe1c2SSimon Glass        """
237608e399fSSimon Glass        toolchain = Toolchain(fname, test, verbose, priority, arch)
238fc3fe1c2SSimon Glass        add_it = toolchain.ok
239fc3fe1c2SSimon Glass        if toolchain.arch in self.toolchains:
240fc3fe1c2SSimon Glass            add_it = (toolchain.priority <
241fc3fe1c2SSimon Glass                        self.toolchains[toolchain.arch].priority)
242fc3fe1c2SSimon Glass        if add_it:
243fc3fe1c2SSimon Glass            self.toolchains[toolchain.arch] = toolchain
244ff690df9SSimon Glass        elif verbose:
245ff690df9SSimon Glass            print ("Toolchain '%s' at priority %d will be ignored because "
246ff690df9SSimon Glass                   "another toolchain for arch '%s' has priority %d" %
247ff690df9SSimon Glass                   (toolchain.gcc, toolchain.priority, toolchain.arch,
248ff690df9SSimon Glass                    self.toolchains[toolchain.arch].priority))
249fc3fe1c2SSimon Glass
250827e37b5SSimon Glass    def ScanPath(self, path, verbose):
251827e37b5SSimon Glass        """Scan a path for a valid toolchain
252827e37b5SSimon Glass
253827e37b5SSimon Glass        Args:
254827e37b5SSimon Glass            path: Path to scan
255827e37b5SSimon Glass            verbose: True to print out progress information
256827e37b5SSimon Glass        Returns:
257827e37b5SSimon Glass            Filename of C compiler if found, else None
258827e37b5SSimon Glass        """
259d9088983SAlbert ARIBAUD        fnames = []
260827e37b5SSimon Glass        for subdir in ['.', 'bin', 'usr/bin']:
261827e37b5SSimon Glass            dirname = os.path.join(path, subdir)
262827e37b5SSimon Glass            if verbose: print "      - looking in '%s'" % dirname
263827e37b5SSimon Glass            for fname in glob.glob(dirname + '/*gcc'):
264827e37b5SSimon Glass                if verbose: print "         - found '%s'" % fname
265d9088983SAlbert ARIBAUD                fnames.append(fname)
266d9088983SAlbert ARIBAUD        return fnames
267827e37b5SSimon Glass
26817bce66cSSimon Glass    def ScanPathEnv(self, fname):
26917bce66cSSimon Glass        """Scan the PATH environment variable for a given filename.
27017bce66cSSimon Glass
27117bce66cSSimon Glass        Args:
27217bce66cSSimon Glass            fname: Filename to scan for
27317bce66cSSimon Glass        Returns:
27417bce66cSSimon Glass            List of matching pathanames, or [] if none
27517bce66cSSimon Glass        """
27617bce66cSSimon Glass        pathname_list = []
27717bce66cSSimon Glass        for path in os.environ["PATH"].split(os.pathsep):
27817bce66cSSimon Glass            path = path.strip('"')
27917bce66cSSimon Glass            pathname = os.path.join(path, fname)
28017bce66cSSimon Glass            if os.path.exists(pathname):
28117bce66cSSimon Glass                pathname_list.append(pathname)
28217bce66cSSimon Glass        return pathname_list
283827e37b5SSimon Glass
284fc3fe1c2SSimon Glass    def Scan(self, verbose):
285fc3fe1c2SSimon Glass        """Scan for available toolchains and select the best for each arch.
286fc3fe1c2SSimon Glass
287fc3fe1c2SSimon Glass        We look for all the toolchains we can file, figure out the
288fc3fe1c2SSimon Glass        architecture for each, and whether it works. Then we select the
289fc3fe1c2SSimon Glass        highest priority toolchain for each arch.
290fc3fe1c2SSimon Glass
291fc3fe1c2SSimon Glass        Args:
292fc3fe1c2SSimon Glass            verbose: True to print out progress information
293fc3fe1c2SSimon Glass        """
294fc3fe1c2SSimon Glass        if verbose: print 'Scanning for tool chains'
29517bce66cSSimon Glass        for name, value in self.prefixes:
29617bce66cSSimon Glass            if verbose: print "   - scanning prefix '%s'" % value
29717bce66cSSimon Glass            if os.path.exists(value):
29817bce66cSSimon Glass                self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
29917bce66cSSimon Glass                continue
30017bce66cSSimon Glass            fname = value + 'gcc'
30117bce66cSSimon Glass            if os.path.exists(fname):
30217bce66cSSimon Glass                self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
30317bce66cSSimon Glass                continue
30417bce66cSSimon Glass            fname_list = self.ScanPathEnv(fname)
30517bce66cSSimon Glass            for f in fname_list:
30617bce66cSSimon Glass                self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
30717bce66cSSimon Glass            if not fname_list:
30817bce66cSSimon Glass                raise ValueError, ("No tool chain found for prefix '%s'" %
30917bce66cSSimon Glass                                   value)
310fc3fe1c2SSimon Glass        for path in self.paths:
311fc3fe1c2SSimon Glass            if verbose: print "   - scanning path '%s'" % path
312d9088983SAlbert ARIBAUD            fnames = self.ScanPath(path, verbose)
313d9088983SAlbert ARIBAUD            for fname in fnames:
314fc3fe1c2SSimon Glass                self.Add(fname, True, verbose)
315fc3fe1c2SSimon Glass
316fc3fe1c2SSimon Glass    def List(self):
317fc3fe1c2SSimon Glass        """List out the selected toolchains for each architecture"""
318713bea38SSimon Glass        col = terminal.Color()
319713bea38SSimon Glass        print col.Color(col.BLUE, 'List of available toolchains (%d):' %
320713bea38SSimon Glass                        len(self.toolchains))
321fc3fe1c2SSimon Glass        if len(self.toolchains):
322fc3fe1c2SSimon Glass            for key, value in sorted(self.toolchains.iteritems()):
323fc3fe1c2SSimon Glass                print '%-10s: %s' % (key, value.gcc)
324fc3fe1c2SSimon Glass        else:
325fc3fe1c2SSimon Glass            print 'None'
326fc3fe1c2SSimon Glass
327fc3fe1c2SSimon Glass    def Select(self, arch):
328fc3fe1c2SSimon Glass        """Returns the toolchain for a given architecture
329fc3fe1c2SSimon Glass
330fc3fe1c2SSimon Glass        Args:
331fc3fe1c2SSimon Glass            args: Name of architecture (e.g. 'arm', 'ppc_8xx')
332fc3fe1c2SSimon Glass
333fc3fe1c2SSimon Glass        returns:
334fc3fe1c2SSimon Glass            toolchain object, or None if none found
335fc3fe1c2SSimon Glass        """
3369b83bfdcSSimon Glass        for tag, value in bsettings.GetItems('toolchain-alias'):
3379b83bfdcSSimon Glass            if arch == tag:
3389b83bfdcSSimon Glass                for alias in value.split():
3399b83bfdcSSimon Glass                    if alias in self.toolchains:
3409b83bfdcSSimon Glass                        return self.toolchains[alias]
341fc3fe1c2SSimon Glass
342fc3fe1c2SSimon Glass        if not arch in self.toolchains:
343fc3fe1c2SSimon Glass            raise ValueError, ("No tool chain found for arch '%s'" % arch)
344fc3fe1c2SSimon Glass        return self.toolchains[arch]
3454281ad8eSSimon Glass
3464281ad8eSSimon Glass    def ResolveReferences(self, var_dict, args):
3474281ad8eSSimon Glass        """Resolve variable references in a string
3484281ad8eSSimon Glass
3494281ad8eSSimon Glass        This converts ${blah} within the string to the value of blah.
3504281ad8eSSimon Glass        This function works recursively.
3514281ad8eSSimon Glass
3524281ad8eSSimon Glass        Args:
3534281ad8eSSimon Glass            var_dict: Dictionary containing variables and their values
3544281ad8eSSimon Glass            args: String containing make arguments
3554281ad8eSSimon Glass        Returns:
3564281ad8eSSimon Glass            Resolved string
3574281ad8eSSimon Glass
3584281ad8eSSimon Glass        >>> bsettings.Setup()
3594281ad8eSSimon Glass        >>> tcs = Toolchains()
3604281ad8eSSimon Glass        >>> tcs.Add('fred', False)
3614281ad8eSSimon Glass        >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
3624281ad8eSSimon Glass                        'second' : '2nd'}
3634281ad8eSSimon Glass        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
3644281ad8eSSimon Glass        'this=OBLIQUE_set'
3654281ad8eSSimon Glass        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
3664281ad8eSSimon Glass        'this=OBLIQUE_setfi2ndrstnd'
3674281ad8eSSimon Glass        """
368f60c9d4fSSimon Glass        re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
3694281ad8eSSimon Glass
3704281ad8eSSimon Glass        while True:
3714281ad8eSSimon Glass            m = re_var.search(args)
3724281ad8eSSimon Glass            if not m:
3734281ad8eSSimon Glass                break
3744281ad8eSSimon Glass            lookup = m.group(0)[2:-1]
3754281ad8eSSimon Glass            value = var_dict.get(lookup, '')
3764281ad8eSSimon Glass            args = args[:m.start(0)] + value + args[m.end(0):]
3774281ad8eSSimon Glass        return args
3784281ad8eSSimon Glass
3794281ad8eSSimon Glass    def GetMakeArguments(self, board):
3804281ad8eSSimon Glass        """Returns 'make' arguments for a given board
3814281ad8eSSimon Glass
3824281ad8eSSimon Glass        The flags are in a section called 'make-flags'. Flags are named
3834281ad8eSSimon Glass        after the target they represent, for example snapper9260=TESTING=1
3844281ad8eSSimon Glass        will pass TESTING=1 to make when building the snapper9260 board.
3854281ad8eSSimon Glass
3864281ad8eSSimon Glass        References to other boards can be added in the string also. For
3874281ad8eSSimon Glass        example:
3884281ad8eSSimon Glass
3894281ad8eSSimon Glass        [make-flags]
3904281ad8eSSimon Glass        at91-boards=ENABLE_AT91_TEST=1
3914281ad8eSSimon Glass        snapper9260=${at91-boards} BUILD_TAG=442
3924281ad8eSSimon Glass        snapper9g45=${at91-boards} BUILD_TAG=443
3934281ad8eSSimon Glass
3944281ad8eSSimon Glass        This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
3954281ad8eSSimon Glass        and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
3964281ad8eSSimon Glass
3974281ad8eSSimon Glass        A special 'target' variable is set to the board target.
3984281ad8eSSimon Glass
3994281ad8eSSimon Glass        Args:
4004281ad8eSSimon Glass            board: Board object for the board to check.
4014281ad8eSSimon Glass        Returns:
4024281ad8eSSimon Glass            'make' flags for that board, or '' if none
4034281ad8eSSimon Glass        """
4044281ad8eSSimon Glass        self._make_flags['target'] = board.target
4054281ad8eSSimon Glass        arg_str = self.ResolveReferences(self._make_flags,
4064281ad8eSSimon Glass                           self._make_flags.get(board.target, ''))
4074281ad8eSSimon Glass        args = arg_str.split(' ')
4084281ad8eSSimon Glass        i = 0
4094281ad8eSSimon Glass        while i < len(args):
4104281ad8eSSimon Glass            if not args[i]:
4114281ad8eSSimon Glass                del args[i]
4124281ad8eSSimon Glass            else:
4134281ad8eSSimon Glass                i += 1
4144281ad8eSSimon Glass        return args
415827e37b5SSimon Glass
416827e37b5SSimon Glass    def LocateArchUrl(self, fetch_arch):
417827e37b5SSimon Glass        """Find a toolchain available online
418827e37b5SSimon Glass
419827e37b5SSimon Glass        Look in standard places for available toolchains. At present the
420827e37b5SSimon Glass        only standard place is at kernel.org.
421827e37b5SSimon Glass
422827e37b5SSimon Glass        Args:
423827e37b5SSimon Glass            arch: Architecture to look for, or 'list' for all
424827e37b5SSimon Glass        Returns:
425827e37b5SSimon Glass            If fetch_arch is 'list', a tuple:
426827e37b5SSimon Glass                Machine architecture (e.g. x86_64)
427827e37b5SSimon Glass                List of toolchains
428827e37b5SSimon Glass            else
429827e37b5SSimon Glass                URL containing this toolchain, if avaialble, else None
430827e37b5SSimon Glass        """
431827e37b5SSimon Glass        arch = command.OutputOneLine('uname', '-m')
432827e37b5SSimon Glass        base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
433*4c58d273SDaniel Schwierzeck        versions = ['7.3.0', '6.4.0', '4.9.4']
434827e37b5SSimon Glass        links = []
435827e37b5SSimon Glass        for version in versions:
436827e37b5SSimon Glass            url = '%s/%s/%s/' % (base, arch, version)
437827e37b5SSimon Glass            print 'Checking: %s' % url
438827e37b5SSimon Glass            response = urllib2.urlopen(url)
439827e37b5SSimon Glass            html = response.read()
440827e37b5SSimon Glass            parser = MyHTMLParser(fetch_arch)
441827e37b5SSimon Glass            parser.feed(html)
442827e37b5SSimon Glass            if fetch_arch == 'list':
443827e37b5SSimon Glass                links += parser.links
444827e37b5SSimon Glass            elif parser.arch_link:
445827e37b5SSimon Glass                return url + parser.arch_link
446827e37b5SSimon Glass        if fetch_arch == 'list':
447827e37b5SSimon Glass            return arch, links
448827e37b5SSimon Glass        return None
449827e37b5SSimon Glass
450827e37b5SSimon Glass    def Download(self, url):
451827e37b5SSimon Glass        """Download a file to a temporary directory
452827e37b5SSimon Glass
453827e37b5SSimon Glass        Args:
454827e37b5SSimon Glass            url: URL to download
455827e37b5SSimon Glass        Returns:
456827e37b5SSimon Glass            Tuple:
457827e37b5SSimon Glass                Temporary directory name
458827e37b5SSimon Glass                Full path to the downloaded archive file in that directory,
459827e37b5SSimon Glass                    or None if there was an error while downloading
460827e37b5SSimon Glass        """
461ad24ebacSSimon Glass        print 'Downloading: %s' % url
462827e37b5SSimon Glass        leaf = url.split('/')[-1]
463827e37b5SSimon Glass        tmpdir = tempfile.mkdtemp('.buildman')
464827e37b5SSimon Glass        response = urllib2.urlopen(url)
465827e37b5SSimon Glass        fname = os.path.join(tmpdir, leaf)
466827e37b5SSimon Glass        fd = open(fname, 'wb')
467827e37b5SSimon Glass        meta = response.info()
468ad24ebacSSimon Glass        size = int(meta.getheaders('Content-Length')[0])
469827e37b5SSimon Glass        done = 0
470827e37b5SSimon Glass        block_size = 1 << 16
471827e37b5SSimon Glass        status = ''
472827e37b5SSimon Glass
473827e37b5SSimon Glass        # Read the file in chunks and show progress as we go
474827e37b5SSimon Glass        while True:
475827e37b5SSimon Glass            buffer = response.read(block_size)
476827e37b5SSimon Glass            if not buffer:
477827e37b5SSimon Glass                print chr(8) * (len(status) + 1), '\r',
478827e37b5SSimon Glass                break
479827e37b5SSimon Glass
480827e37b5SSimon Glass            done += len(buffer)
481827e37b5SSimon Glass            fd.write(buffer)
482ad24ebacSSimon Glass            status = r'%10d MiB  [%3d%%]' % (done / 1024 / 1024,
483827e37b5SSimon Glass                                             done * 100 / size)
484827e37b5SSimon Glass            status = status + chr(8) * (len(status) + 1)
485827e37b5SSimon Glass            print status,
486827e37b5SSimon Glass            sys.stdout.flush()
487827e37b5SSimon Glass        fd.close()
488827e37b5SSimon Glass        if done != size:
489827e37b5SSimon Glass            print 'Error, failed to download'
490827e37b5SSimon Glass            os.remove(fname)
491827e37b5SSimon Glass            fname = None
492827e37b5SSimon Glass        return tmpdir, fname
493827e37b5SSimon Glass
494827e37b5SSimon Glass    def Unpack(self, fname, dest):
495827e37b5SSimon Glass        """Unpack a tar file
496827e37b5SSimon Glass
497827e37b5SSimon Glass        Args:
498827e37b5SSimon Glass            fname: Filename to unpack
499827e37b5SSimon Glass            dest: Destination directory
500827e37b5SSimon Glass        Returns:
501827e37b5SSimon Glass            Directory name of the first entry in the archive, without the
502827e37b5SSimon Glass            trailing /
503827e37b5SSimon Glass        """
504827e37b5SSimon Glass        stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
505827e37b5SSimon Glass        return stdout.splitlines()[0][:-1]
506827e37b5SSimon Glass
507827e37b5SSimon Glass    def TestSettingsHasPath(self, path):
5082289b276SSimon Glass        """Check if buildman will find this toolchain
509827e37b5SSimon Glass
510827e37b5SSimon Glass        Returns:
511827e37b5SSimon Glass            True if the path is in settings, False if not
512827e37b5SSimon Glass        """
51380e6a487SSimon Glass        paths = self.GetPathList(False)
514827e37b5SSimon Glass        return path in paths
515827e37b5SSimon Glass
516827e37b5SSimon Glass    def ListArchs(self):
517827e37b5SSimon Glass        """List architectures with available toolchains to download"""
518827e37b5SSimon Glass        host_arch, archives = self.LocateArchUrl('list')
519827e37b5SSimon Glass        re_arch = re.compile('[-a-z0-9.]*_([^-]*)-.*')
520827e37b5SSimon Glass        arch_set = set()
521827e37b5SSimon Glass        for archive in archives:
522827e37b5SSimon Glass            # Remove the host architecture from the start
523827e37b5SSimon Glass            arch = re_arch.match(archive[len(host_arch):])
524827e37b5SSimon Glass            if arch:
525827e37b5SSimon Glass                arch_set.add(arch.group(1))
526827e37b5SSimon Glass        return sorted(arch_set)
527827e37b5SSimon Glass
528827e37b5SSimon Glass    def FetchAndInstall(self, arch):
529827e37b5SSimon Glass        """Fetch and install a new toolchain
530827e37b5SSimon Glass
531827e37b5SSimon Glass        arch:
532827e37b5SSimon Glass            Architecture to fetch, or 'list' to list
533827e37b5SSimon Glass        """
534827e37b5SSimon Glass        # Fist get the URL for this architecture
535713bea38SSimon Glass        col = terminal.Color()
536713bea38SSimon Glass        print col.Color(col.BLUE, "Downloading toolchain for arch '%s'" % arch)
537827e37b5SSimon Glass        url = self.LocateArchUrl(arch)
538827e37b5SSimon Glass        if not url:
539827e37b5SSimon Glass            print ("Cannot find toolchain for arch '%s' - use 'list' to list" %
540827e37b5SSimon Glass                   arch)
541827e37b5SSimon Glass            return 2
542827e37b5SSimon Glass        home = os.environ['HOME']
543827e37b5SSimon Glass        dest = os.path.join(home, '.buildman-toolchains')
544827e37b5SSimon Glass        if not os.path.exists(dest):
545827e37b5SSimon Glass            os.mkdir(dest)
546827e37b5SSimon Glass
547827e37b5SSimon Glass        # Download the tar file for this toolchain and unpack it
548827e37b5SSimon Glass        tmpdir, tarfile = self.Download(url)
549827e37b5SSimon Glass        if not tarfile:
550827e37b5SSimon Glass            return 1
551713bea38SSimon Glass        print col.Color(col.GREEN, 'Unpacking to: %s' % dest),
552827e37b5SSimon Glass        sys.stdout.flush()
553827e37b5SSimon Glass        path = self.Unpack(tarfile, dest)
554827e37b5SSimon Glass        os.remove(tarfile)
555827e37b5SSimon Glass        os.rmdir(tmpdir)
556827e37b5SSimon Glass        print
557827e37b5SSimon Glass
558827e37b5SSimon Glass        # Check that the toolchain works
559713bea38SSimon Glass        print col.Color(col.GREEN, 'Testing')
560827e37b5SSimon Glass        dirpath = os.path.join(dest, path)
5612a76a649SSimon Glass        compiler_fname_list = self.ScanPath(dirpath, True)
5622a76a649SSimon Glass        if not compiler_fname_list:
563827e37b5SSimon Glass            print 'Could not locate C compiler - fetch failed.'
564827e37b5SSimon Glass            return 1
5652a76a649SSimon Glass        if len(compiler_fname_list) != 1:
566713bea38SSimon Glass            print col.Color(col.RED, 'Warning, ambiguous toolchains: %s' %
567713bea38SSimon Glass                            ', '.join(compiler_fname_list))
5682a76a649SSimon Glass        toolchain = Toolchain(compiler_fname_list[0], True, True)
569827e37b5SSimon Glass
570827e37b5SSimon Glass        # Make sure that it will be found by buildman
571827e37b5SSimon Glass        if not self.TestSettingsHasPath(dirpath):
572827e37b5SSimon Glass            print ("Adding 'download' to config file '%s'" %
573827e37b5SSimon Glass                   bsettings.config_fname)
574c8785c5bSSimon Glass            bsettings.SetItem('toolchain', 'download', '%s/*/*' % dest)
575827e37b5SSimon Glass        return 0
576