Update To 11.40.268.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 DefaultPCHOutputName(filename):
431   # Clang currently uses the GCC '.gch' by default for precompiled headers,
432   # though their documentation example uses '-o foo.h.pch' as the example.
433   return filename + '.gch'
434
435 def RemoveExtension(filename):
436   if filename.endswith('.opt.bc'):
437     return filename[0:-len('.opt.bc')]
438
439   name, ext = pathtools.splitext(filename)
440   return name
441
442 def PathSplit(f):
443   paths = []
444   cur = f
445   while True:
446     cur, piece = pathtools.split(cur)
447     if piece == '':
448       break
449     paths.append(piece)
450   paths.reverse()
451   return paths
452
453
454 def CheckPathLength(filename, exit_on_failure=True):
455   '''Check that the length of the path is short enough for Windows.
456
457   On Windows, MAX_PATH is ~260 and applies to absolute paths, and to relative
458   paths and the absolute paths they expand to (except for specific uses of
459   some APIs; see link below). Most applications don't bother to support long
460   paths properly (including LLVM, GNU binutils, and ninja). If a path is too
461   long, ERROR_PATH_NOT_FOUND is returned, which isn't very useful or clear for
462   users. In addition the Chrome build has deep directory hierarchies with long
463   names.
464   This function checks that the path is valid, so we can throw meaningful
465   errors.
466
467   http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
468   '''
469   if not IsWindowsPython() and not env.has('PNACL_RUNNING_UNITTESTS'):
470     return True
471
472   # First check the name as-is (it's usually a relative path)
473   if len(filename) > 255:
474     if exit_on_failure:
475       Log.Fatal('Path name %s is too long (%d characters)' %
476                 (filename, len(filename)))
477     return False
478   if os.path.isabs(filename):
479     return True
480
481   # Don't assume that the underlying tools or windows APIs will normalize
482   # the path before using it. Conservatively count the length of CWD + filename
483   appended_name = os.path.join(os.getcwd(), filename)
484   if len(appended_name) > 255:
485     if exit_on_failure:
486       Log.Fatal('Path name %s (expanded from %s) is too long (%d characters)' %
487                 (appended_name, filename, len(appended_name)))
488     return False
489   return True
490
491 # Generate a unique identifier for each input file.
492 # Start with the basename, and if that is not unique enough,
493 # add parent directories. Rinse, repeat.
494 class TempNameGen(object):
495   def __init__(self, inputs, output):
496     inputs = [ pathtools.abspath(i) for i in inputs ]
497     output = pathtools.abspath(output)
498
499     self.TempBase = output + '---linked'
500     self.OutputDir = pathtools.dirname(output)
501
502     # TODO(pdox): Figure out if there's a less confusing way
503     #             to simplify the intermediate filename in this case.
504     #if len(inputs) == 1:
505     #  # There's only one input file, don't bother adding the source name.
506     #  TempMap[inputs[0]] = output + '---'
507     #  return
508
509     # Build the initial mapping
510     self.TempMap = dict()
511     for f in inputs:
512       if f.startswith('-'):
513         continue
514       path = PathSplit(f)
515       self.TempMap[f] = [1, path]
516
517     while True:
518       # Find conflicts
519       ConflictMap = dict()
520       Conflicts = set()
521       for (f, [n, path]) in self.TempMap.iteritems():
522         candidate = output + '---' + '_'.join(path[-n:]) + '---'
523         if candidate in ConflictMap:
524           Conflicts.add(ConflictMap[candidate])
525           Conflicts.add(f)
526         else:
527           ConflictMap[candidate] = f
528
529       if len(Conflicts) == 0:
530         break
531
532       # Resolve conflicts
533       for f in Conflicts:
534         n = self.TempMap[f][0]
535         if n+1 > len(self.TempMap[f][1]):
536           Log.Fatal('Unable to resolve naming conflicts')
537         self.TempMap[f][0] = n+1
538
539     # Clean up the map
540     NewMap = dict()
541     for (f, [n, path]) in self.TempMap.iteritems():
542       candidate = output + '---' + '_'.join(path[-n:]) + '---'
543       NewMap[f] = candidate
544     self.TempMap = NewMap
545     return
546
547   def ValidatePathLength(self, temp, imtype):
548     temp = pathtools.normpath(temp) if temp else temp
549     # If the temp name is too long, just pick a random one instead.
550     if not CheckPathLength(temp, exit_on_failure=False):
551       # imtype is sometimes just an extension, and sometimes a compound
552       # extension (e.g. pre_opt.pexe). To keep name length shorter,
553       # only take the last extension
554       if '.' in imtype:
555         imtype = imtype[imtype.rfind('.') + 1:]
556       temp = pathtools.join(
557           self.OutputDir,
558           str(random.randrange(100000, 1000000)) + '.' + imtype)
559       CheckPathLength(temp)
560     return temp
561
562   def TempNameForOutput(self, imtype):
563     temp = self.ValidatePathLength(self.TempBase + '.' + imtype, imtype)
564     TempFiles.add(temp)
565     return temp
566
567   def TempNameForInput(self, input, imtype):
568     fullpath = pathtools.abspath(input)
569     # If input is already a temporary name, just change the extension
570     if fullpath.startswith(self.TempBase):
571       temp = self.TempBase + '.' + imtype
572     else:
573       # Source file
574       temp = self.TempMap[fullpath] + '.' + imtype
575
576     temp = self.ValidatePathLength(temp, imtype)
577     TempFiles.add(temp)
578     return temp
579
580 # (Invoked from loader.py)
581 # If the driver is waiting on a background process in RunWithLog()
582 # and the user Ctrl-C's or kill's the driver, it may leave
583 # the child process (such as llc) running. To prevent this,
584 # the code below sets up a signal handler which issues a kill to
585 # the currently running child processes.
586 CleanupProcesses = []
587 def SetupSignalHandlers():
588   global CleanupProcesses
589   def signal_handler(unused_signum, unused_frame):
590     for p in CleanupProcesses:
591       try:
592         p.kill()
593       except BaseException:
594         pass
595     os.kill(os.getpid(), signal.SIGKILL)
596     return 0
597   if os.name == "posix":
598     signal.signal(signal.SIGINT, signal_handler)
599     signal.signal(signal.SIGHUP, signal_handler)
600     signal.signal(signal.SIGTERM, signal_handler)
601
602
603 def ArgsTooLongForWindows(args):
604   """ Detect when a command line might be too long for Windows.  """
605   if not IsWindowsPython():
606     return False
607   else:
608     return len(' '.join(args)) > 8191
609
610
611 def ConvertArgsToFile(args):
612   fd, outfile = tempfile.mkstemp()
613   # Remember to delete this file afterwards.
614   TempFiles.add(outfile)
615   cmd = args[0]
616   other_args = args[1:]
617   os.write(fd, ' '.join(other_args))
618   os.close(fd)
619   return [cmd, '@' + outfile]
620
621 # Note:
622 # The redirect_stdout and redirect_stderr is only used a handful of times
623 #
624 # The stdin_contents feature is currently only used by:
625 #  sel_universal invocations in the translator
626 #
627 def Run(args,
628         errexit=True,
629         stdin_contents=None,
630         redirect_stdout=None,
631         redirect_stderr=None):
632   """ Run: Run a command.
633       Returns: return_code, stdout, stderr
634
635       Run() is used to invoke "other" tools, e.g.
636       those NOT prefixed with "pnacl-"
637
638       stdout and stderr only contain meaningful data if
639           redirect_{stdout,stderr} == subprocess.PIPE
640
641       Run will terminate the program upon failure unless errexit == False
642       TODO(robertm): errexit == True has not been tested and needs more work
643
644       redirect_stdout and redirect_stderr are passed straight
645       to subprocess.Popen
646       stdin_contents is an optional string used as stdin
647   """
648
649   result_stdout = None
650   result_stderr = None
651   if isinstance(args, str):
652     args = shell.split(env.eval(args))
653
654   args = [pathtools.tosys(args[0])] + args[1:]
655
656   Log.Info('Running: ' + StringifyCommand(args))
657   if stdin_contents:
658     Log.Info('--------------stdin: begin')
659     Log.Info(stdin_contents)
660     Log.Info('--------------stdin: end')
661
662   if env.getbool('DRY_RUN'):
663     if redirect_stderr or redirect_stdout:
664       # TODO(pdox): Prevent this from happening, so that
665       # dry-run is more useful.
666       Log.Fatal("Unhandled dry-run case.")
667     return 0, None, None
668
669   try:
670     # If we have too long of a cmdline on windows, running it would fail.
671     # Attempt to use a file with the command line options instead in that case.
672     if ArgsTooLongForWindows(args):
673       actual_args = ConvertArgsToFile(args)
674       Log.Info('Wrote long commandline to file for Windows: ' +
675                StringifyCommand(actual_args))
676
677     else:
678       actual_args = args
679
680     redirect_stdin = None
681     if stdin_contents:
682       redirect_stdin = subprocess.PIPE
683
684     p = subprocess.Popen(actual_args,
685                          stdin=redirect_stdin,
686                          stdout=redirect_stdout,
687                          stderr=redirect_stderr)
688     result_stdout, result_stderr = p.communicate(input=stdin_contents)
689   except Exception, e:
690     msg =  '%s\nCommand was: %s' % (str(e),
691                                     StringifyCommand(args, stdin_contents))
692     print msg
693     DriverExit(1)
694
695   Log.Info('Return Code: ' + str(p.returncode))
696
697   if errexit and p.returncode != 0:
698     if redirect_stdout == subprocess.PIPE:
699       Log.Error('--------------stdout: begin')
700       Log.Error(result_stdout)
701       Log.Error('--------------stdout: end')
702
703     if redirect_stderr == subprocess.PIPE:
704       Log.Error('--------------stderr: begin')
705       Log.Error(result_stderr)
706       Log.Error('--------------stderr: end')
707     DriverExit(p.returncode)
708
709   return p.returncode, result_stdout, result_stderr
710
711
712 def IsWindowsPython():
713   return 'windows' in platform.system().lower()
714
715 def SetupCygwinLibs():
716   bindir = env.getone('DRIVER_BIN')
717   # Prepend the directory containing cygwin1.dll etc. to the PATH to ensure we
718   # get the right one.
719   os.environ['PATH'] = os.pathsep.join(
720       [pathtools.tosys(bindir)] + os.environ['PATH'].split(os.pathsep))
721
722
723 def HelpNotAvailable():
724   return 'Help text not available'
725
726 def DriverMain(module, argv):
727   # TODO(robertm): this is ugly - try to get rid of this
728   if '--pnacl-driver-verbose' in argv:
729     Log.IncreaseVerbosity()
730     env.set('LOG_VERBOSE', '1')
731
732   # driver_path has the form: /foo/bar/pnacl_root/newlib/bin/pnacl-clang
733   driver_path = pathtools.abspath(pathtools.normalize(argv[0]))
734   driver_bin = pathtools.dirname(driver_path)
735   script_name = pathtools.basename(driver_path)
736   env.set('SCRIPT_NAME', script_name)
737   env.set('DRIVER_PATH', driver_path)
738   env.set('DRIVER_BIN', driver_bin)
739
740   Log.SetScriptName(script_name)
741
742   ReadConfig()
743
744   if IsWindowsPython():
745     SetupCygwinLibs()
746
747   # skip tool name
748   argv = argv[1:]
749
750   # Handle help info
751   if ('--help' in argv or
752       '-h' in argv or
753       '-help' in argv or
754       '--help-full' in argv):
755     help_func = getattr(module, 'get_help', None)
756     if not help_func:
757       Log.Fatal(HelpNotAvailable())
758     helpstr = help_func(argv)
759     print helpstr
760     return 0
761
762   return module.main(argv)
763
764
765 def MaybeStripNonSFISuffix(s):
766   """Removes _NONSFI suffix if possible, otherwise |s| as is."""
767   return s[:-len('_NONSFI')] if s.endswith('_NONSFI') else s
768
769
770 def SetArch(arch):
771   arch = FixArch(arch)
772   env.set('ARCH', arch)
773   base_arch = MaybeStripNonSFISuffix(arch)
774   env.set('BASE_ARCH', base_arch)
775   env.setbool('NONSFI_NACL', arch != base_arch)
776
777
778 def GetArch(required = False):
779   arch = env.getone('ARCH')
780   if arch == '':
781     arch = None
782
783   if required and not arch:
784     Log.Fatal('Missing -arch!')
785
786   return arch
787
788 # Read an ELF file or an archive file to determine the machine type. If ARCH is
789 # already set, make sure the file has the same architecture. If ARCH is not
790 # set, set the ARCH to the file's architecture.
791 # Note that the SFI and NONSFI shares the same file format, so they will be
792 # treated as same.
793 #
794 # Returns True if the file matches ARCH.
795 #
796 # Returns False if the file doesn't match ARCH. This only happens when
797 # must_match is False. If must_match is True, then a fatal error is generated
798 # instead.
799 def ArchMerge(filename, must_match):
800   file_type = filetype.FileType(filename)
801   if file_type in ('o','so'):
802     elfheader = elftools.GetELFHeader(filename)
803     if not elfheader:
804       Log.Fatal("%s: Cannot read ELF header", filename)
805     new_arch = elfheader.arch
806   elif filetype.IsNativeArchive(filename):
807     new_arch = file_type[len('archive-'):]
808   else:
809     Log.Fatal('%s: Unexpected file type in ArchMerge', filename)
810
811   existing_arch = GetArch()
812   if not existing_arch:
813     SetArch(new_arch)
814     return True
815
816   # The _NONSFI binary format is as same as the SFI's.
817   existing_arch = MaybeStripNonSFISuffix(existing_arch)
818
819   if new_arch != existing_arch:
820     if must_match:
821       msg = "%s: Incompatible object file (%s != %s)"
822       logfunc = Log.Fatal
823     else:
824       msg = "%s: Skipping incompatible object file (%s != %s)"
825       logfunc = Log.Warning
826     logfunc(msg, filename, new_arch, existing_arch)
827     return False
828
829   # existing_arch and new_arch == existing_arch
830   return True
831
832 def CheckTranslatorPrerequisites():
833   """ Assert that the scons artifacts for running the sandboxed translator
834       exist: sel_universal, sel_ldr, and the IRT blob. """
835   if env.getbool('DRY_RUN'):
836     return
837   reqs = ['SEL_UNIVERSAL', 'SEL_LDR', 'IRT_BLOB']
838   # Linux also requires the nacl bootstrap helper.
839   if GetBuildOS() == 'linux':
840     reqs.append('BOOTSTRAP_LDR')
841   for var in reqs:
842     needed_file = env.getone(var)
843     if not pathtools.exists(needed_file):
844       Log.Fatal('Could not find %s [%s]', var, needed_file)
845
846 class DriverChain(object):
847   """ The DriverChain class takes one or more input files,
848       an output file, and a sequence of steps. It executes
849       those steps, using intermediate files in between,
850       to generate the final outpu.
851   """
852
853   def __init__(self, input, output, namegen):
854     self.input = input
855     self.output = pathtools.normpath(output) if output else output
856     self.steps = []
857     self.namegen = namegen
858
859     # "input" can be a list of files or a single file.
860     # If we're compiling for a single file, then we use
861     # TempNameForInput. If there are multiple files
862     # (e.g. linking), then we use TempNameForOutput.
863     if isinstance(self.input, str):
864       self.use_names_for_input = True
865       self.input = pathtools.normpath(self.input) if self.input else self.input
866       CheckPathLength(self.input)
867     else:
868       self.use_names_for_input = False
869       self.input = [pathtools.normpath(p) if p else p for p in self.input]
870       for path in self.input:
871         CheckPathLength(path)
872     CheckPathLength(output)
873
874   def add(self, callback, output_type, **extra):
875     step = (callback, output_type, extra)
876     self.steps.append(step)
877
878   def run(self):
879     step_input = self.input
880     for (i, (callback, output_type, extra)) in enumerate(self.steps):
881       if i == len(self.steps)-1:
882         # Last step
883         step_output = self.output
884       else:
885         # Intermediate step
886         if self.use_names_for_input:
887           step_output = self.namegen.TempNameForInput(self.input, output_type)
888         else:
889           step_output = self.namegen.TempNameForOutput(output_type)
890       callback(step_input, step_output, **extra)
891       step_input = step_output