Unverified Commit d745eec0 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Add --use-application-binary to "flutter install" (#101324)

parent d08f7ab9
......@@ -42,7 +42,7 @@ class BuildIOSCommand extends _BuildIOSSubCommand {
final String name = 'ios';
@override
final String description = 'Build an iOS application bundle (Mac OS X host only).';
final String description = 'Build an iOS application bundle (macOS host only).';
@override
final XcodeBuildAction xcodeBuildAction = XcodeBuildAction.build;
......@@ -94,7 +94,7 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
final List<String> aliases = <String>['xcarchive'];
@override
final String description = 'Build an iOS archive bundle and IPA for distribution (Mac OS X host only).';
final String description = 'Build an iOS archive bundle and IPA for distribution (macOS host only).';
@override
final XcodeBuildAction xcodeBuildAction = XcodeBuildAction.archive;
......
......@@ -92,7 +92,7 @@ class DriveCommand extends RunCommandBase {
)
..addFlag('build',
defaultsTo: true,
help: '(deprecated) Build the app before running. To use an existing app, pass the "--use-application-binary" '
help: '(deprecated) Build the app before running. To use an existing app, pass the "--${FlutterOptions.kUseApplicationBinary}" '
'flag with an existing APK.',
)
..addOption('screenshot',
......@@ -209,7 +209,8 @@ class DriveCommand extends RunCommandBase {
if (await _fileSystem.type(testFile) != FileSystemEntityType.file) {
throwToolExit('Test file not found: $testFile');
}
final Device device = await findTargetDevice(includeUnsupportedDevices: stringArg('use-application-binary') == null);
final String applicationBinaryPath = stringArg(FlutterOptions.kUseApplicationBinary);
final Device device = await findTargetDevice(includeUnsupportedDevices: applicationBinaryPath == null);
if (device == null) {
throwToolExit(null);
}
......@@ -233,9 +234,9 @@ class DriveCommand extends RunCommandBase {
final DriverService driverService = _flutterDriverFactory.createDriverService(web);
final BuildInfo buildInfo = await getBuildInfo();
final DebuggingOptions debuggingOptions = await createDebuggingOptions(web);
final File applicationBinary = stringArg('use-application-binary') == null
final File applicationBinary = applicationBinaryPath == null
? null
: _fileSystem.file(stringArg('use-application-binary'));
: _fileSystem.file(applicationBinaryPath);
bool screenshotTaken = false;
try {
......
......@@ -5,6 +5,7 @@
import '../android/android_device.dart';
import '../application_package.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../device.dart';
import '../globals.dart' as globals;
......@@ -15,6 +16,7 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
requiresPubspecYaml();
usesDeviceUserOption();
usesDeviceTimeoutOption();
usesApplicationBinaryOption();
argParser.addFlag('uninstall-only',
help: 'Uninstall the app if already on the device. Skip install.',
);
......@@ -34,6 +36,9 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
bool get uninstallOnly => boolArg('uninstall-only');
String? get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
String? get _applicationBinaryPath => stringArg(FlutterOptions.kUseApplicationBinary);
File? get _applicationBinary => _applicationBinaryPath == null ? null : globals.fs.file(_applicationBinaryPath);
@override
Future<void> validateCommand() async {
await super.validateCommand();
......@@ -44,6 +49,9 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
if (userIdentifier != null && device is! AndroidDevice) {
throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android');
}
if (_applicationBinaryPath != null && !(_applicationBinary?.existsSync() ?? true)) {
throwToolExit('Prebuilt binary $_applicationBinaryPath does not exist');
}
}
@override
......@@ -51,6 +59,7 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
final Device targetDevice = device!;
final ApplicationPackage? package = await applicationPackages?.getPackageForPlatform(
await targetDevice.targetPlatform,
applicationBinary: _applicationBinary,
);
if (package == null) {
throwToolExit('Could not find or build package');
......
......@@ -38,6 +38,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
usesWebRendererOption();
addNativeNullAssertions(hide: !verboseHelp);
addBundleSkSLPathOption(hide: !verboseHelp);
usesApplicationBinaryOption();
argParser
..addFlag('trace-startup',
negatable: false,
......@@ -87,12 +88,6 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
help: '(deprecated) Allow connections to the VM service without using authentication codes. '
'(Not recommended! This can open your device to remote code execution attacks!)'
)
..addOption('use-application-binary',
help: 'Specify a pre-built application binary to use when running. For Android applications, '
'this must be the path to an APK. For iOS applications, the path to an IPA. Other device types '
'do not yet support prebuilt application binaries.',
valueHelp: 'path/to/app.apk',
)
..addFlag('start-paused',
defaultsTo: startPausedDefault,
help: 'Start in a paused mode and wait for a debugger to connect.',
......@@ -168,7 +163,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
bool get purgePersistentCache => boolArg('purge-persistent-cache');
bool get disableServiceAuthCodes => boolArg('disable-service-auth-codes');
bool get cacheStartupProfile => boolArg('cache-startup-profile');
bool get runningWithPrebuiltApplication => argResults['use-application-binary'] != null;
bool get runningWithPrebuiltApplication => argResults[FlutterOptions.kUseApplicationBinary] != null;
bool get trackWidgetCreation => boolArg('track-widget-creation');
bool get enableImpeller => boolArg('enable-impeller');
......@@ -347,7 +342,7 @@ class RunCommand extends RunCommandBase {
defaultsTo: false,
help: 'Whether to quickly bootstrap applications with a minimal app. '
'Currently this is only supported on Android devices. This option '
'cannot be paired with "--use-application-binary".',
'cannot be paired with "--${FlutterOptions.kUseApplicationBinary}".',
hide: !verboseHelp,
);
}
......@@ -484,7 +479,7 @@ class RunCommand extends RunCommandBase {
throwToolExit(null);
}
if (globals.deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication) {
throwToolExit('Using "-d all" with "--use-application-binary" is not supported');
throwToolExit('Using "-d all" with "--${FlutterOptions.kUseApplicationBinary}" is not supported');
}
if (userIdentifier != null
......@@ -562,7 +557,7 @@ class RunCommand extends RunCommandBase {
// debug mode.
final BuildInfo buildInfo = await getBuildInfo();
final bool hotMode = shouldUseHotMode(buildInfo);
final String applicationBinaryPath = stringArg('use-application-binary');
final String applicationBinaryPath = stringArg(FlutterOptions.kUseApplicationBinary);
if (boolArg('machine')) {
if (devices.length > 1) {
......
......@@ -117,6 +117,7 @@ class FlutterOptions {
static const String kInitializeFromDill = 'initialize-from-dill';
static const String kAssumeInitializeFromDillUpToDate = 'assume-initialize-from-dill-up-to-date';
static const String kFatalWarnings = 'fatal-warnings';
static const String kUseApplicationBinary = 'use-application-binary';
}
/// flutter command categories for usage.
......@@ -606,6 +607,16 @@ abstract class FlutterCommand extends Command<void> {
);
}
void usesApplicationBinaryOption() {
argParser.addOption(
FlutterOptions.kUseApplicationBinary,
help: 'Specify a pre-built application binary to use when running. For Android applications, '
'this must be the path to an APK. For iOS applications, the path to an IPA. Other device types '
'do not yet support prebuilt application binaries.',
valueHelp: 'path/to/app.apk',
);
}
/// Whether it is safe for this command to use a cached pub invocation.
bool get cachePubGet => true;
......
......@@ -5,6 +5,7 @@
// @dart = 2.8
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_device.dart';
import 'package:flutter_tools/src/android/application_package.dart';
import 'package:flutter_tools/src/application_package.dart';
......@@ -18,6 +19,7 @@ import 'package:test/fake.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fake_process_manager.dart';
import '../../src/test_flutter_command_runner.dart';
void main() {
......@@ -26,6 +28,12 @@ void main() {
Cache.disableLocking();
});
FileSystem fileSystem;
setUp(() {
fileSystem = MemoryFileSystem.test();
fileSystem.file('pubspec.yaml').createSync(recursive: true);
});
testUsingContext('returns 0 when Android is connected and ready for an install', () async {
final InstallCommand command = InstallCommand();
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
......@@ -36,6 +44,8 @@ void main() {
await createTestCommandRunner(command).run(<String>['install']);
}, overrides: <Type, Generator>{
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('returns 1 when targeted device is not Android with --device-user', () async {
......@@ -49,6 +59,8 @@ void main() {
throwsToolExit(message: '--device-user is only supported for Android'));
}, overrides: <Type, Generator>{
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('returns 0 when iOS is connected and ready for an install', () async {
......@@ -61,6 +73,38 @@ void main() {
await createTestCommandRunner(command).run(<String>['install']);
}, overrides: <Type, Generator>{
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('fails when prebuilt binary not found', () async {
final InstallCommand command = InstallCommand();
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final FakeAndroidDevice device = FakeAndroidDevice();
testDeviceManager.addDevice(device);
expect(() async => createTestCommandRunner(command).run(<String>['install', '--use-application-binary', 'bogus']),
throwsToolExit(message: 'Prebuilt binary bogus does not exist'));
}, overrides: <Type, Generator>{
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('succeeds using prebuilt binary', () async {
final InstallCommand command = InstallCommand();
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final FakeAndroidDevice device = FakeAndroidDevice();
testDeviceManager.addDevice(device);
fileSystem.file('binary').createSync(recursive: true);
await createTestCommandRunner(command).run(<String>['install', '--use-application-binary', 'binary']);
}, overrides: <Type, Generator>{
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
});
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment