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.
12 from optparse import OptionParser
23 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
24 import pynacl.platform
26 class Error(Exception):
31 # On Windows, |path| can be a long relative path: ..\..\..\..\out\Foo\bar...
32 # If the full path -- os.path.join(os.getcwd(), path) -- is longer than 255
33 # characters, then any operations that open or check for existence will fail.
34 # We can't use os.path.abspath here, because that calls into a Windows
35 # function that still has the path length limit. Instead, we'll cheat and
36 # normalize the path lexically.
37 path = os.path.normpath(os.path.join(os.getcwd(), path))
38 if pynacl.platform.IsWindows():
40 raise Error('Path "%s" is too long (%d characters), and will fail.' % (
46 return os.path.isfile(FixPath(path))
50 outdir = FixPath(outdir)
51 if outdir and not os.path.exists(outdir):
52 # There may be a race creating this directory, so ignore failure.
60 os.remove(FixPath(path))
63 def OpenFile(path, mode='r'):
64 return open(FixPath(path), mode)
67 def RemoveQuotes(opt):
68 if opt and opt[0] == '"':
77 optlist = RemoveQuotes(opt).split(' ')
78 for optitem in optlist:
79 optitem = RemoveQuotes(optitem).replace('\\"', '"')
81 outlist.append(optitem)
85 def GetMTime(filepath):
86 """GetMTime returns the last modification time of the file or None."""
88 return os.path.getmtime(FixPath(filepath))
93 def IsStale(out_ts, src_ts, rebuilt=False):
94 # If either source or output timestamp was not available, assume stale.
95 if not out_ts or not src_ts:
98 # If just rebuilt timestamps may be equal due to time granularity.
100 return out_ts < src_ts
101 # If about to build, be conservative and rebuilt just in case.
102 return out_ts <= src_ts
105 def IsEnvFlagTrue(flag_name, default=False):
106 """Return true when the given flag is true.
109 Any values that do not match the true pattern are False.
112 flag_name: a string name of a flag.
113 default: default return value if the flag is not set.
116 True if the flag is in the true pattern. Otherwise False.
118 flag_value = os.environ.get(flag_name)
119 if flag_value is None:
121 return bool(re.search(r'^([tTyY]|1:?)', flag_value))
124 def GetIntegerEnv(flag_name, default=0):
125 """Parses and returns integer environment variable.
128 flag_name: a string name of a flag.
129 default: default return value if the flag is not set.
132 Integer value of the flag.
134 flag_value = os.environ.get(flag_name)
135 if flag_value is None:
138 return int(flag_value)
140 raise Error('Invalid ' + flag_name + ': ' + flag_value)
143 class Builder(object):
144 """Builder object maintains options and generates build command-lines.
146 The Builder object takes a set of script command-line options, and generates
147 a set of paths, and command-line options for the NaCl toolchain.
149 def __init__(self, options):
152 build_type = options.build.split('_')
153 toolname = build_type[0]
154 self.outtype = build_type[1]
155 self.osname = pynacl.platform.GetOS()
157 # pnacl toolchain can be selected in three different ways
158 # 1. by specifying --arch=pnacl directly to generate
160 # 2. by specifying --build=newlib_translate to generated
161 # nexe via translation
162 # 3. by specifying --build=newlib_{nexe,nlib}_pnacl use pnacl
163 # toolchain in native mode (e.g. the IRT shim)
164 self.is_pnacl_toolchain = False
165 if self.outtype == 'translate':
166 self.is_pnacl_toolchain = True
168 if len(build_type) > 2 and build_type[2] == 'pnacl':
169 self.is_pnacl_toolchain = True
171 if arch.endswith('-nonsfi'):
172 arch = arch[:-len('-nonsfi')]
174 if arch in ['x86-32', 'x86-64']:
176 self.tool_prefix = 'x86_64-nacl-'
178 self.tool_prefix = 'arm-nacl-'
181 self.is_pnacl_toolchain = True
182 elif arch == 'pnacl':
183 self.is_pnacl_toolchain = True
185 raise Error('Toolchain architecture %s not supported.' % arch)
187 if toolname not in ['newlib', 'glibc']:
188 raise Error('Toolchain of type %s not supported.' % toolname)
190 if arch == 'arm' and toolname == 'glibc':
191 raise Error('arm glibc not yet supported.')
193 if arch == 'mips' and toolname == 'glibc':
194 raise Error('mips glibc not supported.')
196 if arch == 'pnacl' and toolname == 'glibc':
197 raise Error('pnacl glibc not yet supported.')
199 if self.is_pnacl_toolchain:
200 self.tool_prefix = 'pnacl-'
201 tool_subdir = 'pnacl_newlib'
203 tool_subdir = 'nacl_%s_%s' % (mainarch, toolname)
205 build_arch = pynacl.platform.GetArch()
206 tooldir = os.path.join('%s_%s' % (self.osname, build_arch), tool_subdir)
208 self.root_path = options.root
209 self.nacl_path = os.path.join(self.root_path, 'native_client')
211 project_path, project_name = os.path.split(options.name)
212 self.outdir = options.objdir
214 # Set the toolchain directories
215 self.toolchain = os.path.join(options.toolpath, tooldir)
216 self.toolbin = os.path.join(self.toolchain, 'bin')
217 self.toolstamp = os.path.join(self.toolchain, 'stamp.prep')
218 if not IsFile(self.toolstamp):
219 raise Error('Could not find toolchain prep stamp file: ' + self.toolstamp)
221 self.inc_paths = ArgToList(options.incdirs)
222 self.lib_paths = ArgToList(options.libdirs)
223 self.define_list = ArgToList(options.defines)
225 self.name = options.name
226 self.BuildCompileOptions(options.compile_flags, self.define_list)
227 self.BuildLinkOptions(options.link_flags)
228 self.BuildArchiveOptions()
229 self.verbose = options.verbose
230 self.suffix = options.suffix
231 self.strip = options.strip
232 self.empty = options.empty
233 self.strip_all = options.strip_all
234 self.strip_debug = options.strip_debug
235 self.tls_edit = options.tls_edit
236 self.finalize_pexe = options.finalize_pexe and arch == 'pnacl'
237 goma_config = self.GetGomaConfig(options.gomadir, arch, toolname)
238 self.gomacc = goma_config.get('gomacc', '')
239 self.goma_burst = goma_config.get('burst', False)
240 self.goma_threads = goma_config.get('threads', 1)
242 # Define NDEBUG for Release builds.
243 if options.build_config == 'Release':
244 self.compile_options.append('-DNDEBUG')
246 # Use unoptimized native objects for debug IRT builds for faster compiles.
247 if (self.is_pnacl_toolchain
248 and (self.outtype == 'nlib'
249 or self.outtype == 'nexe')
250 and self.arch != 'pnacl'):
251 if (options.build_config is not None
252 and options.build_config == 'Debug'):
253 self.compile_options.extend(['--pnacl-allow-translate',
254 '--pnacl-allow-native',
256 # Also use fast translation because we are still translating libc/libc++
257 self.link_options.append('-Wt,-O0')
259 self.irt_layout = options.irt_layout
261 # IRT constraints for auto layout.
262 # IRT text can only go up to 256MB. Addresses after that are for data.
263 # Reserve an extra page because:
264 # * sel_ldr requires a HLT sled at the end of the dynamic code area;
265 # * dynamic_load_test currently tests loading at the end of the dynamic
267 self.irt_text_max = 0x10000000 - 0x10000
268 # Data can only go up to the sandbox_top - sizeof(stack).
269 # NaCl allocates 16MB for the initial thread's stack (see
270 # NACL_DEFAULT_STACK_MAX in sel_ldr.h).
271 # Assume sandbox_top is 1GB, since even on x86-64 the limit would
272 # only be 2GB (rip-relative references can only be +/- 2GB).
273 sandbox_top = 0x40000000
274 self.irt_data_max = sandbox_top - (16 << 20)
275 # Initialize layout flags with "too-close-to-max" flags so that
276 # we can relax this later and get a tight fit.
277 self.link_options += [
278 '-Wl,-Ttext-segment=0x%x' % (self.irt_text_max - 0x10000),
279 '-Wl,-Trodata-segment=0x%x' % (self.irt_data_max - 0x10000)]
280 self.Log('Compile options: %s' % self.compile_options)
281 self.Log('Linker options: %s' % self.link_options)
283 def GenNaClPath(self, path):
284 """Helper which prepends path with the native client source directory."""
285 return os.path.join(self.root_path, 'native_client', path)
287 def GetBinName(self, name):
288 """Helper which prepends executable with the toolchain bin directory."""
289 return os.path.join(self.toolbin, self.tool_prefix + name)
291 def GetCCompiler(self):
292 """Helper which returns C compiler path."""
293 if self.is_pnacl_toolchain:
294 return self.GetBinName('clang')
296 return self.GetBinName('gcc')
298 def GetCXXCompiler(self):
299 """Helper which returns C++ compiler path."""
300 if self.is_pnacl_toolchain:
301 return self.GetBinName('clang++')
303 return self.GetBinName('g++')
306 """Helper which returns ar path."""
307 return self.GetBinName('ar')
310 """Helper which returns strip path."""
311 return self.GetBinName('strip')
313 def GetObjCopy(self):
314 """Helper which returns objcopy path."""
315 return self.GetBinName('objcopy')
317 def GetReadElf(self):
318 """Helper which returns readelf path."""
319 return self.GetBinName('readelf')
321 def GetPnaclFinalize(self):
322 """Helper which returns pnacl-finalize path."""
323 assert self.is_pnacl_toolchain
324 return self.GetBinName('finalize')
326 def BuildAssembleOptions(self, options):
327 options = ArgToList(options)
328 self.assemble_options = options + ['-I' + name for name in self.inc_paths]
331 return self.name + '.debug'
333 def UntaggedName(self):
334 return self.name + '.untagged'
336 def LinkOutputName(self):
337 if (self.is_pnacl_toolchain and self.finalize_pexe or
338 self.strip_all or self.strip_debug):
339 return self.DebugName()
343 def ArchiveOutputName(self):
345 return self.DebugName()
349 def StripOutputName(self):
352 def TranslateOutputName(self):
358 def BuildCompileOptions(self, options, define_list):
359 """Generates compile options, called once by __init__."""
360 options = ArgToList(options)
361 # We want to shared gyp 'defines' with other targets, but not
362 # ones that are host system dependent. Filtering them out.
363 # This really should be better.
364 # See: http://code.google.com/p/nativeclient/issues/detail?id=2936
365 define_list = [define for define in define_list
366 if not (define.startswith('NACL_TARGET_ARCH=') or
367 define.startswith('NACL_TARGET_SUBARCH=') or
368 define.startswith('NACL_WINDOWS=') or
369 define.startswith('NACL_OSX=') or
370 define.startswith('NACL_LINUX=') or
371 define.startswith('NACL_ANDROID=') or
372 define == 'COMPONENT_BUILD' or
374 'WINDOWS' in define or
376 define_list.extend(['NACL_WINDOWS=0',
380 options += ['-D' + define for define in define_list]
381 self.compile_options = options + ['-I' + name for name in self.inc_paths]
383 def BuildLinkOptions(self, options):
384 """Generates link options, called once by __init__."""
385 options = ArgToList(options)
386 if self.outtype == 'nso':
387 options += ['-Wl,-rpath-link,' + name for name in self.lib_paths]
388 options += ['-shared']
389 options += ['-Wl,-soname,' + os.path.basename(self.Soname())]
390 self.link_options = options + ['-L' + name for name in self.lib_paths]
392 def BuildArchiveOptions(self):
393 """Generates link options, called once by __init__."""
394 self.archive_options = []
398 sys.stderr.write(str(msg) + '\n')
400 def Run(self, cmd_line, get_output=False, **kwargs):
401 """Helper which runs a command line.
403 Returns the error code if get_output is False.
404 Returns the output if get_output is True.
407 # For POSIX style path on windows for POSIX based toolchain
408 # (just for arguments, not for the path to the command itself)
409 cmd_line = [cmd_line[0]] + [cmd.replace('\\', '/') for cmd in cmd_line[1:]]
410 # Windows has a command line length limitation of 8191 characters.
412 if len(' '.join(cmd_line)) > 8000:
413 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
414 temp_file.write(' '.join(cmd_line[1:]))
415 cmd_line = [cmd_line[0], '@' + temp_file.name]
417 self.Log(' '.join(cmd_line))
419 runner = subprocess.call
421 runner = subprocess.check_output
422 if self.is_pnacl_toolchain and pynacl.platform.IsWindows():
423 # PNaCl toolchain executable is a script, not a binary, so it doesn't
424 # want to run on Windows without a shell
425 result = runner(' '.join(cmd_line), shell=True, **kwargs)
427 result = runner(cmd_line, **kwargs)
428 except Exception as err:
429 raise Error('%s\nFAILED: %s' % (' '.join(cmd_line), str(err)))
431 if temp_file is not None:
432 RemoveFile(temp_file.name)
435 def GetObjectName(self, src):
437 src = src.replace(self.strip,'')
438 _, filename = os.path.split(src)
439 filename, _ = os.path.splitext(filename)
441 return os.path.join(self.outdir, filename + '.o')
443 filename = os.path.split(src)[1]
444 return os.path.join(self.outdir, os.path.splitext(filename)[0] + '.o')
446 def CleanOutput(self, out):
450 def FixWindowsPath(self, path):
451 # The windows version of the nacl toolchain returns badly
452 # formed system header paths. As we do want changes in the
453 # toolchain to trigger rebuilds, compensate by detecting
454 # malformed paths (starting with /libexec/) and assume these
455 # are actually toolchain relative.
457 # Additionally, in some cases the toolchains emit cygwin paths
458 # which don't work in a win32 python.
459 # Assume they are all /cygdrive/ relative and convert to a
461 cygdrive = '/cygdrive/'
462 if path.startswith('/cygdrive/'):
463 path = os.path.normpath(
464 path[len(cygdrive)] + ':' + path[len(cygdrive)+1:])
465 elif path.startswith('/libexec/'):
466 path = os.path.normpath(os.path.join(self.toolchain, path[1:]))
469 def GetGomaConfig(self, gomadir, arch, toolname):
470 """Returns a goma config dictionary if goma is available or {}."""
472 # Start goma support from os/arch/toolname that have been tested.
473 # Set NO_NACL_GOMA=true to force to avoid using goma.
474 default_no_nacl_goma = True if pynacl.platform.IsWindows() else False
475 if (arch not in ['x86-32', 'x86-64', 'pnacl']
476 or toolname not in ['newlib', 'glibc']
477 or IsEnvFlagTrue('NO_NACL_GOMA', default=default_no_nacl_goma)):
481 gomacc_base = 'gomacc.exe' if pynacl.platform.IsWindows() else 'gomacc'
482 # Search order of gomacc:
483 # --gomadir command argument -> GOMA_DIR env. -> PATH env.
485 # 1. --gomadir in the command argument.
487 search_path.append(gomadir)
488 # 2. Use GOMA_DIR environment variable if exist.
489 goma_dir_env = os.environ.get('GOMA_DIR')
491 search_path.append(goma_dir_env)
492 # 3. Append PATH env.
493 path_env = os.environ.get('PATH')
495 search_path.extend(path_env.split(os.path.pathsep))
497 for directory in search_path:
498 gomacc = os.path.join(directory, gomacc_base)
499 if os.path.isfile(gomacc):
501 port = int(subprocess.Popen(
503 stdout=subprocess.PIPE).communicate()[0].strip())
504 status = urllib2.urlopen(
505 'http://127.0.0.1:%d/healthz' % port).read().strip()
507 goma_config['gomacc'] = gomacc
509 except (OSError, ValueError, urllib2.URLError) as e:
510 # Try another gomacc in the search path.
511 self.Log('Strange gomacc %s found, try another one: %s' % (gomacc, e))
514 goma_config['burst'] = IsEnvFlagTrue('NACL_GOMA_BURST')
515 default_threads = 100 if pynacl.platform.IsLinux() else 1
516 goma_config['threads'] = GetIntegerEnv('NACL_GOMA_THREADS',
517 default=default_threads)
520 def NeedsRebuild(self, outd, out, src, rebuilt=False):
521 if not IsFile(self.toolstamp):
523 raise Error('Could not find toolchain stamp file %s.' % self.toolstamp)
527 raise Error('Could not find dependency file %s.' % outd)
531 raise Error('Could not find output file %s.' % out)
534 inputs = [__file__, self.toolstamp, src]
535 outputs = [out, outd]
537 # Find their timestamps if any.
538 input_times = [(GetMTime(f), f) for f in inputs]
539 output_times = [(GetMTime(f), f) for f in outputs]
541 # All inputs must exist.
542 missing_inputs = [p[1] for p in input_times if p[0] is None]
544 raise Error('Missing inputs: %s' % str(missing_inputs))
546 # Rebuild if any outputs are missing.
547 missing_outputs = [p[1] for p in output_times if p[0] is None]
550 raise Error('Outputs missing after rebuild: %s' % str(missing_outputs))
553 newest_input = max(input_times)
554 oldest_output = min(output_times)
556 if IsStale(oldest_output[0], newest_input[0], rebuilt):
558 raise Error('Output %s is older than toolchain stamp %s' % (
559 oldest_output[1], newest_input[1]))
562 # Decode emitted makefile.
563 with open(FixPath(outd), 'r') as fh:
565 # Remove line continuations
566 deps = deps.replace('\\\n', ' ')
567 deps = deps.replace('\n', '')
568 # The dependencies are whitespace delimited following the first ':'
569 # (that is not part of a windows drive letter)
570 deps = deps.split(':', 1)
571 if pynacl.platform.IsWindows() and len(deps[0]) == 1:
572 # The path has a drive letter, find the next ':'
573 deps = deps[1].split(':', 1)[1]
577 if pynacl.platform.IsWindows():
578 deps = [self.FixWindowsPath(d) for d in deps]
579 # Check if any input has changed.
580 for filename in deps:
581 file_tm = GetMTime(filename)
582 if IsStale(oldest_output[0], file_tm, rebuilt):
584 raise Error('Dependency %s is older than output %s.' % (
585 filename, oldest_output[1]))
589 def Compile(self, src):
590 """Compile the source with pre-determined options."""
592 compile_options = self.compile_options[:]
593 _, ext = os.path.splitext(src)
594 if ext in ['.c', '.S']:
595 bin_name = self.GetCCompiler()
596 compile_options.append('-std=gnu99')
597 if self.is_pnacl_toolchain and ext == '.S':
598 compile_options.append('-arch')
599 compile_options.append(self.arch)
600 elif ext in ['.cc', '.cpp']:
601 bin_name = self.GetCXXCompiler()
604 self.Log('Skipping unknown type %s for %s.' % (ext, src))
607 # This option is only applicable to C, and C++ compilers warn if
608 # it is present, so remove it for C++ to avoid the warning.
609 if ext != '.c' and '-Wstrict-prototypes' in compile_options:
610 compile_options.remove('-Wstrict-prototypes')
612 self.Log('\nCompile %s' % src)
614 out = self.GetObjectName(src)
617 # Don't rebuild unneeded.
618 if not self.NeedsRebuild(outd, out, src):
621 MakeDir(os.path.dirname(out))
622 self.CleanOutput(out)
623 self.CleanOutput(outd)
624 cmd_line = [bin_name, '-c', src, '-o', out,
625 '-MD', '-MF', outd] + compile_options
627 cmd_line.insert(0, self.gomacc)
628 err = self.Run(cmd_line, out)
630 self.CleanOutput(outd)
631 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
634 self.NeedsRebuild(outd, out, src, True)
636 raise Error('Failed to compile %s to %s with deps %s and cmdline:\t%s'
637 '\nNeedsRebuild returned error: %s' % (
638 src, out, outd, ' '.join(cmd_line), e))
641 def IRTLayoutFits(self, irt_file):
642 """Check if the IRT's data and text segment fit layout constraints.
644 Returns a tuple containing:
645 * whether the IRT data/text top addresses fit within the max limit
646 * current data/text top addrs
648 cmd_line = [self.GetReadElf(), '-W', '--segments', irt_file]
649 # Put LC_ALL=C in the environment for readelf, so that its messages
650 # will reliably match what we're looking for rather than being in some
651 # other language and/or character set.
652 env = dict(os.environ)
654 seginfo = self.Run(cmd_line, get_output=True, env=env)
655 lines = seginfo.splitlines()
657 for i, line in enumerate(lines):
658 if line == 'Program Headers:':
662 raise Error('Could not find Program Headers start: %s\n' % lines)
663 seg_lines = lines[ph_start:]
666 for line in seg_lines:
667 pieces = line.split()
668 # Type, Offset, Vaddr, Paddr, FileSz, MemSz, Flg(multiple), Align
669 if len(pieces) >= 8 and pieces[0] == 'LOAD':
671 segment_top = int(pieces[2], 16) + int(pieces[5], 16)
672 if pieces[6] == 'R' and pieces[7] == 'E':
673 text_top = max(segment_top, text_top)
675 if pieces[6] == 'R' or pieces[6] == 'RW':
676 data_top = max(segment_top, data_top)
678 if text_top == 0 or data_top == 0:
679 raise Error('Could not parse IRT Layout: text_top=0x%x data_top=0x%x\n'
680 'readelf output: %s\n' % (text_top, data_top, lines))
681 return (text_top <= self.irt_text_max and
682 data_top <= self.irt_data_max), text_top, data_top
684 def FindOldIRTFlagPosition(self, cmd_line, flag_name):
685 """Search for a given IRT link flag's position and value."""
688 for i, option in enumerate(cmd_line):
689 m = re.search('.*%s=(0x.*)' % flag_name, option)
692 raise Exception('Duplicate %s flag at position %d' % (flag_name, i))
694 old_start = m.group(1)
696 raise Exception('Could not find IRT layout flag %s' % flag_name)
697 return pos, old_start
699 def AdjustIRTLinkToFit(self, cmd_line, text_top, data_top):
700 """Adjust the linker options so that the IRT's data and text segment fit."""
701 def RoundDownToAlign(x):
702 return x - (x % 0x10000)
703 def AdjustFlag(flag_name, orig_max, expected_max):
704 if orig_max < expected_max:
706 pos, old_start = self.FindOldIRTFlagPosition(cmd_line, flag_name)
707 size = orig_max - int(old_start, 16)
708 self.Log('IRT %s size is %s' % (flag_name, size))
709 new_start = RoundDownToAlign(expected_max - size)
710 self.Log('Adjusting link flag %s from %s to %s' % (flag_name,
713 cmd_line[pos] = cmd_line[pos].replace(old_start, hex(new_start))
714 AdjustFlag('-Ttext-segment', text_top, self.irt_text_max)
715 AdjustFlag('-Trodata-segment', data_top, self.irt_data_max)
716 self.Log('Adjusted link options to %s' % ' '.join(cmd_line))
719 def RunLink(self, cmd_line, link_out):
720 self.CleanOutput(link_out)
721 err = self.Run(cmd_line, link_out)
723 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
725 def Link(self, srcs):
726 """Link these objects with predetermined options and output name."""
727 out = self.LinkOutputName()
728 self.Log('\nLink %s' % out)
729 bin_name = self.GetCXXCompiler()
732 if self.tls_edit is not None:
733 link_out = out + '.raw'
735 MakeDir(os.path.dirname(link_out))
737 cmd_line = [bin_name, '-o', link_out, '-Wl,--as-needed']
740 cmd_line += self.link_options
742 self.RunLink(cmd_line, link_out)
745 fits, text_top, data_top = self.IRTLayoutFits(link_out)
747 self.Log('IRT layout does not fit: text_top=0x%x and data_top=0x%x' %
748 (text_top, data_top))
749 cmd_line = self.AdjustIRTLinkToFit(cmd_line, text_top, data_top)
750 self.RunLink(cmd_line, link_out)
751 fits, text_top, data_top = self.IRTLayoutFits(link_out)
753 raise Error('Already re-linked IRT and it still does not fit:\n'
754 'text_top=0x%x and data_top=0x%x\n' % (
756 self.Log('IRT layout fits: text_top=0x%x and data_top=0x%x' %
757 (text_top, data_top))
759 if self.tls_edit is not None:
760 tls_edit_cmd = [FixPath(self.tls_edit), link_out, out]
761 tls_edit_err = self.Run(tls_edit_cmd, out)
763 raise Error('FAILED with %d: %s' % (err, ' '.join(tls_edit_cmd)))
767 # For now, only support translating a pexe, and not .o file(s)
768 def Translate(self, src):
769 """Translate a pexe to a nexe."""
770 out = self.TranslateOutputName()
771 self.Log('\nTranslate %s' % out)
772 bin_name = self.GetBinName('translate')
773 cmd_line = [bin_name, '-arch', self.arch, src, '-o', out]
774 cmd_line += self.link_options
776 err = self.Run(cmd_line, out)
778 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
781 def Archive(self, srcs):
782 """Archive these objects with predetermined options and output name."""
783 out = self.ArchiveOutputName()
784 self.Log('\nArchive %s' % out)
786 if '-r' in self.link_options:
787 bin_name = self.GetCXXCompiler()
788 cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
791 cmd_line += self.link_options
793 bin_name = self.GetAr()
794 cmd_line = [bin_name, '-rc', out]
798 MakeDir(os.path.dirname(out))
799 self.CleanOutput(out)
800 err = self.Run(cmd_line, out)
802 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
805 def Strip(self, src):
807 self.Log('\nStrip %s' % src)
809 out = self.StripOutputName()
810 pre_debug_tagging = self.UntaggedName()
811 self.CleanOutput(out)
812 self.CleanOutput(pre_debug_tagging)
814 # Strip from foo.debug to foo.untagged.
815 strip_name = self.GetStrip()
816 strip_option = '--strip-all' if self.strip_all else '--strip-debug'
817 # pnacl does not have an objcopy so there are no way to embed a link
818 if self.is_pnacl_toolchain:
819 cmd_line = [strip_name, strip_option, src, '-o', out]
820 err = self.Run(cmd_line, out)
822 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
824 cmd_line = [strip_name, strip_option, src, '-o', pre_debug_tagging]
825 err = self.Run(cmd_line, pre_debug_tagging)
827 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
829 # Tag with a debug link to foo.debug copying from foo.untagged to foo.
830 objcopy_name = self.GetObjCopy()
831 cmd_line = [objcopy_name, '--add-gnu-debuglink', src,
832 pre_debug_tagging, out]
833 err = self.Run(cmd_line, out)
835 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
837 # Drop the untagged intermediate.
838 self.CleanOutput(pre_debug_tagging)
842 def Finalize(self, src):
843 """Finalize the PEXE"""
844 self.Log('\nFinalize %s' % src)
846 out = self.StripOutputName()
847 self.CleanOutput(out)
848 bin_name = self.GetPnaclFinalize()
849 cmd_line = [bin_name, src, '-o', out]
850 err = self.Run(cmd_line, out)
852 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
855 def Generate(self, srcs):
856 """Generate final output file.
858 Link or Archive the final output file, from the compiled sources.
860 if self.outtype in ['nexe', 'pexe', 'nso']:
861 out = self.Link(srcs)
862 if self.is_pnacl_toolchain and self.finalize_pexe:
863 # Note: pnacl-finalize also does stripping.
865 elif self.strip_all or self.strip_debug:
867 elif self.outtype in ['nlib', 'plib']:
868 out = self.Archive(srcs)
872 raise Error('FAILED: --strip-all on libs will result in unusable libs.')
874 raise Error('FAILED: Unknown outtype: %s' % (self.outtype))
878 parser = OptionParser()
879 parser.add_option('--empty', dest='empty', default=False,
880 help='Do not pass sources to library.', action='store_true')
881 parser.add_option('--no-suffix', dest='suffix', default=True,
882 help='Do not append arch suffix.', action='store_false')
883 parser.add_option('--sufix', dest='suffix',
884 help='Do append arch suffix.', action='store_true')
885 parser.add_option('--strip-debug', dest='strip_debug', default=False,
886 help='Strip the NEXE for debugging', action='store_true')
887 parser.add_option('--strip-all', dest='strip_all', default=False,
888 help='Strip the NEXE for production', action='store_true')
889 parser.add_option('--strip', dest='strip', default='',
890 help='Strip the filename')
891 parser.add_option('--nonstable-pnacl', dest='finalize_pexe', default=True,
892 help='Do not finalize pnacl bitcode for ABI stability',
893 action='store_false')
894 parser.add_option('--source-list', dest='source_list',
895 help='Filename to load a source list from')
896 parser.add_option('--tls-edit', dest='tls_edit', default=None,
897 help='tls_edit location if TLS should be modified for IRT')
898 parser.add_option('--irt-layout', dest='irt_layout', default=False,
899 help='Apply the IRT layout (pick data/text seg addresses)',
901 parser.add_option('-a', '--arch', dest='arch',
902 help='Set target architecture')
903 parser.add_option('-c', '--compile', dest='compile_only', default=False,
904 help='Compile only.', action='store_true')
905 parser.add_option('-i', '--include-dirs', dest='incdirs',
906 help='Set include directories.')
907 parser.add_option('-l', '--lib-dirs', dest='libdirs',
908 help='Set library directories.')
909 parser.add_option('-n', '--name', dest='name',
910 help='Base path and name of the nexe.')
911 parser.add_option('-o', '--objdir', dest='objdir',
912 help='Base path of the object output dir.')
913 parser.add_option('-r', '--root', dest='root',
914 help='Set the root directory of the sources')
915 parser.add_option('-b', '--build', dest='build',
916 help='Set build type (<toolchain>_<outtype>, ' +
917 'where toolchain is newlib or glibc and outtype is ' +
918 'one of nexe, nlib, nso, pexe, or translate)')
919 parser.add_option('--compile_flags', dest='compile_flags',
920 help='Set compile flags.')
921 parser.add_option('--defines', dest='defines',
923 parser.add_option('--link_flags', dest='link_flags',
924 help='Set link flags.')
925 parser.add_option('-v', '--verbose', dest='verbose', default=False,
926 help='Enable verbosity', action='store_true')
927 parser.add_option('-t', '--toolpath', dest='toolpath',
928 help='Set the path for of the toolchains.')
929 parser.add_option('--config-name', dest='build_config',
930 help='GYP build configuration name (Release/Debug)')
931 parser.add_option('--gomadir', dest='gomadir',
932 help='Path of the goma directory.')
933 options, files = parser.parse_args(argv[1:])
935 if options.name is None:
936 parser.error('--name is required!')
937 if options.build_config is None:
938 parser.error('--config-name is required!')
939 if options.root is None:
940 parser.error('--root is required!')
941 if options.arch is None:
942 parser.error('--arch is required!')
943 if options.build is None:
944 parser.error('--build is required!')
951 if options.source_list:
952 source_list_handle = open(options.source_list, 'r')
953 source_list = source_list_handle.read().splitlines()
954 source_list_handle.close()
955 files = files + source_list
957 # Use set instead of list not to compile the same file twice.
958 # To keep in mind that the order of files may differ from the .gypcmd file,
959 # the set is not converted to a list.
960 # Having duplicated files can cause race condition of compiling during
961 # parallel build using goma.
962 # TODO(sbc): remove the duplication and turn it into an error.
965 # Fix slash style to insulate invoked toolchains.
966 options.toolpath = os.path.normpath(options.toolpath)
968 build = Builder(options)
971 if build.outtype == 'translate':
972 # Just translate a pexe to a nexe
974 parser.error('Pexe translation requires exactly one input file.')
975 build.Translate(list(files)[0])
978 if build.gomacc and (build.goma_burst or build.goma_threads > 1):
979 returns = Queue.Queue()
981 # Push all files into the inputs queue
982 inputs = Queue.Queue()
983 for filename in files:
986 def CompileThread(input_queue, output_queue):
990 filename = input_queue.get_nowait()
993 output_queue.put(build.Compile(filename))
995 # Put current exception info to the queue.
996 output_queue.put(sys.exc_info())
998 # Don't limit number of threads in the burst mode.
1000 num_threads = len(files)
1002 num_threads = min(build.goma_threads, len(files))
1004 # Start parallel build.
1006 for _ in xrange(num_threads):
1007 thr = threading.Thread(target=CompileThread, args=(inputs, returns))
1009 build_threads.append(thr)
1014 # An exception raised in the thread may come through the queue.
1015 # Raise it again here.
1016 if (isinstance(out, tuple) and len(out) == 3 and
1017 isinstance(out[1], Exception)):
1018 raise out[0], None, out[2]
1022 assert inputs.empty()
1024 # Wait until all threads have stopped and verify that there are no more
1026 for thr in build_threads:
1028 assert returns.empty()
1031 for filename in files:
1032 out = build.Compile(filename)
1036 # Do not link if building an object. However we still want the output file
1037 # to be what was specified in options.name
1038 if options.compile_only:
1040 raise Error('--compile mode cannot be used with multiple sources')
1041 shutil.copy(objs[0], options.name)
1043 build.Generate(objs)
1046 sys.stderr.write('%s\n' % e)
1049 if __name__ == '__main__':
1050 sys.exit(Main(sys.argv))