File: build_nexe.py

package info (click to toggle)
chromium-browser 41.0.2272.118-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie-kfreebsd
  • size: 2,189,132 kB
  • sloc: cpp: 9,691,462; ansic: 3,341,451; python: 712,689; asm: 518,779; xml: 208,926; java: 169,820; sh: 119,353; perl: 68,907; makefile: 28,311; yacc: 13,305; objc: 11,385; tcl: 3,186; cs: 2,225; sql: 2,217; lex: 2,215; lisp: 1,349; pascal: 1,256; awk: 407; ruby: 155; sed: 53; php: 14; exp: 11
file content (952 lines) | stat: -rw-r--r-- 34,000 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
#!/usr/bin/python
# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""NEXE building script

This module will take a set of source files, include paths, library paths, and
additional arguments, and use them to build.
"""

import hashlib
import json
from optparse import OptionParser
import os
import re
import Queue
import shutil
import StringIO
import subprocess
import sys
import tempfile
import threading
import urllib2

from build_nexe_tools import (CommandRunner, Error, FixPath,
                              IsFile, MakeDir, RemoveFile)
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import pynacl.platform


# When a header file defining NACL_BUILD_SUBARCH is introduced,
# we can simply remove this map.
# cf) https://code.google.com/p/chromium/issues/detail?id=440012.
NACL_BUILD_ARCH_MAP = {
  'x86-32': ['NACL_BUILD_ARCH=x86', 'NACL_BUILD_SUBARCH=32'],
  'x86-32-nonsfi': ['NACL_BUILD_ARCH=x86', 'NACL_BUILD_SUBARCH=32'],
  'x86-64': ['NACL_BUILD_ARCH=x86', 'NACL_BUILD_SUBARCH=64'],
  'arm': ['NACL_BUILD_ARCH=arm', 'NACL_BUILD_SUBARCH=32'],
  'arm-nonsfi': ['NACL_BUILD_ARCH=arm', 'NACL_BUILD_SUBARCH=32'],
  'mips': ['NACL_BUILD_ARCH=mips', 'NACL_BUILD_SUBARCH=32'],
  'pnacl': ['NACL_BUILD_ARCH=pnacl'],
}


def RemoveQuotes(opt):
  if opt and opt[0] == '"':
    assert opt[-1] == '"', opt
    return opt[1:-1].replace('\\"', '"')
  return opt


def ArgToList(opt):
  outlist = []
  if opt is None:
    return outlist
  optlist = opt.split(' ')
  for optitem in optlist:
    optitem = RemoveQuotes(optitem)
    if optitem:
      outlist.append(optitem)
  return outlist


def GetMTime(filepath):
  """GetMTime returns the last modification time of the file or None."""
  try:
    return os.path.getmtime(FixPath(filepath))
  except OSError:
    return None


def IsStale(out_ts, src_ts, rebuilt=False):
  # If either source or output timestamp was not available, assume stale.
  if not out_ts or not src_ts:
    return True

  # If just rebuilt timestamps may be equal due to time granularity.
  if rebuilt:
    return out_ts < src_ts
  # If about to build, be conservative and rebuilt just in case.
  return out_ts <= src_ts


def IsEnvFlagTrue(flag_name, default=False):
  """Return true when the given flag is true.

  Note:
  Any values that do not match the true pattern are False.

  Args:
    flag_name: a string name of a flag.
    default: default return value if the flag is not set.

  Returns:
    True if the flag is in the true pattern.  Otherwise False.
  """
  flag_value = os.environ.get(flag_name)
  if flag_value is None:
    return default
  return bool(re.search(r'^([tTyY]|1:?)', flag_value))


def GetIntegerEnv(flag_name, default=0):
  """Parses and returns integer environment variable.

  Args:
    flag_name: a string name of a flag.
    default: default return value if the flag is not set.

  Returns:
    Integer value of the flag.
  """
  flag_value = os.environ.get(flag_name)
  if flag_value is None:
    return default
  try:
    return int(flag_value)
  except ValueError:
    raise Error('Invalid ' + flag_name + ': ' + flag_value)


class Builder(CommandRunner):
  """Builder object maintains options and generates build command-lines.

  The Builder object takes a set of script command-line options, and generates
  a set of paths, and command-line options for the NaCl toolchain.
  """
  def __init__(self, options):
    super(Builder, self).__init__(options)
    arch = options.arch
    self.arch = arch
    build_type = options.build.split('_')
    toolname = build_type[0]
    self.outtype = build_type[1]
    self.osname = pynacl.platform.GetOS()

    # pnacl toolchain can be selected in three different ways
    # 1. by specifying --arch=pnacl directly to generate
    #    pexe targets.
    # 2. by specifying --build=newlib_translate to generated
    #    nexe via translation
    # 3. by specifying --build=newlib_{nexe,nlib}_pnacl use pnacl
    #    toolchain in native mode (e.g. the IRT shim)
    self.is_pnacl_toolchain = False
    if self.outtype == 'translate':
      self.is_pnacl_toolchain = True

    if len(build_type) > 2 and build_type[2] == 'pnacl':
      self.is_pnacl_toolchain = True

    if arch.endswith('-nonsfi'):
      arch = arch[:-len('-nonsfi')]

    if arch in ['x86-32', 'x86-64']:
      mainarch = 'x86'
      self.tool_prefix = 'x86_64-nacl-'
    elif arch == 'arm':
      self.tool_prefix = 'arm-nacl-'
      mainarch = 'arm'
    elif arch == 'mips':
      self.is_pnacl_toolchain = True
    elif arch == 'pnacl':
      self.is_pnacl_toolchain = True
    else:
      raise Error('Toolchain architecture %s not supported.' % arch)

    if toolname not in ['newlib', 'glibc']:
      raise Error('Toolchain of type %s not supported.' % toolname)

    if arch == 'arm' and toolname == 'glibc':
      raise Error('arm glibc not yet supported.')

    if arch == 'mips' and toolname == 'glibc':
      raise Error('mips glibc not supported.')

    if arch == 'pnacl' and toolname == 'glibc':
      raise Error('pnacl glibc not yet supported.')

    if self.is_pnacl_toolchain:
      self.tool_prefix = 'pnacl-'
      tool_subdir = 'pnacl_newlib'
    else:
      tool_subdir = 'nacl_%s_%s' % (mainarch, toolname)
    # The pnacl-clang, etc. tools are scripts. Note that for the CommandRunner
    # so that it can know if a shell is needed or not.
    self.SetCommandsAreScripts(self.is_pnacl_toolchain)

    build_arch = pynacl.platform.GetArch()
    tooldir = os.path.join('%s_%s' % (self.osname, build_arch), tool_subdir)

    self.root_path = options.root
    self.nacl_path = os.path.join(self.root_path, 'native_client')

    project_path, project_name = os.path.split(options.name)
    self.outdir = options.objdir

    # Set the toolchain directories
    self.toolchain = os.path.join(options.toolpath, tooldir)
    self.toolbin = os.path.join(self.toolchain, 'bin')
    self.toolstamp = os.path.join(self.toolchain, 'stamp.prep')
    if not IsFile(self.toolstamp):
      raise Error('Could not find toolchain prep stamp file: ' + self.toolstamp)

    self.inc_paths = ArgToList(options.incdirs)
    self.lib_paths = ArgToList(options.libdirs)
    self.define_list = ArgToList(options.defines)

    self.name = options.name
    self.cmd_file = options.cmd_file
    self.BuildCompileOptions(
        options.compile_flags, self.define_list, options.arch)
    self.BuildLinkOptions(options.link_flags)
    self.BuildArchiveOptions()
    self.strip = options.strip
    self.empty = options.empty
    self.strip_all = options.strip_all
    self.strip_debug = options.strip_debug
    self.tls_edit = options.tls_edit
    self.finalize_pexe = options.finalize_pexe and arch == 'pnacl'
    goma_config = self.GetGomaConfig(options.gomadir, arch, toolname)
    self.gomacc = goma_config.get('gomacc', '')
    self.goma_burst = goma_config.get('burst', False)
    self.goma_threads = goma_config.get('threads', 1)

    # Define NDEBUG for Release builds.
    if options.build_config.startswith('Release'):
      self.compile_options.append('-DNDEBUG')

    # Use unoptimized native objects for debug IRT builds for faster compiles.
    if (self.is_pnacl_toolchain
        and (self.outtype == 'nlib'
             or self.outtype == 'nexe')
        and self.arch != 'pnacl'):
      if (options.build_config is not None
          and options.build_config.startswith('Debug')):
        self.compile_options.extend(['--pnacl-allow-translate',
                                     '--pnacl-allow-native',
                                     '-arch', self.arch])
        # Also use fast translation because we are still translating libc/libc++
        self.link_options.append('-Wt,-O0')

    self.irt_linker = options.irt_linker
    self.Log('Compile options: %s' % self.compile_options)
    self.Log('Linker options: %s' % self.link_options)

  def GenNaClPath(self, path):
    """Helper which prepends path with the native client source directory."""
    return os.path.join(self.root_path, 'native_client', path)

  def GetBinName(self, name):
    """Helper which prepends executable with the toolchain bin directory."""
    return os.path.join(self.toolbin, self.tool_prefix + name)

  def GetCCompiler(self):
    """Helper which returns C compiler path."""
    if self.is_pnacl_toolchain:
      return self.GetBinName('clang')
    else:
      return self.GetBinName('gcc')

  def GetCXXCompiler(self):
    """Helper which returns C++ compiler path."""
    if self.is_pnacl_toolchain:
      return self.GetBinName('clang++')
    else:
      return self.GetBinName('g++')

  def GetAr(self):
    """Helper which returns ar path."""
    return self.GetBinName('ar')

  def GetStrip(self):
    """Helper which returns strip path."""
    return self.GetBinName('strip')

  def GetObjCopy(self):
    """Helper which returns objcopy path."""
    return self.GetBinName('objcopy')

  def GetReadElf(self):
    """Helper which returns readelf path."""
    return self.GetBinName('readelf')

  def GetPnaclFinalize(self):
    """Helper which returns pnacl-finalize path."""
    assert self.is_pnacl_toolchain
    return self.GetBinName('finalize')

  def BuildAssembleOptions(self, options):
    options = ArgToList(options)
    self.assemble_options = options + ['-I' + name for name in self.inc_paths]

  def DebugName(self):
    return self.name + '.debug'

  def UntaggedName(self):
    return self.name + '.untagged'

  def LinkOutputName(self):
    if (self.is_pnacl_toolchain and self.finalize_pexe or
        self.strip_all or self.strip_debug):
      return self.DebugName()
    else:
      return self.name

  def ArchiveOutputName(self):
    if self.strip_debug:
      return self.DebugName()
    else:
      return self.name

  def StripOutputName(self):
    return self.name

  def TranslateOutputName(self):
    return self.name

  def Soname(self):
    return self.name

  def BuildCompileOptions(self, options, define_list, arch):
    """Generates compile options, called once by __init__."""
    options = ArgToList(options)
    # We want to shared gyp 'defines' with other targets, but not
    # ones that are host system dependent. Filtering them out.
    # This really should be better.
    # See: http://code.google.com/p/nativeclient/issues/detail?id=2936
    define_list = [define for define in define_list
                   if not (define.startswith('NACL_WINDOWS=') or
                           define.startswith('NACL_OSX=') or
                           define.startswith('NACL_LINUX=') or
                           define.startswith('NACL_ANDROID=') or
                           define.startswith('NACL_BUILD_ARCH=') or
                           define.startswith('NACL_BUILD_SUBARCH=') or
                           define == 'COMPONENT_BUILD' or
                           'WIN32' in define or
                           'WINDOWS' in define or
                           'WINVER' in define)]
    define_list.extend(['NACL_WINDOWS=0',
                        'NACL_OSX=0',
                        'NACL_LINUX=0',
                        'NACL_ANDROID=0'])
    define_list.extend(NACL_BUILD_ARCH_MAP[arch])
    options += ['-D' + define for define in define_list]
    self.compile_options = options + ['-I' + name for name in self.inc_paths]

  def BuildLinkOptions(self, options):
    """Generates link options, called once by __init__."""
    options = ArgToList(options)
    if self.outtype == 'nso':
      options += ['-Wl,-rpath-link,' + name for name in self.lib_paths]
      options += ['-shared']
      options += ['-Wl,-soname,' + os.path.basename(self.Soname())]
    self.link_options = options + ['-L' + name for name in self.lib_paths]

  def BuildArchiveOptions(self):
    """Generates link options, called once by __init__."""
    self.archive_options = []

  def GetObjectName(self, src):
    if self.strip:
      src = src.replace(self.strip,'')
    # Hash the full path of the source file and add 32 bits of that hash onto
    # the end of the object file name.  This helps disambiguate files with the
    # same name, because all of the object files are placed into the same
    # directory.  Technically, the correct solution would be to preserve the
    # directory structure of the input source files inside the object file
    # directory, but doing that runs the risk of running into filename length
    # issues on Windows.
    h = hashlib.sha1()
    h.update(src)
    wart = h.hexdigest()[:8]
    _, filename = os.path.split(src)
    filename, _ = os.path.splitext(filename)
    return os.path.join(self.outdir, filename + '_' + wart + '.o')

  def FixWindowsPath(self, path):
    # The windows version of the nacl toolchain returns badly
    # formed system header paths. As we do want changes in the
    # toolchain to trigger rebuilds, compensate by detecting
    # malformed paths (starting with /libexec/) and assume these
    # are actually toolchain relative.
    #
    # Additionally, in some cases the toolchains emit cygwin paths
    # which don't work in a win32 python.
    # Assume they are all /cygdrive/ relative and convert to a
    # drive letter.
    cygdrive = '/cygdrive/'
    if path.startswith('/cygdrive/'):
      path = os.path.normpath(
          path[len(cygdrive)] + ':' + path[len(cygdrive)+1:])
    elif path.startswith('/libexec/'):
      path = os.path.normpath(os.path.join(self.toolchain, path[1:]))
    return path

  def GetGomaConfig(self, gomadir, arch, toolname):
    """Returns a goma config dictionary if goma is available or {}."""

    # Start goma support from os/arch/toolname that have been tested.
    # Set NO_NACL_GOMA=true to force to avoid using goma.
    default_no_nacl_goma = True if pynacl.platform.IsWindows() else False
    if (arch not in ['x86-32', 'x86-64', 'pnacl']
        or toolname not in ['newlib', 'glibc']
        or IsEnvFlagTrue('NO_NACL_GOMA', default=default_no_nacl_goma)
        or IsEnvFlagTrue('GOMA_DISABLED')):
      return {}

    goma_config = {}
    gomacc_base = 'gomacc.exe' if pynacl.platform.IsWindows() else 'gomacc'
    # Search order of gomacc:
    # --gomadir command argument -> GOMA_DIR env. -> PATH env.
    search_path = []
    # 1. --gomadir in the command argument.
    if gomadir:
      search_path.append(gomadir)
    # 2. Use GOMA_DIR environment variable if exist.
    goma_dir_env = os.environ.get('GOMA_DIR')
    if goma_dir_env:
      search_path.append(goma_dir_env)
    # 3. Append PATH env.
    path_env = os.environ.get('PATH')
    if path_env:
      search_path.extend(path_env.split(os.path.pathsep))

    for directory in search_path:
      gomacc = os.path.join(directory, gomacc_base)
      if os.path.isfile(gomacc):
        try:
          port = int(subprocess.Popen(
              [gomacc, 'port'],
              stdout=subprocess.PIPE).communicate()[0].strip())
          status = urllib2.urlopen(
              'http://127.0.0.1:%d/healthz' % port).read().strip()
          if status == 'ok':
            goma_config['gomacc'] = gomacc
            break
        except (OSError, ValueError, urllib2.URLError) as e:
          # Try another gomacc in the search path.
          self.Log('Strange gomacc %s found, try another one: %s' % (gomacc, e))

    if goma_config:
      goma_config['burst'] = IsEnvFlagTrue('NACL_GOMA_BURST')
      default_threads = 100 if pynacl.platform.IsLinux() else 10
      goma_config['threads'] = GetIntegerEnv('NACL_GOMA_THREADS',
                                             default=default_threads)
    return goma_config

  def NeedsRebuild(self, outd, out, src, rebuilt=False):
    if not IsFile(self.toolstamp):
      if rebuilt:
        raise Error('Could not find toolchain stamp file %s.' % self.toolstamp)
      return True
    if not IsFile(self.cmd_file):
      if rebuilt:
        raise Error('Could not find cmd file %s.' % self.cmd_file)
      return True
    if not IsFile(outd):
      if rebuilt:
        raise Error('Could not find dependency file %s.' % outd)
      return True
    if not IsFile(out):
      if rebuilt:
        raise Error('Could not find output file %s.' % out)
      return True

    inputs = [__file__, self.toolstamp, src, self.cmd_file]
    outputs = [out, outd]

    # Find their timestamps if any.
    input_times = [(GetMTime(f), f) for f in inputs]
    output_times = [(GetMTime(f), f) for f in outputs]

    # All inputs must exist.
    missing_inputs = [p[1] for p in input_times if p[0] is None]
    if missing_inputs:
      raise Error('Missing inputs: %s' % str(missing_inputs))

    # Rebuild if any outputs are missing.
    missing_outputs = [p[1] for p in output_times if p[0] is None]
    if missing_outputs:
      if rebuilt:
        raise Error('Outputs missing after rebuild: %s' % str(missing_outputs))
      return True

    newest_input = max(input_times)
    oldest_output = min(output_times)

    if IsStale(oldest_output[0], newest_input[0], rebuilt):
      if rebuilt:
        raise Error('Output %s is older than toolchain stamp %s' % (
            oldest_output[1], newest_input[1]))
      return True

    # Decode emitted makefile.
    with open(FixPath(outd), 'r') as fh:
      deps = fh.read()
    # Remove line continuations
    deps = deps.replace('\\\n', ' ')
    deps = deps.replace('\n', '')
    # The dependencies are whitespace delimited following the first ':'
    # (that is not part of a windows drive letter)
    deps = deps.split(':', 1)
    if pynacl.platform.IsWindows() and len(deps[0]) == 1:
      # The path has a drive letter, find the next ':'
      deps = deps[1].split(':', 1)[1]
    else:
      deps = deps[1]
    deps = deps.split()
    if pynacl.platform.IsWindows():
      deps = [self.FixWindowsPath(d) for d in deps]
    # Check if any input has changed.
    for filename in deps:
      file_tm = GetMTime(filename)
      if IsStale(oldest_output[0], file_tm, rebuilt):
        if rebuilt:
          raise Error('Dependency %s is older than output %s.' % (
              filename, oldest_output[1]))
        return True
    return False

  def Compile(self, src):
    """Compile the source with pre-determined options."""

    compile_options = self.compile_options[:]
    _, ext = os.path.splitext(src)
    if ext in ['.c', '.S']:
      bin_name = self.GetCCompiler()
      compile_options.append('-std=gnu99')
      if self.is_pnacl_toolchain and ext == '.S':
        compile_options.append('-arch')
        compile_options.append(self.arch)
    elif ext in ['.cc', '.cpp']:
      compile_options.append('-std=gnu++0x')
      compile_options.append('-Wno-deprecated-register')
      bin_name = self.GetCXXCompiler()
    else:
      if ext != '.h':
        self.Log('Skipping unknown type %s for %s.' % (ext, src))
      return None

    # This option is only applicable to C, and C++ compilers warn if
    # it is present, so remove it for C++ to avoid the warning.
    if ext != '.c' and '-Wstrict-prototypes' in compile_options:
      compile_options.remove('-Wstrict-prototypes')

    self.Log('\nCompile %s' % src)

    out = self.GetObjectName(src)
    outd = out + '.d'

    # Don't rebuild unneeded.
    if not self.NeedsRebuild(outd, out, src):
      return out

    MakeDir(os.path.dirname(out))
    self.CleanOutput(out)
    self.CleanOutput(outd)
    cmd_line = [bin_name, '-c', src, '-o', out,
                '-MD', '-MF', outd] + compile_options
    if self.gomacc:
      cmd_line.insert(0, self.gomacc)
    err = self.Run(cmd_line)
    if err:
      self.CleanOutput(outd)
      raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
    else:
      try:
        self.NeedsRebuild(outd, out, src, True)
      except Error as e:
        raise Error('Failed to compile %s to %s with deps %s and cmdline:\t%s'
                    '\nNeedsRebuild returned error: %s' % (
                        src, out, outd, ' '.join(cmd_line), e))
    return out

  def RunLink(self, cmd_line, link_out):
    self.CleanOutput(link_out)
    err = self.Run(cmd_line)
    if err:
      raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))

  def Link(self, srcs):
    """Link these objects with predetermined options and output name."""
    out = self.LinkOutputName()
    self.Log('\nLink %s' % out)
    bin_name = self.GetCXXCompiler()
    srcs_flags = []
    if not self.empty:
      srcs_flags += srcs
    srcs_flags += self.link_options
    # Handle an IRT link specially, using a separate script.
    if self.irt_linker:
      if self.tls_edit is None:
        raise Error('Linking the IRT requires tls_edit')
      irt_link_cmd = [sys.executable, self.irt_linker,
                      '--output=' + out,
                      '--tls-edit=' + self.tls_edit,
                      '--link-cmd=' + bin_name,
                      '--readelf-cmd=' + self.GetReadElf()]
      if self.commands_are_scripts:
        irt_link_cmd += ['--commands-are-scripts']
      irt_link_cmd += srcs_flags
      err = self.Run(irt_link_cmd, normalize_slashes=False)
      if err:
        raise Error('FAILED with %d: %s' % (err, ' '.join(irt_link_cmd)))
      return out

    MakeDir(os.path.dirname(out))
    cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
    cmd_line += srcs_flags

    self.RunLink(cmd_line, out)
    return out

  # For now, only support translating a pexe, and not .o file(s)
  def Translate(self, src):
    """Translate a pexe to a nexe."""
    out = self.TranslateOutputName()
    self.Log('\nTranslate %s' % out)
    bin_name = self.GetBinName('translate')
    cmd_line = [bin_name, '-arch', self.arch, src, '-o', out]
    cmd_line += self.link_options

    err = self.Run(cmd_line)
    if err:
      raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
    return out

  def Archive(self, srcs):
    """Archive these objects with predetermined options and output name."""
    out = self.ArchiveOutputName()
    self.Log('\nArchive %s' % out)

    if '-r' in self.link_options:
      bin_name = self.GetCXXCompiler()
      cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
      if not self.empty:
        cmd_line += srcs
      cmd_line += self.link_options
    else:
      bin_name = self.GetAr()
      cmd_line = [bin_name, '-rc', out]
      if not self.empty:
        cmd_line += srcs

    MakeDir(os.path.dirname(out))
    self.CleanOutput(out)
    err = self.Run(cmd_line)
    if err:
      raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
    return out

  def Strip(self, src):
    """Strip the NEXE"""
    self.Log('\nStrip %s' % src)

    out = self.StripOutputName()
    pre_debug_tagging = self.UntaggedName()
    self.CleanOutput(out)
    self.CleanOutput(pre_debug_tagging)

    # Strip from foo.debug to foo.untagged.
    strip_name = self.GetStrip()
    strip_option = '--strip-all' if self.strip_all else '--strip-debug'
    # pnacl does not have an objcopy so there are no way to embed a link
    if self.is_pnacl_toolchain:
      cmd_line = [strip_name, strip_option, src, '-o', out]
      err = self.Run(cmd_line)
      if err:
        raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
    else:
      cmd_line = [strip_name, strip_option, src, '-o', pre_debug_tagging]
      err = self.Run(cmd_line)
      if err:
        raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))

      # Tag with a debug link to foo.debug copying from foo.untagged to foo.
      objcopy_name = self.GetObjCopy()
      cmd_line = [objcopy_name, '--add-gnu-debuglink', src,
                  pre_debug_tagging, out]
      err = self.Run(cmd_line)
      if err:
        raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))

      # Drop the untagged intermediate.
      self.CleanOutput(pre_debug_tagging)

    return out

  def Finalize(self, src):
    """Finalize the PEXE"""
    self.Log('\nFinalize %s' % src)

    out = self.StripOutputName()
    self.CleanOutput(out)
    bin_name = self.GetPnaclFinalize()
    cmd_line = [bin_name, src, '-o', out]
    err = self.Run(cmd_line)
    if err:
      raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
    return out

  def Generate(self, srcs):
    """Generate final output file.

    Link or Archive the final output file, from the compiled sources.
    """
    if self.outtype in ['nexe', 'pexe', 'nso']:
      out = self.Link(srcs)
      if self.is_pnacl_toolchain and self.finalize_pexe:
        # Note: pnacl-finalize also does stripping.
        self.Finalize(out)
      elif self.strip_all or self.strip_debug:
        self.Strip(out)
    elif self.outtype in ['nlib', 'plib']:
      out = self.Archive(srcs)
      if self.strip_debug:
        self.Strip(out)
      elif self.strip_all:
        raise Error('FAILED: --strip-all on libs will result in unusable libs.')
    else:
      raise Error('FAILED: Unknown outtype: %s' % (self.outtype))


def UpdateBuildArgs(args, filename):
  new_cmd = json.dumps(args)

  try:
    with open(filename, 'r') as fileobj:
      old_cmd = fileobj.read()
  except:
    old_cmd = None

  if old_cmd == new_cmd:
    return False

  with open(filename, 'w') as fileobj:
    fileobj.write(new_cmd)
  return True


def Main(argv):
  parser = OptionParser()
  parser.add_option('--empty', dest='empty', default=False,
                    help='Do not pass sources to library.', action='store_true')
  parser.add_option('--no-suffix', dest='suffix', default=True,
                    help='Do not append arch suffix.', action='store_false')
  parser.add_option('--strip-debug', dest='strip_debug', default=False,
                    help='Strip the NEXE for debugging', action='store_true')
  parser.add_option('--strip-all', dest='strip_all', default=False,
                    help='Strip the NEXE for production', action='store_true')
  parser.add_option('--strip', dest='strip', default='',
                    help='Strip the filename')
  parser.add_option('--nonstable-pnacl', dest='finalize_pexe', default=True,
                    help='Do not finalize pnacl bitcode for ABI stability',
                    action='store_false')
  parser.add_option('--source-list', dest='source_list',
                    help='Filename to load a source list from')
  parser.add_option('--tls-edit', dest='tls_edit', default=None,
                    help='tls_edit location if TLS should be modified for IRT')
  parser.add_option('--irt-linker', dest='irt_linker', default=None,
                    help='linker tool to use if linking the IRT')
  parser.add_option('-a', '--arch', dest='arch',
                    help='Set target architecture')
  parser.add_option('-c', '--compile', dest='compile_only', default=False,
                    help='Compile only.', action='store_true')
  parser.add_option('-i', '--include-dirs', dest='incdirs',
                    help='Set include directories.')
  parser.add_option('-l', '--lib-dirs', dest='libdirs',
                    help='Set library directories.')
  parser.add_option('-n', '--name', dest='name',
                    help='Base path and name of the nexe.')
  parser.add_option('-o', '--objdir', dest='objdir',
                    help='Base path of the object output dir.')
  parser.add_option('-r', '--root', dest='root',
                    help='Set the root directory of the sources')
  parser.add_option('--product-directory', dest='product_directory',
                    help='Set the root directory of the build')
  parser.add_option('-b', '--build', dest='build',
                    help='Set build type (<toolchain>_<outtype>, ' +
                    'where toolchain is newlib or glibc and outtype is ' +
                    'one of nexe, nlib, nso, pexe, or translate)')
  parser.add_option('--compile_flags', dest='compile_flags',
                    help='Set compile flags.')
  parser.add_option('--defines', dest='defines',
                    help='Set defines')
  parser.add_option('--link_flags', dest='link_flags',
                    help='Set link flags.')
  parser.add_option('-v', '--verbose', dest='verbose', default=False,
                    help='Enable verbosity', action='store_true')
  parser.add_option('-t', '--toolpath', dest='toolpath',
                    help='Set the path for of the toolchains.')
  parser.add_option('--config-name', dest='build_config',
                    help='GYP build configuration name (Release/Debug)')
  parser.add_option('--gomadir', dest='gomadir',
                    help='Path of the goma directory.')
  options, files = parser.parse_args(argv[1:])

  if options.name is None:
    parser.error('--name is required!')
  if options.build_config is None:
    parser.error('--config-name is required!')
  if options.root is None:
    parser.error('--root is required!')
  if options.arch is None:
    parser.error('--arch is required!')
  if options.build is None:
    parser.error('--build is required!')

  if not argv:
    parser.print_help()
    return 1

  # Compare command-line options to last run, and force a rebuild if they
  # have changed.
  options.cmd_file = options.name + '.cmd'
  UpdateBuildArgs(argv, options.cmd_file)

  try:
    if options.source_list:
      source_list_handle = open(options.source_list, 'r')
      source_list = source_list_handle.read().splitlines()
      source_list_handle.close()

      for file_name in source_list:
        file_name = RemoveQuotes(file_name)
        if "$" in file_name:
          # Only require product directory if we need to interpolate it.  This
          # provides backwards compatibility in the cases where we don't need to
          # interpolate.  The downside is this creates a subtle landmine.
          if options.product_directory is None:
            parser.error('--product-dir is required')
          product_dir = options.product_directory
          # Normalize to forward slashes because re.sub interprets backslashes
          # as escape characters. This also simplifies the subsequent regexes.
          product_dir = product_dir.replace('\\', '/')
          # Remove fake child that may be apended to the path.
          # See untrusted.gypi.
          product_dir = re.sub(r'/+xyz$', '', product_dir)
          # The "make" backend can have an "obj" interpolation variable.
          file_name = re.sub(r'\$!?[({]?obj[)}]?', product_dir + '/obj',
                             file_name)
          # Expected patterns:
          # $!PRODUCT_DIR in ninja.
          # $(builddir) in make.
          # $(OutDir) in MSVC.
          # $(BUILT_PRODUCTS_DIR) in xcode.
          # Also strip off and re-add the trailing directory seperator because
          # different platforms are inconsistent on if it's there or not.
          # HACK assume only the product directory is the only var left.
          file_name = re.sub(r'\$!?[({]?\w+[)}]?/?', product_dir + '/',
                             file_name)
          assert "$" not in file_name, file_name
        files.append(file_name)

    # Use set instead of list not to compile the same file twice.
    # To keep in mind that the order of files may differ from the .gypcmd file,
    # the set is not converted to a list.
    # Having duplicated files can cause race condition of compiling during
    # parallel build using goma.
    # TODO(sbc): remove the duplication and turn it into an error.
    files = set(files)

    # Fix slash style to insulate invoked toolchains.
    options.toolpath = os.path.normpath(options.toolpath)

    build = Builder(options)
    objs = []

    if build.outtype == 'translate':
      # Just translate a pexe to a nexe
      if len(files) != 1:
        parser.error('Pexe translation requires exactly one input file.')
      build.Translate(list(files)[0])
      return 0

    if build.gomacc and (build.goma_burst or build.goma_threads > 1):
      returns = Queue.Queue()

      # Push all files into the inputs queue
      inputs = Queue.Queue()
      for filename in files:
        inputs.put(filename)

      def CompileThread(input_queue, output_queue):
        try:
          while True:
            try:
              filename = input_queue.get_nowait()
            except Queue.Empty:
              return
            output_queue.put(build.Compile(filename))
        except Exception:
          # Put current exception info to the queue.
          output_queue.put(sys.exc_info())

      # Don't limit number of threads in the burst mode.
      if build.goma_burst:
        num_threads = len(files)
      else:
        num_threads = min(build.goma_threads, len(files))

      # Start parallel build.
      build_threads = []
      for _ in xrange(num_threads):
        thr = threading.Thread(target=CompileThread, args=(inputs, returns))
        thr.start()
        build_threads.append(thr)

      # Wait for results.
      for _ in files:
        out = returns.get()
        # An exception raised in the thread may come through the queue.
        # Raise it again here.
        if (isinstance(out, tuple) and len(out) == 3 and
            isinstance(out[1], Exception)):
          raise out[0], None, out[2]
        elif out:
          objs.append(out)

      assert inputs.empty()

      # Wait until all threads have stopped and verify that there are no more
      # results.
      for thr in build_threads:
        thr.join()
      assert returns.empty()

    else:  # slow path.
      for filename in files:
        out = build.Compile(filename)
        if out:
          objs.append(out)

    # Do not link if building an object. However we still want the output file
    # to be what was specified in options.name
    if options.compile_only:
      if len(objs) > 1:
        raise Error('--compile mode cannot be used with multiple sources')
      shutil.copy(objs[0], options.name)
    else:
      build.Generate(objs)
    return 0
  except Error as e:
    sys.stderr.write('%s\n' % e)
    return 1
  except:
    build.EmitDeferredLog()
    raise

if __name__ == '__main__':
  sys.exit(Main(sys.argv))