Upstream version 9.37.195.0
[platform/framework/web/crosswalk.git] / src / native_client / build / build_nexe.py
1 #!/usr/bin/python
2 # Copyright (c) 2012 The Native Client Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """NEXE building script
7
8 This module will take a set of source files, include paths, library paths, and
9 additional arguments, and use them to build.
10 """
11
12 from optparse import OptionParser
13 import os
14 import re
15 import Queue
16 import shutil
17 import subprocess
18 import sys
19 import tempfile
20 import threading
21 import urllib2
22
23 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
534     inputs = [__file__, self.toolstamp, src]
535     outputs = [out, outd]
536
537     # Find their timestamps if any.
538     input_times = [(GetMTime(f), f) for f in inputs]
539     output_times = [(GetMTime(f), f) for f in outputs]
540
541     # All inputs must exist.
542     missing_inputs = [p[1] for p in input_times if p[0] is None]
543     if missing_inputs:
544       raise Error('Missing inputs: %s' % str(missing_inputs))
545
546     # Rebuild if any outputs are missing.
547     missing_outputs = [p[1] for p in output_times if p[0] is None]
548     if missing_outputs:
549       if rebuilt:
550         raise Error('Outputs missing after rebuild: %s' % str(missing_outputs))
551       return True
552
553     newest_input = max(input_times)
554     oldest_output = min(output_times)
555
556     if IsStale(oldest_output[0], newest_input[0], rebuilt):
557       if rebuilt:
558         raise Error('Output %s is older than toolchain stamp %s' % (
559             oldest_output[1], newest_input[1]))
560       return True
561
562     # Decode emitted makefile.
563     with open(FixPath(outd), 'r') as fh:
564       deps = fh.read()
565     # Remove line continuations
566     deps = deps.replace('\\\n', ' ')
567     deps = deps.replace('\n', '')
568     # The dependencies are whitespace delimited following the first ':'
569     # (that is not part of a windows drive letter)
570     deps = deps.split(':', 1)
571     if pynacl.platform.IsWindows() and len(deps[0]) == 1:
572       # The path has a drive letter, find the next ':'
573       deps = deps[1].split(':', 1)[1]
574     else:
575       deps = deps[1]
576     deps = deps.split()
577     if pynacl.platform.IsWindows():
578       deps = [self.FixWindowsPath(d) for d in deps]
579     # Check if any input has changed.
580     for filename in deps:
581       file_tm = GetMTime(filename)
582       if IsStale(oldest_output[0], file_tm, rebuilt):
583         if rebuilt:
584           raise Error('Dependency %s is older than output %s.' % (
585               filename, oldest_output[1]))
586         return True
587     return False
588
589   def Compile(self, src):
590     """Compile the source with pre-determined options."""
591
592     compile_options = self.compile_options[:]
593     _, ext = os.path.splitext(src)
594     if ext in ['.c', '.S']:
595       bin_name = self.GetCCompiler()
596       compile_options.append('-std=gnu99')
597       if self.is_pnacl_toolchain and ext == '.S':
598         compile_options.append('-arch')
599         compile_options.append(self.arch)
600     elif ext in ['.cc', '.cpp']:
601       bin_name = self.GetCXXCompiler()
602     else:
603       if ext != '.h':
604         self.Log('Skipping unknown type %s for %s.' % (ext, src))
605       return None
606
607     # This option is only applicable to C, and C++ compilers warn if
608     # it is present, so remove it for C++ to avoid the warning.
609     if ext != '.c' and '-Wstrict-prototypes' in compile_options:
610       compile_options.remove('-Wstrict-prototypes')
611
612     self.Log('\nCompile %s' % src)
613
614     out = self.GetObjectName(src)
615     outd = out + '.d'
616
617     # Don't rebuild unneeded.
618     if not self.NeedsRebuild(outd, out, src):
619       return out
620
621     MakeDir(os.path.dirname(out))
622     self.CleanOutput(out)
623     self.CleanOutput(outd)
624     cmd_line = [bin_name, '-c', src, '-o', out,
625                 '-MD', '-MF', outd] + compile_options
626     if self.gomacc:
627       cmd_line.insert(0, self.gomacc)
628     err = self.Run(cmd_line, out)
629     if err:
630       self.CleanOutput(outd)
631       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
632     else:
633       try:
634         self.NeedsRebuild(outd, out, src, True)
635       except Error as e:
636         raise Error('Failed to compile %s to %s with deps %s and cmdline:\t%s'
637                     '\nNeedsRebuild returned error: %s' % (
638                         src, out, outd, ' '.join(cmd_line), e))
639     return out
640
641   def IRTLayoutFits(self, irt_file):
642     """Check if the IRT's data and text segment fit layout constraints.
643
644     Returns a tuple containing:
645       * whether the IRT data/text top addresses fit within the max limit
646       * current data/text top addrs
647     """
648     cmd_line = [self.GetReadElf(), '-W', '--segments', irt_file]
649     # Put LC_ALL=C in the environment for readelf, so that its messages
650     # will reliably match what we're looking for rather than being in some
651     # other language and/or character set.
652     env = dict(os.environ)
653     env['LC_ALL'] = 'C'
654     seginfo = self.Run(cmd_line, get_output=True, env=env)
655     lines = seginfo.splitlines()
656     ph_start = -1
657     for i, line in enumerate(lines):
658       if line == 'Program Headers:':
659         ph_start = i + 1
660         break
661     if ph_start == -1:
662       raise Error('Could not find Program Headers start: %s\n' % lines)
663     seg_lines = lines[ph_start:]
664     text_top = 0
665     data_top = 0
666     for line in seg_lines:
667       pieces = line.split()
668       # Type, Offset, Vaddr, Paddr, FileSz, MemSz, Flg(multiple), Align
669       if len(pieces) >= 8 and pieces[0] == 'LOAD':
670         # Vaddr + MemSz
671         segment_top = int(pieces[2], 16) + int(pieces[5], 16)
672         if pieces[6] == 'R' and pieces[7] == 'E':
673           text_top = max(segment_top, text_top)
674           continue
675         if pieces[6] == 'R' or pieces[6] == 'RW':
676           data_top = max(segment_top, data_top)
677           continue
678     if text_top == 0 or data_top == 0:
679       raise Error('Could not parse IRT Layout: text_top=0x%x data_top=0x%x\n'
680                   'readelf output: %s\n' % (text_top, data_top, lines))
681     return (text_top <= self.irt_text_max and
682             data_top <= self.irt_data_max), text_top, data_top
683
684   def FindOldIRTFlagPosition(self, cmd_line, flag_name):
685     """Search for a given IRT link flag's position and value."""
686     pos = -1
687     old_start = ''
688     for i, option in enumerate(cmd_line):
689       m = re.search('.*%s=(0x.*)' % flag_name, option)
690       if m:
691         if pos != -1:
692           raise Exception('Duplicate %s flag at position %d' % (flag_name, i))
693         pos = i
694         old_start = m.group(1)
695     if pos == -1:
696       raise Exception('Could not find IRT layout flag %s' % flag_name)
697     return pos, old_start
698
699   def AdjustIRTLinkToFit(self, cmd_line, text_top, data_top):
700     """Adjust the linker options so that the IRT's data and text segment fit."""
701     def RoundDownToAlign(x):
702       return x - (x % 0x10000)
703     def AdjustFlag(flag_name, orig_max, expected_max):
704       if orig_max < expected_max:
705         return
706       pos, old_start = self.FindOldIRTFlagPosition(cmd_line, flag_name)
707       size = orig_max - int(old_start, 16)
708       self.Log('IRT %s size is %s' % (flag_name, size))
709       new_start = RoundDownToAlign(expected_max - size)
710       self.Log('Adjusting link flag %s from %s to %s' % (flag_name,
711                                                          old_start,
712                                                          hex(new_start)))
713       cmd_line[pos] = cmd_line[pos].replace(old_start, hex(new_start))
714     AdjustFlag('-Ttext-segment', text_top, self.irt_text_max)
715     AdjustFlag('-Trodata-segment', data_top, self.irt_data_max)
716     self.Log('Adjusted link options to %s' % ' '.join(cmd_line))
717     return cmd_line
718
719   def RunLink(self, cmd_line, link_out):
720     self.CleanOutput(link_out)
721     err = self.Run(cmd_line, link_out)
722     if err:
723       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
724
725   def Link(self, srcs):
726     """Link these objects with predetermined options and output name."""
727     out = self.LinkOutputName()
728     self.Log('\nLink %s' % out)
729     bin_name = self.GetCXXCompiler()
730
731     link_out = out
732     if self.tls_edit is not None:
733       link_out = out + '.raw'
734
735     MakeDir(os.path.dirname(link_out))
736
737     cmd_line = [bin_name, '-o', link_out, '-Wl,--as-needed']
738     if not self.empty:
739       cmd_line += srcs
740     cmd_line += self.link_options
741
742     self.RunLink(cmd_line, link_out)
743
744     if self.irt_layout:
745       fits, text_top, data_top = self.IRTLayoutFits(link_out)
746       if not fits:
747         self.Log('IRT layout does not fit: text_top=0x%x and data_top=0x%x' %
748                  (text_top, data_top))
749         cmd_line = self.AdjustIRTLinkToFit(cmd_line, text_top, data_top)
750         self.RunLink(cmd_line, link_out)
751         fits, text_top, data_top = self.IRTLayoutFits(link_out)
752         if not fits:
753           raise Error('Already re-linked IRT and it still does not fit:\n'
754                       'text_top=0x%x and data_top=0x%x\n' % (
755                           text_top, data_top))
756       self.Log('IRT layout fits: text_top=0x%x and data_top=0x%x' %
757                (text_top, data_top))
758
759     if self.tls_edit is not None:
760       tls_edit_cmd = [FixPath(self.tls_edit), link_out, out]
761       tls_edit_err = self.Run(tls_edit_cmd, out)
762       if tls_edit_err:
763         raise Error('FAILED with %d: %s' % (err, ' '.join(tls_edit_cmd)))
764
765     return out
766
767   # For now, only support translating a pexe, and not .o file(s)
768   def Translate(self, src):
769     """Translate a pexe to a nexe."""
770     out = self.TranslateOutputName()
771     self.Log('\nTranslate %s' % out)
772     bin_name = self.GetBinName('translate')
773     cmd_line = [bin_name, '-arch', self.arch, src, '-o', out]
774     cmd_line += self.link_options
775
776     err = self.Run(cmd_line, out)
777     if err:
778       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
779     return out
780
781   def Archive(self, srcs):
782     """Archive these objects with predetermined options and output name."""
783     out = self.ArchiveOutputName()
784     self.Log('\nArchive %s' % out)
785
786     if '-r' in self.link_options:
787       bin_name = self.GetCXXCompiler()
788       cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
789       if not self.empty:
790         cmd_line += srcs
791       cmd_line += self.link_options
792     else:
793       bin_name = self.GetAr()
794       cmd_line = [bin_name, '-rc', out]
795       if not self.empty:
796         cmd_line += srcs
797
798     MakeDir(os.path.dirname(out))
799     self.CleanOutput(out)
800     err = self.Run(cmd_line, out)
801     if err:
802       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
803     return out
804
805   def Strip(self, src):
806     """Strip the NEXE"""
807     self.Log('\nStrip %s' % src)
808
809     out = self.StripOutputName()
810     pre_debug_tagging = self.UntaggedName()
811     self.CleanOutput(out)
812     self.CleanOutput(pre_debug_tagging)
813
814     # Strip from foo.debug to foo.untagged.
815     strip_name = self.GetStrip()
816     strip_option = '--strip-all' if self.strip_all else '--strip-debug'
817     # pnacl does not have an objcopy so there are no way to embed a link
818     if self.is_pnacl_toolchain:
819       cmd_line = [strip_name, strip_option, src, '-o', out]
820       err = self.Run(cmd_line, out)
821       if err:
822         raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
823     else:
824       cmd_line = [strip_name, strip_option, src, '-o', pre_debug_tagging]
825       err = self.Run(cmd_line, pre_debug_tagging)
826       if err:
827         raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
828
829       # Tag with a debug link to foo.debug copying from foo.untagged to foo.
830       objcopy_name = self.GetObjCopy()
831       cmd_line = [objcopy_name, '--add-gnu-debuglink', src,
832                   pre_debug_tagging, out]
833       err = self.Run(cmd_line, out)
834       if err:
835         raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
836
837       # Drop the untagged intermediate.
838       self.CleanOutput(pre_debug_tagging)
839
840     return out
841
842   def Finalize(self, src):
843     """Finalize the PEXE"""
844     self.Log('\nFinalize %s' % src)
845
846     out = self.StripOutputName()
847     self.CleanOutput(out)
848     bin_name = self.GetPnaclFinalize()
849     cmd_line = [bin_name, src, '-o', out]
850     err = self.Run(cmd_line, out)
851     if err:
852       raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
853     return out
854
855   def Generate(self, srcs):
856     """Generate final output file.
857
858     Link or Archive the final output file, from the compiled sources.
859     """
860     if self.outtype in ['nexe', 'pexe', 'nso']:
861       out = self.Link(srcs)
862       if self.is_pnacl_toolchain and self.finalize_pexe:
863         # Note: pnacl-finalize also does stripping.
864         self.Finalize(out)
865       elif self.strip_all or self.strip_debug:
866         self.Strip(out)
867     elif self.outtype in ['nlib', 'plib']:
868       out = self.Archive(srcs)
869       if self.strip_debug:
870         self.Strip(out)
871       elif self.strip_all:
872         raise Error('FAILED: --strip-all on libs will result in unusable libs.')
873     else:
874       raise Error('FAILED: Unknown outtype: %s' % (self.outtype))
875
876
877 def Main(argv):
878   parser = OptionParser()
879   parser.add_option('--empty', dest='empty', default=False,
880                     help='Do not pass sources to library.', action='store_true')
881   parser.add_option('--no-suffix', dest='suffix', default=True,
882                     help='Do not append arch suffix.', action='store_false')
883   parser.add_option('--sufix', dest='suffix',
884                     help='Do append arch suffix.', action='store_true')
885   parser.add_option('--strip-debug', dest='strip_debug', default=False,
886                     help='Strip the NEXE for debugging', action='store_true')
887   parser.add_option('--strip-all', dest='strip_all', default=False,
888                     help='Strip the NEXE for production', action='store_true')
889   parser.add_option('--strip', dest='strip', default='',
890                     help='Strip the filename')
891   parser.add_option('--nonstable-pnacl', dest='finalize_pexe', default=True,
892                     help='Do not finalize pnacl bitcode for ABI stability',
893                     action='store_false')
894   parser.add_option('--source-list', dest='source_list',
895                     help='Filename to load a source list from')
896   parser.add_option('--tls-edit', dest='tls_edit', default=None,
897                     help='tls_edit location if TLS should be modified for IRT')
898   parser.add_option('--irt-layout', dest='irt_layout', default=False,
899                     help='Apply the IRT layout (pick data/text seg addresses)',
900                     action='store_true')
901   parser.add_option('-a', '--arch', dest='arch',
902                     help='Set target architecture')
903   parser.add_option('-c', '--compile', dest='compile_only', default=False,
904                     help='Compile only.', action='store_true')
905   parser.add_option('-i', '--include-dirs', dest='incdirs',
906                     help='Set include directories.')
907   parser.add_option('-l', '--lib-dirs', dest='libdirs',
908                     help='Set library directories.')
909   parser.add_option('-n', '--name', dest='name',
910                     help='Base path and name of the nexe.')
911   parser.add_option('-o', '--objdir', dest='objdir',
912                     help='Base path of the object output dir.')
913   parser.add_option('-r', '--root', dest='root',
914                     help='Set the root directory of the sources')
915   parser.add_option('-b', '--build', dest='build',
916                     help='Set build type (<toolchain>_<outtype>, ' +
917                     'where toolchain is newlib or glibc and outtype is ' +
918                     'one of nexe, nlib, nso, pexe, or translate)')
919   parser.add_option('--compile_flags', dest='compile_flags',
920                     help='Set compile flags.')
921   parser.add_option('--defines', dest='defines',
922                     help='Set defines')
923   parser.add_option('--link_flags', dest='link_flags',
924                     help='Set link flags.')
925   parser.add_option('-v', '--verbose', dest='verbose', default=False,
926                     help='Enable verbosity', action='store_true')
927   parser.add_option('-t', '--toolpath', dest='toolpath',
928                     help='Set the path for of the toolchains.')
929   parser.add_option('--config-name', dest='build_config',
930                     help='GYP build configuration name (Release/Debug)')
931   parser.add_option('--gomadir', dest='gomadir',
932                     help='Path of the goma directory.')
933   options, files = parser.parse_args(argv[1:])
934
935   if options.name is None:
936     parser.error('--name is required!')
937   if options.build_config is None:
938     parser.error('--config-name is required!')
939   if options.root is None:
940     parser.error('--root is required!')
941   if options.arch is None:
942     parser.error('--arch is required!')
943   if options.build is None:
944     parser.error('--build is required!')
945
946   if not argv:
947     parser.print_help()
948     return 1
949
950   try:
951     if options.source_list:
952       source_list_handle = open(options.source_list, 'r')
953       source_list = source_list_handle.read().splitlines()
954       source_list_handle.close()
955       files = files + source_list
956
957     # Use set instead of list not to compile the same file twice.
958     # To keep in mind that the order of files may differ from the .gypcmd file,
959     # the set is not converted to a list.
960     # Having duplicated files can cause race condition of compiling during
961     # parallel build using goma.
962     # TODO(sbc): remove the duplication and turn it into an error.
963     files = set(files)
964
965     # Fix slash style to insulate invoked toolchains.
966     options.toolpath = os.path.normpath(options.toolpath)
967
968     build = Builder(options)
969     objs = []
970
971     if build.outtype == 'translate':
972       # Just translate a pexe to a nexe
973       if len(files) != 1:
974         parser.error('Pexe translation requires exactly one input file.')
975       build.Translate(list(files)[0])
976       return 0
977
978     if build.gomacc and (build.goma_burst or build.goma_threads > 1):
979       returns = Queue.Queue()
980
981       # Push all files into the inputs queue
982       inputs = Queue.Queue()
983       for filename in files:
984         inputs.put(filename)
985
986       def CompileThread(input_queue, output_queue):
987         try:
988           while True:
989             try:
990               filename = input_queue.get_nowait()
991             except Queue.Empty:
992               return
993             output_queue.put(build.Compile(filename))
994         except Exception:
995           # Put current exception info to the queue.
996           output_queue.put(sys.exc_info())
997
998       # Don't limit number of threads in the burst mode.
999       if build.goma_burst:
1000         num_threads = len(files)
1001       else:
1002         num_threads = min(build.goma_threads, len(files))
1003
1004       # Start parallel build.
1005       build_threads = []
1006       for _ in xrange(num_threads):
1007         thr = threading.Thread(target=CompileThread, args=(inputs, returns))
1008         thr.start()
1009         build_threads.append(thr)
1010
1011       # Wait for results.
1012       for _ in files:
1013         out = returns.get()
1014         # An exception raised in the thread may come through the queue.
1015         # Raise it again here.
1016         if (isinstance(out, tuple) and len(out) == 3 and
1017             isinstance(out[1], Exception)):
1018           raise out[0], None, out[2]
1019         elif out:
1020           objs.append(out)
1021
1022       assert inputs.empty()
1023
1024       # Wait until all threads have stopped and verify that there are no more
1025       # results.
1026       for thr in build_threads:
1027         thr.join()
1028       assert returns.empty()
1029
1030     else:  # slow path.
1031       for filename in files:
1032         out = build.Compile(filename)
1033         if out:
1034           objs.append(out)
1035
1036     # Do not link if building an object. However we still want the output file
1037     # to be what was specified in options.name
1038     if options.compile_only:
1039       if len(objs) > 1:
1040         raise Error('--compile mode cannot be used with multiple sources')
1041       shutil.copy(objs[0], options.name)
1042     else:
1043       build.Generate(objs)
1044     return 0
1045   except Error as e:
1046     sys.stderr.write('%s\n' % e)
1047     return 1
1048
1049 if __name__ == '__main__':
1050   sys.exit(Main(sys.argv))