blob: dc015938d58d6e5d0dc820dd8a3b9de8e9f9c045 [file]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' show ProcessStartMode;
import 'package:engine_build_configs/engine_build_configs.dart';
import 'package:process_runner/process_runner.dart';
import '../build_utils.dart';
import '../label.dart';
import '../run_utils.dart';
import 'command.dart';
import 'flags.dart';
/// The root 'run' command.
final class RunCommand extends CommandBase {
/// Constructs the 'run' command.
RunCommand({
required super.environment,
required Map<String, BuilderConfig> configs,
super.help = false,
super.usageLineLength,
}) {
// When printing the help/usage for this command, only list all builds
// when the --verbose flag is supplied.
final bool includeCiBuilds = environment.verbose || !help;
builds = runnableBuilds(environment, configs, includeCiBuilds);
debugCheckBuilds(builds);
// We default to nothing in order to automatically detect attached devices
// and select an appropriate target from them.
addConfigOption(
environment,
argParser,
builds,
defaultsTo: '',
);
addConcurrencyOption(argParser);
argParser.addFlag(
rbeFlag,
defaultsTo: environment.hasRbeConfigInTree(),
help: 'RBE is enabled by default when available.',
);
}
/// List of compatible builds.
late final List<Build> builds;
@override
String get name => 'run';
@override
String get description => '''
Run a Flutter app with a local engine build.
All arguments after -- are forwarded to flutter run, e.g.:
et run -- --profile
et run -- -d macos
See `flutter run --help` for a listing
''';
Build? _lookup(String configName) {
final String demangledName = demangleConfigName(environment, configName);
return builds
.where((Build build) => build.name == demangledName)
.firstOrNull;
}
Build? _findHostBuild(Build? targetBuild) {
if (targetBuild == null) {
return null;
}
final String mangledName = mangleConfigName(environment, targetBuild.name);
if (mangledName.contains('host_')) {
return targetBuild;
}
// TODO(johnmccutchan): This is brittle, it would be better if we encoded
// the host config name in the target config.
final String ci =
mangledName.startsWith('ci') ? mangledName.substring(0, 3) : '';
if (mangledName.contains('_debug')) {
return _lookup('${ci}host_debug');
} else if (mangledName.contains('_profile')) {
return _lookup('${ci}host_profile');
} else if (mangledName.contains('_release')) {
return _lookup('${ci}host_release');
}
return null;
}
String _getDeviceId() {
if (argResults!.rest.contains('-d')) {
final int index = argResults!.rest.indexOf('-d') + 1;
if (index < argResults!.rest.length) {
return argResults!.rest[index];
}
}
if (argResults!.rest.contains('--device-id')) {
final int index = argResults!.rest.indexOf('--device-id') + 1;
if (index < argResults!.rest.length) {
return argResults!.rest[index];
}
}
return '';
}
String _getMode() {
// Sniff the build mode from the args that will be passed to flutter run.
String mode = 'debug';
if (argResults!.rest.contains('--profile')) {
mode = 'profile';
} else if (argResults!.rest.contains('--release')) {
mode = 'release';
}
return mode;
}
late final Future<RunTarget?> _runTarget =
detectAndSelectRunTarget(environment, _getDeviceId());
Future<String?> _selectTargetConfig() async {
final String configName = argResults![configFlag] as String;
if (configName.isNotEmpty) {
return demangleConfigName(environment, configName);
}
final RunTarget? target = await _runTarget;
if (target == null) {
return demangleConfigName(environment, 'host_debug');
}
environment.logger.status(
'Building to run on "${target.name}" running ${target.targetPlatform}');
return target.buildConfigFor(_getMode());
}
@override
Future<int> run() async {
if (!environment.processRunner.processManager.canRun('flutter')) {
environment.logger.error('Cannot find the flutter command in your path');
return 1;
}
final String? configName = await _selectTargetConfig();
if (configName == null) {
environment.logger.error('Could not find target config');
return 1;
}
final Build? build = _lookup(configName);
final Build? hostBuild = _findHostBuild(build);
if (build == null) {
environment.logger.error('Could not find build $configName');
return 1;
}
if (hostBuild == null) {
environment.logger.error('Could not find host build for $configName');
return 1;
}
final bool useRbe = argResults![rbeFlag] as bool;
if (useRbe && !environment.hasRbeConfigInTree()) {
environment.logger.error('RBE was requested but no RBE config was found');
return 1;
}
final List<String> extraGnArgs = <String>[
if (!useRbe) '--no-rbe',
];
final RunTarget? target = await _runTarget;
final List<Label> buildTargetsForShell =
target?.buildTargetsForShell() ?? <Label>[];
final String dashJ = argResults![concurrencyFlag] as String;
final int? concurrency = int.tryParse(dashJ);
if (concurrency == null || concurrency < 0) {
environment.logger.error('-j must specify a positive integer.');
return 1;
}
// First build the host.
int r = await runBuild(
environment,
hostBuild,
concurrency: concurrency,
extraGnArgs: extraGnArgs,
enableRbe: useRbe,
);
if (r != 0) {
return r;
}
// Now build the target if it isn't the same.
if (hostBuild.name != build.name) {
r = await runBuild(
environment,
build,
concurrency: concurrency,
extraGnArgs: extraGnArgs,
enableRbe: useRbe,
targets: buildTargetsForShell,
);
if (r != 0) {
return r;
}
}
final String mangledBuildName = mangleConfigName(environment, build.name);
final String mangledHostBuildName =
mangleConfigName(environment, hostBuild.name);
final List<String> command = <String>[
'flutter',
'run',
'--local-engine-src-path',
environment.engine.srcDir.path,
'--local-engine',
mangledBuildName,
'--local-engine-host',
mangledHostBuildName,
...argResults!.rest
];
// TODO(johnmccutchan): Be smart and if the user requested a profile
// config, add the '--profile' flag when invoking flutter run.
final ProcessRunnerResult result =
await environment.processRunner.runProcess(
command,
runInShell: true,
startMode: ProcessStartMode.inheritStdio,
);
return result.exitCode;
}
}