2 # Copyright (c) 2012 The Native Client Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """NEXE building script
8 This module will take a set of source files, include paths, library paths, and
9 additional arguments, and use them to build.
14 from optparse import OptionParser
26 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
27 import pynacl.platform
29 class Error(Exception):
34 # On Windows, |path| can be a long relative path: ..\..\..\..\out\Foo\bar...
35 # If the full path -- os.path.join(os.getcwd(), path) -- is longer than 255
36 # characters, then any operations that open or check for existence will fail.
37 # We can't use os.path.abspath here, because that calls into a Windows
38 # function that still has the path length limit. Instead, we'll cheat and
39 # normalize the path lexically.
40 path = os.path.normpath(os.path.join(os.getcwd(), path))
41 if pynacl.platform.IsWindows():
43 raise Error('Path "%s" is too long (%d characters), and will fail.' % (
49 return os.path.isfile(FixPath(path))
53 outdir = FixPath(outdir)
54 if outdir and not os.path.exists(outdir):
55 # There may be a race creating this directory, so ignore failure.
63 os.remove(FixPath(path))
66 def OpenFile(path, mode='r'):
67 return open(FixPath(path), mode)
70 def RemoveQuotes(opt):
71 if opt and opt[0] == '"':
72 assert opt[-1] == '"', opt
73 return opt[1:-1].replace('\\"', '"')
81 optlist = opt.split(' ')
82 for optitem in optlist:
83 optitem = RemoveQuotes(optitem)
85 outlist.append(optitem)
89 def GetMTime(filepath):
90 """GetMTime returns the last modification time of the file or None."""
92 return os.path.getmtime(FixPath(filepath))
97 def IsStale(out_ts, src_ts, rebuilt=False):
98 # If either source or output timestamp was not available, assume stale.
99 if not out_ts or not src_ts:
102 # If just rebuilt timestamps may be equal due to time granularity.
104 return out_ts < src_ts
105 # If about to build, be conservative and rebuilt just in case.
106 return out_ts <= src_ts
109 def IsEnvFlagTrue(flag_name, default=False):
110 """Return true when the given flag is true.
113 Any values that do not match the true pattern are False.
116 flag_name: a string name of a flag.
117 default: default return value if the flag is not set.
120 True if the flag is in the true pattern. Otherwise False.
122 flag_value = os.environ.get(flag_name)
123 if flag_value is None:
125 return bool(re.search(r'^([tTyY]|1:?)', flag_value))
128 def GetIntegerEnv(flag_name, default=0):
129 """Parses and returns integer environment variable.
132 flag_name: a string name of a flag.
133 default: default return value if the flag is not set.
136 Integer value of the flag.
138 flag_value = os.environ.get(flag_name)
139 if flag_value is None:
142 return int(flag_value)
144 raise Error('Invalid ' + flag_name + ': ' + flag_value)
147 class Builder(object):
148 """Builder object maintains options and generates build command-lines.
150 The Builder object takes a set of script command-line options, and generates
151 a set of paths, and command-line options for the NaCl toolchain.
153 def __init__(self, options):
156 build_type = options.build.split('_')
157 toolname = build_type[0]
158 self.outtype = build_type[1]
159 self.osname = pynacl.platform.GetOS()
161 # pnacl toolchain can be selected in three different ways
162 # 1. by specifying --arch=pnacl directly to generate
164 # 2. by specifying --build=newlib_translate to generated
165 # nexe via translation
166 # 3. by specifying --build=newlib_{nexe,nlib}_pnacl use pnacl
167 # toolchain in native mode (e.g. the IRT shim)
168 self.is_pnacl_toolchain = False
169 if self.outtype == 'translate':
170 self.is_pnacl_toolchain = True
172 if len(build_type) > 2 and build_type[2] == 'pnacl':
173 self.is_pnacl_toolchain = True
175 if arch.endswith('-nonsfi'):
176 arch = arch[:-len('-nonsfi')]
178 if arch in ['x86-32', 'x86-64']:
180 self.tool_prefix = 'x86_64-nacl-'
182 self.tool_prefix = 'arm-nacl-'
185 self.is_pnacl_toolchain = True
186 elif arch == 'pnacl':
187 self.is_pnacl_toolchain = True
189 raise Error('Toolchain architecture %s not supported.' % arch)
191 if toolname not in ['newlib', 'glibc']:
192 raise Error('Toolchain of type %s not supported.' % toolname)
194 if arch == 'arm' and toolname == 'glibc':
195 raise Error('arm glibc not yet supported.')
197 if arch == 'mips' and toolname == 'glibc':
198 raise Error('mips glibc not supported.')
200 if arch == 'pnacl' and toolname == 'glibc':
201 raise Error('pnacl glibc not yet supported.')
203 if self.is_pnacl_toolchain:
204 self.tool_prefix = 'pnacl-'
205 tool_subdir = 'pnacl_newlib'
207 tool_subdir = 'nacl_%s_%s' % (mainarch, toolname)
209 build_arch = pynacl.platform.GetArch()
210 tooldir = os.path.join('%s_%s' % (self.osname, build_arch), tool_subdir)
212 self.root_path = options.root
213 self.nacl_path = os.path.join(self.root_path, 'native_client')
215 project_path, project_name = os.path.split(options.name)
216 self.outdir = options.objdir
218 # Set the toolchain directories
219 self.toolchain = os.path.join(options.toolpath, tooldir)
220 self.toolbin = os.path.join(self.toolchain, 'bin')
221 self.toolstamp = os.path.join(self.toolchain, 'stamp.prep')
222 if not IsFile(self.toolstamp):
223 raise Error('Could not find toolchain prep stamp file: ' + self.toolstamp)
225 self.inc_paths = ArgToList(options.incdirs)
226 self.lib_paths = ArgToList(options.libdirs)
227 self.define_list = ArgToList(options.defines)
229 self.name = options.name
230 self.cmd_file = options.cmd_file
231 self.BuildCompileOptions(options.compile_flags, self.define_list)
232 self.BuildLinkOptions(options.link_flags)
233 self.BuildArchiveOptions()
234 self.verbose = options.verbose
235 self.strip = options.strip
236 self.empty = options.empty
237 self.strip_all = options.strip_all
238 self.strip_debug = options.strip_debug
239 self.tls_edit = options.tls_edit
240 self.finalize_pexe = options.finalize_pexe and arch == 'pnacl'
241 goma_config = self.GetGomaConfig(options.gomadir, arch, toolname)
242 self.gomacc = goma_config.get('gomacc', '')
243 self.goma_burst = goma_config.get('burst', False)
244 self.goma_threads = goma_config.get('threads', 1)
246 # Define NDEBUG for Release builds.
247 if options.build_config.startswith('Release'):
248 self.compile_options.append('-DNDEBUG')
250 # Use unoptimized native objects for debug IRT builds for faster compiles.
251 if (self.is_pnacl_toolchain
252 and (self.outtype == 'nlib'
253 or self.outtype == 'nexe')
254 and self.arch != 'pnacl'):
255 if (options.build_config is not None
256 and options.build_config.startswith('Debug')):
257 self.compile_options.extend(['--pnacl-allow-translate',
258 '--pnacl-allow-native',
260 # Also use fast translation because we are still translating libc/libc++
261 self.link_options.append('-Wt,-O0')
263 self.irt_layout = options.irt_layout
265 # IRT constraints for auto layout.
266 # IRT text can only go up to 256MB. Addresses after that are for data.
267 # Reserve an extra page because:
268 # * sel_ldr requires a HLT sled at the end of the dynamic code area;
269 # * dynamic_load_test currently tests loading at the end of the dynamic
271 self.irt_text_max = 0x10000000 - 0x10000
272 # Data can only go up to the sandbox_top - sizeof(stack).
273 # NaCl allocates 16MB for the initial thread's stack (see
274 # NACL_DEFAULT_STACK_MAX in sel_ldr.h).
275 # Assume sandbox_top is 1GB, since even on x86-64 the limit would
276 # only be 2GB (rip-relative references can only be +/- 2GB).
277 sandbox_top = 0x40000000
278 self.irt_data_max = sandbox_top - (16 << 20)
279 # Initialize layout flags with "too-close-to-max" flags so that
280 # we can relax this later and get a tight fit.
281 self.link_options += [
282 '-Wl,-Ttext-segment=0x%x' % (self.irt_text_max - 0x10000),
283 '-Wl,-Trodata-segment=0x%x' % (self.irt_data_max - 0x10000)]
284 self.Log('Compile options: %s' % self.compile_options)
285 self.Log('Linker options: %s' % self.link_options)
287 def GenNaClPath(self, path):
288 """Helper which prepends path with the native client source directory."""
289 return os.path.join(self.root_path, 'native_client', path)
291 def GetBinName(self, name):
292 """Helper which prepends executable with the toolchain bin directory."""
293 return os.path.join(self.toolbin, self.tool_prefix + name)
295 def GetCCompiler(self):
296 """Helper which returns C compiler path."""
297 if self.is_pnacl_toolchain:
298 return self.GetBinName('clang')
300 return self.GetBinName('gcc')
302 def GetCXXCompiler(self):
303 """Helper which returns C++ compiler path."""
304 if self.is_pnacl_toolchain:
305 return self.GetBinName('clang++')
307 return self.GetBinName('g++')
310 """Helper which returns ar path."""
311 return self.GetBinName('ar')
314 """Helper which returns strip path."""
315 return self.GetBinName('strip')
317 def GetObjCopy(self):
318 """Helper which returns objcopy path."""
319 return self.GetBinName('objcopy')
321 def GetReadElf(self):
322 """Helper which returns readelf path."""
323 return self.GetBinName('readelf')
325 def GetPnaclFinalize(self):
326 """Helper which returns pnacl-finalize path."""
327 assert self.is_pnacl_toolchain
328 return self.GetBinName('finalize')
330 def BuildAssembleOptions(self, options):
331 options = ArgToList(options)
332 self.assemble_options = options + ['-I' + name for name in self.inc_paths]
335 return self.name + '.debug'
337 def UntaggedName(self):
338 return self.name + '.untagged'
340 def LinkOutputName(self):
341 if (self.is_pnacl_toolchain and self.finalize_pexe or
342 self.strip_all or self.strip_debug):
343 return self.DebugName()
347 def ArchiveOutputName(self):
349 return self.DebugName()
353 def StripOutputName(self):
356 def TranslateOutputName(self):
362 def BuildCompileOptions(self, options, define_list):
363 """Generates compile options, called once by __init__."""
364 options = ArgToList(options)
365 # We want to shared gyp 'defines' with other targets, but not
366 # ones that are host system dependent. Filtering them out.
367 # This really should be better.
368 # See: http://code.google.com/p/nativeclient/issues/detail?id=2936
369 define_list = [define for define in define_list
370 if not (define.startswith('NACL_TARGET_ARCH=') or
371 define.startswith('NACL_TARGET_SUBARCH=') or
372 define.startswith('NACL_WINDOWS=') or
373 define.startswith('NACL_OSX=') or
374 define.startswith('NACL_LINUX=') or
375 define.startswith('NACL_ANDROID=') or
376 define == 'COMPONENT_BUILD' or
378 'WINDOWS' in define or
380 define_list.extend(['NACL_WINDOWS=0',
384 options += ['-D' + define for define in define_list]
385 self.compile_options = options + ['-I' + name for name in self.inc_paths]
387 def BuildLinkOptions(self, options):
388 """Generates link options, called once by __init__."""
389 options = ArgToList(options)
390 if self.outtype == 'nso':
391 options += ['-Wl,-rpath-link,' + name for name in self.lib_paths]
392 options += ['-shared']
393 options += ['-Wl,-soname,' + os.path.basename(self.Soname())]
394 self.link_options = options + ['-L' + name for name in self.lib_paths]
396 def BuildArchiveOptions(self):
397 """Generates link options, called once by __init__."""
398 self.archive_options = []
402 sys.stderr.write(str(msg) + '\n')
404 def Run(self, cmd_line, get_output=False, possibly_batch=True, **kwargs):
405 """Helper which runs a command line.
407 Returns the error code if get_output is False.
408 Returns the output if get_output is True.
411 # For POSIX style path on windows for POSIX based toolchain
412 # (just for arguments, not for the path to the command itself)
413 cmd_line = [cmd_line[0]] + [cmd.replace('\\', '/') for cmd in cmd_line[1:]]
414 # Windows has a command line length limitation of 8191 characters.
416 if len(' '.join(cmd_line)) > 8000:
417 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
418 temp_file.write(' '.join(cmd_line[1:]))
419 cmd_line = [cmd_line[0], '@' + temp_file.name]
421 self.Log(' '.join(cmd_line))
423 runner = subprocess.call
425 runner = subprocess.check_output
426 if (possibly_batch and self.is_pnacl_toolchain and
427 pynacl.platform.IsWindows()):
428 # PNaCl toolchain executable is a script, not a binary, so it doesn't
429 # want to run on Windows without a shell
430 result = runner(' '.join(cmd_line), shell=True, **kwargs)
432 result = runner(cmd_line, **kwargs)
433 except Exception as err:
434 raise Error('%s\nFAILED: %s' % (' '.join(cmd_line), str(err)))
436 if temp_file is not None:
437 RemoveFile(temp_file.name)
440 def GetObjectName(self, src):
442 src = src.replace(self.strip,'')
443 # Hash the full path of the source file and add 32 bits of that hash onto
444 # the end of the object file name. This helps disambiguate files with the
445 # same name, because all of the object files are placed into the same
446 # directory. Technically, the correct solution would be to preserve the
447 # directory structure of the input source files inside the object file
448 # directory, but doing that runs the risk of running into filename length
452 wart = h.hexdigest()[:8]
453 _, filename = os.path.split(src)
454 filename, _ = os.path.splitext(filename)
455 return os.path.join(self.outdir, filename + '_' + wart + '.o')
457 def CleanOutput(self, out):
461 def FixWindowsPath(self, path):
462 # The windows version of the nacl toolchain returns badly
463 # formed system header paths. As we do want changes in the
464 # toolchain to trigger rebuilds, compensate by detecting
465 # malformed paths (starting with /libexec/) and assume these
466 # are actually toolchain relative.
468 # Additionally, in some cases the toolchains emit cygwin paths
469 # which don't work in a win32 python.
470 # Assume they are all /cygdrive/ relative and convert to a
472 cygdrive = '/cygdrive/'
473 if path.startswith('/cygdrive/'):
474 path = os.path.normpath(
475 path[len(cygdrive)] + ':' + path[len(cygdrive)+1:])
476 elif path.startswith('/libexec/'):
477 path = os.path.normpath(os.path.join(self.toolchain, path[1:]))
480 def GetGomaConfig(self, gomadir, arch, toolname):
481 """Returns a goma config dictionary if goma is available or {}."""
483 # Start goma support from os/arch/toolname that have been tested.
484 # Set NO_NACL_GOMA=true to force to avoid using goma.
485 default_no_nacl_goma = True if pynacl.platform.IsWindows() else False
486 if (arch not in ['x86-32', 'x86-64', 'pnacl']
487 or toolname not in ['newlib', 'glibc']
488 or IsEnvFlagTrue('NO_NACL_GOMA', default=default_no_nacl_goma)
489 or IsEnvFlagTrue('GOMA_DISABLED')):
493 gomacc_base = 'gomacc.exe' if pynacl.platform.IsWindows() else 'gomacc'
494 # Search order of gomacc:
495 # --gomadir command argument -> GOMA_DIR env. -> PATH env.
497 # 1. --gomadir in the command argument.
499 search_path.append(gomadir)
500 # 2. Use GOMA_DIR environment variable if exist.
501 goma_dir_env = os.environ.get('GOMA_DIR')
503 search_path.append(goma_dir_env)
504 # 3. Append PATH env.
505 path_env = os.environ.get('PATH')
507 search_path.extend(path_env.split(os.path.pathsep))
509 for directory in search_path:
510 gomacc = os.path.join(directory, gomacc_base)
511 if os.path.isfile(gomacc):
513 port = int(subprocess.Popen(
515 stdout=subprocess.PIPE).communicate()[0].strip())
516 status = urllib2.urlopen(
517 'http://127.0.0.1:%d/healthz' % port).read().strip()
519 goma_config['gomacc'] = gomacc
521 except (OSError, ValueError, urllib2.URLError) as e:
522 # Try another gomacc in the search path.
523 self.Log('Strange gomacc %s found, try another one: %s' % (gomacc, e))
526 goma_config['burst'] = IsEnvFlagTrue('NACL_GOMA_BURST')
527 default_threads = 100 if pynacl.platform.IsLinux() else 10
528 goma_config['threads'] = GetIntegerEnv('NACL_GOMA_THREADS',
529 default=default_threads)
532 def NeedsRebuild(self, outd, out, src, rebuilt=False):
533 if not IsFile(self.toolstamp):
535 raise Error('Could not find toolchain stamp file %s.' % self.toolstamp)
537 if not IsFile(self.cmd_file):
539 raise Error('Could not find cmd file %s.' % self.cmd_file)
543 raise Error('Could not find dependency file %s.' % outd)
547 raise Error('Could not find output file %s.' % out)
550 inputs = [__file__, self.toolstamp, src, self.cmd_file]
551 outputs = [out, outd]
553 # Find their timestamps if any.
554 input_times = [(GetMTime(f), f) for f in inputs]
555 output_times = [(GetMTime(f), f) for f in outputs]
557 # All inputs must exist.
558 missing_inputs = [p[1] for p in input_times if p[0] is None]
560 raise Error('Missing inputs: %s' % str(missing_inputs))
562 # Rebuild if any outputs are missing.
563 missing_outputs = [p[1] for p in output_times if p[0] is None]
566 raise Error('Outputs missing after rebuild: %s' % str(missing_outputs))
569 newest_input = max(input_times)
570 oldest_output = min(output_times)
572 if IsStale(oldest_output[0], newest_input[0], rebuilt):
574 raise Error('Output %s is older than toolchain stamp %s' % (
575 oldest_output[1], newest_input[1]))
578 # Decode emitted makefile.
579 with open(FixPath(outd), 'r') as fh:
581 # Remove line continuations
582 deps = deps.replace('\\\n', ' ')
583 deps = deps.replace('\n', '')
584 # The dependencies are whitespace delimited following the first ':'
585 # (that is not part of a windows drive letter)
586 deps = deps.split(':', 1)
587 if pynacl.platform.IsWindows() and len(deps[0]) == 1:
588 # The path has a drive letter, find the next ':'
589 deps = deps[1].split(':', 1)[1]
593 if pynacl.platform.IsWindows():
594 deps = [self.FixWindowsPath(d) for d in deps]
595 # Check if any input has changed.
596 for filename in deps:
597 file_tm = GetMTime(filename)
598 if IsStale(oldest_output[0], file_tm, rebuilt):
600 raise Error('Dependency %s is older than output %s.' % (
601 filename, oldest_output[1]))
605 def Compile(self, src):
606 """Compile the source with pre-determined options."""
608 compile_options = self.compile_options[:]
609 _, ext = os.path.splitext(src)
610 if ext in ['.c', '.S']:
611 bin_name = self.GetCCompiler()
612 compile_options.append('-std=gnu99')
613 if self.is_pnacl_toolchain and ext == '.S':
614 compile_options.append('-arch')
615 compile_options.append(self.arch)
616 elif ext in ['.cc', '.cpp']:
617 compile_options.append('-std=gnu++0x')
618 compile_options.append('-Wno-deprecated-register')
619 bin_name = self.GetCXXCompiler()
622 self.Log('Skipping unknown type %s for %s.' % (ext, src))
625 # This option is only applicable to C, and C++ compilers warn if
626 # it is present, so remove it for C++ to avoid the warning.
627 if ext != '.c' and '-Wstrict-prototypes' in compile_options:
628 compile_options.remove('-Wstrict-prototypes')
630 self.Log('\nCompile %s' % src)
632 out = self.GetObjectName(src)
635 # Don't rebuild unneeded.
636 if not self.NeedsRebuild(outd, out, src):
639 MakeDir(os.path.dirname(out))
640 self.CleanOutput(out)
641 self.CleanOutput(outd)
642 cmd_line = [bin_name, '-c', src, '-o', out,
643 '-MD', '-MF', outd] + compile_options
645 cmd_line.insert(0, self.gomacc)
646 err = self.Run(cmd_line, out)
648 self.CleanOutput(outd)
649 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
652 self.NeedsRebuild(outd, out, src, True)
654 raise Error('Failed to compile %s to %s with deps %s and cmdline:\t%s'
655 '\nNeedsRebuild returned error: %s' % (
656 src, out, outd, ' '.join(cmd_line), e))
659 def IRTLayoutFits(self, irt_file):
660 """Check if the IRT's data and text segment fit layout constraints.
662 Returns a tuple containing:
663 * whether the IRT data/text top addresses fit within the max limit
664 * current data/text top addrs
666 cmd_line = [self.GetReadElf(), '-W', '--segments', irt_file]
667 # Put LC_ALL=C in the environment for readelf, so that its messages
668 # will reliably match what we're looking for rather than being in some
669 # other language and/or character set.
670 env = dict(os.environ)
672 seginfo = self.Run(cmd_line, get_output=True, env=env)
673 lines = seginfo.splitlines()
675 for i, line in enumerate(lines):
676 if line == 'Program Headers:':
680 raise Error('Could not find Program Headers start: %s\n' % lines)
681 seg_lines = lines[ph_start:]
684 for line in seg_lines:
685 pieces = line.split()
686 # Type, Offset, Vaddr, Paddr, FileSz, MemSz, Flg(multiple), Align
687 if len(pieces) >= 8 and pieces[0] == 'LOAD':
689 segment_top = int(pieces[2], 16) + int(pieces[5], 16)
690 if pieces[6] == 'R' and pieces[7] == 'E':
691 text_top = max(segment_top, text_top)
693 if pieces[6] == 'R' or pieces[6] == 'RW':
694 data_top = max(segment_top, data_top)
696 if text_top == 0 or data_top == 0:
697 raise Error('Could not parse IRT Layout: text_top=0x%x data_top=0x%x\n'
698 'readelf output: %s\n' % (text_top, data_top, lines))
699 return (text_top <= self.irt_text_max and
700 data_top <= self.irt_data_max), text_top, data_top
702 def FindOldIRTFlagPosition(self, cmd_line, flag_name):
703 """Search for a given IRT link flag's position and value."""
706 for i, option in enumerate(cmd_line):
707 m = re.search('.*%s=(0x.*)' % flag_name, option)
710 raise Exception('Duplicate %s flag at position %d' % (flag_name, i))
712 old_start = m.group(1)
714 raise Exception('Could not find IRT layout flag %s' % flag_name)
715 return pos, old_start
717 def AdjustIRTLinkToFit(self, cmd_line, text_top, data_top):
718 """Adjust the linker options so that the IRT's data and text segment fit."""
719 def RoundDownToAlign(x):
720 return x - (x % 0x10000)
721 def AdjustFlag(flag_name, orig_max, expected_max):
722 if orig_max < expected_max:
724 pos, old_start = self.FindOldIRTFlagPosition(cmd_line, flag_name)
725 size = orig_max - int(old_start, 16)
726 self.Log('IRT %s size is %s' % (flag_name, size))
727 new_start = RoundDownToAlign(expected_max - size)
728 self.Log('Adjusting link flag %s from %s to %s' % (flag_name,
731 cmd_line[pos] = cmd_line[pos].replace(old_start, hex(new_start))
732 AdjustFlag('-Ttext-segment', text_top, self.irt_text_max)
733 AdjustFlag('-Trodata-segment', data_top, self.irt_data_max)
734 self.Log('Adjusted link options to %s' % ' '.join(cmd_line))
737 def RunLink(self, cmd_line, link_out):
738 self.CleanOutput(link_out)
739 err = self.Run(cmd_line, link_out)
741 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
743 def Link(self, srcs):
744 """Link these objects with predetermined options and output name."""
745 out = self.LinkOutputName()
746 self.Log('\nLink %s' % out)
747 bin_name = self.GetCXXCompiler()
750 if self.tls_edit is not None:
751 link_out = out + '.raw'
753 MakeDir(os.path.dirname(link_out))
755 cmd_line = [bin_name, '-o', link_out, '-Wl,--as-needed']
758 cmd_line += self.link_options
760 self.RunLink(cmd_line, link_out)
763 fits, text_top, data_top = self.IRTLayoutFits(link_out)
765 self.Log('IRT layout does not fit: text_top=0x%x and data_top=0x%x' %
766 (text_top, data_top))
767 cmd_line = self.AdjustIRTLinkToFit(cmd_line, text_top, data_top)
768 self.RunLink(cmd_line, link_out)
769 fits, text_top, data_top = self.IRTLayoutFits(link_out)
771 raise Error('Already re-linked IRT and it still does not fit:\n'
772 'text_top=0x%x and data_top=0x%x\n' % (
774 self.Log('IRT layout fits: text_top=0x%x and data_top=0x%x' %
775 (text_top, data_top))
777 if self.tls_edit is not None:
778 tls_edit_cmd = [FixPath(self.tls_edit), link_out, out]
779 tls_edit_err = self.Run(tls_edit_cmd, out, possibly_batch=False)
781 raise Error('FAILED with %d: %s' % (err, ' '.join(tls_edit_cmd)))
785 # For now, only support translating a pexe, and not .o file(s)
786 def Translate(self, src):
787 """Translate a pexe to a nexe."""
788 out = self.TranslateOutputName()
789 self.Log('\nTranslate %s' % out)
790 bin_name = self.GetBinName('translate')
791 cmd_line = [bin_name, '-arch', self.arch, src, '-o', out]
792 cmd_line += self.link_options
794 err = self.Run(cmd_line, out)
796 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
799 def Archive(self, srcs):
800 """Archive these objects with predetermined options and output name."""
801 out = self.ArchiveOutputName()
802 self.Log('\nArchive %s' % out)
804 if '-r' in self.link_options:
805 bin_name = self.GetCXXCompiler()
806 cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
809 cmd_line += self.link_options
811 bin_name = self.GetAr()
812 cmd_line = [bin_name, '-rc', out]
816 MakeDir(os.path.dirname(out))
817 self.CleanOutput(out)
818 err = self.Run(cmd_line, out)
820 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
823 def Strip(self, src):
825 self.Log('\nStrip %s' % src)
827 out = self.StripOutputName()
828 pre_debug_tagging = self.UntaggedName()
829 self.CleanOutput(out)
830 self.CleanOutput(pre_debug_tagging)
832 # Strip from foo.debug to foo.untagged.
833 strip_name = self.GetStrip()
834 strip_option = '--strip-all' if self.strip_all else '--strip-debug'
835 # pnacl does not have an objcopy so there are no way to embed a link
836 if self.is_pnacl_toolchain:
837 cmd_line = [strip_name, strip_option, src, '-o', out]
838 err = self.Run(cmd_line, out)
840 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
842 cmd_line = [strip_name, strip_option, src, '-o', pre_debug_tagging]
843 err = self.Run(cmd_line, pre_debug_tagging)
845 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
847 # Tag with a debug link to foo.debug copying from foo.untagged to foo.
848 objcopy_name = self.GetObjCopy()
849 cmd_line = [objcopy_name, '--add-gnu-debuglink', src,
850 pre_debug_tagging, out]
851 err = self.Run(cmd_line, out)
853 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
855 # Drop the untagged intermediate.
856 self.CleanOutput(pre_debug_tagging)
860 def Finalize(self, src):
861 """Finalize the PEXE"""
862 self.Log('\nFinalize %s' % src)
864 out = self.StripOutputName()
865 self.CleanOutput(out)
866 bin_name = self.GetPnaclFinalize()
867 cmd_line = [bin_name, src, '-o', out]
868 err = self.Run(cmd_line, out)
870 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
873 def Generate(self, srcs):
874 """Generate final output file.
876 Link or Archive the final output file, from the compiled sources.
878 if self.outtype in ['nexe', 'pexe', 'nso']:
879 out = self.Link(srcs)
880 if self.is_pnacl_toolchain and self.finalize_pexe:
881 # Note: pnacl-finalize also does stripping.
883 elif self.strip_all or self.strip_debug:
885 elif self.outtype in ['nlib', 'plib']:
886 out = self.Archive(srcs)
890 raise Error('FAILED: --strip-all on libs will result in unusable libs.')
892 raise Error('FAILED: Unknown outtype: %s' % (self.outtype))
895 def UpdateBuildArgs(args, filename):
896 new_cmd = json.dumps(args)
899 with open(filename, 'r') as fileobj:
900 old_cmd = fileobj.read()
904 if old_cmd == new_cmd:
907 with open(filename, 'w') as fileobj:
908 fileobj.write(new_cmd)
913 parser = OptionParser()
914 parser.add_option('--empty', dest='empty', default=False,
915 help='Do not pass sources to library.', action='store_true')
916 parser.add_option('--no-suffix', dest='suffix', default=True,
917 help='Do not append arch suffix.', action='store_false')
918 parser.add_option('--strip-debug', dest='strip_debug', default=False,
919 help='Strip the NEXE for debugging', action='store_true')
920 parser.add_option('--strip-all', dest='strip_all', default=False,
921 help='Strip the NEXE for production', action='store_true')
922 parser.add_option('--strip', dest='strip', default='',
923 help='Strip the filename')
924 parser.add_option('--nonstable-pnacl', dest='finalize_pexe', default=True,
925 help='Do not finalize pnacl bitcode for ABI stability',
926 action='store_false')
927 parser.add_option('--source-list', dest='source_list',
928 help='Filename to load a source list from')
929 parser.add_option('--tls-edit', dest='tls_edit', default=None,
930 help='tls_edit location if TLS should be modified for IRT')
931 parser.add_option('--irt-layout', dest='irt_layout', default=False,
932 help='Apply the IRT layout (pick data/text seg addresses)',
934 parser.add_option('-a', '--arch', dest='arch',
935 help='Set target architecture')
936 parser.add_option('-c', '--compile', dest='compile_only', default=False,
937 help='Compile only.', action='store_true')
938 parser.add_option('-i', '--include-dirs', dest='incdirs',
939 help='Set include directories.')
940 parser.add_option('-l', '--lib-dirs', dest='libdirs',
941 help='Set library directories.')
942 parser.add_option('-n', '--name', dest='name',
943 help='Base path and name of the nexe.')
944 parser.add_option('-o', '--objdir', dest='objdir',
945 help='Base path of the object output dir.')
946 parser.add_option('-r', '--root', dest='root',
947 help='Set the root directory of the sources')
948 parser.add_option('--product-directory', dest='product_directory',
949 help='Set the root directory of the build')
950 parser.add_option('-b', '--build', dest='build',
951 help='Set build type (<toolchain>_<outtype>, ' +
952 'where toolchain is newlib or glibc and outtype is ' +
953 'one of nexe, nlib, nso, pexe, or translate)')
954 parser.add_option('--compile_flags', dest='compile_flags',
955 help='Set compile flags.')
956 parser.add_option('--defines', dest='defines',
958 parser.add_option('--link_flags', dest='link_flags',
959 help='Set link flags.')
960 parser.add_option('-v', '--verbose', dest='verbose', default=False,
961 help='Enable verbosity', action='store_true')
962 parser.add_option('-t', '--toolpath', dest='toolpath',
963 help='Set the path for of the toolchains.')
964 parser.add_option('--config-name', dest='build_config',
965 help='GYP build configuration name (Release/Debug)')
966 parser.add_option('--gomadir', dest='gomadir',
967 help='Path of the goma directory.')
968 options, files = parser.parse_args(argv[1:])
970 if options.name is None:
971 parser.error('--name is required!')
972 if options.build_config is None:
973 parser.error('--config-name is required!')
974 if options.root is None:
975 parser.error('--root is required!')
976 if options.arch is None:
977 parser.error('--arch is required!')
978 if options.build is None:
979 parser.error('--build is required!')
985 # Compare command-line options to last run, and force a rebuild if they
987 options.cmd_file = options.name + '.cmd'
988 UpdateBuildArgs(argv, options.cmd_file)
991 if options.source_list:
992 source_list_handle = open(options.source_list, 'r')
993 source_list = source_list_handle.read().splitlines()
994 source_list_handle.close()
996 for file_name in source_list:
997 file_name = RemoveQuotes(file_name)
999 # Only require product directory if we need to interpolate it. This
1000 # provides backwards compatibility in the cases where we don't need to
1001 # interpolate. The downside is this creates a subtle landmine.
1002 if options.product_directory is None:
1003 parser.error('--product-dir is required')
1004 product_dir = options.product_directory
1005 # Normalize to forward slashes because re.sub interprets backslashes
1006 # as escape characters. This also simplifies the subsequent regexes.
1007 product_dir = product_dir.replace('\\', '/')
1008 # Remove fake child that may be apended to the path.
1009 # See untrusted.gypi.
1010 product_dir = re.sub(r'/+xyz$', '', product_dir)
1011 # The "make" backend can have an "obj" interpolation variable.
1012 file_name = re.sub(r'\$!?[({]?obj[)}]?', product_dir + '/obj',
1014 # Expected patterns:
1015 # $!PRODUCT_DIR in ninja.
1016 # $(builddir) in make.
1017 # $(OutDir) in MSVC.
1018 # $(BUILT_PRODUCTS_DIR) in xcode.
1019 # Also strip off and re-add the trailing directory seperator because
1020 # different platforms are inconsistent on if it's there or not.
1021 # HACK assume only the product directory is the only var left.
1022 file_name = re.sub(r'\$!?[({]?\w+[)}]?/?', product_dir + '/',
1024 assert "$" not in file_name, file_name
1025 files.append(file_name)
1027 # Use set instead of list not to compile the same file twice.
1028 # To keep in mind that the order of files may differ from the .gypcmd file,
1029 # the set is not converted to a list.
1030 # Having duplicated files can cause race condition of compiling during
1031 # parallel build using goma.
1032 # TODO(sbc): remove the duplication and turn it into an error.
1035 # Fix slash style to insulate invoked toolchains.
1036 options.toolpath = os.path.normpath(options.toolpath)
1038 build = Builder(options)
1041 if build.outtype == 'translate':
1042 # Just translate a pexe to a nexe
1044 parser.error('Pexe translation requires exactly one input file.')
1045 build.Translate(list(files)[0])
1048 if build.gomacc and (build.goma_burst or build.goma_threads > 1):
1049 returns = Queue.Queue()
1051 # Push all files into the inputs queue
1052 inputs = Queue.Queue()
1053 for filename in files:
1054 inputs.put(filename)
1056 def CompileThread(input_queue, output_queue):
1060 filename = input_queue.get_nowait()
1063 output_queue.put(build.Compile(filename))
1065 # Put current exception info to the queue.
1066 output_queue.put(sys.exc_info())
1068 # Don't limit number of threads in the burst mode.
1069 if build.goma_burst:
1070 num_threads = len(files)
1072 num_threads = min(build.goma_threads, len(files))
1074 # Start parallel build.
1076 for _ in xrange(num_threads):
1077 thr = threading.Thread(target=CompileThread, args=(inputs, returns))
1079 build_threads.append(thr)
1084 # An exception raised in the thread may come through the queue.
1085 # Raise it again here.
1086 if (isinstance(out, tuple) and len(out) == 3 and
1087 isinstance(out[1], Exception)):
1088 raise out[0], None, out[2]
1092 assert inputs.empty()
1094 # Wait until all threads have stopped and verify that there are no more
1096 for thr in build_threads:
1098 assert returns.empty()
1101 for filename in files:
1102 out = build.Compile(filename)
1106 # Do not link if building an object. However we still want the output file
1107 # to be what was specified in options.name
1108 if options.compile_only:
1110 raise Error('--compile mode cannot be used with multiple sources')
1111 shutil.copy(objs[0], options.name)
1113 build.Generate(objs)
1116 sys.stderr.write('%s\n' % e)
1119 if __name__ == '__main__':
1120 sys.exit(Main(sys.argv))