audk/BaseTools/gcc/mingw-gcc-build.py

566 lines
20 KiB
Python
Executable File

#!/usr/bin/env python
## @file
#
# Automation of instructions from:
# http://mingw-w64.svn.sourceforge.net/viewvc/mingw-w64/trunk/mingw-w64-doc/
# howto-build/mingw-w64-howto-build.txt?revision=216&view=markup
#
# Copyright (c) 2008 - 2018, Intel Corporation. All rights reserved.<BR>
# This program and the accompanying materials
# are licensed and made available under the terms and conditions of the BSD License
# which accompanies this distribution. The full text of the license may be found at
# http://opensource.org/licenses/bsd-license.php
#
# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#
from __future__ import print_function
from optparse import OptionParser
import os
import shutil
import subprocess
import sys
import tarfile
import urllib
import urlparse
try:
from hashlib import md5
except Exception:
from md5 import md5
if sys.version_info < (2, 5):
#
# This script (and edk2 BaseTools) require Python 2.5 or newer
#
print('Python version 2.5 or later is required.')
sys.exit(-1)
#
# Version and Copyright
#
VersionNumber = "0.01"
__version__ = "%prog Version " + VersionNumber
__copyright__ = "Copyright (c) 2008 - 2018, Intel Corporation. All rights reserved."
class Config:
"""class Config
Stores the configuration options for the rest of the script.
Handles the command line options, and allows the code within
the script to easily interact with the 'config' requested by
the user.
"""
def __init__(self):
self.base_dir = os.getcwd()
(self.options, self.args) = self.CheckOptions()
self.__init_dirs__()
def CheckOptions(self):
Parser = \
OptionParser(
description=__copyright__,
version=__version__,
prog="mingw-gcc-build",
usage="%prog [options] [target]"
)
Parser.add_option(
"--arch",
action = "store", type = "string",
default = '',
dest = "arch",
help = "Processor architecture to build gcc for."
)
Parser.add_option(
"--src-dir",
action = "store", type = "string", dest = "src_dir",
default = os.path.join(self.base_dir, 'src'),
help = "Directory to download/extract binutils/gcc sources"
)
Parser.add_option(
"--build-dir",
action = "store", type = "string", dest = "build_dir",
default = os.path.join(self.base_dir, 'build'),
help = "Directory to download/extract binutils/gcc sources"
)
Parser.add_option(
"--prefix",
action = "store", type = "string", dest = "prefix",
default = os.path.join(self.base_dir, 'install'),
help = "Prefix to install binutils/gcc into"
)
Parser.add_option(
"--skip-binutils",
action = "store_true", dest = "skip_binutils",
default = False,
help = "Will skip building binutils"
)
Parser.add_option(
"--skip-gcc",
action = "store_true", dest = "skip_gcc",
default = False,
help = "Will skip building GCC"
)
Parser.add_option(
"--symlinks",
action = "store", type = "string", dest = "symlinks",
default = os.path.join(self.base_dir, 'symlinks'),
help = "Directory to create binutils/gcc symbolic links into."
)
Parser.add_option(
"-v", "--verbose",
action="store_true",
type=None, help="Print verbose messages"
)
(Opt, Args) = Parser.parse_args()
self.arch = Opt.arch.lower()
allowedArchs = ('ia32', 'x64', 'ipf')
if self.arch not in allowedArchs:
Parser.error(
'Please use --arch to specify one of: %s' %
', '.join(allowedArchs)
)
self.target_arch = {'ia32': 'i686', 'x64': 'x86_64', 'ipf': 'ia64'}[self.arch]
self.target_sys = {'ia32': 'pc', 'x64': 'pc', 'ipf': 'pc'}[self.arch]
self.target_bin = {'ia32': 'mingw32', 'x64': 'mingw32', 'ipf': 'elf'}[self.arch]
self.target_combo = '-'.join((self.target_arch, self.target_sys, self.target_bin))
return (Opt, Args)
def __init_dirs__(self):
self.src_dir = os.path.realpath(os.path.expanduser(self.options.src_dir))
self.build_dir = os.path.realpath(os.path.expanduser(self.options.build_dir))
self.prefix = os.path.realpath(os.path.expanduser(self.options.prefix))
self.symlinks = os.path.realpath(os.path.expanduser(self.options.symlinks))
def IsConfigOk(self):
building = []
if not self.options.skip_binutils:
building.append('binutils')
if not self.options.skip_gcc:
building.append('gcc')
if len(building) == 0:
print("Nothing will be built!")
print()
print("Please try using --help and then change the configuration.")
return False
print("Current directory:")
print(" ", self.base_dir)
print("Sources download/extraction:", self.Relative(self.src_dir))
print("Build directory :", self.Relative(self.build_dir))
print("Prefix (install) directory :", self.Relative(self.prefix))
print("Create symlinks directory :", self.Relative(self.symlinks))
print("Building :", ', '.join(building))
print()
answer = raw_input("Is this configuration ok? (default = no): ")
if (answer.lower() not in ('y', 'yes')):
print()
print("Please try using --help and then change the configuration.")
return False
if self.arch.lower() == 'ipf':
print()
print('Please note that the IPF compiler built by this script has')
print('not yet been validated!')
print()
answer = raw_input("Are you sure you want to build it? (default = no): ")
if (answer.lower() not in ('y', 'yes')):
print()
print("Please try using --help and then change the configuration.")
return False
print()
return True
def Relative(self, path):
if path.startswith(self.base_dir):
return '.' + path[len(self.base_dir):]
return path
def MakeDirs(self):
for path in (self.src_dir, self.build_dir, self.prefix, self.symlinks):
if not os.path.exists(path):
os.makedirs(path)
class SourceFiles:
"""class SourceFiles
Handles the downloading of source files used by the script.
"""
def __init__(self, config):
self.config = config
self.source_files = self.source_files[config.arch]
if config.options.skip_binutils:
del self.source_files['binutils']
if config.options.skip_gcc:
del self.source_files['gcc']
del self.source_files['mingw_hdr']
source_files_common = {
'binutils': {
'url': 'http://www.kernel.org/pub/linux/devel/binutils/' + \
'binutils-$version.tar.bz2',
'version': '2.20.51.0.5',
'md5': '6d2de7cdf7a8389e70b124e3d73b4d37',
},
}
source_files_x64 = {
'gcc': {
'url': 'http://ftpmirror.gnu.org/gcc/' + \
'gcc-$version/gcc-$version.tar.bz2',
'version': '4.3.0',
'md5': '197ed8468b38db1d3481c3111691d85b',
},
}
source_files_ia32 = {
'gcc': source_files_x64['gcc'],
}
source_files_ipf = source_files_x64.copy()
source_files_ipf['gcc']['configure-params'] = (
'--with-gnu-as', '--with-gnu-ld', '--with-newlib',
'--verbose', '--disable-libssp', '--disable-nls',
'--enable-languages=c,c++'
)
source_files = {
'ia32': [source_files_common, source_files_ia32],
'x64': [source_files_common, source_files_x64],
'ipf': [source_files_common, source_files_ipf],
}
for arch in source_files:
mergedSourceFiles = {}
for source_files_dict in source_files[arch]:
mergedSourceFiles.update(source_files_dict)
for downloadItem in mergedSourceFiles:
fdata = mergedSourceFiles[downloadItem]
fdata['filename'] = fdata['url'].split('/')[-1]
if 'extract-dir' not in fdata:
for ext in ('.tar.gz', '.tar.bz2', '.zip'):
if fdata['filename'].endswith(ext):
fdata['extract-dir'] = fdata['filename'][:-len(ext)]
break
replaceables = ('extract-dir', 'filename', 'url')
for replaceItem in fdata:
if replaceItem in replaceables: continue
if not isinstance(fdata[replaceItem], str): continue
for replaceable in replaceables:
if not isinstance(fdata[replaceable], str): continue
if replaceable in fdata:
fdata[replaceable] = \
fdata[replaceable].replace(
'$' + replaceItem,
fdata[replaceItem]
)
source_files[arch] = mergedSourceFiles
#print 'source_files:', source_files
def GetAll(self):
def progress(received, blockSize, fileSize):
if fileSize < 0: return
wDots = (100 * received * blockSize) / fileSize / 10
if wDots > self.dots:
for i in range(wDots - self.dots):
print('.', end=' ')
sys.stdout.flush()
self.dots += 1
maxRetries = 1
for (fname, fdata) in self.source_files.items():
for retries in range(maxRetries):
try:
self.dots = 0
local_file = os.path.join(self.config.src_dir, fdata['filename'])
url = fdata['url']
print('Downloading %s:' % fname, url)
if retries > 0:
print('(retry)', end=' ')
sys.stdout.flush()
completed = False
if os.path.exists(local_file):
md5_pass = self.checkHash(fdata)
if md5_pass:
print('[md5 match]', end=' ')
else:
print('[md5 mismatch]', end=' ')
sys.stdout.flush()
completed = md5_pass
if not completed:
urllib.urlretrieve(url, local_file, progress)
#
# BUGBUG: Suggest proxy to user if download fails.
#
# export http_proxy=http://proxyservername.mycompany.com:911
# export ftp_proxy=http://proxyservername.mycompany.com:911
if not completed and os.path.exists(local_file):
md5_pass = self.checkHash(fdata)
if md5_pass:
print('[md5 match]', end=' ')
else:
print('[md5 mismatch]', end=' ')
sys.stdout.flush()
completed = md5_pass
if completed:
print('[done]')
break
else:
print('[failed]')
print(' Tried to retrieve', url)
print(' to', local_file)
print('Possible fixes:')
print('* If you are behind a web-proxy, try setting the', end=' ')
print('http_proxy environment variable')
print('* You can try to download this file separately', end=' ')
print('and rerun this script')
raise Exception()
except KeyboardInterrupt:
print('[KeyboardInterrupt]')
return False
except Exception as e:
print(e)
if not completed: return False
return True
def checkHash(self, fdata):
local_file = os.path.join(self.config.src_dir, fdata['filename'])
expect_md5 = fdata['md5']
data = open(local_file).read()
md5sum = md5()
md5sum.update(data)
return md5sum.hexdigest().lower() == expect_md5.lower()
def GetModules(self):
return self.source_files.keys()
def GetFilenameOf(self, module):
return self.source_files[module]['filename']
def GetMd5Of(self, module):
return self.source_files[module]['md5']
def GetExtractDirOf(self, module):
return self.source_files[module]['extract-dir']
def GetAdditionalParameters(self, module, step):
key = step + '-params'
if key in self.source_files[module]:
return self.source_files[module][key]
else:
return tuple()
class Extracter:
"""class Extracter
Handles the extraction of the source files from their downloaded
archive files.
"""
def __init__(self, source_files, config):
self.source_files = source_files
self.config = config
def Extract(self, module):
src = self.config.src_dir
extractDst = os.path.join(src, self.config.arch)
local_file = os.path.join(src, self.source_files.GetFilenameOf(module))
moduleMd5 = self.source_files.GetMd5Of(module)
extracted = os.path.join(extractDst, os.path.split(local_file)[1] + '.extracted')
if not os.path.exists(extractDst):
os.mkdir(extractDst)
extractedMd5 = None
if os.path.exists(extracted):
extractedMd5 = open(extracted).read()
if extractedMd5 != moduleMd5:
print('Extracting %s:' % self.config.Relative(local_file))
tar = tarfile.open(local_file)
tar.extractall(extractDst)
open(extracted, 'w').write(moduleMd5)
else:
pass
#print 'Previously extracted', self.config.Relative(local_file)
def ExtractAll(self):
for module in self.source_files.GetModules():
self.Extract(module)
class Builder:
"""class Builder
Builds and installs the GCC tool suite.
"""
def __init__(self, source_files, config):
self.source_files = source_files
self.config = config
def Build(self):
if not self.config.options.skip_binutils:
self.BuildModule('binutils')
if not self.config.options.skip_gcc:
self.BuildModule('gcc')
self.MakeSymLinks()
def IsBuildStepComplete(self, step):
return \
os.path.exists(
os.path.join(
self.config.build_dir, self.config.arch, step + '.completed'
)
)
def MarkBuildStepComplete(self, step):
open(
os.path.join(
self.config.build_dir, self.config.arch, step + '.completed'
),
"w"
).close()
def BuildModule(self, module):
base_dir = os.getcwd()
build_dir = os.path.join(self.config.build_dir, self.config.arch, module)
module_dir = self.source_files.GetExtractDirOf(module)
module_dir = os.path.realpath(os.path.join('src', self.config.arch, module_dir))
configure = os.path.join(module_dir, 'configure')
prefix = self.config.prefix
if not os.path.exists(build_dir):
os.makedirs(build_dir)
os.chdir(build_dir)
cmd = (
configure,
'--target=%s' % self.config.target_combo,
'--prefix=' + prefix,
'--with-sysroot=' + prefix,
'--disable-werror',
)
if os.path.exists('/opt/local/include/gmp.h'):
cmd += ('--with-gmp=/opt/local',)
if module == 'gcc': cmd += ('--oldincludedir=/opt/local/include',)
cmd += self.source_files.GetAdditionalParameters(module, 'configure')
self.RunCommand(cmd, module, 'config', skipable=True)
cmd = ('make',)
if module == 'gcc':
cmd += ('all-gcc',)
self.RunCommand(cmd, module, 'build')
cmd = ('make',)
if module == 'gcc':
cmd += ('install-gcc',)
else:
cmd += ('install',)
self.RunCommand(cmd, module, 'install')
os.chdir(base_dir)
print('%s module is now built and installed' % module)
def RunCommand(self, cmd, module, stage, skipable=False):
if skipable:
if self.IsBuildStepComplete('%s.%s' % (module, stage)):
return
popen = lambda cmd: \
subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
print('%s [%s] ...' % (module, stage), end=' ')
sys.stdout.flush()
p = popen(cmd)
output = p.stdout.read()
p.wait()
if p.returncode != 0:
print('[failed!]')
logFile = os.path.join(self.config.build_dir, 'log.txt')
f = open(logFile, "w")
f.write(output)
f.close()
raise Exception('Failed to %s %s\n' % (stage, module) + \
'See output log at %s' % self.config.Relative(logFile))
else:
print('[done]')
if skipable:
self.MarkBuildStepComplete('%s.%s' % (module, stage))
def MakeSymLinks(self):
links_dir = os.path.join(self.config.symlinks, self.config.arch)
if not os.path.exists(links_dir):
os.makedirs(links_dir)
startPrinted = False
for link in ('ar', 'ld', 'gcc'):
src = os.path.join(
self.config.prefix, 'bin', self.config.target_combo + '-' + link
)
linkdst = os.path.join(links_dir, link)
if not os.path.lexists(linkdst):
if not startPrinted:
print('Making symlinks in %s:' % self.config.Relative(links_dir), end=' ')
startPrinted = True
print(link, end=' ')
os.symlink(src, linkdst)
if startPrinted:
print('[done]')
class App:
"""class App
The main body of the application.
"""
def __init__(self):
config = Config()
if not config.IsConfigOk():
return
config.MakeDirs()
sources = SourceFiles(config)
result = sources.GetAll()
if result:
print('All files have been downloaded & verified')
else:
print('An error occured while downloading a file')
return
Extracter(sources, config).ExtractAll()
Builder(sources, config).Build()
App()