diff --git a/makepanda/makepackage.py b/makepanda/makepackage.py index c11651283a..9b34192e94 100755 --- a/makepanda/makepackage.py +++ b/makepanda/makepackage.py @@ -7,6 +7,7 @@ import os import shutil import glob import re +import subprocess INSTALLER_DEB_FILE = """ @@ -837,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] @@ -859,24 +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 ' ' in line: - line = line.split(' ', 1)[0] + # 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 line: - dep = line.rpartition('.so.')[0] + '.so' - oscmd("patchelf --replace-needed %s %s %s" % (line, dep, target), True) - else: - dep = line + 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: @@ -969,11 +973,12 @@ def MakeInstallerAndroid(version, **kwargs): 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)) @@ -984,7 +989,7 @@ def MakeInstallerAndroid(version, **kwargs): 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("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. diff --git a/makepanda/makepandacore.py b/makepanda/makepandacore.py index 6babeb7fd8..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))) @@ -2468,9 +2481,15 @@ def SdkLocateAndroid(): 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): @@ -2550,6 +2569,8 @@ def SdkLocateAndroid(): # 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 = [] @@ -2564,6 +2585,24 @@ def SdkLocateAndroid(): 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 @@ -2831,6 +2870,10 @@ def SetupBuildEnvironment(compiler): 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() @@ -2882,10 +2925,19 @@ 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() + " -x c++ -v -E " + os.devnull if "ANDROID_NDK" in SDK: - cmd += " --sysroot=%s/sysroot" % (SDK["ANDROID_NDK"].replace('\\', '/')) + 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