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