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 class Error(Exception):
28 # On Windows, |path| can be a long relative path: ..\..\..\..\out\Foo\bar...
29 # If the full path -- os.path.join(os.getcwd(), path) -- is longer than 255
30 # characters, then any operations that open or check for existence will fail.
31 # We can't use os.path.abspath here, because that calls into a Windows
32 # function that still has the path length limit. Instead, we'll cheat and
33 # normalize the path lexically.
34 path = os.path.normpath(os.path.join(os.getcwd(), path))
35 if sys.platform in ['win32', 'cygwin']:
37 raise Error('Path "%s" is too long (%d characters), and will fail.' % (
43 return os.path.isfile(FixPath(path))
47 outdir = FixPath(outdir)
48 if outdir and not os.path.exists(outdir):
49 # There may be a race creating this directory, so ignore failure.
57 os.remove(FixPath(path))
60 def OpenFile(path, mode='r'):
61 return open(FixPath(path), mode)
64 def RemoveQuotes(opt):
65 if opt and opt[0] == '"':
74 optlist = RemoveQuotes(opt).split(' ')
75 for optitem in optlist:
76 optitem = RemoveQuotes(optitem).replace('\\"', '"')
78 outlist.append(optitem)
82 def GetMTime(filepath):
83 """GetMTime returns the last modification time of the file or None."""
85 return os.path.getmtime(FixPath(filepath))
90 def IsStale(out_ts, src_ts, rebuilt=False):
91 # If either source or output timestamp was not available, assume stale.
92 if not out_ts or not src_ts:
95 # If just rebuilt timestamps may be equal due to time granularity.
97 return out_ts < src_ts
98 # If about to build, be conservative and rebuilt just in case.
99 return out_ts <= src_ts
102 def IsEnvFlagTrue(flag_name, default=False):
103 """Return true when the given flag is true.
106 Any values that do not match the true pattern are False.
109 flag_name: a string name of a flag.
110 default: default return value if the flag is not set.
113 True if the flag is in the true pattern. Otherwise False.
115 flag_value = os.environ.get(flag_name)
116 if flag_value is None:
118 return bool(re.search(flag_value, r'^([tTyY]|1:?)'))
121 def GetGomaConfig(gomadir, osname, arch, toolname, is_pnacl_toolchain):
122 """Returns full-path of gomacc if goma is available or None."""
123 # Start goma support from os/arch/toolname that have been tested.
124 # Set NO_NACL_GOMA=true to force to avoid using goma.
125 if (osname not in ['linux', 'mac']
126 or arch not in ['x86-32', 'x86-64', 'pnacl']
127 or toolname not in ['newlib', 'glibc']
128 or IsEnvFlagTrue('NO_NACL_GOMA', default=False)):
132 gomacc_base = 'gomacc.exe' if osname == 'win' else 'gomacc'
133 # Search order of gomacc:
134 # --gomadir command argument -> GOMA_DIR env. -> PATH env.
136 # 1. --gomadir in the command argument.
138 search_path.append(gomadir)
139 # 2. Use GOMA_DIR environment variable if exist.
140 goma_dir_env = os.environ.get('GOMA_DIR')
142 search_path.append(goma_dir_env)
143 # 3. Append PATH env.
144 path_env = os.environ.get('PATH')
146 search_path.extend(path_env.split(os.path.pathsep))
148 for directory in search_path:
149 gomacc = os.path.join(directory, gomacc_base)
150 if os.path.isfile(gomacc):
152 port = int(subprocess.Popen(
153 [gomacc, 'port'], stdout=subprocess.PIPE).communicate()[0].strip())
154 status = urllib2.urlopen(
155 'http://127.0.0.1:%d/healthz' % port).read().strip()
157 goma_config['gomacc'] = gomacc
159 except (OSError, ValueError, urllib2.URLError) as e:
160 # Try another gomacc in the search path.
161 self.Log('Strange gomacc %s found, try another one: %s' % (gomacc, e))
164 default_value = False
165 if osname == 'linux':
167 goma_config['burst'] = IsEnvFlagTrue('NACL_GOMA_BURST',
168 default=default_value)
172 class Builder(object):
173 """Builder object maintains options and generates build command-lines.
175 The Builder object takes a set of script command-line options, and generates
176 a set of paths, and command-line options for the NaCl toolchain.
178 def __init__(self, options):
181 build_type = options.build.split('_')
182 toolname = build_type[0]
183 self.outtype = build_type[1]
185 if sys.platform.startswith('linux'):
186 self.osname = 'linux'
187 elif sys.platform.startswith('win'):
189 elif sys.platform.startswith('darwin'):
192 raise Error('Toolchain OS %s not supported.' % sys.platform)
194 # pnacl toolchain can be selected in three different ways
195 # 1. by specifying --arch=pnacl directly to generate
197 # 2. by specifying --build=newlib_translate to generated
198 # nexe via translation
199 # 3. by specifying --build=newlib_{nexe,nlib}_pnacl use pnacl
200 # toolchain in native mode (e.g. the IRT shim)
201 self.is_pnacl_toolchain = False
202 if self.outtype == 'translate':
203 self.is_pnacl_toolchain = True
205 if len(build_type) > 2 and build_type[2] == 'pnacl':
206 self.is_pnacl_toolchain = True
208 if arch in ['x86-32', 'x86-64']:
210 self.subarch = arch.split('-')[1]
211 self.tool_prefix = 'x86_64-nacl-'
214 self.tool_prefix = 'arm-nacl-'
217 self.is_pnacl_toolchain = True
218 elif arch == 'pnacl':
220 self.is_pnacl_toolchain = True
222 raise Error('Toolchain architecture %s not supported.' % arch)
224 if toolname not in ['newlib', 'glibc']:
225 raise Error('Toolchain of type %s not supported.' % toolname)
227 if arch == 'arm' and toolname == 'glibc':
228 raise Error('arm glibc not yet supported.')
230 if arch == 'mips' and toolname == 'glibc':
231 raise Error('mips glibc not supported.')
233 if arch == 'pnacl' and toolname == 'glibc':
234 raise Error('pnacl glibc not yet supported.')
236 if self.is_pnacl_toolchain:
237 self.tool_prefix = 'pnacl-'
238 tooldir = '%s_pnacl' % self.osname
240 tooldir = '%s_%s_%s' % (self.osname, mainarch, toolname)
242 self.root_path = options.root
243 self.nacl_path = os.path.join(self.root_path, 'native_client')
245 project_path, project_name = os.path.split(options.name)
246 self.outdir = options.objdir
248 # Set the toolchain directories
249 self.toolchain = os.path.join(options.toolpath, tooldir)
250 self.toolbin = os.path.join(self.toolchain, 'bin')
251 self.toolstamp = os.path.join(self.toolchain, 'stamp.prep')
252 if not IsFile(self.toolstamp):
253 raise Error('Could not find toolchain prep stamp file: ' + self.toolstamp)
255 self.inc_paths = ArgToList(options.incdirs)
256 self.lib_paths = ArgToList(options.libdirs)
257 self.define_list = ArgToList(options.defines)
259 self.name = options.name
260 self.BuildCompileOptions(options.compile_flags, self.define_list)
261 self.BuildLinkOptions(options.link_flags)
262 self.BuildArchiveOptions()
263 self.verbose = options.verbose
264 self.suffix = options.suffix
265 self.strip = options.strip
266 self.empty = options.empty
267 self.strip_all = options.strip_all
268 self.strip_debug = options.strip_debug
269 self.tls_edit = options.tls_edit
270 self.finalize_pexe = options.finalize_pexe and arch == 'pnacl'
271 goma_config = GetGomaConfig(options.gomadir, self.osname, arch, toolname,
272 self.is_pnacl_toolchain)
273 self.gomacc = goma_config.get('gomacc', '')
274 self.goma_burst = goma_config.get('burst', False)
276 # Use unoptimized native objects for debug IRT builds for faster compiles.
277 if (self.is_pnacl_toolchain
278 and (self.outtype == 'nlib'
279 or self.outtype == 'nexe')
280 and self.arch != 'pnacl'):
281 if (options.build_config is not None
282 and options.build_config == 'Debug'):
283 self.compile_options.extend(['--pnacl-allow-translate',
284 '--pnacl-allow-native',
286 # Also use fast translation because we are still translating libc/libc++
287 self.link_options.append('-Wt,-O0')
289 self.Log('Compile options: %s' % self.compile_options)
290 self.Log('Linker options: %s' % self.link_options)
292 def GenNaClPath(self, path):
293 """Helper which prepends path with the native client source directory."""
294 return os.path.join(self.root_path, 'native_client', path)
296 def GetBinName(self, name):
297 """Helper which prepends executable with the toolchain bin directory."""
298 return os.path.join(self.toolbin, self.tool_prefix + name)
300 def GetCCompiler(self):
301 """Helper which returns C compiler path."""
302 if self.is_pnacl_toolchain:
303 return self.GetBinName('clang')
305 return self.GetBinName('gcc')
307 def GetCXXCompiler(self):
308 """Helper which returns C++ compiler path."""
309 if self.is_pnacl_toolchain:
310 return self.GetBinName('clang++')
312 return self.GetBinName('g++')
315 """Helper which returns ar path."""
316 return self.GetBinName('ar')
319 """Helper which returns strip path."""
320 return self.GetBinName('strip')
322 def GetObjCopy(self):
323 """Helper which returns objcopy path."""
324 return self.GetBinName('objcopy')
326 def GetPnaclFinalize(self):
327 """Helper which returns pnacl-finalize path."""
328 assert self.is_pnacl_toolchain
329 return self.GetBinName('finalize')
331 def BuildAssembleOptions(self, options):
332 options = ArgToList(options)
333 self.assemble_options = options + ['-I' + name for name in self.inc_paths]
336 return self.name + '.debug'
338 def UntaggedName(self):
339 return self.name + '.untagged'
341 def LinkOutputName(self):
342 if (self.is_pnacl_toolchain and self.finalize_pexe or
343 self.strip_all or self.strip_debug):
344 return self.DebugName()
348 def ArchiveOutputName(self):
350 return self.DebugName()
354 def StripOutputName(self):
357 def TranslateOutputName(self):
363 def BuildCompileOptions(self, options, define_list):
364 """Generates compile options, called once by __init__."""
365 options = ArgToList(options)
366 # We want to shared gyp 'defines' with other targets, but not
367 # ones that are host system dependent. Filtering them out.
368 # This really should be better.
369 # See: http://code.google.com/p/nativeclient/issues/detail?id=2936
370 define_list = [define for define in define_list
371 if not (define.startswith('NACL_TARGET_ARCH=') or
372 define.startswith('NACL_TARGET_SUBARCH=') or
373 define.startswith('NACL_WINDOWS=') or
374 define.startswith('NACL_OSX=') or
375 define.startswith('NACL_LINUX=') or
376 define == 'COMPONENT_BUILD' or
378 'WINDOWS' in define or
380 define_list.extend(['NACL_WINDOWS=0',
383 options += ['-D' + define for define in define_list]
384 self.compile_options = options + ['-I' + name for name in self.inc_paths]
386 def BuildLinkOptions(self, options):
387 """Generates link options, called once by __init__."""
388 options = ArgToList(options)
389 if self.outtype == 'nso':
390 options += ['-Wl,-rpath-link,' + name for name in self.lib_paths]
391 options += ['-shared']
392 options += ['-Wl,-soname,' + os.path.basename(self.Soname())]
393 self.link_options = options + ['-L' + name for name in self.lib_paths]
395 def BuildArchiveOptions(self):
396 """Generates link options, called once by __init__."""
397 self.archive_options = []
401 sys.stderr.write(str(msg) + '\n')
403 def Run(self, cmd_line, out):
404 """Helper which runs a command line."""
406 # For POSIX style path on windows for POSIX based toolchain
407 # (just for arguments, not for the path to the command itself)
408 cmd_line = [cmd_line[0]] + [cmd.replace('\\', '/') for cmd in cmd_line[1:]]
409 # Windows has a command line length limitation of 8191 characters.
411 if len(' '.join(cmd_line)) > 8000:
412 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
413 temp_file.write(' '.join(cmd_line[1:]))
414 cmd_line = [cmd_line[0], '@' + temp_file.name]
416 self.Log(' '.join(cmd_line))
418 if self.is_pnacl_toolchain:
419 # PNaCl toolchain executable is a script, not a binary, so it doesn't
420 # want to run on Windows without a shell
421 ecode = subprocess.call(' '.join(cmd_line), shell=True)
423 ecode = subprocess.call(cmd_line)
424 except Exception as err:
425 raise Error('%s\nFAILED: %s' % (' '.join(cmd_line), str(err)))
427 if temp_file is not None:
428 RemoveFile(temp_file.name)
431 def GetObjectName(self, src):
433 src = src.replace(self.strip,'')
434 _, filename = os.path.split(src)
435 filename, _ = os.path.splitext(filename)
437 return os.path.join(self.outdir, filename + '.o')
439 filename = os.path.split(src)[1]
440 return os.path.join(self.outdir, os.path.splitext(filename)[0] + '.o')
442 def CleanOutput(self, out):
446 def FixWindowsPath(self, path):
447 # The windows version of the nacl toolchain returns badly
448 # formed system header paths. As we do want changes in the
449 # toolchain to trigger rebuilds, compensate by detecting
450 # malformed paths (starting with /libexec/) and assume these
451 # are actually toolchain relative.
453 # Additionally, in some cases the toolchains emit cygwin paths
454 # which don't work in a win32 python.
455 # Assume they are all /cygdrive/ relative and convert to a
457 cygdrive = '/cygdrive/'
458 if path.startswith('/cygdrive/'):
459 path = os.path.normpath(
460 path[len(cygdrive)] + ':' + path[len(cygdrive)+1:])
461 elif path.startswith('/libexec/'):
462 path = os.path.normpath(os.path.join(self.toolchain, path[1:]))
465 def NeedsRebuild(self, outd, out, src, rebuilt=False):
466 if not IsFile(self.toolstamp):
468 raise Error('Could not find toolchain stamp file %s.' % self.toolstamp)
472 raise Error('Could not find dependency file %s.' % outd)
476 raise Error('Could not find output file %s.' % out)
478 stamp_tm = GetMTime(self.toolstamp)
479 out_tm = GetMTime(out)
480 outd_tm = GetMTime(outd)
481 src_tm = GetMTime(src)
482 if IsStale(out_tm, stamp_tm, rebuilt):
484 raise Error('Output %s is older than toolchain stamp %s' % (
485 out, self.toolstamp))
487 if IsStale(out_tm, src_tm, rebuilt):
489 raise Error('Output %s is older than source %s.' % (out, src))
492 if IsStale(outd_tm, src_tm, rebuilt):
494 raise Error('Dependency file is older than source %s.' % src)
497 # Decode emitted makefile.
498 with open(FixPath(outd), 'r') as fh:
500 # Remove line continuations
501 deps = deps.replace('\\\n', ' ')
502 deps = deps.replace('\n', '')
503 # The dependencies are whitespace delimited following the first ':'
504 deps = deps.split(':', 1)[1]
506 if sys.platform in ['win32', 'cygwin']:
507 deps = [self.FixWindowsPath(d) for d in deps]
508 # Check if any input has changed.
509 for filename in deps:
510 file_tm = GetMTime(filename)
511 if IsStale(out_tm, file_tm, rebuilt):
513 raise Error('Dependency %s is older than output %s.' % (
517 if IsStale(outd_tm, file_tm, rebuilt):
519 raise Error('Dependency %s is older than dep file %s.' % (
524 def Compile(self, src):
525 """Compile the source with pre-determined options."""
527 _, ext = os.path.splitext(src)
528 if ext in ['.c', '.S']:
529 bin_name = self.GetCCompiler()
530 extra = ['-std=gnu99']
531 if self.is_pnacl_toolchain and ext == '.S':
532 extra.append('-arch')
533 extra.append(self.arch)
534 elif ext in ['.cc', '.cpp']:
535 bin_name = self.GetCXXCompiler()
539 self.Log('Skipping unknown type %s for %s.' % (ext, src))
542 self.Log('\nCompile %s' % src)
544 out = self.GetObjectName(src)
547 # Don't rebuild unneeded.
548 if not self.NeedsRebuild(outd, out, src):
551 MakeDir(os.path.dirname(out))
552 self.CleanOutput(out)
553 self.CleanOutput(outd)
554 cmd_line = [bin_name, '-c', src, '-o', out,
555 '-MD', '-MF', outd] + extra + self.compile_options
557 cmd_line.insert(0, self.gomacc)
558 err = self.Run(cmd_line, out)
560 self.CleanOutput(outd)
561 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
564 self.NeedsRebuild(outd, out, src, True)
566 raise Error('Failed to compile %s to %s with deps %s and cmdline:\t%s'
567 '\nNeedsRebuild returned error: %s' % (
568 src, out, outd, ' '.join(cmd_line), e))
571 def Link(self, srcs):
572 """Link these objects with predetermined options and output name."""
573 out = self.LinkOutputName()
574 self.Log('\nLink %s' % out)
575 bin_name = self.GetCXXCompiler()
578 if self.tls_edit is not None:
579 link_out = out + '.raw'
581 MakeDir(os.path.dirname(link_out))
582 self.CleanOutput(link_out)
584 cmd_line = [bin_name, '-o', link_out, '-Wl,--as-needed']
587 cmd_line += self.link_options
589 err = self.Run(cmd_line, link_out)
591 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
593 if self.tls_edit is not None:
594 tls_edit_cmd = [FixPath(self.tls_edit), link_out, out]
595 tls_edit_err = self.Run(tls_edit_cmd, out)
597 raise Error('FAILED with %d: %s' % (err, ' '.join(tls_edit_cmd)))
601 # For now, only support translating a pexe, and not .o file(s)
602 def Translate(self, src):
603 """Translate a pexe to a nexe."""
604 out = self.TranslateOutputName()
605 self.Log('\nTranslate %s' % out)
606 bin_name = self.GetBinName('translate')
607 cmd_line = [bin_name, '-arch', self.arch, src, '-o', out]
608 cmd_line += self.link_options
610 err = self.Run(cmd_line, out)
612 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
615 def Archive(self, srcs):
616 """Archive these objects with predetermined options and output name."""
617 out = self.ArchiveOutputName()
618 self.Log('\nArchive %s' % out)
620 if '-r' in self.link_options:
621 bin_name = self.GetCXXCompiler()
622 cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
625 cmd_line += self.link_options
627 bin_name = self.GetAr()
628 cmd_line = [bin_name, '-rc', out]
632 MakeDir(os.path.dirname(out))
633 self.CleanOutput(out)
634 err = self.Run(cmd_line, out)
636 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
639 def Strip(self, src):
641 self.Log('\nStrip %s' % src)
643 out = self.StripOutputName()
644 pre_debug_tagging = self.UntaggedName()
645 self.CleanOutput(out)
646 self.CleanOutput(pre_debug_tagging)
648 # Strip from foo.debug to foo.untagged.
649 strip_name = self.GetStrip()
650 strip_option = '--strip-all' if self.strip_all else '--strip-debug'
651 # pnacl does not have an objcopy so there are no way to embed a link
652 if self.is_pnacl_toolchain:
653 cmd_line = [strip_name, strip_option, src, '-o', out]
654 err = self.Run(cmd_line, out)
656 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
658 cmd_line = [strip_name, strip_option, src, '-o', pre_debug_tagging]
659 err = self.Run(cmd_line, pre_debug_tagging)
661 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
663 # Tag with a debug link to foo.debug copying from foo.untagged to foo.
664 objcopy_name = self.GetObjCopy()
665 cmd_line = [objcopy_name, '--add-gnu-debuglink', src,
666 pre_debug_tagging, out]
667 err = self.Run(cmd_line, out)
669 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
671 # Drop the untagged intermediate.
672 self.CleanOutput(pre_debug_tagging)
676 def Finalize(self, src):
677 """Finalize the PEXE"""
678 self.Log('\nFinalize %s' % src)
680 out = self.StripOutputName()
681 self.CleanOutput(out)
682 bin_name = self.GetPnaclFinalize()
683 cmd_line = [bin_name, src, '-o', out]
684 err = self.Run(cmd_line, out)
686 raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
689 def Generate(self, srcs):
690 """Generate final output file.
692 Link or Archive the final output file, from the compiled sources.
694 if self.outtype in ['nexe', 'pexe', 'nso']:
695 out = self.Link(srcs)
696 if self.is_pnacl_toolchain and self.finalize_pexe:
697 # Note: pnacl-finalize also does stripping.
699 elif self.strip_all or self.strip_debug:
701 elif self.outtype in ['nlib', 'plib']:
702 out = self.Archive(srcs)
706 raise Error('FAILED: --strip-all on libs will result in unusable libs.')
708 raise Error('FAILED: Unknown outtype: %s' % (self.outtype))
712 parser = OptionParser()
713 parser.add_option('--empty', dest='empty', default=False,
714 help='Do not pass sources to library.', action='store_true')
715 parser.add_option('--no-suffix', dest='suffix', default=True,
716 help='Do not append arch suffix.', action='store_false')
717 parser.add_option('--sufix', dest='suffix',
718 help='Do append arch suffix.', action='store_true')
719 parser.add_option('--strip-debug', dest='strip_debug', default=False,
720 help='Strip the NEXE for debugging', action='store_true')
721 parser.add_option('--strip-all', dest='strip_all', default=False,
722 help='Strip the NEXE for production', action='store_true')
723 parser.add_option('--strip', dest='strip', default='',
724 help='Strip the filename')
725 parser.add_option('--nonstable-pnacl', dest='finalize_pexe', default=True,
726 help='Do not finalize pnacl bitcode for ABI stability',
727 action='store_false')
728 parser.add_option('--source-list', dest='source_list',
729 help='Filename to load a source list from')
730 parser.add_option('--tls-edit', dest='tls_edit', default=None,
731 help='tls_edit location if TLS should be modified for IRT')
732 parser.add_option('-a', '--arch', dest='arch',
733 help='Set target architecture')
734 parser.add_option('-c', '--compile', dest='compile_only', default=False,
735 help='Compile only.', action='store_true')
736 parser.add_option('-i', '--include-dirs', dest='incdirs',
737 help='Set include directories.')
738 parser.add_option('-l', '--lib-dirs', dest='libdirs',
739 help='Set library directories.')
740 parser.add_option('-n', '--name', dest='name',
741 help='Base path and name of the nexe.')
742 parser.add_option('-o', '--objdir', dest='objdir',
743 help='Base path of the object output dir.')
744 parser.add_option('-r', '--root', dest='root',
745 help='Set the root directory of the sources')
746 parser.add_option('-b', '--build', dest='build',
747 help='Set build type (<toolchain>_<outtype>, ' +
748 'where toolchain is newlib or glibc and outtype is ' +
749 'one of nexe, nlib, nso, pexe, or translate)')
750 parser.add_option('--compile_flags', dest='compile_flags',
751 help='Set compile flags.')
752 parser.add_option('--defines', dest='defines',
754 parser.add_option('--link_flags', dest='link_flags',
755 help='Set link flags.')
756 parser.add_option('-v', '--verbose', dest='verbose', default=False,
757 help='Enable verbosity', action='store_true')
758 parser.add_option('-t', '--toolpath', dest='toolpath',
759 help='Set the path for of the toolchains.')
760 parser.add_option('--config-name', dest='build_config',
761 help='GYP build configuration name (Release/Debug)')
762 parser.add_option('--gomadir', dest='gomadir',
763 help='Path of the goma directory.')
764 options, files = parser.parse_args(argv[1:])
771 if options.source_list:
772 source_list_handle = open(options.source_list, 'r')
773 source_list = source_list_handle.read().splitlines()
774 source_list_handle.close()
775 files = files + source_list
777 # Use set instead of list not to compile the same file twice.
778 # To keep in mind that the order of files may differ from the .gypcmd file,
779 # the set is not converted to a list.
780 # Having duplicated files can cause race condition of compiling during
781 # parallel build using goma.
782 # TODO(sbc): remove the duplication and turn it into an error.
785 # Fix slash style to insulate invoked toolchains.
786 options.toolpath = os.path.normpath(options.toolpath)
788 build = Builder(options)
791 if build.outtype == 'translate':
792 # Just translate a pexe to a nexe
794 parser.error('Pexe translation requires exactly one input file.')
795 build.Translate(list(files)[0])
798 if build.gomacc and build.goma_burst: # execute gomacc as many as possible.
799 returns = Queue.Queue()
800 def CompileThread(filename, queue):
802 queue.put(build.Compile(filename))
804 # Put current exception info to the queue.
805 queue.put(sys.exc_info())
807 # Start parallel build.
808 for filename in files:
809 thr = threading.Thread(target=CompileThread, args=(filename, returns))
811 build_threads.append(thr)
812 for thr in build_threads:
815 # An exception raised in the thread may come through the queue.
816 # Raise it again here.
817 if (isinstance(out, tuple) and len(out) == 3 and
818 isinstance(out[1], Exception)):
819 raise out[0], None, out[2]
823 for filename in files:
824 out = build.Compile(filename)
828 # Do not link if building an object. However we still want the output file
829 # to be what was specified in options.name
830 if options.compile_only:
832 raise Error('--compile mode cannot be used with multiple sources')
833 shutil.copy(objs[0], options.name)
838 sys.stderr.write('%s\n' % e)
841 if __name__ == '__main__':
842 sys.exit(Main(sys.argv))