diff --git a/docs/credits_acknowledgements.rst b/docs/credits_acknowledgements.rst
index 264af4941..f3d3d3776 100644
--- a/docs/credits_acknowledgements.rst
+++ b/docs/credits_acknowledgements.rst
@@ -108,6 +108,10 @@ The following libraries and components are incorporated into RenderDoc, listed h
Used for converting capture files to and from xml.
+* `AOSP `_ - Copyright (c) 2006-2016, The Android Open Source Project, distributed under the Apache 2.0 License.
+
+ Used to simplify Android workflows by distributing some tools from the android SDK.
+
Thanks
------
diff --git a/qrenderdoc/Code/Interface/PersistantConfig.cpp b/qrenderdoc/Code/Interface/PersistantConfig.cpp
index 85189dbe3..75fe8416f 100644
--- a/qrenderdoc/Code/Interface/PersistantConfig.cpp
+++ b/qrenderdoc/Code/Interface/PersistantConfig.cpp
@@ -191,11 +191,13 @@ void PersistantConfig::AddAndroidHosts()
}
}
- QString adbExePath =
- QFile::exists(Android_AdbExecutablePath) ? QString(Android_AdbExecutablePath) : QString();
+ QString androidSDKPath = QFile::exists(Android_SDKPath) ? QString(Android_SDKPath) : QString();
- // Set the config setting as it will be reused when we start the remoteserver etc.
- SetConfigSetting("adbExePath", adbExePath);
+ SetConfigSetting("androidSDKPath", androidSDKPath);
+
+ QString androidJDKPath = QFile::exists(Android_JDKPath) ? QString(Android_JDKPath) : QString();
+
+ SetConfigSetting("androidJDKPath", androidJDKPath);
SetConfigSetting("MaxConnectTimeout", QString::number(Android_MaxConnectTimeout));
diff --git a/qrenderdoc/Code/Interface/PersistantConfig.h b/qrenderdoc/Code/Interface/PersistantConfig.h
index e6134301e..3504ab120 100644
--- a/qrenderdoc/Code/Interface/PersistantConfig.h
+++ b/qrenderdoc/Code/Interface/PersistantConfig.h
@@ -122,7 +122,9 @@ DECLARE_REFLECTION_STRUCT(SPIRVDisassembler);
\
CONFIG_SETTING_VAL(public, bool, bool, Font_PreferMonospaced, false) \
\
- CONFIG_SETTING_VAL(public, QString, rdcstr, Android_AdbExecutablePath, "") \
+ CONFIG_SETTING_VAL(public, QString, rdcstr, Android_SDKPath, "") \
+ \
+ CONFIG_SETTING_VAL(public, QString, rdcstr, Android_JDKPath, "") \
\
CONFIG_SETTING_VAL(public, int, int, Android_MaxConnectTimeout, 30) \
\
@@ -374,9 +376,17 @@ For more information about some of these settings that are user-facing see
Defaults to ``False``.
-.. data:: Android_AdbExecutablePath
+.. data:: Android_SDKPath
- The path to the ``adb`` executable to use for Android interaction.
+ The path to the root of the android SDK, to locate android tools to use for android remote hosts.
+
+ Defaults to using the tools distributed with RenderDoc.
+
+.. data:: Android_JDKPath
+
+ The path to the root of the Java JDK, to locate java for running android java tools.
+
+ Defaults to using the JAVA_HOME environment variable, if set.
.. data:: Android_MaxConnectTimeout
diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp b/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp
index 6db7ac587..982089c48 100644
--- a/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp
+++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp
@@ -84,7 +84,8 @@ SettingsDialog::SettingsDialog(ICaptureContext &ctx, QWidget *parent)
ui->externalDisassemblerArgs->setText(m_Ctx.Config().SPIRVDisassemblers[0].args);
ui->externalDisassemblePath->setText(m_Ctx.Config().SPIRVDisassemblers[0].executable);
}
- ui->Android_AdbExecutablePath->setText(m_Ctx.Config().Android_AdbExecutablePath);
+ ui->Android_SDKPath->setText(m_Ctx.Config().Android_SDKPath);
+ ui->Android_JDKPath->setText(m_Ctx.Config().Android_JDKPath);
ui->Android_MaxConnectTimeout->setValue(m_Ctx.Config().Android_MaxConnectTimeout);
ui->Android_AutoPushLayerToApp->setChecked(m_Ctx.Config().Android_AutoPushLayerToApp);
@@ -388,21 +389,52 @@ void SettingsDialog::on_browseTempCaptureDirectory_clicked()
m_Ctx.Config().Save();
}
-void SettingsDialog::on_browseAdbPath_clicked()
+void SettingsDialog::on_browseAndroidSDKPath_clicked()
{
- QString adb = RDDialog::getExecutableFileName(
- this, tr("Locate adb executable"),
- QFileInfo(m_Ctx.Config().Android_AdbExecutablePath).absoluteDir().path());
+ QString adb = RDDialog::getExistingDirectory(
+ this, tr("Locate SDK root folder (containing build-tools, platform-tools)"),
+ QFileInfo(m_Ctx.Config().Android_SDKPath).absoluteDir().path());
if(!adb.isEmpty())
{
- ui->Android_AdbExecutablePath->setText(adb);
- m_Ctx.Config().Android_AdbExecutablePath = adb;
+ ui->Android_SDKPath->setText(adb);
+ m_Ctx.Config().Android_SDKPath = adb;
}
m_Ctx.Config().Save();
}
+void SettingsDialog::on_Android_SDKPath_textEdited(const QString &adb)
+{
+ if(QFileInfo::exists(adb) || adb.isEmpty())
+ m_Ctx.Config().Android_SDKPath = adb;
+
+ m_Ctx.Config().Save();
+}
+
+void SettingsDialog::on_browseAndroidJDKPath_clicked()
+{
+ QString adb =
+ RDDialog::getExistingDirectory(this, tr("Locate JDK root folder (containing bin, jre, lib)"),
+ QFileInfo(m_Ctx.Config().Android_JDKPath).absoluteDir().path());
+
+ if(!adb.isEmpty())
+ {
+ ui->Android_JDKPath->setText(adb);
+ m_Ctx.Config().Android_JDKPath = adb;
+ }
+
+ m_Ctx.Config().Save();
+}
+
+void SettingsDialog::on_Android_JDKPath_textEdited(const QString &adb)
+{
+ if(QFileInfo::exists(adb) || adb.isEmpty())
+ m_Ctx.Config().Android_JDKPath = adb;
+
+ m_Ctx.Config().Save();
+}
+
void SettingsDialog::on_Android_MaxConnectTimeout_valueChanged(double timeout)
{
m_Ctx.Config().Android_MaxConnectTimeout = ui->Android_MaxConnectTimeout->value();
@@ -410,14 +442,6 @@ void SettingsDialog::on_Android_MaxConnectTimeout_valueChanged(double timeout)
m_Ctx.Config().Save();
}
-void SettingsDialog::on_Android_AdbExecutablePath_textEdited(const QString &adb)
-{
- if(QFileInfo::exists(adb) || adb.isEmpty())
- m_Ctx.Config().Android_AdbExecutablePath = adb;
-
- m_Ctx.Config().Save();
-}
-
void SettingsDialog::on_Android_AutoPushLayerToApp_toggled(bool checked)
{
m_Ctx.Config().Android_AutoPushLayerToApp = ui->Android_AutoPushLayerToApp->isChecked();
diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.h b/qrenderdoc/Windows/Dialogs/SettingsDialog.h
index 220eff2df..9e1591df7 100644
--- a/qrenderdoc/Windows/Dialogs/SettingsDialog.h
+++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.h
@@ -87,9 +87,11 @@ private slots:
// android
void on_browseTempCaptureDirectory_clicked();
- void on_browseAdbPath_clicked();
+ void on_browseAndroidSDKPath_clicked();
+ void on_browseAndroidJDKPath_clicked();
void on_Android_MaxConnectTimeout_valueChanged(double timeout);
- void on_Android_AdbExecutablePath_textEdited(const QString &path);
+ void on_Android_SDKPath_textEdited(const QString &path);
+ void on_Android_JDKPath_textEdited(const QString &path);
void on_Android_AutoPushLayerToApp_toggled(bool checked);
// manual slots
diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.ui b/qrenderdoc/Windows/Dialogs/SettingsDialog.ui
index 402bd3e93..fb5d14a23 100644
--- a/qrenderdoc/Windows/Dialogs/SettingsDialog.ui
+++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.ui
@@ -974,17 +974,24 @@ Only happens if the capture is not in the recent files list.
Android
+ -
+
+
+ Java JDK path
+
+
+
-
The location of adb.exe, used to control Android devices.
- Android ADB executable path
+ Android SDK root path
- -
+
-
Qt::Vertical
@@ -997,7 +1004,7 @@ Only happens if the capture is not in the recent files list.
- -
+
-
Automatically push the RenderDoc layer to applications that need it when running on a device with root access.
@@ -1008,7 +1015,7 @@ This can enable debugging of Vulkan apps that don't already contain the layer.
- -
+
-
Automatically push the RenderDoc layer to applications that need it when running on a device with root access.
@@ -1016,7 +1023,7 @@ This can enable debugging of Vulkan apps that don't already contain the layer.
- -
+
-
Maximum time to try connecting to the target app.
@@ -1026,7 +1033,7 @@ This can enable debugging of Vulkan apps that don't already contain the layer.
- -
+
-
Maximum time to try connecting to the target app.
@@ -1040,14 +1047,14 @@ This can enable debugging of Vulkan apps that don't already contain the layer.
-
-
+
The location of adb.exe, used to control Android devices.
-
-
+
The location of adb.exe, used to control Android devices.
@@ -1056,6 +1063,16 @@ This can enable debugging of Vulkan apps that don't already contain the layer.
+ -
+
+
+ -
+
+
+ Browse
+
+
+
diff --git a/renderdoc/core/android.cpp b/renderdoc/core/android.cpp
index 042cb209c..268aa2725 100644
--- a/renderdoc/core/android.cpp
+++ b/renderdoc/core/android.cpp
@@ -30,6 +30,245 @@
namespace Android
{
+enum class ToolDir
+{
+ None,
+ Java,
+ BuildTools,
+ BuildToolsLib,
+ PlatformTools,
+};
+static const char keystoreName[] = "renderdoc.keystore";
+bool toolExists(const std::string &path)
+{
+ if(path.empty())
+ return false;
+ return FileIO::exists(path.c_str()) || FileIO::exists((path + ".exe").c_str());
+}
+std::string getToolInSDK(ToolDir subdir, const std::string &jdkroot, const std::string &sdkroot,
+ const std::string &toolname)
+{
+ std::string toolpath;
+
+ switch(subdir)
+ {
+ case ToolDir::None:
+ {
+ // This indicates the file is not a standard tool and will not exist anywhere but our
+ // distributed folder.
+ break;
+ }
+ case ToolDir::Java:
+ {
+ // if no path is configured, abort
+ if(jdkroot.empty())
+ break;
+
+ toolpath = jdkroot + "/bin/" + toolname;
+
+ if(toolExists(toolpath))
+ return toolpath;
+
+ break;
+ }
+ case ToolDir::BuildTools:
+ case ToolDir::BuildToolsLib:
+ case ToolDir::PlatformTools:
+ {
+ // if no path is configured, abort
+ if(sdkroot.empty())
+ break;
+
+ // if it's in platform tools it's easy, just concatenate the path
+ if(subdir == ToolDir::PlatformTools)
+ {
+ toolpath = sdkroot + "/platform-tools/" + toolname;
+ }
+ else
+ {
+ // otherwise we need to find the build-tools versioned folder
+ toolpath = sdkroot + "/build-tools/";
+
+ std::vector paths = FileIO::GetFilesInDirectory(toolpath.c_str());
+
+ if(paths.empty())
+ break;
+
+ uint32_t bestversion = 0;
+ std::string bestpath;
+
+ for(const PathEntry &path : paths)
+ {
+ // skip non-directories
+ if(!(path.flags & PathProperty::Directory))
+ continue;
+
+ uint32_t version = 0;
+ bool valid = true;
+ for(char c : path.filename)
+ {
+ // add digits to the version
+ if(c >= '0' && c <= '9')
+ {
+ int digit = int(c) - int('0');
+ version *= 10;
+ version += digit;
+ continue;
+ }
+
+ // ignore .s
+ if(c == '.')
+ continue;
+
+ // if any char is not in [.0-9] then this filename is invalid
+ valid = false;
+ break;
+ }
+
+ // skip non-valid directories
+ if(!valid)
+ continue;
+
+ // if this directory is a higher version, prefer it
+ if(version > bestversion)
+ {
+ bestversion = version;
+ bestpath = path.filename;
+ }
+ }
+
+ // if we didn't find a version at all, abort
+ if(bestversion == 0)
+ break;
+
+ toolpath += bestpath + "/";
+
+ if(subdir == ToolDir::BuildToolsLib)
+ toolpath += "lib/";
+
+ toolpath += toolname;
+ }
+
+ if(toolExists(toolpath))
+ return toolpath;
+
+ break;
+ }
+ }
+
+ return "";
+}
+struct ToolPathCache
+{
+ std::string sdk, jdk;
+ std::map paths;
+} cache;
+std::string getToolPath(ToolDir subdir, const std::string &toolname, bool checkExist)
+{
+ // search path for tools:
+ // 1. First look relative to the configured paths, these come from the user manually setting them
+ // so they always have priority.
+ // 2. Next if those paths don't exist or the tool isn't found, we search relative to our
+ // executable looking for an android/ subfolder, and look for the tool in there.
+ // 3. If we still don't have that (most likely because it's a local build from a git clone and not
+ // a distributed build with the tools available) then we fall back to trying to auto-locate it.
+ // - First check if the tool is in the path, assuming the user configured it to their system.
+ // - Otherwise check environment variables or default locations
+
+ std::string sdk = RenderDoc::Inst().GetConfigSetting("androidSDKPath");
+ std::string jdk = RenderDoc::Inst().GetConfigSetting("androidJDKPath");
+
+ // invalidate the cache when these settings change
+ if(sdk != cache.sdk || jdk != cache.jdk)
+ {
+ cache.paths.clear();
+ cache.sdk = sdk;
+ cache.jdk = jdk;
+ }
+
+ // if we have the path cached and it's still valid, return it
+ if(toolExists(cache.paths[toolname]))
+ return cache.paths[toolname];
+
+ std::string &toolpath = cache.paths[toolname];
+
+ // first try according to the configured paths
+ toolpath = getToolInSDK(subdir, jdk, sdk, toolname);
+
+ if(toolExists(toolpath))
+ return toolpath;
+
+ // next try to locate it in our own distributed android subfolder
+ {
+ std::string exepath;
+ FileIO::GetExecutableFilename(exepath);
+ std::string exedir = dirname(FileIO::GetFullPathname(exepath));
+
+ toolpath = exedir + "/android/" + toolname;
+ if(toolExists(toolpath))
+ return toolpath;
+ }
+
+ // need to try to auto-guess the tool's location
+
+ // first try in PATH
+ if(subdir != ToolDir::None)
+ {
+ toolpath = FileIO::FindFileInPath(toolname);
+
+ if(toolExists(toolpath))
+ return toolpath;
+
+ // if the tool name contains a .jar then try stripping that and look for the non-.jar version in
+ // the PATH.
+ if(toolname.find(".jar") != std::string::npos)
+ {
+ toolpath = toolname;
+ toolpath.erase(toolpath.rfind(".jar"), 4);
+ toolpath = FileIO::FindFileInPath(toolpath);
+
+ if(toolExists(toolpath))
+ return toolpath;
+ }
+ }
+
+ // now try to find it based on heuristics/environment variables
+ const char *env = Process::GetEnvVariable("JAVA_HOME");
+
+ jdk = env ? env : "";
+
+ env = Process::GetEnvVariable("ANDROID_HOME");
+ sdk = env ? env : "";
+
+ if(sdk.empty() || !FileIO::exists(sdk.c_str()))
+ {
+ env = Process::GetEnvVariable("ANDROID_SDK_ROOT");
+ sdk = env ? env : "";
+ }
+
+ if(sdk.empty() || !FileIO::exists(sdk.c_str()))
+ {
+ env = Process::GetEnvVariable("ANDROID_SDK");
+ sdk = env ? env : "";
+ }
+
+ // maybe in future we can try to search in common install locations.
+
+ toolpath = getToolInSDK(subdir, jdk, sdk, toolname);
+
+ if(toolExists(toolpath))
+ return toolpath;
+
+ toolpath = "";
+
+ // if we're checking for existence, we have failed so return empty string.
+ if(checkExist)
+ return toolpath;
+
+ // otherwise we at least return the tool name so that there's something to try and run
+ return toolname;
+}
+
bool IsHostADB(const char *hostname)
{
return !strncmp(hostname, "adb:", 4);
@@ -76,35 +315,14 @@ Process::ProcessResult execCommand(const string &exe, const string &args,
}
Process::ProcessResult adbExecCommand(const string &device, const string &args, const string &workDir)
{
- string adbExePath = RenderDoc::Inst().GetConfigSetting("adbExePath");
- if(adbExePath.empty())
- {
- string exepath;
- FileIO::GetExecutableFilename(exepath);
- string exedir = dirname(FileIO::GetFullPathname(exepath));
-
- string adbpath = exedir + "/android/adb.exe";
- if(FileIO::exists(adbpath.c_str()))
- adbExePath = adbpath;
-
- if(adbExePath.empty())
- {
- static bool warnPath = true;
- if(warnPath)
- {
- RDCWARN("adbExePath not set, attempting to call 'adb' in working env");
- warnPath = false;
- }
- adbExePath = "adb";
- }
- }
+ std::string adb = getToolPath(ToolDir::PlatformTools, "adb", false);
Process::ProcessResult result;
string deviceArgs;
if(device.empty())
deviceArgs = args;
else
deviceArgs = StringFormat::Fmt("-s %s %s", device.c_str(), args.c_str());
- return execCommand(adbExePath, deviceArgs, workDir);
+ return execCommand(adb, deviceArgs, workDir);
}
string adbGetDeviceList()
{
@@ -175,8 +393,10 @@ bool RemoveAPKSignature(const string &apk)
{
RDCLOG("Checking for existing signature");
+ std::string aapt = getToolPath(ToolDir::BuildTools, "aapt", false);
+
// Get the list of files in META-INF
- string fileList = execCommand("aapt", "list " + apk).strStdout;
+ string fileList = execCommand(aapt, "list \"" + apk + "\"").strStdout;
if(fileList.empty())
return false;
@@ -193,7 +413,7 @@ bool RemoveAPKSignature(const string &apk)
if(line.compare(0, prefix.size(), prefix) == 0)
{
RDCDEBUG("Match found, removing %s", line.c_str());
- execCommand("aapt", "remove " + apk + " " + line);
+ execCommand(aapt, "remove \"" + apk + "\" " + line);
matchCount++;
}
}
@@ -201,7 +421,7 @@ bool RemoveAPKSignature(const string &apk)
// Ensure no hits on second pass through
RDCDEBUG("Walk through file list again, ensure signature removed");
- fileList = execCommand("aapt", "list " + apk).strStdout;
+ fileList = execCommand(aapt, "list \"" + apk + "\"").strStdout;
std::istringstream recheck(fileList);
while(std::getline(recheck, line))
{
@@ -219,10 +439,12 @@ bool AddLayerToAPK(const string &apk, const string &layerPath, const string &lay
{
RDCLOG("Adding RenderDoc layer");
+ std::string aapt = getToolPath(ToolDir::BuildTools, "aapt", false);
+
// Run aapt from the directory containing "lib" so the relative paths are good
string relativeLayer("lib/" + abi + "/" + layerName);
string workDir = removeFromEnd(layerPath, relativeLayer);
- Process::ProcessResult result = execCommand("aapt", "add " + apk + " " + relativeLayer, workDir);
+ Process::ProcessResult result = execCommand(aapt, "add \"" + apk + "\" " + relativeLayer, workDir);
if(result.strStdout.empty())
{
@@ -235,9 +457,12 @@ bool AddLayerToAPK(const string &apk, const string &layerPath, const string &lay
bool RealignAPK(const string &apk, string &alignedAPK, const string &tmpDir)
{
+ std::string zipalign = getToolPath(ToolDir::BuildTools, "zipalign", false);
+
// Re-align the APK for performance
RDCLOG("Realigning APK");
- string errOut = execCommand("zipalign", "-f 4 " + apk + " " + alignedAPK, tmpDir).strStderror;
+ string errOut =
+ execCommand(zipalign, "-f 4 \"" + apk + "\" \"" + alignedAPK + "\"", tmpDir).strStderror;
if(!errOut.empty())
return false;
@@ -263,14 +488,22 @@ bool RealignAPK(const string &apk, string &alignedAPK, const string &tmpDir)
string GetAndroidDebugKey()
{
- string key = FileIO::GetTempFolderFilename() + "debug.keystore";
+ std::string keystore = getToolPath(ToolDir::None, keystoreName, false);
+
+ // if we found the keystore, use that.
+ if(FileIO::exists(keystore.c_str()))
+ return keystore;
+
+ // otherwise, see if we generated a temporary one
+ string key = FileIO::GetTempFolderFilename() + keystoreName;
if(FileIO::exists(key.c_str()))
return key;
+ // locate keytool and use it to generate a keystore
string create;
create += " -genkey";
- create += " -keystore " + key;
+ create += " -keystore \"" + key + "\"";
create += " -storepass android";
create += " -alias androiddebugkey";
create += " -keypass android";
@@ -279,7 +512,9 @@ string GetAndroidDebugKey()
create += " -validity 10000";
create += " -dname \"CN=, OU=, O=, L=, S=, C=\"";
- Process::ProcessResult result = execCommand("keytool", create);
+ std::string keytool = getToolPath(ToolDir::Java, "keytool", false);
+
+ Process::ProcessResult result = execCommand(keytool, create);
if(!result.strStderror.empty())
RDCERR("Failed to create debug key");
@@ -290,19 +525,42 @@ bool DebugSignAPK(const string &apk, const string &workDir)
{
RDCLOG("Signing with debug key");
+ std::string aapt = getToolPath(ToolDir::BuildTools, "aapt", false);
+ std::string apksigner = getToolPath(ToolDir::BuildToolsLib, "apksigner.jar", false);
+
string debugKey = GetAndroidDebugKey();
string args;
args += " sign ";
- args += " --ks " + debugKey + " ";
+ args += " --ks \"" + debugKey + "\" ";
args += " --ks-pass pass:android ";
args += " --key-pass pass:android ";
args += " --ks-key-alias androiddebugkey ";
- args += apk;
- execScript("apksigner", args.c_str(), workDir.c_str());
+ args += "\"" + apk + "\"";
+
+ if(apksigner.find(".jar") == std::string::npos)
+ {
+ // if we found the non-jar version, then the jar wasn't located and we found the wrapper script
+ // in PATH. Execute it as a script
+ execScript(apksigner, args, workDir);
+ }
+ else
+ {
+ // otherwise, find and invoke java on the .jar
+ std::string java = getToolPath(ToolDir::Java, "java", false);
+
+ std::string signerdir = dirname(FileIO::GetFullPathname(apksigner));
+
+ std::string javaargs;
+ javaargs += " \"-Djava.ext.dirs=" + signerdir + "\"";
+ javaargs += " -jar \"" + apksigner + "\"";
+ javaargs += args;
+
+ execCommand(java, javaargs, workDir);
+ }
// Check for signature
- string list = execCommand("aapt", "list " + apk).strStdout;
+ string list = execCommand(aapt, "list \"" + apk + "\"").strStdout;
// Walk through the output. If it starts with META-INF, we're good
std::istringstream contents(list);
@@ -353,7 +611,7 @@ bool ReinstallPatchedAPK(const string &deviceID, const string &apk, const string
{
RDCLOG("Reinstalling APK");
- adbExecCommand(deviceID, "install --abi " + abi + " " + apk, workDir);
+ adbExecCommand(deviceID, "install --abi " + abi + " \"" + apk + "\"", workDir);
// Wait until re-install completes
string reinstallResult;
@@ -378,19 +636,41 @@ bool ReinstallPatchedAPK(const string &deviceID, const string &apk, const string
bool CheckPatchingRequirements()
{
- // check for aapt, zipalign, apksigner, debug key
- vector requirements;
- vector missingTools;
- requirements.push_back("aapt");
- requirements.push_back("zipalign");
- requirements.push_back("keytool");
- requirements.push_back("apksigner");
- requirements.push_back("java");
+ // check for required tools for patching
+ std::vector> requirements;
+ std::vector missingTools;
+ requirements.push_back(std::make_pair(ToolDir::BuildTools, "aapt"));
+ requirements.push_back(std::make_pair(ToolDir::BuildTools, "zipalign"));
+ requirements.push_back(std::make_pair(ToolDir::BuildToolsLib, "apksigner.jar"));
+ requirements.push_back(std::make_pair(ToolDir::Java, "java"));
for(uint32_t i = 0; i < requirements.size(); i++)
{
- if(FileIO::FindFileInPath(requirements[i]).empty())
- missingTools.push_back(requirements[i]);
+ std::string tool = getToolPath(requirements[i].first, requirements[i].second, true);
+
+ // if we located the tool, we're fine.
+ if(toolExists(tool))
+ continue;
+
+ // didn't find it.
+ missingTools.push_back(requirements[i].second);
+ }
+
+ // keytool is special - we look for a debug key first
+ {
+ std::string key = getToolPath(ToolDir::None, keystoreName, true);
+
+ if(key.empty())
+ {
+ // if we don't have the debug key, check that we can find keytool. First in our normal search
+ std::string tool = getToolPath(ToolDir::Java, "keytool", true);
+
+ if(tool.empty())
+ {
+ // if not, it's missing too
+ missingTools.push_back("keytool");
+ }
+ }
}
if(missingTools.size() > 0)
@@ -407,7 +687,7 @@ bool PullAPK(const string &deviceID, const string &pkgPath, const string &apk)
{
RDCLOG("Pulling APK to patch");
- adbExecCommand(deviceID, "pull " + pkgPath + " " + apk);
+ adbExecCommand(deviceID, "pull " + pkgPath + " \"" + apk + "\"");
// Wait until the apk lands
uint32_t elapsed = 0;
@@ -438,7 +718,9 @@ bool CheckAPKPermissions(const string &apk)
{
RDCLOG("Checking that APK can be can write to sdcard");
- string badging = execCommand("aapt", "dump badging " + apk).strStdout;
+ std::string aapt = getToolPath(ToolDir::BuildTools, "aapt", false);
+
+ string badging = execCommand(aapt, "dump badging \"" + apk + "\"").strStdout;
if(badging.empty())
{
@@ -452,7 +734,9 @@ bool CheckDebuggable(const string &apk)
{
RDCLOG("Checking that APK s debuggable");
- string badging = execCommand("aapt", "dump badging " + apk).strStdout;
+ std::string aapt = getToolPath(ToolDir::BuildTools, "aapt", false);
+
+ string badging = execCommand(aapt, "dump badging \"" + apk + "\"").strStdout;
if(badging.find("application-debuggable") == string::npos)
{
@@ -556,7 +840,7 @@ bool installRenderDocServer(const string &deviceID)
{
case Android_armeabi_v7a:
case Android_arm64_v8a:
- adbInstall = adbExecCommand(deviceID, "install -r -g " + serverApk).strStdout;
+ adbInstall = adbExecCommand(deviceID, "install -r -g \"" + serverApk + "\"").strStdout;
break;
case Android_armeabi:
case Android_x86:
@@ -781,7 +1065,7 @@ string FindAndroidLayer(const string &abi, const string &layerName)
"%s missing! RenderDoc for Android will not work without it. "
"Build your Android ABI in build-android in the root to have it "
"automatically found and installed.",
- layer.c_str());
+ layerName.c_str());
}
return layer;
diff --git a/scripts/dist.sh b/scripts/dist.sh
index 523bc720f..307372ba7 100644
--- a/scripts/dist.sh
+++ b/scripts/dist.sh
@@ -51,22 +51,31 @@ cp -R plugins-win32/ dist/Release32/plugins
find dist/Release{32,64}/ -iname '*.ipdb' -exec rm '{}' \;
find dist/Release{32,64}/ -iname '*.iobj' -exec rm '{}' \;
-if [ -f bin-android32/RenderDocCmd.apk ]; then
+ANDROID32="bin-android32"
+if [ -f build-android32/bin/RenderDocCmd.apk ]; then
+ ANDROID32="build-android32/bin";
+fi
+
+ANDROID64="bin-android64"
+if [ -f build-android64/bin/RenderDocCmd.apk ]; then
+ ANDROID64="build-android64/bin";
+fi
+
+if [ -f $ANDROID32/RenderDocCmd.apk ]; then
# Building for android, copy the apk and vulkan layer into folders
mkdir -p dist/Release64/android/apk dist/Release64/android/lib/armeabi-v7a
- cp bin-android32/RenderDocCmd.apk dist/Release64/android/apk
- cp bin-android32/libVkLayer_GLES_RenderDoc.so dist/Release64/android/lib/armeabi-v7a/libVkLayer_GLES_RenderDoc.so
+ cp $ANDROID32/RenderDocCmd.apk dist/Release64/android/apk
+ cp $ANDROID32/libVkLayer_GLES_RenderDoc.so dist/Release64/android/lib/armeabi-v7a/libVkLayer_GLES_RenderDoc.so
fi
-if [ -f bin-android64/RenderDocCmd.apk ]; then
- # We don't distribute the 64-bit apk, we use armeabi-v7a for both 32-bit and 64-bit
-
+if [ -f $ANDROID64/RenderDocCmd.apk ]; then
# Building for android, copy the vulkan layer into folder
mkdir -p dist/Release64/android/lib/arm64-v8a
- #cp bin-android64/RenderDocCmd.apk dist/Release64/android/apk/64
- cp bin-android64/libVkLayer_GLES_RenderDoc.so dist/Release64/android/lib/arm64-v8a/libVkLayer_GLES_RenderDoc.so
+ # We don't distribute the 64-bit apk, we use armeabi-v7a for both 32-bit and 64-bit
+ #cp $ANDROID64/RenderDocCmd.apk dist/Release64/android/apk/64
+ cp $ANDROID64/libVkLayer_GLES_RenderDoc.so dist/Release64/android/lib/arm64-v8a/libVkLayer_GLES_RenderDoc.so
fi
# try to copy adb.exe in as well, with its dll dependencies
@@ -74,6 +83,28 @@ if [ -f $ANDROID_SDK/platform-tools/adb.exe ] && [ -d dist/Release64/android ];
cp $ANDROID_SDK/platform-tools/{adb.exe,AdbWinApi.dll,AdbWinUsbApi.dll} dist/Release64/android/
fi
+# Copy tools needed for patching apks
+if [ -d "$ANDROID_SDK/build-tools" ] && [ -d dist/Release64/android ]; then
+ BUILD_DIR=$(ls "$ANDROID_SDK/build-tools" | grep -E '^[0-9.]*$' | sort -n | tail -n 1)
+
+ if [ -f "$ANDROID_SDK/build-tools/$BUILD_DIR/aapt.exe" ]; then
+ cp "$ANDROID_SDK/build-tools/$BUILD_DIR/aapt.exe" dist/Release64/android/
+ fi
+
+ if [ -f "$ANDROID_SDK/build-tools/$BUILD_DIR/zipalign.exe" ]; then
+ cp "$ANDROID_SDK/build-tools/$BUILD_DIR/zipalign.exe" dist/Release64/android/
+ fi
+
+ if [ -f "$ANDROID_SDK/build-tools/$BUILD_DIR/lib/apksigner.jar" ]; then
+ cp "$ANDROID_SDK/build-tools/$BUILD_DIR/lib/apksigner.jar" dist/Release64/android/
+ fi
+fi
+
+# Generate a debug key for signing purposes
+if [ -f "$JAVA_HOME/bin/keytool.exe" ] && [ -d dist/Release64/android ]; then
+ keytool -genkey -keystore dist/Release64/android/renderdoc.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=, OU=, O=, L=, S=, C="
+fi
+
if [ -d dist/Release64/android ]; then
cp -R dist/Release64/android dist/Release32/
fi
diff --git a/scripts/installer/LICENSE.rtf b/scripts/installer/LICENSE.rtf
index 2ae2e1826..27b690bfe 100644
--- a/scripts/installer/LICENSE.rtf
+++ b/scripts/installer/LICENSE.rtf
@@ -36,5 +36,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
{\field{\*\fldinst{HYPERLINK "{\pntext\f1\'B7\tab}https://github.com/facebook/zstd/"}}{\fldrslt{\ul\cf1 https://github.com/facebook/zstd/}}}\f0\fs22\line zstd distributed under the BSD License. Copyright 2016-present, Facebook, Inc.\par
{\field{\*\fldinst{HYPERLINK "{\pntext\f1\'B7\tab}https://github.com/philsquared/Catch"}}{\fldrslt{\ul\cf1 https://github.com/philsquared/Catch}}}\f0\fs22\line Catch distributed under the Boost Software License. Copyright (c) 2012 Two Blue Cubes Ltd.\par
{\field{\*\fldinst{HYPERLINK "{\pntext\f1\'B7\tab}https://pugixml.org"}}{\fldrslt{\ul\cf1 https://pugixml.org}}}\f0\fs22\line pugixml distributed under the MIT License. Copyright (c) 2006-2017 Arseny Kapoulkine.\par
+{\field{\*\fldinst{HYPERLINK "{\pntext\f1\'B7\tab}https://source.android.com"}}{\fldrslt{\ul\cf1 https://source.android.com}}}\f0\fs22\line components of AOSP distributed under the Apache 2.0 License. Copyright (c) 2006-2016 The Android Open Source Project.\par
}