e376e98a4ede246c944bfb0b85ef39fb014bff17
[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 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
24 import pynacl.platform
25
26 class Error(Exception):
27   pass
28
29
30 def FixPath(path):
31   # On Windows, |path| can be a long relative path: ..\..\..\..\out\Foo\bar...
32   # If the full path -- os.path.join(os.getcwd(), path) -- is longer than 255
33   # characters, then any operations that open or check for existence will fail.
34   # We can't use os.path.abspath here, because that calls into a Windows
35   # function that still has the path length limit. Instead, we'll cheat and
36   # normalize the path lexically.
37   path = os.path.normpath(os.path.join(os.getcwd(), path))
38   if pynacl.platform.IsWindows():
39     if len(path) > 255:
40       raise Error('Path "%s" is too long (%d characters), and will fail.' % (
41           path, len(path)))
42   return path
43
44
45 def IsFile(path):
46   return os.path.isfile(FixPath(path))
47
48
49 def MakeDir(outdir):
50   outdir = FixPath(outdir)
51   if outdir and not os.path.exists(outdir):
52     # There may be a race creating this directory, so ignore failure.
53     try:
54       os.makedirs(outdir)
55     except OSError:
56       pass
57
58
59 def RemoveFile(path):
60   os.remove(FixPath(path))
61
62
63 def OpenFile(path, mode='r'):
64   return open(FixPath(path), mode)
65
66
67 def RemoveQuotes(opt):
68   if opt and opt[0] == '"':
69     return opt[1:-1]
70   return opt
71
72
73 def ArgToList(opt):
74   outlist = []
75   if opt is None:
76     return outlist
77   optlist = RemoveQuotes(opt).split(' ')
78   for optitem in optlist:
79     optitem = RemoveQuotes(optitem).replace('\\"', '"')
80     if optitem:
81       outlist.append(optitem)
82   return outlist
83
84
85 def GetMTime(filepath):
86   """GetMTime returns the last modification time of the file or None."""
87   try:
88     return os.path.getmtime(FixPath(filepath))
89   except OSError:
90     return None
91
92
93 def IsStale(out_ts, src_ts, rebuilt=False):
94   # If either source or output timestamp was not available, assume stale.
95   if not out_ts or not src_ts:
96     return True
97
98   # If just rebuilt timestamps may be equal due to time granularity.
99   if rebuilt:
100     return out_ts < src_ts
101   # If about to build, be conservative and rebuilt just in case.
102   return out_ts <= src_ts
103
104
105 def IsEnvFlagTrue(flag_name, default=False):
106   """Return true when the given flag is true.
107
108   Note:
109   Any values that do not match the true pattern are False.
110
111   Args:
112     flag_name: a string name of a flag.
113     default: default return value if the flag is not set.
114
115   Returns:
116     True if the flag is in the true pattern.  Otherwise False.
117   """
118   flag_value = os.environ.get(flag_name)
119   if flag_value is None:
120     return default
121   return bool(re.search(r'^([tTyY]|1:?)', flag_value))
122
123
124 def GetIntegerEnv(flag_name, default=0):
125   """Parses and returns integer environment variable.
126
127   Args:
128     flag_name: a string name of a flag.
129     default: default return value if the flag is not set.
130
131   Returns:
132     Integer value of the flag.
133   """
134   flag_value = os.environ.get(flag_name)
135   if flag_value is None:
136     return default
137   try:
138     return int(flag_value)
139   except ValueError:
140     raise Error('Invalid ' + flag_name + ': ' + flag_value)
141
142
143 class Builder(object):
144   """Builder object maintains options and generates build command-lines.
145
146   The Builder object takes a set of script command-line options, and generates
147   a set of paths, and command-line options for the NaCl toolchain.
148   """
149   def __init__(self, options):
150     arch = options.arch
151     self.arch = arch
152     build_type = options.build.split('_')
153     toolname = build_type[0]
154     self.outtype = build_type[1]
155     self.osname = pynacl.platform.GetOS()
156
157     # pnacl toolchain can be selected in three different ways
158     # 1. by specifying --arch=pnacl directly to generate
159     #    pexe targets.
160     # 2. by specifying --build=newlib_translate to generated
161     #    nexe via translation
162     # 3. by specifying --build=newlib_{nexe,nlib}_pnacl use pnacl
163     #    toolchain in native mode (e.g. the IRT shim)
164     self.is_pnacl_toolchain = False
165     if self.outtype == 'translate':
166       self.is_pnacl_toolchain = True
167
168     if len(build_type) > 2 and build_type[2] == 'pnacl':
169       self.is_pnacl_toolchain = True
170
171     if arch.endswith('-nonsfi'):
172       arch = arch[:-len('-nonsfi')]
173
174     if arch in ['x86-32', 'x86-64']:
175       mainarch = 'x86'
176       self.tool_prefix = 'x86_64-nacl-'
177     elif arch == 'arm':
178       self.tool_prefix = 'arm-nacl-'
179       mainarch = 'arm'
180     elif arch == 'mips':
181       self.is_pnacl_toolchain = True
182     elif arch == 'pnacl':
183       self.is_pnacl_toolchain = True
184     else:
185       raise Error('Toolchain architecture %s not supported.' % arch)
186
187     if toolname not in ['newlib', 'glibc']:
188       raise Error('Toolchain of type %s not supported.' % toolname)
189
190     if arch == 'arm' and toolname == 'glibc':
191       raise Error('arm glibc not yet supported.')
192
193     if arch == 'mips' and toolname == 'glibc':
194       raise Error('mips glibc not supported.')
195
196     if arch == 'pnacl' and toolname == 'glibc':
197       raise Error('pnacl glibc not yet supported.')
198
199     if self.is_pnacl_toolchain:
200       self.tool_prefix = 'pnacl-'
201       tool_subdir = 'pnacl_newlib'
202     else:
203       tool_subdir = 'nacl_%s_%s' % (mainarch, toolname)
204
205     build_arch = pynacl.platform.GetArch()
206     tooldir = os.path.join('%s_%s' % (self.osname, build_arch), tool_subdir)
207
208     self.root_path = options.root
209     self.nacl_path = os.path.join(self.root_path, 'native_client')
210
211     project_path, project_name = os.path.split(options.name)
212     self.outdir = options.objdir
213
214     # Set the toolchain directories
215     self.toolchain = os.path.join(options.toolpath, tooldir)
216     self.toolbin = os.path.join(self.toolchain, 'bin')
217     self.toolstamp = os.path.join(self.toolchain, 'stamp.prep')
218     if not IsFile(self.toolstamp):
219       raise Error('Could not find toolchain prep stamp file: ' + self.toolstamp)
220
221     self.inc_paths = ArgToList(options.incdirs)
222     self.lib_paths = ArgToList(options.libdirs)
223     self.define_list = ArgToList(options.defines)
224
225     self.name = options.name
226     self.BuildCompileOptions(options.compile_flags, self.define_list)
227     self.BuildLinkOptions(options.link_flags)
228     self.BuildArchiveOptions()
229     self.verbose = options.verbose
230     self.suffix = options.suffix
231     self.strip = options.strip
232     self.empty = options.empty
233     self.strip_all = options.strip_all
234     self.strip_debug = options.strip_debug
235     self.tls_edit = options.tls_edit
236     self.finalize_pexe = options.finalize_pexe and arch == 'pnacl'
237     goma_config = self.GetGomaConfig(options.gomadir, arch, toolname)
238     self.gomacc = goma_config.get('gomacc', '')
239     self.goma_burst = goma_config.get('burst', False)
240     self.goma_threads = goma_config.get('threads', 1)
241
242     # Define NDEBUG for Release builds.
243     if options.build_config == 'Release':
244       self.compile_options.append('-DNDEBUG')
245
246     # Use unoptimized native objects for debug IRT builds for faster compiles.
247     if (self.is_pnacl_toolchain
248         and (self.outtype == 'nlib'
249              or self.outtype == 'nexe')
250         and self.arch != 'pnacl'):
251       if (options.build_config is not None
252           and options.build_config == 'Debug'):
253         self.compile_options.extend(['--pnacl-allow-translate',
254                                      '--pnacl-allow-native',
255                                      '-arch', self.arch])
256         # Also use fast translation because we are still translating libc/libc++
257         self.link_options.append('-Wt,-O0')
258
259     self.irt_layout = options.irt_layout
260     if self.irt_layout:
261       # IRT constraints for auto layout.
262       # IRT text can only go up to 256MB. Addresses after that are for data.
263       # Reserve an extra page because:
264       # * sel_ldr requires a HLT sled at the end of the dynamic code area;
265       # * dynamic_load_test currently tests loading at the end of the dynamic
266       #   code area.
267       self.irt_text_max = 0x10000000 - 0x10000
268       # Data can only go up to the sandbox_top - sizeof(stack).
269       # NaCl allocates 16MB for the initial thread's stack (see
270       # NACL_DEFAULT_STACK_MAX in sel_ldr.h).
271       # Assume sandbox_top is 1GB, since even on x86-64 the limit would
272       # only be 2GB (rip-relative references can only be +/- 2GB).
273       sandbox_top = 0x40000000
274       self.irt_data_max = sandbox_top - (16 << 20)
275       # Initialize layout flags with "too-close-to-max" flags so that
276       # we can relax this later and get a tight fit.
277       self.link_options += [
278           '-Wl,-Ttext-segment=0x%x' % (self.irt_text_max - 0x10000),
279           '-Wl,-Trodata-segment=0x%x' % (self.irt_data_max - 0x10000)]
280     self.Log('Compile options: %s' % self.compile_options)
281     self.Log('Linker options: %s' % self.link_options)
282
283   def GenNaClPath(self, path):
284     """Helper which prepends path with the native client source directory."""
285     return os.path.join(self.root_path, 'native_client', path)
286
287   def GetBinName(self, name):
288     """Helper which prepends executable with the toolchain bin directory."""
289     return os.path.join(self.toolbin, self.tool_prefix + name)
290
291   def GetCCompiler(self):
292     """Helper which returns C compiler path."""
293     if self.is_pnacl_toolchain:
294       return self.GetBinName('clang')
295     else:
296       return self.GetBinName('gcc')
297
298   def GetCXXCompiler(self):
299     """Helper which returns C++ compiler path."""
300     if self.is_pnacl_toolchain:
301       return self.GetBinName('clang++')
302     else:
303       return self.GetBinName('g++')
304
305   def GetAr(self):
306     """Helper which returns ar path."""
307     return self.GetBinName('ar')
308
309   def GetStrip(self):
310     """Helper which returns strip path."""
311     return self.GetBinName('strip')
312
313   def GetObjCopy(self):
314     """Helper which returns objcopy path."""
315     return self.GetBinName('objcopy')
316
317   def GetReadElf(self):
318     """Helper which returns readelf path."""
319     return self.GetBinName('readelf')
320
321   def GetPnaclFinalize(self):
322     """Helper which returns pnacl-finalize path."""
323     assert self.is_pnacl_toolchain
324     return self.GetBinName('finalize')
325
326   def BuildAssembleOptions(self, options):
327     options = ArgToList(options)
328     self.assemble_options = options + ['-I' + name for name in self.inc_paths]
329
330   def DebugName(self):
331     return self.name + '.debug'
332
333   def UntaggedName(self):
334     return self.name + '.untagged'
335
336   def LinkOutputName(self):
337     if (self.is_pnacl_toolchain and self.finalize_pexe or
338         self.strip_all or self.strip_debug):
339       return self.DebugName()
340     else:
341       return self.name
342
343   def ArchiveOutputName(self):
344     if self.strip_debug:
345       return self.DebugName()
346     else:
347       return self.name
348
349   def StripOutputName(self):
350     return self.name
351
352   def TranslateOutputName(self):
353     return self.name
354
355   def Soname(self):
356     return self.name
357
358   def BuildCompileOptions(self, options, define_list):
359     """Generates compile options, called once by __init__."""
360     options = ArgToList(options)
361     # We want to shared gyp 'defines' with other targets, but not
362     # ones that are host system dependent. Filtering them out.
363     # This really should be better.
364     # See: http://code.google.com/p/nativeclient/issues/detail?id=2936
365     define_list = [define for define in define_list
366                    if not (define.startswith('NACL_TARGET_ARCH=') or
367                            define.startswith('NACL_TARGET_SUBARCH=') or
368                            define.startswith('NACL_WINDOWS=') or
369                            define.startswith('NACL_OSX=') or
370                            define.startswith('NACL_LINUX=') or
371                            define.startswith('NACL_ANDROID=') or
372                            define == 'COMPONENT_BUILD' or
373                            'WIN32' in define or
374                            'WINDOWS' in define or
375                            'WINVER' in define)]
376     define_list.extend(['NACL_WINDOWS=0',
377                         'NACL_OSX=0',
378                         'NACL_LINUX=0',
379                         'NACL_ANDROID=0'])
380     options += ['-D' + define for define in define_list]
381     self.compile_options = options + ['-I' + name for name in self.inc_paths]
382
383   def BuildLinkOptions(self, options):
384     """Generates link options, called once by __init__."""
385     options = ArgToList(options)
386     if self.outtype == 'nso':
387       options += ['-Wl,-rpath-link,' + name for name in self.lib_paths]
388       options += ['-shared']
389       options += ['-Wl,-soname,' + os.path.basename(self.Soname())]
390     self.link_options = options + ['-L' + name for name in self.lib_paths]
391
392   def BuildArchiveOptions(self):
393     """Generates link options, called once by __init__."""
394     self.archive_options = []
395
396   def Log(self, msg):
397     if self.verbose:
398       sys.stderr.write(str(msg) + '\n')
399
400   def Run(self, cmd_line, get_output=False, **kwargs):
401     """Helper which runs a command line.
402
403     Returns the error code if get_output is False.
404     Returns the output if get_output is True.
405     """
406
407     # For POSIX style path on windows for POSIX based toolchain
408     # (just for arguments, not for the path to the command itself)
409     cmd_line = [cmd_line[0]] + [cmd.replace('\\', '/') for cmd in cmd_line[1:]]
410     # Windows has a command line length limitation of 8191 characters.
411     temp_file = None
412     if len(' '.join(cmd_line)) > 8000:
413       with tempfile.NamedTemporaryFile(delete=False) as temp_file:
414         temp_file.write(' '.join(cmd_line[1:]))
415       cmd_line = [cmd_line[0], '@' + temp_file.name]
416
417     self.Log(' '.join(cmd_line))
418     try:
419       runner = subprocess.call
420       if get_output:
421         runner = subprocess.check_output
422       if self.is_pnacl_toolchain and pynacl.platform.IsWindows():
423         # PNaCl toolchain executable is a script, not a binary, so it doesn't
424         # want to run on Windows without a shell
425         result = runner(' '.join(cmd_line), shell=True, **kwargs)
426       else:
427         result = runner(cmd_line, **kwargs)
428     except Exception as err:
429       raise Error('%s\nFAILED: %s' % (' '.join(cmd_line), str(err)))
430     finally:
431       if temp_file is not None:
432         RemoveFile(temp_file.name)
433     return result
434
435   def GetObjectName(self, src):
436     if self.strip:
437       src = src.replace(self.strip,'')
438     _, filename = os.path.split(src)
439     filename, _ = os.path.splitext(filename)
440     if self.suffix:
441       return os.path.join(self.outdir, filename + '.o')
442     else:
443       filename = os.path.split(src)[1]
444       return os.path.join(self.outdir, os.path.splitext(filename)[0] + '.o')
445
446   def CleanOutput(self, out):
447     if IsFile(out):
448       RemoveFile(out)
449
450   def FixWindowsPath(self, path):
451     # The windows version of the nacl toolchain returns badly
452     # formed system header paths. As we do want changes in the
453     # toolchain to trigger rebuilds, compensate by detecting
454     # malformed paths (starting with /libexec/) and assume these
455     # are actually toolchain relative.
456     #
457     # Additionally, in some cases the toolchains emit cygwin paths
458     # which don't work in a win32 python.
459     # Assume they are all /cygdrive/ relative and convert to a
460     # drive letter.
461     cygdrive = '/cygdrive/'
462     if path.startswith('/cygdrive/'):
463       path = os.path.normpath(
464           path[len(cygdrive)] + ':' + path[len(cygdrive)+1:])
465     elif path.startswith('/libexec/'):
466       path = os.path.normpath(os.path.join(self.toolchain, path[1:]))
467     return path
468
469   def GetGomaConfig(self, gomadir, arch, toolname):
470     """Returns a goma config dictionary if goma is available or {}."""
471
472     # Start goma support from os/arch/toolname that have been tested.
473     # Set NO_NACL_GOMA=true to force to avoid using goma.
474     default_no_nacl_goma = True if pynacl.platform.IsWindows() else False
475     if (arch not in ['x86-32', 'x86-64', 'pnacl']
476         or toolname not in ['newlib', 'glibc']
477         or IsEnvFlagTrue('NO_NACL_GOMA', default=default_no_nacl_goma)):
478       return {}
479
480     goma_config = {}
481     gomacc_base = 'gomacc.exe' if pynacl.platform.IsWindows() else 'gomacc'
482     # Search order of gomacc:
483     # --gomadir command argument -> GOMA_DIR env. -> PATH env.
484     search_path = []
485     # 1. --gomadir in the command argument.
486     if gomadir:
487       search_path.append(gomadir)
488     # 2. Use GOMA_DIR environment variable if exist.
489     goma_dir_env = os.environ.get('GOMA_DIR')
490     if goma_dir_env:
491       search_path.append(goma_dir_env)
492     # 3. Append PATH env.
493     path_env = os.environ.get('PATH')
494     if path_env:
495       search_path.extend(path_env.split(os.path.pathsep))
496
497     for directory in search_path:
498       gomacc = os.path.join(directory, gomacc_base)
499       if os.path.isfile(gomacc):
500         try:
501           port = int(subprocess.Popen(
502               [gomacc, 'port'],
503               stdout=subprocess.PIPE).communicate()[0].strip())
504           status = urllib2.urlopen(
505               'http://127.0.0.1:%d/healthz' % port).read().strip()
506           if status == 'ok':
507             goma_config['gomacc'] = gomacc
508             break
509         except (OSError, ValueError, urllib2.URLError) as e:
510           # Try another gomacc in the search path.
511           self.Log('Strange gomacc %s found, try another one: %s' % (gomacc, e))
512
513     if goma_config:
514       goma_config['burst'] = IsEnvFlagTrue('NACL_GOMA_BURST')
515       default_threads = 100 if pynacl.platform.IsLinux() else 1
516       goma_config['threads'] = GetIntegerEnv('NACL_GOMA_THREADS',
517                                              default=default_threads)
518     return goma_config
519
520   def NeedsRebuild(self, outd, out, src, rebuilt=False):
521     if not IsFile(self.toolstamp):
522       if rebuilt:
523         raise Error('Could not find toolchain stamp file %s.' % self.toolstamp)
524       return True
525     if not IsFile(outd):
526       if rebuilt:
527         raise Error('Could not find dependency file %s.' % outd)
528       return True
529     if not IsFile(out):
530       if rebuilt:
531         raise Error('Could not find output file %s.' % out)
532       return True
533     stamp_tm = GetMTime(self.toolstamp)
534     out_tm = GetMTime(out)
535     outd_tm = GetMTime(outd)
536     src_tm = GetMTime(src)
537     if IsStale(out_tm, stamp_tm, rebuilt):
538       if rebuilt:
539         raise Error('Output %s is older than toolchain stamp %s' % (
540             out, self.toolstamp))
541       return True
542     if IsStale(out_tm, src_tm, rebuilt):
543       if rebuilt:
544         raise Error('Output %s is older than source %s.' % (out, src))
545       return True
546
547     if IsStale(outd_tm, src_tm, rebuilt):
548       if rebuilt:
549         raise Error('Dependency file is older than source %s.' % src)
550       return True
551
552     # Decode emitted makefile.
553     with open(FixPath(outd), 'r') as fh:
554       deps = fh.read()
555     # Remove line continuations
556     deps = deps.replace('\\\n', ' ')
557     deps = deps.replace('\n', '')
558     # The dependencies are whitespace delimited following the first ':'
559     # (that is not part of a windows drive letter)
560     deps = deps.split(':', 1)
561     if pynacl.platform.IsWindows() and len(deps[0]) == 1:
562       # The path has a drive letter, find the next ':'
563       deps = deps[1].split(':', 1)[1]
564     else:
565       deps = deps[1]
566     deps = deps.split()
567     if pynacl.platform.IsWindows():
568       deps = [self.FixWindowsPath(d) for d in deps]
569     # Check if any input has changed.
570     for filename in deps:
571       file_tm = GetMTime(filename)
572       if IsStale(out_tm, file_tm, rebuilt):
573         if rebuilt:
574           raise Error('Dependency %s is older than output %s.' % (
575               filename, out))
576         return True
577
578       if IsStale(outd_tm, file_tm, rebuilt):
579         if rebuilt:
580           raise Error('Dependency %s is older than dep file %s.' % (
581               filename, outd))
582         return True
583     return False
584
585   def Compile(self, src):
586     """Compile the source with pre-determined options."""
587
588     compile_options = self.compile_options[:]
589     _, ext = os.path.splitext(src)
590     if ext in ['.c', '.S']:
591       bin_name = self.GetCCompiler()
592       compile_options.append('-std=gnu99')
593       if self.is_pnacl_toolchain and ext == '.S':
594         compile_options.append('-arch')
595         compile_options.append(self.arch)
596     elif ext in ['.cc', '.cpp']:
597       bin_name = self.GetCXXCompiler()
598     else:
599       if ext != '.h':
600         self.Log('Skipping unknown type %s for %s.' % (ext, src))
601       return None
602
603     # This option is only applicable to C, and C++ compilers warn if
604     # it is present, so remove it for C++ to avoid the warning.
605     if ext != '.c' and '-Wstrict-prototypes' in compile_options:
606       compile_options.remove('-Wstrict-prototypes')
607
608     self.Log('\nCompile %s' % src)
609
610     out = self.GetObjectName(src)
611     outd = out + '.d'
612
613     # Don't rebuild unneeded.
614     if not self.NeedsRebuild(outd, out, src):
615       return out
616
617     MakeDir(os.path.dirname(out))
618     self.CleanOutput(out)
619     self.CleanOutput(outd)
620     cmd_line = [bin_name, '-c', src, '-o', out,
621                 '-MD', '-MF', outd] + compile_options
622     if self.gomacc:
623       cmd_line.insert(0, self.gomacc)
624     err = self.Run(cmd_line, out)
625     if err:
626       self.CleanOutput(outd)
627       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
628     else:
629       try:
630         self.NeedsRebuild(outd, out, src, True)
631       except Error as e:
632         raise Error('Failed to compile %s to %s with deps %s and cmdline:\t%s'
633                     '\nNeedsRebuild returned error: %s' % (
634                         src, out, outd, ' '.join(cmd_line), e))
635     return out
636
637   def IRTLayoutFits(self, irt_file):
638     """Check if the IRT's data and text segment fit layout constraints.
639
640     Returns a tuple containing:
641       * whether the IRT data/text top addresses fit within the max limit
642       * current data/text top addrs
643     """
644     cmd_line = [self.GetReadElf(), '-W', '--segments', irt_file]
645     # Put LC_ALL=C in the environment for readelf, so that its messages
646     # will reliably match what we're looking for rather than being in some
647     # other language and/or character set.
648     env = dict(os.environ)
649     env['LC_ALL'] = 'C'
650     seginfo = self.Run(cmd_line, get_output=True, env=env)
651     lines = seginfo.splitlines()
652     ph_start = -1
653     for i, line in enumerate(lines):
654       if line == 'Program Headers:':
655         ph_start = i + 1
656         break
657     if ph_start == -1:
658       raise Error('Could not find Program Headers start: %s\n' % lines)
659     seg_lines = lines[ph_start:]
660     text_top = 0
661     data_top = 0
662     for line in seg_lines:
663       pieces = line.split()
664       # Type, Offset, Vaddr, Paddr, FileSz, MemSz, Flg(multiple), Align
665       if len(pieces) >= 8 and pieces[0] == 'LOAD':
666         # Vaddr + MemSz
667         segment_top = int(pieces[2], 16) + int(pieces[5], 16)
668         if pieces[6] == 'R' and pieces[7] == 'E':
669           text_top = max(segment_top, text_top)
670           continue
671         if pieces[6] == 'R' or pieces[6] == 'RW':
672           data_top = max(segment_top, data_top)
673           continue
674     if text_top == 0 or data_top == 0:
675       raise Error('Could not parse IRT Layout: text_top=0x%x data_top=0x%x\n'
676                   'readelf output: %s\n' % (text_top, data_top, lines))
677     return (text_top <= self.irt_text_max and
678             data_top <= self.irt_data_max), text_top, data_top
679
680   def FindOldIRTFlagPosition(self, cmd_line, flag_name):
681     """Search for a given IRT link flag's position and value."""
682     pos = -1
683     old_start = ''
684     for i, option in enumerate(cmd_line):
685       m = re.search('.*%s=(0x.*)' % flag_name, option)
686       if m:
687         if pos != -1:
688           raise Exception('Duplicate %s flag at position %d' % (flag_name, i))
689         pos = i
690         old_start = m.group(1)
691     if pos == -1:
692       raise Exception('Could not find IRT layout flag %s' % flag_name)
693     return pos, old_start
694
695   def AdjustIRTLinkToFit(self, cmd_line, text_top, data_top):
696     """Adjust the linker options so that the IRT's data and text segment fit."""
697     def RoundDownToAlign(x):
698       return x - (x % 0x10000)
699     def AdjustFlag(flag_name, orig_max, expected_max):
700       if orig_max < expected_max:
701         return
702       pos, old_start = self.FindOldIRTFlagPosition(cmd_line, flag_name)
703       size = orig_max - int(old_start, 16)
704       self.Log('IRT %s size is %s' % (flag_name, size))
705       new_start = RoundDownToAlign(expected_max - size)
706       self.Log('Adjusting link flag %s from %s to %s' % (flag_name,
707                                                          old_start,
708                                                          hex(new_start)))
709       cmd_line[pos] = cmd_line[pos].replace(old_start, hex(new_start))
710     AdjustFlag('-Ttext-segment', text_top, self.irt_text_max)
711     AdjustFlag('-Trodata-segment', data_top, self.irt_data_max)
712     self.Log('Adjusted link options to %s' % ' '.join(cmd_line))
713     return cmd_line
714
715   def RunLink(self, cmd_line, link_out):
716     self.CleanOutput(link_out)
717     err = self.Run(cmd_line, link_out)
718     if err:
719       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
720
721   def Link(self, srcs):
722     """Link these objects with predetermined options and output name."""
723     out = self.LinkOutputName()
724     self.Log('\nLink %s' % out)
725     bin_name = self.GetCXXCompiler()
726
727     link_out = out
728     if self.tls_edit is not None:
729       link_out = out + '.raw'
730
731     MakeDir(os.path.dirname(link_out))
732
733     cmd_line = [bin_name, '-o', link_out, '-Wl,--as-needed']
734     if not self.empty:
735       cmd_line += srcs
736     cmd_line += self.link_options
737
738     self.RunLink(cmd_line, link_out)
739
740     if self.irt_layout:
741       fits, text_top, data_top = self.IRTLayoutFits(link_out)
742       if not fits:
743         self.Log('IRT layout does not fit: text_top=0x%x and data_top=0x%x' %
744                  (text_top, data_top))
745         cmd_line = self.AdjustIRTLinkToFit(cmd_line, text_top, data_top)
746         self.RunLink(cmd_line, link_out)
747         fits, text_top, data_top = self.IRTLayoutFits(link_out)
748         if not fits:
749           raise Error('Already re-linked IRT and it still does not fit:\n'
750                       'text_top=0x%x and data_top=0x%x\n' % (
751                           text_top, data_top))
752       self.Log('IRT layout fits: text_top=0x%x and data_top=0x%x' %
753                (text_top, data_top))
754
755     if self.tls_edit is not None:
756       tls_edit_cmd = [FixPath(self.tls_edit), link_out, out]
757       tls_edit_err = self.Run(tls_edit_cmd, out)
758       if tls_edit_err:
759         raise Error('FAILED with %d: %s' % (err, ' '.join(tls_edit_cmd)))
760
761     return out
762
763   # For now, only support translating a pexe, and not .o file(s)
764   def Translate(self, src):
765     """Translate a pexe to a nexe."""
766     out = self.TranslateOutputName()
767     self.Log('\nTranslate %s' % out)
768     bin_name = self.GetBinName('translate')
769     cmd_line = [bin_name, '-arch', self.arch, src, '-o', out]
770     cmd_line += self.link_options
771
772     err = self.Run(cmd_line, out)
773     if err:
774       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
775     return out
776
777   def Archive(self, srcs):
778     """Archive these objects with predetermined options and output name."""
779     out = self.ArchiveOutputName()
780     self.Log('\nArchive %s' % out)
781
782     if '-r' in self.link_options:
783       bin_name = self.GetCXXCompiler()
784       cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
785       if not self.empty:
786         cmd_line += srcs
787       cmd_line += self.link_options
788     else:
789       bin_name = self.GetAr()
790       cmd_line = [bin_name, '-rc', out]
791       if not self.empty:
792         cmd_line += srcs
793
794     MakeDir(os.path.dirname(out))
795     self.CleanOutput(out)
796     err = self.Run(cmd_line, out)
797     if err:
798       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
799     return out
800
801   def Strip(self, src):
802     """Strip the NEXE"""
803     self.Log('\nStrip %s' % src)
804
805     out = self.StripOutputName()
806     pre_debug_tagging = self.UntaggedName()
807     self.CleanOutput(out)
808     self.CleanOutput(pre_debug_tagging)
809
810     # Strip from foo.debug to foo.untagged.
811     strip_name = self.GetStrip()
812     strip_option = '--strip-all' if self.strip_all else '--strip-debug'
813     # pnacl does not have an objcopy so there are no way to embed a link
814     if self.is_pnacl_toolchain:
815       cmd_line = [strip_name, strip_option, src, '-o', out]
816       err = self.Run(cmd_line, out)
817       if err:
818         raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
819     else:
820       cmd_line = [strip_name, strip_option, src, '-o', pre_debug_tagging]
821       err = self.Run(cmd_line, pre_debug_tagging)
822       if err:
823         raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
824
825       # Tag with a debug link to foo.debug copying from foo.untagged to foo.
826       objcopy_name = self.GetObjCopy()
827       cmd_line = [objcopy_name, '--add-gnu-debuglink', src,
828                   pre_debug_tagging, out]
829       err = self.Run(cmd_line, out)
830       if err:
831         raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
832
833       # Drop the untagged intermediate.
834       self.CleanOutput(pre_debug_tagging)
835
836     return out
837
838   def Finalize(self, src):
839     """Finalize the PEXE"""
840     self.Log('\nFinalize %s' % src)
841
842     out = self.StripOutputName()
843     self.CleanOutput(out)
844     bin_name = self.GetPnaclFinalize()
845     cmd_line = [bin_name, src, '-o', out]
846     err = self.Run(cmd_line, out)
847     if err:
848       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
849     return out
850
851   def Generate(self, srcs):
852     """Generate final output file.
853
854     Link or Archive the final output file, from the compiled sources.
855     """
856     if self.outtype in ['nexe', 'pexe', 'nso']:
857       out = self.Link(srcs)
858       if self.is_pnacl_toolchain and self.finalize_pexe:
859         # Note: pnacl-finalize also does stripping.
860         self.Finalize(out)
861       elif self.strip_all or self.strip_debug:
862         self.Strip(out)
863     elif self.outtype in ['nlib', 'plib']:
864       out = self.Archive(srcs)
865       if self.strip_debug:
866         self.Strip(out)
867       elif self.strip_all:
868         raise Error('FAILED: --strip-all on libs will result in unusable libs.')
869     else:
870       raise Error('FAILED: Unknown outtype: %s' % (self.outtype))
871
872
873 def Main(argv):
874   parser = OptionParser()
875   parser.add_option('--empty', dest='empty', default=False,
876                     help='Do not pass sources to library.', action='store_true')
877   parser.add_option('--no-suffix', dest='suffix', default=True,
878                     help='Do not append arch suffix.', action='store_false')
879   parser.add_option('--sufix', dest='suffix',
880                     help='Do append arch suffix.', action='store_true')
881   parser.add_option('--strip-debug', dest='strip_debug', default=False,
882                     help='Strip the NEXE for debugging', action='store_true')
883   parser.add_option('--strip-all', dest='strip_all', default=False,
884                     help='Strip the NEXE for production', action='store_true')
885   parser.add_option('--strip', dest='strip', default='',
886                     help='Strip the filename')
887   parser.add_option('--nonstable-pnacl', dest='finalize_pexe', default=True,
888                     help='Do not finalize pnacl bitcode for ABI stability',
889                     action='store_false')
890   parser.add_option('--source-list', dest='source_list',
891                     help='Filename to load a source list from')
892   parser.add_option('--tls-edit', dest='tls_edit', default=None,
893                     help='tls_edit location if TLS should be modified for IRT')
894   parser.add_option('--irt-layout', dest='irt_layout', default=False,
895                     help='Apply the IRT layout (pick data/text seg addresses)',
896                     action='store_true')
897   parser.add_option('-a', '--arch', dest='arch',
898                     help='Set target architecture')
899   parser.add_option('-c', '--compile', dest='compile_only', default=False,
900                     help='Compile only.', action='store_true')
901   parser.add_option('-i', '--include-dirs', dest='incdirs',
902                     help='Set include directories.')
903   parser.add_option('-l', '--lib-dirs', dest='libdirs',
904                     help='Set library directories.')
905   parser.add_option('-n', '--name', dest='name',
906                     help='Base path and name of the nexe.')
907   parser.add_option('-o', '--objdir', dest='objdir',
908                     help='Base path of the object output dir.')
909   parser.add_option('-r', '--root', dest='root',
910                     help='Set the root directory of the sources')
911   parser.add_option('-b', '--build', dest='build',
912                     help='Set build type (<toolchain>_<outtype>, ' +
913                     'where toolchain is newlib or glibc and outtype is ' +
914                     'one of nexe, nlib, nso, pexe, or translate)')
915   parser.add_option('--compile_flags', dest='compile_flags',
916                     help='Set compile flags.')
917   parser.add_option('--defines', dest='defines',
918                     help='Set defines')
919   parser.add_option('--link_flags', dest='link_flags',
920                     help='Set link flags.')
921   parser.add_option('-v', '--verbose', dest='verbose', default=False,
922                     help='Enable verbosity', action='store_true')
923   parser.add_option('-t', '--toolpath', dest='toolpath',
924                     help='Set the path for of the toolchains.')
925   parser.add_option('--config-name', dest='build_config',
926                     help='GYP build configuration name (Release/Debug)')
927   parser.add_option('--gomadir', dest='gomadir',
928                     help='Path of the goma directory.')
929   options, files = parser.parse_args(argv[1:])
930
931   if not argv:
932     parser.print_help()
933     return 1
934
935   try:
936     if options.source_list:
937       source_list_handle = open(options.source_list, 'r')
938       source_list = source_list_handle.read().splitlines()
939       source_list_handle.close()
940       files = files + source_list
941
942     # Use set instead of list not to compile the same file twice.
943     # To keep in mind that the order of files may differ from the .gypcmd file,
944     # the set is not converted to a list.
945     # Having duplicated files can cause race condition of compiling during
946     # parallel build using goma.
947     # TODO(sbc): remove the duplication and turn it into an error.
948     files = set(files)
949
950     # Fix slash style to insulate invoked toolchains.
951     options.toolpath = os.path.normpath(options.toolpath)
952
953     build = Builder(options)
954     objs = []
955
956     if build.outtype == 'translate':
957       # Just translate a pexe to a nexe
958       if len(files) != 1:
959         parser.error('Pexe translation requires exactly one input file.')
960       build.Translate(list(files)[0])
961       return 0
962
963     if build.gomacc and (build.goma_burst or build.goma_threads > 1):
964       returns = Queue.Queue()
965
966       # Push all files into the inputs queue
967       inputs = Queue.Queue()
968       for filename in files:
969         inputs.put(filename)
970
971       def CompileThread(input_queue, output_queue):
972         try:
973           while True:
974             try:
975               filename = input_queue.get_nowait()
976             except Queue.Empty:
977               return
978             output_queue.put(build.Compile(filename))
979         except Exception:
980           # Put current exception info to the queue.
981           output_queue.put(sys.exc_info())
982
983       # Don't limit number of threads in the burst mode.
984       if build.goma_burst:
985         num_threads = len(files)
986       else:
987         num_threads = min(build.goma_threads, len(files))
988
989       # Start parallel build.
990       build_threads = []
991       for _ in xrange(num_threads):
992         thr = threading.Thread(target=CompileThread, args=(inputs, returns))
993         thr.start()
994         build_threads.append(thr)
995
996       # Wait for results.
997       for _ in files:
998         out = returns.get()
999         # An exception raised in the thread may come through the queue.
1000         # Raise it again here.
1001         if (isinstance(out, tuple) and len(out) == 3 and
1002             isinstance(out[1], Exception)):
1003           raise out[0], None, out[2]
1004         elif out:
1005           objs.append(out)
1006
1007       assert inputs.empty()
1008
1009       # Wait until all threads have stopped and verify that there are no more
1010       # results.
1011       for thr in build_threads:
1012         thr.join()
1013       assert returns.empty()
1014
1015     else:  # slow path.
1016       for filename in files:
1017         out = build.Compile(filename)
1018         if out:
1019           objs.append(out)
1020
1021     # Do not link if building an object. However we still want the output file
1022     # to be what was specified in options.name
1023     if options.compile_only:
1024       if len(objs) > 1:
1025         raise Error('--compile mode cannot be used with multiple sources')
1026       shutil.copy(objs[0], options.name)
1027     else:
1028       build.Generate(objs)
1029     return 0
1030   except Error as e:
1031     sys.stderr.write('%s\n' % e)
1032     return 1
1033
1034 if __name__ == '__main__':
1035   sys.exit(Main(sys.argv))