blob: 0c463c5d9d3ab54525fb3a4b6edc2b6214b0c1df [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.
"""NEXE building script
This module will take a set of source files, include paths, library paths, and
additional arguments, and use them to build.
"""
import hashlib
import json
from optparse import OptionParser
import os
import re
import Queue
import shutil
import StringIO
import subprocess
import sys
import tempfile
import threading
import urllib2
from build_nexe_tools import (CommandRunner, Error, FixPath,
IsFile, MakeDir, RemoveFile)
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import pynacl.platform
# When a header file defining NACL_BUILD_SUBARCH is introduced,
# we can simply remove this map.
# cf) https://code.google.com/p/chromium/issues/detail?id=440012.
NACL_BUILD_ARCH_MAP = {
'x86-32': ['NACL_BUILD_ARCH=x86', 'NACL_BUILD_SUBARCH=32'],
'x86-32-nonsfi': ['NACL_BUILD_ARCH=x86', 'NACL_BUILD_SUBARCH=32'],
'x86-64': ['NACL_BUILD_ARCH=x86', 'NACL_BUILD_SUBARCH=64'],
'arm': ['NACL_BUILD_ARCH=arm', 'NACL_BUILD_SUBARCH=32'],
'arm-nonsfi': ['NACL_BUILD_ARCH=arm', 'NACL_BUILD_SUBARCH=32'],
'mips': ['NACL_BUILD_ARCH=mips', 'NACL_BUILD_SUBARCH=32'],
'pnacl': ['NACL_BUILD_ARCH=pnacl'],
}
def RemoveQuotes(opt):
if opt and opt[0] == '"':
assert opt[-1] == '"', opt
return opt[1:-1].replace('\\"', '"')
return opt
def ArgToList(opt):
outlist = []
if opt is None:
return outlist
optlist = opt.split(' ')
for optitem in optlist:
optitem = RemoveQuotes(optitem)
if optitem:
outlist.append(optitem)
return outlist
def GetMTime(filepath):
"""GetMTime returns the last modification time of the file or None."""
try:
return os.path.getmtime(FixPath(filepath))
except OSError:
return None
def IsStale(out_ts, src_ts, rebuilt=False):
# If either source or output timestamp was not available, assume stale.
if not out_ts or not src_ts:
return True
# If just rebuilt timestamps may be equal due to time granularity.
if rebuilt:
return out_ts < src_ts
# If about to build, be conservative and rebuilt just in case.
return out_ts <= src_ts
def IsEnvFlagTrue(flag_name, default=False):
"""Return true when the given flag is true.
Note:
Any values that do not match the true pattern are False.
Args:
flag_name: a string name of a flag.
default: default return value if the flag is not set.
Returns:
True if the flag is in the true pattern. Otherwise False.
"""
flag_value = os.environ.get(flag_name)
if flag_value is None:
return default
return bool(re.search(r'^([tTyY]|1:?)', flag_value))
def GetIntegerEnv(flag_name, default=0):
"""Parses and returns integer environment variable.
Args:
flag_name: a string name of a flag.
default: default return value if the flag is not set.
Returns:
Integer value of the flag.
"""
flag_value = os.environ.get(flag_name)
if flag_value is None:
return default
try:
return int(flag_value)
except ValueError:
raise Error('Invalid ' + flag_name + ': ' + flag_value)
class Builder(CommandRunner):
"""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):
super(Builder, self).__init__(options)
arch = options.arch
self.arch = arch
build_type = options.build.split('_')
toolname = build_type[0]
self.outtype = build_type[1]
self.osname = pynacl.platform.GetOS()
# pnacl toolchain can be selected in three different ways
# 1. by specifying --arch=pnacl directly to generate
# pexe targets.
# 2. by specifying --build=newlib_translate to generated
# nexe via translation
# 3. by specifying --build=newlib_{nexe,nlib}_pnacl use pnacl
# toolchain in native mode (e.g. the IRT shim)
self.is_pnacl_toolchain = False
if self.outtype == 'translate':
self.is_pnacl_toolchain = True
if len(build_type) > 2 and build_type[2] == 'pnacl':
self.is_pnacl_toolchain = True
self.is_nacl_clang = len(build_type) > 2 and build_type[2] == 'clang'
if arch.endswith('-nonsfi'):
arch = arch[:-len('-nonsfi')]
if arch in ['x86-32', 'x86-64']:
mainarch = 'x86'
self.tool_prefix = 'x86_64-nacl-'
elif arch == 'arm':
self.tool_prefix = 'arm-nacl-'
mainarch = 'arm'
elif arch == 'mips':
self.is_pnacl_toolchain = True
elif arch == 'pnacl':
self.is_pnacl_toolchain = True
else:
raise Error('Toolchain architecture %s not supported.' % arch)
if toolname not in ['newlib', 'glibc']:
raise Error('Toolchain of type %s not supported.' % toolname)
if arch == 'arm' and toolname == 'glibc':
raise Error('arm glibc not yet supported.')
if arch == 'mips' and toolname == 'glibc':
raise Error('mips glibc not supported.')
if arch == 'pnacl' and toolname == 'glibc':
raise Error('pnacl glibc not yet supported.')
if self.is_pnacl_toolchain:
self.tool_prefix = 'pnacl-'
tool_subdir = 'pnacl_newlib'
elif self.is_nacl_clang:
tool_subdir = 'pnacl_newlib'
else:
tool_subdir = 'nacl_%s_%s' % (mainarch, toolname)
# The pnacl-clang, etc. tools are scripts. Note that for the CommandRunner
# so that it can know if a shell is needed or not.
self.SetCommandsAreScripts(self.is_pnacl_toolchain)
build_arch = pynacl.platform.GetArch()
tooldir = os.path.join('%s_%s' % (self.osname, build_arch), tool_subdir)
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
self.toolchain = os.path.join(options.toolpath, tooldir)
self.toolbin = os.path.join(self.toolchain, 'bin')
self.toolstamp = os.path.join(self.toolchain, tool_subdir + '.json')
if not IsFile(self.toolstamp):
raise Error('Could not find toolchain prep stamp file: ' + self.toolstamp)
self.inc_paths = ArgToList(options.incdirs)
self.lib_paths = ArgToList(options.libdirs)
self.define_list = ArgToList(options.defines)
self.name = options.name
self.cmd_file = options.cmd_file
self.BuildCompileOptions(
options.compile_flags, self.define_list, options.arch)
self.BuildLinkOptions(options.link_flags)
self.BuildArchiveOptions()
self.strip = options.strip
self.empty = options.empty
self.strip_all = options.strip_all
self.strip_debug = options.strip_debug
self.tls_edit = options.tls_edit
self.finalize_pexe = options.finalize_pexe and arch == 'pnacl'
goma_config = self.GetGomaConfig(options.gomadir, arch, toolname)
self.gomacc = goma_config.get('gomacc', '')
self.goma_burst = goma_config.get('burst', False)
self.goma_threads = goma_config.get('threads', 1)
# Define NDEBUG for Release builds.
if options.build_config.startswith('Release'):
self.compile_options.append('-DNDEBUG')
# Use unoptimized native objects for debug IRT builds for faster compiles.
if (self.is_pnacl_toolchain
and (self.outtype == 'nlib'
or self.outtype == 'nexe')
and self.arch != 'pnacl'):
if (options.build_config is not None
and options.build_config.startswith('Debug')):
self.compile_options.extend(['--pnacl-allow-translate',
'--pnacl-allow-native',
'-arch', self.arch])
# Also use fast translation because we are still translating libc/libc++
self.link_options.append('-Wt,-O0')
self.irt_linker = options.irt_linker
self.Log('Compile options: %s' % self.compile_options)
self.Log('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, self.tool_prefix + name)
def GetCCompiler(self):
"""Helper which returns C compiler path."""
if self.is_pnacl_toolchain or self.is_nacl_clang:
return self.GetBinName('clang')
else:
return self.GetBinName('gcc')
def GetCXXCompiler(self):
"""Helper which returns C++ compiler path."""
if self.is_pnacl_toolchain or self.is_nacl_clang:
return self.GetBinName('clang++')
else:
return self.GetBinName('g++')
def GetAr(self):
"""Helper which returns ar path."""
return self.GetBinName('ar')
def GetStrip(self):
"""Helper which returns strip path."""
return self.GetBinName('strip')
def GetObjCopy(self):
"""Helper which returns objcopy path."""
return self.GetBinName('objcopy')
def GetReadElf(self):
"""Helper which returns readelf path."""
return self.GetBinName('readelf')
def GetPnaclFinalize(self):
"""Helper which returns pnacl-finalize path."""
assert self.is_pnacl_toolchain
return self.GetBinName('finalize')
def BuildAssembleOptions(self, options):
options = ArgToList(options)
self.assemble_options = options + ['-I' + name for name in self.inc_paths]
def DebugName(self):
return self.name + '.debug'
def UntaggedName(self):
return self.name + '.untagged'
def LinkOutputName(self):
if (self.is_pnacl_toolchain and self.finalize_pexe or
self.strip_all or self.strip_debug):
return self.DebugName()
else:
return self.name
def ArchiveOutputName(self):
if self.strip_debug:
return self.DebugName()
else:
return self.name
def StripOutputName(self):
return self.name
def TranslateOutputName(self):
return self.name
def Soname(self):
return self.name
def BuildCompileOptions(self, options, define_list, arch):
"""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_WINDOWS=') or
define.startswith('NACL_OSX=') or
define.startswith('NACL_LINUX=') or
define.startswith('NACL_ANDROID=') or
define.startswith('NACL_BUILD_ARCH=') or
define.startswith('NACL_BUILD_SUBARCH=') or
define == 'COMPONENT_BUILD' or
'WIN32' in define or
'WINDOWS' in define or
'WINVER' in define)]
define_list.extend(['NACL_WINDOWS=0',
'NACL_OSX=0',
'NACL_LINUX=0',
'NACL_ANDROID=0'])
define_list.extend(NACL_BUILD_ARCH_MAP[arch])
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.outtype == 'nso':
options += ['-Wl,-rpath-link,' + name for name in self.lib_paths]
options += ['-shared']
options += ['-Wl,-soname,' + os.path.basename(self.Soname())]
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 GetObjectName(self, src):
if self.strip:
src = src.replace(self.strip,'')
# Hash the full path of the source file and add 32 bits of that hash onto
# the end of the object file name. This helps disambiguate files with the
# same name, because all of the object files are placed into the same
# directory. Technically, the correct solution would be to preserve the
# directory structure of the input source files inside the object file
# directory, but doing that runs the risk of running into filename length
# issues on Windows.
h = hashlib.sha1()
h.update(src)
wart = h.hexdigest()[:8]
_, filename = os.path.split(src)
filename, _ = os.path.splitext(filename)
return os.path.join(self.outdir, filename + '_' + wart + '.o')
def FixWindowsPath(self, path):
# The windows version of the nacl toolchain returns badly
# formed system header paths. As we do want changes in the
# toolchain to trigger rebuilds, compensate by detecting
# malformed paths (starting with /libexec/) and assume these
# are actually toolchain relative.
#
# Additionally, in some cases the toolchains emit cygwin paths
# which don't work in a win32 python.
# Assume they are all /cygdrive/ relative and convert to a
# drive letter.
cygdrive = '/cygdrive/'
if path.startswith('/cygdrive/'):
path = os.path.normpath(
path[len(cygdrive)] + ':' + path[len(cygdrive)+1:])
elif path.startswith('/libexec/'):
path = os.path.normpath(os.path.join(self.toolchain, path[1:]))
return path
def GetGomaConfig(self, gomadir, arch, toolname):
"""Returns a goma config dictionary if goma is available or {}."""
# Start goma support from os/arch/toolname that have been tested.
# Set NO_NACL_GOMA=true to force to avoid using goma.
default_no_nacl_goma = True if pynacl.platform.IsWindows() else False
if (arch not in ['x86-32', 'x86-64', 'pnacl']
or toolname not in ['newlib', 'glibc']
or IsEnvFlagTrue('NO_NACL_GOMA', default=default_no_nacl_goma)
or IsEnvFlagTrue('GOMA_DISABLED')):
return {}
goma_config = {}
gomacc_base = 'gomacc.exe' if pynacl.platform.IsWindows() else 'gomacc'
# Search order of gomacc:
# --gomadir command argument -> GOMA_DIR env. -> PATH env.
search_path = []
# 1. --gomadir in the command argument.
if gomadir:
search_path.append(gomadir)
# 2. Use GOMA_DIR environment variable if exist.
goma_dir_env = os.environ.get('GOMA_DIR')
if goma_dir_env:
search_path.append(goma_dir_env)
# 3. Append PATH env.
path_env = os.environ.get('PATH')
if path_env:
search_path.extend(path_env.split(os.path.pathsep))
for directory in search_path:
gomacc = os.path.join(directory, gomacc_base)
if os.path.isfile(gomacc):
try:
port = int(subprocess.Popen(
[gomacc, 'port'],
stdout=subprocess.PIPE).communicate()[0].strip())
status = urllib2.urlopen(
'http://127.0.0.1:%d/healthz' % port).read().strip()
if status == 'ok':
goma_config['gomacc'] = gomacc
break
except (OSError, ValueError, urllib2.URLError) as e:
# Try another gomacc in the search path.
self.Log('Strange gomacc %s found, try another one: %s' % (gomacc, e))
if goma_config:
goma_config['burst'] = IsEnvFlagTrue('NACL_GOMA_BURST')
default_threads = 100 if pynacl.platform.IsLinux() else 10
goma_config['threads'] = GetIntegerEnv('NACL_GOMA_THREADS',
default=default_threads)
return goma_config
def NeedsRebuild(self, outd, out, src, rebuilt=False):
if not IsFile(self.toolstamp):
if rebuilt:
raise Error('Could not find toolchain stamp file %s.' % self.toolstamp)
return True
if not IsFile(self.cmd_file):
if rebuilt:
raise Error('Could not find cmd file %s.' % self.cmd_file)
return True
if not IsFile(outd):
if rebuilt:
raise Error('Could not find dependency file %s.' % outd)
return True
if not IsFile(out):
if rebuilt:
raise Error('Could not find output file %s.' % out)
return True
inputs = [__file__, self.toolstamp, src, self.cmd_file]
outputs = [out, outd]
# Find their timestamps if any.
input_times = [(GetMTime(f), f) for f in inputs]
output_times = [(GetMTime(f), f) for f in outputs]
# All inputs must exist.
missing_inputs = [p[1] for p in input_times if p[0] is None]
if missing_inputs:
raise Error('Missing inputs: %s' % str(missing_inputs))
# Rebuild if any outputs are missing.
missing_outputs = [p[1] for p in output_times if p[0] is None]
if missing_outputs:
if rebuilt:
raise Error('Outputs missing after rebuild: %s' % str(missing_outputs))
return True
newest_input = max(input_times)
oldest_output = min(output_times)
if IsStale(oldest_output[0], newest_input[0], rebuilt):
if rebuilt:
raise Error('Output %s is older than toolchain stamp %s' % (
oldest_output[1], newest_input[1]))
return True
# Decode emitted makefile.
with open(FixPath(outd), 'r') as fh:
deps = fh.read()
# Remove line continuations
deps = deps.replace('\\\n', ' ')
deps = deps.replace('\n', '')
# The dependencies are whitespace delimited following the first ':'
# (that is not part of a windows drive letter)
deps = deps.split(':', 1)
if pynacl.platform.IsWindows() and len(deps[0]) == 1:
# The path has a drive letter, find the next ':'
deps = deps[1].split(':', 1)[1]
else:
deps = deps[1]
deps = deps.split()
if pynacl.platform.IsWindows():
deps = [self.FixWindowsPath(d) for d in deps]
# Check if any input has changed.
for filename in deps:
file_tm = GetMTime(filename)
if IsStale(oldest_output[0], file_tm, rebuilt):
if rebuilt:
raise Error('Dependency %s is older than output %s.' % (
filename, oldest_output[1]))
return True
return False
def Compile(self, src):
"""Compile the source with pre-determined options."""
compile_options = self.compile_options[:]
_, ext = os.path.splitext(src)
if ext in ['.c', '.S']:
bin_name = self.GetCCompiler()
compile_options.append('-std=gnu99')
if self.is_pnacl_toolchain and ext == '.S':
compile_options.append('-arch')
compile_options.append(self.arch)
elif ext in ['.cc', '.cpp']:
compile_options.append('-std=gnu++0x')
compile_options.append('-Wno-deprecated-register')
bin_name = self.GetCXXCompiler()
else:
if ext != '.h':
self.Log('Skipping unknown type %s for %s.' % (ext, src))
return None
# This option is only applicable to C, and C++ compilers warn if
# it is present, so remove it for C++ to avoid the warning.
if ext != '.c' and '-Wstrict-prototypes' in compile_options:
compile_options.remove('-Wstrict-prototypes')
self.Log('\nCompile %s' % src)
out = self.GetObjectName(src)
outd = out + '.d'
# Don't rebuild unneeded.
if not self.NeedsRebuild(outd, out, src):
return out
MakeDir(os.path.dirname(out))
self.CleanOutput(out)
self.CleanOutput(outd)
cmd_line = [bin_name, '-c', src, '-o', out,
'-MD', '-MF', outd] + compile_options
if self.gomacc:
cmd_line.insert(0, self.gomacc)
err = self.Run(cmd_line)
if err:
self.CleanOutput(outd)
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
else:
try:
self.NeedsRebuild(outd, out, src, True)
except Error as e:
raise Error('Failed to compile %s to %s with deps %s and cmdline:\t%s'
'\nNeedsRebuild returned error: %s' % (
src, out, outd, ' '.join(cmd_line), e))
return out
def RunLink(self, cmd_line, link_out):
self.CleanOutput(link_out)
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
def Link(self, srcs):
"""Link these objects with predetermined options and output name."""
out = self.LinkOutputName()
self.Log('\nLink %s' % out)
bin_name = self.GetCXXCompiler()
srcs_flags = []
if not self.empty:
srcs_flags += srcs
srcs_flags += self.link_options
# Handle an IRT link specially, using a separate script.
if self.irt_linker:
if self.tls_edit is None:
raise Error('Linking the IRT requires tls_edit')
irt_link_cmd = [sys.executable, self.irt_linker,
'--output=' + out,
'--tls-edit=' + self.tls_edit,
'--link-cmd=' + bin_name,
'--readelf-cmd=' + self.GetReadElf()]
if self.commands_are_scripts:
irt_link_cmd += ['--commands-are-scripts']
irt_link_cmd += srcs_flags
err = self.Run(irt_link_cmd, normalize_slashes=False)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(irt_link_cmd)))
return out
MakeDir(os.path.dirname(out))
cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
cmd_line += srcs_flags
self.RunLink(cmd_line, out)
return out
# For now, only support translating a pexe, and not .o file(s)
def Translate(self, src):
"""Translate a pexe to a nexe."""
out = self.TranslateOutputName()
self.Log('\nTranslate %s' % out)
bin_name = self.GetBinName('translate')
cmd_line = [bin_name, '-arch', self.arch, src, '-o', out]
cmd_line += self.link_options
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
return out
def Archive(self, srcs):
"""Archive these objects with predetermined options and output name."""
out = self.ArchiveOutputName()
self.Log('\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)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
return out
def Strip(self, src):
"""Strip the NEXE"""
self.Log('\nStrip %s' % src)
out = self.StripOutputName()
pre_debug_tagging = self.UntaggedName()
self.CleanOutput(out)
self.CleanOutput(pre_debug_tagging)
# Strip from foo.debug to foo.untagged.
strip_name = self.GetStrip()
strip_option = '--strip-all' if self.strip_all else '--strip-debug'
# pnacl does not have an objcopy so there are no way to embed a link
if self.is_pnacl_toolchain:
cmd_line = [strip_name, strip_option, src, '-o', out]
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
else:
cmd_line = [strip_name, strip_option, src, '-o', pre_debug_tagging]
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
# Tag with a debug link to foo.debug copying from foo.untagged to foo.
objcopy_name = self.GetObjCopy()
cmd_line = [objcopy_name, '--add-gnu-debuglink', src,
pre_debug_tagging, out]
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
# Drop the untagged intermediate.
self.CleanOutput(pre_debug_tagging)
return out
def Finalize(self, src):
"""Finalize the PEXE"""
self.Log('\nFinalize %s' % src)
out = self.StripOutputName()
self.CleanOutput(out)
bin_name = self.GetPnaclFinalize()
cmd_line = [bin_name, src, '-o', out]
err = self.Run(cmd_line)
if err:
raise Error('FAILED with %d: %s' % (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 in ['nexe', 'pexe', 'nso']:
out = self.Link(srcs)
if self.is_pnacl_toolchain and self.finalize_pexe:
# Note: pnacl-finalize also does stripping.
self.Finalize(out)
elif self.strip_all or self.strip_debug:
self.Strip(out)
elif self.outtype in ['nlib', 'plib']:
out = self.Archive(srcs)
if self.strip_debug:
self.Strip(out)
elif self.strip_all:
raise Error('FAILED: --strip-all on libs will result in unusable libs.')
else:
raise Error('FAILED: Unknown outtype: %s' % (self.outtype))
def UpdateBuildArgs(args, filename):
new_cmd = json.dumps(args)
try:
with open(filename, 'r') as fileobj:
old_cmd = fileobj.read()
except:
old_cmd = None
if old_cmd == new_cmd:
return False
with open(filename, 'w') as fileobj:
fileobj.write(new_cmd)
return True
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('--strip-debug', dest='strip_debug', default=False,
help='Strip the NEXE for debugging', action='store_true')
parser.add_option('--strip-all', dest='strip_all', default=False,
help='Strip the NEXE for production', action='store_true')
parser.add_option('--strip', dest='strip', default='',
help='Strip the filename')
parser.add_option('--nonstable-pnacl', dest='finalize_pexe', default=True,
help='Do not finalize pnacl bitcode for ABI stability',
action='store_false')
parser.add_option('--source-list', dest='source_list',
help='Filename to load a source list from')
parser.add_option('--tls-edit', dest='tls_edit', default=None,
help='tls_edit location if TLS should be modified for IRT')
parser.add_option('--irt-linker', dest='irt_linker', default=None,
help='linker tool to use if linking the IRT')
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('--product-directory', dest='product_directory',
help='Set the root directory of the build')
parser.add_option('-b', '--build', dest='build',
help='Set build type (<toolchain>_<outtype>, ' +
'where toolchain is newlib or glibc and outtype is ' +
'one of nexe, nlib, nso, pexe, or translate)')
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')
parser.add_option('-t', '--toolpath', dest='toolpath',
help='Set the path for of the toolchains.')
parser.add_option('--config-name', dest='build_config',
help='GYP build configuration name (Release/Debug)')
parser.add_option('--gomadir', dest='gomadir',
help='Path of the goma directory.')
options, files = parser.parse_args(argv[1:])
if options.name is None:
parser.error('--name is required!')
if options.build_config is None:
parser.error('--config-name is required!')
if options.root is None:
parser.error('--root is required!')
if options.arch is None:
parser.error('--arch is required!')
if options.build is None:
parser.error('--build is required!')
if not argv:
parser.print_help()
return 1
# Compare command-line options to last run, and force a rebuild if they
# have changed.
options.cmd_file = options.name + '.cmd'
UpdateBuildArgs(argv, options.cmd_file)
if options.product_directory is None:
parser.error('--product-dir is required')
product_dir = options.product_directory
# Normalize to forward slashes because re.sub interprets backslashes
# as escape characters. This also simplifies the subsequent regexes.
product_dir = product_dir.replace('\\', '/')
# Remove fake child that may be apended to the path.
# See untrusted.gypi.
product_dir = re.sub(r'/+xyz$', '', product_dir)
build = None
try:
if options.source_list:
source_list_handle = open(options.source_list, 'r')
source_list = source_list_handle.read().splitlines()
source_list_handle.close()
for file_name in source_list:
file_name = RemoveQuotes(file_name)
if "$" in file_name:
# The "make" backend can have an "obj" interpolation variable.
file_name = re.sub(r'\$!?[({]?obj[)}]?', product_dir + '/obj',
file_name)
# Expected patterns:
# $!PRODUCT_DIR in ninja.
# $(builddir) in make.
# $(OutDir) in MSVC.
# $(BUILT_PRODUCTS_DIR) in xcode.
# Also strip off and re-add the trailing directory seperator because
# different platforms are inconsistent on if it's there or not.
# HACK assume only the product directory is the only var left.
file_name = re.sub(r'\$!?[({]?\w+[)}]?/?', product_dir + '/',
file_name)
assert "$" not in file_name, file_name
files.append(file_name)
# Use set instead of list not to compile the same file twice.
# To keep in mind that the order of files may differ from the .gypcmd file,
# the set is not converted to a list.
# Having duplicated files can cause race condition of compiling during
# parallel build using goma.
# TODO(sbc): remove the duplication and turn it into an error.
files = set(files)
# Fix slash style to insulate invoked toolchains.
options.toolpath = os.path.normpath(options.toolpath)
build = Builder(options)
objs = []
if build.outtype == 'translate':
# Just translate a pexe to a nexe
if len(files) != 1:
parser.error('Pexe translation requires exactly one input file.')
build.Translate(list(files)[0])
return 0
if build.gomacc and (build.goma_burst or build.goma_threads > 1):
returns = Queue.Queue()
# Push all files into the inputs queue
inputs = Queue.Queue()
for filename in files:
inputs.put(filename)
def CompileThread(input_queue, output_queue):
try:
while True:
try:
filename = input_queue.get_nowait()
except Queue.Empty:
return
output_queue.put((filename, build.Compile(filename)))
except Exception:
# Put current exception info to the queue.
output_queue.put(sys.exc_info())
# Don't limit number of threads in the burst mode.
if build.goma_burst:
num_threads = len(files)
else:
num_threads = min(build.goma_threads, len(files))
# Start parallel build.
build_threads = []
for _ in xrange(num_threads):
thr = threading.Thread(target=CompileThread, args=(inputs, returns))
thr.start()
build_threads.append(thr)
# Wait for results.
src_to_obj = {}
for _ in files:
out = returns.get()
# An exception raised in the thread may come through the queue.
# Raise it again here.
if (isinstance(out, tuple) and len(out) == 3 and
isinstance(out[1], Exception)):
raise out[0], out[1], out[2]
elif out and len(out) == 2:
src_to_obj[out[0]] = out[1]
else:
raise Error('Unexpected element in CompileThread output_queue %s' %
out)
# Keep the input files ordering consistent for link phase to ensure
# determinism.
for filename in files:
# If input file to build.Compile is something it cannot handle, it
# returns None.
if src_to_obj[filename]:
objs.append(src_to_obj[filename])
assert inputs.empty()
# Wait until all threads have stopped and verify that there are no more
# results.
for thr in build_threads:
thr.join()
assert returns.empty()
else: # slow path.
for filename in files:
out = build.Compile(filename)
if out:
objs.append(out)
# Do not link if building an object. However we still want the output file
# to be what was specified in options.name
if options.compile_only:
if len(objs) > 1:
raise Error('--compile mode cannot be used with multiple sources')
shutil.copy(objs[0], options.name)
else:
build.Generate(objs)
return 0
except Error as e:
sys.stderr.write('%s\n' % e)
if build is not None:
build.EmitDeferredLog()
return 1
except:
if build is not None:
build.EmitDeferredLog()
raise
if __name__ == '__main__':
sys.exit(Main(sys.argv))