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