diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index abf0e9f777..948891d19d 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -1347,7 +1347,8 @@ class bdist_apps(setuptools.Command): platforms = build_cmd.platforms build_base = os.path.abspath(build_cmd.build_base) - os.makedirs(self.dist_dir, exist_ok=True) + if not os.path.exists(self.dist_dir): + os.makedirs(self.dist_dir) os.chdir(self.dist_dir) for platform in platforms: diff --git a/dtool/src/dtoolutil/load_dso.cxx b/dtool/src/dtoolutil/load_dso.cxx index 79d821b5fa..0c7ae24ebe 100644 --- a/dtool/src/dtoolutil/load_dso.cxx +++ b/dtool/src/dtoolutil/load_dso.cxx @@ -132,6 +132,9 @@ void * load_dso(const DSearchPath &path, const Filename &filename) { Filename abspath = resolve_dso(path, filename); if (!abspath.is_regular_file()) { + // Make sure the error flag is cleared, to prevent a subsequent call to + // load_dso_error() from returning a previously stored error. + dlerror(); return nullptr; } string os_specific = abspath.to_os_specific(); diff --git a/dtool/src/prc/configPageManager.cxx b/dtool/src/prc/configPageManager.cxx index 72e00f0189..b26eb17c69 100644 --- a/dtool/src/prc/configPageManager.cxx +++ b/dtool/src/prc/configPageManager.cxx @@ -129,7 +129,12 @@ reload_implicit_pages() { #else const BlobInfo *blobinfo = (const BlobInfo *)dlsym(dlopen(NULL, RTLD_NOW), "blobinfo"); #endif - if (blobinfo != nullptr && (blobinfo->version == 0 || blobinfo->num_pointers < 10)) { + if (blobinfo == nullptr) { +#ifndef _MSC_VER + // Clear the error flag. + dlerror(); +#endif + } else if (blobinfo->version == 0 || blobinfo->num_pointers < 10) { blobinfo = nullptr; } diff --git a/makepanda/makepackage.py b/makepanda/makepackage.py index 31a45a2bb9..9b34192e94 100755 --- a/makepanda/makepackage.py +++ b/makepanda/makepackage.py @@ -5,6 +5,9 @@ from installpanda import * import sys import os import shutil +import glob +import re +import subprocess INSTALLER_DEB_FILE = """ @@ -835,7 +838,8 @@ def MakeInstallerAndroid(version, **kwargs): # off any suffix (eg. libfile.so.1.0), as Android does not support them. source_dir = os.path.join(outputdir, "lib") target_dir = os.path.join("apkroot", "lib", SDK["ANDROID_ABI"]) - oscmd("mkdir -p %s" % (target_dir)) + if not os.path.exists(target_dir): + os.makedirs(target_dir, mode=0o755) # Determine the library directories we should look in. libpath = [source_dir] @@ -857,19 +861,26 @@ def MakeInstallerAndroid(version, **kwargs): # Already processed. return - oscmd("cp %s %s" % (source, target)) + shutil.copy(source, target) # Walk through the library dependencies. - oscmd("ldd %s | grep .so > %s/tmp/otool-libs.txt" % (target, outputdir), True) - for line in open(outputdir + "/tmp/otool-libs.txt", "r"): - line = line.strip() - if not line: + handle = subprocess.Popen(['readelf', '--dynamic', target], stdout=subprocess.PIPE) + for line in handle.communicate()[0].splitlines(): + # The line will look something like: + # 0x0000000000000001 (NEEDED) Shared library: [libpanda.so] + line = line.decode('utf-8', 'replace').strip() + if not line or '(NEEDED)' not in line or '[' not in line or ']' not in line: continue - if '.so.' in line: - dep = line.rpartition('.so.')[0] + '.so' - oscmd("patchelf --replace-needed %s %s %s" % (line, dep, target), True) - else: - dep = line + + # Extract the part between square brackets. + idx = line.index('[') + dep = line[idx + 1 : line.index(']', idx)] + + # Change .so.1.2 suffix to .so, as needed for loading in .apk + if '.so.' in dep: + orig_dep = dep + dep = dep.rpartition('.so.')[0] + '.so' + oscmd("patchelf --replace-needed %s %s %s" % (orig_dep, dep, target), True) # Find it on the LD_LIBRARY_PATH. for dir in libpath: @@ -958,21 +969,28 @@ def MakeInstallerAndroid(version, **kwargs): aapt_cmd += " -F %s" % (apk_unaligned) aapt_cmd += " -M apkroot/AndroidManifest.xml" aapt_cmd += " -A apkroot/assets -S apkroot/res" - aapt_cmd += " -I $PREFIX/share/aapt/android.jar" + aapt_cmd += " -I %s" % (SDK["ANDROID_JAR"]) oscmd(aapt_cmd) # And add all the libraries to it. - oscmd("cd apkroot && aapt add ../%s classes.dex" % (apk_unaligned)) + oscmd("aapt add %s classes.dex" % (os.path.join('..', apk_unaligned)), cwd="apkroot") for path, dirs, files in os.walk('apkroot/lib'): if files: rel = os.path.relpath(path, 'apkroot') - oscmd("cd apkroot && aapt add ../%s %s/*" % (apk_unaligned, rel)) + rel_files = [os.path.join(rel, file).replace('\\', '/') for file in files] + oscmd("aapt add %s %s" % (os.path.join('..', apk_unaligned), ' '.join(rel_files)), cwd="apkroot") # Now align the .apk, which is necessary for Android to load it. oscmd("zipalign -v -p 4 %s %s" % (apk_unaligned, apk_unsigned)) # Finally, sign it using a debug key. This is generated if it doesn't exist. - oscmd("apksigner debug.ks %s panda3d.apk" % (apk_unsigned)) + if GetHost() == 'android': + # Termux version of apksigner automatically generates a debug key. + oscmd("apksigner debug.ks %s panda3d.apk" % (apk_unsigned)) + else: + if not os.path.isfile('debug.ks'): + oscmd("keytool -genkey -noprompt -dname CN=Panda3D,O=Panda3D,C=US -keystore debug.ks -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 1000") + oscmd("apksigner sign --ks debug.ks --ks-pass pass:android --min-sdk-version %s --out panda3d.apk %s" % (SDK["ANDROID_API"], apk_unsigned)) # Clean up. oscmd("rm -rf apkroot") diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index a522d4f4a8..5c353c213c 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -2004,7 +2004,10 @@ def CompileRsrc(target, src, opts): def CompileJava(target, src, opts): """Compiles a .java file into a .class file.""" - cmd = "ecj " + if GetHost() == 'android': + cmd = "ecj " + else: + cmd = "javac -bootclasspath " + BracketNameWithQuotes(SDK["ANDROID_JAR"]) + " " optlevel = GetOptimizeOption(opts) if optlevel >= 4: diff --git a/makepanda/makepandacore.py b/makepanda/makepandacore.py index 8790538395..144d26f11a 100644 --- a/makepanda/makepandacore.py +++ b/makepanda/makepandacore.py @@ -564,11 +564,12 @@ def LocateBinary(binary): p = os.environ["PATH"] pathList = p.split(os.pathsep) + suffixes = [''] if GetHost() == 'windows': - if not binary.endswith('.exe'): + if not binary.lower().endswith('.exe') and not binary.lower().endswith('.bat'): # Append .exe if necessary - binary += '.exe' + suffixes = ['.exe', '.bat'] # On Windows the current directory is always implicitly # searched before anything else on PATH. @@ -576,8 +577,9 @@ def LocateBinary(binary): for path in pathList: binpath = os.path.join(os.path.expanduser(path), binary) - if os.access(binpath, os.X_OK): - return os.path.abspath(os.path.realpath(binpath)) + for suffix in suffixes: + if os.access(binpath + suffix, os.X_OK): + return os.path.abspath(os.path.realpath(binpath + suffix)) return None ######################################################################## @@ -586,20 +588,31 @@ def LocateBinary(binary): ## ######################################################################## -def oscmd(cmd, ignoreError = False): +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": - exe = cmd.split()[0] + 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(';', '\\;') - res = subprocess.call(cmd, shell=True) + 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))) @@ -2463,22 +2476,46 @@ def SdkLocateAndroid(): 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 - # Determine the NDK installation directory. - if 'NDK_ROOT' not in os.environ: - exit('NDK_ROOT must be set when compiling for Android!') + # 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') - ndk_root = os.environ["NDK_ROOT"] - if not os.path.isdir(ndk_root): - exit("Cannot find %s. Please install Android NDK and set NDK_ROOT." % (ndk_root)) + # 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' % (prebuilt_dir)) + exit('Not found: %s (is the Android NDK installed?)' % (prebuilt_dir)) host_tag = GetHost() + '-x86' if host_64: @@ -2527,7 +2564,44 @@ def SdkLocateAndroid(): # STL that ships with Android. support = os.path.join(ndk_root, 'sources', 'android', 'support', 'include') IncDirectory("ALWAYS", support.replace('\\', '/')) - LibName("ALWAYS", "-landroid_support") + 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 ######################################################################## ## @@ -2793,6 +2867,13 @@ def SetupBuildEnvironment(compiler): 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() @@ -2844,8 +2925,22 @@ def SetupBuildEnvironment(compiler): 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() + sysroot_flag + " -x c++ -v -E /dev/null" + cmd = GetCXX() + " -x c++ -v -E " + os.devnull + if "ANDROID_NDK" in SDK: + ndk_dir = SDK["ANDROID_NDK"].replace('\\', '/') + cmd += ' -isystem %s/sysroot/usr/include' % (ndk_dir) + cmd += ' -isystem %s/sysroot/usr/include/%s' % (ndk_dir, SDK["ANDROID_TRIPLE"]) + else: + cmd += sysroot_flag + null = open(os.devnull, 'w') handle = subprocess.Popen(cmd, stdout=null, stderr=subprocess.PIPE, shell=True) scanning = False @@ -2858,8 +2953,12 @@ def SetupBuildEnvironment(compiler): scanning = True continue - if not line.startswith(' /'): - continue + if sys.platform == "win32": + if not line.startswith(' '): + continue + else: + if not line.startswith(' /'): + continue line = line.strip() if line.endswith(" (framework directory)"): diff --git a/panda/src/android/android_main.cxx b/panda/src/android/android_main.cxx index 2f7d7e3dcc..13895d99f6 100644 --- a/panda/src/android/android_main.cxx +++ b/panda/src/android/android_main.cxx @@ -24,6 +24,7 @@ #include "config_display.h" // #define OPENGLES_1 #include "config_androiddisplay.h" +#include #include #include diff --git a/panda/src/display/graphicsEngine.cxx b/panda/src/display/graphicsEngine.cxx index ae1052ba1d..80973ae071 100644 --- a/panda/src/display/graphicsEngine.cxx +++ b/panda/src/display/graphicsEngine.cxx @@ -593,7 +593,6 @@ remove_all_windows() { // appeared to be happening, and this worked around it. Windows old_windows; old_windows.swap(_windows); - Windows::iterator wi; for (GraphicsOutput *win : old_windows) { nassertv(win != nullptr); do_remove_window(win, current_thread); diff --git a/panda/src/downloader/documentSpec.cxx b/panda/src/downloader/documentSpec.cxx index 34bfe3808d..a27ebe0338 100644 --- a/panda/src/downloader/documentSpec.cxx +++ b/panda/src/downloader/documentSpec.cxx @@ -66,7 +66,7 @@ input(std::istream &in) { // Scan the tag, up to but not including the closing paren. std::string tag; in >> ch; - while (!in.fail() && ch != EOF && ch != ')') { + while (!in.fail() && ch != ')') { tag += ch; // We want to include embedded whitespace, so we use get(). ch = in.get(); @@ -81,7 +81,7 @@ input(std::istream &in) { // Scan the date, up to but not including the closing bracket. if (ch != ']') { std::string date; - while (!in.fail() && ch != EOF && ch != ']') { + while (!in.fail() && ch != ']') { date += ch; ch = in.get(); } diff --git a/panda/src/downloader/httpDate.cxx b/panda/src/downloader/httpDate.cxx index e49769257f..e17c6c06d6 100644 --- a/panda/src/downloader/httpDate.cxx +++ b/panda/src/downloader/httpDate.cxx @@ -279,7 +279,7 @@ input(std::istream &in) { string date; ch = in.get(); - while (!in.fail() && ch != EOF && ch != '"') { + while (!in.fail() && ch != '"') { date += ch; ch = in.get(); } diff --git a/panda/src/pgraph/cullTraverserData.h b/panda/src/pgraph/cullTraverserData.h index e275dcfd93..eca5f142dc 100644 --- a/panda/src/pgraph/cullTraverserData.h +++ b/panda/src/pgraph/cullTraverserData.h @@ -67,6 +67,8 @@ PUBLISHED: void apply_transform_and_state(CullTraverser *trav); void apply_transform(const TransformState *node_transform); + MAKE_PROPERTY(node_path, get_node_path); + private: // We store a chain leading all the way to the root, so that we can compose // a NodePath. We may be able to eliminate this requirement in the future. diff --git a/panda/src/pnmimagetypes/pnmFileTypePNM.cxx b/panda/src/pnmimagetypes/pnmFileTypePNM.cxx index cdc1e62f93..02411c0933 100644 --- a/panda/src/pnmimagetypes/pnmFileTypePNM.cxx +++ b/panda/src/pnmimagetypes/pnmFileTypePNM.cxx @@ -193,7 +193,7 @@ pm_getuint(istream * const ifP) { 'unsigned int'), issue an error message to stderr and abort the program. -----------------------------------------------------------------------------*/ - char ch; + int ch; unsigned int i; // skip whitespace