Add --use-application-binary to "flutter install" (#101324)
This commit is contained in:
parent
d08f7ab9c9
commit
d745eec051
@ -42,7 +42,7 @@ class BuildIOSCommand extends _BuildIOSSubCommand {
|
|||||||
final String name = 'ios';
|
final String name = 'ios';
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
final XcodeBuildAction xcodeBuildAction = XcodeBuildAction.build;
|
final XcodeBuildAction xcodeBuildAction = XcodeBuildAction.build;
|
||||||
@ -94,7 +94,7 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
|
|||||||
final List<String> aliases = <String>['xcarchive'];
|
final List<String> aliases = <String>['xcarchive'];
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
final XcodeBuildAction xcodeBuildAction = XcodeBuildAction.archive;
|
final XcodeBuildAction xcodeBuildAction = XcodeBuildAction.archive;
|
||||||
|
@ -92,7 +92,7 @@ class DriveCommand extends RunCommandBase {
|
|||||||
)
|
)
|
||||||
..addFlag('build',
|
..addFlag('build',
|
||||||
defaultsTo: true,
|
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.',
|
'flag with an existing APK.',
|
||||||
)
|
)
|
||||||
..addOption('screenshot',
|
..addOption('screenshot',
|
||||||
@ -209,7 +209,8 @@ class DriveCommand extends RunCommandBase {
|
|||||||
if (await _fileSystem.type(testFile) != FileSystemEntityType.file) {
|
if (await _fileSystem.type(testFile) != FileSystemEntityType.file) {
|
||||||
throwToolExit('Test file not found: $testFile');
|
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) {
|
if (device == null) {
|
||||||
throwToolExit(null);
|
throwToolExit(null);
|
||||||
}
|
}
|
||||||
@ -233,9 +234,9 @@ class DriveCommand extends RunCommandBase {
|
|||||||
final DriverService driverService = _flutterDriverFactory.createDriverService(web);
|
final DriverService driverService = _flutterDriverFactory.createDriverService(web);
|
||||||
final BuildInfo buildInfo = await getBuildInfo();
|
final BuildInfo buildInfo = await getBuildInfo();
|
||||||
final DebuggingOptions debuggingOptions = await createDebuggingOptions(web);
|
final DebuggingOptions debuggingOptions = await createDebuggingOptions(web);
|
||||||
final File applicationBinary = stringArg('use-application-binary') == null
|
final File applicationBinary = applicationBinaryPath == null
|
||||||
? null
|
? null
|
||||||
: _fileSystem.file(stringArg('use-application-binary'));
|
: _fileSystem.file(applicationBinaryPath);
|
||||||
|
|
||||||
bool screenshotTaken = false;
|
bool screenshotTaken = false;
|
||||||
try {
|
try {
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import '../android/android_device.dart';
|
import '../android/android_device.dart';
|
||||||
import '../application_package.dart';
|
import '../application_package.dart';
|
||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
|
import '../base/file_system.dart';
|
||||||
import '../base/io.dart';
|
import '../base/io.dart';
|
||||||
import '../device.dart';
|
import '../device.dart';
|
||||||
import '../globals.dart' as globals;
|
import '../globals.dart' as globals;
|
||||||
@ -15,6 +16,7 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
|
|||||||
requiresPubspecYaml();
|
requiresPubspecYaml();
|
||||||
usesDeviceUserOption();
|
usesDeviceUserOption();
|
||||||
usesDeviceTimeoutOption();
|
usesDeviceTimeoutOption();
|
||||||
|
usesApplicationBinaryOption();
|
||||||
argParser.addFlag('uninstall-only',
|
argParser.addFlag('uninstall-only',
|
||||||
help: 'Uninstall the app if already on the device. Skip install.',
|
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');
|
bool get uninstallOnly => boolArg('uninstall-only');
|
||||||
String? get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
|
String? get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
|
||||||
|
|
||||||
|
String? get _applicationBinaryPath => stringArg(FlutterOptions.kUseApplicationBinary);
|
||||||
|
File? get _applicationBinary => _applicationBinaryPath == null ? null : globals.fs.file(_applicationBinaryPath);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> validateCommand() async {
|
Future<void> validateCommand() async {
|
||||||
await super.validateCommand();
|
await super.validateCommand();
|
||||||
@ -44,6 +49,9 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
|
|||||||
if (userIdentifier != null && device is! AndroidDevice) {
|
if (userIdentifier != null && device is! AndroidDevice) {
|
||||||
throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android');
|
throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android');
|
||||||
}
|
}
|
||||||
|
if (_applicationBinaryPath != null && !(_applicationBinary?.existsSync() ?? true)) {
|
||||||
|
throwToolExit('Prebuilt binary $_applicationBinaryPath does not exist');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -51,6 +59,7 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
|
|||||||
final Device targetDevice = device!;
|
final Device targetDevice = device!;
|
||||||
final ApplicationPackage? package = await applicationPackages?.getPackageForPlatform(
|
final ApplicationPackage? package = await applicationPackages?.getPackageForPlatform(
|
||||||
await targetDevice.targetPlatform,
|
await targetDevice.targetPlatform,
|
||||||
|
applicationBinary: _applicationBinary,
|
||||||
);
|
);
|
||||||
if (package == null) {
|
if (package == null) {
|
||||||
throwToolExit('Could not find or build package');
|
throwToolExit('Could not find or build package');
|
||||||
|
@ -38,6 +38,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
|
|||||||
usesWebRendererOption();
|
usesWebRendererOption();
|
||||||
addNativeNullAssertions(hide: !verboseHelp);
|
addNativeNullAssertions(hide: !verboseHelp);
|
||||||
addBundleSkSLPathOption(hide: !verboseHelp);
|
addBundleSkSLPathOption(hide: !verboseHelp);
|
||||||
|
usesApplicationBinaryOption();
|
||||||
argParser
|
argParser
|
||||||
..addFlag('trace-startup',
|
..addFlag('trace-startup',
|
||||||
negatable: false,
|
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. '
|
help: '(deprecated) Allow connections to the VM service without using authentication codes. '
|
||||||
'(Not recommended! This can open your device to remote code execution attacks!)'
|
'(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',
|
..addFlag('start-paused',
|
||||||
defaultsTo: startPausedDefault,
|
defaultsTo: startPausedDefault,
|
||||||
help: 'Start in a paused mode and wait for a debugger to connect.',
|
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 purgePersistentCache => boolArg('purge-persistent-cache');
|
||||||
bool get disableServiceAuthCodes => boolArg('disable-service-auth-codes');
|
bool get disableServiceAuthCodes => boolArg('disable-service-auth-codes');
|
||||||
bool get cacheStartupProfile => boolArg('cache-startup-profile');
|
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 trackWidgetCreation => boolArg('track-widget-creation');
|
||||||
bool get enableImpeller => boolArg('enable-impeller');
|
bool get enableImpeller => boolArg('enable-impeller');
|
||||||
|
|
||||||
@ -347,7 +342,7 @@ class RunCommand extends RunCommandBase {
|
|||||||
defaultsTo: false,
|
defaultsTo: false,
|
||||||
help: 'Whether to quickly bootstrap applications with a minimal app. '
|
help: 'Whether to quickly bootstrap applications with a minimal app. '
|
||||||
'Currently this is only supported on Android devices. This option '
|
'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,
|
hide: !verboseHelp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -484,7 +479,7 @@ class RunCommand extends RunCommandBase {
|
|||||||
throwToolExit(null);
|
throwToolExit(null);
|
||||||
}
|
}
|
||||||
if (globals.deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication) {
|
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
|
if (userIdentifier != null
|
||||||
@ -562,7 +557,7 @@ class RunCommand extends RunCommandBase {
|
|||||||
// debug mode.
|
// debug mode.
|
||||||
final BuildInfo buildInfo = await getBuildInfo();
|
final BuildInfo buildInfo = await getBuildInfo();
|
||||||
final bool hotMode = shouldUseHotMode(buildInfo);
|
final bool hotMode = shouldUseHotMode(buildInfo);
|
||||||
final String applicationBinaryPath = stringArg('use-application-binary');
|
final String applicationBinaryPath = stringArg(FlutterOptions.kUseApplicationBinary);
|
||||||
|
|
||||||
if (boolArg('machine')) {
|
if (boolArg('machine')) {
|
||||||
if (devices.length > 1) {
|
if (devices.length > 1) {
|
||||||
|
@ -117,6 +117,7 @@ class FlutterOptions {
|
|||||||
static const String kInitializeFromDill = 'initialize-from-dill';
|
static const String kInitializeFromDill = 'initialize-from-dill';
|
||||||
static const String kAssumeInitializeFromDillUpToDate = 'assume-initialize-from-dill-up-to-date';
|
static const String kAssumeInitializeFromDillUpToDate = 'assume-initialize-from-dill-up-to-date';
|
||||||
static const String kFatalWarnings = 'fatal-warnings';
|
static const String kFatalWarnings = 'fatal-warnings';
|
||||||
|
static const String kUseApplicationBinary = 'use-application-binary';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// flutter command categories for usage.
|
/// 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.
|
/// Whether it is safe for this command to use a cached pub invocation.
|
||||||
bool get cachePubGet => true;
|
bool get cachePubGet => true;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
// @dart = 2.8
|
// @dart = 2.8
|
||||||
|
|
||||||
import 'package:file/file.dart';
|
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/android_device.dart';
|
||||||
import 'package:flutter_tools/src/android/application_package.dart';
|
import 'package:flutter_tools/src/android/application_package.dart';
|
||||||
import 'package:flutter_tools/src/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/common.dart';
|
||||||
import '../../src/context.dart';
|
import '../../src/context.dart';
|
||||||
|
import '../../src/fake_process_manager.dart';
|
||||||
import '../../src/test_flutter_command_runner.dart';
|
import '../../src/test_flutter_command_runner.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -26,6 +28,12 @@ void main() {
|
|||||||
Cache.disableLocking();
|
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 {
|
testUsingContext('returns 0 when Android is connected and ready for an install', () async {
|
||||||
final InstallCommand command = InstallCommand();
|
final InstallCommand command = InstallCommand();
|
||||||
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
|
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
|
||||||
@ -36,6 +44,8 @@ void main() {
|
|||||||
await createTestCommandRunner(command).run(<String>['install']);
|
await createTestCommandRunner(command).run(<String>['install']);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('returns 1 when targeted device is not Android with --device-user', () async {
|
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'));
|
throwsToolExit(message: '--device-user is only supported for Android'));
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('returns 0 when iOS is connected and ready for an install', () async {
|
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']);
|
await createTestCommandRunner(command).run(<String>['install']);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
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(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user