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.
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.
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
30 def ParseError(s, leftpos, rightpos, msg):
31 Log.Error("Parse Error: %s", msg)
33 Log.Error(' ' + (' '*leftpos) + ('^'*(rightpos - leftpos + 1)))
37 # Run a command with extra environment settings
38 def RunWithEnv(cmd, **kwargs):
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.
53 os.chmod(realpath, 0755 & ~umask)
56 def FilterOutArchArgs(args):
57 while '-arch' in args:
58 i = args.index('-arch')
59 args = args[:i] + args[i+2:]
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
65 def ParseTriple(triple):
66 tokens = triple.split('-')
71 # The machine/vendor field could be present or not.
72 if os != 'nacl' and len(tokens) >= 3:
74 # Just check that the os is nacl.
78 Log.Fatal('machine/os ' + '-'.join(tokens[1:]) + ' not supported.')
82 if sys.platform == 'darwin':
84 elif sys.platform.startswith('linux'):
86 elif sys.platform in ('cygwin', 'win32'):
89 Log.Fatal('Machine: %s not supported.' % sys.platform)
93 def GetArchNameShort():
94 machine = platform.machine().lower()
95 if machine.startswith('arm'):
97 elif machine.startswith('mips'):
99 elif (machine.startswith('x86')
100 or machine in ('amd32', 'i386', 'i686', 'ia32', '32', 'amd64', '64')):
103 Log.Fatal('Architecture: %s not supported.' % machine)
106 def RunDriver(module_name, args, suppress_inherited_arch_args=False):
108 RunDriver() is used to invoke "driver" tools, e.g.
109 those prefixed with "pnacl-"
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
116 if isinstance(args, str):
117 args = shell.split(env.eval(args))
119 script = env.eval('${DRIVER_BIN}/%s' % module_name)
120 script = shell.unescape(script)
122 inherited_driver_args = env.get('INHERITED_DRIVER_ARGS')
123 if suppress_inherited_arch_args:
124 inherited_driver_args = FilterOutArchArgs(inherited_driver_args)
126 script = pathtools.tosys(script)
127 cmd = [script] + args + inherited_driver_args
128 Log.Info('Driver invocation: %s', repr(cmd))
130 module = __import__(module_name)
131 # Save the environment, reset the environment, run
132 # the driver module, and then restore the environment.
135 DriverMain(module, cmd)
139 """ Memoize a function with no arguments """
145 newf.__name__ = f.__name__
152 name = platform.system().lower()
153 if name.startswith('cygwin_nt') or 'windows' in name:
155 if name not in ('linux', 'darwin', 'windows'):
156 Log.Fatal("Unsupported platform '%s'", name)
162 m = platform.machine()
168 if m not in ('i386', 'i686', 'x86_64'):
169 Log.Fatal("Unsupported architecture '%s'", m)
172 # Crawl backwards, starting from the directory containing this script,
173 # until we find a directory satisfying a filter function.
174 def FindBaseDir(function):
176 cur = env.getone('DRIVER_BIN')
177 while not function(cur) and Depth < 16:
178 cur = pathtools.dirname(cur)
187 """ Find native_client/ directory """
188 dir = FindBaseDir(lambda cur: pathtools.basename(cur) == 'native_client')
190 Log.Fatal("Unable to find 'native_client' directory")
191 return shell.escape(dir)
195 def FindBaseToolchain():
196 """ Find toolchain/OS_ARCH directory """
197 base_dir = FindBaseDir(lambda cur: pathtools.basename(cur) == 'toolchain')
199 Log.Fatal("Unable to find 'toolchain' directory")
200 toolchain_dir = os.path.join(
202 '%s_%s' % (GetOSName(), GetArchNameShort())
204 return shell.escape(toolchain_dir)
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)
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('/'):
221 env.append('BPREFIXES', prefix)
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'))):
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)
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'):
243 driver_bin = env.getone('DRIVER_BIN')
244 driver_conf = pathtools.join(driver_bin, 'driver.conf')
245 fp = DriverOpen(driver_conf, 'r')
250 if line == '' or line.startswith('#'):
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)
262 def AddPrefix(prefix, varname):
263 values = env.get(varname)
264 return ' '.join([prefix + shell.escape(v) for v in values ])
266 ######################################################################
270 ######################################################################
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),
288 DriverArgPatternsNotInherited = [
289 ( '--pnacl-driver-set-([^=]+)=(.*)', "env.set($0, $1)"),
290 ( '--pnacl-driver-append-([^=]+)=(.*)', "env.append($0, $1)"),
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)
304 def DoExpandCommandFile(argv, i):
306 fd = DriverOpen(pathtools.normalize(arg[1:]), 'r')
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).
316 lex = shlex.shlex(line, posix=True)
318 lex.whitespace_split = True
319 more_args += list(lex)
322 argv = argv[:i] + more_args + argv[i+1:]
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.
334 if driver_patternlist:
335 driver_args, argv = ParseArgsBase(argv, driver_patternlist)
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)
342 _, argv = ParseArgsBase(argv, driver_patternlist_not_inherited)
344 _, unmatched = ParseArgsBase(argv, patternlist)
347 Log.Error('Unrecognized argument: ' + u)
348 Log.Fatal('unknown arguments')
351 def ParseArgsBase(argv, patternlist):
352 """ Parse argv using the patterns in patternlist
353 Returns: (matched, unmatched)
359 if ShouldExpandCommandFile(argv[i]):
360 argv = DoExpandCommandFile(argv, i)
361 num_matched, action, groups = MatchOne(argv, i, patternlist)
363 unmatched.append(argv[i])
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)
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))
379 except Exception, err:
380 Log.Fatal('ParseArgs action [%s] failed with: %s', action, err)
382 return (matched, unmatched)
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):
394 match = re.compile('^'+r+'$').match(argv[i+j])
397 matches.append(match)
401 groups = [ list(m.groups()) for m in matches ]
402 groups = reduce(lambda x,y: x+y, groups, [])
403 return (len(regex), action, groups)
406 def UnrecognizedOption(*args):
407 Log.Fatal("Unrecognized option: " + ' '.join(args) + "\n" +
408 "Use '--help' for more information.")
411 ######################################################################
413 # File Naming System (Temp files & Output files)
415 ######################################################################
417 def DefaultOutputName(filename, outtype):
418 # For pre-processor mode, just print to stdout.
419 if outtype in ('pp'): return '-'
421 base = pathtools.basename(filename)
422 base = RemoveExtension(base)
423 if outtype in ('po'): return base + '.o'
425 assert(outtype in filetype.ExtensionMap.values())
426 assert(not filetype.IsSourceType(outtype))
428 return base + '.' + outtype
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'
435 def RemoveExtension(filename):
436 if filename.endswith('.opt.bc'):
437 return filename[0:-len('.opt.bc')]
439 name, ext = pathtools.splitext(filename)
446 cur, piece = pathtools.split(cur)
454 def CheckPathLength(filename, exit_on_failure=True):
455 '''Check that the length of the path is short enough for Windows.
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
464 This function checks that the path is valid, so we can throw meaningful
467 http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
469 if not IsWindowsPython() and not env.has('PNACL_RUNNING_UNITTESTS'):
472 # First check the name as-is (it's usually a relative path)
473 if len(filename) > 255:
475 Log.Fatal('Path name %s is too long (%d characters)' %
476 (filename, len(filename)))
478 if os.path.isabs(filename):
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:
486 Log.Fatal('Path name %s (expanded from %s) is too long (%d characters)' %
487 (appended_name, filename, len(appended_name)))
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)
499 self.TempBase = output + '---linked'
500 self.OutputDir = pathtools.dirname(output)
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 + '---'
509 # Build the initial mapping
510 self.TempMap = dict()
512 if f.startswith('-'):
515 self.TempMap[f] = [1, path]
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])
527 ConflictMap[candidate] = f
529 if len(Conflicts) == 0:
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
541 for (f, [n, path]) in self.TempMap.iteritems():
542 candidate = output + '---' + '_'.join(path[-n:]) + '---'
543 NewMap[f] = candidate
544 self.TempMap = NewMap
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
555 imtype = imtype[imtype.rfind('.') + 1:]
556 temp = pathtools.join(
558 str(random.randrange(100000, 1000000)) + '.' + imtype)
559 CheckPathLength(temp)
562 def TempNameForOutput(self, imtype):
563 temp = self.ValidatePathLength(self.TempBase + '.' + imtype, imtype)
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
574 temp = self.TempMap[fullpath] + '.' + imtype
576 temp = self.ValidatePathLength(temp, imtype)
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:
593 except BaseException:
595 os.kill(os.getpid(), signal.SIGKILL)
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)
603 def ArgsTooLongForWindows(args):
604 """ Detect when a command line might be too long for Windows. """
605 if not IsWindowsPython():
608 return len(' '.join(args)) > 8191
611 def ConvertArgsToFile(args):
612 fd, outfile = tempfile.mkstemp()
613 # Remember to delete this file afterwards.
614 TempFiles.add(outfile)
616 other_args = args[1:]
617 os.write(fd, ' '.join(other_args))
619 return [cmd, '@' + outfile]
622 # The redirect_stdout and redirect_stderr is only used a handful of times
624 # The stdin_contents feature is currently only used by:
625 # sel_universal invocations in the translator
630 redirect_stdout=None,
631 redirect_stderr=None):
632 """ Run: Run a command.
633 Returns: return_code, stdout, stderr
635 Run() is used to invoke "other" tools, e.g.
636 those NOT prefixed with "pnacl-"
638 stdout and stderr only contain meaningful data if
639 redirect_{stdout,stderr} == subprocess.PIPE
641 Run will terminate the program upon failure unless errexit == False
642 TODO(robertm): errexit == True has not been tested and needs more work
644 redirect_stdout and redirect_stderr are passed straight
646 stdin_contents is an optional string used as stdin
651 if isinstance(args, str):
652 args = shell.split(env.eval(args))
654 args = [pathtools.tosys(args[0])] + args[1:]
656 Log.Info('Running: ' + StringifyCommand(args))
658 Log.Info('--------------stdin: begin')
659 Log.Info(stdin_contents)
660 Log.Info('--------------stdin: end')
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.")
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))
680 redirect_stdin = None
682 redirect_stdin = subprocess.PIPE
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)
690 msg = '%s\nCommand was: %s' % (str(e),
691 StringifyCommand(args, stdin_contents))
695 Log.Info('Return Code: ' + str(p.returncode))
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')
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)
709 return p.returncode, result_stdout, result_stderr
712 def IsWindowsPython():
713 return 'windows' in platform.system().lower()
715 def SetupCygwinLibs():
716 bindir = env.getone('DRIVER_BIN')
717 # Prepend the directory containing cygwin1.dll etc. to the PATH to ensure we
719 os.environ['PATH'] = os.pathsep.join(
720 [pathtools.tosys(bindir)] + os.environ['PATH'].split(os.pathsep))
723 def HelpNotAvailable():
724 return 'Help text not available'
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')
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)
740 Log.SetScriptName(script_name)
744 if IsWindowsPython():
751 if ('--help' in argv or
754 '--help-full' in argv):
755 help_func = getattr(module, 'get_help', None)
757 Log.Fatal(HelpNotAvailable())
758 helpstr = help_func(argv)
762 return module.main(argv)
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
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)
778 def GetArch(required = False):
779 arch = env.getone('ARCH')
783 if required and not arch:
784 Log.Fatal('Missing -arch!')
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
794 # Returns True if the file matches ARCH.
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
799 def ArchMerge(filename, must_match):
800 file_type = filetype.FileType(filename)
801 if file_type in ('o','so'):
802 elfheader = elftools.GetELFHeader(filename)
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-'):]
809 Log.Fatal('%s: Unexpected file type in ArchMerge', filename)
811 existing_arch = GetArch()
812 if not existing_arch:
816 # The _NONSFI binary format is as same as the SFI's.
817 existing_arch = MaybeStripNonSFISuffix(existing_arch)
819 if new_arch != existing_arch:
821 msg = "%s: Incompatible object file (%s != %s)"
824 msg = "%s: Skipping incompatible object file (%s != %s)"
825 logfunc = Log.Warning
826 logfunc(msg, filename, new_arch, existing_arch)
829 # existing_arch and new_arch == existing_arch
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'):
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')
842 needed_file = env.getone(var)
843 if not pathtools.exists(needed_file):
844 Log.Fatal('Could not find %s [%s]', var, needed_file)
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.
853 def __init__(self, input, output, namegen):
855 self.output = pathtools.normpath(output) if output else output
857 self.namegen = namegen
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)
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)
874 def add(self, callback, output_type, **extra):
875 step = (callback, output_type, extra)
876 self.steps.append(step)
879 step_input = self.input
880 for (i, (callback, output_type, extra)) in enumerate(self.steps):
881 if i == len(self.steps)-1:
883 step_output = self.output
886 if self.use_names_for_input:
887 step_output = self.namegen.TempNameForInput(self.input, output_type)
889 step_output = self.namegen.TempNameForOutput(output_type)
890 callback(step_input, step_output, **extra)
891 step_input = step_output