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