Files
panda3d/makepanda/makepandacore.py
T
2021-12-05 17:13:04 +01:00

3704 lines
133 KiB
Python

########################################################################
##
## This file, makepandacore, contains all the global state and
## global functions for the makepanda system.
##
########################################################################
import configparser
from distutils import sysconfig
import fnmatch
import getpass
import glob
import os
import pickle
import platform
import re
import shutil
import signal
import subprocess
import sys
import threading
import _thread as thread
import time
SUFFIX_INC = [".cxx",".cpp",".c",".h",".I",".yxx",".lxx",".mm",".rc",".r"]
SUFFIX_DLL = [".dll",".dlo",".dle",".dli",".dlm",".mll",".exe",".pyd",".ocx"]
SUFFIX_LIB = [".lib",".ilb"]
VCS_DIRS = set(["CVS", "CVSROOT", ".git", ".hg", "__pycache__"])
VCS_FILES = set([".cvsignore", ".gitignore", ".gitmodules", ".hgignore"])
STARTTIME = time.time()
MAINTHREAD = threading.current_thread()
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
ANDROID_TRIPLE = None
ANDROID_API = None
SYS_LIB_DIRS = []
SYS_INC_DIRS = []
DEBUG_DEPENDENCIES = False
# 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 macOS, platform.architecture reports '64bit' even if it is
# currently running in 32-bit mode. But sys.maxint is a reliable
# indicator.
host_64 = (sys.maxsize > 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')
# On Android, get a list of all the public system libraries.
ANDROID_SYS_LIBS = []
if os.path.exists("/etc/public.libraries.txt"):
for line in open("/etc/public.libraries.txt", "r"):
line = line.strip()
ANDROID_SYS_LIBS.append(line)
########################################################################
##
## Visual C++ Version (MSVC) and Visual Studio Information Map
##
########################################################################
MSVCVERSIONINFO = {
(10,0): {"vsversion":(10,0), "vsname":"Visual Studio 2010"},
(11,0): {"vsversion":(11,0), "vsname":"Visual Studio 2012"},
(12,0): {"vsversion":(12,0), "vsname":"Visual Studio 2013"},
(14,0): {"vsversion":(14,0), "vsname":"Visual Studio 2015"},
(14,1): {"vsversion":(15,0), "vsname":"Visual Studio 2017"},
(14,2): {"vsversion":(16,0), "vsname":"Visual Studio 2019"},
}
########################################################################
##
## 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"),
("MAYA2015","2015"),
("MAYA2016","2016"),
("MAYA20165","2016.5"),
("MAYA2017","2017"),
("MAYA2018","2018"),
("MAYA2019","2019"),
("MAYA2020","2020"),
]
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 = ["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",
"contrib/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):
Warn("file may conflict with build:", cfile)
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 is None:
SETF = curses.tigetstr("setaf")
assert SETF is not 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 is not 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"))
return token.decode('ascii')
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):
sys.stdout.flush()
sys.stderr.flush()
prefix = ""
thisthread = threading.current_thread()
if thisthread 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 = thread.get_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)))
sys.stdout.flush()
sys.stderr.flush()
def exit(msg = ""):
sys.stdout.flush()
sys.stderr.flush()
if threading.current_thread() == 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"
def Warn(msg, extra=None):
if extra is not None:
print("%sWARNING:%s %s %s%s%s" % (GetColor("red"), GetColor(), msg, GetColor("green"), extra, GetColor()))
else:
print("%sWARNING:%s %s" % (GetColor("red"), GetColor(), msg))
sys.stdout.flush()
def Error(msg, extra=None):
if extra is not None:
print("%sERROR:%s %s %s%s%s" % (GetColor("red"), GetColor(), msg, GetColor("green"), extra, GetColor()))
else:
print("%sERROR:%s %s" % (GetColor("red"), GetColor(), msg))
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'):
try:
# Python seems to offer no built-in way to check this.
osname = subprocess.check_output(["uname", "-o"])
if osname.strip().lower() == b'android':
return 'android'
else:
return 'linux'
except:
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'
machine = platform.machine()
if machine.startswith('armv7'):
return 'armv7a'
else:
return 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 == 'aarch64':
arch = 'arm64'
if arch is not None:
choices = ('i386', 'x86_64', 'ppc', 'ppc64', 'arm64')
if arch not in choices:
exit('macOS architecture must be one of %s' % (', '.join(choices)))
elif target == 'android' or target.startswith('android-'):
if arch is None:
# If compiling on Android, default to same architecture. Otherwise, arm.
if host == 'android':
arch = host_arch
else:
arch = 'armv7a'
if arch == 'aarch64':
arch = 'arm64'
# Did we specify an API level?
global ANDROID_API
target, _, api = target.partition('-')
if api:
ANDROID_API = int(api)
elif arch in ('mips64', 'arm64', 'x86_64'):
# 64-bit platforms were introduced in Android 21.
ANDROID_API = 21
else:
# Default to the lowest API level still supported by Google.
ANDROID_API = 19
# Determine the prefix for our gcc tools, eg. arm-linux-androideabi-gcc
global ANDROID_ABI, ANDROID_TRIPLE
if arch == 'armv7a':
ANDROID_ABI = 'armeabi-v7a'
ANDROID_TRIPLE = 'arm-linux-androideabi'
elif arch == 'arm':
ANDROID_ABI = 'armeabi'
ANDROID_TRIPLE = 'arm-linux-androideabi'
elif arch == 'arm64':
ANDROID_ABI = 'arm64-v8a'
ANDROID_TRIPLE = 'aarch64-linux-android'
elif arch == 'mips':
ANDROID_ABI = 'mips'
ANDROID_TRIPLE = 'mipsel-linux-android'
elif arch == 'mips64':
ANDROID_ABI = 'mips64'
ANDROID_TRIPLE = 'mips64el-linux-android'
elif arch == 'x86':
ANDROID_ABI = 'x86'
ANDROID_TRIPLE = 'i686-linux-android'
elif arch == 'x86_64':
ANDROID_ABI = 'x86_64'
ANDROID_TRIPLE = 'x86_64-linux-android'
else:
exit('Android architecture must be arm, armv7a, arm64, mips, mips64, x86 or x86_64')
ANDROID_TRIPLE += str(ANDROID_API)
TOOLCHAIN_PREFIX = ANDROID_TRIPLE + '-'
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():
if TARGET in ('darwin', 'freebsd', 'android'):
return os.environ.get('CC', TOOLCHAIN_PREFIX + 'clang')
else:
return os.environ.get('CC', TOOLCHAIN_PREFIX + 'gcc')
def GetCXX():
if TARGET in ('darwin', 'freebsd', 'android'):
return os.environ.get('CXX', TOOLCHAIN_PREFIX + 'clang++')
else:
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
# We now require a newer version of Bison than the one we previously
# shipped in the win-util dir. The new version has a 'data'
# subdirectory, so check for that.
win_util_data = os.path.join(GetThirdpartyBase(), 'win-util', 'data')
if GetHost() == 'windows' and os.path.isdir(win_util_data):
BISON = os.path.join(GetThirdpartyBase(), 'win-util', 'bison.exe')
elif LocateBinary('bison'):
BISON = 'bison'
else:
# We don't strictly need it, so don't give an error
return None
return BISON
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
return FLEX
def GetFlexVersion():
flex = GetFlex()
if not flex:
return (0, 0, 0)
try:
handle = subprocess.Popen(["flex", "--version"], executable=flex, stdout=subprocess.PIPE)
words = handle.communicate()[0].strip().splitlines()[0].split(b' ')
if words[1] != "version":
version = words[1]
else:
version = words[2]
return tuple(map(int, version.split(b'.')))
except:
Warn("Unable to detect flex version")
return (0, 0, 0)
########################################################################
##
## 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)
suffixes = ['']
if GetHost() == 'windows':
if not binary.lower().endswith('.exe') and not binary.lower().endswith('.bat'):
# Append .exe if necessary
suffixes = ['.exe', '.bat']
# 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)
for suffix in suffixes:
if os.access(binpath + suffix, os.X_OK):
return os.path.abspath(os.path.realpath(binpath + suffix))
return None
########################################################################
##
## Run a command.
##
########################################################################
def oscmd(cmd, ignoreError = False, cwd=None):
if VERBOSE:
print(GetColor("blue") + cmd.split(" ", 1)[0] + " " + GetColor("magenta") + cmd.split(" ", 1)[1] + GetColor())
sys.stdout.flush()
if sys.platform == "win32":
if cmd[0] == '"':
exe = cmd[1 : cmd.index('"', 1)]
else:
exe = cmd.split()[0]
exe_path = LocateBinary(exe)
if exe_path is None:
exit("Cannot find "+exe+" on search path")
if cwd is not None:
pwd = os.getcwd()
os.chdir(cwd)
res = os.spawnl(os.P_WAIT, exe_path, cmd)
if cwd is not None:
os.chdir(pwd)
else:
cmd = cmd.replace(';', '\\;')
cmd = cmd.replace('$', '\\$')
res = subprocess.call(cmd, cwd=cwd, shell=True)
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..."))
sys.stdout.flush()
verbose_cmd = cmd.split(" ", 1)[0] + " -vv " + cmd.split(" ", 1)[1]
if sys.platform == "win32":
os.spawnl(os.P_WAIT, exe_path, verbose_cmd)
else:
subprocess.call(verbose_cmd, shell=True)
exit("The following command returned a non-zero value: " + str(cmd))
return res
########################################################################
##
## GetDirectoryContents
##
########################################################################
def GetDirectoryContents(dir, filters="*", skip=[]):
if isinstance(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
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):
if DEBUG_DEPENDENCIES:
print("rebuilding %s because it does not exist" % (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
elif DEBUG_DEPENDENCIES:
print("rebuilding %s because:" % (key))
for key in frozenset(cached.keys()) | frozenset(dates.keys()):
if key not in cached:
print(" new dependency: %s" % (key))
elif key not in dates:
print(" removed dependency: %s" % (key))
elif cached[key] != dates[key]:
print(" dependency changed: %s" % (key))
if VERBOSE and frozenset(cached) != frozenset(dates):
Warn("file dependencies changed:", files)
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 = {}
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 = []
try:
for line in sfile:
match = CxxIncludeRegex.match(line,0)
if match:
incname = match.group(1)
include.append(incname)
except:
print("Failed to determine dependencies of \""+path+"\".")
raise
sfile.close()
CXXINCLUDECACHE[path] = [date, include]
return include
JAVAIMPORTCACHE = {}
JavaImportRegex = re.compile('[ \t\r\n;]import[ \t]+([a-zA-Z][^;]+)[ \t\r\n]*;')
def JavaGetImports(path):
date = GetTimestamp(path)
if path in JAVAIMPORTCACHE:
cached = JAVAIMPORTCACHE[path]
if cached[0] == date:
return cached[1]
try:
source = open(path, 'r').read()
except:
exit("Cannot open source file \"" + path + "\" for reading.")
imports = []
try:
for match in JavaImportRegex.finditer(source, 0):
impname = match.group(1)
imports.append(impname.strip())
except:
print("Failed to determine dependencies of \"" + path +"\".")
raise
JAVAIMPORTCACHE[path] = [date, imports]
return imports
########################################################################
##
## 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
def JavaFindClasses(impspec, clspath):
path = clspath + '/' + impspec.replace('.', '/') + '.class'
if '*' in path:
return glob.glob(path)
else:
return [path]
########################################################################
##
## 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.
##
########################################################################
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
global JavaDependencyCache
JavaDependencyCache = {}
def JavaCalcDependencies(srcfile, clspath):
if srcfile in JavaDependencyCache:
return JavaDependencyCache[srcfile]
deps = set((srcfile,))
JavaDependencyCache[srcfile] = deps
imports = JavaGetImports(srcfile)
for impspec in imports:
for cls in JavaFindClasses(impspec, clspath):
deps.add(cls)
return deps
########################################################################
##
## 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.
import 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
def GetProgramFiles_x86():
if ("ProgramFiles(x86)" in os.environ):
return os.environ["ProgramFiles(x86)"]
elif (os.path.isdir("C:\\Program Files (x86)")):
return "C:\\Program Files (x86)"
elif (os.path.isdir("D:\\Program Files (x86)")):
return "D:\\Program Files (x86)"
elif (os.path.isdir("E:\\Program Files (x86)")):
return "E:\\Program Files (x86)"
return GetProgramFiles()
########################################################################
##
## 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, mode=None, recursive=False):
if os.path.isdir(path):
return
if recursive:
parent = os.path.dirname(path)
if parent and not os.path.isdir(parent):
MakeDirectory(parent, mode=mode, recursive=True)
if mode is not None:
os.mkdir(path, mode)
else:
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, newline=None):
if newline is not None:
data = data.replace('\r\n', '\n')
data = data.replace('\r', '\n')
data = data.replace('\n', newline)
try:
dsthandle = open(wfile, "w", newline='')
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, data, newline=None):
if newline is not None:
data = data.replace('\r\n', '\n')
data = data.replace('\r', '\n')
data = data.replace('\n', newline)
try:
rfile = open(dest, 'r')
contents = rfile.read(-1)
rfile.close()
except:
contents = 0
if contents != data:
if VERBOSE:
print("Writing %s" % (dest))
sys.stdout.flush()
WriteFile(dest, data)
def DeleteVCS(dir):
if dir == "": dir = "."
for entry in os.listdir(dir):
subdir = os.path.join(dir, entry)
if (os.path.isdir(subdir)):
if entry in VCS_DIRS:
shutil.rmtree(subdir)
else:
DeleteVCS(subdir)
elif (os.path.isfile(subdir) and (entry in VCS_FILES 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")
MakeDirectory(OUTPUTDIR + "/panda3d")
if GetTarget() == 'android':
MakeDirectory(OUTPUTDIR + "/classes")
########################################################################
#
# 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 is not 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 is not None:
return THIRDPARTYDIR
base = GetThirdpartyBase()
target = GetTarget()
target_arch = GetTargetArch()
if (target == 'windows'):
vc = str(SDK["MSVC_VERSION"][0])
if target_arch == 'x64':
THIRDPARTYDIR = base + "/win-libs-vc" + vc + "-x64/"
else:
THIRDPARTYDIR = base + "/win-libs-vc" + vc + "/"
elif (target == 'darwin'):
# macOS thirdparty binaries are universal, where possible.
THIRDPARTYDIR = base + "/darwin-libs-a/"
elif (target == 'linux'):
if target_arch in ("aarch64", "arm64"):
THIRDPARTYDIR = base + "/linux-libs-arm64/"
elif 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 in ("aarch64", "arm64"):
THIRDPARTYDIR = base + "/freebsd-libs-arm64/"
elif 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 = base + "/android-libs-%s/" % (target_arch)
else:
Warn("Unsupported platform:", 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 SetDebugDependencies(dd = True):
global DEBUG_DEPENDENCIES
DEBUG_DEPENDENCIES = dd
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 = {}
PKG_LIST_CUSTOM = set()
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 PkgSetCustomLocation(pkg):
PKG_LIST_CUSTOM.add(pkg)
def PkgHasCustomLocation(pkg):
return pkg in PKG_LIST_CUSTOM
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 CrossCompiling() 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) is not None)
result = handle.read().strip()
returnval = handle.close()
if returnval is not 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 CrossCompiling() 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 CrossCompiling() 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()
handle.close()
if len(result) == 0: return []
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 CrossCompiling() 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 CrossCompiling() 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 d[0] in ('NDEBUG', '_DEBUG'):
# Setting one of these flags by accident could cause serious harm.
if GetVerbose():
print("Ignoring %s flag provided by %s" % (l, tool))
elif 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 LocateLibrary(lib, lpath=[], prefer_static=False):
"""Searches for the library in the search path, returning its path if found,
or None if it was not found."""
target = GetTarget()
if prefer_static and target != 'windows':
for dir in lpath:
if os.path.isfile(os.path.join(dir, 'lib%s.a' % lib)):
return os.path.join(dir, 'lib%s.a' % lib)
for dir in lpath:
if target == 'darwin' and os.path.isfile(os.path.join(dir, 'lib%s.dylib' % lib)):
return os.path.join(dir, 'lib%s.dylib' % lib)
elif target != 'darwin' and os.path.isfile(os.path.join(dir, 'lib%s.so' % lib)):
return os.path.join(dir, 'lib%s.so' % lib)
elif os.path.isfile(os.path.join(dir, 'lib%s.a' % lib)):
return os.path.join(dir, 'lib%s.a' % lib)
return None
def SystemLibraryExists(lib):
result = LocateLibrary(lib, SYS_LIB_DIRS)
if result is not None:
return True
if GetHost() == "android" and GetTarget() == "android":
return ('lib%s.so' % lib) in ANDROID_SYS_LIBS
return False
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 LocateLibrary(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", thirdparty_dir = None):
global PKG_LIST_ALL
if (pkg in PkgListGet() and PkgSkip(pkg)):
return
if (target_pkg == "" or target_pkg is None):
target_pkg = pkg
if (pkgconfig == ""):
pkgconfig = None
if (framework == ""):
framework = None
if (libs is None or libs == ""):
libs = ()
elif (isinstance(libs, str)):
libs = (libs, )
if (incs is None or incs == ""):
incs = ()
elif (isinstance(incs, str)):
incs = (incs, )
if (defs is 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] = ""
custom_loc = PkgHasCustomLocation(pkg)
# Determine the location of the thirdparty directory.
if not thirdparty_dir:
thirdparty_dir = pkg.lower()
pkg_dir = os.path.join(GetThirdpartyDir(), thirdparty_dir)
# First check if the library can be found in the thirdparty directory.
if not custom_loc and os.path.isdir(pkg_dir):
if framework and os.path.isdir(os.path.join(pkg_dir, framework + ".framework")):
FrameworkDirectory(target_pkg, pkg_dir)
LibName(target_pkg, "-framework " + framework)
return
inc_dir = os.path.join(pkg_dir, "include")
if os.path.isdir(inc_dir):
IncDirectory(target_pkg, inc_dir)
# Handle cases like freetype2 where the include dir is a subdir under "include"
for i in incs:
if os.path.isdir(os.path.join(inc_dir, i)):
IncDirectory(target_pkg, os.path.join(inc_dir, i))
lib_dir = os.path.join(pkg_dir, "lib")
lpath = [lib_dir]
if not PkgSkip("PYTHON"):
py_lib_dir = os.path.join(lib_dir, SDK["PYTHONVERSION"])
if os.path.isdir(py_lib_dir):
lpath.append(py_lib_dir)
# TODO: check for a .pc file in the lib/pkgconfig/ dir
if (tool is not 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):
if i.startswith('-l'):
# To make sure we don't pick up the system copy, write out
# the full path instead.
libname = i[2:]
location = LocateLibrary(libname, lpath, prefer_static=True)
if location is not None:
LibName(target_pkg, location)
else:
print(GetColor("cyan") + "Couldn't find library lib" + libname + " in thirdparty directory " + pkg.lower() + GetColor())
LibName(target_pkg, i)
else:
LibName(target_pkg, i)
for i, j in PkgConfigGetDefSymbols(None, tool).items():
DefSymbol(target_pkg, i, j)
return
# Now search for the libraries in the package's lib directories.
for l in libs:
libname = l
if l.startswith("lib"):
libname = l[3:]
location = LocateLibrary(libname, lpath, prefer_static=True)
if location is not None:
# If it's a .so or .dylib we may have changed it and copied it to the built/lib dir.
if location.endswith('.so') or location.endswith('.dylib'):
location = os.path.join(GetOutputDir(), "lib", os.path.basename(location))
LibName(target_pkg, location)
else:
# This is for backward compatibility - in the thirdparty dir,
# we kept some libs with "panda" prefix, like libpandatiff.
location = LocateLibrary("panda" + libname, lpath, prefer_static=True)
if location is not None:
if location.endswith('.so') or location.endswith('.dylib'):
location = os.path.join(GetOutputDir(), "lib", os.path.basename(location))
LibName(target_pkg, location)
else:
print(GetColor("cyan") + "Couldn't find library lib" + libname + " in thirdparty directory " + thirdparty_dir + GetColor())
for d, v in defs.values():
DefSymbol(target_pkg, d, v)
return
elif not custom_loc and GetHost() == "darwin" and framework is not None:
prefix = SDK["MACOSX"]
if (os.path.isdir(prefix + "/Library/Frameworks/%s.framework" % framework) or
os.path.isdir(prefix + "/System/Library/Frameworks/%s.framework" % framework) or
os.path.isdir(prefix + "/Developer/Library/Frameworks/%s.framework" % framework) or
os.path.isdir(prefix + "/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)
return
elif VERBOSE:
print(ColorText("cyan", "Couldn't find the framework %s" % (framework)))
elif not custom_loc and LocateBinary(tool) is not None and (tool != "pkg-config" or pkgconfig is not 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 not custom_loc and pkgconfig is not None and not libs:
# pkg-config is all we can do, abort if it wasn't found.
if pkg in PkgListGet():
Warn("Could not locate pkg-config package %s, excluding from build" % (pkgconfig))
PkgDisable(pkg)
else:
Error("Could not locate pkg-config package %s, aborting build" % (pkgconfig))
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 custom_loc:
# Try searching in the package's LibDirectories.
lpath = [dir for ppkg, dir in LIBDIRECTORIES if pkg == ppkg]
location = LocateLibrary(libname, lpath)
if location is not None:
LibName(target_pkg, location)
else:
have_pkg = False
print(GetColor("cyan") + "Couldn't find library lib" + libname + GetColor())
elif SystemLibraryExists(libname):
# It exists in a system library directory.
LibName(target_pkg, "-l" + libname)
else:
# Try searching in the package's LibDirectories.
lpath = [dir for ppkg, dir in LIBDIRECTORIES if pkg == ppkg or ppkg == "ALWAYS"]
location = LocateLibrary(libname, lpath)
if location is not None:
LibName(target_pkg, "-l" + libname)
else:
have_pkg = False
if VERBOSE or custom_loc:
print(GetColor("cyan") + "Couldn't find library lib" + libname + GetColor())
# Determine which include directories to look in.
incdirs = []
if not custom_loc:
incdirs += list(SYS_INC_DIRS)
for ppkg, pdir in INCDIRECTORIES[:]:
if pkg == ppkg or (ppkg == "ALWAYS" and not custom_loc):
incdirs.append(pdir)
if custom_loc and pkg != target_pkg:
IncDirectory(target_pkg, pdir)
# The incs list contains both subdirectories to explicitly add to
# the include path and header files to check the existence of.
for i in incs:
incdir = None
for dir in incdirs:
if len(glob.glob(os.path.join(dir, i))) > 0:
incdir = sorted(glob.glob(os.path.join(dir, i)))[-1]
# Note: It's possible to specify a file instead of a dir, for the sake of checking if it exists.
if incdir is None and (i.endswith('/Dense') or i.endswith(".h")):
have_pkg = False
if VERBOSE or custom_loc:
print(GetColor("cyan") + "Couldn't find header file " + i + GetColor())
if incdir is not None and os.path.isdir(incdir):
IncDirectory(target_pkg, incdir)
if not have_pkg:
if custom_loc:
Error("Could not locate thirdparty package %s in specified directory, aborting build" % (pkg.lower()))
elif pkg in PkgListGet():
Warn("Could not locate thirdparty package %s, excluding from build" % (pkg.lower()))
PkgDisable(pkg)
else:
Error("Could not locate thirdparty package %s, aborting build" % (pkg.lower()))
########################################################################
##
## 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("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("/")
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("/")
if ("DX9" not in SDK):
dir = "C:/Program Files (x86)/Microsoft DirectX SDK (June 2010)"
if os.path.isdir(dir):
print("Using DirectX SDK June 2010")
SDK["DX9"] = dir
if ("DX9" not in SDK):
dir = "C:/Program Files/Microsoft DirectX SDK (June 2010)"
if os.path.isdir(dir):
print("Using DirectX SDK June 2010")
SDK["DX9"] = dir
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("/")
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("/")
archStr = GetTargetArch()
if ("DX9" 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 (("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):
SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
if ("DX9" not in SDK):
dir = GetRegistryKey("SOFTWARE\\Microsoft\\DirectX\\Microsoft DirectX SDK (June 2010)", "InstallPath")
if (dir != 0):
SDK["DX9"] = dir.replace("\\", "/").rstrip("/")
if ("DX9" not in SDK):
dir = "C:/Program Files (x86)/Microsoft DirectX SDK (June 2010)"
if os.path.isdir(dir):
SDK["DX9"] = dir
if ("DX9" not in SDK):
dir = "C:/Program Files/Microsoft DirectX SDK (June 2010)"
if os.path.isdir(dir):
SDK["DX9"] = dir
if ("DX9" not in SDK):
exit("Couldn't find DirectX June2010 SDK")
else:
print("Found DirectX SDK June 2010")
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("/")
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("/")
if ("DX9" not in SDK):
exit("Couldn't find DirectX Aug 2009 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(prefer_thirdparty_python=False):
if PkgSkip("PYTHON"):
# We're not compiling with Python support. We still need to set this
# in case we want to run any scripts that use Python, though.
SDK["PYTHONEXEC"] = os.path.realpath(sys.executable)
return
abiflags = getattr(sys, 'abiflags', '')
if GetTarget() == 'windows':
if PkgHasCustomLocation("PYTHON"):
# Check our custom location instead (--python-libdir, --python-incdir)
sdkdir = FindOptDirectory("PYTHON")
if sdkdir is None:
exit("Could not find a Python installation using these command line options.")
else:
sdkdir = GetThirdpartyBase() + "/win-python"
sdkdir += "%d.%d" % sys.version_info[:2]
if GetOptimize() <= 2:
sdkdir += "-dbg"
if GetTargetArch() == 'x64':
sdkdir += "-x64"
sdkdir = sdkdir.replace('\\', '/')
SDK["PYTHON"] = sdkdir
SDK["PYTHONEXEC"] = SDK["PYTHON"] + "/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.
if (GetOptimize() <= 2):
py_dlls = glob.glob(SDK["PYTHON"] + "/python[0-9][0-9]_d.dll") + \
glob.glob(SDK["PYTHON"] + "/python[0-9][0-9][0-9]_d.dll")
else:
py_dlls = glob.glob(SDK["PYTHON"] + "/python[0-9][0-9].dll") + \
glob.glob(SDK["PYTHON"] + "/python[0-9][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"]))
py_dll = os.path.basename(py_dlls[0])
py_dllver = py_dll.strip(".DHLNOPTY_dhlnopty")
ver = py_dllver[0] + '.' + py_dllver[1:]
SDK["PYTHONVERSION"] = "python" + ver
os.environ["PYTHONHOME"] = SDK["PYTHON"]
running_ver = '%d.%d' % sys.version_info[:2]
if ver != running_ver:
Warn("running makepanda with Python %s, but building Panda3D with Python %s." % (running_ver, ver))
elif CrossCompiling() or (prefer_thirdparty_python and os.path.isdir(os.path.join(GetThirdpartyDir(), "python"))):
tp_python = os.path.join(GetThirdpartyDir(), "python")
if GetTarget() == 'darwin':
py_libs = glob.glob(tp_python + "/lib/libpython[0-9].[0-9].dylib") + \
glob.glob(tp_python + "/lib/libpython[0-9].[0-9][0-9].dylib")
else:
py_libs = glob.glob(tp_python + "/lib/libpython[0-9].[0-9].so") + \
glob.glob(tp_python + "/lib/libpython[0-9].[0-9][0-9].so")
if len(py_libs) == 0:
py_libs = glob.glob(tp_python + "/lib/libpython[0-9].[0-9].a") + \
glob.glob(tp_python + "/lib/libpython[0-9].[0-9][0-9].a")
if len(py_libs) == 0:
exit("Could not find the Python library in %s." % (tp_python))
elif len(py_libs) > 1:
exit("Found multiple Python libraries in %s." % (tp_python))
py_lib = os.path.basename(py_libs[0])
py_libver = py_lib.strip('.abdhilnopsty')
SDK["PYTHONVERSION"] = "python" + py_libver
SDK["PYTHONEXEC"] = tp_python + "/bin/" + SDK["PYTHONVERSION"]
SDK["PYTHON"] = tp_python + "/include/" + SDK["PYTHONVERSION"]
elif GetTarget() == 'darwin' and not PkgHasCustomLocation("PYTHON"):
# On macOS, search for the Python framework directory matching the
# version number of our current Python version.
sysroot = SDK.get("MACOSX", "")
version = sysconfig.get_python_version()
py_fwx = "{0}/System/Library/Frameworks/Python.framework/Versions/{1}".format(sysroot, version)
if not os.path.exists(py_fwx):
# Fall back to looking on the system.
py_fwx = "/Library/Frameworks/Python.framework/Versions/" + version
if not os.path.exists(py_fwx):
exit("Could not locate Python installation at %s" % (py_fwx))
SDK["PYTHON"] = py_fwx + "/Headers"
SDK["PYTHONVERSION"] = "python" + version + abiflags
SDK["PYTHONEXEC"] = py_fwx + "/bin/python" + version
# Avoid choosing the one in the thirdparty package dir.
PkgSetCustomLocation("PYTHON")
IncDirectory("PYTHON", py_fwx + "/include")
LibDirectory("PYTHON", py_fwx + "/lib")
#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() + abiflags
SDK["PYTHONEXEC"] = os.path.realpath(sys.executable)
if CrossCompiling():
# We need a version of Python we can run.
SDK["PYTHONEXEC"] = sys.executable
host_version = "python" + sysconfig.get_python_version() + abiflags
if SDK["PYTHONVERSION"] != host_version:
exit("Host Python version (%s) must be the same as target Python version (%s)!" % (host_version, SDK["PYTHONVERSION"]))
if GetVerbose():
print("Using Python %s build located at %s" % (SDK["PYTHONVERSION"][6:], SDK["PYTHON"]))
else:
print("Using Python %s" % (SDK["PYTHONVERSION"][6:]))
def SdkLocateVisualStudio(version=(10,0)):
if (GetHost() != "windows"): return
try:
msvcinfo = MSVCVERSIONINFO[version]
except:
exit("Couldn't get Visual Studio infomation with MSVC %s.%s version." % version)
vsversion = msvcinfo["vsversion"]
vsversion_str = "%s.%s" % vsversion
version_str = "%s.%s" % version
# try to use vswhere.exe
vswhere_path = LocateBinary("vswhere.exe")
if not vswhere_path:
if sys.platform == 'cygwin':
vswhere_path = "/cygdrive/c/Program Files/Microsoft Visual Studio/Installer/vswhere.exe"
else:
vswhere_path = "%s\\Microsoft Visual Studio\\Installer\\vswhere.exe" % GetProgramFiles()
if not os.path.isfile(vswhere_path):
vswhere_path = None
if not vswhere_path:
if sys.platform == 'cygwin':
vswhere_path = "/cygdrive/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
else:
vswhere_path = "%s\\Microsoft Visual Studio\\Installer\\vswhere.exe" % GetProgramFiles_x86()
if not os.path.isfile(vswhere_path):
vswhere_path = None
vsdir = 0
if vswhere_path:
min_vsversion = vsversion_str
max_vsversion = "%s.%s" % (vsversion[0]+1, 0)
vswhere_cmd = ["vswhere.exe", "-legacy", "-property", "installationPath",
"-version", "[{},{})".format(min_vsversion, max_vsversion)]
handle = subprocess.Popen(vswhere_cmd, executable=vswhere_path, stdout=subprocess.PIPE)
found_paths = handle.communicate()[0].splitlines()
if found_paths:
vsdir = found_paths[0].decode("utf-8") + "\\"
# try to use registry
if (vsdir == 0):
vsdir = GetRegistryKey("SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", vsversion_str)
vcdir = GetRegistryKey("SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VC7", version_str)
if (vsdir != 0):
SDK["VISUALSTUDIO"] = vsdir
elif (vcdir != 0) and (vcdir[-4:] == "\\VC\\"):
vcdir = vcdir[:-3]
SDK["VISUALSTUDIO"] = vcdir
elif (os.path.isfile("C:\\Program Files\\Microsoft Visual Studio %s\\VC\\bin\\cl.exe" % (vsversion_str))):
SDK["VISUALSTUDIO"] = "C:\\Program Files\\Microsoft Visual Studio %s\\" % (vsversion_str)
elif (os.path.isfile("C:\\Program Files (x86)\\Microsoft Visual Studio %s\\VC\\bin\\cl.exe" % (vsversion_str))):
SDK["VISUALSTUDIO"] = "C:\\Program Files (x86)\\Microsoft Visual Studio %s\\" % (vsversion_str)
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
else:
exit("Couldn't find %s. To use a different version, use the --msvc-version option." % msvcinfo["vsname"])
SDK["MSVC_VERSION"] = version
SDK["VISUALSTUDIO_VERSION"] = vsversion
if GetVerbose():
print("Using %s located at %s" % (msvcinfo["vsname"], SDK["VISUALSTUDIO"]))
else:
print("Using %s" % (msvcinfo["vsname"]))
print("Using MSVC %s" % version_str)
def SdkLocateWindows(version=None):
if GetTarget() != "windows" or GetHost() != "windows":
return
if version:
version = version.upper()
if version == '10':
version = '10.0'
if version and version.startswith('10.') and version.count('.') == 1:
# Choose the latest version of the Windows 10 SDK.
platsdk = GetRegistryKey("SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot10")
# Fallback in case we can't read the registry.
if not platsdk or not os.path.isdir(platsdk):
platsdk = "C:\\Program Files (x86)\\Windows Kits\\10\\"
if platsdk and os.path.isdir(platsdk):
incdirs = glob.glob(os.path.join(platsdk, 'Include', version + '.*.*'))
max_version = ()
for dir in incdirs:
verstring = os.path.basename(dir)
# Check that the important include directories exist.
if not os.path.isdir(os.path.join(dir, 'ucrt')):
continue
if not os.path.isdir(os.path.join(dir, 'shared')):
continue
if not os.path.isdir(os.path.join(dir, 'um')):
continue
if not os.path.isdir(os.path.join(platsdk, 'Lib', verstring, 'ucrt')):
continue
if not os.path.isdir(os.path.join(platsdk, 'Lib', verstring, 'um')):
continue
vertuple = tuple(map(int, verstring.split('.')))
if vertuple > max_version:
version = verstring
max_version = vertuple
if not max_version:
# No suitable version found.
platsdk = None
elif version and version.startswith('10.'):
# We chose a specific version of the Windows 10 SDK. Verify it exists.
platsdk = GetRegistryKey("SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot10")
# Fallback in case we can't read the registry.
if not platsdk or not os.path.isdir(platsdk):
platsdk = "C:\\Program Files (x86)\\Windows Kits\\10\\"
if version.count('.') == 2:
version += '.0'
if platsdk and not os.path.isdir(os.path.join(platsdk, 'Include', version)):
platsdk = None
elif version == '8.1' or not version:
platsdk = GetRegistryKey("SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot81")
# Fallback in case we can't read the registry.
if not platsdk or not os.path.isdir(platsdk):
platsdk = "C:\\Program Files (x86)\\Windows Kits\\8.1\\"
if not version:
if not os.path.isdir(platsdk):
# Fall back to 7.1 SDK.
return SdkLocateWindows("7.1")
version = '8.1'
elif version == '8.0':
platsdk = GetRegistryKey("SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot")
else:
platsdk = GetRegistryKey("SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v" + version, "InstallationFolder")
DefSymbol("ALWAYS", "_USING_V110_SDK71_")
if not platsdk or not os.path.isdir(platsdk):
# Most common location. Worth a try.
platsdk = GetProgramFiles() + "\\Microsoft SDKs\\Windows\\v" + version
if not os.path.isdir(platsdk):
if not version.endswith('A'):
# Try the stripped-down version that is bundled with Visual Studio.
return SdkLocateWindows(version + 'A')
platsdk = None
if not platsdk or not os.path.isdir(platsdk):
exit("Couldn't find Windows SDK version %s. To use a different version, use the --windows-sdk option." % (version))
if not platsdk.endswith("\\"):
platsdk += "\\"
SDK["MSPLATFORM"] = platsdk
SDK["MSPLATFORM_VERSION"] = version
if GetVerbose():
print("Using Windows SDK %s located at %s" % (version, platsdk))
else:
print("Using Windows SDK %s" % (version))
def SdkLocateMacOSX(archs = []):
if (GetHost() != "darwin"): return
handle = os.popen("xcode-select -print-path")
xcode_dir = handle.read().strip().rstrip('/')
handle.close()
# Make a list of SDK versions that will work for us, then grab the latest.
sdk_versions = []
if 'arm64' not in archs:
# Prefer pre-10.14 for now so that we can keep building FMOD.
sdk_versions += ["10.13", "10.12", "10.11", "10.10", "10.9"]
sdk_versions += ["11.1", "11.0"]
if 'arm64' not in archs:
sdk_versions += ["10.15", "10.14"]
for version in sdk_versions:
sdkname = "MacOSX" + version
if os.path.exists("/Library/Developer/CommandLineTools/SDKs/%s.sdk" % sdkname):
SDK["MACOSX"] = "/Library/Developer/CommandLineTools/SDKs/%s.sdk" % sdkname
return
elif os.path.exists("/Developer/SDKs/%s.sdk" % sdkname):
SDK["MACOSX"] = "/Developer/SDKs/%s.sdk" % sdkname
return
elif os.path.exists("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % sdkname):
SDK["MACOSX"] = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % sdkname
return
elif xcode_dir and os.path.exists("%s/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % (xcode_dir, sdkname)):
SDK["MACOSX"] = "%s/Platforms/MacOSX.platform/Developer/SDKs/%s.sdk" % (xcode_dir, sdkname)
return
exit("Couldn't find any suitable MacOSX SDK!")
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."""
global TOOLCHAIN_PREFIX
if GetTarget() != 'android':
return
# Allow ANDROID_API/ANDROID_ABI to be used in makepanda.py.
if ANDROID_API is None:
SetTarget('android')
api = ANDROID_API
SDK["ANDROID_API"] = api
abi = ANDROID_ABI
SDK["ANDROID_ABI"] = abi
SDK["ANDROID_TRIPLE"] = ANDROID_TRIPLE
if GetHost() == 'android':
# Assume we're compiling from termux.
prefix = os.environ.get("PREFIX", "/data/data/com.termux/files/usr")
SDK["ANDROID_JAR"] = prefix + "/share/aapt/android.jar"
return
# Find the location of the Android SDK.
sdk_root = os.environ.get('ANDROID_HOME')
if not sdk_root or not os.path.isdir(sdk_root):
sdk_root = os.environ.get('ANDROID_SDK_ROOT')
# Try the default installation location on Windows.
if not sdk_root and GetHost() == 'windows':
sdk_root = os.path.expanduser(os.path.join('~', 'AppData', 'Local', 'Android', 'Sdk'))
if not sdk_root:
exit('ANDROID_SDK_ROOT must be set when compiling for Android!')
elif not os.path.isdir(sdk_root):
exit('Cannot find %s. Please install Android SDK and set ANDROID_SDK_ROOT or ANDROID_HOME.' % (sdk_root))
# Determine the NDK installation directory.
if os.environ.get('NDK_ROOT') or os.environ.get('ANDROID_NDK_ROOT'):
# We have an explicit setting from an environment variable.
ndk_root = os.environ.get('ANDROID_NDK_ROOT')
if not ndk_root or not os.path.isdir(ndk_root):
ndk_root = os.environ.get('NDK_ROOT')
if not ndk_root or not os.path.isdir(ndk_root):
exit("Cannot find %s. Please install Android NDK and set ANDROID_NDK_ROOT." % (ndk_root))
else:
# Often, it's installed in the ndk-bundle subdirectory of the SDK.
ndk_root = os.path.join(sdk_root, 'ndk-bundle')
if not os.path.isdir(os.path.join(ndk_root, 'toolchains')):
exit('Cannot find the Android NDK. Install it via the SDK manager or set the ANDROID_NDK_ROOT variable if you have installed it in a different location.')
SDK["ANDROID_NDK"] = ndk_root
# Determine the toolchain location.
prebuilt_dir = os.path.join(ndk_root, 'toolchains', 'llvm', 'prebuilt')
if not os.path.isdir(prebuilt_dir):
exit('Not found: %s (is the Android NDK installed?)' % (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_tag == 'windows-x86_64' and not os.path.isdir(prebuilt_dir):
# Try the 32-bits toolchain instead.
host_tag = 'windows'
prebuilt_dir = os.path.join(prebuilt_dir, host_tag)
SDK["ANDROID_TOOLCHAIN"] = prebuilt_dir
# And locate the GCC toolchain, which is needed for some tools (eg. as/ld)
arch = GetTargetArch()
for opt in (TOOLCHAIN_PREFIX + '4.9', arch + '-4.9', TOOLCHAIN_PREFIX + '4.8', arch + '-4.8'):
if os.path.isdir(os.path.join(ndk_root, 'toolchains', opt)):
SDK["ANDROID_GCC_TOOLCHAIN"] = os.path.join(ndk_root, 'toolchains', opt, 'prebuilt', host_tag)
break
# The prebuilt binaries have no toolchain prefix.
TOOLCHAIN_PREFIX = ''
# Determine the sysroot directory.
if arch == 'armv7a':
arch_dir = 'arch-arm'
elif arch == 'aarch64':
arch_dir = 'arch-arm64'
else:
arch_dir = 'arch-' + arch
SDK["SYSROOT"] = os.path.join(ndk_root, 'platforms', 'android-%s' % (api), arch_dir).replace('\\', '/')
#IncDirectory("ALWAYS", os.path.join(SDK["SYSROOT"], 'usr', 'include'))
# The Android support library polyfills C++ features not available in the
# STL that ships with Android.
support = os.path.join(ndk_root, 'sources', 'android', 'support', 'include')
IncDirectory("ALWAYS", support.replace('\\', '/'))
if api < 21:
LibName("ALWAYS", "-landroid_support")
# Determine the location of android.jar.
SDK["ANDROID_JAR"] = os.path.join(sdk_root, 'platforms', 'android-%s' % (api), 'android.jar')
if not os.path.isfile(SDK["ANDROID_JAR"]):
exit("Cannot find %s. Install platform API level %s via the SDK manager or change the targeted API level with --target=android-#" % (SDK["ANDROID_JAR"], api))
# Which build tools versions do we have? Pick the latest.
versions = []
for version in os.listdir(os.path.join(sdk_root, "build-tools")):
match = re.match('([0-9]+)\\.([0-9]+)\\.([0-9]+)', version)
if match:
version_tuple = int(match.group(1)), int(match.group(2)), int(match.group(3))
versions.append(version_tuple)
versions.sort()
if versions:
version = versions[-1]
SDK["ANDROID_BUILD_TOOLS"] = os.path.join(sdk_root, "build-tools", "{0}.{1}.{2}".format(*version))
# And find the location of the Java compiler.
if GetHost() == "windows":
jdk_home = os.environ.get("JDK_HOME") or os.environ.get("JAVA_HOME")
if not jdk_home:
# Try to use the Java shipped with Android Studio.
studio_path = GetRegistryKey("SOFTWARE\\Android Studio", "Path", override64=False)
if studio_path and os.path.isdir(studio_path):
jdk_home = os.path.join(studio_path, "jre")
if not jdk_home or not os.path.isdir(jdk_home):
exit("Cannot find JDK. Please set JDK_HOME or JAVA_HOME.")
javac = os.path.join(jdk_home, "bin", "javac.exe")
if not os.path.isfile(javac):
exit("Cannot find %s. Install the JDK and set JDK_HOME or JAVA_HOME." % (javac))
SDK["JDK"] = jdk_home
########################################################################
##
## SDK Auto-Disables
##
## Disable packages whose SDKs could not be found.
##
########################################################################
def SdkAutoDisableDirectX():
for ver in DXVERSIONS + ["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 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:
if sys.platform == 'cygwin' and path != "PATH":
# INCLUDE, LIB, etc. must remain in Windows-style in cygwin.
os.environ[path] = add + ';' + os.environ[path]
else:
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")
if (SDK["VISUALSTUDIO_VERSION"] >= (15,0)):
try:
vsver_file = open(os.path.join(SDK["VISUALSTUDIO"],
"VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"), "r")
SDK["VCTOOLSVERSION"] = vsver_file.readline().strip()
vcdir_suffix = "VC\\Tools\\MSVC\\%s\\" % SDK["VCTOOLSVERSION"]
except:
exit("Couldn't find tool version of %s." % MSVCVERSIONINFO[SDK["MSVC_VERSION"]]["vsname"])
else:
vcdir_suffix = "VC\\"
os.environ["VCINSTALLDIR"] = SDK["VISUALSTUDIO"] + vcdir_suffix
os.environ["WindowsSdkDir"] = SDK["MSPLATFORM"]
winsdk_ver = SDK["MSPLATFORM_VERSION"]
# Determine the directories to look in based on the architecture.
arch = GetTargetArch()
bindir = ""
libdir = ""
if ("VCTOOLSVERSION" in SDK):
bindir = "Host" + GetHostArch().upper() + "\\" + arch
libdir = arch
else:
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
vc_binpath = SDK["VISUALSTUDIO"] + vcdir_suffix + "bin"
binpath = os.path.join(vc_binpath, bindir)
if not os.path.isfile(binpath + "\\cl.exe"):
# Try the x86 tools, those should work just as well.
if arch == 'x64' and os.path.isfile(vc_binpath + "\\x86_amd64\\cl.exe"):
binpath = "{0}\\x86_amd64;{0}".format(vc_binpath)
elif winsdk_ver.startswith('10.'):
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." % binpath)
else:
exit("Couldn't find compilers in %s." % binpath)
AddToPathEnv("PATH", binpath)
AddToPathEnv("PATH", SDK["VISUALSTUDIO"] + "Common7\\IDE")
AddToPathEnv("INCLUDE", os.environ["VCINSTALLDIR"] + "include")
AddToPathEnv("INCLUDE", os.environ["VCINSTALLDIR"] + "atlmfc\\include")
AddToPathEnv("LIB", os.environ["VCINSTALLDIR"] + "lib\\" + libdir)
AddToPathEnv("LIB", os.environ["VCINSTALLDIR"] + "atlmfc\\lib\\" + libdir)
winsdk_ver = SDK["MSPLATFORM_VERSION"]
if winsdk_ver.startswith('10.'):
AddToPathEnv("PATH", SDK["MSPLATFORM"] + "bin\\" + arch)
AddToPathEnv("PATH", SDK["MSPLATFORM"] + "bin\\" + winsdk_ver + "\\" + arch)
# Windows Kit 10 introduces the "universal CRT".
inc_dir = SDK["MSPLATFORM"] + "Include\\" + winsdk_ver + "\\"
lib_dir = SDK["MSPLATFORM"] + "Lib\\" + winsdk_ver + "\\"
AddToPathEnv("INCLUDE", inc_dir + "shared")
AddToPathEnv("INCLUDE", inc_dir + "ucrt")
AddToPathEnv("INCLUDE", inc_dir + "um")
AddToPathEnv("LIB", lib_dir + "ucrt\\" + arch)
AddToPathEnv("LIB", lib_dir + "um\\" + arch)
elif winsdk_ver == '8.1':
AddToPathEnv("PATH", SDK["MSPLATFORM"] + "bin\\" + arch)
inc_dir = SDK["MSPLATFORM"] + "Include\\"
lib_dir = SDK["MSPLATFORM"] + "Lib\\winv6.3\\"
AddToPathEnv("INCLUDE", inc_dir + "shared")
AddToPathEnv("INCLUDE", inc_dir + "ucrt")
AddToPathEnv("INCLUDE", inc_dir + "um")
AddToPathEnv("LIB", lib_dir + "ucrt\\" + arch)
AddToPathEnv("LIB", lib_dir + "um\\" + arch)
else:
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 Windows SDK directory!\nUsing directory: %s" % SDK["MSPLATFORM"])
# Targeting the 7.1 SDK (which is the only way to have Windows XP support)
# with Visual Studio 2015+ requires use of the Universal CRT.
if winsdk_ver in ('7.1', '7.1A', '8.0', '8.1') and SDK["VISUALSTUDIO_VERSION"] >= (14,0):
win_kit = GetRegistryKey("SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot10")
# Fallback in case we can't read the registry.
if not win_kit or not os.path.isdir(win_kit):
win_kit = "C:\\Program Files (x86)\\Windows Kits\\10\\"
elif not win_kit.endswith('\\'):
win_kit += '\\'
for vnum in 10150, 10240, 10586, 14393, 15063, 16299, 17134, 17763, 18362, 19041:
version = "10.0.{0}.0".format(vnum)
if os.path.isfile(win_kit + "Include\\" + version + "\\ucrt\\assert.h"):
print("Using Universal CRT %s" % (version))
break
AddToPathEnv("LIB", "%s\\Lib\\%s\\ucrt\\%s" % (win_kit, version, arch))
AddToPathEnv("INCLUDE", "%s\\Include\\%s\\ucrt" % (win_kit, version))
# Copy the DLLs to the bin directory.
CopyAllFiles(GetOutputDir() + "/bin/", win_kit + "Redist\\ucrt\\DLLs\\" + arch + "\\")
########################################################################
#
# 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 = []
FRAMEWORKDIRECTORIES = []
LIBNAMES = []
DEFSYMBOLS = []
def IncDirectory(opt, dir):
INCDIRECTORIES.append((opt, dir))
def LibDirectory(opt, dir):
LIBDIRECTORIES.append((opt, dir))
def FrameworkDirectory(opt, dir):
FRAMEWORKDIRECTORIES.append((opt, dir))
def FindIncDirectory(opt):
# Find the include directory associated with this module
for mod, dir in INCDIRECTORIES:
if mod == opt:
return os.path.abspath(dir)
def FindLibDirectory(opt):
# Find the library directory associated with this module
for mod, dir in LIBDIRECTORIES:
if mod == opt:
return os.path.abspath(dir)
def FindOptDirectory(opt):
# Find the common directory associated with this module
# using the include and library directories as a guide
include_dir = FindIncDirectory(opt)
lib_dir = FindLibDirectory(opt)
if include_dir and lib_dir:
# The module's common directory is the common prefix of
# its include and library directory
common_dir = os.path.commonprefix([include_dir, lib_dir])
if common_dir:
return os.path.abspath(common_dir)
elif include_dir:
# The module's common directory is the parent of the include
# directory
return os.path.abspath(os.path.join(include_dir, os.pardir))
elif lib_dir:
# The module's common directory is the parent of the library
# directory
return os.path.abspath(os.path.join(lib_dir, os.pardir))
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()):
if not PkgSkip(opt):
Warn("Could not locate thirdparty package %s, excluding from build" % (opt.lower()))
PkgDisable(opt)
return
else:
Error("Could not locate thirdparty package %s, aborting build" % (opt.lower()))
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())
target = GetTarget()
if target != 'android':
print("Target OS: %s" % GetTarget())
else:
print("Target OS: %s (API level %d)" % (GetTarget(), ANDROID_API))
print("Target arch: %s" % GetTargetArch())
# Set to English so we can safely parse the result of gcc commands.
# Setting it to UTF-8 is necessary for Python 3 modules to import
# correctly.
os.environ["LC_ALL"] = "en_US.UTF-8"
os.environ["LANGUAGE"] = "en"
# In the case of Android, we have to put the toolchain on the PATH in order to use it.
if GetTarget() == 'android' and GetHost() != 'android':
AddToPathEnv("PATH", os.path.join(SDK["ANDROID_TOOLCHAIN"], "bin"))
if "ANDROID_BUILD_TOOLS" in SDK:
AddToPathEnv("PATH", SDK["ANDROID_BUILD_TOOLS"])
if "JDK" in SDK:
AddToPathEnv("PATH", os.path.join(SDK["JDK"], "bin"))
os.environ["JAVA_HOME"] = SDK["JDK"]
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, SYS_INC_DIRS
if sys.platform == "darwin":
# We need to add this one explicitly for some reason.
SYS_LIB_DIRS.append(SDK["MACOSX"] + "/usr/lib")
if not SDK.get("MACOSX"):
# gcc doesn't add this one, but we do want it:
local_lib = SDK.get("SYSROOT", "") + "/usr/local/lib"
if os.path.isdir(local_lib):
SYS_LIB_DIRS.append(local_lib)
sysroot_flag = ""
if SDK.get("MACOSX"):
# The default compiler in Leopard does not respect --sysroot correctly.
sysroot_flag = " -isysroot " + SDK["MACOSX"]
#if SDK.get("SYSROOT"):
# sysroot_flag = ' --sysroot=%s -no-canonical-prefixes' % (SDK["SYSROOT"])
if GetTarget() == "android":
sysroot_flag = " -target " + ANDROID_TRIPLE
# Extract the dirs from the line that starts with 'libraries: ='.
cmd = GetCXX() + " -print-search-dirs" + sysroot_flag
handle = os.popen(cmd)
for line in handle:
if not line.startswith('libraries: ='):
continue
line = line[12:].strip()
libdirs = line.split(':')
while libdirs:
libdir = os.path.normpath(libdirs.pop(0))
if os.path.isdir(libdir):
if libdir not in SYS_LIB_DIRS:
SYS_LIB_DIRS.append(libdir)
elif len(libdir) == 1:
# Oops, is this a drive letter? Prepend it to the next.
libdirs[0] = libdir + ':' + libdirs[0]
elif GetVerbose():
print("Ignoring non-existent library directory %s" % (libdir))
returnval = handle.close()
if returnval is not None and returnval != 0:
Warn("%s failed" % (cmd))
SYS_LIB_DIRS += [SDK.get("SYSROOT", "") + "/usr/lib"]
# The Android toolchain on Windows doesn't actually add this one.
if target == 'android' and GetHost() == 'windows':
libdir = SDK.get("SYSROOT", "") + "/usr/lib"
if GetTargetArch() == 'x86_64':
libdir += '64'
SYS_LIB_DIRS += [libdir]
# Now extract the preprocessor's include directories.
cmd = GetCXX() + " -x c++ -v -E " + os.devnull
cmd += sysroot_flag
null = open(os.devnull, 'w')
handle = subprocess.Popen(cmd, stdout=null, stderr=subprocess.PIPE, shell=True)
scanning = False
for line in handle.communicate()[1].splitlines():
line = line.decode('utf-8', 'replace')
# Start looking at a line that says: #include "..." search starts here
if not scanning:
if line.startswith('#include'):
scanning = True
continue
if sys.platform == "win32":
if not line.startswith(' '):
continue
else:
if not line.startswith(' /'):
continue
line = line.strip()
if line.endswith(" (framework directory)"):
pass
elif os.path.isdir(line):
SYS_INC_DIRS.append(os.path.normpath(line))
elif GetVerbose():
print("Ignoring non-existent include directory %s" % (line))
if handle.returncode != 0 or not SYS_INC_DIRS:
Warn("%s failed or did not produce the expected result" % (cmd))
sysroot = SDK.get("SYSROOT", "")
# Add some sensible directories as a fallback.
SYS_INC_DIRS = [
sysroot + "/usr/include",
sysroot + "/usr/local/include"
]
pcbsd_inc = sysroot + "/usr/PCBSD/local/include"
if os.path.isdir(pcbsd_inc):
SYS_INC_DIRS.append(pcbsd_inc)
null.close()
# Print out the search paths
if GetVerbose():
print("System library search path:")
for dir in SYS_LIB_DIRS:
print(" " + dir)
print("System include search path:")
for dir in SYS_INC_DIRS:
print(" " + dir)
# 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 = GetOutputDir()
AddToPathEnv("PYTHONPATH", builtdir)
AddToPathEnv("PANDA_PRC_DIR", os.path.join(builtdir, "etc"))
AddToPathEnv("PATH", os.path.join(builtdir, "bin"))
if GetHost() == 'windows':
# extension_native_helpers.py currently expects to find libpandaexpress on sys.path.
AddToPathEnv("PYTHONPATH", os.path.join(builtdir, "bin"))
AddToPathEnv("PATH", os.path.join(builtdir, "plugins"))
# 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)
# macOS 10.11 removed DYLD_LIBRARY_PATH, but we still need to pass
# on our lib directory to ppackage, so add it to PATH instead.
os.environ["PATH"] = os.path.join(builtdir, 'lib') + ':' + os.environ.get("PATH", "")
# 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] == '/':
dstfile += os.path.basename(srcfile)
if NeedsBuild([dstfile], [srcfile]):
if os.path.islink(srcfile):
# Preserve symlinks
if os.path.isfile(dstfile) or os.path.islink(dstfile):
print("Removing file %s" % (dstfile))
os.unlink(dstfile)
elif os.path.isdir(dstfile):
print("Removing directory %s" % (dstfile))
shutil.rmtree(dstfile)
os.symlink(os.readlink(srcfile), dstfile)
else:
WriteBinaryFile(dstfile, ReadBinaryFile(srcfile))
if sys.platform == 'cygwin' and os.path.splitext(dstfile)[1].lower() in ('.dll', '.exe'):
os.chmod(dstfile, 0o755)
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 CopyTree(dstdir, srcdir, omitVCS=True, exclude=()):
if os.path.isdir(dstdir):
source_entries = os.listdir(srcdir)
for entry in source_entries:
srcpth = os.path.join(srcdir, entry)
dstpth = os.path.join(dstdir, entry)
if entry in exclude:
continue
if os.path.islink(srcpth) or os.path.isfile(srcpth):
if not omitVCS or entry not in VCS_FILES:
CopyFile(dstpth, srcpth)
else:
if not omitVCS or entry not in VCS_DIRS:
CopyTree(dstpth, srcpth)
# Delete files in dstdir that are not in srcdir.
for entry in os.listdir(dstdir):
if entry not in source_entries or entry in exclude:
path = os.path.join(dstdir, entry)
if os.path.islink(path) or os.path.isfile(path):
os.remove(path)
elif os.path.isdir(path):
shutil.rmtree(path)
else:
if GetHost() == 'windows':
srcdir = srcdir.replace('/', '\\')
dstdir = dstdir.replace('/', '\\')
cmd = 'xcopy /I/Y/E/Q "' + srcdir + '" "' + dstdir + '"'
oscmd(cmd)
else:
if subprocess.call(['cp', '-R', '-f', srcdir, dstdir]) != 0:
exit("Copy failed.")
for entry in exclude:
path = os.path.join(dstdir, entry)
if os.path.islink(path) or os.path.isfile(path):
os.remove(path)
elif os.path.isdir(path):
shutil.rmtree(path)
if omitVCS:
DeleteVCS(dstdir)
def CopyPythonTree(dstdir, srcdir, threads=0):
if (not os.path.isdir(dstdir)):
os.mkdir(dstdir)
exclude_files = set(VCS_FILES)
exclude_files.add('panda3d.py')
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 not in exclude_files and ext not in SUFFIX_INC + ['.pyc', '.pyo']:
if (NeedsBuild([dstpth], [srcpth])):
WriteBinaryFile(dstpth, ReadBinaryFile(srcpth))
JustBuilt([dstpth], [srcpth])
elif entry not in VCS_DIRS:
CopyPythonTree(dstpth, srcpth, threads=threads)
########################################################################
##
## Parse setup.cfg to extract the version number.
##
########################################################################
cfg_parser = None
def GetMetadataValue(key):
global cfg_parser
if not cfg_parser:
# Parse the metadata from the setup.cfg file.
cfg_parser = configparser.ConfigParser()
path = os.path.join(os.path.dirname(__file__), '..', 'setup.cfg')
assert cfg_parser.read(path), "Could not read setup.cfg file."
value = cfg_parser.get('metadata', key)
if key == 'classifiers':
value = value.strip().split('\n')
return value
# This function is being phased out.
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"
##########################################################################################
#
# 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 "noinclude" not 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
def GenerateEmbeddedStringFile(string_name, data):
yield 'extern const char %s[] = {\n' % (string_name)
i = 0
for byte in data:
if i == 0:
yield ' '
yield ' 0x%02x,' % (byte)
i += 1
if i >= 12:
yield '\n'
i = 0
yield '\n};\n'
def WriteEmbeddedStringFile(basename, inputs, string_name=None):
if os.path.splitext(basename)[1] not in SUFFIX_INC:
basename += '.cxx'
target = GetOutputDir() + "/tmp/" + basename
if string_name is None:
string_name = os.path.basename(os.path.splitext(target)[0])
string_name = string_name.replace('-', '_')
data = bytearray()
for input in inputs:
fp = open(input, 'rb')
# Insert a #line so that we get meaningful compile/assert errors when
# the result is inserted by interrogate_module into generated code.
if os.path.splitext(input)[1] in SUFFIX_INC:
line = '#line 1 "%s"\n' % (input)
data += bytearray(line.encode('ascii', 'replace'))
data += bytearray(fp.read())
fp.close()
data.append(0)
output = ''.join(GenerateEmbeddedStringFile(string_name, data))
ConditionalWriteFile(target, output)
return target
########################################################################
##
## FindLocation
##
########################################################################
ORIG_EXT = {}
PYABI_SPECIFIC = set()
WARNED_FILES = set()
def GetOrigExt(x):
return ORIG_EXT[x]
def SetOrigExt(x, v):
ORIG_EXT[x] = v
def GetExtensionSuffix():
import _imp
return _imp.extension_suffixes()[0]
def GetPythonABI():
soabi = sysconfig.get_config_var('SOABI')
if soabi:
return soabi
soabi = 'cpython-%d%d' % (sys.version_info[:2])
if sys.version_info >= (3, 8):
return soabi
debug_flag = sysconfig.get_config_var('Py_DEBUG')
if (debug_flag is None and hasattr(sys, 'gettotalrefcount')) or debug_flag:
soabi += 'd'
malloc_flag = sysconfig.get_config_var('WITH_PYMALLOC')
if malloc_flag is None or malloc_flag:
soabi += 'm'
return soabi
def CalcLocation(fn, ipath):
if fn.startswith("panda3d/") and fn.endswith(".py"):
return OUTPUTDIR + "/" + fn
if (fn.endswith(".class")):return OUTPUTDIR+"/classes/"+fn
if (fn.count("/")): return fn
dllext = ""
target = GetTarget()
if (GetOptimize() <= 2 and target == 'windows'): dllext = "_d"
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(".py")): return CxxFindSource(fn, ipath)
if (fn.endswith(".yxx")): return CxxFindSource(fn, ipath)
if (fn.endswith(".lxx")): return CxxFindSource(fn, ipath)
if (fn.endswith(".xml")): return CxxFindSource(fn, ipath)
if (fn.endswith(".java")):return CxxFindSource(fn, ipath)
if (fn.endswith(".egg")): return OUTPUTDIR+"/models/"+fn
if (fn.endswith(".egg.pz")):return OUTPUTDIR+"/models/"+fn
if (fn.endswith(".pyd")): return OUTPUTDIR+"/panda3d/"+fn[:-4]+GetExtensionSuffix()
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(".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(".p3d")): 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(".mll")): return OUTPUTDIR+"/plugins/"+fn
if (fn.endswith(".exe")): return OUTPUTDIR+"/bin/"+fn[:-4]
if (fn.endswith(".p3d")): 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
else:
if (fn.endswith(".obj")): return OUTPUTDIR+"/tmp/"+fn[:-4]+".o"
if (fn.endswith(".dll")): 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(".p3d")): 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, pyabi=None):
if GetLinkAllStatic():
if fn.endswith(".dll"):
fn = fn[:-4] + ".lib"
elif fn.endswith(".pyd"):
fn = "libpy.panda3d." \
+ os.path.splitext(fn[:-4] + GetExtensionSuffix())[0] + ".lib"
loc = CalcLocation(fn, ipath)
base, ext = os.path.splitext(fn)
# If this is a target created with PyTargetAdd, we need to make sure it
# it put in a Python-version-specific directory.
if loc in PYABI_SPECIFIC:
if loc.startswith(OUTPUTDIR + "/tmp"):
if pyabi is not None:
loc = OUTPUTDIR + "/tmp/" + pyabi + loc[len(OUTPUTDIR) + 4:]
else:
raise RuntimeError("%s is a Python-specific target, use PyTargetAdd instead of TargetAdd" % (fn))
elif ext != ".pyd" and loc not in WARNED_FILES:
WARNED_FILES.add(loc)
Warn("file depends on Python but is not in an ABI-specific directory:", loc)
ORIG_EXT[loc] = ext
return loc
########################################################################
##
## These files maintain a python_versions.json file in the built/tmp
## directory that can be used by the other scripts in this directory.
##
########################################################################
def GetCurrentPythonVersionInfo():
if PkgSkip("PYTHON"):
return
from distutils.sysconfig import get_python_lib
return {
"version": SDK["PYTHONVERSION"][6:].rstrip('dmu'),
"soabi": GetPythonABI(),
"ext_suffix": GetExtensionSuffix(),
"executable": sys.executable,
"purelib": get_python_lib(False),
"platlib": get_python_lib(True),
}
def UpdatePythonVersionInfoFile(new_info):
import json
json_file = os.path.join(GetOutputDir(), "tmp", "python_versions.json")
json_data = []
if os.path.isfile(json_file) and not PkgSkip("PYTHON"):
try:
with open(json_file, 'r') as fh:
json_data = json.load(fh)
except:
json_data = []
# Prune the list by removing the entries that conflict with our build,
# plus the entries that no longer exist, and the EOL Python versions
for version_info in json_data[:]:
core_pyd = os.path.join(GetOutputDir(), "panda3d", "core" + version_info["ext_suffix"])
if version_info["ext_suffix"] == new_info["ext_suffix"] or \
version_info["soabi"] == new_info["soabi"] or \
not os.path.isfile(core_pyd) or \
version_info["version"].split(".", 1)[0] == "2" or \
version_info["version"] in ("3.0", "3.1", "3.2", "3.3", "3.4", "3.5"):
json_data.remove(version_info)
if not PkgSkip("PYTHON"):
json_data.append(new_info)
if VERBOSE:
print("Writing %s" % (json_file))
with open(json_file, 'w') as fh:
json.dump(json_data, fh, indent=4)
def ReadPythonVersionInfoFile():
import json
json_file = os.path.join(GetOutputDir(), "tmp", "python_versions.json")
if os.path.isfile(json_file):
try:
json_data = json.load(open(json_file, 'r'))
except:
pass
# Don't include unsupported versions of Python.
for version_info in json_data[:]:
if version_info["version"] in ("2.6", "2.7", "3.0", "3.1", "3.2", "3.3", "3.4"):
json_data.remove(version_info)
return json_data
return []
########################################################################
##
## 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.
##
## PyTargetAdd is a special version for targets that depend on Python.
## It will create a target for each Python version we are building with,
## ensuring that builds with different Python versions won't conflict
## when we build for multiple Python ABIs side-by-side.
##
########################################################################
class Target:
pass
TARGET_LIST = []
TARGET_TABLE = {}
def TargetAdd(target, dummy=0, opts=[], input=[], dep=[], ipath=None, winrc=None, pyabi=None):
if dummy != 0:
exit("Syntax error in TargetAdd " + target)
if ipath is None:
ipath = opts
if not ipath:
ipath = []
if isinstance(input, str):
input = [input]
if isinstance(dep, str):
dep = [dep]
if target.endswith(".pyd") and not pyabi:
raise RuntimeError("Use PyTargetAdd to build .pyd targets")
full = FindLocation(target, [OUTPUTDIR + "/include"], pyabi=pyabi)
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]
for x in opts:
if x not in t.opts:
t.opts.append(x)
ipath = [OUTPUTDIR + "/tmp"] + GetListOption(ipath, "DIR:") + [OUTPUTDIR+"/include"]
for x in input:
fullinput = FindLocation(x, ipath, pyabi=pyabi)
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
elif suffix == '.java':
for d in JavaCalcDependencies(fullinput, OUTPUTDIR + "/classes"):
t.deps[d] = 1
# If we are linking statically, add the source DLL's dynamic dependencies.
if GetLinkAllStatic() and ORIG_EXT[fullinput] == '.lib' and fullinput in TARGET_TABLE:
tdep = TARGET_TABLE[fullinput]
for y in tdep.inputs:
if ORIG_EXT[y] == '.lib':
t.inputs.append(y)
for opt, _ in LIBNAMES + LIBDIRECTORIES + FRAMEWORKDIRECTORIES:
if opt in tdep.opts and opt not in t.opts:
t.opts.append(opt)
if x.endswith(".in"):
# Mark the _igate.cxx file as a dependency also.
outbase = os.path.basename(x)[:-3]
woutc = GetOutputDir()+"/tmp/"+outbase+"_igate.cxx"
t.deps[woutc] = 1
if target.endswith(".in"):
# Add any .N files.
base, ext = os.path.splitext(fullinput)
fulln = base + ".N"
if os.path.isfile(fulln):
t.deps[fulln] = 1
for x in dep:
fulldep = FindLocation(x, ipath, pyabi=pyabi)
t.deps[fulldep] = 1
if winrc and GetTarget() == 'windows':
TargetAdd(target, input=WriteResourceFile(target.split("/")[-1].split(".")[0], **winrc))
ext = os.path.splitext(target)[1]
if ext == ".in":
if not CrossCompiling():
t.deps[FindLocation("interrogate.exe", [])] = 1
t.deps[FindLocation("dtool_have_python.dat", [])] = 1
if ext in (".obj", ".tlb", ".res", ".plugin", ".app") or ext in SUFFIX_DLL or ext in SUFFIX_LIB:
t.deps[FindLocation("platform.dat", [])] = 1
if target.endswith(".obj") and any(x.endswith(".in") for x in input):
if not CrossCompiling():
t.deps[FindLocation("interrogate_module.exe", [])] = 1
if target.endswith(".pz") and not CrossCompiling():
t.deps[FindLocation("pzip.exe", [])] = 1
if target.endswith(".in"):
# Also add a target to compile the _igate.cxx file into an _igate.obj.
outbase = os.path.basename(target)[:-3]
woutc = OUTPUTDIR + "/tmp/" + outbase + "_igate.cxx"
CxxDependencyCache[woutc] = []
PyTargetAdd(outbase + "_igate.obj", opts=opts+['PYTHON','BIGOBJ'], input=woutc, dep=target)
def PyTargetAdd(target, opts=[], **kwargs):
if PkgSkip("PYTHON"):
return
if 'PYTHON' not in opts:
opts = opts + ['PYTHON']
abi = GetPythonABI()
MakeDirectory(OUTPUTDIR + "/tmp/" + abi)
# Mark this target as being a Python-specific target.
orig = CalcLocation(target, [OUTPUTDIR + "/include"])
PYABI_SPECIFIC.add(orig)
if orig.startswith(OUTPUTDIR + "/tmp/") and os.path.exists(orig):
print("Removing file %s" % (orig))
os.unlink(orig)
TargetAdd(target, opts=opts, pyabi=abi, **kwargs)