xref: /openbmc/u-boot/tools/buildman/toolchain.py (revision 77c07e7e)
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 = []
354c58d273SDaniel 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)
434c58d273SDaniel 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)
57*00beb248SSimon Glass        override_toolchain: Toolchain to use for sandbox, overriding the normal
58*00beb248SSimon Glass                one
59fc3fe1c2SSimon Glass    """
60608e399fSSimon Glass    def __init__(self, fname, test, verbose=False, priority=PRIORITY_CALC,
61*00beb248SSimon Glass                 arch=None, override_toolchain=None):
62fc3fe1c2SSimon Glass        """Create a new toolchain object.
63fc3fe1c2SSimon Glass
64fc3fe1c2SSimon Glass        Args:
65fc3fe1c2SSimon Glass            fname: Filename of the gcc component
66fc3fe1c2SSimon Glass            test: True to run the toolchain to test it
67ad24ebacSSimon Glass            verbose: True to print out the information
68ff690df9SSimon Glass            priority: Priority to use for this toolchain, or PRIORITY_CALC to
69ff690df9SSimon Glass                calculate it
70fc3fe1c2SSimon Glass        """
71fc3fe1c2SSimon Glass        self.gcc = fname
72fc3fe1c2SSimon Glass        self.path = os.path.dirname(fname)
73*00beb248SSimon Glass        self.override_toolchain = override_toolchain
74b5324123SSimon Glass
75b5324123SSimon Glass        # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
76b5324123SSimon Glass        # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
77b5324123SSimon Glass        basename = os.path.basename(fname)
78b5324123SSimon Glass        pos = basename.rfind('-')
79b5324123SSimon Glass        self.cross = basename[:pos + 1] if pos != -1 else ''
80b5324123SSimon Glass
81b5324123SSimon Glass        # The architecture is the first part of the name
82fc3fe1c2SSimon Glass        pos = self.cross.find('-')
83608e399fSSimon Glass        if arch:
84608e399fSSimon Glass            self.arch = arch
85608e399fSSimon Glass        else:
86fc3fe1c2SSimon Glass            self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
87*00beb248SSimon Glass        if self.arch == 'sandbox' and override_toolchain:
88*00beb248SSimon Glass            self.gcc = override_toolchain
89fc3fe1c2SSimon Glass
90bb1501f2SSimon Glass        env = self.MakeEnvironment(False)
91fc3fe1c2SSimon Glass
92fc3fe1c2SSimon Glass        # As a basic sanity check, run the C compiler with --version
93fc3fe1c2SSimon Glass        cmd = [fname, '--version']
94ff690df9SSimon Glass        if priority == PRIORITY_CALC:
95ff690df9SSimon Glass            self.priority = self.GetPriority(fname)
96ff690df9SSimon Glass        else:
97ff690df9SSimon Glass            self.priority = priority
98fc3fe1c2SSimon Glass        if test:
998bb2bddcSStephen Warren            result = command.RunPipe([cmd], capture=True, env=env,
1008bb2bddcSStephen Warren                                     raise_on_error=False)
101fc3fe1c2SSimon Glass            self.ok = result.return_code == 0
102fc3fe1c2SSimon Glass            if verbose:
103fc3fe1c2SSimon Glass                print 'Tool chain test: ',
104fc3fe1c2SSimon Glass                if self.ok:
105608e399fSSimon Glass                    print "OK, arch='%s', priority %d" % (self.arch,
106608e399fSSimon Glass                                                          self.priority)
107fc3fe1c2SSimon Glass                else:
108fc3fe1c2SSimon Glass                    print 'BAD'
109fc3fe1c2SSimon Glass                    print 'Command: ', cmd
110fc3fe1c2SSimon Glass                    print result.stdout
111fc3fe1c2SSimon Glass                    print result.stderr
112fc3fe1c2SSimon Glass        else:
113fc3fe1c2SSimon Glass            self.ok = True
114fc3fe1c2SSimon Glass
115fc3fe1c2SSimon Glass    def GetPriority(self, fname):
116fc3fe1c2SSimon Glass        """Return the priority of the toolchain.
117fc3fe1c2SSimon Glass
118fc3fe1c2SSimon Glass        Toolchains are ranked according to their suitability by their
119fc3fe1c2SSimon Glass        filename prefix.
120fc3fe1c2SSimon Glass
121fc3fe1c2SSimon Glass        Args:
122fc3fe1c2SSimon Glass            fname: Filename of toolchain
123fc3fe1c2SSimon Glass        Returns:
124ff690df9SSimon Glass            Priority of toolchain, PRIORITY_CALC=highest, 20=lowest.
125fc3fe1c2SSimon Glass        """
1268708267fSMasahiro Yamada        priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
127546a6f3aSTom Rini            '-none-linux-gnueabi', '-none-linux-gnueabihf', '-uclinux',
128546a6f3aSTom Rini            '-none-eabi', '-gentoo-linux-gnu', '-linux-gnueabi',
129546a6f3aSTom Rini            '-linux-gnueabihf', '-le-linux', '-uclinux']
130fc3fe1c2SSimon Glass        for prio in range(len(priority_list)):
131fc3fe1c2SSimon Glass            if priority_list[prio] in fname:
132ff690df9SSimon Glass                return PRIORITY_CALC + prio
133ff690df9SSimon Glass        return PRIORITY_CALC + prio
134fc3fe1c2SSimon Glass
135d5fe013cSYork Sun    def GetWrapper(self, show_warning=True):
136d5fe013cSYork Sun        """Get toolchain wrapper from the setting file.
137d5fe013cSYork Sun        """
138d5fe013cSYork Sun        value = ''
139d5fe013cSYork Sun        for name, value in bsettings.GetItems('toolchain-wrapper'):
140d5fe013cSYork Sun            if not value:
141d5fe013cSYork Sun                print "Warning: Wrapper not found"
142d5fe013cSYork Sun        if value:
143d5fe013cSYork Sun            value = value + ' '
144d5fe013cSYork Sun
145d5fe013cSYork Sun        return value
146d5fe013cSYork Sun
147bb1501f2SSimon Glass    def MakeEnvironment(self, full_path):
148fc3fe1c2SSimon Glass        """Returns an environment for using the toolchain.
149fc3fe1c2SSimon Glass
150bb1501f2SSimon Glass        Thie takes the current environment and adds CROSS_COMPILE so that
151b0e994c2SDaniel Schwierzeck        the tool chain will operate correctly. This also disables localized
152b0e994c2SDaniel Schwierzeck        output and possibly unicode encoded output of all build tools by
153b0e994c2SDaniel Schwierzeck        adding LC_ALL=C.
154bb1501f2SSimon Glass
155bb1501f2SSimon Glass        Args:
156bb1501f2SSimon Glass            full_path: Return the full path in CROSS_COMPILE and don't set
157bb1501f2SSimon Glass                PATH
158*00beb248SSimon Glass        Returns:
159*00beb248SSimon Glass            Dict containing the environemnt to use. This is based on the current
160*00beb248SSimon Glass            environment, with changes as needed to CROSS_COMPILE, PATH and
161*00beb248SSimon Glass            LC_ALL.
162fc3fe1c2SSimon Glass        """
163fc3fe1c2SSimon Glass        env = dict(os.environ)
164d5fe013cSYork Sun        wrapper = self.GetWrapper()
165d5fe013cSYork Sun
166*00beb248SSimon Glass        if self.override_toolchain:
167*00beb248SSimon Glass            # We'll use MakeArgs() to provide this
168*00beb248SSimon Glass            pass
169*00beb248SSimon Glass        elif full_path:
170d5fe013cSYork Sun            env['CROSS_COMPILE'] = wrapper + os.path.join(self.path, self.cross)
171bb1501f2SSimon Glass        else:
172d5fe013cSYork Sun            env['CROSS_COMPILE'] = wrapper + self.cross
173f210b587SSimon Glass            env['PATH'] = self.path + ':' + env['PATH']
174bb1501f2SSimon Glass
175b0e994c2SDaniel Schwierzeck        env['LC_ALL'] = 'C'
176b0e994c2SDaniel Schwierzeck
177fc3fe1c2SSimon Glass        return env
178fc3fe1c2SSimon Glass
179*00beb248SSimon Glass    def MakeArgs(self):
180*00beb248SSimon Glass        """Create the 'make' arguments for a toolchain
181*00beb248SSimon Glass
182*00beb248SSimon Glass        This is only used when the toolchain is being overridden. Since the
183*00beb248SSimon Glass        U-Boot Makefile sets CC and HOSTCC explicitly we cannot rely on the
184*00beb248SSimon Glass        environment (and MakeEnvironment()) to override these values. This
185*00beb248SSimon Glass        function returns the arguments to accomplish this.
186*00beb248SSimon Glass
187*00beb248SSimon Glass        Returns:
188*00beb248SSimon Glass            List of arguments to pass to 'make'
189*00beb248SSimon Glass        """
190*00beb248SSimon Glass        if self.override_toolchain:
191*00beb248SSimon Glass            return ['HOSTCC=%s' % self.override_toolchain,
192*00beb248SSimon Glass                    'CC=%s' % self.override_toolchain]
193*00beb248SSimon Glass        return []
194*00beb248SSimon Glass
195fc3fe1c2SSimon Glass
196fc3fe1c2SSimon Glassclass Toolchains:
197fc3fe1c2SSimon Glass    """Manage a list of toolchains for building U-Boot
198fc3fe1c2SSimon Glass
199fc3fe1c2SSimon Glass    We select one toolchain for each architecture type
200fc3fe1c2SSimon Glass
201fc3fe1c2SSimon Glass    Public members:
202fc3fe1c2SSimon Glass        toolchains: Dict of Toolchain objects, keyed by architecture name
20317bce66cSSimon Glass        prefixes: Dict of prefixes to check, keyed by architecture. This can
20417bce66cSSimon Glass            be a full path and toolchain prefix, for example
20517bce66cSSimon Glass            {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
20617bce66cSSimon Glass            something on the search path, for example
20717bce66cSSimon Glass            {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
208fc3fe1c2SSimon Glass        paths: List of paths to check for toolchains (may contain wildcards)
209fc3fe1c2SSimon Glass    """
210fc3fe1c2SSimon Glass
211*00beb248SSimon Glass    def __init__(self, override_toolchain=None):
212fc3fe1c2SSimon Glass        self.toolchains = {}
21317bce66cSSimon Glass        self.prefixes = {}
214fc3fe1c2SSimon Glass        self.paths = []
215*00beb248SSimon Glass        self.override_toolchain = override_toolchain
216d4144e45SSimon Glass        self._make_flags = dict(bsettings.GetItems('make-flags'))
217d4144e45SSimon Glass
21880e6a487SSimon Glass    def GetPathList(self, show_warning=True):
219827e37b5SSimon Glass        """Get a list of available toolchain paths
220827e37b5SSimon Glass
22180e6a487SSimon Glass        Args:
22280e6a487SSimon Glass            show_warning: True to show a warning if there are no tool chains.
22380e6a487SSimon Glass
224827e37b5SSimon Glass        Returns:
225827e37b5SSimon Glass            List of strings, each a path to a toolchain mentioned in the
226827e37b5SSimon Glass            [toolchain] section of the settings file.
227827e37b5SSimon Glass        """
2284281ad8eSSimon Glass        toolchains = bsettings.GetItems('toolchain')
22980e6a487SSimon Glass        if show_warning and not toolchains:
230713bea38SSimon Glass            print ("Warning: No tool chains. Please run 'buildman "
231713bea38SSimon Glass                   "--fetch-arch all' to download all available toolchains, or "
232713bea38SSimon Glass                   "add a [toolchain] section to your buildman config file "
233713bea38SSimon Glass                   "%s. See README for details" %
2341826a18dSMasahiro Yamada                   bsettings.config_fname)
2354281ad8eSSimon Glass
236827e37b5SSimon Glass        paths = []
2374281ad8eSSimon Glass        for name, value in toolchains:
238fc3fe1c2SSimon Glass            if '*' in value:
239827e37b5SSimon Glass                paths += glob.glob(value)
240fc3fe1c2SSimon Glass            else:
241827e37b5SSimon Glass                paths.append(value)
242827e37b5SSimon Glass        return paths
243827e37b5SSimon Glass
24480e6a487SSimon Glass    def GetSettings(self, show_warning=True):
24580e6a487SSimon Glass        """Get toolchain settings from the settings file.
24680e6a487SSimon Glass
24780e6a487SSimon Glass        Args:
24880e6a487SSimon Glass            show_warning: True to show a warning if there are no tool chains.
24980e6a487SSimon Glass        """
25017bce66cSSimon Glass        self.prefixes = bsettings.GetItems('toolchain-prefix')
25180e6a487SSimon Glass        self.paths += self.GetPathList(show_warning)
252fc3fe1c2SSimon Glass
253608e399fSSimon Glass    def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
254608e399fSSimon Glass            arch=None):
255fc3fe1c2SSimon Glass        """Add a toolchain to our list
256fc3fe1c2SSimon Glass
257fc3fe1c2SSimon Glass        We select the given toolchain as our preferred one for its
258fc3fe1c2SSimon Glass        architecture if it is a higher priority than the others.
259fc3fe1c2SSimon Glass
260fc3fe1c2SSimon Glass        Args:
261fc3fe1c2SSimon Glass            fname: Filename of toolchain's gcc driver
262fc3fe1c2SSimon Glass            test: True to run the toolchain to test it
263ff690df9SSimon Glass            priority: Priority to use for this toolchain
264608e399fSSimon Glass            arch: Toolchain architecture, or None if not known
265fc3fe1c2SSimon Glass        """
266*00beb248SSimon Glass        toolchain = Toolchain(fname, test, verbose, priority, arch,
267*00beb248SSimon Glass                              self.override_toolchain)
268fc3fe1c2SSimon Glass        add_it = toolchain.ok
269fc3fe1c2SSimon Glass        if toolchain.arch in self.toolchains:
270fc3fe1c2SSimon Glass            add_it = (toolchain.priority <
271fc3fe1c2SSimon Glass                        self.toolchains[toolchain.arch].priority)
272fc3fe1c2SSimon Glass        if add_it:
273fc3fe1c2SSimon Glass            self.toolchains[toolchain.arch] = toolchain
274ff690df9SSimon Glass        elif verbose:
275ff690df9SSimon Glass            print ("Toolchain '%s' at priority %d will be ignored because "
276ff690df9SSimon Glass                   "another toolchain for arch '%s' has priority %d" %
277ff690df9SSimon Glass                   (toolchain.gcc, toolchain.priority, toolchain.arch,
278ff690df9SSimon Glass                    self.toolchains[toolchain.arch].priority))
279fc3fe1c2SSimon Glass
280827e37b5SSimon Glass    def ScanPath(self, path, verbose):
281827e37b5SSimon Glass        """Scan a path for a valid toolchain
282827e37b5SSimon Glass
283827e37b5SSimon Glass        Args:
284827e37b5SSimon Glass            path: Path to scan
285827e37b5SSimon Glass            verbose: True to print out progress information
286827e37b5SSimon Glass        Returns:
287827e37b5SSimon Glass            Filename of C compiler if found, else None
288827e37b5SSimon Glass        """
289d9088983SAlbert ARIBAUD        fnames = []
290827e37b5SSimon Glass        for subdir in ['.', 'bin', 'usr/bin']:
291827e37b5SSimon Glass            dirname = os.path.join(path, subdir)
292827e37b5SSimon Glass            if verbose: print "      - looking in '%s'" % dirname
293827e37b5SSimon Glass            for fname in glob.glob(dirname + '/*gcc'):
294827e37b5SSimon Glass                if verbose: print "         - found '%s'" % fname
295d9088983SAlbert ARIBAUD                fnames.append(fname)
296d9088983SAlbert ARIBAUD        return fnames
297827e37b5SSimon Glass
29817bce66cSSimon Glass    def ScanPathEnv(self, fname):
29917bce66cSSimon Glass        """Scan the PATH environment variable for a given filename.
30017bce66cSSimon Glass
30117bce66cSSimon Glass        Args:
30217bce66cSSimon Glass            fname: Filename to scan for
30317bce66cSSimon Glass        Returns:
30417bce66cSSimon Glass            List of matching pathanames, or [] if none
30517bce66cSSimon Glass        """
30617bce66cSSimon Glass        pathname_list = []
30717bce66cSSimon Glass        for path in os.environ["PATH"].split(os.pathsep):
30817bce66cSSimon Glass            path = path.strip('"')
30917bce66cSSimon Glass            pathname = os.path.join(path, fname)
31017bce66cSSimon Glass            if os.path.exists(pathname):
31117bce66cSSimon Glass                pathname_list.append(pathname)
31217bce66cSSimon Glass        return pathname_list
313827e37b5SSimon Glass
314fc3fe1c2SSimon Glass    def Scan(self, verbose):
315fc3fe1c2SSimon Glass        """Scan for available toolchains and select the best for each arch.
316fc3fe1c2SSimon Glass
317fc3fe1c2SSimon Glass        We look for all the toolchains we can file, figure out the
318fc3fe1c2SSimon Glass        architecture for each, and whether it works. Then we select the
319fc3fe1c2SSimon Glass        highest priority toolchain for each arch.
320fc3fe1c2SSimon Glass
321fc3fe1c2SSimon Glass        Args:
322fc3fe1c2SSimon Glass            verbose: True to print out progress information
323fc3fe1c2SSimon Glass        """
324fc3fe1c2SSimon Glass        if verbose: print 'Scanning for tool chains'
32517bce66cSSimon Glass        for name, value in self.prefixes:
32617bce66cSSimon Glass            if verbose: print "   - scanning prefix '%s'" % value
32717bce66cSSimon Glass            if os.path.exists(value):
32817bce66cSSimon Glass                self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
32917bce66cSSimon Glass                continue
33017bce66cSSimon Glass            fname = value + 'gcc'
33117bce66cSSimon Glass            if os.path.exists(fname):
33217bce66cSSimon Glass                self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
33317bce66cSSimon Glass                continue
33417bce66cSSimon Glass            fname_list = self.ScanPathEnv(fname)
33517bce66cSSimon Glass            for f in fname_list:
33617bce66cSSimon Glass                self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
33717bce66cSSimon Glass            if not fname_list:
33817bce66cSSimon Glass                raise ValueError, ("No tool chain found for prefix '%s'" %
33917bce66cSSimon Glass                                   value)
340fc3fe1c2SSimon Glass        for path in self.paths:
341fc3fe1c2SSimon Glass            if verbose: print "   - scanning path '%s'" % path
342d9088983SAlbert ARIBAUD            fnames = self.ScanPath(path, verbose)
343d9088983SAlbert ARIBAUD            for fname in fnames:
344fc3fe1c2SSimon Glass                self.Add(fname, True, verbose)
345fc3fe1c2SSimon Glass
346fc3fe1c2SSimon Glass    def List(self):
347fc3fe1c2SSimon Glass        """List out the selected toolchains for each architecture"""
348713bea38SSimon Glass        col = terminal.Color()
349713bea38SSimon Glass        print col.Color(col.BLUE, 'List of available toolchains (%d):' %
350713bea38SSimon Glass                        len(self.toolchains))
351fc3fe1c2SSimon Glass        if len(self.toolchains):
352fc3fe1c2SSimon Glass            for key, value in sorted(self.toolchains.iteritems()):
353fc3fe1c2SSimon Glass                print '%-10s: %s' % (key, value.gcc)
354fc3fe1c2SSimon Glass        else:
355fc3fe1c2SSimon Glass            print 'None'
356fc3fe1c2SSimon Glass
357fc3fe1c2SSimon Glass    def Select(self, arch):
358fc3fe1c2SSimon Glass        """Returns the toolchain for a given architecture
359fc3fe1c2SSimon Glass
360fc3fe1c2SSimon Glass        Args:
361fc3fe1c2SSimon Glass            args: Name of architecture (e.g. 'arm', 'ppc_8xx')
362fc3fe1c2SSimon Glass
363fc3fe1c2SSimon Glass        returns:
364fc3fe1c2SSimon Glass            toolchain object, or None if none found
365fc3fe1c2SSimon Glass        """
3669b83bfdcSSimon Glass        for tag, value in bsettings.GetItems('toolchain-alias'):
3679b83bfdcSSimon Glass            if arch == tag:
3689b83bfdcSSimon Glass                for alias in value.split():
3699b83bfdcSSimon Glass                    if alias in self.toolchains:
3709b83bfdcSSimon Glass                        return self.toolchains[alias]
371fc3fe1c2SSimon Glass
372fc3fe1c2SSimon Glass        if not arch in self.toolchains:
373fc3fe1c2SSimon Glass            raise ValueError, ("No tool chain found for arch '%s'" % arch)
374fc3fe1c2SSimon Glass        return self.toolchains[arch]
3754281ad8eSSimon Glass
3764281ad8eSSimon Glass    def ResolveReferences(self, var_dict, args):
3774281ad8eSSimon Glass        """Resolve variable references in a string
3784281ad8eSSimon Glass
3794281ad8eSSimon Glass        This converts ${blah} within the string to the value of blah.
3804281ad8eSSimon Glass        This function works recursively.
3814281ad8eSSimon Glass
3824281ad8eSSimon Glass        Args:
3834281ad8eSSimon Glass            var_dict: Dictionary containing variables and their values
3844281ad8eSSimon Glass            args: String containing make arguments
3854281ad8eSSimon Glass        Returns:
3864281ad8eSSimon Glass            Resolved string
3874281ad8eSSimon Glass
3884281ad8eSSimon Glass        >>> bsettings.Setup()
3894281ad8eSSimon Glass        >>> tcs = Toolchains()
3904281ad8eSSimon Glass        >>> tcs.Add('fred', False)
3914281ad8eSSimon Glass        >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
3924281ad8eSSimon Glass                        'second' : '2nd'}
3934281ad8eSSimon Glass        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
3944281ad8eSSimon Glass        'this=OBLIQUE_set'
3954281ad8eSSimon Glass        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
3964281ad8eSSimon Glass        'this=OBLIQUE_setfi2ndrstnd'
3974281ad8eSSimon Glass        """
398f60c9d4fSSimon Glass        re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
3994281ad8eSSimon Glass
4004281ad8eSSimon Glass        while True:
4014281ad8eSSimon Glass            m = re_var.search(args)
4024281ad8eSSimon Glass            if not m:
4034281ad8eSSimon Glass                break
4044281ad8eSSimon Glass            lookup = m.group(0)[2:-1]
4054281ad8eSSimon Glass            value = var_dict.get(lookup, '')
4064281ad8eSSimon Glass            args = args[:m.start(0)] + value + args[m.end(0):]
4074281ad8eSSimon Glass        return args
4084281ad8eSSimon Glass
4094281ad8eSSimon Glass    def GetMakeArguments(self, board):
4104281ad8eSSimon Glass        """Returns 'make' arguments for a given board
4114281ad8eSSimon Glass
4124281ad8eSSimon Glass        The flags are in a section called 'make-flags'. Flags are named
4134281ad8eSSimon Glass        after the target they represent, for example snapper9260=TESTING=1
4144281ad8eSSimon Glass        will pass TESTING=1 to make when building the snapper9260 board.
4154281ad8eSSimon Glass
4164281ad8eSSimon Glass        References to other boards can be added in the string also. For
4174281ad8eSSimon Glass        example:
4184281ad8eSSimon Glass
4194281ad8eSSimon Glass        [make-flags]
4204281ad8eSSimon Glass        at91-boards=ENABLE_AT91_TEST=1
4214281ad8eSSimon Glass        snapper9260=${at91-boards} BUILD_TAG=442
4224281ad8eSSimon Glass        snapper9g45=${at91-boards} BUILD_TAG=443
4234281ad8eSSimon Glass
4244281ad8eSSimon Glass        This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
4254281ad8eSSimon Glass        and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
4264281ad8eSSimon Glass
4274281ad8eSSimon Glass        A special 'target' variable is set to the board target.
4284281ad8eSSimon Glass
4294281ad8eSSimon Glass        Args:
4304281ad8eSSimon Glass            board: Board object for the board to check.
4314281ad8eSSimon Glass        Returns:
4324281ad8eSSimon Glass            'make' flags for that board, or '' if none
4334281ad8eSSimon Glass        """
4344281ad8eSSimon Glass        self._make_flags['target'] = board.target
4354281ad8eSSimon Glass        arg_str = self.ResolveReferences(self._make_flags,
4364281ad8eSSimon Glass                           self._make_flags.get(board.target, ''))
4374281ad8eSSimon Glass        args = arg_str.split(' ')
4384281ad8eSSimon Glass        i = 0
4394281ad8eSSimon Glass        while i < len(args):
4404281ad8eSSimon Glass            if not args[i]:
4414281ad8eSSimon Glass                del args[i]
4424281ad8eSSimon Glass            else:
4434281ad8eSSimon Glass                i += 1
4444281ad8eSSimon Glass        return args
445827e37b5SSimon Glass
446827e37b5SSimon Glass    def LocateArchUrl(self, fetch_arch):
447827e37b5SSimon Glass        """Find a toolchain available online
448827e37b5SSimon Glass
449827e37b5SSimon Glass        Look in standard places for available toolchains. At present the
450827e37b5SSimon Glass        only standard place is at kernel.org.
451827e37b5SSimon Glass
452827e37b5SSimon Glass        Args:
453827e37b5SSimon Glass            arch: Architecture to look for, or 'list' for all
454827e37b5SSimon Glass        Returns:
455827e37b5SSimon Glass            If fetch_arch is 'list', a tuple:
456827e37b5SSimon Glass                Machine architecture (e.g. x86_64)
457827e37b5SSimon Glass                List of toolchains
458827e37b5SSimon Glass            else
459827e37b5SSimon Glass                URL containing this toolchain, if avaialble, else None
460827e37b5SSimon Glass        """
461827e37b5SSimon Glass        arch = command.OutputOneLine('uname', '-m')
462827e37b5SSimon Glass        base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
4634c58d273SDaniel Schwierzeck        versions = ['7.3.0', '6.4.0', '4.9.4']
464827e37b5SSimon Glass        links = []
465827e37b5SSimon Glass        for version in versions:
466827e37b5SSimon Glass            url = '%s/%s/%s/' % (base, arch, version)
467827e37b5SSimon Glass            print 'Checking: %s' % url
468827e37b5SSimon Glass            response = urllib2.urlopen(url)
469827e37b5SSimon Glass            html = response.read()
470827e37b5SSimon Glass            parser = MyHTMLParser(fetch_arch)
471827e37b5SSimon Glass            parser.feed(html)
472827e37b5SSimon Glass            if fetch_arch == 'list':
473827e37b5SSimon Glass                links += parser.links
474827e37b5SSimon Glass            elif parser.arch_link:
475827e37b5SSimon Glass                return url + parser.arch_link
476827e37b5SSimon Glass        if fetch_arch == 'list':
477827e37b5SSimon Glass            return arch, links
478827e37b5SSimon Glass        return None
479827e37b5SSimon Glass
480827e37b5SSimon Glass    def Download(self, url):
481827e37b5SSimon Glass        """Download a file to a temporary directory
482827e37b5SSimon Glass
483827e37b5SSimon Glass        Args:
484827e37b5SSimon Glass            url: URL to download
485827e37b5SSimon Glass        Returns:
486827e37b5SSimon Glass            Tuple:
487827e37b5SSimon Glass                Temporary directory name
488827e37b5SSimon Glass                Full path to the downloaded archive file in that directory,
489827e37b5SSimon Glass                    or None if there was an error while downloading
490827e37b5SSimon Glass        """
491ad24ebacSSimon Glass        print 'Downloading: %s' % url
492827e37b5SSimon Glass        leaf = url.split('/')[-1]
493827e37b5SSimon Glass        tmpdir = tempfile.mkdtemp('.buildman')
494827e37b5SSimon Glass        response = urllib2.urlopen(url)
495827e37b5SSimon Glass        fname = os.path.join(tmpdir, leaf)
496827e37b5SSimon Glass        fd = open(fname, 'wb')
497827e37b5SSimon Glass        meta = response.info()
498ad24ebacSSimon Glass        size = int(meta.getheaders('Content-Length')[0])
499827e37b5SSimon Glass        done = 0
500827e37b5SSimon Glass        block_size = 1 << 16
501827e37b5SSimon Glass        status = ''
502827e37b5SSimon Glass
503827e37b5SSimon Glass        # Read the file in chunks and show progress as we go
504827e37b5SSimon Glass        while True:
505827e37b5SSimon Glass            buffer = response.read(block_size)
506827e37b5SSimon Glass            if not buffer:
507827e37b5SSimon Glass                print chr(8) * (len(status) + 1), '\r',
508827e37b5SSimon Glass                break
509827e37b5SSimon Glass
510827e37b5SSimon Glass            done += len(buffer)
511827e37b5SSimon Glass            fd.write(buffer)
512ad24ebacSSimon Glass            status = r'%10d MiB  [%3d%%]' % (done / 1024 / 1024,
513827e37b5SSimon Glass                                             done * 100 / size)
514827e37b5SSimon Glass            status = status + chr(8) * (len(status) + 1)
515827e37b5SSimon Glass            print status,
516827e37b5SSimon Glass            sys.stdout.flush()
517827e37b5SSimon Glass        fd.close()
518827e37b5SSimon Glass        if done != size:
519827e37b5SSimon Glass            print 'Error, failed to download'
520827e37b5SSimon Glass            os.remove(fname)
521827e37b5SSimon Glass            fname = None
522827e37b5SSimon Glass        return tmpdir, fname
523827e37b5SSimon Glass
524827e37b5SSimon Glass    def Unpack(self, fname, dest):
525827e37b5SSimon Glass        """Unpack a tar file
526827e37b5SSimon Glass
527827e37b5SSimon Glass        Args:
528827e37b5SSimon Glass            fname: Filename to unpack
529827e37b5SSimon Glass            dest: Destination directory
530827e37b5SSimon Glass        Returns:
531827e37b5SSimon Glass            Directory name of the first entry in the archive, without the
532827e37b5SSimon Glass            trailing /
533827e37b5SSimon Glass        """
534827e37b5SSimon Glass        stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
535d82f539aSTrevor Woerner        dirs = stdout.splitlines()[1].split('/')[:2]
536d82f539aSTrevor Woerner        return '/'.join(dirs)
537827e37b5SSimon Glass
538827e37b5SSimon Glass    def TestSettingsHasPath(self, path):
5392289b276SSimon Glass        """Check if buildman will find this toolchain
540827e37b5SSimon Glass
541827e37b5SSimon Glass        Returns:
542827e37b5SSimon Glass            True if the path is in settings, False if not
543827e37b5SSimon Glass        """
54480e6a487SSimon Glass        paths = self.GetPathList(False)
545827e37b5SSimon Glass        return path in paths
546827e37b5SSimon Glass
547827e37b5SSimon Glass    def ListArchs(self):
548827e37b5SSimon Glass        """List architectures with available toolchains to download"""
549827e37b5SSimon Glass        host_arch, archives = self.LocateArchUrl('list')
550b11f1264STrevor Woerner        re_arch = re.compile('[-a-z0-9.]*[-_]([^-]*)-.*')
551827e37b5SSimon Glass        arch_set = set()
552827e37b5SSimon Glass        for archive in archives:
553827e37b5SSimon Glass            # Remove the host architecture from the start
554827e37b5SSimon Glass            arch = re_arch.match(archive[len(host_arch):])
555827e37b5SSimon Glass            if arch:
556b11f1264STrevor Woerner                if arch.group(1) != '2.0' and arch.group(1) != '64':
557827e37b5SSimon Glass                    arch_set.add(arch.group(1))
558827e37b5SSimon Glass        return sorted(arch_set)
559827e37b5SSimon Glass
560827e37b5SSimon Glass    def FetchAndInstall(self, arch):
561827e37b5SSimon Glass        """Fetch and install a new toolchain
562827e37b5SSimon Glass
563827e37b5SSimon Glass        arch:
564827e37b5SSimon Glass            Architecture to fetch, or 'list' to list
565827e37b5SSimon Glass        """
566827e37b5SSimon Glass        # Fist get the URL for this architecture
567713bea38SSimon Glass        col = terminal.Color()
568713bea38SSimon Glass        print col.Color(col.BLUE, "Downloading toolchain for arch '%s'" % arch)
569827e37b5SSimon Glass        url = self.LocateArchUrl(arch)
570827e37b5SSimon Glass        if not url:
571827e37b5SSimon Glass            print ("Cannot find toolchain for arch '%s' - use 'list' to list" %
572827e37b5SSimon Glass                   arch)
573827e37b5SSimon Glass            return 2
574827e37b5SSimon Glass        home = os.environ['HOME']
575827e37b5SSimon Glass        dest = os.path.join(home, '.buildman-toolchains')
576827e37b5SSimon Glass        if not os.path.exists(dest):
577827e37b5SSimon Glass            os.mkdir(dest)
578827e37b5SSimon Glass
579827e37b5SSimon Glass        # Download the tar file for this toolchain and unpack it
580827e37b5SSimon Glass        tmpdir, tarfile = self.Download(url)
581827e37b5SSimon Glass        if not tarfile:
582827e37b5SSimon Glass            return 1
583713bea38SSimon Glass        print col.Color(col.GREEN, 'Unpacking to: %s' % dest),
584827e37b5SSimon Glass        sys.stdout.flush()
585827e37b5SSimon Glass        path = self.Unpack(tarfile, dest)
586827e37b5SSimon Glass        os.remove(tarfile)
587827e37b5SSimon Glass        os.rmdir(tmpdir)
588827e37b5SSimon Glass        print
589827e37b5SSimon Glass
590827e37b5SSimon Glass        # Check that the toolchain works
591713bea38SSimon Glass        print col.Color(col.GREEN, 'Testing')
592827e37b5SSimon Glass        dirpath = os.path.join(dest, path)
5932a76a649SSimon Glass        compiler_fname_list = self.ScanPath(dirpath, True)
5942a76a649SSimon Glass        if not compiler_fname_list:
595827e37b5SSimon Glass            print 'Could not locate C compiler - fetch failed.'
596827e37b5SSimon Glass            return 1
5972a76a649SSimon Glass        if len(compiler_fname_list) != 1:
598713bea38SSimon Glass            print col.Color(col.RED, 'Warning, ambiguous toolchains: %s' %
599713bea38SSimon Glass                            ', '.join(compiler_fname_list))
6002a76a649SSimon Glass        toolchain = Toolchain(compiler_fname_list[0], True, True)
601827e37b5SSimon Glass
602827e37b5SSimon Glass        # Make sure that it will be found by buildman
603827e37b5SSimon Glass        if not self.TestSettingsHasPath(dirpath):
604827e37b5SSimon Glass            print ("Adding 'download' to config file '%s'" %
605827e37b5SSimon Glass                   bsettings.config_fname)
606c8785c5bSSimon Glass            bsettings.SetItem('toolchain', 'download', '%s/*/*' % dest)
607827e37b5SSimon Glass        return 0
608