blob: 420b3dea80f7976fab5067bccc1ad0d5a6aa0f24 [file] [log] [blame]
Vishal Bhoj82c80712015-12-15 21:13:33 +05301#!/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