support custom launch arguments (#84)

* add amStartArgs launch property to allow launch command arguments to be customised.

* update readme launch options docs
This commit is contained in:
Dave Holoway
2020-04-23 13:27:17 +01:00
committed by GitHub
parent 0672e54401
commit 9aeca6b96b
5 changed files with 99 additions and 25 deletions

View File

@@ -34,31 +34,61 @@ The following settings are used to configure the debugger:
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
// configuration type, request and name. "launch" is used to deploy the app to your device and start a debugging session // configuration type, request and name. "launch" is used to deploy the app
// to your device and start a debugging session.
"type": "android", "type": "android",
"request": "launch", "request": "launch",
"name": "Launch App", "name": "Launch App",
// Location of the App source files. This value must point to the root of your App source tree (containing AndroidManifest.xml) // Location of the App source files. This value must point to the root of
// your App source tree (containing AndroidManifest.xml).
"appSrcRoot": "${workspaceRoot}/app/src/main", "appSrcRoot": "${workspaceRoot}/app/src/main",
// Fully qualified path to the built APK (Android Application Package) // Fully qualified path to the built APK (Android Application Package).
"apkFile": "${workspaceRoot}/app/build/outputs/apk/app-debug.apk", "apkFile": "${workspaceRoot}/app/build/outputs/apk/app-debug.apk",
// Port number to connect to the local ADB (Android Debug Bridge) instance. Default: 5037 // Port number to connect to the local ADB (Android Debug Bridge) instance.
// Default: 5037
"adbPort": 5037, "adbPort": 5037,
// Launch behaviour if source files have been saved after the APK was built. One of: [ ignore warn stop ]. Default: warn // Automatically launch 'adb start-server' if not already started.
// Default: true
"autoStartADB": true,
// Launch behaviour if source files have been saved after the APK was built.
// One of: [ ignore warn stop ]. Default: warn
"staleBuild": "warn", "staleBuild": "warn",
// Fully qualified path to the AndroidManifest.xml file compiled in the APK. Default: appSrcRoot/AndroidManifest.xml // Target Device ID (as indicated by 'adb devices').
// Use this to specify which device is used for deployment
// when multiple devices are connected.
"targetDevice": "",
// Fully qualified path to the AndroidManifest.xml file compiled into the APK.
// Default: "${appSrcRoot}/AndroidManifest.xml"
"manifestFile": "${workspaceRoot}/app/src/main/AndroidManifest.xml", "manifestFile": "${workspaceRoot}/app/src/main/AndroidManifest.xml",
// APK install arguments passed to the Android package manager. Run 'adb shell pm' to show valid arguments. Default: ["-r"] // Custom arguments passed to the Android package manager to install the app.
// Run 'adb shell pm' to show valid arguments. Default: ["-r"]
"pmInstallArgs": ["-r"], "pmInstallArgs": ["-r"],
// Manually specify the activity to run when the app is started. // Custom arguments passed to the Android application manager to start the app.
"launchActivity": ".MainActivity" // Run `adb shell am` to show valid arguments.
// Note that `-D` is required to enable debugging.
"amStartArgs": [
"-D",
"--activity-brought-to-front",
"-a android.intent.action.MAIN",
"-c android.intent.category.LAUNCHER",
"-n package.name/launch.activity"
],
// Manually specify the activity to run when the app is started. This option is
// mutually exclusive with "amStartArgs".
"launchActivity": ".MainActivity",
// Set to true to output debugging logs for diagnostics.
"trace": false
} }
] ]
} }

View File

@@ -54,6 +54,17 @@
"adbPort" "adbPort"
], ],
"properties": { "properties": {
"amStartArgs": {
"type": "array",
"description": "Custom arguments to pass to the Android application manager to start the app. Run `adb shell am` to show valid arguments. Note that `-D` is required to enable debugging.\r\nBe careful using this option - you must specify the correct parameters or the app will not start.\r\n\r\nThis option is incompatible with the `launchActivity` option.",
"default": [
"-D",
"--activity-brought-to-front",
"-a android.intent.action.MAIN",
"-c android.intent.category.LAUNCHER",
"-n package.name/launch.activity"
]
},
"appSrcRoot": { "appSrcRoot": {
"type": "string", "type": "string",
"description": "Location of the App source files. This value must point to the root of your App source tree (containing AndroidManifest.xml)", "description": "Location of the App source files. This value must point to the root of your App source tree (containing AndroidManifest.xml)",

View File

@@ -53,6 +53,18 @@ class AndroidDebugSession extends DebugSession {
// the full file path name of the AndroidManifest.xml, taken from the manifestFile launch property // the full file path name of the AndroidManifest.xml, taken from the manifestFile launch property
this.manifest_fpn = ''; this.manifest_fpn = '';
/**
* array of custom arguments to pass to `pm install`
* @type {string[]}
*/
this.pm_install_args = null;
/**
* array of custom arguments to pass to `am start`
* @type {string[]}
*/
this.am_start_args = null;
/** /**
* the threads (from the last refreshThreads() call) * the threads (from the last refreshThreads() call)
* @type {AndroidThread[]} * @type {AndroidThread[]}
@@ -263,10 +275,18 @@ class AndroidDebugSession extends DebugSession {
this.app_src_root = ensure_path_end_slash(args.appSrcRoot); this.app_src_root = ensure_path_end_slash(args.appSrcRoot);
this.apk_fpn = args.apkFile; this.apk_fpn = args.apkFile;
this.manifest_fpn = args.manifestFile; this.manifest_fpn = args.manifestFile;
this.pmInstallArgs = args.pmInstallArgs; this.pm_install_args = args.pmInstallArgs;
this.am_start_args = args.amStartArgs;
if (typeof args.callStackDisplaySize === 'number' && args.callStackDisplaySize >= 0) if (typeof args.callStackDisplaySize === 'number' && args.callStackDisplaySize >= 0)
this.callStackDisplaySize = args.callStackDisplaySize|0; this.callStackDisplaySize = args.callStackDisplaySize|0;
// we don't allow both amStartArgs and launchActivity to be specified (the launch activity must be included in amStartArgs)
if (args.amStartArgs && args.launchActivity) {
this.LOG('amStartArgs and launchActivity options cannot both be specified in the launch configuration.');
this.sendEvent(new TerminatedEvent(false));
return;
}
// set the custom ADB port - this should be changed to pass it to each ADBClient instance // set the custom ADB port - this should be changed to pass it to each ADBClient instance
if (typeof args.adbPort === 'number' && args.adbPort >= 0 && args.adbPort <= 65535) { if (typeof args.adbPort === 'number' && args.adbPort >= 0 && args.adbPort <= 65535) {
ADBSocket.ADBPort = args.adbPort; ADBSocket.ADBPort = args.adbPort;
@@ -378,16 +398,24 @@ class AndroidDebugSession extends DebugSession {
} }
} }
startLaunchActivity(launchActivity) { async startLaunchActivity(launchActivity) {
if (!launchActivity) { if (!launchActivity) {
if (!(launchActivity = this.apk_file_info.manifest.launcher)) { // we're allowed no launchActivity if we have a custom am start command
throw new Error('No valid launch activity found in AndroidManifest.xml or launch.json'); if (!this.am_start_args) {
if (!(launchActivity = this.apk_file_info.manifest.launcher)) {
throw new Error('No valid launch activity found in AndroidManifest.xml or launch.json');
}
} }
} }
const build = new BuildInfo(this.apk_file_info.manifest.package, new Map(this.src_packages.packages), launchActivity); const build = new BuildInfo(this.apk_file_info.manifest.package, new Map(this.src_packages.packages), launchActivity, this.am_start_args);
this.LOG(`Launching ${build.pkgname}/${launchActivity} on device ${this._device.serial} [API:${this.device_api_level||'?'}]`);
return this.dbgr.startDebugSession(build, this._device.serial); this.LOG(`Launching on device ${this._device.serial} [API:${this.device_api_level||'?'}]`);
if (this.am_start_args) {
this.LOG(`Using custom launch arguments '${this.am_start_args.join(' ')}'`);
}
const am_stdout = await this.dbgr.startDebugSession(build, this._device.serial);
this.LOG(am_stdout);
} }
async configureAPISourcePath() { async configureAPISourcePath() {
@@ -440,7 +468,8 @@ class AndroidDebugSession extends DebugSession {
}) })
// send the install command // send the install command
this.LOG('Installing...'); this.LOG('Installing...');
const command = `pm install ${Array.isArray(this.pmInstallArgs) ? this.pmInstallArgs.join(' ') : '-r'} ${device_apk_fpn}`; const pm_install_args = Array.isArray(this.pm_install_args) ? this.pm_install_args.join(' ') : '-r';
const command = `pm install ${pm_install_args} ${device_apk_fpn}`;
D(command); D(command);
const stdout = await this._device.adbclient.shell_cmd({ const stdout = await this._device.adbclient.shell_cmd({
command, command,

View File

@@ -9,13 +9,14 @@ class BuildInfo {
* @param {string} pkgname * @param {string} pkgname
* @param {Map<string,PackageInfo>} packages * @param {Map<string,PackageInfo>} packages
* @param {string} launchActivity * @param {string} launchActivity
* @param {string[]} amCommandArgs custom arguments passed to `am start`
*/ */
constructor(pkgname, packages, launchActivity) { constructor(pkgname, packages, launchActivity, amCommandArgs) {
this.pkgname = pkgname; this.pkgname = pkgname;
this.packages = packages; this.packages = packages;
this.launchActivity = launchActivity; this.launchActivity = launchActivity;
/** the arguments passed to `am start` */ /** the arguments passed to `am start` */
this.startCommandArgs = [ this.startCommandArgs = amCommandArgs || [
'-D', // enable debugging '-D', // enable debugging
'--activity-brought-to-front', '--activity-brought-to-front',
'-a android.intent.action.MAIN', '-a android.intent.action.MAIN',

View File

@@ -29,7 +29,7 @@ const {
TypeNotAvailable, TypeNotAvailable,
} = require('./debugger-types'); } = require('./debugger-types');
class Debugger extends EventEmitter { class Debugger extends EventEmitter {
constructor () { constructor () {
super(); super();
@@ -76,7 +76,7 @@ class Debugger extends EventEmitter {
*/ */
async startDebugSession(build, deviceid) { async startDebugSession(build, deviceid) {
this.session = new DebugSession(build, deviceid); this.session = new DebugSession(build, deviceid);
await Debugger.runApp(deviceid, build.startCommandArgs, build.postLaunchPause); const stdout = await Debugger.runApp(deviceid, build.startCommandArgs, build.postLaunchPause);
// retrieve the list of debuggable processes // retrieve the list of debuggable processes
const pids = await this.getDebuggablePIDs(this.session.deviceid); const pids = await this.getDebuggablePIDs(this.session.deviceid);
@@ -84,6 +84,7 @@ class Debugger extends EventEmitter {
const pid = pids[pids.length - 1]; const pid = pids[pids.length - 1];
// after connect(), the caller must call resume() to begin // after connect(), the caller must call resume() to begin
await this.connect(pid); await this.connect(pid);
return stdout;
} }
/** /**
@@ -94,7 +95,7 @@ class Debugger extends EventEmitter {
static async runApp(deviceid, launch_cmd_args, post_launch_pause = 1000) { static async runApp(deviceid, launch_cmd_args, post_launch_pause = 1000) {
// older (<3) versions of Android only allow target components to be specified with -n // older (<3) versions of Android only allow target components to be specified with -n
const shell_cmd = { const shell_cmd = {
command: 'am start ' + launch_cmd_args.join(' '), command: `am start ${launch_cmd_args.join(' ')}`,
}; };
let retries = 10 let retries = 10
for (;;) { for (;;) {
@@ -104,12 +105,14 @@ class Debugger extends EventEmitter {
await sleep(post_launch_pause); await sleep(post_launch_pause);
// failures: // failures:
// Error: Activity not started... // Error: Activity not started...
const m = stdout.match(/Error:.*/g); // /system/bin/sh: syntax error: unexpected EOF - this happens with invalid am command arguments
const m = stdout.match(/Error:.*|syntax error:/gi);
if (!m) { if (!m) {
break; // return the stdout from am (it shows the fully qualified component name)
return stdout.toString().trim();
} }
else if (retries <= 0){ else if (retries <= 0){
throw new Error(m[0]); throw new Error(stdout.toString().trim());
} }
retries -= 1; retries -= 1;
} }