xref: /OK3568_Linux_fs/u-boot/tools/buildman/toolchain.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# Copyright (c) 2012 The Chromium OS Authors.
2*4882a593Smuzhiyun#
3*4882a593Smuzhiyun# SPDX-License-Identifier:	GPL-2.0+
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyun
6*4882a593Smuzhiyunimport re
7*4882a593Smuzhiyunimport glob
8*4882a593Smuzhiyunfrom HTMLParser import HTMLParser
9*4882a593Smuzhiyunimport os
10*4882a593Smuzhiyunimport sys
11*4882a593Smuzhiyunimport tempfile
12*4882a593Smuzhiyunimport urllib2
13*4882a593Smuzhiyun
14*4882a593Smuzhiyunimport bsettings
15*4882a593Smuzhiyunimport command
16*4882a593Smuzhiyunimport terminal
17*4882a593Smuzhiyun
18*4882a593Smuzhiyun(PRIORITY_FULL_PREFIX, PRIORITY_PREFIX_GCC, PRIORITY_PREFIX_GCC_PATH,
19*4882a593Smuzhiyun    PRIORITY_CALC) = range(4)
20*4882a593Smuzhiyun
21*4882a593Smuzhiyun# Simple class to collect links from a page
22*4882a593Smuzhiyunclass MyHTMLParser(HTMLParser):
23*4882a593Smuzhiyun    def __init__(self, arch):
24*4882a593Smuzhiyun        """Create a new parser
25*4882a593Smuzhiyun
26*4882a593Smuzhiyun        After the parser runs, self.links will be set to a list of the links
27*4882a593Smuzhiyun        to .xz archives found in the page, and self.arch_link will be set to
28*4882a593Smuzhiyun        the one for the given architecture (or None if not found).
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun        Args:
31*4882a593Smuzhiyun            arch: Architecture to search for
32*4882a593Smuzhiyun        """
33*4882a593Smuzhiyun        HTMLParser.__init__(self)
34*4882a593Smuzhiyun        self.arch_link = None
35*4882a593Smuzhiyun        self.links = []
36*4882a593Smuzhiyun        self._match = '_%s-' % arch
37*4882a593Smuzhiyun
38*4882a593Smuzhiyun    def handle_starttag(self, tag, attrs):
39*4882a593Smuzhiyun        if tag == 'a':
40*4882a593Smuzhiyun            for tag, value in attrs:
41*4882a593Smuzhiyun                if tag == 'href':
42*4882a593Smuzhiyun                    if value and value.endswith('.xz'):
43*4882a593Smuzhiyun                        self.links.append(value)
44*4882a593Smuzhiyun                        if self._match in value:
45*4882a593Smuzhiyun                            self.arch_link = value
46*4882a593Smuzhiyun
47*4882a593Smuzhiyun
48*4882a593Smuzhiyunclass Toolchain:
49*4882a593Smuzhiyun    """A single toolchain
50*4882a593Smuzhiyun
51*4882a593Smuzhiyun    Public members:
52*4882a593Smuzhiyun        gcc: Full path to C compiler
53*4882a593Smuzhiyun        path: Directory path containing C compiler
54*4882a593Smuzhiyun        cross: Cross compile string, e.g. 'arm-linux-'
55*4882a593Smuzhiyun        arch: Architecture of toolchain as determined from the first
56*4882a593Smuzhiyun                component of the filename. E.g. arm-linux-gcc becomes arm
57*4882a593Smuzhiyun        priority: Toolchain priority (0=highest, 20=lowest)
58*4882a593Smuzhiyun    """
59*4882a593Smuzhiyun    def __init__(self, fname, test, verbose=False, priority=PRIORITY_CALC,
60*4882a593Smuzhiyun                 arch=None):
61*4882a593Smuzhiyun        """Create a new toolchain object.
62*4882a593Smuzhiyun
63*4882a593Smuzhiyun        Args:
64*4882a593Smuzhiyun            fname: Filename of the gcc component
65*4882a593Smuzhiyun            test: True to run the toolchain to test it
66*4882a593Smuzhiyun            verbose: True to print out the information
67*4882a593Smuzhiyun            priority: Priority to use for this toolchain, or PRIORITY_CALC to
68*4882a593Smuzhiyun                calculate it
69*4882a593Smuzhiyun        """
70*4882a593Smuzhiyun        self.gcc = fname
71*4882a593Smuzhiyun        self.path = os.path.dirname(fname)
72*4882a593Smuzhiyun
73*4882a593Smuzhiyun        # Find the CROSS_COMPILE prefix to use for U-Boot. For example,
74*4882a593Smuzhiyun        # 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
75*4882a593Smuzhiyun        basename = os.path.basename(fname)
76*4882a593Smuzhiyun        pos = basename.rfind('-')
77*4882a593Smuzhiyun        self.cross = basename[:pos + 1] if pos != -1 else ''
78*4882a593Smuzhiyun
79*4882a593Smuzhiyun        # The architecture is the first part of the name
80*4882a593Smuzhiyun        pos = self.cross.find('-')
81*4882a593Smuzhiyun        if arch:
82*4882a593Smuzhiyun            self.arch = arch
83*4882a593Smuzhiyun        else:
84*4882a593Smuzhiyun            self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
85*4882a593Smuzhiyun
86*4882a593Smuzhiyun        env = self.MakeEnvironment(False)
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun        # As a basic sanity check, run the C compiler with --version
89*4882a593Smuzhiyun        cmd = [fname, '--version']
90*4882a593Smuzhiyun        if priority == PRIORITY_CALC:
91*4882a593Smuzhiyun            self.priority = self.GetPriority(fname)
92*4882a593Smuzhiyun        else:
93*4882a593Smuzhiyun            self.priority = priority
94*4882a593Smuzhiyun        if test:
95*4882a593Smuzhiyun            result = command.RunPipe([cmd], capture=True, env=env,
96*4882a593Smuzhiyun                                     raise_on_error=False)
97*4882a593Smuzhiyun            self.ok = result.return_code == 0
98*4882a593Smuzhiyun            if verbose:
99*4882a593Smuzhiyun                print 'Tool chain test: ',
100*4882a593Smuzhiyun                if self.ok:
101*4882a593Smuzhiyun                    print "OK, arch='%s', priority %d" % (self.arch,
102*4882a593Smuzhiyun                                                          self.priority)
103*4882a593Smuzhiyun                else:
104*4882a593Smuzhiyun                    print 'BAD'
105*4882a593Smuzhiyun                    print 'Command: ', cmd
106*4882a593Smuzhiyun                    print result.stdout
107*4882a593Smuzhiyun                    print result.stderr
108*4882a593Smuzhiyun        else:
109*4882a593Smuzhiyun            self.ok = True
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun    def GetPriority(self, fname):
112*4882a593Smuzhiyun        """Return the priority of the toolchain.
113*4882a593Smuzhiyun
114*4882a593Smuzhiyun        Toolchains are ranked according to their suitability by their
115*4882a593Smuzhiyun        filename prefix.
116*4882a593Smuzhiyun
117*4882a593Smuzhiyun        Args:
118*4882a593Smuzhiyun            fname: Filename of toolchain
119*4882a593Smuzhiyun        Returns:
120*4882a593Smuzhiyun            Priority of toolchain, PRIORITY_CALC=highest, 20=lowest.
121*4882a593Smuzhiyun        """
122*4882a593Smuzhiyun        priority_list = ['-elf', '-unknown-linux-gnu', '-linux',
123*4882a593Smuzhiyun            '-none-linux-gnueabi', '-none-linux-gnueabihf', '-uclinux',
124*4882a593Smuzhiyun            '-none-eabi', '-gentoo-linux-gnu', '-linux-gnueabi',
125*4882a593Smuzhiyun            '-linux-gnueabihf', '-le-linux', '-uclinux']
126*4882a593Smuzhiyun        for prio in range(len(priority_list)):
127*4882a593Smuzhiyun            if priority_list[prio] in fname:
128*4882a593Smuzhiyun                return PRIORITY_CALC + prio
129*4882a593Smuzhiyun        return PRIORITY_CALC + prio
130*4882a593Smuzhiyun
131*4882a593Smuzhiyun    def GetWrapper(self, show_warning=True):
132*4882a593Smuzhiyun        """Get toolchain wrapper from the setting file.
133*4882a593Smuzhiyun        """
134*4882a593Smuzhiyun	value = ''
135*4882a593Smuzhiyun	for name, value in bsettings.GetItems('toolchain-wrapper'):
136*4882a593Smuzhiyun            if not value:
137*4882a593Smuzhiyun                print "Warning: Wrapper not found"
138*4882a593Smuzhiyun        if value:
139*4882a593Smuzhiyun            value = value + ' '
140*4882a593Smuzhiyun
141*4882a593Smuzhiyun        return value
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun    def MakeEnvironment(self, full_path):
144*4882a593Smuzhiyun        """Returns an environment for using the toolchain.
145*4882a593Smuzhiyun
146*4882a593Smuzhiyun        Thie takes the current environment and adds CROSS_COMPILE so that
147*4882a593Smuzhiyun        the tool chain will operate correctly. This also disables localized
148*4882a593Smuzhiyun        output and possibly unicode encoded output of all build tools by
149*4882a593Smuzhiyun        adding LC_ALL=C.
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun        Args:
152*4882a593Smuzhiyun            full_path: Return the full path in CROSS_COMPILE and don't set
153*4882a593Smuzhiyun                PATH
154*4882a593Smuzhiyun        """
155*4882a593Smuzhiyun        env = dict(os.environ)
156*4882a593Smuzhiyun        wrapper = self.GetWrapper()
157*4882a593Smuzhiyun
158*4882a593Smuzhiyun        if full_path:
159*4882a593Smuzhiyun            env['CROSS_COMPILE'] = wrapper + os.path.join(self.path, self.cross)
160*4882a593Smuzhiyun        else:
161*4882a593Smuzhiyun            env['CROSS_COMPILE'] = wrapper + self.cross
162*4882a593Smuzhiyun            env['PATH'] = self.path + ':' + env['PATH']
163*4882a593Smuzhiyun
164*4882a593Smuzhiyun        env['LC_ALL'] = 'C'
165*4882a593Smuzhiyun
166*4882a593Smuzhiyun        return env
167*4882a593Smuzhiyun
168*4882a593Smuzhiyun
169*4882a593Smuzhiyunclass Toolchains:
170*4882a593Smuzhiyun    """Manage a list of toolchains for building U-Boot
171*4882a593Smuzhiyun
172*4882a593Smuzhiyun    We select one toolchain for each architecture type
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun    Public members:
175*4882a593Smuzhiyun        toolchains: Dict of Toolchain objects, keyed by architecture name
176*4882a593Smuzhiyun        prefixes: Dict of prefixes to check, keyed by architecture. This can
177*4882a593Smuzhiyun            be a full path and toolchain prefix, for example
178*4882a593Smuzhiyun            {'x86', 'opt/i386-linux/bin/i386-linux-'}, or the name of
179*4882a593Smuzhiyun            something on the search path, for example
180*4882a593Smuzhiyun            {'arm', 'arm-linux-gnueabihf-'}. Wildcards are not supported.
181*4882a593Smuzhiyun        paths: List of paths to check for toolchains (may contain wildcards)
182*4882a593Smuzhiyun    """
183*4882a593Smuzhiyun
184*4882a593Smuzhiyun    def __init__(self):
185*4882a593Smuzhiyun        self.toolchains = {}
186*4882a593Smuzhiyun        self.prefixes = {}
187*4882a593Smuzhiyun        self.paths = []
188*4882a593Smuzhiyun        self._make_flags = dict(bsettings.GetItems('make-flags'))
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun    def GetPathList(self, show_warning=True):
191*4882a593Smuzhiyun        """Get a list of available toolchain paths
192*4882a593Smuzhiyun
193*4882a593Smuzhiyun        Args:
194*4882a593Smuzhiyun            show_warning: True to show a warning if there are no tool chains.
195*4882a593Smuzhiyun
196*4882a593Smuzhiyun        Returns:
197*4882a593Smuzhiyun            List of strings, each a path to a toolchain mentioned in the
198*4882a593Smuzhiyun            [toolchain] section of the settings file.
199*4882a593Smuzhiyun        """
200*4882a593Smuzhiyun        toolchains = bsettings.GetItems('toolchain')
201*4882a593Smuzhiyun        if show_warning and not toolchains:
202*4882a593Smuzhiyun            print ("Warning: No tool chains. Please run 'buildman "
203*4882a593Smuzhiyun                   "--fetch-arch all' to download all available toolchains, or "
204*4882a593Smuzhiyun                   "add a [toolchain] section to your buildman config file "
205*4882a593Smuzhiyun                   "%s. See README for details" %
206*4882a593Smuzhiyun                   bsettings.config_fname)
207*4882a593Smuzhiyun
208*4882a593Smuzhiyun        paths = []
209*4882a593Smuzhiyun        for name, value in toolchains:
210*4882a593Smuzhiyun            if '*' in value:
211*4882a593Smuzhiyun                paths += glob.glob(value)
212*4882a593Smuzhiyun            else:
213*4882a593Smuzhiyun                paths.append(value)
214*4882a593Smuzhiyun        return paths
215*4882a593Smuzhiyun
216*4882a593Smuzhiyun    def GetSettings(self, show_warning=True):
217*4882a593Smuzhiyun        """Get toolchain settings from the settings file.
218*4882a593Smuzhiyun
219*4882a593Smuzhiyun        Args:
220*4882a593Smuzhiyun            show_warning: True to show a warning if there are no tool chains.
221*4882a593Smuzhiyun        """
222*4882a593Smuzhiyun        self.prefixes = bsettings.GetItems('toolchain-prefix')
223*4882a593Smuzhiyun        self.paths += self.GetPathList(show_warning)
224*4882a593Smuzhiyun
225*4882a593Smuzhiyun    def Add(self, fname, test=True, verbose=False, priority=PRIORITY_CALC,
226*4882a593Smuzhiyun            arch=None):
227*4882a593Smuzhiyun        """Add a toolchain to our list
228*4882a593Smuzhiyun
229*4882a593Smuzhiyun        We select the given toolchain as our preferred one for its
230*4882a593Smuzhiyun        architecture if it is a higher priority than the others.
231*4882a593Smuzhiyun
232*4882a593Smuzhiyun        Args:
233*4882a593Smuzhiyun            fname: Filename of toolchain's gcc driver
234*4882a593Smuzhiyun            test: True to run the toolchain to test it
235*4882a593Smuzhiyun            priority: Priority to use for this toolchain
236*4882a593Smuzhiyun            arch: Toolchain architecture, or None if not known
237*4882a593Smuzhiyun        """
238*4882a593Smuzhiyun        toolchain = Toolchain(fname, test, verbose, priority, arch)
239*4882a593Smuzhiyun        add_it = toolchain.ok
240*4882a593Smuzhiyun        if toolchain.arch in self.toolchains:
241*4882a593Smuzhiyun            add_it = (toolchain.priority <
242*4882a593Smuzhiyun                        self.toolchains[toolchain.arch].priority)
243*4882a593Smuzhiyun        if add_it:
244*4882a593Smuzhiyun            self.toolchains[toolchain.arch] = toolchain
245*4882a593Smuzhiyun        elif verbose:
246*4882a593Smuzhiyun            print ("Toolchain '%s' at priority %d will be ignored because "
247*4882a593Smuzhiyun                   "another toolchain for arch '%s' has priority %d" %
248*4882a593Smuzhiyun                   (toolchain.gcc, toolchain.priority, toolchain.arch,
249*4882a593Smuzhiyun                    self.toolchains[toolchain.arch].priority))
250*4882a593Smuzhiyun
251*4882a593Smuzhiyun    def ScanPath(self, path, verbose):
252*4882a593Smuzhiyun        """Scan a path for a valid toolchain
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun        Args:
255*4882a593Smuzhiyun            path: Path to scan
256*4882a593Smuzhiyun            verbose: True to print out progress information
257*4882a593Smuzhiyun        Returns:
258*4882a593Smuzhiyun            Filename of C compiler if found, else None
259*4882a593Smuzhiyun        """
260*4882a593Smuzhiyun        fnames = []
261*4882a593Smuzhiyun        for subdir in ['.', 'bin', 'usr/bin']:
262*4882a593Smuzhiyun            dirname = os.path.join(path, subdir)
263*4882a593Smuzhiyun            if verbose: print "      - looking in '%s'" % dirname
264*4882a593Smuzhiyun            for fname in glob.glob(dirname + '/*gcc'):
265*4882a593Smuzhiyun                if verbose: print "         - found '%s'" % fname
266*4882a593Smuzhiyun                fnames.append(fname)
267*4882a593Smuzhiyun        return fnames
268*4882a593Smuzhiyun
269*4882a593Smuzhiyun    def ScanPathEnv(self, fname):
270*4882a593Smuzhiyun        """Scan the PATH environment variable for a given filename.
271*4882a593Smuzhiyun
272*4882a593Smuzhiyun        Args:
273*4882a593Smuzhiyun            fname: Filename to scan for
274*4882a593Smuzhiyun        Returns:
275*4882a593Smuzhiyun            List of matching pathanames, or [] if none
276*4882a593Smuzhiyun        """
277*4882a593Smuzhiyun        pathname_list = []
278*4882a593Smuzhiyun        for path in os.environ["PATH"].split(os.pathsep):
279*4882a593Smuzhiyun            path = path.strip('"')
280*4882a593Smuzhiyun            pathname = os.path.join(path, fname)
281*4882a593Smuzhiyun            if os.path.exists(pathname):
282*4882a593Smuzhiyun                pathname_list.append(pathname)
283*4882a593Smuzhiyun        return pathname_list
284*4882a593Smuzhiyun
285*4882a593Smuzhiyun    def Scan(self, verbose):
286*4882a593Smuzhiyun        """Scan for available toolchains and select the best for each arch.
287*4882a593Smuzhiyun
288*4882a593Smuzhiyun        We look for all the toolchains we can file, figure out the
289*4882a593Smuzhiyun        architecture for each, and whether it works. Then we select the
290*4882a593Smuzhiyun        highest priority toolchain for each arch.
291*4882a593Smuzhiyun
292*4882a593Smuzhiyun        Args:
293*4882a593Smuzhiyun            verbose: True to print out progress information
294*4882a593Smuzhiyun        """
295*4882a593Smuzhiyun        if verbose: print 'Scanning for tool chains'
296*4882a593Smuzhiyun        for name, value in self.prefixes:
297*4882a593Smuzhiyun            if verbose: print "   - scanning prefix '%s'" % value
298*4882a593Smuzhiyun            if os.path.exists(value):
299*4882a593Smuzhiyun                self.Add(value, True, verbose, PRIORITY_FULL_PREFIX, name)
300*4882a593Smuzhiyun                continue
301*4882a593Smuzhiyun            fname = value + 'gcc'
302*4882a593Smuzhiyun            if os.path.exists(fname):
303*4882a593Smuzhiyun                self.Add(fname, True, verbose, PRIORITY_PREFIX_GCC, name)
304*4882a593Smuzhiyun                continue
305*4882a593Smuzhiyun            fname_list = self.ScanPathEnv(fname)
306*4882a593Smuzhiyun            for f in fname_list:
307*4882a593Smuzhiyun                self.Add(f, True, verbose, PRIORITY_PREFIX_GCC_PATH, name)
308*4882a593Smuzhiyun            if not fname_list:
309*4882a593Smuzhiyun                raise ValueError, ("No tool chain found for prefix '%s'" %
310*4882a593Smuzhiyun                                   value)
311*4882a593Smuzhiyun        for path in self.paths:
312*4882a593Smuzhiyun            if verbose: print "   - scanning path '%s'" % path
313*4882a593Smuzhiyun            fnames = self.ScanPath(path, verbose)
314*4882a593Smuzhiyun            for fname in fnames:
315*4882a593Smuzhiyun                self.Add(fname, True, verbose)
316*4882a593Smuzhiyun
317*4882a593Smuzhiyun    def List(self):
318*4882a593Smuzhiyun        """List out the selected toolchains for each architecture"""
319*4882a593Smuzhiyun        col = terminal.Color()
320*4882a593Smuzhiyun        print col.Color(col.BLUE, 'List of available toolchains (%d):' %
321*4882a593Smuzhiyun                        len(self.toolchains))
322*4882a593Smuzhiyun        if len(self.toolchains):
323*4882a593Smuzhiyun            for key, value in sorted(self.toolchains.iteritems()):
324*4882a593Smuzhiyun                print '%-10s: %s' % (key, value.gcc)
325*4882a593Smuzhiyun        else:
326*4882a593Smuzhiyun            print 'None'
327*4882a593Smuzhiyun
328*4882a593Smuzhiyun    def Select(self, arch):
329*4882a593Smuzhiyun        """Returns the toolchain for a given architecture
330*4882a593Smuzhiyun
331*4882a593Smuzhiyun        Args:
332*4882a593Smuzhiyun            args: Name of architecture (e.g. 'arm', 'ppc_8xx')
333*4882a593Smuzhiyun
334*4882a593Smuzhiyun        returns:
335*4882a593Smuzhiyun            toolchain object, or None if none found
336*4882a593Smuzhiyun        """
337*4882a593Smuzhiyun        for tag, value in bsettings.GetItems('toolchain-alias'):
338*4882a593Smuzhiyun            if arch == tag:
339*4882a593Smuzhiyun                for alias in value.split():
340*4882a593Smuzhiyun                    if alias in self.toolchains:
341*4882a593Smuzhiyun                        return self.toolchains[alias]
342*4882a593Smuzhiyun
343*4882a593Smuzhiyun        if not arch in self.toolchains:
344*4882a593Smuzhiyun            raise ValueError, ("No tool chain found for arch '%s'" % arch)
345*4882a593Smuzhiyun        return self.toolchains[arch]
346*4882a593Smuzhiyun
347*4882a593Smuzhiyun    def ResolveReferences(self, var_dict, args):
348*4882a593Smuzhiyun        """Resolve variable references in a string
349*4882a593Smuzhiyun
350*4882a593Smuzhiyun        This converts ${blah} within the string to the value of blah.
351*4882a593Smuzhiyun        This function works recursively.
352*4882a593Smuzhiyun
353*4882a593Smuzhiyun        Args:
354*4882a593Smuzhiyun            var_dict: Dictionary containing variables and their values
355*4882a593Smuzhiyun            args: String containing make arguments
356*4882a593Smuzhiyun        Returns:
357*4882a593Smuzhiyun            Resolved string
358*4882a593Smuzhiyun
359*4882a593Smuzhiyun        >>> bsettings.Setup()
360*4882a593Smuzhiyun        >>> tcs = Toolchains()
361*4882a593Smuzhiyun        >>> tcs.Add('fred', False)
362*4882a593Smuzhiyun        >>> var_dict = {'oblique' : 'OBLIQUE', 'first' : 'fi${second}rst', \
363*4882a593Smuzhiyun                        'second' : '2nd'}
364*4882a593Smuzhiyun        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set')
365*4882a593Smuzhiyun        'this=OBLIQUE_set'
366*4882a593Smuzhiyun        >>> tcs.ResolveReferences(var_dict, 'this=${oblique}_set${first}nd')
367*4882a593Smuzhiyun        'this=OBLIQUE_setfi2ndrstnd'
368*4882a593Smuzhiyun        """
369*4882a593Smuzhiyun        re_var = re.compile('(\$\{[-_a-z0-9A-Z]{1,}\})')
370*4882a593Smuzhiyun
371*4882a593Smuzhiyun        while True:
372*4882a593Smuzhiyun            m = re_var.search(args)
373*4882a593Smuzhiyun            if not m:
374*4882a593Smuzhiyun                break
375*4882a593Smuzhiyun            lookup = m.group(0)[2:-1]
376*4882a593Smuzhiyun            value = var_dict.get(lookup, '')
377*4882a593Smuzhiyun            args = args[:m.start(0)] + value + args[m.end(0):]
378*4882a593Smuzhiyun        return args
379*4882a593Smuzhiyun
380*4882a593Smuzhiyun    def GetMakeArguments(self, board):
381*4882a593Smuzhiyun        """Returns 'make' arguments for a given board
382*4882a593Smuzhiyun
383*4882a593Smuzhiyun        The flags are in a section called 'make-flags'. Flags are named
384*4882a593Smuzhiyun        after the target they represent, for example snapper9260=TESTING=1
385*4882a593Smuzhiyun        will pass TESTING=1 to make when building the snapper9260 board.
386*4882a593Smuzhiyun
387*4882a593Smuzhiyun        References to other boards can be added in the string also. For
388*4882a593Smuzhiyun        example:
389*4882a593Smuzhiyun
390*4882a593Smuzhiyun        [make-flags]
391*4882a593Smuzhiyun        at91-boards=ENABLE_AT91_TEST=1
392*4882a593Smuzhiyun        snapper9260=${at91-boards} BUILD_TAG=442
393*4882a593Smuzhiyun        snapper9g45=${at91-boards} BUILD_TAG=443
394*4882a593Smuzhiyun
395*4882a593Smuzhiyun        This will return 'ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
396*4882a593Smuzhiyun        and 'ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45.
397*4882a593Smuzhiyun
398*4882a593Smuzhiyun        A special 'target' variable is set to the board target.
399*4882a593Smuzhiyun
400*4882a593Smuzhiyun        Args:
401*4882a593Smuzhiyun            board: Board object for the board to check.
402*4882a593Smuzhiyun        Returns:
403*4882a593Smuzhiyun            'make' flags for that board, or '' if none
404*4882a593Smuzhiyun        """
405*4882a593Smuzhiyun        self._make_flags['target'] = board.target
406*4882a593Smuzhiyun        arg_str = self.ResolveReferences(self._make_flags,
407*4882a593Smuzhiyun                           self._make_flags.get(board.target, ''))
408*4882a593Smuzhiyun        args = arg_str.split(' ')
409*4882a593Smuzhiyun        i = 0
410*4882a593Smuzhiyun        while i < len(args):
411*4882a593Smuzhiyun            if not args[i]:
412*4882a593Smuzhiyun                del args[i]
413*4882a593Smuzhiyun            else:
414*4882a593Smuzhiyun                i += 1
415*4882a593Smuzhiyun        return args
416*4882a593Smuzhiyun
417*4882a593Smuzhiyun    def LocateArchUrl(self, fetch_arch):
418*4882a593Smuzhiyun        """Find a toolchain available online
419*4882a593Smuzhiyun
420*4882a593Smuzhiyun        Look in standard places for available toolchains. At present the
421*4882a593Smuzhiyun        only standard place is at kernel.org.
422*4882a593Smuzhiyun
423*4882a593Smuzhiyun        Args:
424*4882a593Smuzhiyun            arch: Architecture to look for, or 'list' for all
425*4882a593Smuzhiyun        Returns:
426*4882a593Smuzhiyun            If fetch_arch is 'list', a tuple:
427*4882a593Smuzhiyun                Machine architecture (e.g. x86_64)
428*4882a593Smuzhiyun                List of toolchains
429*4882a593Smuzhiyun            else
430*4882a593Smuzhiyun                URL containing this toolchain, if avaialble, else None
431*4882a593Smuzhiyun        """
432*4882a593Smuzhiyun        arch = command.OutputOneLine('uname', '-m')
433*4882a593Smuzhiyun        base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
434*4882a593Smuzhiyun        versions = ['4.9.0', '4.6.3', '4.6.2', '4.5.1', '4.2.4']
435*4882a593Smuzhiyun        links = []
436*4882a593Smuzhiyun        for version in versions:
437*4882a593Smuzhiyun            url = '%s/%s/%s/' % (base, arch, version)
438*4882a593Smuzhiyun            print 'Checking: %s' % url
439*4882a593Smuzhiyun            response = urllib2.urlopen(url)
440*4882a593Smuzhiyun            html = response.read()
441*4882a593Smuzhiyun            parser = MyHTMLParser(fetch_arch)
442*4882a593Smuzhiyun            parser.feed(html)
443*4882a593Smuzhiyun            if fetch_arch == 'list':
444*4882a593Smuzhiyun                links += parser.links
445*4882a593Smuzhiyun            elif parser.arch_link:
446*4882a593Smuzhiyun                return url + parser.arch_link
447*4882a593Smuzhiyun        if fetch_arch == 'list':
448*4882a593Smuzhiyun            return arch, links
449*4882a593Smuzhiyun        return None
450*4882a593Smuzhiyun
451*4882a593Smuzhiyun    def Download(self, url):
452*4882a593Smuzhiyun        """Download a file to a temporary directory
453*4882a593Smuzhiyun
454*4882a593Smuzhiyun        Args:
455*4882a593Smuzhiyun            url: URL to download
456*4882a593Smuzhiyun        Returns:
457*4882a593Smuzhiyun            Tuple:
458*4882a593Smuzhiyun                Temporary directory name
459*4882a593Smuzhiyun                Full path to the downloaded archive file in that directory,
460*4882a593Smuzhiyun                    or None if there was an error while downloading
461*4882a593Smuzhiyun        """
462*4882a593Smuzhiyun        print 'Downloading: %s' % url
463*4882a593Smuzhiyun        leaf = url.split('/')[-1]
464*4882a593Smuzhiyun        tmpdir = tempfile.mkdtemp('.buildman')
465*4882a593Smuzhiyun        response = urllib2.urlopen(url)
466*4882a593Smuzhiyun        fname = os.path.join(tmpdir, leaf)
467*4882a593Smuzhiyun        fd = open(fname, 'wb')
468*4882a593Smuzhiyun        meta = response.info()
469*4882a593Smuzhiyun        size = int(meta.getheaders('Content-Length')[0])
470*4882a593Smuzhiyun        done = 0
471*4882a593Smuzhiyun        block_size = 1 << 16
472*4882a593Smuzhiyun        status = ''
473*4882a593Smuzhiyun
474*4882a593Smuzhiyun        # Read the file in chunks and show progress as we go
475*4882a593Smuzhiyun        while True:
476*4882a593Smuzhiyun            buffer = response.read(block_size)
477*4882a593Smuzhiyun            if not buffer:
478*4882a593Smuzhiyun                print chr(8) * (len(status) + 1), '\r',
479*4882a593Smuzhiyun                break
480*4882a593Smuzhiyun
481*4882a593Smuzhiyun            done += len(buffer)
482*4882a593Smuzhiyun            fd.write(buffer)
483*4882a593Smuzhiyun            status = r'%10d MiB  [%3d%%]' % (done / 1024 / 1024,
484*4882a593Smuzhiyun                                             done * 100 / size)
485*4882a593Smuzhiyun            status = status + chr(8) * (len(status) + 1)
486*4882a593Smuzhiyun            print status,
487*4882a593Smuzhiyun            sys.stdout.flush()
488*4882a593Smuzhiyun        fd.close()
489*4882a593Smuzhiyun        if done != size:
490*4882a593Smuzhiyun            print 'Error, failed to download'
491*4882a593Smuzhiyun            os.remove(fname)
492*4882a593Smuzhiyun            fname = None
493*4882a593Smuzhiyun        return tmpdir, fname
494*4882a593Smuzhiyun
495*4882a593Smuzhiyun    def Unpack(self, fname, dest):
496*4882a593Smuzhiyun        """Unpack a tar file
497*4882a593Smuzhiyun
498*4882a593Smuzhiyun        Args:
499*4882a593Smuzhiyun            fname: Filename to unpack
500*4882a593Smuzhiyun            dest: Destination directory
501*4882a593Smuzhiyun        Returns:
502*4882a593Smuzhiyun            Directory name of the first entry in the archive, without the
503*4882a593Smuzhiyun            trailing /
504*4882a593Smuzhiyun        """
505*4882a593Smuzhiyun        stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
506*4882a593Smuzhiyun        return stdout.splitlines()[0][:-1]
507*4882a593Smuzhiyun
508*4882a593Smuzhiyun    def TestSettingsHasPath(self, path):
509*4882a593Smuzhiyun        """Check if buildman will find this toolchain
510*4882a593Smuzhiyun
511*4882a593Smuzhiyun        Returns:
512*4882a593Smuzhiyun            True if the path is in settings, False if not
513*4882a593Smuzhiyun        """
514*4882a593Smuzhiyun        paths = self.GetPathList(False)
515*4882a593Smuzhiyun        return path in paths
516*4882a593Smuzhiyun
517*4882a593Smuzhiyun    def ListArchs(self):
518*4882a593Smuzhiyun        """List architectures with available toolchains to download"""
519*4882a593Smuzhiyun        host_arch, archives = self.LocateArchUrl('list')
520*4882a593Smuzhiyun        re_arch = re.compile('[-a-z0-9.]*_([^-]*)-.*')
521*4882a593Smuzhiyun        arch_set = set()
522*4882a593Smuzhiyun        for archive in archives:
523*4882a593Smuzhiyun            # Remove the host architecture from the start
524*4882a593Smuzhiyun            arch = re_arch.match(archive[len(host_arch):])
525*4882a593Smuzhiyun            if arch:
526*4882a593Smuzhiyun                arch_set.add(arch.group(1))
527*4882a593Smuzhiyun        return sorted(arch_set)
528*4882a593Smuzhiyun
529*4882a593Smuzhiyun    def FetchAndInstall(self, arch):
530*4882a593Smuzhiyun        """Fetch and install a new toolchain
531*4882a593Smuzhiyun
532*4882a593Smuzhiyun        arch:
533*4882a593Smuzhiyun            Architecture to fetch, or 'list' to list
534*4882a593Smuzhiyun        """
535*4882a593Smuzhiyun        # Fist get the URL for this architecture
536*4882a593Smuzhiyun        col = terminal.Color()
537*4882a593Smuzhiyun        print col.Color(col.BLUE, "Downloading toolchain for arch '%s'" % arch)
538*4882a593Smuzhiyun        url = self.LocateArchUrl(arch)
539*4882a593Smuzhiyun        if not url:
540*4882a593Smuzhiyun            print ("Cannot find toolchain for arch '%s' - use 'list' to list" %
541*4882a593Smuzhiyun                   arch)
542*4882a593Smuzhiyun            return 2
543*4882a593Smuzhiyun        home = os.environ['HOME']
544*4882a593Smuzhiyun        dest = os.path.join(home, '.buildman-toolchains')
545*4882a593Smuzhiyun        if not os.path.exists(dest):
546*4882a593Smuzhiyun            os.mkdir(dest)
547*4882a593Smuzhiyun
548*4882a593Smuzhiyun        # Download the tar file for this toolchain and unpack it
549*4882a593Smuzhiyun        tmpdir, tarfile = self.Download(url)
550*4882a593Smuzhiyun        if not tarfile:
551*4882a593Smuzhiyun            return 1
552*4882a593Smuzhiyun        print col.Color(col.GREEN, 'Unpacking to: %s' % dest),
553*4882a593Smuzhiyun        sys.stdout.flush()
554*4882a593Smuzhiyun        path = self.Unpack(tarfile, dest)
555*4882a593Smuzhiyun        os.remove(tarfile)
556*4882a593Smuzhiyun        os.rmdir(tmpdir)
557*4882a593Smuzhiyun        print
558*4882a593Smuzhiyun
559*4882a593Smuzhiyun        # Check that the toolchain works
560*4882a593Smuzhiyun        print col.Color(col.GREEN, 'Testing')
561*4882a593Smuzhiyun        dirpath = os.path.join(dest, path)
562*4882a593Smuzhiyun        compiler_fname_list = self.ScanPath(dirpath, True)
563*4882a593Smuzhiyun        if not compiler_fname_list:
564*4882a593Smuzhiyun            print 'Could not locate C compiler - fetch failed.'
565*4882a593Smuzhiyun            return 1
566*4882a593Smuzhiyun        if len(compiler_fname_list) != 1:
567*4882a593Smuzhiyun            print col.Color(col.RED, 'Warning, ambiguous toolchains: %s' %
568*4882a593Smuzhiyun                            ', '.join(compiler_fname_list))
569*4882a593Smuzhiyun        toolchain = Toolchain(compiler_fname_list[0], True, True)
570*4882a593Smuzhiyun
571*4882a593Smuzhiyun        # Make sure that it will be found by buildman
572*4882a593Smuzhiyun        if not self.TestSettingsHasPath(dirpath):
573*4882a593Smuzhiyun            print ("Adding 'download' to config file '%s'" %
574*4882a593Smuzhiyun                   bsettings.config_fname)
575*4882a593Smuzhiyun            bsettings.SetItem('toolchain', 'download', '%s/*/*' % dest)
576*4882a593Smuzhiyun        return 0
577