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

Commit be2bc34

Browse files
authored
Refactor multi-file build parsing into a single BuildPlan class. (#55720)
Closes flutter/flutter#148444 (code de-duplicated). Closes flutter/flutter#150877 (`--build-strategy=local`). Closes flutter/flutter#150884 (`--build-strategy=remote`). Replaces duplicate code across ~3 commands (query, test, build) with a class called `BuildPlan`, which encapsulates (a) concurrency, (b) build configuration, (c) RBE, (d) LTO, and (e) build strategy. I also moved all of the validation of the build plan into `build_plan_test`, which gives us better coverage at a lower cost (less things to think about in that suite of tests). I know the diff looks scary, but 1K of the 1.4K is tests. /cc @gaaclarke @flar
1 parent db0c0b7 commit be2bc34

15 files changed

+1425
-455
lines changed
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
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:args/args.dart';
6+
import 'package:collection/collection.dart';
7+
import 'package:engine_build_configs/engine_build_configs.dart';
8+
import 'package:meta/meta.dart';
9+
10+
import 'build_utils.dart';
11+
import 'environment.dart';
12+
import 'logger.dart';
13+
14+
const _flagConfig = 'config';
15+
const _flagConcurrency = 'concurrency';
16+
const _flagStrategy = 'build-strategy';
17+
const _flagRbe = 'rbe';
18+
const _flagLto = 'lto';
19+
20+
/// Describes what (platform, targets) and how (strategy, options) to build.
21+
///
22+
/// Multiple commands in `et` are used to indirectly run builds, which often
23+
/// means running some combination of `gn`, `ninja`, and RBE bootstrap scripts;
24+
/// this class encapsulates the information needed to do so.
25+
@immutable
26+
final class BuildPlan {
27+
/// Creates a new build plan from parsed command-line arguments.
28+
///
29+
/// The [ArgParser] that produced [args] must have been configured with
30+
/// [configureArgParser].
31+
factory BuildPlan.fromArgResults(
32+
ArgResults args,
33+
Environment environment, {
34+
required List<Build> builds,
35+
String? Function() defaultBuild = _defaultHostDebug,
36+
}) {
37+
final build = () {
38+
final name = args.option(_flagConfig) ?? defaultBuild();
39+
final config = builds.firstWhereOrNull(
40+
(b) => mangleConfigName(environment, b.name) == name,
41+
);
42+
if (config == null) {
43+
if (name == null) {
44+
throw FatalError('No build configuration specified.');
45+
}
46+
throw FatalError('Unknown build configuration: $name');
47+
}
48+
return config;
49+
}();
50+
return BuildPlan._(
51+
build: build,
52+
strategy: BuildStrategy.values.byName(args.option(_flagStrategy)!),
53+
useRbe: () {
54+
final useRbe = args.flag(_flagRbe);
55+
if (useRbe && !environment.hasRbeConfigInTree()) {
56+
throw FatalError(
57+
'RBE requested but configuration not found.\n\n$_rbeInstructions',
58+
);
59+
}
60+
return useRbe;
61+
}(),
62+
useLto: () {
63+
if (args.wasParsed(_flagLto)) {
64+
return args.flag(_flagLto);
65+
}
66+
return !build.gn.contains('--no-lto');
67+
}(),
68+
concurrency: () {
69+
final value = args.option(_flagConcurrency);
70+
if (value == null) {
71+
return null;
72+
}
73+
if (int.tryParse(value) case final value? when value >= 0) {
74+
return value;
75+
}
76+
throw FatalError('Invalid value for --$_flagConcurrency: $value');
77+
}(),
78+
);
79+
}
80+
81+
BuildPlan._({
82+
required this.build,
83+
required this.strategy,
84+
required this.useRbe,
85+
required this.useLto,
86+
required this.concurrency,
87+
}) {
88+
if (!useRbe && strategy == BuildStrategy.remote) {
89+
throw FatalError(
90+
'Cannot use remote builds without RBE enabled.\n\n$_rbeInstructions',
91+
);
92+
}
93+
}
94+
95+
static String _defaultHostDebug() {
96+
return 'host_debug';
97+
}
98+
99+
/// Adds options to [parser] for configuring a [BuildPlan].
100+
///
101+
/// Returns the list of builds that can be configured.
102+
@useResult
103+
static List<Build> configureArgParser(
104+
ArgParser parser,
105+
Environment environment, {
106+
required bool help,
107+
required Map<String, BuilderConfig> configs,
108+
}) {
109+
// Add --config.
110+
final builds = runnableBuilds(
111+
environment,
112+
configs,
113+
environment.verbose || !help,
114+
);
115+
debugCheckBuilds(builds);
116+
parser.addOption(
117+
_flagConfig,
118+
abbr: 'c',
119+
defaultsTo: () {
120+
if (builds.any((b) => b.name == 'host_debug')) {
121+
return 'host_debug';
122+
}
123+
return null;
124+
}(),
125+
allowed: [
126+
for (final config in builds) mangleConfigName(environment, config.name),
127+
],
128+
allowedHelp: {
129+
for (final config in builds)
130+
mangleConfigName(environment, config.name): config.description,
131+
},
132+
);
133+
134+
// Add --lto.
135+
parser.addFlag(
136+
_flagLto,
137+
help: ''
138+
'Whether LTO should be enabled for a build.\n'
139+
"If omitted, defaults to the configuration's specified value, "
140+
'which is typically (but not always) --no-lto.',
141+
defaultsTo: null,
142+
hide: !environment.verbose,
143+
);
144+
145+
// Add --rbe.
146+
final hasRbeConfigInTree = environment.hasRbeConfigInTree();
147+
parser.addFlag(
148+
_flagRbe,
149+
defaultsTo: hasRbeConfigInTree,
150+
help: () {
151+
var rbeHelp = 'Enable pre-configured remote build execution.';
152+
if (!hasRbeConfigInTree || environment.verbose) {
153+
rbeHelp += '\n\n$_rbeInstructions';
154+
}
155+
return rbeHelp;
156+
}(),
157+
);
158+
159+
// Add --build-strategy.
160+
parser.addOption(
161+
_flagStrategy,
162+
defaultsTo: _defaultStrategy.name,
163+
allowed: BuildStrategy.values.map((e) => e.name),
164+
allowedHelp: {
165+
for (final e in BuildStrategy.values) e.name: e._help,
166+
},
167+
help: 'How to prefer remote or local builds.',
168+
hide: !hasRbeConfigInTree && !environment.verbose,
169+
);
170+
171+
// Add --concurrency.
172+
parser.addOption(
173+
_flagConcurrency,
174+
abbr: 'j',
175+
help: 'How many jobs to run in parallel.',
176+
);
177+
178+
return builds;
179+
}
180+
181+
/// The build configuration to use.
182+
final Build build;
183+
184+
/// How to prefer remote or local builds.
185+
final BuildStrategy strategy;
186+
static const _defaultStrategy = BuildStrategy.auto;
187+
188+
/// Whether to configure the build plan to use RBE (remote build execution).
189+
final bool useRbe;
190+
static const _rbeInstructions = ''
191+
'Google employees can follow the instructions at '
192+
'https://flutter.dev/to/engine-rbe to enable RBE, which can '
193+
'parallelize builds and reduce build times on faster internet '
194+
'connections.';
195+
196+
/// How many jobs to run in parallel.
197+
///
198+
/// If `null`, the build system will use the default number of jobs.
199+
final int? concurrency;
200+
201+
/// Whether to build with LTO (link-time optimization).
202+
final bool useLto;
203+
204+
@override
205+
bool operator ==(Object other) {
206+
return other is BuildPlan &&
207+
build.name == other.build.name &&
208+
strategy == other.strategy &&
209+
useRbe == other.useRbe &&
210+
useLto == other.useLto &&
211+
concurrency == other.concurrency;
212+
}
213+
214+
@override
215+
int get hashCode {
216+
return Object.hash(build.name, strategy, useRbe, useLto, concurrency);
217+
}
218+
219+
/// Converts this build plan to its equivalent [RbeConfig].
220+
RbeConfig toRbeConfig() {
221+
switch (strategy) {
222+
case BuildStrategy.auto:
223+
return const RbeConfig();
224+
case BuildStrategy.local:
225+
return const RbeConfig(
226+
execStrategy: RbeExecStrategy.local,
227+
remoteDisabled: true,
228+
);
229+
case BuildStrategy.remote:
230+
return const RbeConfig(execStrategy: RbeExecStrategy.remote);
231+
}
232+
}
233+
234+
/// Converts this build plan into extra GN arguments to pass to the build.
235+
List<String> toGnArgs() {
236+
return [
237+
if (!useRbe) '--no-rbe',
238+
if (useLto) '--lto' else '--no-lto',
239+
];
240+
}
241+
242+
@override
243+
String toString() {
244+
final buffer = StringBuffer('BuildPlan <');
245+
buffer.writeln();
246+
buffer.writeln(' build: ${build.name}');
247+
buffer.writeln(' useLto: $useLto');
248+
buffer.writeln(' useRbe: $useRbe');
249+
buffer.writeln(' strategy: $strategy');
250+
buffer.writeln(' concurrency: $concurrency');
251+
buffer.write('>');
252+
return buffer.toString();
253+
}
254+
}
255+
256+
/// User-specified strategy for executing a build.
257+
enum BuildStrategy {
258+
/// Automatically determine the best build strategy.
259+
auto(
260+
'Prefer remote builds and fallback silently to local builds.',
261+
),
262+
263+
/// Build locally.
264+
local(
265+
'Use local builds.'
266+
'\n'
267+
'No internet connection is required.',
268+
),
269+
270+
/// Build remotely.
271+
remote(
272+
'Use remote builds.'
273+
'\n'
274+
'If --$_flagStrategy is not specified, the build will fail.',
275+
);
276+
277+
const BuildStrategy(this._help);
278+
final String _help;
279+
}

tools/engine_tool/lib/src/build_utils.dart

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import 'dart:io' as io;
77
import 'package:engine_build_configs/engine_build_configs.dart';
88
import 'package:path/path.dart' as p;
99

10-
import 'commands/flags.dart';
1110
import 'environment.dart';
1211
import 'label.dart';
1312
import 'logger.dart';
@@ -120,20 +119,6 @@ String demangleConfigName(Environment env, String name) {
120119
return _doNotMangle(env, name) ? name : '${_osPrefix(env)}$name';
121120
}
122121

123-
/// Make an RbeConfig.
124-
RbeConfig makeRbeConfig(String execStrategy) {
125-
switch (execStrategy) {
126-
case buildStrategyFlagValueAuto:
127-
return const RbeConfig();
128-
case buildStrategyFlagValueLocal:
129-
return const RbeConfig(execStrategy: RbeExecStrategy.local);
130-
case buildStrategyFlagValueRemote:
131-
return const RbeConfig(execStrategy: RbeExecStrategy.remote);
132-
default:
133-
throw FatalError('Unknown RBE execution strategy "$execStrategy"');
134-
}
135-
}
136-
137122
/// Build the build target in the environment.
138123
Future<int> runBuild(
139124
Environment environment,

0 commit comments

Comments
 (0)