• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2
3## @file
4#
5# Automation of instructions from:
6#   http://mingw-w64.svn.sourceforge.net/viewvc/mingw-w64/trunk/mingw-w64-doc/
7#     howto-build/mingw-w64-howto-build.txt?revision=216&view=markup
8#
9# Copyright (c) 2008 - 2010, Intel Corporation. All rights reserved.<BR>
10# This program and the accompanying materials
11# are licensed and made available under the terms and conditions of the BSD License
12# which accompanies this distribution.    The full text of the license may be found at
13# http://opensource.org/licenses/bsd-license.php
14#
15# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
16# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
17#
18
19
20from optparse import OptionParser
21import os
22import shutil
23import subprocess
24import sys
25import tarfile
26import urllib
27import urlparse
28try:
29    from hashlib import md5
30except Exception:
31    from md5 import md5
32
33if sys.version_info < (2, 5):
34    #
35    # This script (and edk2 BaseTools) require Python 2.5 or newer
36    #
37    print 'Python version 2.5 or later is required.'
38    sys.exit(-1)
39
40#
41# Version and Copyright
42#
43VersionNumber = "0.01"
44__version__ = "%prog Version " + VersionNumber
45__copyright__ = "Copyright (c) 2008 - 2010, Intel Corporation.  All rights reserved."
46
47class Config:
48    """class Config
49
50    Stores the configuration options for the rest of the script.
51
52    Handles the command line options, and allows the code within
53    the script to easily interact with the 'config' requested by
54    the user.
55    """
56
57    def __init__(self):
58        self.base_dir = os.getcwd()
59        (self.options, self.args) = self.CheckOptions()
60        self.__init_dirs__()
61
62    def CheckOptions(self):
63        Parser = \
64            OptionParser(
65                description=__copyright__,
66                version=__version__,
67                prog="mingw-gcc-build",
68                usage="%prog [options] [target]"
69                )
70        Parser.add_option(
71            "--arch",
72            action = "store", type = "string",
73            default = '',
74            dest = "arch",
75            help = "Processor architecture to build gcc for."
76            )
77        Parser.add_option(
78            "--src-dir",
79            action = "store", type = "string", dest = "src_dir",
80            default = os.path.join(self.base_dir, 'src'),
81            help = "Directory to download/extract binutils/gcc sources"
82            )
83        Parser.add_option(
84            "--build-dir",
85            action = "store", type = "string", dest = "build_dir",
86            default = os.path.join(self.base_dir, 'build'),
87            help = "Directory to download/extract binutils/gcc sources"
88            )
89        Parser.add_option(
90            "--prefix",
91            action = "store", type = "string", dest = "prefix",
92            default = os.path.join(self.base_dir, 'install'),
93            help = "Prefix to install binutils/gcc into"
94            )
95        Parser.add_option(
96            "--skip-binutils",
97            action = "store_true", dest = "skip_binutils",
98            default = False,
99            help = "Will skip building binutils"
100            )
101        Parser.add_option(
102            "--skip-gcc",
103            action = "store_true", dest = "skip_gcc",
104            default = False,
105            help = "Will skip building GCC"
106            )
107        Parser.add_option(
108            "--symlinks",
109            action = "store", type = "string", dest = "symlinks",
110            default = os.path.join(self.base_dir, 'symlinks'),
111            help = "Directory to create binutils/gcc symbolic links into."
112            )
113        Parser.add_option(
114            "-v", "--verbose",
115            action="store_true",
116            type=None, help="Print verbose messages"
117            )
118
119        (Opt, Args) = Parser.parse_args()
120
121        self.arch = Opt.arch.lower()
122        allowedArchs = ('ia32', 'x64', 'ipf')
123        if self.arch not in allowedArchs:
124            Parser.error(
125                'Please use --arch to specify one of: %s' %
126                    ', '.join(allowedArchs)
127                )
128        self.target_arch = {'ia32': 'i686', 'x64': 'x86_64', 'ipf': 'ia64'}[self.arch]
129        self.target_sys = {'ia32': 'pc', 'x64': 'pc', 'ipf': 'pc'}[self.arch]
130        self.target_bin = {'ia32': 'mingw32', 'x64': 'mingw32', 'ipf': 'elf'}[self.arch]
131        self.target_combo = '-'.join((self.target_arch, self.target_sys, self.target_bin))
132
133        return (Opt, Args)
134
135    def __init_dirs__(self):
136        self.src_dir = os.path.realpath(os.path.expanduser(self.options.src_dir))
137        self.build_dir = os.path.realpath(os.path.expanduser(self.options.build_dir))
138        self.prefix = os.path.realpath(os.path.expanduser(self.options.prefix))
139        self.symlinks = os.path.realpath(os.path.expanduser(self.options.symlinks))
140
141    def IsConfigOk(self):
142
143        building = []
144        if not self.options.skip_binutils:
145            building.append('binutils')
146        if not self.options.skip_gcc:
147            building.append('gcc')
148        if len(building) == 0:
149            print "Nothing will be built!"
150            print
151            print "Please try using --help and then change the configuration."
152            return False
153
154        print "Current directory:"
155        print "   ", self.base_dir
156        print "Sources download/extraction:", self.Relative(self.src_dir)
157        print "Build directory            :", self.Relative(self.build_dir)
158        print "Prefix (install) directory :", self.Relative(self.prefix)
159        print "Create symlinks directory  :", self.Relative(self.symlinks)
160        print "Building                   :", ', '.join(building)
161        print
162        answer = raw_input("Is this configuration ok? (default = no): ")
163        if (answer.lower() not in ('y', 'yes')):
164            print
165            print "Please try using --help and then change the configuration."
166            return False
167
168        if self.arch.lower() == 'ipf':
169            print
170            print 'Please note that the IPF compiler built by this script has'
171            print 'not yet been validated!'
172            print
173            answer = raw_input("Are you sure you want to build it? (default = no): ")
174            if (answer.lower() not in ('y', 'yes')):
175                print
176                print "Please try using --help and then change the configuration."
177                return False
178
179        print
180        return True
181
182    def Relative(self, path):
183        if path.startswith(self.base_dir):
184            return '.' + path[len(self.base_dir):]
185        return path
186
187    def MakeDirs(self):
188        for path in (self.src_dir, self.build_dir,self.prefix, self.symlinks):
189            if not os.path.exists(path):
190                os.makedirs(path)
191
192class SourceFiles:
193    """class SourceFiles
194
195    Handles the downloading of source files used by the script.
196    """
197
198    def __init__(self, config):
199        self.config = config
200        self.source_files = self.source_files[config.arch]
201
202        if config.options.skip_binutils:
203            del self.source_files['binutils']
204
205        if config.options.skip_gcc:
206            del self.source_files['gcc']
207            del self.source_files['mingw_hdr']
208
209    source_files_common = {
210        'binutils': {
211            'url': 'http://www.kernel.org/pub/linux/devel/binutils/' + \
212                   'binutils-$version.tar.bz2',
213            'version': '2.20.51.0.5',
214            'md5': '6d2de7cdf7a8389e70b124e3d73b4d37',
215            },
216        }
217
218    source_files_x64 = {
219        'gcc': {
220            'url': 'http://ftpmirror.gnu.org/gcc/' + \
221                   'gcc-$version/gcc-$version.tar.bz2',
222            'version': '4.3.0',
223            'md5': '197ed8468b38db1d3481c3111691d85b',
224            },
225        }
226
227    source_files_ia32 = {
228        'gcc': source_files_x64['gcc'],
229        }
230
231    source_files_ipf = source_files_x64.copy()
232    source_files_ipf['gcc']['configure-params'] = (
233        '--with-gnu-as', '--with-gnu-ld', '--with-newlib',
234        '--verbose', '--disable-libssp', '--disable-nls',
235        '--enable-languages=c,c++'
236        )
237
238    source_files = {
239        'ia32': [source_files_common, source_files_ia32],
240        'x64': [source_files_common, source_files_x64],
241        'ipf': [source_files_common, source_files_ipf],
242        }
243
244    for arch in source_files:
245        mergedSourceFiles = {}
246        for source_files_dict in source_files[arch]:
247            mergedSourceFiles.update(source_files_dict)
248        for downloadItem in mergedSourceFiles:
249            fdata = mergedSourceFiles[downloadItem]
250            fdata['filename'] = fdata['url'].split('/')[-1]
251            if 'extract-dir' not in fdata:
252                for ext in ('.tar.gz', '.tar.bz2', '.zip'):
253                    if fdata['filename'].endswith(ext):
254                        fdata['extract-dir'] = fdata['filename'][:-len(ext)]
255                        break
256            replaceables = ('extract-dir', 'filename', 'url')
257            for replaceItem in fdata:
258                if replaceItem in replaceables: continue
259                if type(fdata[replaceItem]) != str: continue
260                for replaceable in replaceables:
261                    if type(fdata[replaceable]) != str: continue
262                    if replaceable in fdata:
263                        fdata[replaceable] = \
264                            fdata[replaceable].replace(
265                                '$' + replaceItem,
266                                fdata[replaceItem]
267                                )
268        source_files[arch] = mergedSourceFiles
269    #print 'source_files:', source_files
270
271    def GetAll(self):
272
273        def progress(received, blockSize, fileSize):
274            if fileSize < 0: return
275            wDots = (100 * received * blockSize) / fileSize / 10
276            if wDots > self.dots:
277                for i in range(wDots - self.dots):
278                    print '.',
279                    sys.stdout.flush()
280                    self.dots += 1
281
282        maxRetries = 1
283        for (fname, fdata) in self.source_files.items():
284            for retries in range(maxRetries):
285                try:
286                    self.dots = 0
287                    local_file = os.path.join(self.config.src_dir, fdata['filename'])
288                    url = fdata['url']
289                    print 'Downloading %s:' % fname, url
290                    if retries > 0:
291                        print '(retry)',
292                    sys.stdout.flush()
293
294                    completed = False
295                    if os.path.exists(local_file):
296                        md5_pass = self.checkHash(fdata)
297                        if md5_pass:
298                            print '[md5 match]',
299                        else:
300                            print '[md5 mismatch]',
301                        sys.stdout.flush()
302                        completed = md5_pass
303
304                    if not completed:
305                        urllib.urlretrieve(url, local_file, progress)
306
307                    #
308                    # BUGBUG: Suggest proxy to user if download fails.
309                    #
310                    # export http_proxy=http://proxyservername.mycompany.com:911
311                    # export ftp_proxy=http://proxyservername.mycompany.com:911
312
313                    if not completed and os.path.exists(local_file):
314                        md5_pass = self.checkHash(fdata)
315                        if md5_pass:
316                            print '[md5 match]',
317                        else:
318                            print '[md5 mismatch]',
319                        sys.stdout.flush()
320                        completed = md5_pass
321
322                    if completed:
323                        print '[done]'
324                        break
325                    else:
326                        print '[failed]'
327                        print '  Tried to retrieve', url
328                        print '  to', local_file
329                        print 'Possible fixes:'
330                        print '* If you are behind a web-proxy, try setting the',
331                        print 'http_proxy environment variable'
332                        print '* You can try to download this file separately',
333                        print 'and rerun this script'
334                        raise Exception()
335
336                except KeyboardInterrupt:
337                    print '[KeyboardInterrupt]'
338                    return False
339
340                except Exception, e:
341                    print e
342
343            if not completed: return False
344
345        return True
346
347    def checkHash(self, fdata):
348        local_file = os.path.join(self.config.src_dir, fdata['filename'])
349        expect_md5 = fdata['md5']
350        data = open(local_file).read()
351        md5sum = md5()
352        md5sum.update(data)
353        return md5sum.hexdigest().lower() == expect_md5.lower()
354
355    def GetModules(self):
356        return self.source_files.keys()
357
358    def GetFilenameOf(self, module):
359        return self.source_files[module]['filename']
360
361    def GetMd5Of(self, module):
362        return self.source_files[module]['md5']
363
364    def GetExtractDirOf(self, module):
365        return self.source_files[module]['extract-dir']
366
367    def GetAdditionalParameters(self, module, step):
368        key = step + '-params'
369        if key in self.source_files[module]:
370            return self.source_files[module][key]
371        else:
372            return tuple()
373
374class Extracter:
375    """class Extracter
376
377    Handles the extraction of the source files from their downloaded
378    archive files.
379    """
380
381    def __init__(self, source_files, config):
382        self.source_files = source_files
383        self.config = config
384
385    def Extract(self, module):
386        src = self.config.src_dir
387        extractDst = os.path.join(src, self.config.arch)
388        local_file = os.path.join(src, self.source_files.GetFilenameOf(module))
389        moduleMd5 = self.source_files.GetMd5Of(module)
390        extracted = os.path.join(extractDst, os.path.split(local_file)[1] + '.extracted')
391        if not os.path.exists(extractDst):
392            os.mkdir(extractDst)
393
394        extractedMd5 = None
395        if os.path.exists(extracted):
396            extractedMd5 = open(extracted).read()
397
398        if extractedMd5 != moduleMd5:
399            print 'Extracting %s:' % self.config.Relative(local_file)
400            tar = tarfile.open(local_file)
401            tar.extractall(extractDst)
402            open(extracted, 'w').write(moduleMd5)
403        else:
404            pass
405            #print 'Previously extracted', self.config.Relative(local_file)
406
407    def ExtractAll(self):
408        for module in self.source_files.GetModules():
409            self.Extract(module)
410
411class Builder:
412    """class Builder
413
414    Builds and installs the GCC tool suite.
415    """
416
417    def __init__(self, source_files, config):
418        self.source_files = source_files
419        self.config = config
420
421    def Build(self):
422        if not self.config.options.skip_binutils:
423            self.BuildModule('binutils')
424        if not self.config.options.skip_gcc:
425            self.BuildModule('gcc')
426            self.MakeSymLinks()
427
428    def IsBuildStepComplete(self, step):
429        return \
430            os.path.exists(
431                os.path.join(
432                    self.config.build_dir, self.config.arch, step + '.completed'
433                    )
434                )
435
436    def MarkBuildStepComplete(self, step):
437        open(
438            os.path.join(
439                self.config.build_dir, self.config.arch, step + '.completed'
440                ),
441            "w"
442            ).close()
443
444
445    def BuildModule(self, module):
446        base_dir = os.getcwd()
447        build_dir = os.path.join(self.config.build_dir, self.config.arch, module)
448        module_dir = self.source_files.GetExtractDirOf(module)
449        module_dir = os.path.realpath(os.path.join('src', self.config.arch, module_dir))
450        configure = os.path.join(module_dir, 'configure')
451        prefix = self.config.prefix
452        if not os.path.exists(build_dir):
453            os.makedirs(build_dir)
454        os.chdir(build_dir)
455
456        cmd = (
457            configure,
458            '--target=%s' % self.config.target_combo,
459            '--prefix=' + prefix,
460            '--with-sysroot=' + prefix,
461            '--disable-werror',
462            )
463        if os.path.exists('/opt/local/include/gmp.h'):
464            cmd += ('--with-gmp=/opt/local',)
465        if module == 'gcc': cmd += ('--oldincludedir=/opt/local/include',)
466        cmd += self.source_files.GetAdditionalParameters(module, 'configure')
467        self.RunCommand(cmd, module, 'config', skipable=True)
468
469        cmd = ('make',)
470        if module == 'gcc':
471            cmd += ('all-gcc',)
472        self.RunCommand(cmd, module, 'build')
473
474        cmd = ('make',)
475        if module == 'gcc':
476            cmd += ('install-gcc',)
477        else:
478            cmd += ('install',)
479        self.RunCommand(cmd, module, 'install')
480
481        os.chdir(base_dir)
482
483        print '%s module is now built and installed' % module
484
485    def RunCommand(self, cmd, module, stage, skipable=False):
486        if skipable:
487            if self.IsBuildStepComplete('%s.%s' % (module, stage)):
488                return
489
490        popen = lambda cmd: \
491            subprocess.Popen(
492                cmd,
493                stdin=subprocess.PIPE,
494                stdout=subprocess.PIPE,
495                stderr=subprocess.STDOUT
496                )
497
498        print '%s [%s] ...' % (module, stage),
499        sys.stdout.flush()
500        p = popen(cmd)
501        output = p.stdout.read()
502        p.wait()
503        if p.returncode != 0:
504            print '[failed!]'
505            logFile = os.path.join(self.config.build_dir, 'log.txt')
506            f = open(logFile, "w")
507            f.write(output)
508            f.close()
509            raise Exception, 'Failed to %s %s\n' % (stage, module) + \
510                'See output log at %s' % self.config.Relative(logFile)
511        else:
512            print '[done]'
513
514        if skipable:
515            self.MarkBuildStepComplete('%s.%s' % (module, stage))
516
517    def MakeSymLinks(self):
518        links_dir = os.path.join(self.config.symlinks, self.config.arch)
519        if not os.path.exists(links_dir):
520            os.makedirs(links_dir)
521        startPrinted = False
522        for link in ('ar', 'ld', 'gcc'):
523            src = os.path.join(
524                self.config.prefix, 'bin', self.config.target_combo + '-' + link
525                )
526            linkdst = os.path.join(links_dir, link)
527            if not os.path.lexists(linkdst):
528                if not startPrinted:
529                    print 'Making symlinks in %s:' % self.config.Relative(links_dir),
530                    startPrinted = True
531                print link,
532                os.symlink(src, linkdst)
533
534        if startPrinted:
535            print '[done]'
536
537class App:
538    """class App
539
540    The main body of the application.
541    """
542
543    def __init__(self):
544        config = Config()
545
546        if not config.IsConfigOk():
547            return
548
549        config.MakeDirs()
550
551        sources = SourceFiles(config)
552        result = sources.GetAll()
553        if result:
554            print 'All files have been downloaded & verified'
555        else:
556            print 'An error occured while downloading a file'
557            return
558
559        Extracter(sources, config).ExtractAll()
560
561        Builder(sources, config).Build()
562
563App()
564
565