Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / native_client / pnacl / driver / driver_tools.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 # IMPORTANT NOTE: If you make local mods to this file, you must run:
7 #   %  pnacl/build.sh driver
8 # in order for them to take effect in the scons build.  This command
9 # updates the copy in the toolchain/ tree.
10 #
11
12 import platform
13 import os
14 import re
15 import shlex
16 import signal
17 import subprocess
18 import struct
19 import sys
20 import tempfile
21
22 import artools
23 import elftools
24 import ldtools
25 # filetype needs to be imported here because pnacl-driver injects calls to
26 # filetype.ForceFileType into argument parse actions.
27 # TODO(dschuff): That's ugly. Find a better way.
28 import filetype
29 import pathtools
30
31 from driver_env import env
32 # TODO: import driver_log and change these references from 'foo' to
33 # 'driver_log.foo', or split driver_log further
34 from driver_log import Log, DriverOpen, DriverClose, StringifyCommand, TempFiles, DriverExit, FixArch
35 from shelltools import shell
36
37 def ParseError(s, leftpos, rightpos, msg):
38   Log.Error("Parse Error: %s", msg)
39   Log.Error('  ' + s)
40   Log.Error('  ' + (' '*leftpos) + ('^'*(rightpos - leftpos + 1)))
41   DriverExit(1)
42
43
44 # Run a command with extra environment settings
45 def RunWithEnv(cmd, **kwargs):
46   env.push()
47   env.setmany(**kwargs)
48   ret = Run(cmd)
49   env.pop()
50   return ret
51
52
53 def SetExecutableMode(path):
54   if os.name == "posix":
55     realpath = pathtools.tosys(path)
56     # os.umask gets and sets at the same time.
57     # There's no way to get it without setting it.
58     umask = os.umask(0)
59     os.umask(umask)
60     os.chmod(realpath, 0755 & ~umask)
61
62
63 def FilterOutArchArgs(args):
64   while '-arch' in args:
65     i = args.index('-arch')
66     args = args[:i] + args[i+2:]
67   return args
68
69 # Parse and validate the target triple and return the architecture.
70 # We don't attempt to recognize all possible targets here, just the ones we
71 # support.
72 def ParseTriple(triple):
73   tokens = triple.split('-')
74   arch = tokens[0]
75   if arch != 'le32':
76     arch = FixArch(arch)
77   os = tokens[1]
78   # The machine/vendor field could be present or not.
79   if os != 'nacl' and len(tokens) >= 3:
80     os = tokens[2]
81   # Just check that the os is nacl.
82   if os == 'nacl':
83     return arch
84
85   Log.Fatal('machine/os ' + '-'.join(tokens[1:]) + ' not supported.')
86
87
88 def RunDriver(invocation, args, suppress_inherited_arch_args=False):
89   """
90   RunDriver() is used to invoke "driver" tools, e.g.
91   those prefixed  with "pnacl-"
92
93   It automatically appends some additional flags to the invocation
94   which were inherited from the current invocation.
95   Those flags were preserved by ParseArgs
96   """
97
98   if isinstance(args, str):
99     args = shell.split(env.eval(args))
100
101   module_name = 'pnacl-%s' % invocation
102   script = env.eval('${DRIVER_BIN}/%s' % module_name)
103   script = shell.unescape(script)
104
105   inherited_driver_args = env.get('INHERITED_DRIVER_ARGS')
106   if suppress_inherited_arch_args:
107     inherited_driver_args = FilterOutArchArgs(inherited_driver_args)
108
109   script = pathtools.tosys(script)
110   cmd = [script] + args + inherited_driver_args
111   Log.Info('Driver invocation: %s', repr(cmd))
112
113   module = __import__(module_name)
114   # Save the environment, reset the environment, run
115   # the driver module, and then restore the environment.
116   env.push()
117   env.reset()
118   DriverMain(module, cmd)
119   env.pop()
120
121 def memoize(f):
122   """ Memoize a function with no arguments """
123   saved = {}
124   def newf():
125     if len(saved) == 0:
126       saved[None] = f()
127     return saved[None]
128   newf.__name__ = f.__name__
129   return newf
130
131
132 @env.register
133 @memoize
134 def GetBuildOS():
135   name = platform.system().lower()
136   if name.startswith('cygwin_nt') or 'windows' in name:
137     name = 'windows'
138   if name not in ('linux', 'darwin', 'windows'):
139     Log.Fatal("Unsupported platform '%s'", name)
140   return name
141
142 @env.register
143 @memoize
144 def GetBuildArch():
145   m = platform.machine()
146
147   # Windows is special
148   if m == 'x86':
149     m = 'i686'
150
151   if m not in ('i386', 'i686', 'x86_64'):
152     Log.Fatal("Unsupported architecture '%s'", m)
153   return m
154
155 # Crawl backwards, starting from the directory containing this script,
156 # until we find a directory satisfying a filter function.
157 def FindBaseDir(function):
158   Depth = 0
159   cur = env.getone('DRIVER_BIN')
160   while not function(cur) and Depth < 16:
161     cur = pathtools.dirname(cur)
162     Depth += 1
163   if function(cur):
164     return cur
165   return None
166
167 @env.register
168 @memoize
169 def FindBaseNaCl():
170   """ Find native_client/ directory """
171   dir = FindBaseDir(lambda cur: pathtools.basename(cur) == 'native_client')
172   if dir is None:
173     Log.Fatal("Unable to find 'native_client' directory")
174   return shell.escape(dir)
175
176 @env.register
177 @memoize
178 def FindBaseToolchain():
179   """ Find toolchain/ directory """
180   dir = FindBaseDir(lambda cur: pathtools.basename(cur) == 'toolchain')
181   if dir is None:
182     Log.Fatal("Unable to find 'toolchain' directory")
183   return shell.escape(dir)
184
185 @env.register
186 @memoize
187 def FindBasePNaCl():
188   """ Find the base directory of the PNaCl toolchain """
189   # The <base> directory is one level up from the <base>/bin:
190   bindir = env.getone('DRIVER_BIN')
191   basedir = pathtools.dirname(bindir)
192   return shell.escape(basedir)
193
194 def AddHostBinarySearchPath(prefix):
195   """ Add a path to the list searched for host binaries. """
196   prefix = pathtools.normalize(prefix)
197   if pathtools.isdir(prefix) and not prefix.endswith('/'):
198     prefix += '/'
199
200   env.append('BPREFIXES', prefix)
201
202 @env.register
203 def FindBaseHost(tool):
204   """ Find the base directory for host binaries (i.e. llvm/binutils) """
205   if env.has('BPREFIXES'):
206     for prefix in env.get('BPREFIXES'):
207       if os.path.exists(pathtools.join(prefix, 'bin',
208                                        tool + env.getone('EXEC_EXT'))):
209         return prefix
210
211   base_pnacl = FindBasePNaCl()
212   base_host = pathtools.join(base_pnacl, 'host_' + env.getone('HOST_ARCH'))
213   if not pathtools.exists(pathtools.join(base_host, 'bin',
214                           tool + env.getone('EXEC_EXT'))):
215     Log.Fatal('Could not find PNaCl host directory for ' + tool)
216   return base_host
217
218 def ReadConfig():
219   # Mock out ReadConfig if running unittests.  Settings are applied directly
220   # by DriverTestEnv rather than reading this configuration file.
221   if env.has('PNACL_RUNNING_UNITTESTS'):
222     return
223   driver_bin = env.getone('DRIVER_BIN')
224   driver_conf = pathtools.join(driver_bin, 'driver.conf')
225   fp = DriverOpen(driver_conf, 'r')
226   linecount = 0
227   for line in fp:
228     linecount += 1
229     line = line.strip()
230     if line == '' or line.startswith('#'):
231       continue
232     sep = line.find('=')
233     if sep < 0:
234       Log.Fatal("%s: Parse error, missing '=' on line %d",
235                 pathtools.touser(driver_conf), linecount)
236     keyname = line[:sep].strip()
237     value = line[sep+1:].strip()
238     env.setraw(keyname, value)
239   DriverClose(fp)
240
241 @env.register
242 def AddPrefix(prefix, varname):
243   values = env.get(varname)
244   return ' '.join([prefix + shell.escape(v) for v in values ])
245
246 ######################################################################
247 #
248 # Argument Parser
249 #
250 ######################################################################
251
252 DriverArgPatterns = [
253   ( '--pnacl-driver-verbose',          "env.set('LOG_VERBOSE', '1')"),
254   ( ('-arch', '(.+)'),                 "SetArch($0)"),
255   ( '--pnacl-sb',                      "env.set('SANDBOXED', '1')"),
256   ( '--pnacl-use-emulator',            "env.set('USE_EMULATOR', '1')"),
257   ( '--dry-run',                       "env.set('DRY_RUN', '1')"),
258   ( '--pnacl-arm-bias',                "env.set('BIAS', 'ARM')"),
259   ( '--pnacl-mips-bias',               "env.set('BIAS', 'MIPS32')"),
260   ( '--pnacl-i686-bias',               "env.set('BIAS', 'X8632')"),
261   ( '--pnacl-x86_64-bias',             "env.set('BIAS', 'X8664')"),
262   ( '--pnacl-bias=(.+)',               "env.set('BIAS', FixArch($0))"),
263   ( '--pnacl-default-command-line',    "env.set('USE_DEFAULT_CMD_LINE', '1')"),
264   ( '-save-temps',                     "env.set('SAVE_TEMPS', '1')"),
265   ( '-no-save-temps',                  "env.set('SAVE_TEMPS', '0')"),
266   ( ('-B', '(.*)'),  AddHostBinarySearchPath),
267  ]
268
269 DriverArgPatternsNotInherited = [
270   ( '--pnacl-driver-set-([^=]+)=(.*)',    "env.set($0, $1)"),
271   ( '--pnacl-driver-append-([^=]+)=(.*)', "env.append($0, $1)"),
272 ]
273
274
275 def ShouldExpandCommandFile(arg):
276   """ We may be given files with commandline arguments.
277   Read in the arguments so that they can be handled as usual. """
278   if arg.startswith('@'):
279     possible_file = pathtools.normalize(arg[1:])
280     return pathtools.isfile(possible_file)
281   else:
282     return False
283
284
285 def DoExpandCommandFile(argv, i):
286   arg = argv[i]
287   fd = DriverOpen(pathtools.normalize(arg[1:]), 'r')
288   more_args = []
289
290   # Use shlex here to process the response file contents.
291   # This ensures that single and double quoted args are
292   # handled correctly.  Since this file is very likely
293   # to contain paths with windows path seperators we can't
294   # use the normal shlex.parse() since we need to disable
295   # disable '\' (the default escape char).
296   for line in fd:
297     lex = shlex.shlex(line, posix=True)
298     lex.escape = ''
299     lex.whitespace_split = True
300     more_args += list(lex)
301
302   fd.close()
303   argv = argv[:i] + more_args + argv[i+1:]
304   return argv
305
306
307 def ParseArgs(argv,
308               patternlist,
309               driver_patternlist=DriverArgPatterns,
310               driver_patternlist_not_inherited=DriverArgPatternsNotInherited):
311   """Parse argv using the patterns in patternlist
312      Also apply the built-in DriverArgPatterns unless instructed otherwise.
313      This function must be called by all (real) drivers.
314   """
315   if driver_patternlist:
316     driver_args, argv = ParseArgsBase(argv, driver_patternlist)
317
318     # TODO(robertm): think about a less obscure mechanism to
319     #                replace the inherited args feature
320     assert not env.get('INHERITED_DRIVER_ARGS')
321     env.append('INHERITED_DRIVER_ARGS', *driver_args)
322
323   _, argv = ParseArgsBase(argv, driver_patternlist_not_inherited)
324
325   _, unmatched = ParseArgsBase(argv, patternlist)
326   if unmatched:
327     for u in unmatched:
328       Log.Error('Unrecognized argument: ' + u)
329     Log.Fatal('unknown arguments')
330
331
332 def ParseArgsBase(argv, patternlist):
333   """ Parse argv using the patterns in patternlist
334       Returns: (matched, unmatched)
335   """
336   matched = []
337   unmatched = []
338   i = 0
339   while i < len(argv):
340     if ShouldExpandCommandFile(argv[i]):
341       argv = DoExpandCommandFile(argv, i)
342     num_matched, action, groups = MatchOne(argv, i, patternlist)
343     if num_matched == 0:
344       unmatched.append(argv[i])
345       i += 1
346       continue
347     matched += argv[i:i+num_matched]
348     if isinstance(action, str):
349       # Perform $N substitution
350       for g in xrange(0, len(groups)):
351         action = action.replace('$%d' % g, 'groups[%d]' % g)
352     try:
353       if isinstance(action, str):
354         # NOTE: this is essentially an eval for python expressions
355         # which does rely on the current environment for unbound vars
356         # Log.Info('about to exec [%s]', str(action))
357         exec(action)
358       else:
359         action(*groups)
360     except Exception, err:
361       Log.Fatal('ParseArgs action [%s] failed with: %s', action, err)
362     i += num_matched
363   return (matched, unmatched)
364
365
366 def MatchOne(argv, i, patternlist):
367   """Find a pattern which matches argv starting at position i"""
368   for (regex, action) in patternlist:
369     if isinstance(regex, str):
370       regex = [regex]
371     j = 0
372     matches = []
373     for r in regex:
374       if i+j < len(argv):
375         match = re.compile('^'+r+'$').match(argv[i+j])
376       else:
377         match = None
378       matches.append(match)
379       j += 1
380     if None in matches:
381       continue
382     groups = [ list(m.groups()) for m in matches ]
383     groups = reduce(lambda x,y: x+y, groups, [])
384     return (len(regex), action, groups)
385   return (0, '', [])
386
387 def UnrecognizedOption(*args):
388   Log.Fatal("Unrecognized option: " + ' '.join(args) + "\n" +
389             "Use '--help' for more information.")
390
391
392 ######################################################################
393 #
394 # File Naming System (Temp files & Output files)
395 #
396 ######################################################################
397
398 def DefaultOutputName(filename, outtype):
399   # For pre-processor mode, just print to stdout.
400   if outtype in ('pp'): return '-'
401
402   base = pathtools.basename(filename)
403   base = RemoveExtension(base)
404   if outtype in ('po'): return base + '.o'
405
406   assert(outtype in filetype.ExtensionMap.values())
407   assert(not filetype.IsSourceType(outtype))
408
409   return base + '.' + outtype
410
411 def RemoveExtension(filename):
412   if filename.endswith('.opt.bc'):
413     return filename[0:-len('.opt.bc')]
414
415   name, ext = pathtools.splitext(filename)
416   return name
417
418 def PathSplit(f):
419   paths = []
420   cur = f
421   while True:
422     cur, piece = pathtools.split(cur)
423     if piece == '':
424       break
425     paths.append(piece)
426   paths.reverse()
427   return paths
428
429 # Generate a unique identifier for each input file.
430 # Start with the basename, and if that is not unique enough,
431 # add parent directories. Rinse, repeat.
432 class TempNameGen(object):
433   def __init__(self, inputs, output):
434     inputs = [ pathtools.abspath(i) for i in inputs ]
435     output = pathtools.abspath(output)
436
437     self.TempBase = output + '---linked'
438
439     # TODO(pdox): Figure out if there's a less confusing way
440     #             to simplify the intermediate filename in this case.
441     #if len(inputs) == 1:
442     #  # There's only one input file, don't bother adding the source name.
443     #  TempMap[inputs[0]] = output + '---'
444     #  return
445
446     # Build the initial mapping
447     self.TempMap = dict()
448     for f in inputs:
449       if f.startswith('-'):
450         continue
451       path = PathSplit(f)
452       self.TempMap[f] = [1, path]
453
454     while True:
455       # Find conflicts
456       ConflictMap = dict()
457       Conflicts = set()
458       for (f, [n, path]) in self.TempMap.iteritems():
459         candidate = output + '---' + '_'.join(path[-n:]) + '---'
460         if candidate in ConflictMap:
461           Conflicts.add(ConflictMap[candidate])
462           Conflicts.add(f)
463         else:
464           ConflictMap[candidate] = f
465
466       if len(Conflicts) == 0:
467         break
468
469       # Resolve conflicts
470       for f in Conflicts:
471         n = self.TempMap[f][0]
472         if n+1 > len(self.TempMap[f][1]):
473           Log.Fatal('Unable to resolve naming conflicts')
474         self.TempMap[f][0] = n+1
475
476     # Clean up the map
477     NewMap = dict()
478     for (f, [n, path]) in self.TempMap.iteritems():
479       candidate = output + '---' + '_'.join(path[-n:]) + '---'
480       NewMap[f] = candidate
481     self.TempMap = NewMap
482     return
483
484   def TempNameForOutput(self, imtype):
485     temp = self.TempBase + '.' + imtype
486     if not env.getbool('SAVE_TEMPS'):
487       TempFiles.add(temp)
488     return temp
489
490   def TempNameForInput(self, input, imtype):
491     fullpath = pathtools.abspath(input)
492     # If input is already a temporary name, just change the extension
493     if fullpath.startswith(self.TempBase):
494       temp = self.TempBase + '.' + imtype
495     else:
496       # Source file
497       temp = self.TempMap[fullpath] + '.' + imtype
498
499     if not env.getbool('SAVE_TEMPS'):
500       TempFiles.add(temp)
501     return temp
502
503 # (Invoked from loader.py)
504 # If the driver is waiting on a background process in RunWithLog()
505 # and the user Ctrl-C's or kill's the driver, it may leave
506 # the child process (such as llc) running. To prevent this,
507 # the code below sets up a signal handler which issues a kill to
508 # the currently running child processes.
509 CleanupProcesses = []
510 def SetupSignalHandlers():
511   global CleanupProcesses
512   def signal_handler(unused_signum, unused_frame):
513     for p in CleanupProcesses:
514       try:
515         p.kill()
516       except BaseException:
517         pass
518     os.kill(os.getpid(), signal.SIGKILL)
519     return 0
520   if os.name == "posix":
521     signal.signal(signal.SIGINT, signal_handler)
522     signal.signal(signal.SIGHUP, signal_handler)
523     signal.signal(signal.SIGTERM, signal_handler)
524
525
526 def ArgsTooLongForWindows(args):
527   """ Detect when a command line might be too long for Windows.  """
528   if not IsWindowsPython():
529     return False
530   else:
531     return len(' '.join(args)) > 8191
532
533
534 def ConvertArgsToFile(args):
535   fd, outfile = tempfile.mkstemp()
536   # Remember to delete this file afterwards.
537   TempFiles.add(outfile)
538   cmd = args[0]
539   other_args = args[1:]
540   os.write(fd, ' '.join(other_args))
541   os.close(fd)
542   return [cmd, '@' + outfile]
543
544 # Note:
545 # The redirect_stdout and redirect_stderr is only used a handful of times
546 #
547 # The stdin_contents feature is currently only used by:
548 #  sel_universal invocations in the translator
549 #
550 def Run(args,
551         errexit=True,
552         stdin_contents=None,
553         redirect_stdout=None,
554         redirect_stderr=None):
555   """ Run: Run a command.
556       Returns: return_code, stdout, stderr
557
558       Run() is used to invoke "other" tools, e.g.
559       those NOT prefixed with "pnacl-"
560
561       stdout and stderr only contain meaningful data if
562           redirect_{stdout,stderr} == subprocess.PIPE
563
564       Run will terminate the program upon failure unless errexit == False
565       TODO(robertm): errexit == True has not been tested and needs more work
566
567       redirect_stdout and redirect_stderr are passed straight
568       to subprocess.Popen
569       stdin_contents is an optional string used as stdin
570   """
571
572   result_stdout = None
573   result_stderr = None
574   if isinstance(args, str):
575     args = shell.split(env.eval(args))
576
577   args = [pathtools.tosys(args[0])] + args[1:]
578
579   Log.Info('Running: ' + StringifyCommand(args))
580   if stdin_contents:
581     Log.Info('--------------stdin: begin')
582     Log.Info(stdin_contents)
583     Log.Info('--------------stdin: end')
584
585   if env.getbool('DRY_RUN'):
586     if redirect_stderr or redirect_stdout:
587       # TODO(pdox): Prevent this from happening, so that
588       # dry-run is more useful.
589       Log.Fatal("Unhandled dry-run case.")
590     return 0, None, None
591
592   try:
593     # If we have too long of a cmdline on windows, running it would fail.
594     # Attempt to use a file with the command line options instead in that case.
595     if ArgsTooLongForWindows(args):
596       actual_args = ConvertArgsToFile(args)
597       Log.Info('Wrote long commandline to file for Windows: ' +
598                StringifyCommand(actual_args))
599
600     else:
601       actual_args = args
602
603     redirect_stdin = None
604     if stdin_contents:
605       redirect_stdin = subprocess.PIPE
606
607     p = subprocess.Popen(actual_args,
608                          stdin=redirect_stdin,
609                          stdout=redirect_stdout,
610                          stderr=redirect_stderr)
611     result_stdout, result_stderr = p.communicate(input=stdin_contents)
612   except Exception, e:
613     msg =  '%s\nCommand was: %s' % (str(e),
614                                     StringifyCommand(args, stdin_contents))
615     print msg
616     DriverExit(1)
617
618   Log.Info('Return Code: ' + str(p.returncode))
619
620   if errexit and p.returncode != 0:
621     if redirect_stdout == subprocess.PIPE:
622       Log.Error('--------------stdout: begin')
623       Log.Error(result_stdout)
624       Log.Error('--------------stdout: end')
625
626     if redirect_stderr == subprocess.PIPE:
627       Log.Error('--------------stderr: begin')
628       Log.Error(result_stderr)
629       Log.Error('--------------stderr: end')
630     DriverExit(p.returncode)
631
632   return p.returncode, result_stdout, result_stderr
633
634
635 def IsWindowsPython():
636   return 'windows' in platform.system().lower()
637
638 def SetupCygwinLibs():
639   bindir = env.getone('DRIVER_BIN')
640   # Prepend the directory containing cygwin1.dll etc. to the PATH to ensure we
641   # get the right one.
642   os.environ['PATH'] = os.pathsep.join(
643       [pathtools.tosys(bindir)] + os.environ['PATH'].split(os.pathsep))
644
645
646 def HelpNotAvailable():
647   return 'Help text not available'
648
649 def DriverMain(module, argv):
650   # TODO(robertm): this is ugly - try to get rid of this
651   if '--pnacl-driver-verbose' in argv:
652     Log.IncreaseVerbosity()
653     env.set('LOG_VERBOSE', '1')
654
655   # driver_path has the form: /foo/bar/pnacl_root/newlib/bin/pnacl-clang
656   driver_path = pathtools.abspath(pathtools.normalize(argv[0]))
657   driver_bin = pathtools.dirname(driver_path)
658   script_name = pathtools.basename(driver_path)
659   env.set('SCRIPT_NAME', script_name)
660   env.set('DRIVER_PATH', driver_path)
661   env.set('DRIVER_BIN', driver_bin)
662
663   Log.SetScriptName(script_name)
664
665   ReadConfig()
666
667   if IsWindowsPython():
668     SetupCygwinLibs()
669
670   # skip tool name
671   argv = argv[1:]
672
673   # Handle help info
674   if ('--help' in argv or
675       '-h' in argv or
676       '-help' in argv or
677       '--help-full' in argv):
678     help_func = getattr(module, 'get_help', None)
679     if not help_func:
680       Log.Fatal(HelpNotAvailable())
681     helpstr = help_func(argv)
682     print helpstr
683     return 0
684
685   return module.main(argv)
686
687
688 def SetArch(arch):
689   env.set('ARCH', FixArch(arch))
690
691 def GetArch(required = False):
692   arch = env.getone('ARCH')
693   if arch == '':
694     arch = None
695
696   if required and not arch:
697     Log.Fatal('Missing -arch!')
698
699   return arch
700
701 # Read an ELF file to determine the machine type. If ARCH is already set,
702 # make sure the file has the same architecture. If ARCH is not set,
703 # set the ARCH to the file's architecture.
704 #
705 # Returns True if the file matches ARCH.
706 #
707 # Returns False if the file doesn't match ARCH. This only happens when
708 # must_match is False. If must_match is True, then a fatal error is generated
709 # instead.
710 def ArchMerge(filename, must_match):
711   file_type = filetype.FileType(filename)
712   if file_type in ('o','so'):
713     elfheader = elftools.GetELFHeader(filename)
714     if not elfheader:
715       Log.Fatal("%s: Cannot read ELF header", filename)
716     new_arch = elfheader.arch
717   elif filetype.IsNativeArchive(filename):
718     new_arch = file_type[len('archive-'):]
719   else:
720     Log.Fatal('%s: Unexpected file type in ArchMerge', filename)
721
722   existing_arch = GetArch()
723
724   if not existing_arch:
725     SetArch(new_arch)
726     return True
727   elif new_arch != existing_arch:
728     if must_match:
729       msg = "%s: Incompatible object file (%s != %s)"
730       logfunc = Log.Fatal
731     else:
732       msg = "%s: Skipping incompatible object file (%s != %s)"
733       logfunc = Log.Warning
734     logfunc(msg, filename, new_arch, existing_arch)
735     return False
736   else: # existing_arch and new_arch == existing_arch
737     return True
738
739 def CheckTranslatorPrerequisites():
740   """ Assert that the scons artifacts for running the sandboxed translator
741       exist: sel_universal, and sel_ldr. """
742   if env.getbool('DRY_RUN'):
743     return
744   reqs = ['SEL_UNIVERSAL', 'SEL_LDR']
745   # Linux also requires the nacl bootstrap helper.
746   if GetBuildOS() == 'linux':
747     reqs.append('BOOTSTRAP_LDR')
748   for var in reqs:
749     needed_file = env.getone(var)
750     if not pathtools.exists(needed_file):
751       Log.Fatal('Could not find %s [%s]', var, needed_file)
752
753 class DriverChain(object):
754   """ The DriverChain class takes one or more input files,
755       an output file, and a sequence of steps. It executes
756       those steps, using intermediate files in between,
757       to generate the final outpu.
758   """
759
760   def __init__(self, input, output, namegen):
761     self.input = input
762     self.output = output
763     self.steps = []
764     self.namegen = namegen
765
766     # "input" can be a list of files or a single file.
767     # If we're compiling for a single file, then we use
768     # TempNameForInput. If there are multiple files
769     # (e.g. linking), then we use TempNameForOutput.
770     self.use_names_for_input = isinstance(input, str)
771
772   def add(self, callback, output_type, **extra):
773     step = (callback, output_type, extra)
774     self.steps.append(step)
775
776   def run(self):
777     step_input = self.input
778     for (i, (callback, output_type, extra)) in enumerate(self.steps):
779       if i == len(self.steps)-1:
780         # Last step
781         step_output = self.output
782       else:
783         # Intermediate step
784         if self.use_names_for_input:
785           step_output = self.namegen.TempNameForInput(self.input, output_type)
786         else:
787           step_output = self.namegen.TempNameForOutput(output_type)
788       callback(step_input, step_output, **extra)
789       step_input = step_output