import os
import sys # FIXME: Shouldn't be needed.

import Arguments
import Jobs
import Types

class Tool(object):
    """Tool - A concrete implementation of an action."""

    eFlagsPipedInput = 1 << 0
    eFlagsPipedOutput = 1 << 1
    eFlagsIntegratedCPP = 1 << 2

    def __init__(self, name, flags = 0):
        self.name = name
        self.flags = flags

    def acceptsPipedInput(self):
        return not not (self.flags & Tool.eFlagsPipedInput)
    def canPipeOutput(self):
        return not not (self.flags & Tool.eFlagsPipedOutput)
    def hasIntegratedCPP(self):
        return not not (self.flags & Tool.eFlagsIntegratedCPP)

class GCC_Common_Tool(Tool):
    def constructJob(self, phase, arch, jobs, inputs, 
                     output, outputType, args, arglist,
                     extraArgs):
        cmd_args = sum(map(arglist.render, args),[]) + extraArgs
        if arch:
            cmd_args.extend(arglist.render(arch))
        if isinstance(output, Jobs.PipedJob):
            cmd_args.extend(['-o', '-'])
        elif output is None:
            cmd_args.append('-fsyntax-only')
        else:
            cmd_args.extend(arglist.render(output))

        # Only pass -x if gcc will understand it; otherwise hope gcc
        # understands the suffix correctly. The main use case this
        # would go wrong in is for linker inputs if they happened to
        # have an odd suffix; really the only way to get this to
        # happen is a command like '-x foobar a.c' which will treat
        # a.c like a linker input.
        #
        # FIXME: For the linker case specifically, can we safely
        # convert inputs into '-Wl,' options?
        for input in inputs:
            if input.type.canBeUserSpecified:
                cmd_args.extend(['-x', input.type.name])

            if isinstance(input.source, Jobs.PipedJob):
                cmd_args.append('-')
            else:
                assert isinstance(input.source, Arguments.Arg)
                # If this is a linker input then assume we can forward
                # just by rendering.
                if input.source.opt.isLinkerInput:
                    cmd_args.extend(arglist.render(input.source))
                else:
                    cmd_args.extend(arglist.renderAsInput(input.source))

        jobs.addJob(Jobs.Command('gcc', cmd_args))

class GCC_PreprocessTool(GCC_Common_Tool):
    def __init__(self):
        super(GCC_PreprocessTool, self).__init__('gcc (cpp)',
                                                 (Tool.eFlagsPipedInput |
                                                  Tool.eFlagsPipedOutput))

    def constructJob(self, phase, arch, jobs, inputs, 
                     output, outputType, args, arglist):
        return super(GCC_PreprocessTool, self).constructJob(phase, arch, jobs, inputs,
                                                            output, outputType, args, arglist,
                                                            ['-E'])

class GCC_CompileTool(GCC_Common_Tool):
    def __init__(self):
        super(GCC_CompileTool, self).__init__('gcc (cc1)',
                                              (Tool.eFlagsPipedInput |
                                               Tool.eFlagsPipedOutput |
                                               Tool.eFlagsIntegratedCPP))

    def constructJob(self, phase, arch, jobs, inputs, 
                     output, outputType, args, arglist):
        return super(GCC_CompileTool, self).constructJob(phase, arch, jobs, inputs,
                                                         output, outputType, args, arglist,
                                                         ['-S'])

class GCC_PrecompileTool(GCC_Common_Tool):
    def __init__(self):
        super(GCC_PrecompileTool, self).__init__('gcc (pch)',
                                                 (Tool.eFlagsPipedInput |
                                                  Tool.eFlagsIntegratedCPP))

    def constructJob(self, phase, arch, jobs, inputs, 
                     output, outputType, args, arglist):
        return super(GCC_PrecompileTool, self).constructJob(phase, arch, jobs, inputs,
                                                            output, outputType, args, arglist,
                                                            [])

class Darwin_AssembleTool(Tool):
    def __init__(self, toolChain):
        super(Darwin_AssembleTool, self).__init__('as',
                                                  Tool.eFlagsPipedInput)
        self.toolChain = toolChain

    def constructJob(self, phase, arch, jobs, inputs, 
                     output, outputType, args, arglist):
        assert len(inputs) == 1
        assert outputType is Types.ObjectType

        input = inputs[0]

        cmd_args = []
        
        if (arglist.getLastArg(arglist.parser.gOption) or
            arglist.getLastArg(arglist.parser.g3Option)):
            cmd_args.append('--gstabs')

        # Derived from asm spec.
        if arch:
            cmd_args.extend(arglist.render(arch))
        cmd_args.append('-force_cpusubtype_ALL')
        if (arglist.getLastArg(arglist.parser.m_kernelOption) or
            arglist.getLastArg(arglist.parser.staticOption) or
            arglist.getLastArg(arglist.parser.f_appleKextOption)):
            if not arglist.getLastArg(arglist.parser.ZdynamicOption):
                cmd_args.append('-static')

        for arg in arglist.getArgs2(arglist.parser.WaOption,
                                    arglist.parser.XassemblerOption):
            cmd_args.extend(arglist.getValues(arg))

        cmd_args.extend(arglist.render(output))
        if isinstance(input.source, Jobs.PipedJob):
            cmd_args.append('-')
        else:
            cmd_args.extend(arglist.renderAsInput(input.source))
            
        # asm_final spec is empty.

        jobs.addJob(Jobs.Command(self.toolChain.getProgramPath('as'), 
                                 cmd_args))

class GCC_AssembleTool(GCC_Common_Tool):
    def __init__(self):
        # We can't generally assume the assembler can take or output
        # on pipes.
        super(GCC_AssembleTool, self).__init__('gcc (as)')

    def constructJob(self, phase, arch, jobs, inputs, 
                     output, outputType, args, arglist):
        return super(GCC_AssembleTool, self).constructJob(phase, arch, jobs, inputs,
                                                          output, outputType, args, arglist,
                                                          ['-c'])

class GCC_LinkTool(GCC_Common_Tool):
    def __init__(self):
        super(GCC_LinkTool, self).__init__('gcc (ld)')

    def constructJob(self, phase, arch, jobs, inputs, 
                     output, outputType, args, arglist):
        return super(GCC_LinkTool, self).constructJob(phase, arch, jobs, inputs,
                                                      output, outputType, args, arglist,
                                                      [])

class Darwin_X86_CompileTool(Tool):
    def __init__(self, toolChain):
        super(Darwin_X86_CompileTool, self).__init__('cc1',
                                                     (Tool.eFlagsPipedInput |
                                                      Tool.eFlagsPipedOutput |
                                                      Tool.eFlagsIntegratedCPP))
        self.toolChain = toolChain

    def addCPPArgs(self, cmd_args, arch, arglist):
        # Derived from cpp spec.

        # FIXME: The gcc spec is broken here, it refers to dynamic but
        # that has been translated.
        if arglist.getLastArg(arglist.parser.staticOption):
            if not arglist.getLastArg(arglist.parser.ZdynamicOption):
                cmd_args.append('-D__STATIC__')
        else:
            cmd_args.append('-D__DYNAMIC__')
        
        if arglist.getLastArg(arglist.parser.pthreadOption):
            cmd_args.append('-D_REENTRANT')
        
    def addCC1Args(self, cmd_args, arch, arglist):
        # Derived from cc1 spec.

        # FIXME: -fapple-kext seems to disable this too. Investigate.
        if (not arglist.getLastArg(arglist.parser.m_kernelOption) and
            not arglist.getLastArg(arglist.parser.staticOption) and
            not arglist.getLastArg(arglist.parser.m_dynamicNoPicOption)):
            cmd_args.append('-fPIC')

        # FIXME: Remove mthumb
        # FIXME: Remove mno-thumb

        # FIXME: As with ld, something else is going on. My best guess
        # is gcc is faking an -mmacosx-version-min
        # somewhere. Investigate.
        if (not arglist.getLastArg(arglist.parser.m_macosxVersionMinOption) and
            not arglist.getLastArg(arglist.parser.m_iphoneosVersionMinOption)):
            cmd_args.append('-mmacosx-version-min=' + 
                            self.toolChain.getMacosxVersionMin())

        # FIXME: Remove faltivec
        # FIXME: Remove mno-fused-madd
        # FIXME: Remove mlong-branch
        # FIXME: Remove mlongcall
        # FIXME: Remove mcpu=G4
        # FIXME: Remove mcpu=G5

        if (arglist.getLastArg(arglist.parser.gOption) and
            not arglist.getLastArg(arglist.parser.f_noEliminateUnusedDebugSymbolsOption)):
            cmd_args.append('-feliminate-unused-debug-symbols')

    def getBaseInputName(self, inputs, arglist):
        # FIXME: gcc uses a temporary name here when the base
        # input is stdin, but only in auxbase. Investigate.
        baseInputValue = arglist.getValue(inputs[0].baseInput)
        return os.path.basename(baseInputValue)
    
    def getBaseInputStem(self, inputs, arglist):
        return os.path.splitext(self.getBaseInputName(inputs, arglist))[0]

    def constructJob(self, phase, arch, jobs, inputs, 
                     output, outputType, args, arglist):
        inputType = inputs[0].type
        assert not [i for i in inputs if i.type != inputType]

        usePP = False
        if inputType is Types.CType:
            cc1Name = 'cc1'
            usePP = True
        elif inputType is Types.CTypeNoPP:
            cc1Name = 'cc1'
            usePP = False
        elif inputType is Types.ObjCType:
            cc1Name = 'cc1obj'
            usePP = True
        elif inputType is Types.ObjCTypeNoPP:
            cc1Name = 'cc1obj'
            usePP = False
        elif inputType is Types.CXXType:
            cc1Name = 'cc1plus'
            usePP = True
        elif inputType is Types.CXXTypeNoPP:
            cc1Name = 'cc1plus'
            usePP = False
        elif inputType is Types.ObjCXXType:
            cc1Name = 'cc1objplus'
            usePP = True
        elif inputType is Types.ObjCXXTypeNoPP:
            cc1Name = 'cc1objplus'
            usePP = False
        else:
            raise RuntimeError,"Unexpected input type for Darwin compile tool."

        cmd_args = []
        if (arglist.getLastArg(arglist.parser.traditionalOption) or
            arglist.getLastArg(arglist.parser.f_traditionalOption)):
            raise ValueError,"-traditional is not supported without -E"

        if usePP:
            # Derived from cpp_unique_options.

            if (arglist.getLastArg(arglist.parser.COption) or
                arglist.getLastArg(arglist.parser.CCOption)):
                if not arglist.getLastArg(arglist.parser.EOption):
                    raise ValueError,"-C or -CC is not supported without -E"
            if not arglist.getLastArg(arglist.parser.QOption):
                cmd_args.append('-quiet')
            arglist.addAllArgs(cmd_args, arglist.parser.nostdincOption)
            arglist.addLastArg(cmd_args, arglist.parser.vOption)
            arglist.addAllArgs2(cmd_args, arglist.parser.IOption, arglist.parser.FOption)
            arglist.addLastArg(cmd_args, arglist.parser.POption)

            # FIXME: Handle %I properly.
            if arglist.getValue(arch) == 'x86_64':
                cmd_args.append('-imultilib')
                cmd_args.append('x86_64')

            if arglist.getLastArg(arglist.parser.MDOption):
                cmd_args.append('-MD')
                # FIXME: Think about this more.
                outputOpt = arglist.getLastArg(arglist.parser.oOption)
                if outputOpt:
                    base,ext = os.path.splitext(arglist.getValue(outputOpt))
                    cmd_args.append(base+'.d')
                else:
                    cmd_args.append(self.getBaseInputStem(inputs, arglist)+'.d')
            if arglist.getLastArg(arglist.parser.MMDOption):
                cmd_args.append('-MMD')
                # FIXME: Think about this more.
                outputOpt = arglist.getLastArg(arglist.parser.oOption)
                if outputOpt:
                    base,ext = os.path.splitext(arglist.getValue(outputOpt))
                    cmd_args.append(base+'.d')
                else:
                    cmd_args.append(self.getBaseInputStem(inputs, arglist)+'.d')
            arglist.addLastArg(cmd_args, arglist.parser.MOption)
            arglist.addLastArg(cmd_args, arglist.parser.MMOption)
            arglist.addAllArgs(cmd_args, arglist.parser.MFOption)
            arglist.addLastArg(cmd_args, arglist.parser.MGOption)
            arglist.addLastArg(cmd_args, arglist.parser.MPOption)
            arglist.addAllArgs(cmd_args, arglist.parser.MQOption)
            arglist.addAllArgs(cmd_args, arglist.parser.MTOption)
            if (not arglist.getLastArg(arglist.parser.MOption) and
                not arglist.getLastArg(arglist.parser.MMOption) and
                (arglist.getLastArg(arglist.parser.MDOption) or
                 arglist.getLastArg(arglist.parser.MMDOption))):
                outputOpt = arglist.getLastArg(arglist.parser.oOption)
                if outputOpt:
                    cmd_args.append('-MQ')
                    cmd_args.append(arglist.getValue(outputOpt))

            arglist.addLastArg(cmd_args, arglist.parser.remapOption)
            if arglist.getLastArg(arglist.parser.g3Option):
                cmd_args.append('-dD')
            arglist.addLastArg(cmd_args, arglist.parser.HOption)

            self.addCPPArgs(cmd_args, arch, arglist)

            arglist.addAllArgs3(cmd_args, 
                                arglist.parser.DOption,
                                arglist.parser.UOption,
                                arglist.parser.AOption)

            # FIXME: Add i*

            for input in inputs:
                if isinstance(input.source, Jobs.PipedJob):
                    cmd_args.append('-')
                else:
                    cmd_args.extend(arglist.renderAsInput(input.source))

            for arg in arglist.getArgs2(arglist.parser.WpOption,
                                        arglist.parser.XpreprocessorOption):
                cmd_args.extend(arglist.getValues(arg))

            if arglist.getLastArg(arglist.parser.f_mudflapOption):
                cmd_args.append('-D_MUDFLAP')
                cmd_args.append('-include')
                cmd_args.append('mf-runtime.h')

            if arglist.getLastArg(arglist.parser.f_mudflapthOption):
                cmd_args.append('-D_MUDFLAP')
                cmd_args.append('-D_MUDFLAPTH')
                cmd_args.append('-include')
                cmd_args.append('mf-runtime.h')
        else:
            cmd_args.append('-fpreprocessed')
            # FIXME: There is a spec command to remove
            # -fpredictive-compilation args here. Investigate.

            for input in inputs:
                if isinstance(input.source, Jobs.PipedJob):
                    cmd_args.append('-')
                else:
                    cmd_args.extend(arglist.renderAsInput(input.source))

        # Derived from cc1_options spec.
        if (arglist.getLastArg(arglist.parser.fastOption) or
            arglist.getLastArg(arglist.parser.fastfOption) or
            arglist.getLastArg(arglist.parser.fastcpOption)):
            cmd_args.append('-O3')
            
        if (arglist.getLastArg(arglist.parser.pgOption) and
            arglist.getLastArg(arglist.parser.f_omitFramePointerOption)):
            raise ValueError,"-pg and -fomit-frame-pointer are incompatible"

        self.addCC1Args(cmd_args, arch, arglist)

        if not arglist.getLastArg(arglist.parser.QOption):
            cmd_args.append('-quiet')

        cmd_args.append('-dumpbase')
        cmd_args.append(self.getBaseInputName(inputs, arglist))

        # FIXME: d*
        # FIXME: m*
        # FIXME: a*

        # FIXME: The goal is to use the user provided -o if that is
        # our final output, otherwise to drive from the original input
        # name.
        #
        # This implementation is close, but gcc also does this for -S
        # which is broken, and it would be nice to find a cleaner way
        # which doesn't introduce a dependency on the output argument
        # we are given.
        outputOpt = arglist.getLastArg(arglist.parser.oOption)
        if outputOpt and outputOpt is output:
            cmd_args.append('-auxbase-strip')
            cmd_args.append(arglist.getValue(outputOpt))
        else:
            cmd_args.append('-auxbase')
            cmd_args.append(self.getBaseInputStem(inputs, arglist))

        # FIXME: g*
            
        arglist.addAllArgs(cmd_args, arglist.parser.OOption)
        # FIXME: -Wall is getting some special treatment. Investigate.
        arglist.addAllArgs2(cmd_args, arglist.parser.WOption, arglist.parser.pedanticOption)
        arglist.addLastArg(cmd_args, arglist.parser.wOption)
        arglist.addAllArgs3(cmd_args, arglist.parser.stdOption, arglist.parser.ansiOption, arglist.parser.trigraphsOption)
        if arglist.getLastArg(arglist.parser.vOption):
            cmd_args.append('-version')
        if arglist.getLastArg(arglist.parser.pgOption):
            cmd_args.append('-p')
        arglist.addLastArg(cmd_args, arglist.parser.pOption)
        
        # FIXME: f*

        arglist.addLastArg(cmd_args, arglist.parser.undefOption)
        if arglist.getLastArg(arglist.parser.QnOption):
            cmd_args.append('-fno-ident')
         
        # FIXME: This isn't correct.
        #arglist.addLastArg(cmd_args, arglist.parser._helpOption)
        #arglist.addLastArg(cmd_args, arglist.parser._targetHelpOption)

        if isinstance(output, Jobs.PipedJob):
            cmd_args.extend(['-o', '-'])
        elif output is None:
            cmd_args.extend(['-o', '/dev/null'])
        else:
            cmd_args.extend(arglist.render(output))
  
        # FIXME: Still don't get what is happening here. Investigate.
        arglist.addAllArgs(cmd_args, arglist.parser._paramOption)

        if (arglist.getLastArg(arglist.parser.f_mudflapOption) or
            arglist.getLastArg(arglist.parser.f_mudflapthOption)):
            cmd_args.append('-fno-builtin')
            cmd_args.append('-fno-merge-constants')

        if arglist.getLastArg(arglist.parser.coverageOption):
            cmd_args.append('-fprofile-arcs')
            cmd_args.append('-ftest-coverage')

        jobs.addJob(Jobs.Command(self.toolChain.getProgramPath(cc1Name), 
                                 cmd_args))

        
class Darwin_X86_LinkTool(Tool):
    def __init__(self, toolChain):
        super(Darwin_X86_LinkTool, self).__init__('collect2')
        self.toolChain = toolChain

    def getMacosxVersionTuple(self, arglist):
        arg = arglist.getLastArg(arglist.parser.m_macosxVersionMinOption)
        if arg:
            version = arglist.getValue(arg)
            components = version.split('.')
            try:
                return tuple(map(int, components))
            except:
                raise ValueError,"invalid version number %r" % version
        else:
            major,minor,minorminor = self.toolChain.darwinVersion
            return (10, major-4, minor)

    def addDarwinArch(self, cmd_args, arch, arglist):
        # Derived from darwin_arch spec.
        cmd_args.append('-arch')
        # FIXME: The actual spec uses -m64 for this, but we want to
        # respect arch. Figure out what exactly gcc is doing.
        #if arglist.getLastArg(arglist.parser.m_64Option):
        if arglist.getValue(arch) == 'x86_64':
            cmd_args.append('x86_64')
        else:
            cmd_args.append('i386')

    def addDarwinSubArch(self, cmd_args, arch, arglist):
        # Derived from darwin_subarch spec, not sure what the
        # distinction exists for but at least for this chain it is the same.
        return self.addDarwinArch(cmd_args, arch, arglist)

    def addLinkArgs(self, cmd_args, arch, arglist):
        # Derived from link spec.
        if arglist.getLastArg(arglist.parser.staticOption):
            cmd_args.append('-static')
        else:
            cmd_args.append('-dynamic')
        if arglist.getLastArg(arglist.parser.f_gnuRuntimeOption):
            # FIXME: Replace -lobjc in forward args with
            # -lobjc-gnu. How do we wish to handle such things?
            pass

        if not arglist.getLastArg(arglist.parser.ZdynamiclibOption):
            if arglist.getLastArg(arglist.parser.Zforce_cpusubtype_ALLOption):
                self.addDarwinArch(cmd_args, arch, arglist)
                cmd_args.append('-force_cpusubtype_all')
            else:
                self.addDarwinSubArch(cmd_args, arch, arglist)
        
            if arglist.getLastArg(arglist.parser.ZbundleOption):
                cmd_args.append('-bundle')
            arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zbundle_loaderOption,
                                         '-bundle_loader')
            arglist.addAllArgs(cmd_args, arglist.parser.client_nameOption)
            if arglist.getLastArg(arglist.parser.compatibility_versionOption):
                # FIXME: Where should diagnostics go?
                print >>sys.stderr, "-compatibility_version only allowed with -dynamiclib"
                sys.exit(1)
            if arglist.getLastArg(arglist.parser.current_versionOption):
                print >>sys.stderr, "-current_version only allowed with -dynamiclib"
                sys.exit(1)
            if arglist.getLastArg(arglist.parser.Zforce_flat_namespaceOption):
                cmd_args.append('-force_flat_namespace')
            if arglist.getLastArg(arglist.parser.Zinstall_nameOption):
                print >>sys.stderr, "-install_name only allowed with -dynamiclib"
                sys.exit(1)
            arglist.addLastArg(cmd_args, arglist.parser.keep_private_externsOption)
            arglist.addLastArg(cmd_args, arglist.parser.private_bundleOption)
        else:
            cmd_args.append('-dylib')
            if arglist.getLastArg(arglist.parser.ZbundleOption):
                print >>sys.stderr, "-bundle not allowed with -dynamiclib"
                sys.exit(1)
            if arglist.getLastArg(arglist.parser.Zbundle_loaderOption):
                print >>sys.stderr, "-bundle_loader not allowed with -dynamiclib"
                sys.exit(1)
            if arglist.getLastArg(arglist.parser.client_nameOption):
                print >>sys.stderr, "-client_name not allowed with -dynamiclib"
                sys.exit(1)
            arglist.addAllArgsTranslated(cmd_args, arglist.parser.compatibility_versionOption,
                                         '-dylib_compatibility_version')
            arglist.addAllArgsTranslated(cmd_args, arglist.parser.current_versionOption,
                                         '-dylib_current_version')
 
            if arglist.getLastArg(arglist.parser.Zforce_cpusubtype_ALLOption):
                self.addDarwinArch(cmd_args, arch, arglist)
                # NOTE: We don't add -force_cpusubtype_ALL on this path. Ok.
            else:
                self.addDarwinSubArch(cmd_args, arch, arglist)
        
            if arglist.getLastArg(arglist.parser.Zforce_flat_namespaceOption):
                print >>sys.stderr, "-force_flat_namespace not allowed with -dynamiclib"
                sys.exit(1)

            arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zinstall_nameOption,
                                         '-dylib_install_name')

            if arglist.getLastArg(arglist.parser.keep_private_externsOption):
                print >>sys.stderr, "-keep_private_externs not allowed with -dynamiclib"
                sys.exit(1)
            if arglist.getLastArg(arglist.parser.private_bundleOption):
                print >>sys.stderr, "-private_bundle not allowed with -dynamiclib"
                sys.exit(1)

        if arglist.getLastArg(arglist.parser.Zall_loadOption):
            cmd_args.append('-all_load')

        arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zallowable_clientOption,
                                     '-allowable_client')

        if arglist.getLastArg(arglist.parser.Zbind_at_loadOption):
            cmd_args.append('-bind_at_load')

        if arglist.getLastArg(arglist.parser.Zdead_stripOption):
            cmd_args.append('-dead_strip')
        
        if arglist.getLastArg(arglist.parser.Zno_dead_strip_inits_and_termsOption):
            cmd_args.append('-no_dead_strip_inits_and_terms')
        
        arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zdylib_fileOption,
                                     '-dylib_file')

        if arglist.getLastArg(arglist.parser.ZdynamicOption):
            cmd_args.append('-dynamic')

        arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zexported_symbols_listOption,
                                     '-exported_symbols_list')

        if arglist.getLastArg(arglist.parser.Zflat_namespaceOption):
            cmd_args.append('-flat_namespace')

        arglist.addAllArgs(cmd_args, arglist.parser.headerpad_max_install_namesOption)
        arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zimage_baseOption,
                                     '-image_base')
        arglist.addAllArgsTranslated(cmd_args, arglist.parser.ZinitOption,
                                     '-init')

        if not arglist.getLastArg(arglist.parser.m_macosxVersionMinOption):
            if not arglist.getLastArg(arglist.parser.m_iphoneosVersionMinOption):
                # FIXME: I don't understand what is going on
                # here. This is supposed to come from
                # darwin_ld_minversion, but gcc doesn't seem to be
                # following that; it must be getting over-ridden
                # somewhere.
                cmd_args.append('-macosx_version_min')
                cmd_args.append(self.toolChain.getMacosxVersionMin())
        else:
            # addAll doesn't make sense here but this is what gcc
            # does.
            arglist.addAllArgsTranslated(cmd_args, arglist.parser.m_macosxVersionMinOption,
                                         '-macosx_version_min')

        arglist.addAllArgsTranslated(cmd_args, arglist.parser.m_iphoneosVersionMinOption,
                                     '-iphoneos_version_min')        
        arglist.addLastArg(cmd_args, arglist.parser.nomultidefsOption)
        
        if arglist.getLastArg(arglist.parser.Zmulti_moduleOption):
            cmd_args.append('-multi_module')
        
        if arglist.getLastArg(arglist.parser.Zsingle_moduleOption):
            cmd_args.append('-single_module')

        arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zmultiply_definedOption,
                                     '-multiply_defined')

        arglist.addAllArgsTranslated(cmd_args, arglist.parser.ZmultiplydefinedunusedOption,
                                     '-multiply_defined_unused')

        if arglist.getLastArg(arglist.parser.f_pieOption):
            cmd_args.append('-pie')

        arglist.addLastArg(cmd_args, arglist.parser.prebindOption)
        arglist.addLastArg(cmd_args, arglist.parser.noprebindOption)
        arglist.addLastArg(cmd_args, arglist.parser.nofixprebindingOption)
        arglist.addLastArg(cmd_args, arglist.parser.prebind_all_twolevel_modulesOption)
        arglist.addLastArg(cmd_args, arglist.parser.read_only_relocsOption)
        arglist.addAllArgs(cmd_args, arglist.parser.sectcreateOption)
        arglist.addAllArgs(cmd_args, arglist.parser.sectorderOption)
        arglist.addAllArgs(cmd_args, arglist.parser.seg1addrOption)
        arglist.addAllArgs(cmd_args, arglist.parser.segprotOption)
        arglist.addAllArgsTranslated(cmd_args, arglist.parser.ZsegaddrOption,
                                     '-segaddr')
        arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zsegs_read_only_addrOption,
                                     '-segs_read_only_addr')
        arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zsegs_read_write_addrOption,
                                     '-segs_read_write_addr')
        arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zseg_addr_tableOption,
                                     '-seg_addr_table')
        arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zfn_seg_addr_table_filenameOption,
                                     '-fn_seg_addr_table_filename')
        arglist.addAllArgs(cmd_args, arglist.parser.sub_libraryOption)
        arglist.addAllArgs(cmd_args, arglist.parser.sub_umbrellaOption)
        arglist.addAllArgsTranslated(cmd_args, arglist.parser.isysrootOption,
                                     '-syslibroot')
        arglist.addLastArg(cmd_args, arglist.parser.twolevel_namespaceOption)
        arglist.addLastArg(cmd_args, arglist.parser.twolevel_namespace_hintsOption)
        arglist.addAllArgsTranslated(cmd_args, arglist.parser.ZumbrellaOption,
                                     '-umbrella')
        arglist.addAllArgs(cmd_args, arglist.parser.undefinedOption)
        arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zunexported_symbols_listOption,
                                     '-unexported_symbols_list')
        arglist.addAllArgsTranslated(cmd_args, arglist.parser.Zweak_reference_mismatchesOption,
                                     '-weak_reference_mismatches')
        
        if not arglist.getLastArg(arglist.parser.Zweak_reference_mismatchesOption):
            cmd_args.append('-weak_reference_mismatches')
            cmd_args.append('non-weak')

        arglist.addLastArg(cmd_args, arglist.parser.XOption)
        arglist.addAllArgs(cmd_args, arglist.parser.yOption)
        arglist.addLastArg(cmd_args, arglist.parser.wOption)
        arglist.addAllArgs(cmd_args, arglist.parser.pagezero_sizeOption)
        arglist.addAllArgs(cmd_args, arglist.parser.segs_read_Option)
        arglist.addLastArg(cmd_args, arglist.parser.seglinkeditOption)
        arglist.addLastArg(cmd_args, arglist.parser.noseglinkeditOption)
        arglist.addAllArgs(cmd_args, arglist.parser.sectalignOption)
        arglist.addAllArgs(cmd_args, arglist.parser.sectobjectsymbolsOption)
        arglist.addAllArgs(cmd_args, arglist.parser.segcreateOption)
        arglist.addLastArg(cmd_args, arglist.parser.whyloadOption)
        arglist.addLastArg(cmd_args, arglist.parser.whatsloadedOption)
        arglist.addAllArgs(cmd_args, arglist.parser.dylinker_install_nameOption)
        arglist.addLastArg(cmd_args, arglist.parser.dylinkerOption)
        arglist.addLastArg(cmd_args, arglist.parser.MachOption)

    def constructJob(self, phase, arch, jobs, inputs,
                     output, outputType, args, arglist):
        assert outputType is Types.ImageType

        # The logic here is derived from gcc's behavior; most of which
        # comes from specs (starting with link_command). Consult gcc
        # for more information.

        # FIXME: gcc's spec controls when this is done; certain things
        # like -filelist or -Wl, still trigger a link stage. I don't
        # quite understand how gcc decides to execute the linker,
        # investigate. Also, the spec references -fdump= which seems
        # to have disappeared?
        cmd_args = []

        # Not sure why this particular decomposition exists in gcc.
        self.addLinkArgs(cmd_args, arch, arglist)
        
        # This toolchain never accumlates options in specs, the only
        # place this gets used is to add -ObjC.
        if (arglist.getLastArg(arglist.parser.ObjCOption) or
            arglist.getLastArg(arglist.parser.f_objcOption)):
            cmd_args.append('-ObjC')
        if arglist.getLastArg(arglist.parser.ObjCXXOption):
            cmd_args.append('-ObjC')        

        # FIXME: gcc has %{x} in here. How could this ever happen?
        # Cruft?
        arglist.addLastArg(cmd_args, arglist.parser.dOption)
        arglist.addLastArg(cmd_args, arglist.parser.tOption)
        arglist.addLastArg(cmd_args, arglist.parser.ZOption)
        arglist.addLastArg(cmd_args, arglist.parser.uOption)
        arglist.addLastArg(cmd_args, arglist.parser.AOption)
        arglist.addLastArg(cmd_args, arglist.parser.eOption)
        arglist.addLastArg(cmd_args, arglist.parser.mOption)
        arglist.addLastArg(cmd_args, arglist.parser.rOption)

        cmd_args.extend(arglist.render(output))

        macosxVersion = self.getMacosxVersionTuple(arglist)
        if (not arglist.getLastArg(arglist.parser.AOption) and
            not arglist.getLastArg(arglist.parser.nostdlibOption) and
            not arglist.getLastArg(arglist.parser.nostartfilesOption)):
            # Derived from startfile spec.
            if arglist.getLastArg(arglist.parser.ZdynamiclibOption):
                # Derived from darwin_dylib1 spec.
                if arglist.getLastArg(arglist.parser.m_iphoneosVersionMinOption):
                    cmd_args.append('-ldylib1.o')
                else:
                    if macosxVersion < (10,5):
                        cmd_args.append('-ldylib1.o')
                    else:
                        cmd_args.append('-ldylib1.10.5.o')
            else:
                if arglist.getLastArg(arglist.parser.ZbundleOption):
                    if not arglist.getLastArg(arglist.parser.staticOption):
                        cmd_args.append('-lbundle1.o')
                else:
                    if arglist.getLastArg(arglist.parser.pgOption):
                        if arglist.getLastArg(arglist.parser.staticOption):
                            cmd_args.append('-lgcrt0.o')
                        else:
                            if arglist.getLastArg(arglist.parser.objectOption):
                                cmd_args.append('-lgcrt0.o')
                            else:
                                if arglist.getLastArg(arglist.parser.preloadOption):
                                    cmd_args.append('-lgcrt0.o')
                                else:
                                    cmd_args.append('-lgcrt1.o')

                                    # darwin_crt2 spec is empty.
                                    pass 
                    else:
                        if arglist.getLastArg(arglist.parser.staticOption):
                            cmd_args.append('-lcrt0.o')
                        else:
                            if arglist.getLastArg(arglist.parser.objectOption):
                                cmd_args.append('-lcrt0.o')
                            else:
                                if arglist.getLastArg(arglist.parser.preloadOption):
                                    cmd_args.append('-lcrt0.o')
                                else:
                                    # Derived from darwin_crt1 spec.
                                    if arglist.getLastArg(arglist.parser.m_iphoneosVersionMinOption):
                                        cmd_args.append('-lcrt1.o')
                                    else:
                                        if macosxVersion < (10,5):
                                            cmd_args.append('-lcrt1.o')
                                        else:
                                            cmd_args.append('-lcrt1.10.5.o')

                                    # darwin_crt2 spec is empty.
                                    pass 

            if arglist.getLastArg(arglist.parser.sharedLibgccOption):
                if not arglist.getLastArg(arglist.parser.m_iphoneosVersionMinOption):
                    if macosxVersion < (10,5):
                        # FIXME: gcc does a library search for this
                        # file, this will be be broken currently.
                        cmd_args.append('crt3.o')

        arglist.addAllArgs(cmd_args, arglist.parser.LOption)
        
        if arglist.getLastArg(arglist.parser.f_openmpOption):
            # This is more complicated in gcc...
            cmd_args.append('-lgomp')

        # FIXME: Derive these correctly.
        tcDir = self.toolChain.getToolChainDir()
        if arglist.getValue(arch) == 'x86_64':            
            cmd_args.extend(["-L/usr/lib/gcc/%s/x86_64" % tcDir,
                             "-L/usr/lib/gcc/%s/x86_64" % tcDir])
        cmd_args.extend(["-L/usr/lib/%s" % tcDir,
                         "-L/usr/lib/gcc/%s" % tcDir,
                         "-L/usr/lib/gcc/%s" % tcDir,
                         "-L/usr/lib/gcc/%s/../../../%s" % (tcDir,tcDir),
                         "-L/usr/lib/gcc/%s/../../.." % tcDir])

        for input in inputs:
            cmd_args.extend(arglist.renderAsInput(input.source))

        if (arglist.getLastArg(arglist.parser.f_profileArcsOption) or
            arglist.getLastArg(arglist.parser.f_profileGenerateOption) or
            arglist.getLastArg(arglist.parser.f_createProfileOption) or
            arglist.getLastArg(arglist.parser.coverageOption)):
            cmd_args.append('-lgcov')
        
        if arglist.getLastArg(arglist.parser.f_nestedFunctionsOption):
            cmd_args.append('-allow_stack_execute')

        if (not arglist.getLastArg(arglist.parser.nostdlibOption) and
            not arglist.getLastArg(arglist.parser.nodefaultlibsOption)):
            # link_ssp spec is empty.

            # Derived from libgcc spec.
            if arglist.getLastArg(arglist.parser.staticOption):
                cmd_args.append('-lgcc_static')
            elif arglist.getLastArg(arglist.parser.staticLibgccOption):
                cmd_args.append('-lgcc_eh')
                cmd_args.append('-lgcc')
            elif arglist.getLastArg(arglist.parser.m_iphoneosVersionMinOption):
                # Derived from darwin_iphoneos_libgcc spec.
                cmd_args.append('-lgcc_s.10.5')
                cmd_args.append('-lgcc')
            elif (arglist.getLastArg(arglist.parser.sharedLibgccOption) or
                  arglist.getLastArg(arglist.parser.f_exceptionsOption) or
                  arglist.getLastArg(arglist.parser.f_gnuRuntimeOption)):
                if macosxVersion < (10,5):
                    cmd_args.append('-lgcc_s.10.4')
                else:
                    cmd_args.append('-lgcc_s.10.5')
                cmd_args.append('-lgcc')
            else:
                if macosxVersion < (10,5) and macosxVersion >= (10,3,9):
                    cmd_args.append('-lgcc_s.10.4')
                if macosxVersion >= (10,5):
                    cmd_args.append('-lgcc_s.10.5')
                cmd_args.append('-lgcc')

            # Derived from lib spec.
            if not arglist.getLastArg(arglist.parser.staticOption):
                cmd_args.append('-lSystem')

        if (not arglist.getLastArg(arglist.parser.AOption) and
            not arglist.getLastArg(arglist.parser.nostdlibOption) and
            not arglist.getLastArg(arglist.parser.nostartfilesOption)):
            # endfile_spec is empty.
            pass

        arglist.addAllArgs(cmd_args, arglist.parser.TOption)
        arglist.addAllArgs(cmd_args, arglist.parser.FOption)

        jobs.addJob(Jobs.Command(self.toolChain.getProgramPath('collect2'), 
                                 cmd_args))

        # FIXME: We need to add a dsymutil job here in some particular
        # cases (basically whenever we have a c-family input we are
        # compiling, I think). Find out why this is the condition, and
        # implement. See link_command spec for more details.

class LipoTool(Tool):
    def __init__(self):
        super(LipoTool, self).__init__('lipo')

    def constructJob(self, phase, arch, jobs, inputs,
                     output, outputType, args, arglist):
        assert outputType is Types.ImageType

        cmd_args = ['-create']
        cmd_args.extend(arglist.render(output))
        for input in inputs:
            cmd_args.extend(arglist.renderAsInput(input.source))
        jobs.addJob(Jobs.Command('lipo', cmd_args))
