@@ -16,6 +16,7 @@ const _flagConcurrency = 'concurrency';
1616const _flagStrategy = 'build-strategy' ;
1717const _flagRbe = 'rbe' ;
1818const _flagLto = 'lto' ;
19+ const _flagExtraGnArgs = 'gn-args' ;
1920
2021/// Describes what (platform, targets) and how (strategy, options) to build.
2122///
@@ -75,6 +76,11 @@ final class BuildPlan {
7576 }
7677 throw FatalError ('Invalid value for --$_flagConcurrency : $value ' );
7778 }(),
79+ extraGnArgs: () {
80+ final value = args.multiOption (_flagExtraGnArgs);
81+ _checkExtraGnArgs (value);
82+ return value;
83+ }(),
7884 );
7985 }
8086
@@ -84,14 +90,63 @@ final class BuildPlan {
8490 required this .useRbe,
8591 required this .useLto,
8692 required this .concurrency,
87- }) {
93+ required Iterable <String > extraGnArgs,
94+ }) : extraGnArgs = List .unmodifiable (extraGnArgs) {
8895 if (! useRbe && strategy == BuildStrategy .remote) {
8996 throw FatalError (
9097 'Cannot use remote builds without RBE enabled.\n\n $_rbeInstructions ' ,
9198 );
9299 }
93100 }
94101
102+ /// Arguments that cannot be provided to [BuildPlan.extraGnArgs] .
103+ ///
104+ /// Instead, provide them explicitly as other [BuildPlan] arguments.
105+ @visibleForTesting
106+ static const reservedGnArgs = {
107+ _flagRbe,
108+ _flagLto,
109+ 'no-$_flagRbe ' ,
110+ 'no-$_flagLto ' ,
111+ // If we are to expand this list to include flags that are not a 1:1 mapping
112+ // - for example we want to reserve "--foo-bar" but it's called "--use-baz"
113+ // in "et", let's (a) re-think having these arguments named differently and
114+ // (b) if necessary, consider changing this set to a map instead so a clear
115+ // error can be presented below.
116+ };
117+
118+ /// Error thrown when [reservedGnArgs] are used as [extraGnArgs] .
119+ @visibleForTesting
120+ static final reservedGnArgsError = FatalError (
121+ 'Flags such as ${reservedGnArgs .join (', ' )} should be specified as '
122+ 'direct arguments to "et" and not using "--gn-args". For example, '
123+ '`et build --no-lto` instead of `et build --gn-args="--no-lto"`.' ,
124+ );
125+
126+ /// Error thrown when a non-flag argument is provided as [extraGnArgs] .
127+ @visibleForTesting
128+ static final argumentsMustBeFlagsError = FatalError (
129+ 'Arguments provided to --gn-args must be flags (booleans) and be '
130+ 'specified as either in the format "--flag" or "--no-flag". Options '
131+ 'that are not flags or are abberviated ("-F") are not currently '
132+ 'supported; consider filing a request: '
133+ 'https://fluter.dev/to/engine-tool-bug.' ,
134+ );
135+
136+ static void _checkExtraGnArgs (Iterable <String > gnArgs) {
137+ for (final arg in gnArgs) {
138+ if (! arg.startsWith ('--' ) || arg.contains ('=' ) || arg.contains (' ' )) {
139+ throw argumentsMustBeFlagsError;
140+ }
141+
142+ // Strip off the prefix and compare it to reserved flags.
143+ final withoutPrefix = arg.replaceFirst ('--' , '' );
144+ if (reservedGnArgs.contains (withoutPrefix)) {
145+ throw reservedGnArgsError;
146+ }
147+ }
148+ }
149+
95150 static String _defaultHostDebug () {
96151 return 'host_debug' ;
97152 }
@@ -175,6 +230,18 @@ final class BuildPlan {
175230 help: 'How many jobs to run in parallel.' ,
176231 );
177232
233+ // Add --gn-args.
234+ parser.addMultiOption (
235+ _flagExtraGnArgs,
236+ help: ''
237+ 'Additional arguments to provide to "gn".\n '
238+ 'GN arguments change the parameters of the compiler and invalidate '
239+ 'the current build, and should be used sparingly. If there is an '
240+ 'engine build that should be reused and tested on CI prefer adding '
241+ 'the arguments to "//flutter/ci/builders/local_engine.json".' ,
242+ hide: ! environment.verbose,
243+ );
244+
178245 return builds;
179246 }
180247
@@ -201,19 +268,34 @@ final class BuildPlan {
201268 /// Whether to build with LTO (link-time optimization).
202269 final bool useLto;
203270
271+ /// Additional GN arguments to use for a build.
272+ ///
273+ /// By contract, these arguments are always strictly _flags_ (not options),
274+ /// and specified as either `--flag` , `-F` , or as the negative variant (such
275+ /// as `--no-flag` ).
276+ final List <String > extraGnArgs;
277+
204278 @override
205279 bool operator == (Object other) {
206280 return other is BuildPlan &&
207281 build.name == other.build.name &&
208282 strategy == other.strategy &&
209283 useRbe == other.useRbe &&
210284 useLto == other.useLto &&
211- concurrency == other.concurrency;
285+ concurrency == other.concurrency &&
286+ const ListEquality <Object ?>().equals (extraGnArgs, other.extraGnArgs);
212287 }
213288
214289 @override
215290 int get hashCode {
216- return Object .hash (build.name, strategy, useRbe, useLto, concurrency);
291+ return Object .hash (
292+ build.name,
293+ strategy,
294+ useRbe,
295+ useLto,
296+ concurrency,
297+ Object .hashAll (extraGnArgs),
298+ );
217299 }
218300
219301 /// Converts this build plan to its equivalent [RbeConfig] .
@@ -236,6 +318,7 @@ final class BuildPlan {
236318 return [
237319 if (! useRbe) '--no-rbe' ,
238320 if (useLto) '--lto' else '--no-lto' ,
321+ ...extraGnArgs,
239322 ];
240323 }
241324
@@ -248,6 +331,7 @@ final class BuildPlan {
248331 buffer.writeln (' useRbe: $useRbe ' );
249332 buffer.writeln (' strategy: $strategy ' );
250333 buffer.writeln (' concurrency: $concurrency ' );
334+ buffer.writeln (' extraGnArgs: $extraGnArgs ' );
251335 buffer.write ('>' );
252336 return buffer.toString ();
253337 }
0 commit comments