55import 'dart:async' ;
66import 'dart:convert' ;
77import 'dart:ffi' as ffi;
8- import 'dart:io' as io show Directory, File, Process;
8+ import 'dart:io' as io show Directory, File, Process, ProcessResult ;
99import 'dart:math' ;
1010
1111import 'package:path/path.dart' as p;
@@ -14,9 +14,9 @@ import 'package:process_runner/process_runner.dart';
1414
1515import 'build_config.dart' ;
1616
17- /// The base clase for events generated by a command.
17+ /// The base class for events generated by a command.
1818sealed class RunnerEvent {
19- RunnerEvent (this .name, this .command, this .timestamp);
19+ RunnerEvent (this .name, this .command, this .timestamp, [ this .environment] );
2020
2121 /// The name of the task or command.
2222 final String name;
@@ -26,11 +26,14 @@ sealed class RunnerEvent {
2626
2727 /// When the event happened.
2828 final DateTime timestamp;
29+
30+ /// Additional environment variables set during the command, if any.
31+ final Map <String , String >? environment;
2932}
3033
3134/// A [RunnerEvent] representing the start of a command.
3235final class RunnerStart extends RunnerEvent {
33- RunnerStart (super .name, super .command, super .timestamp);
36+ RunnerStart (super .name, super .command, super .timestamp, [ super .environment] );
3437
3538 @override
3639 String toString () {
@@ -120,6 +123,85 @@ final class RunnerError extends RunnerEvent {
120123 }
121124}
122125
126+ /// The strategy that RBE uses for local vs. remote actions.
127+ enum RbeExecStrategy {
128+ /// On a cache miss, all actions will be executed locally.
129+ local,
130+
131+ /// On a cache miss, actions will run both remotely and locally (capacity
132+ /// allowing), with the action completing when the faster of the two finishes.
133+ racing,
134+
135+ /// On a cache miss, all actions will be executed remotely.
136+ remote,
137+
138+ /// On a cache miss, actions will be executed locally if the latency of the
139+ /// remote action completing is too high.
140+ remoteLocalFallback;
141+
142+ @override
143+ String toString () => switch (this ) {
144+ RbeExecStrategy .local => 'local' ,
145+ RbeExecStrategy .racing => 'racing' ,
146+ RbeExecStrategy .remote => 'remote' ,
147+ RbeExecStrategy .remoteLocalFallback => 'remote_local_fallback' ,
148+ };
149+ }
150+
151+ /// Configuration options that affect how RBE works.
152+ class RbeConfig {
153+ const RbeConfig ({
154+ this .remoteDisabled = false ,
155+ this .execStrategy = RbeExecStrategy .racing,
156+ this .racingBias = 0.95 ,
157+ this .localResourceFraction = 0.2 ,
158+ });
159+
160+ /// Whether all remote queries/actions are disabled.
161+ ///
162+ /// When this is true, not even the remote cache will be queried. All actions
163+ /// will be performed locally.
164+ final bool remoteDisabled;
165+
166+ /// The RBE execution strategy.
167+ final RbeExecStrategy execStrategy;
168+
169+ /// When the RBE execution strategy is 'racing', how much to bias towards
170+ /// local vs. remote.
171+ ///
172+ /// Values should be in the range of [0, 1] . Closer to 1 implies biasing
173+ /// towards remote.
174+ final double racingBias;
175+
176+ /// When the RBE execution strategy is 'racing', how much of the local
177+ /// machine to use.
178+ final double localResourceFraction;
179+
180+ /// Environment variables to set when running RBE related subprocesses.
181+ /// Defaults mirroring:
182+ /// https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/refs/heads/main/reclient_helper.py
183+ Map <String , String > get environment => < String , String > {
184+ if (remoteDisabled)
185+ 'RBE_remote_disabled' : '1'
186+ else ...< String , String > {
187+ 'RBE_exec_strategy' : execStrategy.toString (),
188+ if (execStrategy == RbeExecStrategy .racing) ...< String , String > {
189+ 'RBE_racing_bias' : racingBias.toString (),
190+ 'RBE_local_resource_fraction' : localResourceFraction.toString (),
191+ },
192+ },
193+ // Reduce the cas concurrency. Lower value doesn't impact
194+ // performance when on high-speed connection, but does show improvements
195+ // on easily congested networks.
196+ 'RBE_cas_concurrency' : '100' ,
197+ // Enable the deps cache. Mac needs a larger deps cache as it
198+ // seems to have larger dependency sets per action. A larger deps cache
199+ // on other platforms doesn't necessarily help but also won't hurt.
200+ 'RBE_enable_deps_cache' : '1' ,
201+ 'RBE_deps_cache_max_mb' : '1024' ,
202+ };
203+ }
204+
123205/// The type of a callback that handles [RunnerEvent] s while a [Runner]
124206/// is executing its `run()` method.
125207typedef RunnerEventHandler = void Function (RunnerEvent );
@@ -190,6 +272,7 @@ final class BuildRunner extends Runner {
190272 ffi.Abi ? abi,
191273 required io.Directory engineSrcDir,
192274 required this .build,
275+ this .rbeConfig = const RbeConfig (),
193276 this .concurrency = 0 ,
194277 this .extraGnArgs = const < String > [],
195278 this .extraNinjaArgs = const < String > [],
@@ -210,6 +293,9 @@ final class BuildRunner extends Runner {
210293 /// The [Build] to run.
211294 final Build build;
212295
296+ /// Configuration options for RBE.
297+ final RbeConfig rbeConfig;
298+
213299 /// The maximum number of concurrent jobs.
214300 ///
215301 /// This currently only applies to the ninja build, passed as the -j
@@ -450,14 +536,22 @@ final class BuildRunner extends Runner {
450536 '${build .name }: RBE ${shutdown ? 'shutdown' : 'startup' }' ,
451537 bootstrapCommand,
452538 DateTime .now (),
539+ rbeConfig.environment,
453540 ));
454541 final ProcessRunnerResult bootstrapResult;
455542 if (dryRun) {
456543 bootstrapResult = _dryRunResult;
457544 } else {
458- bootstrapResult = await processRunner.runProcess (
545+ final io. ProcessResult result = await processRunner.processManager. run (
459546 bootstrapCommand,
460- failOk: true ,
547+ environment: rbeConfig.environment,
548+ );
549+ bootstrapResult = ProcessRunnerResult (
550+ result.exitCode,
551+ (result.stdout as String ).codeUnits, // stdout.
552+ (result.stderr as String ).codeUnits, // stderr.
553+ < int > [], // combined,
554+ pid: result.pid, // pid,
461555 );
462556 }
463557 String okMessage = bootstrapResult.stdout.trim ();
@@ -515,16 +609,20 @@ final class BuildRunner extends Runner {
515609 ...extraNinjaArgs,
516610 ...build.ninja.targets,
517611 ];
518- eventHandler (
519- RunnerStart ('${build .name }: ninja' , command, DateTime .now ()),
520- );
612+ eventHandler (RunnerStart (
613+ '${build .name }: ninja' ,
614+ command,
615+ DateTime .now (),
616+ rbeConfig.environment,
617+ ));
521618 final ProcessRunnerResult processResult;
522619 if (dryRun) {
523620 processResult = _dryRunResult;
524621 } else {
525622 final io.Process process = await processRunner.processManager.start (
526623 command,
527624 workingDirectory: engineSrcDir.path,
625+ environment: rbeConfig.environment,
528626 );
529627 final List <int > stderrOutput = < int > [];
530628 final List <int > stdoutOutput = < int > [];
0 commit comments