######################################################################## ## ## 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"}, (14,3): {"vsversion":(17,0), "vsname":"Visual Studio 2022"}, } ######################################################################## ## ## 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"), ("MAYA2022","2022"), ] 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 = 'armv7a-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 'llvm-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.3", "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')) # We need to redistribute the C++ standard library. stdlibc = os.path.join(ndk_root, 'sources', 'cxx-stl', 'llvm-libc++') stl_lib = os.path.join(stdlibc, 'libs', abi, 'libc++_shared.so') CopyFile(os.path.join(GetOutputDir(), 'lib', 'libc++_shared.so'), stl_lib) # 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(): if CrossCompiling(): return '.{0}.so'.format(GetPythonABI()) else: import _imp return _imp.extension_suffixes()[0] def GetPythonABI(): if not CrossCompiling(): 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 == "classes.dex"): 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)