blob: 7d9ac12482923d1a961927034ad0f8dd9d9fa0c8 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from optparse import OptionParser
import os
import subprocess
import sys
"""NEXE building script
This module will take a set of source files, include paths, library paths, and
additional arguments, and use them to build.
"""
def ErrOut(text):
"""ErrOut prints an error message and the command-line that caused it.
Prints to standard err, both the command-line normally, and separated by
>>...<< to make it easier to copy and paste the command, or to
find command formating issues.
"""
sys.stderr.write('\n\n')
sys.stderr.write( '>>>' + '>> <<'.join(sys.argv) + '<<\n\n')
sys.stderr.write(' '.join(sys.argv) + '<<\n\n')
sys.stderr.write(text + '\n')
sys.exit(1)
def MakeDir(outdir):
if outdir and not os.path.exists(outdir):
# There may be a race creating this directory, so ignore failure
try:
os.makedirs(outdir)
except OSError:
pass
def RemoveQuotes(opt):
if opt and opt[0] == '"':
return opt[1:-1]
return opt
def ArgToList(opt):
outlist = []
optlist = RemoveQuotes(opt).split(' ')
for optitem in optlist:
optitem = RemoveQuotes(optitem).replace('\\"', '"')
if optitem:
outlist.append(optitem)
return outlist
class Builder(object):
"""Builder object maintains options and generates build command-lines.
The Builder object takes a set of script command-line options, and generates
a set of paths, and command-line options for the NaCl toolchain.
"""
def __init__(self, options):
arch = options.arch
build_type = options.build.split('_')
toolname = build_type[0]
self.outtype = build_type[1]
if sys.platform.startswith('linux'):
self.osname = 'linux'
elif sys.platform.startswith('win'):
self.osname = 'win'
elif sys.platform.startswith('darwin'):
self.osname = 'mac'
else:
ErrOut('Toolchain OS %s not supported.' % sys.platform)
if arch in ['x86-32', 'x86-64']:
self.arch = arch
self.mainarch = 'x86'
self.subarch = arch.split('-')[1]
tool_subdir = 'x86_64-nacl'
self.pnacl = False
elif arch == 'arm':
self.arch = arch
self.mainarch = 'arm'
self.subarch = ''
self.pnacl = True
else:
ErrOut('Toolchain architecture %s not supported.' % arch)
if arch == 'arm' and toolname == 'glibc':
ErrOut('arm/glibc not yet supported.')
if toolname == 'newlib':
toolchain = '%s_%s_newlib' % (self.osname, self.mainarch)
self.toolname = 'newlib'
elif toolname == 'glibc':
toolchain = '%s_%s' % (self.osname, self.mainarch)
self.toolname = 'glibc'
else:
ErrOut('Toolchain of type %s not supported.' % toolname)
self.root_path = options.root
self.nacl_path = os.path.join(self.root_path, 'native_client')
project_path, project_name = os.path.split(options.name)
self.outdir = options.objdir
# Set the toolchain directories
if self.pnacl:
pnacldir = 'pnacl_%s_x86_64' % self.osname
self.toolchain = self.GenNaClPath(os.path.join('toolchain',
pnacldir,
self.toolname))
self.toolbin = os.path.join(self.toolchain, 'bin')
self.toollib = os.path.join(self.toolchain, 'lib')
self.toolinc = os.path.join(self.toolchain, 'sysroot', 'include')
else:
self.toolchain = self.GenNaClPath(os.path.join('toolchain',
toolchain))
self.toolbin = os.path.join(self.toolchain, tool_subdir, 'bin')
self.toollib = os.path.join(self.toolchain,
tool_subdir,
'lib' + self.subarch)
self.toolinc = os.path.join(self.toolchain, tool_subdir, 'include')
self.inc_paths = ArgToList(options.incdirs)
self.lib_paths = ArgToList(options.libdirs)
self.define_list = ArgToList(options.defines)
self.name = options.name
self.BuildCompileOptions(options.compile_flags, self.define_list)
self.BuildLinkOptions(options.link_flags)
self.BuildArchiveOptions()
self.verbose = options.verbose
self.suffix = options.suffix
self.strip = options.strip
self.empty = options.empty
self.strip_debug = options.strip_debug
if self.verbose:
print 'Compile options: %s' % self.compile_options
print 'Linker options: %s' % self.link_options
def GenNaClPath(self, path):
"""Helper which prepends path with the native client source directory."""
return os.path.join(self.root_path, 'native_client', path)
def GetBinName(self, name):
"""Helper which prepends executable with the toolchain bin directory."""
return os.path.join(self.toolbin, name)
def GetCCompiler(self):
"""Helper which returns C compiler path."""
if self.pnacl:
return self.GetBinName('pnacl-clang')
else:
return self.GetBinName('gcc')
def GetCXXCompiler(self):
"""Helper which returns C++ compiler path."""
if self.pnacl:
return self.GetBinName('pnacl-clang++')
else:
return self.GetBinName('g++')
def GetAr(self):
"""Helper which returns ar path."""
if self.pnacl:
return self.GetBinName('pnacl-ar')
else:
return self.GetBinName('ar')
def GetStrip(self):
"""Helper which returns strip path."""
if self.pnacl:
return self.GetBinName('pnacl-strip')
else:
return self.GetBinName('strip')
def BuildAssembleOptions(self, options):
options = ArgToList(options)
self.assemble_options = options + ['-I' + name for name in self.inc_paths]
def BuildCompileOptions(self, options, define_list):
"""Generates compile options, called once by __init__."""
options = ArgToList(options)
# We want to shared gyp 'defines' with other targets, but not
# ones that are host system dependent. Filtering them out.
# This really should be better.
# See: http://code.google.com/p/nativeclient/issues/detail?id=2936
define_list = [define for define in define_list
if not (define.startswith('NACL_TARGET_ARCH=') or
define.startswith('NACL_TARGET_SUBARCH=') or
define.startswith('NACL_WINDOWS=') or
define.startswith('NACL_OSX=') or
define.startswith('NACL_LINUX=') or
define == 'COMPONENT_BUILD' or
'WIN32' in define or
'WINDOWS' in define or
'WINVER' in define)]
options += ['-D' + define for define in define_list]
self.compile_options = options + ['-I' + name for name in self.inc_paths]
def BuildLinkOptions(self, options):
"""Generates link options, called once by __init__."""
options = ArgToList(options)
if self.toolname in ['glibc', 'newlib'] and self.mainarch == 'x86':
options += ['-B' + self.toollib]
self.link_options = options + ['-L' + name for name in self.lib_paths]
def BuildArchiveOptions(self):
"""Generates link options, called once by __init__."""
self.archive_options = []
def Run(self, cmd_line, out):
"""Helper which runs a command line."""
# For POSIX style path on windows for POSIX based toolchain
cmd_line = [cmd.replace('\\', '/') for cmd in cmd_line]
if self.verbose:
print ' '.join(cmd_line)
try:
ecode = subprocess.call(cmd_line)
except Exception, err:
ErrOut('\n%s\nFAILED: %s\n\n' % (' '.join(cmd_line), str(err)))
if ecode != 0:
print 'Err %d: nacl-%s %s' % (ecode, os.path.basename(cmd_line[0]), out)
print '>>%s<<' % '<< >>'.join(cmd_line)
return ecode
def GetObjectName(self, src):
if self.strip:
src = src.replace(self.strip,'')
filepath, filename = os.path.split(src)
filename, ext = os.path.splitext(filename)
if self.suffix:
return os.path.join(self.outdir, filename + '.o')
else:
filename = os.path.split(src)[1]
return os.path.join(self.outdir, os.path.splitext(filename)[0] + '.o')
def CleanOutput(self, out):
if os.path.isfile(out):
os.remove(out)
def Compile(self, src):
"""Compile the source with pre-determined options."""
filename, ext = os.path.splitext(src)
if ext in ['.c', '.S']:
bin_name = self.GetCCompiler()
extra = ['-std=gnu99']
if self.pnacl and ext == '.S':
extra.append('-arch')
extra.append(self.arch)
elif ext in ['.cc', '.cpp']:
bin_name = self.GetCXXCompiler()
extra = []
else:
if self.verbose and ext != '.h':
print 'Skipping unknown type %s for %s.' % (ext, src)
return None
if self.verbose:
print '\nCompile %s' % src
out = self.GetObjectName(src)
MakeDir(os.path.dirname(out))
self.CleanOutput(out)
cmd_line = [bin_name, '-c', src, '-o', out] + extra + self.compile_options
err = self.Run(cmd_line, out)
if sys.platform.startswith('win') and err == 5:
# Try again on mystery windows failure.
err = self.Run(cmd_line, out)
if err:
ErrOut('\nFAILED with %d: %s\n\n' % (err, ' '.join(cmd_line)))
return out
def Link(self, srcs):
"""Link these objects with predetermined options and output name."""
out = self.name
if self.verbose:
print '\nLink %s' % out
bin_name = self.GetCXXCompiler()
MakeDir(os.path.dirname(out))
self.CleanOutput(out)
cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
if not self.empty:
cmd_line += srcs
cmd_line += self.link_options
err = self.Run(cmd_line, out)
# TODO( Retry on windows
if sys.platform.startswith('win') and err == 5:
# Try again on mystery windows failure.
err = self.Run(cmd_line, out)
if err:
ErrOut('\nFAILED with %d: %s\n\n' % (err, ' '.join(cmd_line)))
return out
def Archive(self, srcs):
"""Archive these objects with predetermined options and output name."""
out = self.name
if self.verbose:
print '\nArchive %s' % out
if '-r' in self.link_options:
bin_name = self.GetCXXCompiler()
cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
if not self.empty:
cmd_line += srcs
cmd_line += self.link_options
else:
bin_name = self.GetAr()
cmd_line = [bin_name, '-rc', out]
if not self.empty:
cmd_line += srcs
MakeDir(os.path.dirname(out))
self.CleanOutput(out)
err = self.Run(cmd_line, out)
if sys.platform.startswith('win') and err == 5:
# Try again on mystery windows failure.
err = self.Run(cmd_line, out)
if err:
ErrOut('\nFAILED with %d: %s\n\n' % (err, ' '.join(cmd_line)))
return out
def Strip(self, out):
"""Strip the NEXE"""
if self.verbose:
print '\nStrip %s' % out
tmp = out + '.tmp'
self.CleanOutput(tmp)
os.rename(out, tmp)
bin_name = self.GetStrip()
cmd_line = [bin_name, '--strip-debug', tmp, '-o', out]
err = self.Run(cmd_line, out)
if sys.platform.startswith('win') and err == 5:
# Try again on mystery windows failure.
err = self.Run(cmd_line, out)
if err:
ErrOut('\nFAILED with %d: %s\n\n' % (err, ' '.join(cmd_line)))
return out
def Generate(self, srcs):
"""Generate final output file.
Link or Archive the final output file, from the compiled sources.
"""
if self.outtype == 'nexe':
out = self.Link(srcs)
if self.strip_debug:
self.Strip(out)
elif self.outtype == 'nlib':
self.Archive(srcs)
def Main(argv):
parser = OptionParser()
parser.add_option('--empty', dest='empty', default=False,
help='Do not pass sources to library.', action='store_true')
parser.add_option('--no-suffix', dest='suffix', default=True,
help='Do not append arch suffix.', action='store_false')
parser.add_option('--sufix', dest='suffix',
help='Do append arch suffix.', action='store_true')
parser.add_option('--strip-debug', dest='strip_debug', default=False,
help='Strip the NEXE', action='store_true')
parser.add_option('--strip', dest='strip', default='',
help='Strip the filename')
parser.add_option('--source-list', dest='source_list',
help='Filename to load a source list from')
parser.add_option('-a', '--arch', dest='arch',
help='Set target architecture')
parser.add_option('-c', '--compile', dest='compile_only', default=False,
help='Compile only.', action='store_true')
parser.add_option('-i', '--include-dirs', dest='incdirs',
help='Set include directories.')
parser.add_option('-l', '--lib-dirs', dest='libdirs',
help='Set library directories.')
parser.add_option('-n', '--name', dest='name',
help='Base path and name of the nexe.')
parser.add_option('-o', '--objdir', dest='objdir',
help='Base path of the object output dir.')
parser.add_option('-r', '--root', dest='root',
help='Set the root directory of the sources')
parser.add_option('-b', '--build', dest='build',
help='Set build type (newlib, glibc).')
parser.add_option('--compile_flags', dest='compile_flags',
help='Set compile flags.')
parser.add_option('--defines', dest='defines',
help='Set defines')
parser.add_option('--link_flags', dest='link_flags',
help='Set link flags.')
parser.add_option('-v', '--verbose', dest='verbose', default=False,
help='Enable verbosity', action='store_true')
(options, files) = parser.parse_args(argv[1:])
if not argv:
parser.print_help()
return 1
if options.source_list:
source_list_handle = open(options.source_list, 'r')
source_list = source_list_handle.read().splitlines()
source_list_handle.close()
files = files + source_list
build = Builder(options)
objs = []
for filename in files:
out = build.Compile(filename)
if out:
objs.append(out)
# Do not link if building an object
if not options.compile_only:
build.Generate(objs)
return 0
if __name__ == '__main__':
sys.exit(Main(sys.argv))