xref: /rk3399_rockchip-uboot/tools/buildman/toolchain.py (revision 827e37b55869933951e7e7db3693d4df001fd609)
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
8*827e37b5SSimon Glassfrom HTMLParser import HTMLParser
9fc3fe1c2SSimon Glassimport os
10*827e37b5SSimon Glassimport sys
11*827e37b5SSimon Glassimport tempfile
12*827e37b5SSimon Glassimport urllib2
13fc3fe1c2SSimon Glass
14fc3fe1c2SSimon Glassimport bsettings
15fc3fe1c2SSimon Glassimport command
16fc3fe1c2SSimon Glass
17*827e37b5SSimon Glass# Simple class to collect links from a page
18*827e37b5SSimon Glassclass MyHTMLParser(HTMLParser):
19*827e37b5SSimon Glass    def __init__(self, arch):
20*827e37b5SSimon Glass        """Create a new parser
21*827e37b5SSimon Glass
22*827e37b5SSimon Glass        After the parser runs, self.links will be set to a list of the links
23*827e37b5SSimon Glass        to .xz archives found in the page, and self.arch_link will be set to
24*827e37b5SSimon Glass        the one for the given architecture (or None if not found).
25*827e37b5SSimon Glass
26*827e37b5SSimon Glass        Args:
27*827e37b5SSimon Glass            arch: Architecture to search for
28*827e37b5SSimon Glass        """
29*827e37b5SSimon Glass        HTMLParser.__init__(self)
30*827e37b5SSimon Glass        self.arch_link = None
31*827e37b5SSimon Glass        self.links = []
32*827e37b5SSimon Glass        self._match = '_%s-' % arch
33*827e37b5SSimon Glass
34*827e37b5SSimon Glass    def handle_starttag(self, tag, attrs):
35*827e37b5SSimon Glass        if tag == 'a':
36*827e37b5SSimon Glass            for tag, value in attrs:
37*827e37b5SSimon Glass                if tag == 'href':
38*827e37b5SSimon Glass                    if value and value.endswith('.xz'):
39*827e37b5SSimon Glass                        self.links.append(value)
40*827e37b5SSimon Glass                        if self._match in value:
41*827e37b5SSimon Glass                            self.arch_link = value
42*827e37b5SSimon Glass
43*827e37b5SSimon Glass
44fc3fe1c2SSimon Glassclass Toolchain:
45fc3fe1c2SSimon Glass    """A single toolchain
46fc3fe1c2SSimon Glass
47fc3fe1c2SSimon Glass    Public members:
48fc3fe1c2SSimon Glass        gcc: Full path to C compiler
49fc3fe1c2SSimon Glass        path: Directory path containing C compiler
50fc3fe1c2SSimon Glass        cross: Cross compile string, e.g. 'arm-linux-'
51fc3fe1c2SSimon Glass        arch: Architecture of toolchain as determined from the first
52fc3fe1c2SSimon Glass                component of the filename. E.g. arm-linux-gcc becomes arm
53fc3fe1c2SSimon Glass    """
54fc3fe1c2SSimon Glass    def __init__(self, fname, test, verbose=False):
55fc3fe1c2SSimon Glass        """Create a new toolchain object.
56fc3fe1c2SSimon Glass
57fc3fe1c2SSimon Glass        Args:
58fc3fe1c2SSimon Glass            fname: Filename of the gcc component
59fc3fe1c2SSimon Glass            test: True to run the toolchain to test it
60fc3fe1c2SSimon Glass        """
61fc3fe1c2SSimon Glass        self.gcc = fname
62fc3fe1c2SSimon Glass        self.path = os.path.dirname(fname)
63b5324123SSimon Glass
64b5324123SSimon Glass        # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
65b5324123SSimon Glass        # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
66b5324123SSimon Glass        basename = os.path.basename(fname)
67b5324123SSimon Glass        pos = basename.rfind('-')
68b5324123SSimon Glass        self.cross = basename[:pos + 1] if pos != -1 else ''
69b5324123SSimon Glass
70b5324123SSimon Glass        # The architecture is the first part of the name
71fc3fe1c2SSimon Glass        pos = self.cross.find('-')
72fc3fe1c2SSimon Glass        self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
73fc3fe1c2SSimon Glass
74bb1501f2SSimon Glass        env = self.MakeEnvironment(False)
75fc3fe1c2SSimon Glass
76fc3fe1c2SSimon Glass        # As a basic sanity check, run the C compiler with --version
77fc3fe1c2SSimon Glass        cmd = [fname, '--version']
78fc3fe1c2SSimon Glass        if test:
798bb2bddcSStephen Warren            result = command.RunPipe([cmd], capture=True, env=env,
808bb2bddcSStephen Warren                                     raise_on_error=False)
81fc3fe1c2SSimon Glass            self.ok = result.return_code == 0
82fc3fe1c2SSimon Glass            if verbose:
83fc3fe1c2SSimon Glass                print 'Tool chain test: ',
84fc3fe1c2SSimon Glass                if self.ok:
85fc3fe1c2SSimon Glass                    print 'OK'
86fc3fe1c2SSimon Glass                else:
87fc3fe1c2SSimon Glass                    print 'BAD'
88fc3fe1c2SSimon Glass                    print 'Command: ', cmd
89fc3fe1c2SSimon Glass                    print result.stdout
90fc3fe1c2SSimon Glass                    print result.stderr
91fc3fe1c2SSimon Glass        else:
92fc3fe1c2SSimon Glass            self.ok = True
93fc3fe1c2SSimon Glass        self.priority = self.GetPriority(fname)
94fc3fe1c2SSimon Glass
95fc3fe1c2SSimon Glass    def GetPriority(self, fname):
96fc3fe1c2SSimon Glass        """Return the priority of the toolchain.
97fc3fe1c2SSimon Glass
98fc3fe1c2SSimon Glass        Toolchains are ranked according to their suitability by their
99fc3fe1c2SSimon Glass        filename prefix.
100fc3fe1c2SSimon Glass
101fc3fe1c2SSimon Glass        Args:
102fc3fe1c2SSimon Glass            fname: Filename of toolchain
103fc3fe1c2SSimon Glass        Returns:
104fc3fe1c2SSimon Glass            Priority of toolchain, 0=highest, 20=lowest.
105fc3fe1c2SSimon Glass        """
1068708267fSMasahiro Yamada        priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
107fc3fe1c2SSimon Glass            '-none-linux-gnueabi', '-uclinux', '-none-eabi',
108fc3fe1c2SSimon Glass            '-gentoo-linux-gnu', '-linux-gnueabi', '-le-linux', '-uclinux']
109fc3fe1c2SSimon Glass        for prio in range(len(priority_list)):
110fc3fe1c2SSimon Glass            if priority_list[prio] in fname:
111fc3fe1c2SSimon Glass                return prio
112fc3fe1c2SSimon Glass        return prio
113fc3fe1c2SSimon Glass
114bb1501f2SSimon Glass    def MakeEnvironment(self, full_path):
115fc3fe1c2SSimon Glass        """Returns an environment for using the toolchain.
116fc3fe1c2SSimon Glass
117bb1501f2SSimon Glass        Thie takes the current environment and adds CROSS_COMPILE so that
118bb1501f2SSimon Glass        the tool chain will operate correctly.
119bb1501f2SSimon Glass
120bb1501f2SSimon Glass        Args:
121bb1501f2SSimon Glass            full_path: Return the full path in CROSS_COMPILE and don't set
122bb1501f2SSimon Glass                PATH
123fc3fe1c2SSimon Glass        """
124fc3fe1c2SSimon Glass        env = dict(os.environ)
125bb1501f2SSimon Glass        if full_path:
126bb1501f2SSimon Glass            env['CROSS_COMPILE'] = os.path.join(self.path, self.cross)
127bb1501f2SSimon Glass        else:
128fc3fe1c2SSimon Glass            env['CROSS_COMPILE'] = self.cross
129f210b587SSimon Glass            env['PATH'] = self.path + ':' + env['PATH']
130bb1501f2SSimon Glass
131fc3fe1c2SSimon Glass        return env
132fc3fe1c2SSimon Glass
133fc3fe1c2SSimon Glass
134fc3fe1c2SSimon Glassclass Toolchains:
135fc3fe1c2SSimon Glass    """Manage a list of toolchains for building U-Boot
136fc3fe1c2SSimon Glass
137fc3fe1c2SSimon Glass    We select one toolchain for each architecture type
138fc3fe1c2SSimon Glass
139fc3fe1c2SSimon Glass    Public members:
140fc3fe1c2SSimon Glass        toolchains: Dict of Toolchain objects, keyed by architecture name
141fc3fe1c2SSimon Glass        paths: List of paths to check for toolchains (may contain wildcards)
142fc3fe1c2SSimon Glass    """
143fc3fe1c2SSimon Glass
144fc3fe1c2SSimon Glass    def __init__(self):
145fc3fe1c2SSimon Glass        self.toolchains = {}
146fc3fe1c2SSimon Glass        self.paths = []
147d4144e45SSimon Glass        self._make_flags = dict(bsettings.GetItems('make-flags'))
148d4144e45SSimon Glass
149*827e37b5SSimon Glass    def GetPathList(self):
150*827e37b5SSimon Glass        """Get a list of available toolchain paths
151*827e37b5SSimon Glass
152*827e37b5SSimon Glass        Returns:
153*827e37b5SSimon Glass            List of strings, each a path to a toolchain mentioned in the
154*827e37b5SSimon Glass            [toolchain] section of the settings file.
155*827e37b5SSimon Glass        """
1564281ad8eSSimon Glass        toolchains = bsettings.GetItems('toolchain')
1574281ad8eSSimon Glass        if not toolchains:
1584281ad8eSSimon Glass            print ("Warning: No tool chains - please add a [toolchain] section"
1594281ad8eSSimon Glass                 " to your buildman config file %s. See README for details" %
1601826a18dSMasahiro Yamada                 bsettings.config_fname)
1614281ad8eSSimon Glass
162*827e37b5SSimon Glass        paths = []
1634281ad8eSSimon Glass        for name, value in toolchains:
164fc3fe1c2SSimon Glass            if '*' in value:
165*827e37b5SSimon Glass                paths += glob.glob(value)
166fc3fe1c2SSimon Glass            else:
167*827e37b5SSimon Glass                paths.append(value)
168*827e37b5SSimon Glass        return paths
169*827e37b5SSimon Glass
170*827e37b5SSimon Glass    def GetSettings(self):
171*827e37b5SSimon Glass      self.paths += self.GetPathList()
172fc3fe1c2SSimon Glass
173fc3fe1c2SSimon Glass    def Add(self, fname, test=True, verbose=False):
174fc3fe1c2SSimon Glass        """Add a toolchain to our list
175fc3fe1c2SSimon Glass
176fc3fe1c2SSimon Glass        We select the given toolchain as our preferred one for its
177fc3fe1c2SSimon Glass        architecture if it is a higher priority than the others.
178fc3fe1c2SSimon Glass
179fc3fe1c2SSimon Glass        Args:
180fc3fe1c2SSimon Glass            fname: Filename of toolchain's gcc driver
181fc3fe1c2SSimon Glass            test: True to run the toolchain to test it
182fc3fe1c2SSimon Glass        """
183fc3fe1c2SSimon Glass        toolchain = Toolchain(fname, test, verbose)
184fc3fe1c2SSimon Glass        add_it = toolchain.ok
185fc3fe1c2SSimon Glass        if toolchain.arch in self.toolchains:
186fc3fe1c2SSimon Glass            add_it = (toolchain.priority <
187fc3fe1c2SSimon Glass                        self.toolchains[toolchain.arch].priority)
188fc3fe1c2SSimon Glass        if add_it:
189fc3fe1c2SSimon Glass            self.toolchains[toolchain.arch] = toolchain
190fc3fe1c2SSimon Glass
191*827e37b5SSimon Glass    def ScanPath(self, path, verbose):
192*827e37b5SSimon Glass        """Scan a path for a valid toolchain
193*827e37b5SSimon Glass
194*827e37b5SSimon Glass        Args:
195*827e37b5SSimon Glass            path: Path to scan
196*827e37b5SSimon Glass            verbose: True to print out progress information
197*827e37b5SSimon Glass        Returns:
198*827e37b5SSimon Glass            Filename of C compiler if found, else None
199*827e37b5SSimon Glass        """
200*827e37b5SSimon Glass        for subdir in ['.', 'bin', 'usr/bin']:
201*827e37b5SSimon Glass            dirname = os.path.join(path, subdir)
202*827e37b5SSimon Glass            if verbose: print "      - looking in '%s'" % dirname
203*827e37b5SSimon Glass            for fname in glob.glob(dirname + '/*gcc'):
204*827e37b5SSimon Glass                if verbose: print "         - found '%s'" % fname
205*827e37b5SSimon Glass                return fname
206*827e37b5SSimon Glass        return None
207*827e37b5SSimon Glass
208*827e37b5SSimon Glass
209fc3fe1c2SSimon Glass    def Scan(self, verbose):
210fc3fe1c2SSimon Glass        """Scan for available toolchains and select the best for each arch.
211fc3fe1c2SSimon Glass
212fc3fe1c2SSimon Glass        We look for all the toolchains we can file, figure out the
213fc3fe1c2SSimon Glass        architecture for each, and whether it works. Then we select the
214fc3fe1c2SSimon Glass        highest priority toolchain for each arch.
215fc3fe1c2SSimon Glass
216fc3fe1c2SSimon Glass        Args:
217fc3fe1c2SSimon Glass            verbose: True to print out progress information
218fc3fe1c2SSimon Glass        """
219fc3fe1c2SSimon Glass        if verbose: print 'Scanning for tool chains'
220fc3fe1c2SSimon Glass        for path in self.paths:
221fc3fe1c2SSimon Glass            if verbose: print "   - scanning path '%s'" % path
222*827e37b5SSimon Glass            fname = self.ScanPath(path, verbose)
223*827e37b5SSimon Glass            if fname:
224fc3fe1c2SSimon Glass                self.Add(fname, True, verbose)
225fc3fe1c2SSimon Glass
226fc3fe1c2SSimon Glass    def List(self):
227fc3fe1c2SSimon Glass        """List out the selected toolchains for each architecture"""
228fc3fe1c2SSimon Glass        print 'List of available toolchains (%d):' % len(self.toolchains)
229fc3fe1c2SSimon Glass        if len(self.toolchains):
230fc3fe1c2SSimon Glass            for key, value in sorted(self.toolchains.iteritems()):
231fc3fe1c2SSimon Glass                print '%-10s: %s' % (key, value.gcc)
232fc3fe1c2SSimon Glass        else:
233fc3fe1c2SSimon Glass            print 'None'
234fc3fe1c2SSimon Glass
235fc3fe1c2SSimon Glass    def Select(self, arch):
236fc3fe1c2SSimon Glass        """Returns the toolchain for a given architecture
237fc3fe1c2SSimon Glass
238fc3fe1c2SSimon Glass        Args:
239fc3fe1c2SSimon Glass            args: Name of architecture (e.g. 'arm', 'ppc_8xx')
240fc3fe1c2SSimon Glass
241fc3fe1c2SSimon Glass        returns:
242fc3fe1c2SSimon Glass            toolchain object, or None if none found
243fc3fe1c2SSimon Glass        """
2449b83bfdcSSimon Glass        for tag, value in bsettings.GetItems('toolchain-alias'):
2459b83bfdcSSimon Glass            if arch == tag:
2469b83bfdcSSimon Glass                for alias in value.split():
2479b83bfdcSSimon Glass                    if alias in self.toolchains:
2489b83bfdcSSimon Glass                        return self.toolchains[alias]
249fc3fe1c2SSimon Glass
250fc3fe1c2SSimon Glass        if not arch in self.toolchains:
251fc3fe1c2SSimon Glass            raise ValueError, ("No tool chain found for arch '%s'" % arch)
252fc3fe1c2SSimon Glass        return self.toolchains[arch]
2534281ad8eSSimon Glass
2544281ad8eSSimon Glass    def ResolveReferences(self, var_dict, args):
2554281ad8eSSimon Glass        """Resolve variable references in a string
2564281ad8eSSimon Glass
2574281ad8eSSimon Glass        This converts ${blah} within the string to the value of blah.
2584281ad8eSSimon Glass        This function works recursively.
2594281ad8eSSimon Glass
2604281ad8eSSimon Glass        Args:
2614281ad8eSSimon Glass            var_dict: Dictionary containing variables and their values
2624281ad8eSSimon Glass            args: String containing make arguments
2634281ad8eSSimon Glass        Returns:
2644281ad8eSSimon Glass            Resolved string
2654281ad8eSSimon Glass
2664281ad8eSSimon Glass        >>> bsettings.Setup()
2674281ad8eSSimon Glass        >>> tcs = Toolchains()
2684281ad8eSSimon Glass        >>> tcs.Add('fred', False)
2694281ad8eSSimon Glass        >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
2704281ad8eSSimon Glass                        'second' : '2nd'}
2714281ad8eSSimon Glass        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
2724281ad8eSSimon Glass        'this=OBLIQUE_set'
2734281ad8eSSimon Glass        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
2744281ad8eSSimon Glass        'this=OBLIQUE_setfi2ndrstnd'
2754281ad8eSSimon Glass        """
276f60c9d4fSSimon Glass        re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
2774281ad8eSSimon Glass
2784281ad8eSSimon Glass        while True:
2794281ad8eSSimon Glass            m = re_var.search(args)
2804281ad8eSSimon Glass            if not m:
2814281ad8eSSimon Glass                break
2824281ad8eSSimon Glass            lookup = m.group(0)[2:-1]
2834281ad8eSSimon Glass            value = var_dict.get(lookup, '')
2844281ad8eSSimon Glass            args = args[:m.start(0)] + value + args[m.end(0):]
2854281ad8eSSimon Glass        return args
2864281ad8eSSimon Glass
2874281ad8eSSimon Glass    def GetMakeArguments(self, board):
2884281ad8eSSimon Glass        """Returns 'make' arguments for a given board
2894281ad8eSSimon Glass
2904281ad8eSSimon Glass        The flags are in a section called 'make-flags'. Flags are named
2914281ad8eSSimon Glass        after the target they represent, for example snapper9260=TESTING=1
2924281ad8eSSimon Glass        will pass TESTING=1 to make when building the snapper9260 board.
2934281ad8eSSimon Glass
2944281ad8eSSimon Glass        References to other boards can be added in the string also. For
2954281ad8eSSimon Glass        example:
2964281ad8eSSimon Glass
2974281ad8eSSimon Glass        [make-flags]
2984281ad8eSSimon Glass        at91-boards=ENABLE_AT91_TEST=1
2994281ad8eSSimon Glass        snapper9260=${at91-boards} BUILD_TAG=442
3004281ad8eSSimon Glass        snapper9g45=${at91-boards} BUILD_TAG=443
3014281ad8eSSimon Glass
3024281ad8eSSimon Glass        This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
3034281ad8eSSimon Glass        and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
3044281ad8eSSimon Glass
3054281ad8eSSimon Glass        A special 'target' variable is set to the board target.
3064281ad8eSSimon Glass
3074281ad8eSSimon Glass        Args:
3084281ad8eSSimon Glass            board: Board object for the board to check.
3094281ad8eSSimon Glass        Returns:
3104281ad8eSSimon Glass            'make' flags for that board, or '' if none
3114281ad8eSSimon Glass        """
3124281ad8eSSimon Glass        self._make_flags['target'] = board.target
3134281ad8eSSimon Glass        arg_str = self.ResolveReferences(self._make_flags,
3144281ad8eSSimon Glass                           self._make_flags.get(board.target, ''))
3154281ad8eSSimon Glass        args = arg_str.split(' ')
3164281ad8eSSimon Glass        i = 0
3174281ad8eSSimon Glass        while i < len(args):
3184281ad8eSSimon Glass            if not args[i]:
3194281ad8eSSimon Glass                del args[i]
3204281ad8eSSimon Glass            else:
3214281ad8eSSimon Glass                i += 1
3224281ad8eSSimon Glass        return args
323*827e37b5SSimon Glass
324*827e37b5SSimon Glass    def LocateArchUrl(self, fetch_arch):
325*827e37b5SSimon Glass        """Find a toolchain available online
326*827e37b5SSimon Glass
327*827e37b5SSimon Glass        Look in standard places for available toolchains. At present the
328*827e37b5SSimon Glass        only standard place is at kernel.org.
329*827e37b5SSimon Glass
330*827e37b5SSimon Glass        Args:
331*827e37b5SSimon Glass            arch: Architecture to look for, or 'list' for all
332*827e37b5SSimon Glass        Returns:
333*827e37b5SSimon Glass            If fetch_arch is 'list', a tuple:
334*827e37b5SSimon Glass                Machine architecture (e.g. x86_64)
335*827e37b5SSimon Glass                List of toolchains
336*827e37b5SSimon Glass            else
337*827e37b5SSimon Glass                URL containing this toolchain, if avaialble, else None
338*827e37b5SSimon Glass        """
339*827e37b5SSimon Glass        arch = command.OutputOneLine('uname', '-m')
340*827e37b5SSimon Glass        base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
341*827e37b5SSimon Glass        versions = ['4.6.3', '4.6.2', '4.5.1', '4.2.4']
342*827e37b5SSimon Glass        links = []
343*827e37b5SSimon Glass        for version in versions:
344*827e37b5SSimon Glass            url = '%s/%s/%s/' % (base, arch, version)
345*827e37b5SSimon Glass            print 'Checking: %s' % url
346*827e37b5SSimon Glass            response = urllib2.urlopen(url)
347*827e37b5SSimon Glass            html = response.read()
348*827e37b5SSimon Glass            parser = MyHTMLParser(fetch_arch)
349*827e37b5SSimon Glass            parser.feed(html)
350*827e37b5SSimon Glass            if fetch_arch == 'list':
351*827e37b5SSimon Glass                links += parser.links
352*827e37b5SSimon Glass            elif parser.arch_link:
353*827e37b5SSimon Glass                return url + parser.arch_link
354*827e37b5SSimon Glass        if fetch_arch == 'list':
355*827e37b5SSimon Glass            return arch, links
356*827e37b5SSimon Glass        return None
357*827e37b5SSimon Glass
358*827e37b5SSimon Glass    def Download(self, url):
359*827e37b5SSimon Glass        """Download a file to a temporary directory
360*827e37b5SSimon Glass
361*827e37b5SSimon Glass        Args:
362*827e37b5SSimon Glass            url: URL to download
363*827e37b5SSimon Glass        Returns:
364*827e37b5SSimon Glass            Tuple:
365*827e37b5SSimon Glass                Temporary directory name
366*827e37b5SSimon Glass                Full path to the downloaded archive file in that directory,
367*827e37b5SSimon Glass                    or None if there was an error while downloading
368*827e37b5SSimon Glass        """
369*827e37b5SSimon Glass        print "Downloading: %s" % url
370*827e37b5SSimon Glass        leaf = url.split('/')[-1]
371*827e37b5SSimon Glass        tmpdir = tempfile.mkdtemp('.buildman')
372*827e37b5SSimon Glass        response = urllib2.urlopen(url)
373*827e37b5SSimon Glass        fname = os.path.join(tmpdir, leaf)
374*827e37b5SSimon Glass        fd = open(fname, 'wb')
375*827e37b5SSimon Glass        meta = response.info()
376*827e37b5SSimon Glass        size = int(meta.getheaders("Content-Length")[0])
377*827e37b5SSimon Glass        done = 0
378*827e37b5SSimon Glass        block_size = 1 << 16
379*827e37b5SSimon Glass        status = ''
380*827e37b5SSimon Glass
381*827e37b5SSimon Glass        # Read the file in chunks and show progress as we go
382*827e37b5SSimon Glass        while True:
383*827e37b5SSimon Glass            buffer = response.read(block_size)
384*827e37b5SSimon Glass            if not buffer:
385*827e37b5SSimon Glass                print chr(8) * (len(status) + 1), '\r',
386*827e37b5SSimon Glass                break
387*827e37b5SSimon Glass
388*827e37b5SSimon Glass            done += len(buffer)
389*827e37b5SSimon Glass            fd.write(buffer)
390*827e37b5SSimon Glass            status = r"%10d MiB  [%3d%%]" % (done / 1024 / 1024,
391*827e37b5SSimon Glass                                             done * 100 / size)
392*827e37b5SSimon Glass            status = status + chr(8) * (len(status) + 1)
393*827e37b5SSimon Glass            print status,
394*827e37b5SSimon Glass            sys.stdout.flush()
395*827e37b5SSimon Glass        fd.close()
396*827e37b5SSimon Glass        if done != size:
397*827e37b5SSimon Glass            print 'Error, failed to download'
398*827e37b5SSimon Glass            os.remove(fname)
399*827e37b5SSimon Glass            fname = None
400*827e37b5SSimon Glass        return tmpdir, fname
401*827e37b5SSimon Glass
402*827e37b5SSimon Glass    def Unpack(self, fname, dest):
403*827e37b5SSimon Glass        """Unpack a tar file
404*827e37b5SSimon Glass
405*827e37b5SSimon Glass        Args:
406*827e37b5SSimon Glass            fname: Filename to unpack
407*827e37b5SSimon Glass            dest: Destination directory
408*827e37b5SSimon Glass        Returns:
409*827e37b5SSimon Glass            Directory name of the first entry in the archive, without the
410*827e37b5SSimon Glass            trailing /
411*827e37b5SSimon Glass        """
412*827e37b5SSimon Glass        stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
413*827e37b5SSimon Glass        return stdout.splitlines()[0][:-1]
414*827e37b5SSimon Glass
415*827e37b5SSimon Glass    def TestSettingsHasPath(self, path):
416*827e37b5SSimon Glass        """Check if builmand will find this toolchain
417*827e37b5SSimon Glass
418*827e37b5SSimon Glass        Returns:
419*827e37b5SSimon Glass            True if the path is in settings, False if not
420*827e37b5SSimon Glass        """
421*827e37b5SSimon Glass        paths = self.GetPathList()
422*827e37b5SSimon Glass        return path in paths
423*827e37b5SSimon Glass
424*827e37b5SSimon Glass    def ListArchs(self):
425*827e37b5SSimon Glass        """List architectures with available toolchains to download"""
426*827e37b5SSimon Glass        host_arch, archives = self.LocateArchUrl('list')
427*827e37b5SSimon Glass        re_arch = re.compile('[-a-z0-9.]*_([^-]*)-.*')
428*827e37b5SSimon Glass        arch_set = set()
429*827e37b5SSimon Glass        for archive in archives:
430*827e37b5SSimon Glass            # Remove the host architecture from the start
431*827e37b5SSimon Glass            arch = re_arch.match(archive[len(host_arch):])
432*827e37b5SSimon Glass            if arch:
433*827e37b5SSimon Glass                arch_set.add(arch.group(1))
434*827e37b5SSimon Glass        return sorted(arch_set)
435*827e37b5SSimon Glass
436*827e37b5SSimon Glass    def FetchAndInstall(self, arch):
437*827e37b5SSimon Glass        """Fetch and install a new toolchain
438*827e37b5SSimon Glass
439*827e37b5SSimon Glass        arch:
440*827e37b5SSimon Glass            Architecture to fetch, or 'list' to list
441*827e37b5SSimon Glass        """
442*827e37b5SSimon Glass        # Fist get the URL for this architecture
443*827e37b5SSimon Glass        url = self.LocateArchUrl(arch)
444*827e37b5SSimon Glass        if not url:
445*827e37b5SSimon Glass            print ("Cannot find toolchain for arch '%s' - use 'list' to list" %
446*827e37b5SSimon Glass                   arch)
447*827e37b5SSimon Glass            return 2
448*827e37b5SSimon Glass        home = os.environ['HOME']
449*827e37b5SSimon Glass        dest = os.path.join(home, '.buildman-toolchains')
450*827e37b5SSimon Glass        if not os.path.exists(dest):
451*827e37b5SSimon Glass            os.mkdir(dest)
452*827e37b5SSimon Glass
453*827e37b5SSimon Glass        # Download the tar file for this toolchain and unpack it
454*827e37b5SSimon Glass        tmpdir, tarfile = self.Download(url)
455*827e37b5SSimon Glass        if not tarfile:
456*827e37b5SSimon Glass            return 1
457*827e37b5SSimon Glass        print 'Unpacking to: %s' % dest,
458*827e37b5SSimon Glass        sys.stdout.flush()
459*827e37b5SSimon Glass        path = self.Unpack(tarfile, dest)
460*827e37b5SSimon Glass        os.remove(tarfile)
461*827e37b5SSimon Glass        os.rmdir(tmpdir)
462*827e37b5SSimon Glass        print
463*827e37b5SSimon Glass
464*827e37b5SSimon Glass        # Check that the toolchain works
465*827e37b5SSimon Glass        print 'Testing'
466*827e37b5SSimon Glass        dirpath = os.path.join(dest, path)
467*827e37b5SSimon Glass        compiler_fname = self.ScanPath(dirpath, True)
468*827e37b5SSimon Glass        if not compiler_fname:
469*827e37b5SSimon Glass            print 'Could not locate C compiler - fetch failed.'
470*827e37b5SSimon Glass            return 1
471*827e37b5SSimon Glass        toolchain = Toolchain(compiler_fname, True, True)
472*827e37b5SSimon Glass
473*827e37b5SSimon Glass        # Make sure that it will be found by buildman
474*827e37b5SSimon Glass        if not self.TestSettingsHasPath(dirpath):
475*827e37b5SSimon Glass            print ("Adding 'download' to config file '%s'" %
476*827e37b5SSimon Glass                   bsettings.config_fname)
477*827e37b5SSimon Glass            tools_dir = os.path.dirname(dirpath)
478*827e37b5SSimon Glass            bsettings.SetItem('toolchain', 'download', '%s/*' % tools_dir)
479*827e37b5SSimon Glass        return 0
480