Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / native_client / build / build_nexe.py
1 #!/usr/bin/python
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.
5
6 """NEXE building script
7
8 This module will take a set of source files, include paths, library paths, and
9 additional arguments, and use them to build.
10 """
11
12 from optparse import OptionParser
13 import os
14 import re
15 import Queue
16 import shutil
17 import subprocess
18 import sys
19 import tempfile
20 import threading
21 import urllib2
22
23 class Error(Exception):
24   pass
25
26
27 def FixPath(path):
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']:
36     if len(path) > 255:
37       raise Error('Path "%s" is too long (%d characters), and will fail.' % (
38           path, len(path)))
39   return path
40
41
42 def IsFile(path):
43   return os.path.isfile(FixPath(path))
44
45
46 def MakeDir(outdir):
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.
50     try:
51       os.makedirs(outdir)
52     except OSError:
53       pass
54
55
56 def RemoveFile(path):
57   os.remove(FixPath(path))
58
59
60 def OpenFile(path, mode='r'):
61   return open(FixPath(path), mode)
62
63
64 def RemoveQuotes(opt):
65   if opt and opt[0] == '"':
66     return opt[1:-1]
67   return opt
68
69
70 def ArgToList(opt):
71   outlist = []
72   if opt is None:
73     return outlist
74   optlist = RemoveQuotes(opt).split(' ')
75   for optitem in optlist:
76     optitem = RemoveQuotes(optitem).replace('\\"', '"')
77     if optitem:
78       outlist.append(optitem)
79   return outlist
80
81
82 def GetMTime(filepath):
83   """GetMTime returns the last modification time of the file or None."""
84   try:
85     return os.path.getmtime(FixPath(filepath))
86   except OSError:
87     return None
88
89
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:
93     return True
94
95   # If just rebuilt timestamps may be equal due to time granularity.
96   if rebuilt:
97     return out_ts < src_ts
98   # If about to build, be conservative and rebuilt just in case.
99   return out_ts <= src_ts
100
101
102 def IsEnvFlagTrue(flag_name, default=False):
103   """Return true when the given flag is true.
104
105   Note:
106   Any values that do not match the true pattern are False.
107
108   Args:
109     flag_name: a string name of a flag.
110     default: default return value if the flag is not set.
111
112   Returns:
113     True if the flag is in the true pattern.  Otherwise False.
114   """
115   flag_value = os.environ.get(flag_name)
116   if flag_value is None:
117     return default
118   return bool(re.search(flag_value, r'^([tTyY]|1:?)'))
119
120
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)):
129     return {}
130
131   goma_config = {}
132   gomacc_base = 'gomacc.exe' if osname == 'win' else 'gomacc'
133   # Search order of gomacc:
134   # --gomadir command argument -> GOMA_DIR env. -> PATH env.
135   search_path = []
136   # 1. --gomadir in the command argument.
137   if gomadir:
138     search_path.append(gomadir)
139   # 2. Use GOMA_DIR environment variable if exist.
140   goma_dir_env = os.environ.get('GOMA_DIR')
141   if goma_dir_env:
142     search_path.append(goma_dir_env)
143   # 3. Append PATH env.
144   path_env = os.environ.get('PATH')
145   if path_env:
146     search_path.extend(path_env.split(os.path.pathsep))
147
148   for directory in search_path:
149     gomacc = os.path.join(directory, gomacc_base)
150     if os.path.isfile(gomacc):
151       try:
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()
156         if status == 'ok':
157           goma_config['gomacc'] = gomacc
158           break
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))
162
163   if goma_config:
164     default_value = False
165     if osname == 'linux':
166       default_value = True
167     goma_config['burst'] = IsEnvFlagTrue('NACL_GOMA_BURST',
168                                          default=default_value)
169   return goma_config
170
171
172 class Builder(object):
173   """Builder object maintains options and generates build command-lines.
174
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.
177   """
178   def __init__(self, options):
179     arch = options.arch
180     self.arch = arch
181     build_type = options.build.split('_')
182     toolname = build_type[0]
183     self.outtype = build_type[1]
184
185     if sys.platform.startswith('linux'):
186       self.osname = 'linux'
187     elif sys.platform.startswith('win'):
188       self.osname = 'win'
189     elif sys.platform.startswith('darwin'):
190       self.osname = 'mac'
191     else:
192       raise Error('Toolchain OS %s not supported.' % sys.platform)
193
194     # pnacl toolchain can be selected in three different ways
195     # 1. by specifying --arch=pnacl directly to generate
196     #    pexe targets.
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
204
205     if len(build_type) > 2 and build_type[2] == 'pnacl':
206       self.is_pnacl_toolchain = True
207
208     if arch in ['x86-32', 'x86-64']:
209       mainarch = 'x86'
210       self.subarch = arch.split('-')[1]
211       self.tool_prefix = 'x86_64-nacl-'
212     elif arch == 'arm':
213       self.subarch = ''
214       self.tool_prefix = 'arm-nacl-'
215       mainarch = 'arm'
216     elif arch == 'mips':
217       self.is_pnacl_toolchain = True
218     elif arch == 'pnacl':
219       self.subarch = ''
220       self.is_pnacl_toolchain = True
221     else:
222       raise Error('Toolchain architecture %s not supported.' % arch)
223
224     if toolname not in ['newlib', 'glibc']:
225       raise Error('Toolchain of type %s not supported.' % toolname)
226
227     if arch == 'arm' and toolname == 'glibc':
228       raise Error('arm glibc not yet supported.')
229
230     if arch == 'mips' and toolname == 'glibc':
231       raise Error('mips glibc not supported.')
232
233     if arch == 'pnacl' and toolname == 'glibc':
234       raise Error('pnacl glibc not yet supported.')
235
236     if self.is_pnacl_toolchain:
237       self.tool_prefix = 'pnacl-'
238       tooldir = '%s_pnacl' % self.osname
239     else:
240       tooldir = '%s_%s_%s' % (self.osname, mainarch, toolname)
241
242     self.root_path = options.root
243     self.nacl_path = os.path.join(self.root_path, 'native_client')
244
245     project_path, project_name = os.path.split(options.name)
246     self.outdir = options.objdir
247
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)
254
255     self.inc_paths = ArgToList(options.incdirs)
256     self.lib_paths = ArgToList(options.libdirs)
257     self.define_list = ArgToList(options.defines)
258
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)
275
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',
285                                      '-arch', self.arch])
286         # Also use fast translation because we are still translating libc/libc++
287         self.link_options.append('-Wt,-O0')
288
289     self.Log('Compile options: %s' % self.compile_options)
290     self.Log('Linker options: %s' % self.link_options)
291
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)
295
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)
299
300   def GetCCompiler(self):
301     """Helper which returns C compiler path."""
302     if self.is_pnacl_toolchain:
303       return self.GetBinName('clang')
304     else:
305       return self.GetBinName('gcc')
306
307   def GetCXXCompiler(self):
308     """Helper which returns C++ compiler path."""
309     if self.is_pnacl_toolchain:
310       return self.GetBinName('clang++')
311     else:
312       return self.GetBinName('g++')
313
314   def GetAr(self):
315     """Helper which returns ar path."""
316     return self.GetBinName('ar')
317
318   def GetStrip(self):
319     """Helper which returns strip path."""
320     return self.GetBinName('strip')
321
322   def GetObjCopy(self):
323     """Helper which returns objcopy path."""
324     return self.GetBinName('objcopy')
325
326   def GetPnaclFinalize(self):
327     """Helper which returns pnacl-finalize path."""
328     assert self.is_pnacl_toolchain
329     return self.GetBinName('finalize')
330
331   def BuildAssembleOptions(self, options):
332     options = ArgToList(options)
333     self.assemble_options = options + ['-I' + name for name in self.inc_paths]
334
335   def DebugName(self):
336     return self.name + '.debug'
337
338   def UntaggedName(self):
339     return self.name + '.untagged'
340
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()
345     else:
346       return self.name
347
348   def ArchiveOutputName(self):
349     if self.strip_debug:
350       return self.DebugName()
351     else:
352       return self.name
353
354   def StripOutputName(self):
355     return self.name
356
357   def TranslateOutputName(self):
358     return self.name
359
360   def Soname(self):
361     return self.name
362
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
377                            'WIN32' in define or
378                            'WINDOWS' in define or
379                            'WINVER' in define)]
380     define_list.extend(['NACL_WINDOWS=0',
381                         'NACL_OSX=0',
382                         'NACL_LINUX=0'])
383     options += ['-D' + define for define in define_list]
384     self.compile_options = options + ['-I' + name for name in self.inc_paths]
385
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]
394
395   def BuildArchiveOptions(self):
396     """Generates link options, called once by __init__."""
397     self.archive_options = []
398
399   def Log(self, msg):
400     if self.verbose:
401       sys.stderr.write(str(msg) + '\n')
402
403   def Run(self, cmd_line, out):
404     """Helper which runs a command line."""
405
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.
410     temp_file = None
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]
415
416     self.Log(' '.join(cmd_line))
417     try:
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)
422       else:
423         ecode = subprocess.call(cmd_line)
424     except Exception as err:
425       raise Error('%s\nFAILED: %s' % (' '.join(cmd_line), str(err)))
426
427     if temp_file is not None:
428       RemoveFile(temp_file.name)
429     return ecode
430
431   def GetObjectName(self, src):
432     if self.strip:
433       src = src.replace(self.strip,'')
434     _, filename = os.path.split(src)
435     filename, _ = os.path.splitext(filename)
436     if self.suffix:
437       return os.path.join(self.outdir, filename + '.o')
438     else:
439       filename = os.path.split(src)[1]
440       return os.path.join(self.outdir, os.path.splitext(filename)[0] + '.o')
441
442   def CleanOutput(self, out):
443     if IsFile(out):
444       RemoveFile(out)
445
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.
452     #
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
456     # drive letter.
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:]))
463     return path
464
465   def NeedsRebuild(self, outd, out, src, rebuilt=False):
466     if not IsFile(self.toolstamp):
467       if rebuilt:
468         raise Error('Could not find toolchain stamp file %s.' % self.toolstamp)
469       return True
470     if not IsFile(outd):
471       if rebuilt:
472         raise Error('Could not find dependency file %s.' % outd)
473       return True
474     if not IsFile(out):
475       if rebuilt:
476         raise Error('Could not find output file %s.' % out)
477       return True
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):
483       if rebuilt:
484         raise Error('Output %s is older than toolchain stamp %s' % (
485             out, self.toolstamp))
486       return True
487     if IsStale(out_tm, src_tm, rebuilt):
488       if rebuilt:
489         raise Error('Output %s is older than source %s.' % (out, src))
490       return True
491
492     if IsStale(outd_tm, src_tm, rebuilt):
493       if rebuilt:
494         raise Error('Dependency file is older than source %s.' % src)
495       return True
496
497     # Decode emitted makefile.
498     with open(FixPath(outd), 'r') as fh:
499       deps = fh.read()
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]
505     deps = deps.split()
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):
512         if rebuilt:
513           raise Error('Dependency %s is older than output %s.' % (
514               filename, out))
515         return True
516
517       if IsStale(outd_tm, file_tm, rebuilt):
518         if rebuilt:
519           raise Error('Dependency %s is older than dep file %s.' % (
520               filename, outd))
521         return True
522     return False
523
524   def Compile(self, src):
525     """Compile the source with pre-determined options."""
526
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()
536       extra = []
537     else:
538       if ext != '.h':
539         self.Log('Skipping unknown type %s for %s.' % (ext, src))
540       return None
541
542     self.Log('\nCompile %s' % src)
543
544     out = self.GetObjectName(src)
545     outd = out + '.d'
546
547     # Don't rebuild unneeded.
548     if not self.NeedsRebuild(outd, out, src):
549       return out
550
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
556     if self.gomacc:
557       cmd_line.insert(0, self.gomacc)
558     err = self.Run(cmd_line, out)
559     if err:
560       self.CleanOutput(outd)
561       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
562     else:
563       try:
564         self.NeedsRebuild(outd, out, src, True)
565       except Error as e:
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))
569     return out
570
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()
576
577     link_out = out
578     if self.tls_edit is not None:
579       link_out = out + '.raw'
580
581     MakeDir(os.path.dirname(link_out))
582     self.CleanOutput(link_out)
583
584     cmd_line = [bin_name, '-o', link_out, '-Wl,--as-needed']
585     if not self.empty:
586       cmd_line += srcs
587     cmd_line += self.link_options
588
589     err = self.Run(cmd_line, link_out)
590     if err:
591       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
592
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)
596       if tls_edit_err:
597         raise Error('FAILED with %d: %s' % (err, ' '.join(tls_edit_cmd)))
598
599     return out
600
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
609
610     err = self.Run(cmd_line, out)
611     if err:
612       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
613     return out
614
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)
619
620     if '-r' in self.link_options:
621       bin_name = self.GetCXXCompiler()
622       cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
623       if not self.empty:
624         cmd_line += srcs
625       cmd_line += self.link_options
626     else:
627       bin_name = self.GetAr()
628       cmd_line = [bin_name, '-rc', out]
629       if not self.empty:
630         cmd_line += srcs
631
632     MakeDir(os.path.dirname(out))
633     self.CleanOutput(out)
634     err = self.Run(cmd_line, out)
635     if err:
636       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
637     return out
638
639   def Strip(self, src):
640     """Strip the NEXE"""
641     self.Log('\nStrip %s' % src)
642
643     out = self.StripOutputName()
644     pre_debug_tagging = self.UntaggedName()
645     self.CleanOutput(out)
646     self.CleanOutput(pre_debug_tagging)
647
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)
655       if err:
656         raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
657     else:
658       cmd_line = [strip_name, strip_option, src, '-o', pre_debug_tagging]
659       err = self.Run(cmd_line, pre_debug_tagging)
660       if err:
661         raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
662
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)
668       if err:
669         raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
670
671       # Drop the untagged intermediate.
672       self.CleanOutput(pre_debug_tagging)
673
674     return out
675
676   def Finalize(self, src):
677     """Finalize the PEXE"""
678     self.Log('\nFinalize %s' % src)
679
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)
685     if err:
686       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
687     return out
688
689   def Generate(self, srcs):
690     """Generate final output file.
691
692     Link or Archive the final output file, from the compiled sources.
693     """
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.
698         self.Finalize(out)
699       elif self.strip_all or self.strip_debug:
700         self.Strip(out)
701     elif self.outtype in ['nlib', 'plib']:
702       out = self.Archive(srcs)
703       if self.strip_debug:
704         self.Strip(out)
705       elif self.strip_all:
706         raise Error('FAILED: --strip-all on libs will result in unusable libs.')
707     else:
708       raise Error('FAILED: Unknown outtype: %s' % (self.outtype))
709
710
711 def Main(argv):
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',
753                     help='Set 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:])
765
766   if not argv:
767     parser.print_help()
768     return 1
769
770   try:
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
776
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.
783     files = set(files)
784
785     # Fix slash style to insulate invoked toolchains.
786     options.toolpath = os.path.normpath(options.toolpath)
787
788     build = Builder(options)
789     objs = []
790
791     if build.outtype == 'translate':
792       # Just translate a pexe to a nexe
793       if len(files) != 1:
794         parser.error('Pexe translation requires exactly one input file.')
795       build.Translate(list(files)[0])
796       return 0
797
798     if build.gomacc and build.goma_burst:  # execute gomacc as many as possible.
799       returns = Queue.Queue()
800       def CompileThread(filename, queue):
801         try:
802           queue.put(build.Compile(filename))
803         except Exception:
804           # Put current exception info to the queue.
805           queue.put(sys.exc_info())
806       build_threads = []
807       # Start parallel build.
808       for filename in files:
809         thr = threading.Thread(target=CompileThread, args=(filename, returns))
810         thr.start()
811         build_threads.append(thr)
812       for thr in build_threads:
813         thr.join()
814         out = returns.get()
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]
820         elif out:
821           objs.append(out)
822     else:  # slow path.
823       for filename in files:
824         out = build.Compile(filename)
825         if out:
826           objs.append(out)
827
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:
831       if len(objs) > 1:
832         raise Error('--compile mode cannot be used with multiple sources')
833       shutil.copy(objs[0], options.name)
834     else:
835       build.Generate(objs)
836     return 0
837   except Error as e:
838     sys.stderr.write('%s\n' % e)
839     return 1
840
841 if __name__ == '__main__':
842   sys.exit(Main(sys.argv))