Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 223 additions & 45 deletions tools/engine_tool/lib/src/commands/run_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
import 'dart:io' show ProcessStartMode;

import 'package:engine_build_configs/engine_build_configs.dart';
import 'package:meta/meta.dart';
import 'package:process_runner/process_runner.dart';

import '../build_utils.dart';
import '../flutter_tool_interop/device.dart';
import '../flutter_tool_interop/flutter_tool.dart';
import '../flutter_tool_interop/target_platform.dart';
import '../label.dart';
import '../run_utils.dart';
import '../logger.dart';
import 'command.dart';
import 'flags.dart';

Expand All @@ -21,6 +25,7 @@ final class RunCommand extends CommandBase {
required Map<String, BuilderConfig> configs,
super.help = false,
super.usageLineLength,
@visibleForTesting FlutterTool? flutterTool,
}) {
// When printing the help/usage for this command, only list all builds
// when the --verbose flag is supplied.
Expand All @@ -41,8 +46,13 @@ final class RunCommand extends CommandBase {
defaultsTo: environment.hasRbeConfigInTree(),
help: 'RBE is enabled by default when available.',
);

_flutterTool = flutterTool ?? FlutterTool.fromEnvironment(environment);
}

/// Flutter tool.
late final FlutterTool _flutterTool;

/// List of compatible builds.
late final List<Build> builds;

Expand All @@ -58,7 +68,7 @@ Run a Flutter app with a local engine build.
See `flutter run --help` for a listing
''';

Build? _lookup(String configName) {
Build? _findTargetBuild(String configName) {
final String demangledName = demangleConfigName(environment, configName);
return builds
.where((Build build) => build.name == demangledName)
Expand All @@ -78,11 +88,11 @@ See `flutter run --help` for a listing
final String ci =
mangledName.startsWith('ci') ? mangledName.substring(0, 3) : '';
if (mangledName.contains('_debug')) {
return _lookup('${ci}host_debug');
return _findTargetBuild('${ci}host_debug');
} else if (mangledName.contains('_profile')) {
return _lookup('${ci}host_profile');
return _findTargetBuild('${ci}host_profile');
} else if (mangledName.contains('_release')) {
return _lookup('${ci}host_release');
return _findTargetBuild('${ci}host_release');
}
return null;
}
Expand Down Expand Up @@ -114,62 +124,59 @@ See `flutter run --help` for a listing
return mode;
}

late final Future<RunTarget?> _runTarget =
detectAndSelectRunTarget(environment, _getDeviceId());
late final Future<RunTarget?> _runTarget = (() async {
final devices = await _flutterTool.devices();
return RunTarget.detectAndSelect(devices, idPrefix: _getDeviceId());
})();

Future<String?> _selectTargetConfig() async {
final String configName = argResults![configFlag] as String;
Future<String> _selectTargetConfig() async {
final configName = argResults![configFlag] as String;
if (configName.isNotEmpty) {
return demangleConfigName(environment, configName);
return configName;
}
final RunTarget? target = await _runTarget;
final target = await _runTarget;
if (target == null) {
return demangleConfigName(environment, 'host_debug');
return 'host_debug';
}
environment.logger.status(
'Building to run on "${target.name}" running ${target.targetPlatform}');
return target.buildConfigFor(_getMode());

final result = target.buildConfigFor(_getMode());
environment.logger.status('Building to run on $result');
return result;
}

@override
Future<int> run() async {
if (!environment.processRunner.processManager.canRun('flutter')) {
environment.logger.error('Cannot find the flutter command in your path');
return 1;
throw FatalError('Cannot find the "flutter" command in your PATH');
}
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;

final configName = await _selectTargetConfig();
final targetBuild = _findTargetBuild(configName);
if (targetBuild == null) {
throw FatalError('Could not find build $configName');
}

final hostBuild = _findHostBuild(targetBuild);
if (hostBuild == null) {
environment.logger.error('Could not find host build for $configName');
return 1;
throw FatalError('Could not find host build for $configName');
}

final bool useRbe = argResults![rbeFlag] as bool;
final useRbe = argResults!.flag(rbeFlag);
if (useRbe && !environment.hasRbeConfigInTree()) {
environment.logger.error('RBE was requested but no RBE config was found');
return 1;
throw FatalError('RBE was requested but no RBE config was found');
}
final List<String> extraGnArgs = <String>[

final extraGnArgs = [
if (!useRbe) '--no-rbe',
];
final RunTarget? target = await _runTarget;
final List<Label> buildTargetsForShell =
target?.buildTargetsForShell() ?? <Label>[];
final target = await _runTarget;
final buildTargetsForShell = target?.buildTargetsForShell ?? [];

final String dashJ = argResults![concurrencyFlag] as String;
final int? concurrency = int.tryParse(dashJ);
final concurrency = int.tryParse(argResults![concurrencyFlag] as String);
if (concurrency == null || concurrency < 0) {
environment.logger.error('-j must specify a positive integer.');
return 1;
throw FatalError(
'--$concurrencyFlag (-j) must specify a positive integer.',
);
}

// First build the host.
Expand All @@ -181,25 +188,26 @@ See `flutter run --help` for a listing
enableRbe: useRbe,
);
if (r != 0) {
return r;
throw FatalError('Failed to build host (${hostBuild.name})');
}

// Now build the target if it isn't the same.
if (hostBuild.name != build.name) {
if (hostBuild.name != targetBuild.name) {
r = await runBuild(
environment,
build,
targetBuild,
concurrency: concurrency,
extraGnArgs: extraGnArgs,
enableRbe: useRbe,
targets: buildTargetsForShell,
);
if (r != 0) {
return r;
throw FatalError('Failed to build target (${targetBuild.name})');
}
}

final String mangledBuildName = mangleConfigName(environment, build.name);
final String mangledBuildName =
mangleConfigName(environment, targetBuild.name);

final String mangledHostBuildName =
mangleConfigName(environment, hostBuild.name);
Expand Down Expand Up @@ -227,3 +235,173 @@ See `flutter run --help` for a listing
return result.exitCode;
}
}

/// Metadata about a target to run `flutter run` on.
///
/// This class translates between the `flutter devices` output and the build
/// configurations supported by the engine, including the build targets needed
/// to build the shell for the target platform.
@visibleForTesting
@immutable
final class RunTarget {
/// Construct a run target from a device returned by `flutter devices`.
@visibleForTesting
const RunTarget.fromDevice(this.device);

/// Device to run on.
final Device device;

/// Given a list of devices, returns a build target for the first matching.
///
/// If [idPrefix] is provided, only devices with an id that starts with the
/// prefix will be considered, otherwise the first device is selected. If no
/// devices are available, or none match the prefix, `null` is returned.
@visibleForTesting
static RunTarget? detectAndSelect(
Iterable<Device> devices, {
String idPrefix = '',
}) {
if (devices.isEmpty) {
return null;
}
for (final device in devices) {
if (idPrefix.isNotEmpty) {
if (device.id.startsWith(idPrefix)) {
return RunTarget.fromDevice(device);
}
}
}
if (idPrefix.isNotEmpty) {
return null;
}
return RunTarget.fromDevice(devices.first);
}

/// Returns the build configuration for the current platform and given [mode].
///
/// The [mode] is typically one of `debug`, `profile`, or `release`.
///
/// Throws a [FatalError] if the target platform is not supported.
String buildConfigFor(String mode) {
return switch (device.targetPlatform) {
// Supported platforms with known mappings.
// -----------------------------------------------------------------------
// ANDROID
TargetPlatform.androidUnspecified => 'android_$mode',
TargetPlatform.androidX86 => 'android_${mode}_x86',
TargetPlatform.androidX64 => 'android_${mode}_x64',
TargetPlatform.androidArm64 => 'android_${mode}_arm64',

// DESKTOP (MacOS, Linux, Windows)
// We do not support cross-builds, so implicitly assume the host platform.
TargetPlatform.darwinUnspecified ||
TargetPlatform.darwinX64 ||
TargetPlatform.linuxX64 ||
TargetPlatform.windowsX64 =>
'host_$mode',
TargetPlatform.darwinArm64 ||
TargetPlatform.linuxArm64 ||
TargetPlatform.windowsArm64 =>
'host_${mode}_arm64',

// WEB
TargetPlatform.webJavascript => 'chrome_$mode',

// Unsupported platforms.
// -----------------------------------------------------------------------
// iOS.
// TODO(matanlurey): https://github.com/flutter/flutter/issues/155960
TargetPlatform.iOSUnspecified ||
TargetPlatform.iOSX64 ||
TargetPlatform.iOSArm64 =>
throw FatalError(
'iOS targets are currently unsupported.\n\nIf you are an '
'iOS engine developer, and have a need for this, please either +1 or '
'help us implement https://github.com/flutter/flutter/issues/155960.',
),

// LEGACY ANDROID
TargetPlatform.androidArm => throw FatalError(
'Legacy Android targets are not supported. '
'Please use android-arm64 or android-x64.',
),

// FUCHSIA
TargetPlatform.fuchsiaArm64 ||
TargetPlatform.fuchsiaX64 =>
throw FatalError('Fuchsia is not supported.'),

// TESTER
TargetPlatform.tester =>
throw FatalError('flutter_tester is not supported.'),

// Platforms that maybe could be supported, but we don't know about.
_ => throw FatalError(
'Unknown target platform: ${device.targetPlatform.identifier}.\n\nIf '
'this is a new platform that should be supported, please file a bug: '
'https://github.com/flutter/flutter/issues/new?labels=e:%20engine-tool.',
),
};
}

/// Minimal build targets needed to build the shell for the target platform.
List<Label> get buildTargetsForShell {
return switch (device.targetPlatform) {
// Supported platforms with known mappings.
// -----------------------------------------------------------------------
// ANDROID
TargetPlatform.androidUnspecified ||
TargetPlatform.androidX86 ||
TargetPlatform.androidX64 ||
TargetPlatform.androidArm64 =>
[Label.parseGn('//flutter/shell/platform/android:android_jar')],

// iOS.
TargetPlatform.iOSUnspecified ||
TargetPlatform.iOSX64 ||
TargetPlatform.iOSArm64 =>
[
Label.parseGn('//flutter/shell/platform/darwin/ios:flutter_framework')
],

// Desktop (MacOS).
TargetPlatform.darwinUnspecified ||
TargetPlatform.darwinX64 ||
TargetPlatform.darwinArm64 =>
[
Label.parseGn(
'//flutter/shell/platform/darwin/macos:flutter_framework',
)
],

// Desktop (Linux).
TargetPlatform.linuxX64 || TargetPlatform.linuxArm64 => [
Label.parseGn(
'//flutter/shell/platform/linux:flutter_linux_gtk',
)
],

// Desktop (Windows).
TargetPlatform.windowsX64 || TargetPlatform.windowsArm64 => [
Label.parseGn(
'//flutter/shell/platform/windows',
)
],

// Web.
TargetPlatform.webJavascript => [
Label.parseGn(
'//flutter/web_sdk:flutter_web_sdk_archive',
)
],

// Unsupported platforms.
// -----------------------------------------------------------------------
_ => throw FatalError(
'Unknown target platform: ${device.targetPlatform.identifier}.\n\nIf '
'this is a new platform that should be supported, please file a bug: '
'https://github.com/flutter/flutter/issues/new?labels=e:%20engine-tool.',
),
};
}
}
Loading