|
| 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 | +} |
0 commit comments