diff options
Diffstat (limited to 'clang/tools/ccc/ccclib/Driver.py')
-rw-r--r-- | clang/tools/ccc/ccclib/Driver.py | 666 |
1 files changed, 666 insertions, 0 deletions
diff --git a/clang/tools/ccc/ccclib/Driver.py b/clang/tools/ccc/ccclib/Driver.py new file mode 100644 index 00000000000..4bf0478a505 --- /dev/null +++ b/clang/tools/ccc/ccclib/Driver.py @@ -0,0 +1,666 @@ +import os +import sys +import tempfile +from pprint import pprint + +### + +import Arguments +import Jobs +import Phases +import Tools +import Types +import Util + +# FIXME: Clean up naming of options and arguments. Decide whether to +# rename Option and be consistent about use of Option/Arg. + +#### + +class MissingArgumentError(ValueError): + """MissingArgumentError - An option required an argument but none + was given.""" + +### + +class Driver(object): + def __init__(self): + self.parser = Arguments.createOptionParser() + + def run(self, args): + # FIXME: Things to support from environment: GCC_EXEC_PREFIX, + # COMPILER_PATH, LIBRARY_PATH, LPATH, CC_PRINT_OPTIONS, + # QA_OVERRIDE_GCC3_OPTIONS, ...? + + # FIXME: -V and -b processing + + # Handle some special -ccc- options used for testing which are + # only allowed at the beginning of the command line. + cccPrintOptions = False + cccPrintPhases = False + cccUseDriverDriver = True + while args and args[0].startswith('-ccc-'): + opt,args = args[0][5:],args[1:] + + if opt == 'print-options': + cccPrintOptions = True + elif opt == 'print-phases': + cccPrintPhases = True + elif opt == 'no-driver-driver': + # FIXME: Remove this once we have some way of being a + # cross compiler driver (cross driver compiler? compiler + # cross driver? etc.). + cccUseDriverDriver = False + else: + raise ValueError,"Invalid ccc option: %r" % cccPrintOptions + + options = self.parser.chunkArgs(args) + + # FIXME: Ho hum I have just realized -Xarch_ is broken. We really + # need to reparse the Arguments after they have been expanded by + # -Xarch. How is this going to work? + # + # Scratch that, we aren't going to do that; it really disrupts the + # organization, doesn't consistently work with gcc-dd, and is + # confusing. Instead we are going to enforce that -Xarch_ is only + # used with options which do not alter the driver behavior. Let's + # hope this is ok, because the current architecture is a little + # tied to it. + + if cccPrintOptions: + self.printOptions(args, options) + sys.exit(0) + + self.handleImmediateOptions(args, options) + + if cccUseDriverDriver: + phases = self.buildPipeline(options, args) + else: + phases = self.buildNormalPipeline(options, args) + + if cccPrintPhases: + self.printPhases(args, phases) + sys.exit(0) + + if 0: + print Util.pprint(phases) + + jobs = self.bindPhases(phases, options, args) + + # FIXME: We should provide some basic sanity checking of the + # pipeline as a "verification" sort of stage. For example, the + # pipeline should never end up writing to an output file in two + # places (I think). The pipeline should also never end up writing + # to an output file that is an input. + # + # This is intended to just be a "verify" step, not a functionality + # step. It should catch things like the driver driver not + # preventing -save-temps, but it shouldn't change behavior (so we + # can turn it off in Release-Asserts builds). + + # Print in -### syntax. + hasHashHashHash = None + for oi in options: + if oi.opt and oi.opt.name == '-###': + hasHashHashHash = oi + + if hasHashHashHash: + self.claim(hasHashHashHash) + for j in jobs.iterjobs(): + if isinstance(j, Jobs.Command): + print '"%s"' % '" "'.join(j.render(args)) + elif isinstance(j, Jobs.PipedJob): + for c in j.commands: + print '"%s" %c' % ('" "'.join(c.render(args)), + "| "[c is j.commands[-1]]) + elif not isinstance(j, JobList): + raise ValueError,'Encountered unknown job.' + sys.exit(0) + + for j in jobs.iterjobs(): + if isinstance(j, Jobs.Command): + cmd_args = j.render(args) + res = os.spawnvp(os.P_WAIT, cmd_args[0], cmd_args) + if res: + sys.exit(res) + elif isinstance(j, Jobs.PipedJob): + raise NotImplementedError,"Piped jobs aren't implemented yet." + else: + raise ValueError,'Encountered unknown job.' + + def claim(self, option): + # FIXME: Move to OptionList once introduced and implement. + pass + + def warning(self, message): + print >>sys.stderr,'%s: %s' % (sys.argv[0], message) + + def printOptions(self, args, options): + for i,oi in enumerate(options): + if isinstance(oi, Arguments.InputArg): + name = "<input>" + elif isinstance(oi, Arguments.UnknownArg): + name = "<unknown>" + else: + assert oi.opt + name = oi.opt.name + if isinstance(oi, Arguments.MultipleValuesArg): + values = list(oi.getValues(args)) + elif isinstance(oi, Arguments.ValueArg): + values = [oi.getValue(args)] + elif isinstance(oi, Arguments.JoinedAndSeparateValuesArg): + values = [oi.getJoinedValue(args), oi.getSeparateValue(args)] + else: + values = [] + print 'Option %d - Name: "%s", Values: {%s}' % (i, name, + ', '.join(['"%s"' % v + for v in values])) + + def printPhases(self, args, phases): + def printPhase(p, f, steps, arch=None): + if p in steps: + return steps[p] + elif isinstance(p, Phases.BindArchAction): + for kid in p.inputs: + printPhase(kid, f, steps, p.arch) + steps[p] = len(steps) + return + + if isinstance(p, Phases.InputAction): + phaseName = 'input' + inputStr = '"%s"' % p.filename.getValue(args) + else: + phaseName = p.phase.name + inputs = [printPhase(i, f, steps, arch) + for i in p.inputs] + inputStr = '{%s}' % ', '.join(map(str, inputs)) + if arch is not None: + phaseName += '-' + arch.getValue(args) + steps[p] = index = len(steps) + print "%d: %s, %s, %s" % (index,phaseName,inputStr,p.type.name) + return index + steps = {} + for phase in phases: + printPhase(phase, sys.stdout, steps) + + def handleImmediateOptions(self, args, options): + # FIXME: Some driver Arguments are consumed right off the bat, + # like -dumpversion. Currently the gcc-dd handles these + # poorly, so we should be ok handling them upfront instead of + # after driver-driver level dispatching. + # + # FIXME: The actual order of these options in gcc is all over the + # place. The -dump ones seem to be first and in specification + # order, but there are other levels of precedence. For example, + # -print-search-dirs is evaluated before -print-prog-name=, + # regardless of order (and the last instance of -print-prog-name= + # wins verse itself). + # + # FIXME: Do we want to report "argument unused" type errors in the + # presence of things like -dumpmachine and -print-search-dirs? + # Probably not. + for oi in options: + if oi.opt is not None: + if oi.opt.name == '-dumpmachine': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-dumpspecs': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-dumpversion': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-print-file-name=': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-print-multi-directory': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-print-multi-lib': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-print-prog-name=': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-print-libgcc-file-name': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + elif oi.opt.name == '-print-search-dirs': + print 'FIXME: %s' % oi.opt.name + sys.exit(1) + + def buildNormalPipeline(self, args, inputArgs): + hasCombine = None + hasSyntaxOnly = None + hasDashC = hasDashE = hasDashS = None + + inputType = None + inputTypeOpt = None + inputs = [] + for a in args: + if isinstance(a, Arguments.InputArg): + if inputType is None: + base,ext = os.path.splitext(a.getValue(inputArgs)) + if ext and ext in Types.kTypeSuffixMap: + klass = Types.kTypeSuffixMap[ext] + else: + # FIXME: Its not clear why we shouldn't just + # revert to unknown. I think this is more likely a + # bug / unintended behavior in gcc. Not very + # important though. + klass = Types.ObjectType + else: + assert inputTypeOpt is not None + self.claim(inputTypeOpt) + klass = inputType + inputs.append((klass, a)) + elif a.opt is not None: + # FIXME: We should warn about inconsistent and duplicate + # usage of these flags. + if a.opt.name == '-E': + hasDashE = a + elif a.opt.name == '-S': + hasDashS = a + elif a.opt.name == '-c': + hasDashC = a + elif a.opt.name == '-fsyntax-only': + hasSyntaxOnly = a + elif a.opt.name == '-combine': + hasCombine = a + elif a.opt.name == '-filelist': + # FIXME: This might not be good enough. We may + # need to introduce another type of InputArg for + # this case, so that other code which needs to + # know the inputs handles this properly. Best not + # to try and lipo this, for example. + # + # Treat as a linker input. + inputs.append((Types.ObjectType, a)) + elif a.opt.name == '-x': + self.claim(a) + inputTypeOpt = a + value = a.getValue(inputArgs) + if value in Types.kTypeSpecifierMap: + inputType = Types.kTypeSpecifierMap[value] + else: + # FIXME: How are we going to handle diagnostics. + self.warning("language %s not recognized" % value) + + # FIXME: Its not clear why we shouldn't just + # revert to unknown. I think this is more likely a + # bug / unintended behavior in gcc. Not very + # important though. + inputType = ObjectType + + # We claim things here so that options for which we silently allow + # override only ever claim the used option. + if hasCombine: + self.claim(hasCombine) + + finalPhase = Phases.Phase.eOrderPostAssemble + finalPhaseOpt = None + + # Determine what compilation mode we are in. + if hasDashE: + finalPhase = Phases.Phase.eOrderPreprocess + finalPhaseOpt = hasDashE + elif hasSyntaxOnly: + finalPhase = Phases.Phase.eOrderCompile + finalPhaseOpt = hasSyntaxOnly + elif hasDashS: + finalPhase = Phases.Phase.eOrderCompile + finalPhaseOpt = hasDashS + elif hasDashC: + finalPhase = Phases.Phase.eOrderAssemble + finalPhaseOpt = hasDashC + + if finalPhaseOpt: + self.claim(finalPhaseOpt) + + # FIXME: Support -combine. + if hasCombine: + raise NotImplementedError,"-combine is not yet supported." + + actions = [] + linkerInputs = [] + # FIXME: This is gross. + linkPhase = Phases.LinkPhase() + for klass,input in inputs: + # Figure out what step to start at. + + # FIXME: This should be part of the input class probably? + # Altough it doesn't quite fit there either, things like + # asm-with-preprocess don't easily fit into a linear scheme. + + # FIXME: I think we are going to end up wanting to just build + # a simple FSA which we run the inputs down. + sequence = [] + if klass.preprocess: + sequence.append(Phases.PreprocessPhase()) + if klass == Types.ObjectType: + sequence.append(linkPhase) + elif klass.onlyAssemble: + sequence.extend([Phases.AssemblePhase(), + linkPhase]) + elif klass.onlyPrecompile: + sequence.append(Phases.PrecompilePhase()) + else: + sequence.extend([Phases.CompilePhase(), + Phases.AssemblePhase(), + linkPhase]) + + if sequence[0].order > finalPhase: + assert finalPhaseOpt and finalPhaseOpt.opt + # FIXME: Explain what type of input file is. Or just match + # gcc warning. + self.warning("%s: %s input file unused when %s is present" % (input.getValue(inputArgs), + sequence[0].name, + finalPhaseOpt.opt.name)) + else: + # Build the pipeline for this file. + + current = Phases.InputAction(input, klass) + for transition in sequence: + # If the current action produces no output, or we are + # past what the user requested, we are done. + if (current.type is Types.NothingType or + transition.order > finalPhase): + break + else: + if isinstance(transition, Phases.PreprocessPhase): + assert isinstance(klass.preprocess, Types.InputType) + current = Phases.JobAction(transition, + [current], + klass.preprocess) + elif isinstance(transition, Phases.PrecompilePhase): + current = Phases.JobAction(transition, + [current], + Types.PCHType) + elif isinstance(transition, Phases.CompilePhase): + if hasSyntaxOnly: + output = Types.NothingType + else: + output = Types.AsmTypeNoPP + current = Phases.JobAction(transition, + [current], + output) + elif isinstance(transition, Phases.AssemblePhase): + current = Phases.JobAction(transition, + [current], + Types.ObjectType) + elif transition is linkPhase: + linkerInputs.append(current) + current = None + break + else: + raise RuntimeError,'Unrecognized transition: %s.' % transition + pass + + if current is not None: + assert not isinstance(current, Phases.InputAction) + actions.append(current) + + if linkerInputs: + actions.append(Phases.JobAction(linkPhase, + linkerInputs, + Types.ImageType)) + + return actions + + def buildPipeline(self, args, inputArgs): + # FIXME: We need to handle canonicalization of the specified arch. + + archs = [] + hasOutput = None + hasDashM = hasSaveTemps = None + for o in args: + if o.opt is None: + continue + + if isinstance(o, Arguments.ValueArg): + if o.opt.name == '-arch': + archs.append(o) + elif o.opt.name.startswith('-M'): + hasDashM = o + elif o.opt.name in ('-save-temps','--save-temps'): + hasSaveTemps = o + + if not archs: + # FIXME: Need to infer arch so that we sub -Xarch + # correctly. + archs.append(Arguments.DerivedArg('i386')) + + actions = self.buildNormalPipeline(args, inputArgs) + + # FIXME: Use custom exception for this. + # + # FIXME: We killed off some others but these aren't yet detected in + # a functional manner. If we added information to jobs about which + # "auxiliary" files they wrote then we could detect the conflict + # these cause downstream. + if len(archs) > 1: + if hasDashM: + raise ValueError,"Cannot use -M options with multiple arch flags." + elif hasSaveTemps: + raise ValueError,"Cannot use -save-temps with multiple arch flags." + + # Execute once per arch. + finalActions = [] + for p in actions: + # Make sure we can lipo this kind of output. If not (and it + # is an actual output) then we disallow, since we can't + # create an output file with the right name without + # overwriting it. We could remove this oddity by just + # changing the output names to include the arch, which would + # also fix -save-temps. Compatibility wins for now. + # + # FIXME: Is this error substantially less useful than + # gcc-dd's? The main problem is that "Cannot use compiler + # output with multiple arch flags" won't make sense to most + # developers. + if (len(archs) > 1 and + p.type not in (Types.NothingType,Types.ObjectType,Types.ImageType)): + raise ValueError,'Cannot use %s output with multiple arch flags.' % p.type.name + + inputs = [] + for arch in archs: + inputs.append(Phases.BindArchAction(p, arch)) + + # Lipo if necessary. We do it this way because we need to set + # the arch flag so that -Xarch_ gets rewritten. + if len(inputs) == 1 or p.type == Types.NothingType: + finalActions.extend(inputs) + else: + finalActions.append(Phases.JobAction(Phases.LipoPhase(), + inputs, + p.type)) + + # FIXME: We need to add -Wl,arch_multiple and -Wl,final_output in + # certain cases. This may be icky because we need to figure out the + # mode first. Current plan is to hack on the pipeline once it is built + # and we know what is being spit out. This avoids having to handling + # things like -c and -combine in multiple places. + # + # The annoying one of these is -Wl,final_output because it involves + # communication across different phases. + # + # Hopefully we can do this purely as part of the binding, but + # leaving comment here for now until it is clear this works. + + return finalActions + + def bindPhases(self, phases, args, inputArgs): + jobs = Jobs.JobList() + + finalOutput = None + hasSaveTemps = hasNoIntegratedCPP = hasPipe = None + forward = [] + for a in args: + if isinstance(a, Arguments.InputArg): + pass + elif a.opt is not None: + if a.opt.name == '-save-temps': + hasSaveTemps = a + elif a.opt.name == '-no-integrated-cpp': + hasNoIntegratedCPP = a + elif a.opt.name == '-o': + finalOutput = a + elif a.opt.name == '-pipe': + hasPipe = a + elif a.opt.name in ('-E', '-S', '-c', + '-arch', '-fsyntax-only', '-combine', '-x', + '-###'): + pass + else: + forward.append(a) + else: + forward.append(a) + + # We claim things here so that options for which we silently allow + # override only ever claim the used option. + if hasPipe: + self.claim(hasPipe) + # FIXME: Hack, override -pipe till we support it. + hasPipe = None + # Claim these here. Its not completely accurate but any warnings + # about these being unused are likely to be noise anyway. + if hasSaveTemps: + self.claim(hasSaveTemps) + if hasNoIntegratedCPP: + self.claim(hasNoIntegratedCPP) + + toolMap = { + Phases.PreprocessPhase : Tools.GCC_PreprocessTool(), + Phases.CompilePhase : Tools.GCC_CompileTool(), + Phases.PrecompilePhase : Tools.GCC_PrecompileTool(), + Phases.AssemblePhase : Tools.DarwinAssemblerTool(), + Phases.LinkPhase : Tools.Collect2Tool(), + Phases.LipoPhase : Tools.LipoTool(), + } + + class InputInfo: + def __init__(self, source, type, baseInput): + self.source = source + self.type = type + self.baseInput = baseInput + + def __repr__(self): + return '%s(%r, %r, %r)' % (self.__class__.__name__, + self.source, self.type, self.baseInput) + + def createJobs(phase, forwardArgs, + canAcceptPipe=False, atTopLevel=False, arch=None): + if isinstance(phase, Phases.InputAction): + return InputInfo(phase.filename, phase.type, phase.filename) + elif isinstance(phase, Phases.BindArchAction): + archName = phase.arch.getValue(inputArgs) + filteredArgs = [] + for oi in forwardArgs: + if oi.opt is None: + filteredArgs.append(oi) + elif oi.opt.name == '-arch': + if oi is phase.arch: + filteredArgs.append(oi) + elif oi.opt.name == '-Xarch_': + # FIXME: gcc-dd has another conditional for passing + # through, when the arch conditional array has an empty + # string. Why? + if oi.getJoinedValue(inputArgs) == archName: + # FIXME: This is wrong, we don't want a + # DerivedArg we want an actual parsed version + # of this arg. + filteredArgs.append(Arguments.DerivedArg(oi.getSeparateValue(inputArgs))) + else: + filteredArgs.append(oi) + + return createJobs(phase.inputs[0], filteredArgs, + canAcceptPipe, atTopLevel, phase.arch) + + assert isinstance(phase, Phases.JobAction) + tool = toolMap[phase.phase.__class__] + + # See if we should use an integrated CPP. We only use an + # integrated cpp when we have exactly one input, since this is + # the only use case we care about. + useIntegratedCPP = False + inputList = phase.inputs + if (not hasNoIntegratedCPP and + not hasSaveTemps and + tool.hasIntegratedCPP()): + if (len(phase.inputs) == 1 and + isinstance(phase.inputs[0].phase, Phases.PreprocessPhase)): + useIntegratedCPP = True + inputList = phase.inputs[0].inputs + + # Only try to use pipes when exactly one input. + canAcceptPipe = len(inputList) == 1 and tool.acceptsPipedInput() + inputs = [createJobs(p, forwardArgs, canAcceptPipe, False, arch) for p in inputList] + + # Determine if we should output to a pipe. + canOutputToPipe = canAcceptPipe and tool.canPipeOutput() + outputToPipe = False + if canOutputToPipe: + # Some things default to writing to a pipe if the final + # phase and there was no user override. + # + # FIXME: What is the best way to handle this? + if (atTopLevel and + isinstance(phase, Phases.PreprocessPhase) and + not finalOutput): + outputToPipe = True + elif hasPipe: + outputToPipe = True + + # Figure out where to put the job (pipes). + jobList = jobs + if canAcceptPipe and isinstance(inputs[0].source, Jobs.PipedJob): + jobList = inputs[0].source + + # Figure out where to put the output. + baseInput = inputs[0].baseInput + if phase.type == Types.NothingType: + output = None + elif outputToPipe: + if isinstance(jobList, Jobs.PipedJob): + output = jobList + else: + jobList = output = Jobs.PipedJob([]) + jobs.addJob(output) + else: + # Figure out what the derived output location would be. + # + # FIXME: gcc has some special case in here so that it doesn't + # create output files if they would conflict with an input. + inputName = baseInput.getValue(inputArgs) + if phase.type is Types.ImageType: + namedOutput = "a.out" + else: + base,_ = os.path.splitext(inputName) + assert phase.type.tempSuffix is not None + namedOutput = base + phase.type.tempSuffix + + # Output to user requested destination? + if atTopLevel and finalOutput: + output = finalOutput + # Contruct a named destination? + elif atTopLevel or hasSaveTemps: + output = Arguments.DerivedArg(namedOutput) + else: + # Output to temp file... + fd,filename = tempfile.mkstemp(suffix=phase.type.tempSuffix) + output = Arguments.DerivedArg(filename) + + tool.constructJob(phase, arch, jobList, inputs, output, phase.type, forwardArgs) + + return InputInfo(output, phase.type, baseInput) + + # It is an error to provide a -o option if we are making multiple + # output files. + if finalOutput and len([a for a in phases if a.type is not Types.NothingType]) > 1: + # FIXME: Custom exception. + raise ValueError,"Cannot specify -o when generating multiple files." + + for phase in phases: + createJobs(phase, forward, canAcceptPipe=True, atTopLevel=True) + + return jobs |