xref: /rk3399_rockchip-uboot/tools/buildman/toolchain.py (revision 8cb3ce64f936f5dedbcfc1935c5caf31bb682474)
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
16713bea38SSimon Glassimport terminal
17fc3fe1c2SSimon Glass
1817bce66cSSimon Glass(PRIORITY_FULL_PREFIX, PRIORITY_PREFIX_GCC, PRIORITY_PREFIX_GCC_PATH,
1917bce66cSSimon Glass    PRIORITY_CALC) = range(4)
20ff690df9SSimon Glass
21827e37b5SSimon Glass# Simple class to collect links from a page
22827e37b5SSimon Glassclass MyHTMLParser(HTMLParser):
23827e37b5SSimon Glass    def __init__(self, arch):
24827e37b5SSimon Glass        """Create a new parser
25827e37b5SSimon Glass
26827e37b5SSimon Glass        After the parser runs, self.links will be set to a list of the links
27827e37b5SSimon Glass        to .xz archives found in the page, and self.arch_link will be set to
28827e37b5SSimon Glass        the one for the given architecture (or None if not found).
29827e37b5SSimon Glass
30827e37b5SSimon Glass        Args:
31827e37b5SSimon Glass            arch: Architecture to search for
32827e37b5SSimon Glass        """
33827e37b5SSimon Glass        HTMLParser.__init__(self)
34827e37b5SSimon Glass        self.arch_link = None
35827e37b5SSimon Glass        self.links = []
36827e37b5SSimon Glass        self._match = '_%s-' % arch
37827e37b5SSimon Glass
38827e37b5SSimon Glass    def handle_starttag(self, tag, attrs):
39827e37b5SSimon Glass        if tag == 'a':
40827e37b5SSimon Glass            for tag, value in attrs:
41827e37b5SSimon Glass                if tag == 'href':
42827e37b5SSimon Glass                    if value and value.endswith('.xz'):
43827e37b5SSimon Glass                        self.links.append(value)
44827e37b5SSimon Glass                        if self._match in value:
45827e37b5SSimon Glass                            self.arch_link = value
46827e37b5SSimon Glass
47827e37b5SSimon Glass
48fc3fe1c2SSimon Glassclass Toolchain:
49fc3fe1c2SSimon Glass    """A single toolchain
50fc3fe1c2SSimon Glass
51fc3fe1c2SSimon Glass    Public members:
52fc3fe1c2SSimon Glass        gcc: Full path to C compiler
53fc3fe1c2SSimon Glass        path: Directory path containing C compiler
54fc3fe1c2SSimon Glass        cross: Cross compile string, e.g. 'arm-linux-'
55fc3fe1c2SSimon Glass        arch: Architecture of toolchain as determined from the first
56fc3fe1c2SSimon Glass                component of the filename. E.g. arm-linux-gcc becomes arm
57ff690df9SSimon Glass        priority: Toolchain priority (0=highest, 20=lowest)
58fc3fe1c2SSimon Glass    """
59608e399fSSimon Glass    def __init__(self, fname, test, verbose=False, priority=PRIORITY_CALC,
60608e399fSSimon Glass                 arch=None):
61fc3fe1c2SSimon Glass        """Create a new toolchain object.
62fc3fe1c2SSimon Glass
63fc3fe1c2SSimon Glass        Args:
64fc3fe1c2SSimon Glass            fname: Filename of the gcc component
65fc3fe1c2SSimon Glass            test: True to run the toolchain to test it
66ad24ebacSSimon Glass            verbose: True to print out the information
67ff690df9SSimon Glass            priority: Priority to use for this toolchain, or PRIORITY_CALC to
68ff690df9SSimon Glass                calculate it
69fc3fe1c2SSimon Glass        """
70fc3fe1c2SSimon Glass        self.gcc = fname
71fc3fe1c2SSimon Glass        self.path = os.path.dirname(fname)
72b5324123SSimon Glass
73b5324123SSimon Glass        # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
74b5324123SSimon Glass        # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
75b5324123SSimon Glass        basename = os.path.basename(fname)
76b5324123SSimon Glass        pos = basename.rfind('-')
77b5324123SSimon Glass        self.cross = basename[:pos + 1] if pos != -1 else ''
78b5324123SSimon Glass
79b5324123SSimon Glass        # The architecture is the first part of the name
80fc3fe1c2SSimon Glass        pos = self.cross.find('-')
81608e399fSSimon Glass        if arch:
82608e399fSSimon Glass            self.arch = arch
83608e399fSSimon Glass        else:
84fc3fe1c2SSimon Glass            self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
85fc3fe1c2SSimon Glass
86bb1501f2SSimon Glass        env = self.MakeEnvironment(False)
87fc3fe1c2SSimon Glass
88fc3fe1c2SSimon Glass        # As a basic sanity check, run the C compiler with --version
89fc3fe1c2SSimon Glass        cmd = [fname, '--version']
90ff690df9SSimon Glass        if priority == PRIORITY_CALC:
91ff690df9SSimon Glass            self.priority = self.GetPriority(fname)
92ff690df9SSimon Glass        else:
93ff690df9SSimon Glass            self.priority = priority
94fc3fe1c2SSimon Glass        if test:
958bb2bddcSStephen Warren            result = command.RunPipe([cmd], capture=True, env=env,
968bb2bddcSStephen Warren                                     raise_on_error=False)
97fc3fe1c2SSimon Glass            self.ok = result.return_code == 0
98fc3fe1c2SSimon Glass            if verbose:
99fc3fe1c2SSimon Glass                print 'Tool chain test: ',
100fc3fe1c2SSimon Glass                if self.ok:
101608e399fSSimon Glass                    print "OK, arch='%s', priority %d" % (self.arch,
102608e399fSSimon Glass                                                          self.priority)
103fc3fe1c2SSimon Glass                else:
104fc3fe1c2SSimon Glass                    print 'BAD'
105fc3fe1c2SSimon Glass                    print 'Command: ', cmd
106fc3fe1c2SSimon Glass                    print result.stdout
107fc3fe1c2SSimon Glass                    print result.stderr
108fc3fe1c2SSimon Glass        else:
109fc3fe1c2SSimon Glass            self.ok = True
110fc3fe1c2SSimon Glass
111fc3fe1c2SSimon Glass    def GetPriority(self, fname):
112fc3fe1c2SSimon Glass        """Return the priority of the toolchain.
113fc3fe1c2SSimon Glass
114fc3fe1c2SSimon Glass        Toolchains are ranked according to their suitability by their
115fc3fe1c2SSimon Glass        filename prefix.
116fc3fe1c2SSimon Glass
117fc3fe1c2SSimon Glass        Args:
118fc3fe1c2SSimon Glass            fname: Filename of toolchain
119fc3fe1c2SSimon Glass        Returns:
120ff690df9SSimon Glass            Priority of toolchain, PRIORITY_CALC=highest, 20=lowest.
121fc3fe1c2SSimon Glass        """
1228708267fSMasahiro Yamada        priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
123546a6f3aSTom Rini            '-none-linux-gnueabi', '-none-linux-gnueabihf', '-uclinux',
124546a6f3aSTom Rini            '-none-eabi', '-gentoo-linux-gnu', '-linux-gnueabi',
125546a6f3aSTom Rini            '-linux-gnueabihf', '-le-linux', '-uclinux']
126fc3fe1c2SSimon Glass        for prio in range(len(priority_list)):
127fc3fe1c2SSimon Glass            if priority_list[prio] in fname:
128ff690df9SSimon Glass                return PRIORITY_CALC + prio
129ff690df9SSimon Glass        return PRIORITY_CALC + prio
130fc3fe1c2SSimon Glass
131d5fe013cSYork Sun    def GetWrapper(self, show_warning=True):
132d5fe013cSYork Sun        """Get toolchain wrapper from the setting file.
133d5fe013cSYork Sun        """
134d5fe013cSYork Sun	value = ''
135d5fe013cSYork Sun	for name, value in bsettings.GetItems('toolchain-wrapper'):
136d5fe013cSYork Sun            if not value:
137d5fe013cSYork Sun                print "Warning: Wrapper not found"
138d5fe013cSYork Sun        if value:
139d5fe013cSYork Sun            value = value + ' '
140d5fe013cSYork Sun
141d5fe013cSYork Sun        return value
142d5fe013cSYork Sun
143bb1501f2SSimon Glass    def MakeEnvironment(self, full_path):
144fc3fe1c2SSimon Glass        """Returns an environment for using the toolchain.
145fc3fe1c2SSimon Glass
146bb1501f2SSimon Glass        Thie takes the current environment and adds CROSS_COMPILE so that
147*b0e994c2SDaniel Schwierzeck        the tool chain will operate correctly. This also disables localized
148*b0e994c2SDaniel Schwierzeck        output and possibly unicode encoded output of all build tools by
149*b0e994c2SDaniel Schwierzeck        adding LC_ALL=C.
150bb1501f2SSimon Glass
151bb1501f2SSimon Glass        Args:
152bb1501f2SSimon Glass            full_path: Return the full path in CROSS_COMPILE and don't set
153bb1501f2SSimon Glass                PATH
154fc3fe1c2SSimon Glass        """
155fc3fe1c2SSimon Glass        env = dict(os.environ)
156d5fe013cSYork Sun        wrapper = self.GetWrapper()
157d5fe013cSYork Sun
158bb1501f2SSimon Glass        if full_path:
159d5fe013cSYork Sun            env['CROSS_COMPILE'] = wrapper + os.path.join(self.path, self.cross)
160bb1501f2SSimon Glass        else:
161d5fe013cSYork Sun            env['CROSS_COMPILE'] = wrapper + self.cross
162f210b587SSimon Glass            env['PATH'] = self.path + ':' + env['PATH']
163bb1501f2SSimon Glass
164*b0e994c2SDaniel Schwierzeck        env['LC_ALL'] = 'C'
165*b0e994c2SDaniel Schwierzeck
166fc3fe1c2SSimon Glass        return env
167fc3fe1c2SSimon Glass
168fc3fe1c2SSimon Glass
169fc3fe1c2SSimon Glassclass Toolchains:
170fc3fe1c2SSimon Glass    """Manage a list of toolchains for building U-Boot
171fc3fe1c2SSimon Glass
172fc3fe1c2SSimon Glass    We select one toolchain for each architecture type
173fc3fe1c2SSimon Glass
174fc3fe1c2SSimon Glass    Public members:
175fc3fe1c2SSimon Glass        toolchains: Dict of Toolchain objects, keyed by architecture name
17617bce66cSSimon Glass        prefixes: Dict of prefixes to check, keyed by architecture. This can
17717bce66cSSimon Glass            be a full path and toolchain prefix, for example
17817bce66cSSimon Glass            {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
17917bce66cSSimon Glass            something on the search path, for example
18017bce66cSSimon Glass            {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
181fc3fe1c2SSimon Glass        paths: List of paths to check for toolchains (may contain wildcards)
182fc3fe1c2SSimon Glass    """
183fc3fe1c2SSimon Glass
184fc3fe1c2SSimon Glass    def __init__(self):
185fc3fe1c2SSimon Glass        self.toolchains = {}
18617bce66cSSimon Glass        self.prefixes = {}
187fc3fe1c2SSimon Glass        self.paths = []
188d4144e45SSimon Glass        self._make_flags = dict(bsettings.GetItems('make-flags'))
189d4144e45SSimon Glass
19080e6a487SSimon Glass    def GetPathList(self, show_warning=True):
191827e37b5SSimon Glass        """Get a list of available toolchain paths
192827e37b5SSimon Glass
19380e6a487SSimon Glass        Args:
19480e6a487SSimon Glass            show_warning: True to show a warning if there are no tool chains.
19580e6a487SSimon Glass
196827e37b5SSimon Glass        Returns:
197827e37b5SSimon Glass            List of strings, each a path to a toolchain mentioned in the
198827e37b5SSimon Glass            [toolchain] section of the settings file.
199827e37b5SSimon Glass        """
2004281ad8eSSimon Glass        toolchains = bsettings.GetItems('toolchain')
20180e6a487SSimon Glass        if show_warning and not toolchains:
202713bea38SSimon Glass            print ("Warning: No tool chains. Please run 'buildman "
203713bea38SSimon Glass                   "--fetch-arch all' to download all available toolchains, or "
204713bea38SSimon Glass                   "add a [toolchain] section to your buildman config file "
205713bea38SSimon Glass                   "%s. See README for details" %
2061826a18dSMasahiro Yamada                   bsettings.config_fname)
2074281ad8eSSimon Glass
208827e37b5SSimon Glass        paths = []
2094281ad8eSSimon Glass        for name, value in toolchains:
210fc3fe1c2SSimon Glass            if '*' in value:
211827e37b5SSimon Glass                paths += glob.glob(value)
212fc3fe1c2SSimon Glass            else:
213827e37b5SSimon Glass                paths.append(value)
214827e37b5SSimon Glass        return paths
215827e37b5SSimon Glass
21680e6a487SSimon Glass    def GetSettings(self, show_warning=True):
21780e6a487SSimon Glass        """Get toolchain settings from the settings file.
21880e6a487SSimon Glass
21980e6a487SSimon Glass        Args:
22080e6a487SSimon Glass            show_warning: True to show a warning if there are no tool chains.
22180e6a487SSimon Glass        """
22217bce66cSSimon Glass        self.prefixes = bsettings.GetItems('toolchain-prefix')
22380e6a487SSimon Glass        self.paths += self.GetPathList(show_warning)
224fc3fe1c2SSimon Glass
225608e399fSSimon Glass    def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
226608e399fSSimon Glass            arch=None):
227fc3fe1c2SSimon Glass        """Add a toolchain to our list
228fc3fe1c2SSimon Glass
229fc3fe1c2SSimon Glass        We select the given toolchain as our preferred one for its
230fc3fe1c2SSimon Glass        architecture if it is a higher priority than the others.
231fc3fe1c2SSimon Glass
232fc3fe1c2SSimon Glass        Args:
233fc3fe1c2SSimon Glass            fname: Filename of toolchain's gcc driver
234fc3fe1c2SSimon Glass            test: True to run the toolchain to test it
235ff690df9SSimon Glass            priority: Priority to use for this toolchain
236608e399fSSimon Glass            arch: Toolchain architecture, or None if not known
237fc3fe1c2SSimon Glass        """
238608e399fSSimon Glass        toolchain = Toolchain(fname, test, verbose, priority, arch)
239fc3fe1c2SSimon Glass        add_it = toolchain.ok
240fc3fe1c2SSimon Glass        if toolchain.arch in self.toolchains:
241fc3fe1c2SSimon Glass            add_it = (toolchain.priority <
242fc3fe1c2SSimon Glass                        self.toolchains[toolchain.arch].priority)
243fc3fe1c2SSimon Glass        if add_it:
244fc3fe1c2SSimon Glass            self.toolchains[toolchain.arch] = toolchain
245ff690df9SSimon Glass        elif verbose:
246ff690df9SSimon Glass            print ("Toolchain '%s' at priority %d will be ignored because "
247ff690df9SSimon Glass                   "another toolchain for arch '%s' has priority %d" %
248ff690df9SSimon Glass                   (toolchain.gcc, toolchain.priority, toolchain.arch,
249ff690df9SSimon Glass                    self.toolchains[toolchain.arch].priority))
250fc3fe1c2SSimon Glass
251827e37b5SSimon Glass    def ScanPath(self, path, verbose):
252827e37b5SSimon Glass        """Scan a path for a valid toolchain
253827e37b5SSimon Glass
254827e37b5SSimon Glass        Args:
255827e37b5SSimon Glass            path: Path to scan
256827e37b5SSimon Glass            verbose: True to print out progress information
257827e37b5SSimon Glass        Returns:
258827e37b5SSimon Glass            Filename of C compiler if found, else None
259827e37b5SSimon Glass        """
260d9088983SAlbert ARIBAUD        fnames = []
261827e37b5SSimon Glass        for subdir in ['.', 'bin', 'usr/bin']:
262827e37b5SSimon Glass            dirname = os.path.join(path, subdir)
263827e37b5SSimon Glass            if verbose: print "      - looking in '%s'" % dirname
264827e37b5SSimon Glass            for fname in glob.glob(dirname + '/*gcc'):
265827e37b5SSimon Glass                if verbose: print "         - found '%s'" % fname
266d9088983SAlbert ARIBAUD                fnames.append(fname)
267d9088983SAlbert ARIBAUD        return fnames
268827e37b5SSimon Glass
26917bce66cSSimon Glass    def ScanPathEnv(self, fname):
27017bce66cSSimon Glass        """Scan the PATH environment variable for a given filename.
27117bce66cSSimon Glass
27217bce66cSSimon Glass        Args:
27317bce66cSSimon Glass            fname: Filename to scan for
27417bce66cSSimon Glass        Returns:
27517bce66cSSimon Glass            List of matching pathanames, or [] if none
27617bce66cSSimon Glass        """
27717bce66cSSimon Glass        pathname_list = []
27817bce66cSSimon Glass        for path in os.environ["PATH"].split(os.pathsep):
27917bce66cSSimon Glass            path = path.strip('"')
28017bce66cSSimon Glass            pathname = os.path.join(path, fname)
28117bce66cSSimon Glass            if os.path.exists(pathname):
28217bce66cSSimon Glass                pathname_list.append(pathname)
28317bce66cSSimon Glass        return pathname_list
284827e37b5SSimon Glass
285fc3fe1c2SSimon Glass    def Scan(self, verbose):
286fc3fe1c2SSimon Glass        """Scan for available toolchains and select the best for each arch.
287fc3fe1c2SSimon Glass
288fc3fe1c2SSimon Glass        We look for all the toolchains we can file, figure out the
289fc3fe1c2SSimon Glass        architecture for each, and whether it works. Then we select the
290fc3fe1c2SSimon Glass        highest priority toolchain for each arch.
291fc3fe1c2SSimon Glass
292fc3fe1c2SSimon Glass        Args:
293fc3fe1c2SSimon Glass            verbose: True to print out progress information
294fc3fe1c2SSimon Glass        """
295fc3fe1c2SSimon Glass        if verbose: print 'Scanning for tool chains'
29617bce66cSSimon Glass        for name, value in self.prefixes:
29717bce66cSSimon Glass            if verbose: print "   - scanning prefix '%s'" % value
29817bce66cSSimon Glass            if os.path.exists(value):
29917bce66cSSimon Glass                self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
30017bce66cSSimon Glass                continue
30117bce66cSSimon Glass            fname = value + 'gcc'
30217bce66cSSimon Glass            if os.path.exists(fname):
30317bce66cSSimon Glass                self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
30417bce66cSSimon Glass                continue
30517bce66cSSimon Glass            fname_list = self.ScanPathEnv(fname)
30617bce66cSSimon Glass            for f in fname_list:
30717bce66cSSimon Glass                self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
30817bce66cSSimon Glass            if not fname_list:
30917bce66cSSimon Glass                raise ValueError, ("No tool chain found for prefix '%s'" %
31017bce66cSSimon Glass                                   value)
311fc3fe1c2SSimon Glass        for path in self.paths:
312fc3fe1c2SSimon Glass            if verbose: print "   - scanning path '%s'" % path
313d9088983SAlbert ARIBAUD            fnames = self.ScanPath(path, verbose)
314d9088983SAlbert ARIBAUD            for fname in fnames:
315fc3fe1c2SSimon Glass                self.Add(fname, True, verbose)
316fc3fe1c2SSimon Glass
317fc3fe1c2SSimon Glass    def List(self):
318fc3fe1c2SSimon Glass        """List out the selected toolchains for each architecture"""
319713bea38SSimon Glass        col = terminal.Color()
320713bea38SSimon Glass        print col.Color(col.BLUE, 'List of available toolchains (%d):' %
321713bea38SSimon Glass                        len(self.toolchains))
322fc3fe1c2SSimon Glass        if len(self.toolchains):
323fc3fe1c2SSimon Glass            for key, value in sorted(self.toolchains.iteritems()):
324fc3fe1c2SSimon Glass                print '%-10s: %s' % (key, value.gcc)
325fc3fe1c2SSimon Glass        else:
326fc3fe1c2SSimon Glass            print 'None'
327fc3fe1c2SSimon Glass
328fc3fe1c2SSimon Glass    def Select(self, arch):
329fc3fe1c2SSimon Glass        """Returns the toolchain for a given architecture
330fc3fe1c2SSimon Glass
331fc3fe1c2SSimon Glass        Args:
332fc3fe1c2SSimon Glass            args: Name of architecture (e.g. 'arm', 'ppc_8xx')
333fc3fe1c2SSimon Glass
334fc3fe1c2SSimon Glass        returns:
335fc3fe1c2SSimon Glass            toolchain object, or None if none found
336fc3fe1c2SSimon Glass        """
3379b83bfdcSSimon Glass        for tag, value in bsettings.GetItems('toolchain-alias'):
3389b83bfdcSSimon Glass            if arch == tag:
3399b83bfdcSSimon Glass                for alias in value.split():
3409b83bfdcSSimon Glass                    if alias in self.toolchains:
3419b83bfdcSSimon Glass                        return self.toolchains[alias]
342fc3fe1c2SSimon Glass
343fc3fe1c2SSimon Glass        if not arch in self.toolchains:
344fc3fe1c2SSimon Glass            raise ValueError, ("No tool chain found for arch '%s'" % arch)
345fc3fe1c2SSimon Glass        return self.toolchains[arch]
3464281ad8eSSimon Glass
3474281ad8eSSimon Glass    def ResolveReferences(self, var_dict, args):
3484281ad8eSSimon Glass        """Resolve variable references in a string
3494281ad8eSSimon Glass
3504281ad8eSSimon Glass        This converts ${blah} within the string to the value of blah.
3514281ad8eSSimon Glass        This function works recursively.
3524281ad8eSSimon Glass
3534281ad8eSSimon Glass        Args:
3544281ad8eSSimon Glass            var_dict: Dictionary containing variables and their values
3554281ad8eSSimon Glass            args: String containing make arguments
3564281ad8eSSimon Glass        Returns:
3574281ad8eSSimon Glass            Resolved string
3584281ad8eSSimon Glass
3594281ad8eSSimon Glass        >>> bsettings.Setup()
3604281ad8eSSimon Glass        >>> tcs = Toolchains()
3614281ad8eSSimon Glass        >>> tcs.Add('fred', False)
3624281ad8eSSimon Glass        >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
3634281ad8eSSimon Glass                        'second' : '2nd'}
3644281ad8eSSimon Glass        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
3654281ad8eSSimon Glass        'this=OBLIQUE_set'
3664281ad8eSSimon Glass        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
3674281ad8eSSimon Glass        'this=OBLIQUE_setfi2ndrstnd'
3684281ad8eSSimon Glass        """
369f60c9d4fSSimon Glass        re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
3704281ad8eSSimon Glass
3714281ad8eSSimon Glass        while True:
3724281ad8eSSimon Glass            m = re_var.search(args)
3734281ad8eSSimon Glass            if not m:
3744281ad8eSSimon Glass                break
3754281ad8eSSimon Glass            lookup = m.group(0)[2:-1]
3764281ad8eSSimon Glass            value = var_dict.get(lookup, '')
3774281ad8eSSimon Glass            args = args[:m.start(0)] + value + args[m.end(0):]
3784281ad8eSSimon Glass        return args
3794281ad8eSSimon Glass
3804281ad8eSSimon Glass    def GetMakeArguments(self, board):
3814281ad8eSSimon Glass        """Returns 'make' arguments for a given board
3824281ad8eSSimon Glass
3834281ad8eSSimon Glass        The flags are in a section called 'make-flags'. Flags are named
3844281ad8eSSimon Glass        after the target they represent, for example snapper9260=TESTING=1
3854281ad8eSSimon Glass        will pass TESTING=1 to make when building the snapper9260 board.
3864281ad8eSSimon Glass
3874281ad8eSSimon Glass        References to other boards can be added in the string also. For
3884281ad8eSSimon Glass        example:
3894281ad8eSSimon Glass
3904281ad8eSSimon Glass        [make-flags]
3914281ad8eSSimon Glass        at91-boards=ENABLE_AT91_TEST=1
3924281ad8eSSimon Glass        snapper9260=${at91-boards} BUILD_TAG=442
3934281ad8eSSimon Glass        snapper9g45=${at91-boards} BUILD_TAG=443
3944281ad8eSSimon Glass
3954281ad8eSSimon Glass        This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
3964281ad8eSSimon Glass        and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
3974281ad8eSSimon Glass
3984281ad8eSSimon Glass        A special 'target' variable is set to the board target.
3994281ad8eSSimon Glass
4004281ad8eSSimon Glass        Args:
4014281ad8eSSimon Glass            board: Board object for the board to check.
4024281ad8eSSimon Glass        Returns:
4034281ad8eSSimon Glass            'make' flags for that board, or '' if none
4044281ad8eSSimon Glass        """
4054281ad8eSSimon Glass        self._make_flags['target'] = board.target
4064281ad8eSSimon Glass        arg_str = self.ResolveReferences(self._make_flags,
4074281ad8eSSimon Glass                           self._make_flags.get(board.target, ''))
4084281ad8eSSimon Glass        args = arg_str.split(' ')
4094281ad8eSSimon Glass        i = 0
4104281ad8eSSimon Glass        while i < len(args):
4114281ad8eSSimon Glass            if not args[i]:
4124281ad8eSSimon Glass                del args[i]
4134281ad8eSSimon Glass            else:
4144281ad8eSSimon Glass                i += 1
4154281ad8eSSimon Glass        return args
416827e37b5SSimon Glass
417827e37b5SSimon Glass    def LocateArchUrl(self, fetch_arch):
418827e37b5SSimon Glass        """Find a toolchain available online
419827e37b5SSimon Glass
420827e37b5SSimon Glass        Look in standard places for available toolchains. At present the
421827e37b5SSimon Glass        only standard place is at kernel.org.
422827e37b5SSimon Glass
423827e37b5SSimon Glass        Args:
424827e37b5SSimon Glass            arch: Architecture to look for, or 'list' for all
425827e37b5SSimon Glass        Returns:
426827e37b5SSimon Glass            If fetch_arch is 'list', a tuple:
427827e37b5SSimon Glass                Machine architecture (e.g. x86_64)
428827e37b5SSimon Glass                List of toolchains
429827e37b5SSimon Glass            else
430827e37b5SSimon Glass                URL containing this toolchain, if avaialble, else None
431827e37b5SSimon Glass        """
432827e37b5SSimon Glass        arch = command.OutputOneLine('uname', '-m')
433827e37b5SSimon Glass        base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
4341246231cSMichal Simek        versions = ['4.9.0', '4.6.3', '4.6.2', '4.5.1', '4.2.4']
435827e37b5SSimon Glass        links = []
436827e37b5SSimon Glass        for version in versions:
437827e37b5SSimon Glass            url = '%s/%s/%s/' % (base, arch, version)
438827e37b5SSimon Glass            print 'Checking: %s' % url
439827e37b5SSimon Glass            response = urllib2.urlopen(url)
440827e37b5SSimon Glass            html = response.read()
441827e37b5SSimon Glass            parser = MyHTMLParser(fetch_arch)
442827e37b5SSimon Glass            parser.feed(html)
443827e37b5SSimon Glass            if fetch_arch == 'list':
444827e37b5SSimon Glass                links += parser.links
445827e37b5SSimon Glass            elif parser.arch_link:
446827e37b5SSimon Glass                return url + parser.arch_link
447827e37b5SSimon Glass        if fetch_arch == 'list':
448827e37b5SSimon Glass            return arch, links
449827e37b5SSimon Glass        return None
450827e37b5SSimon Glass
451827e37b5SSimon Glass    def Download(self, url):
452827e37b5SSimon Glass        """Download a file to a temporary directory
453827e37b5SSimon Glass
454827e37b5SSimon Glass        Args:
455827e37b5SSimon Glass            url: URL to download
456827e37b5SSimon Glass        Returns:
457827e37b5SSimon Glass            Tuple:
458827e37b5SSimon Glass                Temporary directory name
459827e37b5SSimon Glass                Full path to the downloaded archive file in that directory,
460827e37b5SSimon Glass                    or None if there was an error while downloading
461827e37b5SSimon Glass        """
462ad24ebacSSimon Glass        print 'Downloading: %s' % url
463827e37b5SSimon Glass        leaf = url.split('/')[-1]
464827e37b5SSimon Glass        tmpdir = tempfile.mkdtemp('.buildman')
465827e37b5SSimon Glass        response = urllib2.urlopen(url)
466827e37b5SSimon Glass        fname = os.path.join(tmpdir, leaf)
467827e37b5SSimon Glass        fd = open(fname, 'wb')
468827e37b5SSimon Glass        meta = response.info()
469ad24ebacSSimon Glass        size = int(meta.getheaders('Content-Length')[0])
470827e37b5SSimon Glass        done = 0
471827e37b5SSimon Glass        block_size = 1 << 16
472827e37b5SSimon Glass        status = ''
473827e37b5SSimon Glass
474827e37b5SSimon Glass        # Read the file in chunks and show progress as we go
475827e37b5SSimon Glass        while True:
476827e37b5SSimon Glass            buffer = response.read(block_size)
477827e37b5SSimon Glass            if not buffer:
478827e37b5SSimon Glass                print chr(8) * (len(status) + 1), '\r',
479827e37b5SSimon Glass                break
480827e37b5SSimon Glass
481827e37b5SSimon Glass            done += len(buffer)
482827e37b5SSimon Glass            fd.write(buffer)
483ad24ebacSSimon Glass            status = r'%10d MiB  [%3d%%]' % (done / 1024 / 1024,
484827e37b5SSimon Glass                                             done * 100 / size)
485827e37b5SSimon Glass            status = status + chr(8) * (len(status) + 1)
486827e37b5SSimon Glass            print status,
487827e37b5SSimon Glass            sys.stdout.flush()
488827e37b5SSimon Glass        fd.close()
489827e37b5SSimon Glass        if done != size:
490827e37b5SSimon Glass            print 'Error, failed to download'
491827e37b5SSimon Glass            os.remove(fname)
492827e37b5SSimon Glass            fname = None
493827e37b5SSimon Glass        return tmpdir, fname
494827e37b5SSimon Glass
495827e37b5SSimon Glass    def Unpack(self, fname, dest):
496827e37b5SSimon Glass        """Unpack a tar file
497827e37b5SSimon Glass
498827e37b5SSimon Glass        Args:
499827e37b5SSimon Glass            fname: Filename to unpack
500827e37b5SSimon Glass            dest: Destination directory
501827e37b5SSimon Glass        Returns:
502827e37b5SSimon Glass            Directory name of the first entry in the archive, without the
503827e37b5SSimon Glass            trailing /
504827e37b5SSimon Glass        """
505827e37b5SSimon Glass        stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
506827e37b5SSimon Glass        return stdout.splitlines()[0][:-1]
507827e37b5SSimon Glass
508827e37b5SSimon Glass    def TestSettingsHasPath(self, path):
5092289b276SSimon Glass        """Check if buildman will find this toolchain
510827e37b5SSimon Glass
511827e37b5SSimon Glass        Returns:
512827e37b5SSimon Glass            True if the path is in settings, False if not
513827e37b5SSimon Glass        """
51480e6a487SSimon Glass        paths = self.GetPathList(False)
515827e37b5SSimon Glass        return path in paths
516827e37b5SSimon Glass
517827e37b5SSimon Glass    def ListArchs(self):
518827e37b5SSimon Glass        """List architectures with available toolchains to download"""
519827e37b5SSimon Glass        host_arch, archives = self.LocateArchUrl('list')
520827e37b5SSimon Glass        re_arch = re.compile('[-a-z0-9.]*_([^-]*)-.*')
521827e37b5SSimon Glass        arch_set = set()
522827e37b5SSimon Glass        for archive in archives:
523827e37b5SSimon Glass            # Remove the host architecture from the start
524827e37b5SSimon Glass            arch = re_arch.match(archive[len(host_arch):])
525827e37b5SSimon Glass            if arch:
526827e37b5SSimon Glass                arch_set.add(arch.group(1))
527827e37b5SSimon Glass        return sorted(arch_set)
528827e37b5SSimon Glass
529827e37b5SSimon Glass    def FetchAndInstall(self, arch):
530827e37b5SSimon Glass        """Fetch and install a new toolchain
531827e37b5SSimon Glass
532827e37b5SSimon Glass        arch:
533827e37b5SSimon Glass            Architecture to fetch, or 'list' to list
534827e37b5SSimon Glass        """
535827e37b5SSimon Glass        # Fist get the URL for this architecture
536713bea38SSimon Glass        col = terminal.Color()
537713bea38SSimon Glass        print col.Color(col.BLUE, "Downloading toolchain for arch '%s'" % arch)
538827e37b5SSimon Glass        url = self.LocateArchUrl(arch)
539827e37b5SSimon Glass        if not url:
540827e37b5SSimon Glass            print ("Cannot find toolchain for arch '%s' - use 'list' to list" %
541827e37b5SSimon Glass                   arch)
542827e37b5SSimon Glass            return 2
543827e37b5SSimon Glass        home = os.environ['HOME']
544827e37b5SSimon Glass        dest = os.path.join(home, '.buildman-toolchains')
545827e37b5SSimon Glass        if not os.path.exists(dest):
546827e37b5SSimon Glass            os.mkdir(dest)
547827e37b5SSimon Glass
548827e37b5SSimon Glass        # Download the tar file for this toolchain and unpack it
549827e37b5SSimon Glass        tmpdir, tarfile = self.Download(url)
550827e37b5SSimon Glass        if not tarfile:
551827e37b5SSimon Glass            return 1
552713bea38SSimon Glass        print col.Color(col.GREEN, 'Unpacking to: %s' % dest),
553827e37b5SSimon Glass        sys.stdout.flush()
554827e37b5SSimon Glass        path = self.Unpack(tarfile, dest)
555827e37b5SSimon Glass        os.remove(tarfile)
556827e37b5SSimon Glass        os.rmdir(tmpdir)
557827e37b5SSimon Glass        print
558827e37b5SSimon Glass
559827e37b5SSimon Glass        # Check that the toolchain works
560713bea38SSimon Glass        print col.Color(col.GREEN, 'Testing')
561827e37b5SSimon Glass        dirpath = os.path.join(dest, path)
5622a76a649SSimon Glass        compiler_fname_list = self.ScanPath(dirpath, True)
5632a76a649SSimon Glass        if not compiler_fname_list:
564827e37b5SSimon Glass            print 'Could not locate C compiler - fetch failed.'
565827e37b5SSimon Glass            return 1
5662a76a649SSimon Glass        if len(compiler_fname_list) != 1:
567713bea38SSimon Glass            print col.Color(col.RED, 'Warning, ambiguous toolchains: %s' %
568713bea38SSimon Glass                            ', '.join(compiler_fname_list))
5692a76a649SSimon Glass        toolchain = Toolchain(compiler_fname_list[0], True, True)
570827e37b5SSimon Glass
571827e37b5SSimon Glass        # Make sure that it will be found by buildman
572827e37b5SSimon Glass        if not self.TestSettingsHasPath(dirpath):
573827e37b5SSimon Glass            print ("Adding 'download' to config file '%s'" %
574827e37b5SSimon Glass                   bsettings.config_fname)
575c8785c5bSSimon Glass            bsettings.SetItem('toolchain', 'download', '%s/*/*' % dest)
576827e37b5SSimon Glass        return 0
577