Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 0f21eba

Browse files
Add an initial 'build' command to engine_tool (#50681)
* Adds a 'build' command to `et`. * Renamed `query builds` to `query builders` because that seemed to be more precise(?) Some extra requests during review: 1. I've left some questions I'd like answers to at the top of `build_command.dart` I suspect @zanderso and @loic-sharma can give me (some?) answers. 2. I suspect I'm holding the FakeProcessManager wrong or there is a better way to write the tests in `build_command_test.dart`. Pointers to good examples are appreciated.
1 parent b283cdf commit 0f21eba

File tree

6 files changed

+306
-36
lines changed

6 files changed

+306
-36
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:engine_build_configs/engine_build_configs.dart';
6+
7+
import 'environment.dart';
8+
9+
/// A function that returns true or false when given a [BuildConfig] and its
10+
/// name.
11+
typedef ConfigFilter = bool Function(String name, BuildConfig config);
12+
13+
/// A function that returns true or false when given a [BuildConfig] name
14+
/// and a [GlobalBuild].
15+
typedef BuildFilter = bool Function(String configName, GlobalBuild build);
16+
17+
/// Returns a filtered copy of [input] filtering out configs where test
18+
/// returns false.
19+
Map<String, BuildConfig> filterBuildConfigs(
20+
Map<String, BuildConfig> input, ConfigFilter test) {
21+
return <String, BuildConfig>{
22+
for (final MapEntry<String, BuildConfig> entry in input.entries)
23+
if (test(entry.key, entry.value)) entry.key: entry.value,
24+
};
25+
}
26+
27+
/// Returns a copy of [input] filtering out configs that are not runnable
28+
/// on the current platform.
29+
Map<String, BuildConfig> runnableBuildConfigs(
30+
Environment env, Map<String, BuildConfig> input) {
31+
return filterBuildConfigs(input, (String name, BuildConfig config) {
32+
return config.canRunOn(env.platform);
33+
});
34+
}
35+
36+
/// Returns a List of [GlobalBuild] that match test.
37+
List<GlobalBuild> filterBuilds(
38+
Map<String, BuildConfig> input, BuildFilter test) {
39+
return <GlobalBuild>[
40+
for (final MapEntry<String, BuildConfig> entry in input.entries)
41+
for (final GlobalBuild build in entry.value.builds)
42+
if (test(entry.key, build)) build,
43+
];
44+
}
45+
46+
/// Returns a list of runnable builds.
47+
List<GlobalBuild> runnableBuilds(
48+
Environment env, Map<String, BuildConfig> input) {
49+
return filterBuilds(input, (String configName, GlobalBuild build) {
50+
return build.canRunOn(env.platform);
51+
});
52+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:engine_build_configs/engine_build_configs.dart';
6+
7+
import '../build_utils.dart';
8+
9+
import 'command.dart';
10+
11+
const String _configFlag = 'config';
12+
13+
// TODO(johnmccutchan): Should BuildConfig be BuilderConfig and GlobalBuild be BuildConfig?
14+
// TODO(johnmccutchan): List all available build targets and allow the user
15+
// to specify which one(s) we should build on the cli.
16+
// TODO(johnmccutchan): Can we show a progress indicator like 'running gn...'?
17+
18+
/// The root 'build' command.
19+
final class BuildCommand extends CommandBase {
20+
/// Constructs the 'build' command.
21+
BuildCommand({
22+
required super.environment,
23+
required Map<String, BuildConfig> configs,
24+
}) {
25+
builds = runnableBuilds(environment, configs);
26+
// Add options here that are common to all queries.
27+
argParser.addOption(
28+
_configFlag,
29+
abbr: 'c',
30+
defaultsTo: 'host_debug',
31+
help: 'Specify the build config to use',
32+
allowed: <String>[
33+
for (final GlobalBuild config in runnableBuilds(environment, configs))
34+
config.name,
35+
],
36+
allowedHelp: <String, String>{
37+
for (final GlobalBuild config in runnableBuilds(environment, configs))
38+
config.name: config.gn.join(' '),
39+
},
40+
);
41+
}
42+
43+
/// List of compatible builds.
44+
late final List<GlobalBuild> builds;
45+
46+
@override
47+
String get name => 'build';
48+
49+
@override
50+
String get description => 'Builds the engine';
51+
52+
@override
53+
Future<int> run() async {
54+
final String configName = argResults![_configFlag] as String;
55+
final GlobalBuild? build = builds
56+
.where((GlobalBuild build) => build.name == configName)
57+
.firstOrNull;
58+
if (build == null) {
59+
environment.logger.error('Could not find config $configName');
60+
return 1;
61+
}
62+
final GlobalBuildRunner buildRunner = GlobalBuildRunner(
63+
platform: environment.platform,
64+
processRunner: environment.processRunner,
65+
abi: environment.abi,
66+
engineSrcDir: environment.engine.srcDir,
67+
build: build);
68+
void handler(RunnerEvent event) {
69+
switch (event) {
70+
case RunnerStart():
71+
environment.logger.info('$event: ${event.command.join(' ')}');
72+
case RunnerProgress(done: true):
73+
environment.logger.clearLine();
74+
environment.logger.status(event);
75+
case RunnerProgress(done: false):
76+
{
77+
final String percent = '${event.percent.toStringAsFixed(1)}%';
78+
final String fraction = '(${event.completed}/${event.total})';
79+
final String prefix = '[${event.name}] $percent $fraction ';
80+
final String what = event.what;
81+
environment.logger.clearLine();
82+
environment.logger.status('$prefix$what');
83+
}
84+
default:
85+
environment.logger.status(event);
86+
}
87+
}
88+
89+
final bool buildResult = await buildRunner.run(handler);
90+
return buildResult ? 0 : 1;
91+
}
92+
}

tools/engine_tool/lib/src/commands/command_runner.dart

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
// found in the LICENSE file.
44

55
import 'package:args/command_runner.dart';
6-
76
import 'package:engine_build_configs/engine_build_configs.dart';
87

98
import '../environment.dart';
10-
import 'command.dart';
9+
import 'build_command.dart';
1110
import 'format_command.dart';
1211
import 'query_command.dart';
1312

@@ -19,14 +18,12 @@ final class ToolCommandRunner extends CommandRunner<int> {
1918
required this.environment,
2019
required this.configs,
2120
}) : super(toolName, toolDescription) {
22-
final List<CommandBase> commands = <CommandBase>[
21+
final List<Command<int>> commands = <Command<int>>[
2322
FormatCommand(
2423
environment: environment,
2524
),
26-
QueryCommand(
27-
environment: environment,
28-
configs: configs,
29-
),
25+
QueryCommand(environment: environment, configs: configs),
26+
BuildCommand(environment: environment, configs: configs),
3027
];
3128
commands.forEach(addCommand);
3229
}
@@ -38,7 +35,7 @@ final class ToolCommandRunner extends CommandRunner<int> {
3835
/// The description of the tool reported in the tool's usage and help
3936
/// messages.
4037
static const String toolDescription = 'A command line tool for working on '
41-
'the Flutter Engine.';
38+
'the Flutter Engine.';
4239

4340
/// The host system environment.
4441
final Environment environment;
@@ -48,7 +45,7 @@ final class ToolCommandRunner extends CommandRunner<int> {
4845

4946
@override
5047
Future<int> run(Iterable<String> args) async {
51-
try{
48+
try {
5249
return await runCommand(parse(args)) ?? 0;
5350
} on FormatException catch (e) {
5451
environment.logger.error(e);

tools/engine_tool/lib/src/commands/query_command.dart

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ final class QueryCommand extends CommandBase {
2828
help: 'Restrict the query to a single builder.',
2929
allowed: <String>[
3030
for (final MapEntry<String, BuildConfig> entry in configs.entries)
31-
if (entry.value.canRunOn(environment.platform))
32-
entry.key,
31+
if (entry.value.canRunOn(environment.platform)) entry.key,
3332
],
3433
allowedHelp: <String, String>{
3534
// TODO(zanderso): Add human readable descriptions to the json files.
@@ -45,7 +44,7 @@ final class QueryCommand extends CommandBase {
4544
negatable: false,
4645
);
4746

48-
addSubcommand(QueryBuildsCommand(
47+
addSubcommand(QueryBuildersCommand(
4948
environment: environment,
5049
configs: configs,
5150
));
@@ -59,13 +58,13 @@ final class QueryCommand extends CommandBase {
5958

6059
@override
6160
String get description => 'Provides information about build configurations '
62-
'and tests.';
61+
'and tests.';
6362
}
6463

65-
// ignore: public_member_api_docs
66-
final class QueryBuildsCommand extends CommandBase {
67-
// ignore: public_member_api_docs
68-
QueryBuildsCommand({
64+
/// The 'query builds' command.
65+
final class QueryBuildersCommand extends CommandBase {
66+
/// Constructs the 'query build' command.
67+
QueryBuildersCommand({
6968
required super.environment,
7069
required this.configs,
7170
});
@@ -74,11 +73,11 @@ final class QueryBuildsCommand extends CommandBase {
7473
final Map<String, BuildConfig> configs;
7574

7675
@override
77-
String get name => 'builds';
76+
String get name => 'builders';
7877

7978
@override
80-
String get description => 'Provides information about CI build '
81-
'configurations';
79+
String get description => 'Provides information about CI builder '
80+
'configurations';
8281

8382
@override
8483
Future<int> run() async {
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:convert' as convert;
6+
import 'dart:ffi' as ffi show Abi;
7+
import 'dart:io' as io;
8+
9+
import 'package:engine_build_configs/engine_build_configs.dart';
10+
import 'package:engine_repo_tools/engine_repo_tools.dart';
11+
import 'package:engine_tool/src/build_utils.dart';
12+
import 'package:engine_tool/src/commands/command_runner.dart';
13+
import 'package:engine_tool/src/environment.dart';
14+
import 'package:engine_tool/src/logger.dart';
15+
import 'package:litetest/litetest.dart';
16+
import 'package:platform/platform.dart';
17+
import 'package:process_fakes/process_fakes.dart';
18+
import 'package:process_runner/process_runner.dart';
19+
20+
import 'fixtures.dart' as fixtures;
21+
22+
void main() {
23+
final Engine engine;
24+
try {
25+
engine = Engine.findWithin();
26+
} catch (e) {
27+
io.stderr.writeln(e);
28+
io.exitCode = 1;
29+
return;
30+
}
31+
32+
final BuildConfig linuxTestConfig = BuildConfig.fromJson(
33+
path: 'ci/builders/linux_test_config.json',
34+
map: convert.jsonDecode(fixtures.testConfig('Linux'))
35+
as Map<String, Object?>,
36+
);
37+
38+
final BuildConfig macTestConfig = BuildConfig.fromJson(
39+
path: 'ci/builders/mac_test_config.json',
40+
map: convert.jsonDecode(fixtures.testConfig('Mac-12'))
41+
as Map<String, Object?>,
42+
);
43+
44+
final BuildConfig winTestConfig = BuildConfig.fromJson(
45+
path: 'ci/builders/win_test_config.json',
46+
map: convert.jsonDecode(fixtures.testConfig('Windows-11'))
47+
as Map<String, Object?>,
48+
);
49+
50+
final Map<String, BuildConfig> configs = <String, BuildConfig>{
51+
'linux_test_config': linuxTestConfig,
52+
'linux_test_config2': linuxTestConfig,
53+
'mac_test_config': macTestConfig,
54+
'win_test_config': winTestConfig,
55+
};
56+
57+
(Environment, List<List<String>>) linuxEnv(Logger logger) {
58+
final List<List<String>> runHistory = <List<String>>[];
59+
return (
60+
Environment(
61+
abi: ffi.Abi.linuxX64,
62+
engine: engine,
63+
platform: FakePlatform(operatingSystem: Platform.linux),
64+
processRunner: ProcessRunner(
65+
processManager: FakeProcessManager(onStart: (List<String> command) {
66+
runHistory.add(command);
67+
return FakeProcess();
68+
}, onRun: (List<String> command) {
69+
runHistory.add(command);
70+
return io.ProcessResult(81, 0, '', '');
71+
}),
72+
),
73+
logger: logger,
74+
),
75+
runHistory
76+
);
77+
}
78+
79+
test('can find host runnable build', () async {
80+
final Logger logger = Logger.test();
81+
final (Environment env, _) = linuxEnv(logger);
82+
final List<GlobalBuild> result = runnableBuilds(env, configs);
83+
expect(result.length, equals(2));
84+
expect(result[0].name, equals('build_name'));
85+
});
86+
87+
test('build command invokes gn', () async {
88+
final Logger logger = Logger.test();
89+
final (Environment env, List<List<String>> runHistory) = linuxEnv(logger);
90+
final ToolCommandRunner runner = ToolCommandRunner(
91+
environment: env,
92+
configs: configs,
93+
);
94+
final int result = await runner.run(<String>[
95+
'build',
96+
'--config',
97+
'build_name',
98+
]);
99+
expect(result, equals(0));
100+
expect(runHistory.length, greaterThanOrEqualTo(1));
101+
expect(runHistory[0].length, greaterThanOrEqualTo(1));
102+
expect(runHistory[0][0], contains('gn'));
103+
});
104+
105+
test('build command invokes ninja', () async {
106+
final Logger logger = Logger.test();
107+
final (Environment env, List<List<String>> runHistory) = linuxEnv(logger);
108+
final ToolCommandRunner runner = ToolCommandRunner(
109+
environment: env,
110+
configs: configs,
111+
);
112+
final int result = await runner.run(<String>[
113+
'build',
114+
'--config',
115+
'build_name',
116+
]);
117+
expect(result, equals(0));
118+
expect(runHistory.length, greaterThanOrEqualTo(2));
119+
expect(runHistory[1].length, greaterThanOrEqualTo(1));
120+
expect(runHistory[1][0], contains('ninja'));
121+
});
122+
}

0 commit comments

Comments
 (0)