########################################################################
##
## Caution: there are two separate, independent build systems:
## 'makepanda', and 'ppremake'.  Use one or the other, do not attempt
## to use both.  This file is part of the 'makepanda' system.
##
## This file, makepandacore, contains all the global state and
## global functions for the makepanda system.
##
########################################################################

import sys,os,time,stat,string,re,getopt,fnmatch,threading,signal,shutil,platform,glob,getpass,signal
from distutils import sysconfig

if sys.version_info >= (3, 0):
    import pickle
else:
    import cPickle as pickle

SUFFIX_INC = [".cxx",".c",".h",".I",".yxx",".lxx",".mm",".rc",".r"]
SUFFIX_DLL = [".dll",".dlo",".dle",".dli",".dlm",".mll",".exe",".pyd",".ocx"]
SUFFIX_LIB = [".lib",".ilb"]
STARTTIME = time.time()
MAINTHREAD = threading.currentThread()
OUTPUTDIR = "built"
CUSTOM_OUTPUTDIR = False
THIRDPARTYBASE = None
THIRDPARTYDIR = None
OPTIMIZE = "3"
VERBOSE = False
LINK_ALL_STATIC = False
TARGET = None
TARGET_ARCH = None
HAS_TARGET_ARCH = False
TOOLCHAIN_PREFIX = ""
ANDROID_ABI = None
SYS_LIB_DIRS = []

# Is the current Python a 32-bit or 64-bit build?  There doesn't
# appear to be a universal test for this.
if sys.platform == 'darwin':
    # On OSX, platform.architecture reports '64bit' even if it is
    # currently running in 32-bit mode.  But sys.maxint is a reliable
    # indicator.
    if sys.version_info >= (3, 0):
        host_64 = (sys.maxsize > 0x100000000)
    else:
        host_64 = (sys.maxint > 0x100000000)
else:
    # On Windows (and Linux?) sys.maxint reports 0x7fffffff even on a
    # 64-bit build.  So we stick with platform.architecture in that
    # case.
    host_64 = (platform.architecture()[0] == '64bit')

########################################################################
##
## Maya and Max Version List (with registry keys)
##
########################################################################

MAYAVERSIONINFO = [("MAYA6",   "6.0"),
                   ("MAYA65",  "6.5"),
                   ("MAYA7",   "7.0"),
                   ("MAYA8",   "8.0"),
                   ("MAYA85",  "8.5"),
                   ("MAYA2008","2008"),
                   ("MAYA2009","2009"),
                   ("MAYA2010","2010"),
                   ("MAYA2011","2011"),
                   ("MAYA2012","2012"),
                   ("MAYA2013","2013"),
                   ("MAYA20135","2013.5"),
                   ("MAYA2014","2014"),
]

MAXVERSIONINFO = [("MAX6", "SOFTWARE\\Autodesk\\3DSMAX\\6.0", "installdir", "maxsdk\\cssdk\\include"),
                  ("MAX7", "SOFTWARE\\Autodesk\\3DSMAX\\7.0", "Installdir", "maxsdk\\include\\CS"),
                  ("MAX8", "SOFTWARE\\Autodesk\\3DSMAX\\8.0", "Installdir", "maxsdk\\include\\CS"),
                  ("MAX9", "SOFTWARE\\Autodesk\\3DSMAX\\9.0", "Installdir", "maxsdk\\include\\CS"),
                  ("MAX2009", "SOFTWARE\\Autodesk\\3DSMAX\\11.0\\MAX-1:409", "Installdir", "maxsdk\\include\\CS"),
                  ("MAX2010", "SOFTWARE\\Autodesk\\3DSMAX\\12.0\\MAX-1:409", "Installdir", "maxsdk\\include\\CS"),
                  ("MAX2011", "SOFTWARE\\Autodesk\\3DSMAX\\13.0\\MAX-1:409", "Installdir", "maxsdk\\include\\CS"),
                  ("MAX2012", "SOFTWARE\\Autodesk\\3DSMAX\\14.0\\MAX-1:409", "Installdir", "maxsdk\\include\\CS"),
                  ("MAX2013", "SOFTWARE\\Autodesk\\3DSMAX\\15.0\\MAX-1:409", "Installdir", "maxsdk\\include\\CS"),
                  ("MAX2014", "SOFTWARE\\Autodesk\\3DSMAX\\16.0\\MAX-1:409", "Installdir", "maxsdk\\include\\CS"),
]

MAYAVERSIONS = []
MAXVERSIONS = []
DXVERSIONS = ["DX8","DX9"]

for (ver,key) in MAYAVERSIONINFO:
    MAYAVERSIONS.append(ver)

for (ver,key1,key2,subdir) in MAXVERSIONINFO:
    MAXVERSIONS.append(ver)

########################################################################
##
## Potentially Conflicting Files
##
## The next function can warn about the existence of files that are
## commonly generated by ppremake that may conflict with the build.
##
########################################################################

CONFLICTING_FILES=["dtool/src/dtoolutil/pandaVersion.h",
                   "dtool/src/dtoolutil/checkPandaVersion.h",
                   "dtool/src/dtoolutil/checkPandaVersion.cxx",
                   "dtool/src/prc/prc_parameters.h",
                   "panda/src/speedtree/speedtree_parameters.h",
                   "direct/src/plugin/p3d_plugin_config.h",
                   "direct/src/plugin_activex/P3DActiveX.rc",
                   "direct/src/plugin_npapi/nppanda3d.rc",
                   "direct/src/plugin_standalone/panda3d.rc"]

def WarnConflictingFiles(delete = False):
    for cfile in CONFLICTING_FILES:
        if os.path.exists(cfile):
            print("%sWARNING:%s file may conflict with build: %s%s%s" % (GetColor("red"), GetColor(), GetColor("green"), cfile, GetColor()))
            if delete:
                os.unlink(cfile)
                print("Deleted.")

########################################################################
##
## The exit routine will normally
##
## - print a message
## - save the dependency cache
## - exit
##
## However, if it is invoked inside a thread, it instead:
##
## - prints a message
## - raises the "initiate-exit" exception
##
## If you create a thread, you must be prepared to catch this
## exception, save the dependency cache, and exit.
##
########################################################################

WARNINGS = []
THREADS = {}
HAVE_COLORS = False
SETF = ""
try:
  import curses
  curses.setupterm()
  SETF = curses.tigetstr("setf")
  if (SETF == None):
    SETF = curses.tigetstr("setaf")
  assert SETF != None
  HAVE_COLORS = sys.stdout.isatty()
except: pass

def DisableColors():
    global HAVE_COLORS
    HAVE_COLORS = False

def GetColor(color = None):
    if not HAVE_COLORS:
        return ""
    if color != None:
        color = color.lower()

    if (color == "blue"):
        token = curses.tparm(SETF, 1)
    elif (color == "green"):
        token = curses.tparm(SETF, 2)
    elif (color == "cyan"):
        token = curses.tparm(SETF, 3)
    elif (color == "red"):
        token = curses.tparm(SETF, 4)
    elif (color == "magenta"):
        token = curses.tparm(SETF, 5)
    elif (color == "yellow"):
        token = curses.tparm(SETF, 6)
    else:
        token = curses.tparm(curses.tigetstr("sgr0"))

    if sys.version_info >= (3, 0):
        return token.decode('ascii')
    else:
        return token

def ColorText(color, text, reset=True):
    if reset is True:
        return ''.join((GetColor(color), text, GetColor()))
    else:
        return ''.join((GetColor(color), text))

def PrettyTime(t):
    t = int(t)
    hours = t // 3600
    t -= hours * 3600
    minutes = t // 60
    t -= minutes * 60
    seconds = t
    if hours:
        return "%d hours %d min" % (hours, minutes)
    if minutes:
        return "%d min %d sec" % (minutes, seconds)
    return "%d sec" % (seconds)

def ProgressOutput(progress, msg, target = None):
    prefix = ""
    if (threading.currentThread() is MAINTHREAD):
        if progress is None:
            prefix = ""
        elif (progress >= 100.0):
            prefix = "%s[%s%d%%%s] " % (GetColor("yellow"), GetColor("cyan"), progress, GetColor("yellow"))
        elif (progress < 10.0):
            prefix = "%s[%s  %d%%%s] " % (GetColor("yellow"), GetColor("cyan"), progress, GetColor("yellow"))
        else:
            prefix = "%s[%s %d%%%s] " % (GetColor("yellow"), GetColor("cyan"), progress, GetColor("yellow"))
    else:
        global THREADS
        ident = threading.currentThread().ident
        if (ident not in THREADS):
            THREADS[ident] = len(THREADS) + 1
        prefix = "%s[%sT%d%s] " % (GetColor("yellow"), GetColor("cyan"), THREADS[ident], GetColor("yellow"))

    if target is not None:
        suffix = ' ' + ColorText("green", target)
    else:
        suffix = GetColor()

    print(''.join((prefix, msg, suffix)))

def exit(msg = ""):
    sys.stdout.flush()
    sys.stderr.flush()
    if (threading.currentThread() == MAINTHREAD):
        SaveDependencyCache()
        print("Elapsed Time: " + PrettyTime(time.time() - STARTTIME))
        print(msg)
        print(ColorText("red", "Build terminated."))
        sys.stdout.flush()
        sys.stderr.flush()
        ##Don't quit the interperter if I'm running this file directly (debugging)
        if __name__ != '__main__':
            os._exit(1)
    else:
        print(msg)
        raise "initiate-exit"

########################################################################
##
## SetTarget, GetTarget, GetHost
##
## These functions control cross-compilation.
##
########################################################################

def GetHost():
    """Returns the host platform, ie. the one we're compiling on."""
    if sys.platform == 'win32' or sys.platform == 'cygwin':
        # sys.platform is win32 on 64-bits Windows as well.
        return 'windows'
    elif sys.platform == 'darwin':
        return 'darwin'
    elif sys.platform.startswith('linux'):
        return 'linux'
    elif sys.platform.startswith('freebsd'):
        return 'freebsd'
    else:
        exit('Unrecognized sys.platform: %s' % (sys.platform))

def GetHostArch():
    """Returns the architecture we're compiling on.
    Its value is also platform-dependent, as different platforms
    have different architecture naming."""

    target = GetTarget()
    if target == 'windows':
        return 'x64' if host_64 else 'x86'
    else: #TODO
        return platform.machine()

def SetTarget(target, arch=None):
    """Sets the target platform; the one we're compiling for.  Also
    sets the target architecture (None for default, if any).  Should
    be called *before* any calls are made to GetOutputDir, GetCC, etc."""
    global TARGET, TARGET_ARCH, HAS_TARGET_ARCH
    global TOOLCHAIN_PREFIX

    host = GetHost()
    host_arch = GetHostArch()
    if target is None:
        target = host
    else:
        target = target.lower()

    if arch is not None:
        HAS_TARGET_ARCH = True

    TOOLCHAIN_PREFIX = ''

    if target == 'windows':
        if arch == 'i386':
             arch = 'x86'
        elif arch == 'amd64':
            arch = 'x64'

        if arch is not None and arch != 'x86' and arch != 'x64':
            exit("Windows architecture must be x86 or x64")

    elif target == 'darwin':
        if arch == 'amd64':
            arch = 'x86_64'

        if arch is not None:
            choices = ('i386', 'x86_64', 'ppc', 'ppc64')
            if arch not in choices:
                exit('Mac OS X architecture must be one of %s' % (', '.join(choices)))

    elif target == 'android':
        if arch is None:
            arch = 'arm'

        # Determine the prefix for our gcc tools, eg. arm-linux-androideabi-gcc
        global ANDROID_ABI
        if arch == 'armv7a':
            ANDROID_ABI = 'armeabi-v7a'
            TOOLCHAIN_PREFIX = 'arm-linux-androideabi-'
        elif arch == 'arm':
            ANDROID_ABI = 'armeabi'
            TOOLCHAIN_PREFIX = 'arm-linux-androideabi-'
        elif arch == 'x86':
            ANDROID_ABI = 'x86'
            TOOLCHAIN_PREFIX = 'i686-linux-android-'
        elif arch == 'mips':
            ANDROID_ABI = 'mips'
            TOOLCHAIN_PREFIX = 'mipsel-linux-android-'
        else:
            exit('Android architecture must be arm, armv7a, x86 or mips')

    elif target == 'linux':
        if arch is not None:
            TOOLCHAIN_PREFIX = '%s-linux-gnu-' % arch

        elif host != 'linux':
            exit('Should specify an architecture when building for Linux')

    elif target == host:
        if arch is None or arch == host_arch:
            # Not a cross build.
            pass
        else:
            exit('Cannot cross-compile for %s-%s from %s-%s' % (target, arch, host, host_arch))

    else:
        exit('Cannot cross-compile for %s from %s' % (target, host))

    if arch is None:
        arch = host_arch

    TARGET = target
    TARGET_ARCH = arch

def GetTarget():
    """Returns the platform we're compiling for.  Defaults to GetHost()."""
    global TARGET
    if TARGET is None:
        TARGET = GetHost()
    return TARGET

def HasTargetArch():
    """Returns True if the user specified an architecture to compile for."""
    return HAS_TARGET_ARCH

def GetTargetArch():
    """Returns the architecture we're compiling for.  Defaults to GetHostArch().
    Its value is also dependent on that of GetTarget(), as different platforms
    use a different architecture naming."""
    global TARGET_ARCH
    if TARGET_ARCH is None:
        TARGET_ARCH = GetHostArch()
    return TARGET_ARCH

def CrossCompiling():
    """Returns True if we're cross-compiling."""
    return GetTarget() != GetHost()

def GetCC():
    return os.environ.get('CC', TOOLCHAIN_PREFIX + 'gcc')

def GetCXX():
    return os.environ.get('CXX', TOOLCHAIN_PREFIX + 'g++')

def GetStrip():
    # Hack
    if TARGET == 'android':
        return TOOLCHAIN_PREFIX + 'strip'
    else:
        return 'strip'

def GetAR():
    # Hack
    if TARGET == 'android':
        return TOOLCHAIN_PREFIX + 'ar'
    else:
        return 'ar'

def GetRanlib():
    # Hack
    if TARGET == 'android':
        return TOOLCHAIN_PREFIX + 'ranlib'
    else:
        return 'ranlib'

BISON = None
def GetBison():
    global BISON
    if BISON is not None:
        return BISON

    win_util = os.path.join(GetThirdpartyBase(), 'win-util')
    if GetHost() == 'windows' and os.path.isdir(win_util):
        BISON = os.path.join(win_util, 'bison.exe')
    elif LocateBinary('bison'):
        BISON = 'bison'
    else:
        # We don't strictly need it, so don't give an error
        return None

FLEX = None
def GetFlex():
    global FLEX
    if FLEX is not None:
        return FLEX

    win_util = os.path.join(GetThirdpartyBase(), 'win-util')
    if GetHost() == 'windows' and os.path.isdir(win_util):
        FLEX = GetThirdpartyBase() + "/win-util/flex.exe"
    elif LocateBinary('flex'):
        FLEX = 'flex'
    else:
        # We don't strictly need it, so don't give an error
        return None

########################################################################
##
## LocateBinary
##
## This function searches the system PATH for the binary. Returns its
## full path when it is found, or None when it was not found.
##
########################################################################

def LocateBinary(binary):
    if os.path.isfile(binary):
        return binary

    if "PATH" not in os.environ or os.environ["PATH"] == "":
        p = os.defpath
    else:
        p = os.environ["PATH"]

    pathList = p.split(os.pathsep)
    
    if GetHost() == 'windows':
        if not binary.endswith('.exe'):
            # Append .exe if necessary
            binary += '.exe'

        # On Windows the current directory is always implicitly
        # searched before anything else on PATH.
        pathList = ['.'] + pathList

    for path in pathList:
        binpath = os.path.join(os.path.expanduser(path), binary)
        if os.access(binpath, os.X_OK):
            return os.path.abspath(os.path.realpath(binpath))
    return None

########################################################################
##
## Run a command.
##
########################################################################

def oscmd(cmd, ignoreError = False):
    if VERBOSE:
        print(GetColor("blue") + cmd.split(" ", 1)[0] + " " + GetColor("magenta") + cmd.split(" ", 1)[1] + GetColor())
    sys.stdout.flush()

    if sys.platform == "win32":
        exe = cmd.split()[0]
        exe_path = LocateBinary(exe)
        if exe_path is None:
            exit("Cannot find "+exe+" on search path")
        res = os.spawnl(os.P_WAIT, exe_path, cmd)
    else:
        res = os.system(cmd)
        sig = res & 0x7F
        if (GetVerbose() and res != 0):
            print(ColorText("red", "Process exited with exit status %d and signal code %d" % ((res & 0xFF00) >> 8, sig)))
        if (sig == signal.SIGINT):
            exit("keyboard interrupt")

        # Don't ask me where the 35584 or 34304 come from...
        if (sig == signal.SIGSEGV or res == 35584 or res == 34304):
            if (LocateBinary("gdb") and GetVerbose() and GetHost() != "windows"):
                print(ColorText("red", "Received SIGSEGV, retrieving traceback..."))
                os.system("gdb -batch -ex 'handle SIG33 pass nostop noprint' -ex 'set pagination 0' -ex 'run' -ex 'bt full' -ex 'info registers' -ex 'thread apply all backtrace' -ex 'quit' --args %s < /dev/null" % cmd)
            else:
                print(ColorText("red", "Received SIGSEGV"))
            exit("")

    if res != 0 and not ignoreError:
        if "interrogate" in cmd.split(" ", 1)[0] and GetVerbose():
            print(ColorText("red", "Interrogate failed, retrieving debug output..."))
            if sys.platform == "win32":
                os.spawnl(os.P_WAIT, exe, cmd.split(" ", 1)[0] + " -v " + cmd.split(" ", 1)[1])
            else:
                os.system(cmd.split(" ", 1)[0] + " -v " + cmd.split(" ", 1)[1])
        exit("The following command returned a non-zero value: " + str(cmd))

    return res

########################################################################
##
## GetDirectoryContents
##
## At times, makepanda will use a function like "os.listdir" to process
## all the files in a directory.  Unfortunately, that means that any
## accidental addition of a file to a directory could cause makepanda
## to misbehave without warning.
##
## To alleviate this weakness, we created GetDirectoryContents.  This
## uses "os.listdir" to fetch the directory contents, but then it
## compares the results to the appropriate CVS/Entries to see if
## they match.  If not, it prints a big warning message.
##
########################################################################

def GetDirectoryContents(dir, filters="*", skip=[]):
    if (type(filters)==str):
        filters = [filters]
    actual = {}
    files = os.listdir(dir)
    for filter in filters:
        for file in fnmatch.filter(files, filter):
            if (skip.count(file)==0) and (os.path.isfile(dir + "/" + file)):
                actual[file] = 1
    if (os.path.isfile(dir + "/CVS/Entries")):
        cvs = {}
        srchandle = open(dir + "/CVS/Entries", "r")
        files = []
        for line in srchandle:
            if (line[0]=="/"):
                s = line.split("/",2)
                if (len(s)==3):
                    files.append(s[1])
        srchandle.close()
        for filter in filters:
            for file in fnmatch.filter(files, filter):
                if (skip.count(file)==0):
                    cvs[file] = 1

        #XXX this happens all the time, do we really need to warn about this?
        #for file in actual.keys():
        #    if (file not in cvs and VERBOSE):
        #        msg = "%sWARNING: %s is in %s, but not in CVS%s" % (GetColor("red"), ColorText("green", file), ColorText("green", dir), GetColor())
        #        print msg
        #        WARNINGS.append(msg)

        for file in cvs.keys():
            if (file not in actual and VERBOSE):
                msg = "%sWARNING: %s is not in %s, but is in CVS%s" % (GetColor("red"), ColorText("green", file), ColorText("green", dir), GetColor())
                print(msg)
                WARNINGS.append(msg)

    results = list(actual.keys())
    results.sort()
    return results

def GetDirectorySize(dir):
    if not os.path.isdir(dir):
        return 0
    size = 0
    for (path, dirs, files) in os.walk(dir):
        for file in files:
            try:
                size += os.path.getsize(os.path.join(path, file))
            except: pass
    return size

########################################################################
##
## The Timestamp Cache
##
## The make utility is constantly fetching the timestamps of files.
## This can represent the bulk of the file accesses during the make
## process.  The timestamp cache eliminates redundant checks.
##
########################################################################

TIMESTAMPCACHE = {}

def GetTimestamp(path):
    if path in TIMESTAMPCACHE:
        return TIMESTAMPCACHE[path]
    try: date = os.path.getmtime(path)
    except: date = 0
    TIMESTAMPCACHE[path] = date
    return date

def ClearTimestamp(path):
    del TIMESTAMPCACHE[path]

########################################################################
##
## The Dependency cache.
##
## Makepanda's strategy for file dependencies is different from most
## make-utilities.  Whenever a file is built, makepanda records
## that the file was built, and it records what the input files were,
## and what their dates were.  Whenever a file is about to be built,
## panda compares the current list of input files and their dates,
## to the previous list of input files and their dates.  If they match,
## there is no need to build the file.
##
########################################################################

BUILTFROMCACHE = {}

def JustBuilt(files, others):
    dates = {}
    for file in files:
        del TIMESTAMPCACHE[file]
        dates[file] = GetTimestamp(file)
    for file in others:
        dates[file] = GetTimestamp(file)

    key = tuple(files)
    BUILTFROMCACHE[key] = dates

def NeedsBuild(files, others):
    dates = {}
    for file in files:
        dates[file] = GetTimestamp(file)
        if not os.path.exists(file):
            return True
    for file in others:
        dates[file] = GetTimestamp(file)

    key = tuple(files)
    if key in BUILTFROMCACHE:
        cached = BUILTFROMCACHE[key]
        if cached == dates:
            return False
        if VERBOSE and frozenset(cached) != frozenset(dates):
            print("%sWARNING:%s file dependencies changed: %s%s%s" % (GetColor("red"), GetColor(), GetColor("green"), files, GetColor()))

    return True

########################################################################
##
## The CXX include cache:
##
## The following routine scans a CXX file and returns a list of
## the include-directives inside that file.  It's not recursive:
## it just returns the includes that are textually inside the
## file.  If you need recursive dependencies, you need the higher-level
## routine CxxCalcDependencies, defined elsewhere.
##
## Since scanning a CXX file is slow, we cache the result.  It records
## the date of the source file and the list of includes that it
## contains.  It assumes that if the file date hasn't changed, that
## the list of include-statements inside the file has not changed
## either.  Once again, this particular routine does not return
## recursive dependencies --- it only returns an explicit list of
## include statements that are textually inside the file.  That
## is what the cache stores, as well.
##
########################################################################

CXXINCLUDECACHE = {}

global CxxIncludeRegex
CxxIncludeRegex = re.compile('^[ \t]*[#][ \t]*include[ \t]+"([^"]+)"[ \t\r\n]*$')

def CxxGetIncludes(path):
    date = GetTimestamp(path)
    if (path in CXXINCLUDECACHE):
        cached = CXXINCLUDECACHE[path]
        if (cached[0]==date): return cached[1]
    try: sfile = open(path, 'r')
    except:
        exit("Cannot open source file \""+path+"\" for reading.")
    include = []
    for line in sfile:
        match = CxxIncludeRegex.match(line,0)
        if (match):
            incname = match.group(1)
            include.append(incname)
    sfile.close()
    CXXINCLUDECACHE[path] = [date, include]
    return include

########################################################################
##
## SaveDependencyCache / LoadDependencyCache
##
## This actually saves both the dependency and cxx-include caches.
##
########################################################################

DCACHE_VERSION = 2
DCACHE_BACKED_UP = False

def SaveDependencyCache():
    global DCACHE_BACKED_UP
    if not DCACHE_BACKED_UP:
        try:
            if (os.path.exists(os.path.join(OUTPUTDIR, "tmp", "makepanda-dcache"))):
                os.rename(os.path.join(OUTPUTDIR, "tmp", "makepanda-dcache"),
                          os.path.join(OUTPUTDIR, "tmp", "makepanda-dcache-backup"))
        except: pass
        DCACHE_BACKED_UP = True

    try:
        icache = open(os.path.join(OUTPUTDIR, "tmp", "makepanda-dcache"),'wb')
    except:
        icache = None

    if icache is not None:
        print("Storing dependency cache.")
        pickle.dump(DCACHE_VERSION, icache, 0)
        pickle.dump(CXXINCLUDECACHE, icache, 2)
        pickle.dump(BUILTFROMCACHE, icache, 2)
        icache.close()

def LoadDependencyCache():
    global CXXINCLUDECACHE
    global BUILTFROMCACHE

    try:
        icache = open(os.path.join(OUTPUTDIR, "tmp", "makepanda-dcache"), 'rb')
    except:
        icache = None

    if icache is not None:
        ver = pickle.load(icache)
        if ver == DCACHE_VERSION:
            CXXINCLUDECACHE = pickle.load(icache)
            BUILTFROMCACHE = pickle.load(icache)
            icache.close()
        else:
            print("Cannot load dependency cache, version is too old!")

########################################################################
##
## CxxFindSource: given a source file name and a directory list,
## searches the directory list for the given source file.  Returns
## the full pathname of the located file.
##
## CxxFindHeader: given a source file, an include directive, and a
## directory list, searches the directory list for the given header
## file.  Returns the full pathname of the located file.
##
## Of course, CxxFindSource and CxxFindHeader cannot find a source
## file that has not been created yet.  This can cause dependency
## problems.  So the function CreateStubHeader can be used to create
## a file that CxxFindSource or CxxFindHeader can subsequently find.
##
########################################################################

def CxxFindSource(name, ipath):
    for dir in ipath:
        if (dir == "."): full = name
        else: full = dir + "/" + name
        if GetTimestamp(full) > 0: return full
    exit("Could not find source file: "+name)

def CxxFindHeader(srcfile, incfile, ipath):
    if (incfile.startswith(".")):
        last = srcfile.rfind("/")
        if (last < 0): exit("CxxFindHeader cannot handle this case #1")
        srcdir = srcfile[:last+1]
        while (incfile[:1]=="."):
            if (incfile[:2]=="./"):
                incfile = incfile[2:]
            elif (incfile[:3]=="../"):
                incfile = incfile[3:]
                last = srcdir[:-1].rfind("/")
                if (last < 0): exit("CxxFindHeader cannot handle this case #2")
                srcdir = srcdir[:last+1]
            else: exit("CxxFindHeader cannot handle this case #3")
        full = srcdir + incfile
        if GetTimestamp(full) > 0: return full
        return 0
    else:
        for dir in ipath:
            full = dir + "/" + incfile
            if GetTimestamp(full) > 0: return full
        return 0

########################################################################
##
## CxxCalcDependencies(srcfile, ipath, ignore)
##
## Calculate the dependencies of a source file given a
## particular include-path.  Any file in the list of files to
## ignore is not considered.
##
########################################################################

global CxxIgnoreHeader
global CxxDependencyCache
CxxIgnoreHeader = {}
CxxDependencyCache = {}

def CxxCalcDependencies(srcfile, ipath, ignore):
    if (srcfile in CxxDependencyCache):
        return CxxDependencyCache[srcfile]
    if (ignore.count(srcfile)): return []
    dep = {}
    dep[srcfile] = 1
    includes = CxxGetIncludes(srcfile)
    for include in includes:
        header = CxxFindHeader(srcfile, include, ipath)
        if (header!=0):
            if (ignore.count(header)==0):
                hdeps = CxxCalcDependencies(header, ipath, [srcfile]+ignore)
                for x in hdeps: dep[x] = 1
    result = list(dep.keys())
    CxxDependencyCache[srcfile] = result
    return result

########################################################################
##
## Registry Key Handling
##
## Of course, these routines will fail if you call them on a
## non win32 platform.  If you use them on a win64 platform, they
## will look in the win32 private hive first, then look in the
## win64 hive.
##
########################################################################

if sys.platform == "win32":
    # Note: not supported on cygwin. 
    if sys.version_info >= (3, 0):
        import winreg
    else:
        import _winreg as winreg

def TryRegistryKey(path):
    try:
        key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path, 0, winreg.KEY_READ)
        return key
    except: pass
    try:
        key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path, 0, winreg.KEY_READ | 256)
        return key
    except: pass
    return 0

def ListRegistryKeys(path):
    result=[]
    index=0
    key = TryRegistryKey(path)
    if (key != 0):
        try:
            while (1):
                result.append(winreg.EnumKey(key, index))
                index = index + 1
        except: pass
        winreg.CloseKey(key)
    return result

def ListRegistryValues(path):
    result = []
    index = 0
    key = TryRegistryKey(path)
    if (key != 0):
        try:
            while (1):
                result.append(winreg.EnumValue(key, index)[0])
                index = index + 1
        except: pass
        winreg.CloseKey(key)
    return result

def GetRegistryKey(path, subkey, override64=True):
    if (host_64 and override64):
        path = path.replace("SOFTWARE\\", "SOFTWARE\\Wow6432Node\\")
    k1=0
    key = TryRegistryKey(path)
    if (key != 0):
        try:
            k1, k2 = winreg.QueryValueEx(key, subkey)
        except: pass
        winreg.CloseKey(key)
    return k1

def GetProgramFiles():
    if ("PROGRAMFILES" in os.environ):
        return os.environ["PROGRAMFILES"]
    elif (os.path.isdir("C:\\Program Files")):
        return "C:\\Program Files"
    elif (os.path.isdir("D:\\Program Files")):
        return "D:\\Program Files"
    elif (os.path.isdir("E:\\Program Files")):
        return "E:\\Program Files"
    return 0

########################################################################
##
## Parsing Compiler Option Lists
##
########################################################################

def GetListOption(opts, prefix):
    res=[]
    for x in opts:
        if (x.startswith(prefix)):
            res.append(x[len(prefix):])
    return res

def GetValueOption(opts, prefix):
    for x in opts:
        if (x.startswith(prefix)):
            return x[len(prefix):]
    return 0

def GetOptimizeOption(opts):
    val = GetValueOption(opts, "OPT:")
    if (val == 0):
        return OPTIMIZE
    return val

########################################################################
##
## General File Manipulation
##
########################################################################

def MakeDirectory(path):
    if os.path.isdir(path): return 0
    os.mkdir(path)

def ReadFile(wfile):
    try:
        srchandle = open(wfile, "r")
        data = srchandle.read()
        srchandle.close()
        return data
    except:
        ex = sys.exc_info()[1]
        exit("Cannot read %s: %s" % (wfile, ex))

def ReadBinaryFile(wfile):
    try:
        srchandle = open(wfile, "rb")
        data = srchandle.read()
        srchandle.close()
        return data
    except:
        ex = sys.exc_info()[1]
        exit("Cannot read %s: %s" % (wfile, ex))

def WriteFile(wfile, data):
    try:
        dsthandle = open(wfile, "w")
        dsthandle.write(data)
        dsthandle.close()
    except:
        ex = sys.exc_info()[1]
        exit("Cannot write to %s: %s" % (wfile, ex))

def WriteBinaryFile(wfile, data):
    try:
        dsthandle = open(wfile, "wb")
        dsthandle.write(data)
        dsthandle.close()
    except:
        ex = sys.exc_info()[1]
        exit("Cannot write to %s: %s" % (wfile, ex))

def ConditionalWriteFile(dest, desiredcontents):
    try:
        rfile = open(dest, 'r')
        contents = rfile.read(-1)
        rfile.close()
    except:
        contents = 0
    if contents != desiredcontents:
        if VERBOSE:
            print("Writing %s" % (dest))
        sys.stdout.flush()
        WriteFile(dest, desiredcontents)

def DeleteCVS(dir):
    if dir == "": dir = "."
    for entry in os.listdir(dir):
        subdir = os.path.join(dir, entry)
        if (os.path.isdir(subdir)):
            if (entry == "CVS" or entry == "CVSROOT"):
                shutil.rmtree(subdir)
            else:
                DeleteCVS(subdir)
        elif (os.path.isfile(subdir) and (entry == ".cvsignore" or entry.startswith(".#"))):
            os.remove(subdir)

def DeleteBuildFiles(dir):
    if dir == "": dir = "."
    for entry in os.listdir(dir):
        subdir = os.path.join(dir, entry)
        if (os.path.isfile(subdir) and os.path.splitext(subdir)[-1] in [".pp", ".moved"]):
            os.remove(subdir)
        elif (os.path.isdir(subdir)):
            if (os.path.basename(subdir)[:3] == "Opt" and os.path.basename(subdir)[4] == "-"):
                shutil.rmtree(subdir)
            else:
                DeleteBuildFiles(subdir)

def DeleteEmptyDirs(dir):
    if dir == "": dir = "."
    entries = os.listdir(dir)
    if not entries:
        os.rmdir(dir)
        return
    for entry in entries:
        subdir = os.path.join(dir, entry)
        if (os.path.isdir(subdir)):
            if (not os.listdir(subdir)):
                os.rmdir(subdir)
            else:
                DeleteEmptyDirs(subdir)

def CreateFile(file):
    if (os.path.exists(file)==0):
        WriteFile(file, "")

########################################################################
#
# Create the panda build tree.
#
########################################################################

def MakeBuildTree():
    MakeDirectory(OUTPUTDIR)
    MakeDirectory(OUTPUTDIR + "/bin")
    MakeDirectory(OUTPUTDIR + "/lib")
    MakeDirectory(OUTPUTDIR + "/tmp")
    MakeDirectory(OUTPUTDIR + "/etc")
    MakeDirectory(OUTPUTDIR + "/plugins")
    MakeDirectory(OUTPUTDIR + "/include")
    MakeDirectory(OUTPUTDIR + "/models")
    MakeDirectory(OUTPUTDIR + "/models/audio")
    MakeDirectory(OUTPUTDIR + "/models/audio/sfx")
    MakeDirectory(OUTPUTDIR + "/models/icons")
    MakeDirectory(OUTPUTDIR + "/models/maps")
    MakeDirectory(OUTPUTDIR + "/models/misc")
    MakeDirectory(OUTPUTDIR + "/models/gui")
    MakeDirectory(OUTPUTDIR + "/pandac")
    MakeDirectory(OUTPUTDIR + "/pandac/input")

    if GetTarget() == 'android':
        MakeDirectory(OUTPUTDIR + "/libs")
        MakeDirectory(OUTPUTDIR + "/libs/" + ANDROID_ABI)
        MakeDirectory(OUTPUTDIR + "/src")
        MakeDirectory(OUTPUTDIR + "/src/org")
        MakeDirectory(OUTPUTDIR + "/src/org/panda3d")
        MakeDirectory(OUTPUTDIR + "/src/org/panda3d/android")

########################################################################
#
# Make sure that you are in the root of the panda tree.
#
########################################################################

def CheckPandaSourceTree():
    dir = os.getcwd()
    if ((os.path.exists(os.path.join(dir, "makepanda/makepanda.py"))==0) or
        (os.path.exists(os.path.join(dir, "dtool", "src", "dtoolbase", "dtoolbase.h"))==0) or
        (os.path.exists(os.path.join(dir, "panda", "src", "pandabase", "pandabase.h"))==0)):
        exit("Current directory is not the root of the panda tree.")

########################################################################
##
## Thirdparty libraries paths
##
########################################################################

def GetThirdpartyBase():
    """Returns the location of the 'thirdparty' directory.
    Normally, this is simply the thirdparty directory relative
    to the root of the source root, but if a MAKEPANDA_THIRDPARTY
    environment variable was set, it is used as the location of the
    thirdparty directory.  This is useful when wanting to use a single
    system-wide thirdparty directory, for instance on a build machine."""
    global THIRDPARTYBASE
    if (THIRDPARTYBASE != None):
        return THIRDPARTYBASE

    THIRDPARTYBASE = "thirdparty"
    if "MAKEPANDA_THIRDPARTY" in os.environ:
        THIRDPARTYBASE = os.environ["MAKEPANDA_THIRDPARTY"]
    
    return THIRDPARTYBASE

def GetThirdpartyDir():
    """Returns the thirdparty directory for the target platform,
    ie. thirdparty/win-libs-vc10/.  May return None in the future."""
    global THIRDPARTYDIR
    if THIRDPARTYDIR != None:
        return THIRDPARTYDIR

    base = GetThirdpartyBase()
    target = GetTarget()
    target_arch = GetTargetArch()

    if (target == 'windows'):
        if target_arch == 'x64':
            THIRDPARTYDIR = base + "/win-libs-vc10-x64/"
            if not os.path.isdir(THIRDPARTYDIR):
                THIRDPARTYDIR = base + "/win-libs-vc10/"
        else:
            THIRDPARTYDIR = base + "/win-libs-vc10/"

    elif (target == 'darwin'):
        # OSX thirdparty binaries are universal, where possible.
        THIRDPARTYDIR = base + "/darwin-libs-a/"

    elif (target == 'linux'):
        if (target_arch.startswith("arm")):
            THIRDPARTYDIR = base + "/linux-libs-arm/"
        elif (target_arch in ("x86_64", "amd64")):
            THIRDPARTYDIR = base + "/linux-libs-x64/"
        else:
            THIRDPARTYDIR = base + "/linux-libs-a/"

    elif (target == 'freebsd'):
        if (target_arch.startswith("arm")):
            THIRDPARTYDIR = base + "/freebsd-libs-arm/"
        elif (target_arch in ("x86_64", "amd64")):
            THIRDPARTYDIR = base + "/freebsd-libs-x64/"
        else:
            THIRDPARTYDIR = base + "/freebsd-libs-a/"

    elif (target == 'android'):
        THIRDPARTYDIR = GetThirdpartyBase()+"/android-libs-%s/" % (GetTargetArch())

    else:
        print("%s Unsupported platform: %s" % (ColorText("red", "WARNING:"), target))
        return

    if (GetVerbose()):
        print("Using thirdparty directory: %s" % THIRDPARTYDIR)

    return THIRDPARTYDIR

########################################################################
##
## Gets or sets the output directory, by default "built".
## Gets or sets the optimize level.
## Gets or sets the verbose flag.
##
########################################################################

def GetOutputDir():
  return OUTPUTDIR

def IsCustomOutputDir():
  return CUSTOM_OUTPUTDIR

def SetOutputDir(outputdir):
  global OUTPUTDIR, CUSTOM_OUTPUTDIR
  OUTPUTDIR=outputdir
  CUSTOM_OUTPUTDIR=True

def GetOptimize():
  return int(OPTIMIZE)

def SetOptimize(optimize):
  global OPTIMIZE
  OPTIMIZE=optimize

def GetVerbose():
  return VERBOSE

def SetVerbose(verbose):
  global VERBOSE
  VERBOSE=verbose

def GetLinkAllStatic():
  return LINK_ALL_STATIC

def SetLinkAllStatic(val = True):
  global LINK_ALL_STATIC
  LINK_ALL_STATIC = val

def UnsetLinkAllStatic():
  global LINK_ALL_STATIC
  LINK_ALL_STATIC = False

########################################################################
##
## Package Selection
##
## This facility enables makepanda to keep a list of packages selected
## by the user for inclusion or omission.
##
########################################################################

PKG_LIST_ALL=[]
PKG_LIST_OMIT={}

def PkgListSet(pkgs):
    global PKG_LIST_ALL
    global PKG_LIST_OMIT
    PKG_LIST_ALL=pkgs
    PKG_LIST_OMIT={}
    PkgEnableAll()

def PkgListGet():
    return PKG_LIST_ALL

def PkgEnableAll():
    for x in PKG_LIST_ALL:
        PKG_LIST_OMIT[x] = 0

def PkgDisableAll():
    for x in PKG_LIST_ALL:
        PKG_LIST_OMIT[x] = 1

def PkgEnable(pkg):
    PKG_LIST_OMIT[pkg] = 0

def PkgDisable(pkg):
    PKG_LIST_OMIT[pkg] = 1

def PkgSkip(pkg):
    return PKG_LIST_OMIT[pkg]

def PkgSelected(pkglist, pkg):
    if (pkglist.count(pkg)==0): return 0
    if (PKG_LIST_OMIT[pkg]): return 0
    return 1

########################################################################
##
## DTOOL/PRC Option value override
##
## This facility enables makepanda to keep a list of parameters
## overriden by the command line.
##
########################################################################

OVERRIDES_LIST={}

def AddOverride(spec):
    if (spec.find("=")==-1):return
    pair = spec.split("=",1)
    OVERRIDES_LIST[pair[0]] = pair[1]

def OverrideValue(parameter, value):
    if parameter in OVERRIDES_LIST:
        print("Overriding value of key \"" + parameter + "\" with value \"" + OVERRIDES_LIST[parameter] + "\"")
        return OVERRIDES_LIST[parameter]
    else:
        return value

########################################################################
##
## These functions are for libraries which use pkg-config.
##
########################################################################

def PkgConfigHavePkg(pkgname, tool = "pkg-config"):
    """Returns a bool whether the pkg-config package is installed."""

    if (sys.platform == "win32" or not LocateBinary(tool)):
        return False
    if (tool == "pkg-config"):
        handle = os.popen(LocateBinary("pkg-config") + " --silence-errors --modversion " + pkgname)
    else:
        return bool(LocateBinary(tool) != None)
    result = handle.read().strip()
    returnval = handle.close()
    if returnval != None and returnval != 0:
        return False
    return bool(len(result) > 0)

def PkgConfigGetLibs(pkgname, tool = "pkg-config"):
    """Returns a list of libs for the package, prefixed by -l."""

    if (sys.platform == "win32" or not LocateBinary(tool)):
        return []
    if (tool == "pkg-config"):
        handle = os.popen(LocateBinary("pkg-config") + " --silence-errors --libs-only-l " + pkgname)
    elif (tool == "fltk-config"):
        handle = os.popen(LocateBinary("fltk-config") + " --ldstaticflags")
    else:
        handle = os.popen(LocateBinary(tool) + " --libs")
    result = handle.read().strip()
    handle.close()
    libs = []

    # Walk through the result arguments carefully.  Look for -lname as
    # well as -framework name.
    r = result.split(' ')
    ri = 0
    while ri < len(r):
        l = r[ri]
        if l.startswith("-l") or l.startswith("/"):
            libs.append(l)
        elif l == '-framework':
            libs.append(l)
            ri += 1
            libs.append(r[ri])
        ri += 1

    return libs

def PkgConfigGetIncDirs(pkgname, tool = "pkg-config"):
    """Returns a list of includes for the package, NOT prefixed by -I."""

    if (sys.platform == "win32" or not LocateBinary(tool)):
        return []
    if (tool == "pkg-config"):
        handle = os.popen(LocateBinary("pkg-config") + " --silence-errors --cflags-only-I " + pkgname)
    else:
        handle = os.popen(LocateBinary(tool) + " --cflags")
    result = handle.read().strip()
    if len(result) == 0: return []
    handle.close()
    dirs = []
    for opt in result.split(" "):
        if (opt.startswith("-I")):
            inc_dir = opt.replace("-I", "").replace("\"", "").strip()
            # Hack for ODE, otherwise -S/usr/include gets added to interrogate
            if inc_dir != '/usr/include' and inc_dir != '/usr/include/':
                dirs.append(inc_dir)
    return dirs

def PkgConfigGetLibDirs(pkgname, tool = "pkg-config"):
    """Returns a list of library paths for the package, NOT prefixed by -L."""

    if (sys.platform == "win32" or not LocateBinary(tool)):
        return []
    if (tool == "pkg-config"):
        handle = os.popen(LocateBinary("pkg-config") + " --silence-errors --libs-only-L " + pkgname)
    elif (tool == "wx-config" or tool == "ode-config"):
        return []
    else:
        handle = os.popen(LocateBinary(tool) + " --ldflags")
    result = handle.read().strip()
    handle.close()
    if len(result) == 0: return []
    libs = []
    for l in result.split(" "):
        if (l.startswith("-L")):
            libs.append(l.replace("-L", "").replace("\"", "").strip())
    return libs

def PkgConfigGetDefSymbols(pkgname, tool = "pkg-config"):
    """Returns a dictionary of preprocessor definitions."""

    if (sys.platform == "win32" or not LocateBinary(tool)):
        return []
    if (tool == "pkg-config"):
        handle = os.popen(LocateBinary("pkg-config") + " --silence-errors --cflags " + pkgname)
    else:
        handle = os.popen(LocateBinary(tool) + " --cflags")
    result = handle.read().strip()
    handle.close()
    if len(result) == 0: return {}
    defs = {}
    for l in result.split(" "):
        if (l.startswith("-D")):
            d = l.replace("-D", "").replace("\"", "").strip().split("=")
            if (len(d) == 1):
                defs[d[0]] = ""
            else:
                defs[d[0]] = d[1]
    return defs

def PkgConfigEnable(opt, pkgname, tool = "pkg-config"):
    """Adds the libraries and includes to IncDirectory, LibName and LibDirectory."""
    for i in PkgConfigGetIncDirs(pkgname, tool):
        IncDirectory(opt, i)
    for i in PkgConfigGetLibDirs(pkgname, tool):
        LibDirectory(opt, i)
    for i in PkgConfigGetLibs(pkgname, tool):
        LibName(opt, i)
    for i, j in PkgConfigGetDefSymbols(pkgname, tool).items():
        DefSymbol(opt, i, j)

def LibraryExists(lib, lpath=[]):
    """ Returns True if this library was found in the given search path, False otherwise. """

    target = GetTarget()

    for dir in lpath:
        if target == 'darwin' and os.path.isfile(os.path.join(dir, 'lib%s.dylib' % lib)):
            return True
        elif target != 'darwin' and os.path.isfile(os.path.join(dir, 'lib%s.so' % lib)):
            return True
        elif os.path.isfile(os.path.join(dir, 'lib%s.a' % lib)):
            return True

    return False

def SystemLibraryExists(lib):
    return LibraryExists(lib, SYS_LIB_DIRS)

def ChooseLib(libs, thirdparty=None):
    """ Chooses a library from the parameters, in order of preference. Returns the first if none of them were found. """

    lpath = []
    if thirdparty is not None:
        lpath.append(os.path.join(GetThirdpartyDir(), thirdparty.lower(), "lib"))
    lpath += SYS_LIB_DIRS

    for l in libs:
        libname = l
        if l.startswith("lib"):
            libname = l[3:]
        if LibraryExists(libname, lpath):
            return libname

    if len(libs) > 0:
        if VERBOSE:
            print(ColorText("cyan", "Couldn't find any of the libraries " + ", ".join(libs)))
        return libs[0]

def SmartPkgEnable(pkg, pkgconfig = None, libs = None, incs = None, defs = None, framework = None, target_pkg = None, tool = "pkg-config"):
    global PKG_LIST_ALL
    if (pkg in PkgListGet() and PkgSkip(pkg)):
        return
    if (target_pkg == "" or target_pkg == None):
        target_pkg = pkg
    if (pkgconfig == ""):
        pkgconfig = None
    if (framework == ""):
        framework = None
    if (libs == None or libs == ""):
        libs = ()
    elif (isinstance(libs, str)):
        libs = (libs, )
    if (incs == None or incs == ""):
        incs = ()
    elif (isinstance(incs, str)):
        incs = (incs, )
    if (defs == None or defs == "" or len(defs) == 0):
        defs = {}
    elif (isinstance(incs, str)):
        defs = {defs : ""}
    elif (isinstance(incs, list) or isinstance(incs, tuple) or isinstance(incs, set)):
        olddefs = defs
        defs = {}
        for d in olddefs:
            defs[d] = ""

    if (pkg.lower() == "swscale" and os.path.isfile(GetThirdpartyDir() + "ffmpeg/include/libswscale/swscale.h")):
        # Let it be handled by the ffmpeg package
        LibName(target_pkg, "-lswscale")
        return
    if (pkg.lower() == "swresample" and os.path.isfile(GetThirdpartyDir() + "ffmpeg/include/libswresample/swresample.h")):
        LibName(target_pkg, "-lswresample")
        return

    pkg_dir = os.path.join(GetThirdpartyDir(), pkg.lower())
    if (os.path.isdir(pkg_dir)):
        if os.path.isdir(os.path.join(pkg_dir, "include")):
            IncDirectory(target_pkg, os.path.join(pkg_dir, "include"))
        if os.path.isdir(os.path.join(pkg_dir, "lib")):
            LibDirectory(target_pkg, os.path.join(pkg_dir, "lib"))

        if (PkgSkip("PYTHON") == 0):
            py_lib_dir = os.path.join(pkg_dir, "lib", SDK["PYTHONVERSION"])
            if os.path.isdir(py_lib_dir):
                LibDirectory(target_pkg, py_lib_dir)

        # TODO: check for a .pc file in the lib/pkg-config/ dir
        if (tool != None and os.path.isfile(os.path.join(pkg_dir, "bin", tool))):
            tool = os.path.join(pkg_dir, "bin", tool)
            for i in PkgConfigGetLibs(None, tool):
                LibName(target_pkg, i)
            for i, j in PkgConfigGetDefSymbols(None, tool).items():
                DefSymbol(target_pkg, i, j)
            return

        for l in libs:
            libname = l
            if l.startswith("lib"):
                libname = l[3:]
            # This is for backward compatibility - in the thirdparty dir, we kept some libs with "panda" prefix, like libpandatiff.
            if len(glob.glob(os.path.join(pkg_dir, "lib", "libpanda%s.*" % (libname)))) > 0 \
               and len(glob.glob(os.path.join(pkg_dir, "lib", "lib%s.*" % (libname)))) == 0:
                libname = "panda" + libname
            LibName(target_pkg, "-l" + libname)

        for d, v in defs.values():
            DefSymbol(target_pkg, d, v)
        return
    elif (GetHost() == "darwin" and framework != None):
        if (os.path.isdir("/Library/Frameworks/%s.framework" % framework) or
            os.path.isdir("/System/Library/Frameworks/%s.framework" % framework) or
            os.path.isdir("/Developer/Library/Frameworks/%s.framework" % framework) or
            os.path.isdir("/Users/%s/System/Library/Frameworks/%s.framework" % (getpass.getuser(), framework))):
            LibName(target_pkg, "-framework " + framework)
            for d, v in defs.values():
                DefSymbol(target_pkg, d, v)
        elif (pkg in PkgListGet()):
            print("%sWARNING:%s Could not locate framework %s, excluding from build" % (GetColor("red"), GetColor(), framework))
            PkgDisable(pkg)
        else:
            print("%sERROR:%s Could not locate framework %s, aborting build" % (GetColor("red"), GetColor(), framework))
            exit()
        return
    elif (LocateBinary(tool) != None and (tool != "pkg-config" or pkgconfig != None)):
        if (isinstance(pkgconfig, str) or tool != "pkg-config"):
            if (PkgConfigHavePkg(pkgconfig, tool)):
                return PkgConfigEnable(target_pkg, pkgconfig, tool)
        else:
            have_all_pkgs = True
            for pc in pkgconfig:
                if (PkgConfigHavePkg(pc, tool)):
                    PkgConfigEnable(target_pkg, pc, tool)
                else:
                    have_all_pkgs = False
            if (have_all_pkgs):
                return

    if (pkgconfig != None and (libs == None or len(libs) == 0)):
        if (pkg in PkgListGet()):
            print("%sWARNING:%s Could not locate pkg-config package %s, excluding from build" % (GetColor("red"), GetColor(), pkgconfig))
            PkgDisable(pkg)
        else:
            print("%sERROR:%s Could not locate pkg-config package %s, aborting build" % (GetColor("red"), GetColor(), pkgconfig))
            exit()
    else:
        # Okay, our pkg-config attempts failed. Let's try locating the libs by ourselves.
        have_pkg = True
        for l in libs:
            libname = l
            if l.startswith("lib"):
                libname = l[3:]
            if SystemLibraryExists(libname):
                LibName(target_pkg, "-l" + libname)
            else:
                have_pkg = False
                if VERBOSE:
                    print(GetColor("cyan") + "Couldn't find library lib" + libname + GetColor())

        for i in incs:
            incdir = None
            sysroot_usr = SDK.get("SYSROOT", "") + "/usr"
            if (len(glob.glob(sysroot_usr + "/include/" + i)) > 0):
                incdir = sorted(glob.glob(sysroot_usr + "/include/" + i))[-1]
            elif (len(glob.glob(sysroot_usr + "/local/include/" + i)) > 0):
                incdir = sorted(glob.glob(sysroot_usr + "/local/include/" + i))[-1]
            elif (os.path.isdir(sysroot_usr + "/PCBSD") and len(glob.glob(sysroot_usr + "/PCBSD/local/include/" + i)) > 0):
                incdir = sorted(glob.glob(sysroot_usr + "/PCBSD/local/include/" + i))[-1]
            else:
                have_pkg = False
                # Try searching in the package's IncDirectories.
                for ppkg, pdir in INCDIRECTORIES:
                    if (pkg == ppkg and len(glob.glob(os.path.join(pdir, i))) > 0):
                        incdir = sorted(glob.glob(os.path.join(pdir, i)))[-1]
                        have_pkg = True
                if (incdir == None and VERBOSE and i.endswith(".h")):
                    print(GetColor("cyan") + "Couldn't find header file " + i + GetColor())

            # Note: It's possible to specify a file instead of a dir, for the sake of checking if it exists.
            if (incdir != None and os.path.isdir(incdir)):
                IncDirectory(target_pkg, incdir)

        if (not have_pkg):
            if (pkg in PkgListGet()):
                print("%sWARNING:%s Could not locate thirdparty package %s, excluding from build" % (GetColor("red"), GetColor(), pkg.lower()))
                PkgDisable(pkg)
            else:
                print("%sERROR:%s Could not locate thirdparty package %s, aborting build" % (GetColor("red"), GetColor(), pkg.lower()))
                exit()

########################################################################
##
## SDK Location
##
## This section is concerned with locating the install directories
## for various third-party packages.  The results are stored in the
## SDK table.
##
## Microsoft keeps changing the &*#$*& registry key for the DirectX SDK.
## The only way to reliably find it is to search through the installer's
## uninstall-directories, look in each one, and see if it contains the
## relevant files.
##
########################################################################

SDK = {}

def GetSdkDir(sdkname, sdkkey = None):
    # Returns the default SDK directory. If it exists,
    # and sdkkey is not None, it is put in SDK[sdkkey].
    # Note: return value may not be an existing path.
    sdkbase = "sdks"
    if "MAKEPANDA_SDKS" in os.environ:
        sdkbase = os.environ["MAKEPANDA_SDKS"]

    sdir = sdkbase[:]
    target = GetTarget()
    target_arch = GetTargetArch()
    if target == 'windows':
        if target_arch == 'x64':
            sdir += "/win64"
        else:
            sdir += "/win32"
    elif target == 'linux':
        sdir += "/linux"
        sdir += platform.architecture()[0][:2]
    elif target == 'darwin':
        sdir += "/macosx"
    sdir += "/" + sdkname

    # If it does not exist, try the old location.
    if (not os.path.isdir(sdir)):
        sdir = sdkbase + "/" + sdir
        if (target == 'linux'):
            sdir += "-linux"
            sdir += platform.architecture()[0][:2]
        elif (target == "darwin"):
            sdir += "-osx"

    if (sdkkey and os.path.isdir(sdir)):
        SDK[sdkkey] = sdir

    return sdir

def SdkLocateDirectX( strMode = 'default' ):
    if (GetHost() != "windows"): return
    if strMode == 'default':
        GetSdkDir("directx8", "DX8")
        GetSdkDir("directx9", "DX9")
        if ("DX9" not in SDK):
            strMode = 'latest'
    if strMode == 'latest':
        ## We first try to locate the August SDK in 64 bits, then 32.
        if ("DX9" not in SDK):
            dir = GetRegistryKey("SOFTWARE\\Wow6432Node\\Microsoft\\DirectX\\Microsoft DirectX SDK (June 2010)", "InstallPath")
            if (dir != 0):
                print("Using DirectX SDK June 2010")
                SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
                SDK["GENERIC_DXERR_LIBRARY"] = 1;
        if ("DX9" not in SDK):
            dir = GetRegistryKey("SOFTWARE\\Microsoft\\DirectX\\Microsoft DirectX SDK (June 2010)", "InstallPath")
            if (dir != 0):
                print("Using DirectX SDK June 2010")
                SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
                SDK["GENERIC_DXERR_LIBRARY"] = 1;
        if ("DX9" not in SDK):
            dir = GetRegistryKey("SOFTWARE\\Wow6432Node\\Microsoft\\DirectX\\Microsoft DirectX SDK (August 2009)", "InstallPath")
            if (dir != 0):
                print("Using DirectX SDK Aug 2009")
                SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
                SDK["GENERIC_DXERR_LIBRARY"] = 1;
        if ("DX9" not in SDK):
            dir = GetRegistryKey("SOFTWARE\\Microsoft\\DirectX\\Microsoft DirectX SDK (August 2009)", "InstallPath")
            if (dir != 0):
                print("Using DirectX SDK Aug 2009")
                SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
                SDK["GENERIC_DXERR_LIBRARY"] = 1;
        if ("DX9" not in SDK):
            ## Try to locate the key within the "new" March 2009 location in the registry (yecch):
            dir = GetRegistryKey("SOFTWARE\\Microsoft\\DirectX\\Microsoft DirectX SDK (March 2009)", "InstallPath")
            if (dir != 0):
                print("Using DirectX SDK March 2009")
                SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
        archStr = GetTargetArch()
        if ("DX9" not in SDK) or ("DX8" not in SDK):
            uninstaller = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
            for subdir in ListRegistryKeys(uninstaller):
                if (subdir[0]=="{"):
                    dir = GetRegistryKey(uninstaller+"\\"+subdir, "InstallLocation")
                    if (dir != 0):
                        if (("DX8" not in SDK) and
                            (os.path.isfile(dir+"\\Include\\d3d8.h")) and
                            (os.path.isfile(dir+"\\Include\\d3dx8.h")) and
                            (os.path.isfile(dir+"\\Lib\\d3d8.lib")) and
                            (os.path.isfile(dir+"\\Lib\\d3dx8.lib"))):
                            SDK["DX8"] = dir.replace("\\", "/").rstrip("/")
                        if (("DX9" not in SDK) and
                            (os.path.isfile(dir+"\\Include\\d3d9.h")) and
                            (os.path.isfile(dir+"\\Include\\d3dx9.h")) and
                            (os.path.isfile(dir+"\\Include\\dxsdkver.h")) and
                            (os.path.isfile(dir+"\\Lib\\" + archStr + "\\d3d9.lib")) and
                            (os.path.isfile(dir+"\\Lib\\" + archStr + "\\d3dx9.lib"))):
                            SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
        if ("DX9" not in SDK):
            return
    elif strMode == 'jun2010':
        if ("DX9" not in SDK):
            dir = GetRegistryKey("SOFTWARE\\Wow6432Node\\Microsoft\\DirectX\\Microsoft DirectX SDK (June 2010)", "InstallPath")
            if (dir != 0):
                print("Found DirectX SDK June 2010")
                SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
                SDK["GENERIC_DXERR_LIBRARY"] = 1;
        if ("DX9" not in SDK):
            dir = GetRegistryKey("SOFTWARE\\Microsoft\\DirectX\\Microsoft DirectX SDK (June 2010)", "InstallPath")
            if (dir != 0):
                print("Found DirectX SDK June 2010")
                SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
                SDK["GENERIC_DXERR_LIBRARY"] = 1;
        if ("DX9" not in SDK):
            exit("Couldn't find DirectX June2010 SDK")
    elif strMode == 'aug2009':
        if ("DX9" not in SDK):
            dir = GetRegistryKey("SOFTWARE\\Wow6432Node\\Microsoft\\DirectX\\Microsoft DirectX SDK (August 2009)", "InstallPath")
            if (dir != 0):
                print("Found DirectX SDK Aug 2009")
                SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
                SDK["GENERIC_DXERR_LIBRARY"] = 1;
        if ("DX9" not in SDK):
            dir = GetRegistryKey("SOFTWARE\\Microsoft\\DirectX\\Microsoft DirectX SDK (August 2009)", "InstallPath")
            if (dir != 0):
                print("Found DirectX SDK Aug 2009")
                SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
                SDK["GENERIC_DXERR_LIBRARY"] = 1;
        if ("DX9" not in SDK):
            exit("Couldn't find DirectX Aug 2009 SDK")
    elif strMode == 'mar2009':
        if ("DX9" not in SDK):
            ## Try to locate the key within the "new" March 2009 location in the registry (yecch):
            dir = GetRegistryKey("SOFTWARE\\Microsoft\\DirectX\\Microsoft DirectX SDK (March 2009)", "InstallPath")
            if (dir != 0):
                print("Found DirectX SDK March 2009")
                SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
        if ("DX9" not in SDK):
            exit("Couldn't find DirectX March 2009 SDK")
    elif strMode == 'aug2006':
        archStr = GetTargetArch()
        if ("DX9" not in SDK) or ("DX8" not in SDK):
            uninstaller = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
            for subdir in ListRegistryKeys(uninstaller):
                if (subdir[0]=="{"):
                    dir = GetRegistryKey(uninstaller+"\\"+subdir, "InstallLocation")
                    if (dir != 0):
                        if (("DX8" not in SDK) and
                            (os.path.isfile(dir+"\\Include\\d3d8.h")) and
                            (os.path.isfile(dir+"\\Include\\d3dx8.h")) and
                            (os.path.isfile(dir+"\\Lib\\d3d8.lib")) and
                            (os.path.isfile(dir+"\\Lib\\d3dx8.lib"))):
                            SDK["DX8"] = dir.replace("\\", "/").rstrip("/")
                        if (("DX9" not in SDK) and
                            (os.path.isfile(dir+"\\Include\\d3d9.h")) and
                            (os.path.isfile(dir+"\\Include\\d3dx9.h")) and
                            (os.path.isfile(dir+"\\Include\\dxsdkver.h")) and
                            (os.path.isfile(dir+"\\Lib\\" + archStr + "\\d3d9.lib")) and
                            (os.path.isfile(dir+"\\Lib\\" + archStr + "\\d3dx9.lib"))):
                            SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
        if ("DX9" not in SDK):
            exit("Couldn't find a DirectX Aug 2006 SDK")
    if ("DX9" in SDK):
        SDK["DIRECTCAM"] = SDK["DX9"]

def SdkLocateMaya():
    for (ver, key) in MAYAVERSIONINFO:
        if (PkgSkip(ver)==0 and ver not in SDK):
            GetSdkDir(ver.lower().replace("x",""), ver)
            if (not ver in SDK):
                if (GetHost() == "windows"):
                    for dev in ["Alias|Wavefront","Alias","Autodesk"]:
                        fullkey="SOFTWARE\\"+dev+"\\Maya\\"+key+"\\Setup\\InstallPath"
                        res = GetRegistryKey(fullkey, "MAYA_INSTALL_LOCATION", override64=False)
                        if (res != 0):
                            res = res.replace("\\", "/").rstrip("/")
                            SDK[ver] = res
                elif (GetHost() == "darwin"):
                    ddir = "/Applications/Autodesk/maya"+key
                    if (os.path.isdir(ddir)): SDK[ver] = ddir
                else:
                    if (GetTargetArch() in ("x86_64", "amd64")):
                        ddir1 = "/usr/autodesk/maya"+key+"-x64"
                        ddir2 = "/usr/aw/maya"+key+"-x64"
                    else:
                        ddir1 = "/usr/autodesk/maya"+key
                        ddir2 = "/usr/aw/maya"+key

                    if (os.path.isdir(ddir1)):   SDK[ver] = ddir1
                    elif (os.path.isdir(ddir2)): SDK[ver] = ddir2

def SdkLocateMax():
    if (GetHost() != "windows"): return
    for version,key1,key2,subdir in MAXVERSIONINFO:
        if (PkgSkip(version)==0):
            if (version not in SDK):
                GetSdkDir("maxsdk"+version.lower()[3:], version)
                GetSdkDir("maxsdk"+version.lower()[3:], version+"CS")
                if (not version in SDK):
                    top = GetRegistryKey(key1,key2)
                    if (top != 0):
                        SDK[version] = top + "maxsdk"
                        if (os.path.isdir(top + "\\" + subdir)!=0):
                            SDK[version+"CS"] = top + subdir

def SdkLocatePython(force_use_sys_executable = False):
    if (PkgSkip("PYTHON")==0):
        if GetTarget() != GetHost():
            exit('Use --no-python when cross-compiling until support has been added')

        if (GetTarget() == 'windows' and not force_use_sys_executable):
            SDK["PYTHON"] = GetThirdpartyBase()+"/win-python"
            if (GetOptimize() <= 2):
                SDK["PYTHON"] += "-dbg"
            if (GetTargetArch() == 'x64' and os.path.isdir(SDK["PYTHON"] + "-x64")):
                SDK["PYTHON"] += "-x64"

            SDK["PYTHONEXEC"] = SDK["PYTHON"].replace('/', '\\') + "\\python"
            if (GetOptimize() <= 2):
                SDK["PYTHONEXEC"] += "_d.exe"
            else:
                SDK["PYTHONEXEC"] += ".exe"

            if (not os.path.isfile(SDK["PYTHONEXEC"])):
                exit("Could not find %s!" % SDK["PYTHONEXEC"])

            # Determine which version it is by checking which dll is in the directory.
            py_dlls = glob.glob(SDK["PYTHON"] + "/python[0-9][0-9].dll")
            if len(py_dlls) == 0:
                exit("Could not find the Python dll in %s." % (SDK["PYTHON"]))
            elif len(py_dlls) > 1:
                exit("Found multiple Python dlls in %s." % (SDK["PYTHON"]))

            SDK["PYTHONVERSION"] = "python" + py_dlls[0][-6] + "." + py_dlls[0][-5]

        elif (GetTarget() == 'windows'):
            SDK["PYTHON"] = os.path.dirname(sysconfig.get_python_inc())
            SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version()
            SDK["PYTHONEXEC"] = sys.executable

        else:
            SDK["PYTHON"] = sysconfig.get_python_inc()
            SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version()
            SDK["PYTHONEXEC"] = os.path.realpath(sys.executable)

        if GetVerbose():
            print("Using Python %s build located at %s" % (SDK["PYTHONVERSION"][6:9], SDK["PYTHON"]))

    else:
        SDK["PYTHONEXEC"] = os.path.realpath(sys.executable)

def SdkLocateVisualStudio():
    if (GetHost() != "windows"): return
    vcdir = GetRegistryKey("SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VC7", "10.0")
    if (vcdir != 0) and (vcdir[-4:] == "\\VC\\"):
        vcdir = vcdir[:-3]
        SDK["VISUALSTUDIO"] = vcdir

    elif (os.path.isfile("C:\\Program Files\\Microsoft Visual Studio 10.0\\VC\\bin\\cl.exe")):
        SDK["VISUALSTUDIO"] = "C:\\Program Files\\Microsoft Visual Studio 10.0\\"

    elif (os.path.isfile("C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\bin\\cl.exe")):
        SDK["VISUALSTUDIO"] = "C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\"

    elif "VCINSTALLDIR" in os.environ:
        vcdir = os.environ["VCINSTALLDIR"]
        if (vcdir[-3:] == "\\VC"):
            vcdir = vcdir[:-2]
        elif (vcdir[-4:] == "\\VC\\"):
            vcdir = vcdir[:-3]

        SDK["VISUALSTUDIO"] = vcdir

def SdkLocateMSPlatform(strMode = 'default'):
    if (GetHost() != "windows"): return
    platsdk = None

    platsdk = GetRegistryKey("SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v7.1", "InstallationFolder")
    if (platsdk and not os.path.isdir(platsdk)):
        platsdk = None

    if not platsdk:
        # Most common location.  Worth a try.
        platsdk = "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1"
        if not os.path.isdir(platsdk):
            platsdk = None

    if not platsdk:
        exit("Couldn't find Windows SDK v7.1")

    if not platsdk.endswith("\\"):
        platsdk += "\\"
    SDK["MSPLATFORM"] = platsdk

def SdkLocateMacOSX(osxtarget = None):
    if (GetHost() != "darwin"): return
    if (osxtarget != None):
        if (os.path.exists("/Developer/SDKs/MacOSX%su.sdk" % osxtarget)):
            SDK["MACOSX"] = "/Developer/SDKs/MacOSX%su.sdk" % osxtarget
        elif (os.path.exists("/Developer/SDKs/MacOSX%s.sdk" % osxtarget)):
            SDK["MACOSX"] = "/Developer/SDKs/MacOSX%s.sdk" % osxtarget
        elif (os.path.exists("/Developer/SDKs/MacOSX%s.0.sdk" % osxtarget)):
            SDK["MACOSX"] = "/Developer/SDKs/MacOSX%s.0.sdk" % osxtarget
        elif (os.path.exists("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX%s.sdk" % osxtarget)):
            SDK["MACOSX"] = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX%s.sdk" % osxtarget
        else:
            handle = os.popen("xcode-select -print-path")
            result = handle.read().strip().rstrip('/')
            handle.close()
            if (os.path.exists("%s/Platforms/MacOSX.platform/Developer/SDKs/MacOSX%s.sdk" % (result, osxtarget))):
                SDK["MACOSX"] = "%s/Platforms/MacOSX.platform/Developer/SDKs/MacOSX%s.sdk" % (result, osxtarget)
            else:
                exit("Couldn't find any MacOSX SDK for OSX version %s!" % osxtarget)
    else:
        SDK["MACOSX"] = ""

# Latest first
PHYSXVERSIONINFO = [
    ("PHYSX284", "v2.8.4"),
    ("PHYSX283", "v2.8.3"),
    ("PHYSX281", "v2.8.1"),
]

def SdkLocatePhysX():
    # First check for a physx directory in sdks.
    dir = GetSdkDir("physx")
    if (dir and os.path.isdir(dir)):
        SDK["PHYSX"] = dir
        SDK["PHYSXLIBS"] = dir + "/lib"
        return

    if CrossCompiling():
        return

    # Try to find a PhysX installation on the system.
    for (ver, key) in PHYSXVERSIONINFO:
        if (GetHost() == "windows"):
            folders = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\Folders"
            for folder in ListRegistryValues(folders):
                if folder.endswith("NVIDIA PhysX SDK\\%s\\SDKs\\" % key) or \
                   folder.endswith("NVIDIA PhysX SDK\\%s_win\\SDKs\\" % key):

                    SDK["PHYSX"] = folder
                    if GetTargetArch() == 'x64':
                        SDK["PHYSXLIBS"] = folder + "/lib/win64"
                        AddToPathEnv("PATH", folder + "/../Bin/win64/")
                    else:
                        SDK["PHYSXLIBS"] = folder + "/lib/win32"
                        AddToPathEnv("PATH", folder + "/../Bin/win32/")

                    return

        elif (GetHost() == "linux"):
            incpath = "/usr/include/PhysX/%s/SDKs" % key
            libpath = "/usr/lib/PhysX/%s" % key
            if (os.path.isdir(incpath) and os.path.isdir(libpath)):
                SDK["PHYSX"] = incpath
                SDK["PHYSXLIBS"] = libpath
                return

def SdkLocateSpeedTree():
    # Look for all of the SpeedTree SDK directories within the
    # sdks/win32/speedtree dir, and pick the highest-numbered one.
    dir = GetSdkDir("speedtree")
    if not os.path.exists(dir):
        return

    speedtrees = []
    for dirname in os.listdir(dir):
        if dirname.startswith('SpeedTree SDK v'):
            version = dirname[15:].split()[0]
            version = tuple(map(int, version.split('.')))
            speedtrees.append((version, dirname))
    if not speedtrees:
        # No installed SpeedTree SDK.
        return

    speedtrees.sort()
    version, dirname = speedtrees[-1]
    SDK["SPEEDTREE"] = os.path.join(dir, dirname)
    SDK["SPEEDTREEAPI"] = "OpenGL"
    SDK["SPEEDTREEVERSION"] = '%s.%s' % (version[0], version[1])

def SdkLocateAndroid():
    """This actually locates the Android NDK, not the Android SDK.
    NDK_ROOT must be set to its root directory."""

    if GetTarget() != 'android':
        return

    # Determine the NDK installation directory.
    if 'NDK_ROOT' not in os.environ:
        exit('NDK_ROOT must be set when compiling for Android!')

    ndk_root = os.environ["NDK_ROOT"]
    if not os.path.isdir(ndk_root):
        exit("Cannot find %s.  Please install Android NDK and set NDK_ROOT." % (ndk_root))

    SDK["ANDROID_NDK"] = ndk_root

    # Determine the toolchain location.
    gcc_ver = '4.7'
    arch = GetTargetArch()
    if arch == 'armv7' or arch == 'arm':
        arch = 'arm'
        toolchain = 'arm-linux-androideabi-' + gcc_ver
    elif arch == 'x86':
        toolchain = 'x86-' + gcc_ver
    elif arch == 'mips':
        toolchain = 'mipsel-linux-android-' + gcc_ver
    SDK["ANDROID_TOOLCHAIN"] = os.path.join(ndk_root, 'toolchains', toolchain)

    # Allow ANDROID_ABI to be used in makepanda.py.
    abi = ANDROID_ABI
    SDK["ANDROID_ABI"] = abi

    # Determine the sysroot directory.
    SDK["SYSROOT"] = os.path.join(ndk_root, 'platforms', 'android-9', 'arch-%s' % (arch))
    #IncDirectory("ALWAYS", os.path.join(SDK["SYSROOT"], 'usr', 'include'))

    stdlibc = os.path.join(ndk_root, 'sources', 'cxx-stl', 'gnu-libstdc++', gcc_ver)
    IncDirectory("ALWAYS", os.path.join(stdlibc, 'include'))
    IncDirectory("ALWAYS", os.path.join(stdlibc, 'libs', abi, 'include'))

    stl_lib = os.path.join(stdlibc, 'libs', abi, 'libgnustl_shared.so')
    LibName("ALWAYS", stl_lib)
    CopyFile(os.path.join(GetOutputDir(), 'libs', abi, 'libgnustl_shared.so'), stl_lib)

########################################################################
##
## SDK Auto-Disables
##
## Disable packages whose SDKs could not be found.
##
########################################################################

def SdkAutoDisableDirectX():
    for ver in ["DX8","DX9","DIRECTCAM"]:
        if (PkgSkip(ver)==0):
            if (ver not in SDK):
                if (GetHost() == "windows"):
                    WARNINGS.append("I cannot locate SDK for "+ver)
                    WARNINGS.append("I have automatically added this command-line option: --no-"+ver.lower())
                PkgDisable(ver)
            else:
                WARNINGS.append("Using "+ver+" sdk: "+SDK[ver])

def SdkAutoDisableMaya():
    for (ver,key) in MAYAVERSIONINFO:
        if (ver not in SDK) and (PkgSkip(ver)==0):
            if (GetHost() == "windows"):
                WARNINGS.append("The registry does not appear to contain a pointer to the "+ver+" SDK.")
            else:
                WARNINGS.append("I cannot locate SDK for "+ver)
            WARNINGS.append("I have automatically added this command-line option: --no-"+ver.lower())
            PkgDisable(ver)

def SdkAutoDisableMax():
    for version,key1,key2,subdir in MAXVERSIONINFO:
        if (PkgSkip(version)==0) and ((version not in SDK) or (version+"CS" not in SDK)):
            if (GetHost() == "windows"):
                if (version in SDK):
                    WARNINGS.append("Your copy of "+version+" does not include the character studio SDK")
                else:
                    WARNINGS.append("The registry does not appear to contain a pointer to "+version)
                WARNINGS.append("I have automatically added this command-line option: --no-"+version.lower())
            PkgDisable(version)

def SdkAutoDisablePhysX():
    if ("PHYSX" not in SDK) and (PkgSkip("PHYSX")==0):
        PkgDisable("PHYSX")
        WARNINGS.append("I cannot locate SDK for PhysX")
        WARNINGS.append("I have automatically added this command-line option: --no-physx")

def SdkAutoDisableSpeedTree():
    if ("SPEEDTREE" not in SDK) and (PkgSkip("SPEEDTREE")==0):
        PkgDisable("SPEEDTREE")
        WARNINGS.append("I cannot locate SDK for SpeedTree")
        WARNINGS.append("I have automatically added this command-line option: --no-speedtree")

########################################################################
##
## Visual Studio comes with a script called VSVARS32.BAT, which
## you need to run before using visual studio command-line tools.
## The following python subroutine serves the same purpose.
##
########################################################################

def AddToPathEnv(path,add):
    if path in os.environ:
        os.environ[path] = add + os.pathsep + os.environ[path]
    else:
        os.environ[path] = add

def SetupVisualStudioEnviron():
    if ("VISUALSTUDIO" not in SDK):
        exit("Could not find Visual Studio install directory")
    if ("MSPLATFORM" not in SDK):
        exit("Could not find the Microsoft Platform SDK")
    os.environ["VCINSTALLDIR"] = SDK["VISUALSTUDIO"] + "VC"
    os.environ["WindowsSdkDir"] = SDK["MSPLATFORM"]

    # Determine the directories to look in based on the architecture.
    arch = GetTargetArch()
    bindir = ""
    libdir = ""
    if (arch == 'x64'):
        bindir = 'amd64'
        libdir = 'amd64'
    elif (arch != 'x86'):
        bindir = arch
        libdir = arch

    if (arch != 'x86' and GetHostArch() == 'x86'):
        # Special version of the tools that run on x86.
        bindir = 'x86_' + bindir

    binpath = SDK["VISUALSTUDIO"] + "VC\\bin\\" + bindir
    if not os.path.isdir(binpath):
        exit("Couldn't find compilers in %s.  You may need to install the Windows SDK 7.1 and the Visual C++ 2010 SP1 Compiler Update for Windows SDK 7.1.")

    AddToPathEnv("PATH",    binpath)
    AddToPathEnv("PATH",    SDK["VISUALSTUDIO"] + "Common7\\IDE")
    AddToPathEnv("INCLUDE", SDK["VISUALSTUDIO"] + "VC\\include")
    AddToPathEnv("INCLUDE", SDK["VISUALSTUDIO"] + "VC\\atlmfc\\include")
    AddToPathEnv("LIB",     SDK["VISUALSTUDIO"] + "VC\\lib\\" + libdir)
    AddToPathEnv("LIB",     SDK["VISUALSTUDIO"] + "VC\\atlmfc\\lib\\" + libdir)
    AddToPathEnv("PATH",    SDK["MSPLATFORM"] + "bin")
    AddToPathEnv("INCLUDE", SDK["MSPLATFORM"] + "include")
    AddToPathEnv("INCLUDE", SDK["MSPLATFORM"] + "include\\atl")
    AddToPathEnv("INCLUDE", SDK["MSPLATFORM"] + "include\\mfc")
    if (arch != 'x64'):
        AddToPathEnv("LIB", SDK["MSPLATFORM"] + "lib")
        AddToPathEnv("PATH",SDK["VISUALSTUDIO"] + "VC\\redist\\x86\\Microsoft.VC100.CRT")
        AddToPathEnv("PATH",SDK["VISUALSTUDIO"] + "VC\\redist\\x86\\Microsoft.VC100.MFC")
    elif (os.path.isdir(SDK["MSPLATFORM"] + "lib\\x64")):
        AddToPathEnv("LIB", SDK["MSPLATFORM"] + "lib\\x64")
    elif (os.path.isdir(SDK["MSPLATFORM"] + "lib\\amd64")):
        AddToPathEnv("LIB", SDK["MSPLATFORM"] + "lib\\amd64")
    else:
        exit("Could not locate 64-bits libraries in Platform SDK!")

########################################################################
#
# Include and Lib directories.
#
# These allow you to add include and lib directories to the
# compiler search paths.  These methods accept a "package"
# parameter, which specifies which package the directory is
# associated with.  The include/lib directory is not used
# if the package is not selected.  The package can be 'ALWAYS'.
#
########################################################################

INCDIRECTORIES = []
LIBDIRECTORIES = []
LIBNAMES = []
DEFSYMBOLS = []

def IncDirectory(opt, dir):
    INCDIRECTORIES.append((opt, dir))

def LibDirectory(opt, dir):
    LIBDIRECTORIES.append((opt, dir))

def LibName(opt, name):
    # Check to see if the lib file actually exists for the thirdparty library given
    # Are we a thirdparty library?
    if name.startswith(GetThirdpartyDir()):
        # Does this lib exist?
        if not os.path.exists(name):
            WARNINGS.append(name + " not found.  Skipping Package " + opt)
            if (opt in PkgListGet()):
                print("%sWARNING:%s Could not locate thirdparty package %s, excluding from build" % (GetColor("red"), GetColor(), opt.lower()))
                PkgDisable(opt)
                return
            else:
                print("%sERROR:%s Could not locate thirdparty package %s, aborting build" % (GetColor("red"), GetColor(), opt.lower()))
                exit()
    LIBNAMES.append((opt, name))

def DefSymbol(opt, sym, val=""):
    DEFSYMBOLS.append((opt, sym, val))

########################################################################
#
# This subroutine prepares the environment for the build.
#
########################################################################

def SetupBuildEnvironment(compiler):
    if GetVerbose():
        print("Using compiler: %s" % compiler)
        print("Host OS: %s" % GetHost())
        print("Host arch: %s" % GetHostArch())
        print("Target OS: %s" % GetTarget())
        print("Target arch: %s" % GetTargetArch())

    if compiler == "MSVC":
        # Add the visual studio tools to PATH et al.
        SetupVisualStudioEnviron()

    if compiler == "GCC":
        # Invoke gcc to determine the system library directories.
        global SYS_LIB_DIRS

        # gcc doesn't add this one, but we do want it:
        SYS_LIB_DIRS.append('/usr/local/lib')

        cmd = GetCXX() + " -print-search-dirs"

        if "MACOSX" in SDK:
            # The default compiler in Leopard does not respect --sysroot correctly.
            cmd += " -isysroot " + SDK["MACOSX"]
        if "SYSROOT" in SDK:
            cmd += ' --sysroot=%s -no-canonical-prefixes' % (SDK["SYSROOT"])

        # Extract the dirs from the line that starts with 'libraries: ='.
        handle = os.popen(cmd)
        for line in handle:
            if not line.startswith('libraries: ='):
                continue

            line = line[12:].strip()
            SYS_LIB_DIRS += line.split(':')

        returnval = handle.close()
        if returnval != None and returnval != 0:
            print("%sWARNING:%s %s failed" % (GetColor("red"), GetColor(), cmd))
            SYS_LIB_DIRS += ['/usr/lib']
        elif GetVerbose():
            print("System library search path: %s" % ':'.join(SYS_LIB_DIRS))

    # In the case of Android, we have to put the toolchain on the PATH in order to use it.
    if GetTarget() == 'android':
        # Locate the directory where the toolchain binaries reside.
        prebuilt_dir = os.path.join(SDK['ANDROID_TOOLCHAIN'], 'prebuilt')
        if not os.path.isdir(prebuilt_dir):
            exit('Not found: %s' % (prebuilt_dir))

        host_tag = GetHost() + '-x86'
        if host_64:
            host_tag += '_64'
        elif host_tag == 'windows-x86':
            host_tag = 'windows'

        prebuilt_dir = os.path.join(prebuilt_dir, host_tag)
        if host_64 and not os.path.isdir(prebuilt_dir):
            # Try the 32-bits toolchain instead.
            prebuilt_dir = os.path.join(prebuilt_dir, host_tag)

        if not os.path.isdir(prebuilt_dir):
            if host_64:
                exit('Not found: %s or %s' % (prebuilt_dir, host_tag))
            else:
                exit('Not found: %s' % (prebuilt_dir))

        # Then, add it to the PATH.
        AddToPathEnv("PATH", os.path.join(prebuilt_dir, 'bin'))

    # If we're cross-compiling, no point in putting our output dirs on the path.
    if CrossCompiling():
        return

    # Add our output directories to the environment.
    builtdir = os.path.join(os.path.abspath(GetOutputDir()))
    AddToPathEnv("PYTHONPATH", builtdir)
    AddToPathEnv("PANDA_PRC_DIR", os.path.join(builtdir, "etc"))
    AddToPathEnv("PATH", os.path.join(builtdir, "bin"))
    if (GetHost() == 'windows'):
        AddToPathEnv("PATH", os.path.join(builtdir, "plugins"))
        AddToPathEnv("PYTHONPATH", os.path.join(builtdir, "bin"))
    else:
        AddToPathEnv("PYTHONPATH", os.path.join(builtdir, "lib"))

    # Now for the special (DY)LD_LIBRARY_PATH on Unix-esque systems.
    if GetHost() != 'windows':
        # Get the current
        ldpath = os.environ.get("LD_LIBRARY_PATH", "").split(os.pathsep)
        if GetHost() == 'darwin':
            dyldpath = os.environ.get("DYLD_LIBRARY_PATH", "").split(os.pathsep)

        # Remove any potential current Panda installation lib dirs
        for i in ldpath[:]:
            if i.startswith("/usr/lib/panda3d") or \
               i.startswith("/usr/local/panda"):
                ldpath.remove(i)

        if GetHost() == 'darwin':
            for i in dyldpath[:]:
                if i.startswith("/Applications/Panda3D") or \
                   i.startswith("/Developer/Panda3D"):
                    dyldpath.remove(i)

        # Add built/lib/ to (DY)LD_LIBRARY_PATH
        ldpath.insert(0, os.path.join(builtdir, 'lib'))
        os.environ["LD_LIBRARY_PATH"] = os.pathsep.join(ldpath)

        if GetHost() == 'darwin':
            dyldpath.insert(0, os.path.join(builtdir, 'lib'))
            os.environ["DYLD_LIBRARY_PATH"] = os.pathsep.join(dyldpath)

        # Workaround around compile issue on PCBSD
        if (os.path.exists("/usr/PCBSD")):
            os.environ["LD_LIBRARY_PATH"] += os.pathsep + "/usr/PCBSD/local/lib"

########################################################################
##
## Routines to copy files into the build tree
##
########################################################################

def CopyFile(dstfile, srcfile):
    if (dstfile[-1]=='/'):
        dstdir = dstfile
        fnl = srcfile.rfind("/")
        if (fnl < 0): fn = srcfile
        else: fn = srcfile[fnl+1:]
        dstfile = dstdir + fn
    if (NeedsBuild([dstfile], [srcfile])):
        WriteBinaryFile(dstfile, ReadBinaryFile(srcfile))
        JustBuilt([dstfile], [srcfile])

def CopyAllFiles(dstdir, srcdir, suffix=""):
    for x in GetDirectoryContents(srcdir, ["*"+suffix]):
        CopyFile(dstdir + x, srcdir + x)

def CopyAllHeaders(dir, skip=[]):
    for filename in GetDirectoryContents(dir, ["*.h", "*.I", "*.T"], skip):
        srcfile = dir + "/" + filename
        dstfile = OUTPUTDIR + "/include/" + filename
        if (NeedsBuild([dstfile], [srcfile])):
            WriteBinaryFile(dstfile, ReadBinaryFile(srcfile))
            JustBuilt([dstfile], [srcfile])

def CopyAllJavaSources(dir, skip=[]):
    for filename in GetDirectoryContents(dir, ["*.java"], skip):
        srcfile = dir + "/" + filename
        dstfile = OUTPUTDIR + "/src/org/panda3d/android/" + filename
        if (NeedsBuild([dstfile], [srcfile])):
            WriteBinaryFile(dstfile, ReadBinaryFile(srcfile))
            JustBuilt([dstfile], [srcfile])

def CopyTree(dstdir, srcdir, omitCVS=True):
    if (os.path.isdir(dstdir)):
        for entry in os.listdir(srcdir):
            srcpth = os.path.join(srcdir, entry)
            dstpth = os.path.join(dstdir, entry)
            if (os.path.isfile(srcpth)):
                if (not omitCVS or entry != ".cvsignore"):
                    CopyFile(dstpth, srcpth)
            else:
                if (not omitCVS or entry != "CVS"):
                    CopyTree(dstpth, srcpth)
    else:
        if sys.platform == 'win32':
            cmd = 'xcopy /I/Y/E/Q "' + srcdir + '" "' + dstdir + '"'
        else:
            cmd = 'cp -R -f ' + srcdir + ' ' + dstdir
        oscmd(cmd)
        if omitCVS:
            DeleteCVS(dstdir)

def CopyPythonTree(dstdir, srcdir, lib2to3_fixers=[]):
    if (not os.path.isdir(dstdir)):
        os.mkdir(dstdir)

    lib2to3 = None
    if len(lib2to3_fixers) > 0 and sys.version_info >= (3, 0):
        from lib2to3.main import main as lib2to3
        lib2to3_args = ['-w', '-n', '--no-diffs', '-x', 'buffer', '-x', 'idioms', '-x', 'set_literal', '-x', 'ws_comma']
        if lib2to3_fixers != ['all']:
            for fixer in lib2to3_fixers:
                lib2to3_args += ['-f', fixer]

    refactor = []
    for entry in os.listdir(srcdir):
        srcpth = os.path.join(srcdir, entry)
        dstpth = os.path.join(dstdir, entry)
        if (os.path.isfile(srcpth)):
            base, ext = os.path.splitext(entry)
            if (entry != ".cvsignore" and ext not in SUFFIX_INC):
                if (NeedsBuild([dstpth], [srcpth])):
                    WriteBinaryFile(dstpth, ReadBinaryFile(srcpth))

                    if ext == '.py' and not entry.endswith('-extensions.py'):
                        refactor.append((dstpth, srcpth))
                    else:
                        JustBuilt([dstpth], [srcpth])

        elif (entry != "CVS"):
            CopyPythonTree(dstpth, srcpth, lib2to3_fixers)

    for dstpth, srcpth in refactor:
        if lib2to3 is not None:
            ret = lib2to3("lib2to3.fixes", lib2to3_args + [dstpth])
            if ret != 0:
                os.remove(dstpth)
                exit("Error in lib2to3.")
        JustBuilt([dstpth], [srcpth])


########################################################################
##
## Parse PandaVersion.pp to extract the version number.
##
########################################################################

def ParsePandaVersion(fn):
    try:
        f = open(fn, "r")
        pattern = re.compile('^[ \t]*[#][ \t]*define[ \t]+PANDA_VERSION[ \t]+([0-9]+)[ \t]+([0-9]+)[ \t]+([0-9]+)')
        for line in f:
            match = pattern.match(line,0)
            if (match):
                f.close()
                return match.group(1)+"."+match.group(2)+"."+match.group(3)
        f.close()
    except: pass
    return "0.0.0"

def ParsePluginVersion(fn):
    try:
        f = open(fn, "r")
        pattern = re.compile('^[ \t]*[#][ \t]*define[ \t]+P3D_PLUGIN_VERSION[ \t]+([0-9]+)[ \t]+([0-9]+)[ \t]+([0-9]+)')
        for line in f:
            match = pattern.match(line,0)
            if (match):
                f.close()
                return match.group(1)+"."+match.group(2)+"."+match.group(3)
        f.close()
    except: pass
    return "0.0.0"

def ParseCoreapiVersion(fn):
    try:
        f = open(fn, "r")
        pattern = re.compile('^[ \t]*[#][ \t]*define[ \t]+P3D_COREAPI_VERSION.*([0-9]+)[ \t]*$')
        for line in f:
            match = pattern.match(line,0)
            if (match):
                f.close()
                return match.group(1)
        f.close()
    except: pass
    return "0"

##########################################################################################
#
# Utility function to generate a resource file
#
##########################################################################################

RESOURCE_FILE_TEMPLATE = """VS_VERSION_INFO VERSIONINFO
 FILEVERSION %(commaversion)s
 PRODUCTVERSION %(commaversion)s
 FILEFLAGSMASK 0x3fL
 FILEFLAGS %(debugflag)s
 FILEOS 0x40004L
 FILETYPE 0x2L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904e4"
        BEGIN
            VALUE "FileDescription", "%(description)s\\0"
            VALUE "FileVersion", "%(dotversion)s"
            VALUE "LegalTrademarks", "\\0"
            VALUE "MIMEType", "%(mimetype)s\\0"
            VALUE "FileExtents", "%(extension)s\\0"
            VALUE "FileOpenName", "%(filedesc)s\\0"
            VALUE "OLESelfRegister", "\\0"
            VALUE "OriginalFilename", "%(filename)s\\0"
            VALUE "ProductName", "%(name)s %(version)s\\0"
            VALUE "ProductVersion", "%(dotversion)s"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x409, 1252
    END
END
"""

def GenerateResourceFile(**kwargs):
    if "debugflag" not in kwargs:
        if GetOptimize() <= 2:
            kwargs["debugflag"] = "0x1L"
        else:
            kwargs["debugflag"] = "0x0L"
    kwargs["dotversion"] = kwargs["version"]
    if len(kwargs["dotversion"].split(".")) == 3:
        kwargs["dotversion"] += ".0"
    if "commaversion" not in kwargs:
        kwargs["commaversion"] = kwargs["dotversion"].replace(".", ",")

    rcdata = ""
    if not "noinclude" in kwargs:
        rcdata += "#define APSTUDIO_READONLY_SYMBOLS\n"
        rcdata += "#include \"winresrc.h\"\n"
        rcdata += "#undef APSTUDIO_READONLY_SYMBOLS\n"
    rcdata += RESOURCE_FILE_TEMPLATE % kwargs

    if "icon" in kwargs:
        rcdata += "\nICON_FILE       ICON    \"%s\"\n" % kwargs["icon"]

    return rcdata


def WriteResourceFile(basename, **kwargs):
    if not basename.endswith(".rc"):
        basename += ".rc"
    basename = GetOutputDir() + "/include/" + basename
    ConditionalWriteFile(basename, GenerateResourceFile(**kwargs))
    return basename

########################################################################
##
## FindLocation
##
########################################################################

ORIG_EXT={}

def GetOrigExt(x):
    return ORIG_EXT[x]

def SetOrigExt(x, v):
    ORIG_EXT[x] = v

def CalcLocation(fn, ipath):
    if (fn.count("/")): return fn
    dllext = ""
    target = GetTarget()
    if (GetOptimize() <= 2 and target == 'windows'): dllext = "_d"

    if (fn == "PandaModules.py"): return OUTPUTDIR+"/pandac/" + fn
    if (fn == "AndroidManifest.xml"): return OUTPUTDIR+"/"+fn
    if (fn.endswith(".cxx")): return CxxFindSource(fn, ipath)
    if (fn.endswith(".I")):   return CxxFindSource(fn, ipath)
    if (fn.endswith(".h")):   return CxxFindSource(fn, ipath)
    if (fn.endswith(".c")):   return CxxFindSource(fn, ipath)
    if (fn.endswith(".yxx")): return CxxFindSource(fn, ipath)
    if (fn.endswith(".lxx")): return CxxFindSource(fn, ipath)
    if (fn.endswith(".pdef")):return CxxFindSource(fn, ipath)
    if (fn.endswith(".xml")): return CxxFindSource(fn, ipath)
    if (fn.endswith(".egg")): return OUTPUTDIR+"/models/"+fn
    if (fn.endswith(".egg.pz")):return OUTPUTDIR+"/models/"+fn
    if (target == 'windows'):
        if (fn.endswith(".def")):   return CxxFindSource(fn, ipath)
        if (fn.endswith(".rc")):    return CxxFindSource(fn, ipath)
        if (fn.endswith(".idl")):   return CxxFindSource(fn, ipath)
        if (fn.endswith(".obj")):   return OUTPUTDIR+"/tmp/"+fn
        if (fn.endswith(".res")):   return OUTPUTDIR+"/tmp/"+fn
        if (fn.endswith(".tlb")):   return OUTPUTDIR+"/tmp/"+fn
        if (fn.endswith(".dll")):   return OUTPUTDIR+"/bin/"+fn[:-4]+dllext+".dll"
        if (fn.endswith(".pyd")):   return OUTPUTDIR+"/bin/"+fn[:-4]+dllext+".pyd"
        if (fn.endswith(".ocx")):   return OUTPUTDIR+"/plugins/"+fn[:-4]+dllext+".ocx"
        if (fn.endswith(".mll")):   return OUTPUTDIR+"/plugins/"+fn[:-4]+dllext+".mll"
        if (fn.endswith(".dlo")):   return OUTPUTDIR+"/plugins/"+fn[:-4]+dllext+".dlo"
        if (fn.endswith(".dli")):   return OUTPUTDIR+"/plugins/"+fn[:-4]+dllext+".dli"
        if (fn.endswith(".dle")):   return OUTPUTDIR+"/plugins/"+fn[:-4]+dllext+".dle"
        if (fn.endswith(".plugin")):return OUTPUTDIR+"/plugins/"+fn[:-7]+dllext+".dll"
        if (fn.endswith(".exe")):   return OUTPUTDIR+"/bin/"+fn
        if (fn.endswith(".lib")):   return OUTPUTDIR+"/lib/"+fn[:-4]+dllext+".lib"
        if (fn.endswith(".ilb")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+dllext+".lib"
    elif (target == 'darwin'):
        if (fn.endswith(".mm")):    return CxxFindSource(fn, ipath)
        if (fn.endswith(".r")):     return CxxFindSource(fn, ipath)
        if (fn.endswith(".plist")): return CxxFindSource(fn, ipath)
        if (fn.endswith(".obj")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".o"
        if (fn.endswith(".dll")):   return OUTPUTDIR+"/lib/"+fn[:-4]+".dylib"
        if (fn.endswith(".pyd")):   return OUTPUTDIR+"/lib/"+fn[:-4]+".so"
        if (fn.endswith(".mll")):   return OUTPUTDIR+"/plugins/"+fn
        if (fn.endswith(".exe")):   return OUTPUTDIR+"/bin/"+fn[:-4]
        if (fn.endswith(".lib")):   return OUTPUTDIR+"/lib/"+fn[:-4]+".a"
        if (fn.endswith(".ilb")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".a"
        if (fn.endswith(".rsrc")):  return OUTPUTDIR+"/tmp/"+fn
        if (fn.endswith(".plugin")):return OUTPUTDIR+"/plugins/"+fn
        if (fn.endswith(".app")):   return OUTPUTDIR+"/bin/"+fn
    elif (target == 'android'):
        # On Android, we build the libraries into built/tmp, then copy them.
        if (fn.endswith(".obj")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".o"
        if (fn.endswith(".dll")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".so"
        if (fn.endswith(".pyd")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".so"
        if (fn.endswith(".mll")):   return OUTPUTDIR+"/plugins/"+fn
        if (fn.endswith(".plugin")):return OUTPUTDIR+"/plugins/"+fn[:-7]+dllext+".so"
        if (fn.endswith(".exe")):   return OUTPUTDIR+"/tmp/lib"+fn[:-4]+".so"
        if (fn.endswith(".lib")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".a"
        if (fn.endswith(".ilb")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".a"
    else:
        if (fn.endswith(".obj")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".o"
        if (fn.endswith(".dll")):   return OUTPUTDIR+"/lib/"+fn[:-4]+".so"
        if (fn.endswith(".pyd")):   return OUTPUTDIR+"/lib/"+fn[:-4]+".so"
        if (fn.endswith(".mll")):   return OUTPUTDIR+"/plugins/"+fn
        if (fn.endswith(".plugin")):return OUTPUTDIR+"/plugins/"+fn[:-7]+dllext+".so"
        if (fn.endswith(".exe")):   return OUTPUTDIR+"/bin/"+fn[:-4]
        if (fn.endswith(".lib")):   return OUTPUTDIR+"/lib/"+fn[:-4]+".a"
        if (fn.endswith(".ilb")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".a"
    if (fn.endswith(".dat")):   return OUTPUTDIR+"/tmp/"+fn
    if (fn.endswith(".in")):    return OUTPUTDIR+"/pandac/input/"+fn
    return fn


def FindLocation(fn, ipath):
    if (GetLinkAllStatic() and fn.endswith(".dll")):
        fn = fn[:-4] + ".lib"
    loc = CalcLocation(fn, ipath)
    base, ext = os.path.splitext(fn)
    ORIG_EXT[loc] = ext
    return loc

########################################################################
##
## TargetAdd
##
## Makepanda maintains a list of make-targets.  Each target has
## these attributes:
##
## name   - the name of the file being created.
## ext    - the original file extension, prior to OS-specific translation
## inputs - the names of the input files to the compiler
## deps   - other input files that the target also depends on
## opts   - compiler options, a catch-all category
##
## TargetAdd will create the target if it does not exist.  Then,
## depending on what options you pass, it will push data onto these
## various target attributes.  This is cumulative: for example, if
## you use TargetAdd to add compiler options, then use TargetAdd
## again with more compiler options, both sets of options will be
## included.
##
## TargetAdd does some automatic dependency generation on C++ files.
## It will scan these files for include-files and automatically push
## the include files onto the list of dependencies.  In order to do
## this, it needs an include-file search path.  So if you supply
## any C++ input, you also need to supply compiler options containing
## include-directories, or alternately, a separate ipath parameter.
##
## The main body of 'makepanda' is a long list of TargetAdd
## directives building up a giant list of make targets.  Then,
## finally, the targets are run and panda is built.
##
## Makepanda's dependency system does not understand multiple
## outputs from a single build step.  When a build step generates
## a primary output file and a secondary output file, it is
## necessary to trick the dependency system.  Insert a dummy
## build step that "generates" the secondary output file, using
## the primary output file as an input.  There is a special
## compiler option DEPENDENCYONLY that creates such a dummy
## build-step.  There are two cases where dummy build steps must
## be inserted: bison generates an OBJ and a secondary header
## file, interrogate generates an IN and a secondary IGATE.OBJ.
##
########################################################################

class Target:
    pass

TARGET_LIST = []
TARGET_TABLE = {}

def TargetAdd(target, dummy=0, opts=0, input=0, dep=0, ipath=0, winrc=0):
    if (dummy != 0):
        exit("Syntax error in TargetAdd "+target)
    if (ipath == 0): ipath = opts
    if (ipath == 0): ipath = []
    if (type(input) == str): input = [input]
    if (type(dep) == str): dep = [dep]
    full = FindLocation(target, [OUTPUTDIR + "/include"])

    if (full not in TARGET_TABLE):
        t = Target()
        t.name = full
        t.inputs = []
        t.deps = {}
        t.opts = []
        TARGET_TABLE[full] = t
        TARGET_LIST.append(t)
    else:
        t = TARGET_TABLE[full]

    if opts != 0:
        for x in opts:
            if (t.opts.count(x)==0):
                t.opts.append(x)

    ipath = [OUTPUTDIR + "/tmp"] + GetListOption(ipath, "DIR:") + [OUTPUTDIR+"/include"]
    if input != 0:
        for x in input:
            fullinput = FindLocation(x, ipath)
            t.inputs.append(fullinput)
            # Don't re-link a library or binary if just its dependency dlls have been altered.
            # This should work out fine in most cases, and often reduces recompilation time.
            if (os.path.splitext(x)[-1] not in SUFFIX_DLL):
                t.deps[fullinput] = 1
                (base,suffix) = os.path.splitext(x)
                if (SUFFIX_INC.count(suffix)):
                    for d in CxxCalcDependencies(fullinput, ipath, []):
                        t.deps[d] = 1
    if dep != 0:
        for x in dep:
            fulldep = FindLocation(x, ipath)
            t.deps[fulldep] = 1

    if winrc != 0 and GetTarget() == 'windows':
        TargetAdd(target, input=WriteResourceFile(target.split("/")[-1].split(".")[0], **winrc))

    if target.endswith(".in"):
        if not CrossCompiling():
            t.deps[FindLocation("interrogate.exe", [])] = 1
        t.deps[FindLocation("dtool_have_python.dat", [])] = 1

    if target.endswith(".pz") and not CrossCompiling():
        t.deps[FindLocation("pzip.exe", [])] = 1     
