blob: 3a25b56df5fb50c6776697de468941cf05e24a42 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' as io;
import 'package:engine_build_configs/engine_build_configs.dart';
import 'package:path/path.dart' as p;
import 'environment.dart';
import 'label.dart';
import 'logger.dart';
/// A function that returns true or false when given a [BuilderConfig] and its
/// name.
typedef ConfigFilter = bool Function(String name, BuilderConfig config);
/// A function that returns true or false when given a [BuilderConfig] name
/// and a [Build].
typedef BuildFilter = bool Function(String configName, Build build);
/// Returns a filtered copy of [input] filtering out configs where test
/// returns false.
Map<String, BuilderConfig> filterBuilderConfigs(
Map<String, BuilderConfig> input, ConfigFilter test) {
return <String, BuilderConfig>{
for (final MapEntry<String, BuilderConfig> entry in input.entries)
if (test(entry.key, entry.value)) entry.key: entry.value,
};
}
/// Returns a copy of [input] filtering out configs that are not runnable
/// on the current platform.
Map<String, BuilderConfig> runnableBuilderConfigs(
Environment env, Map<String, BuilderConfig> input) {
return filterBuilderConfigs(input, (String name, BuilderConfig config) {
return config.canRunOn(env.platform);
});
}
/// Returns a List of [Build] that match test.
List<Build> filterBuilds(Map<String, BuilderConfig> input, BuildFilter test) {
return <Build>[
for (final MapEntry<String, BuilderConfig> entry in input.entries)
for (final Build build in entry.value.builds)
if (test(entry.key, build)) build,
];
}
/// Returns a list of runnable builds.
List<Build> runnableBuilds(
Environment env, Map<String, BuilderConfig> input, bool verbose) {
return filterBuilds(input, (String configName, Build build) {
return build.canRunOn(env.platform) &&
(verbose || build.name.startsWith(env.platform.operatingSystem));
});
}
/// Validates the list of builds.
/// Calls assert.
void debugCheckBuilds(List<Build> builds) {
final Set<String> names = <String>{};
for (final Build build in builds) {
assert(!names.contains(build.name),
'More than one build has the name ${build.name}');
names.add(build.name);
}
}
String _ciPrefix(Environment env) => 'ci${env.platform.pathSeparator}';
String _osPrefix(Environment env) => '${env.platform.operatingSystem}/';
const String _webTestsPrefix = 'web_tests/';
bool _doNotMangle(Environment env, String name) {
return name.startsWith(_ciPrefix(env)) || name.startsWith(_webTestsPrefix);
}
/// Transform the name of a build into the name presented and accepted by the
/// CLI
///
/// If a name starts with '$OS/', it is a local development build, and the
/// mangled name has the '$OS/' part stripped off, where $OS is the value of
/// `platform.operatingSystem` in the passed-in `environment`.
///
/// If the name does not start with '$OS/', then it must start with 'ci/',
/// 'ci\', or 'web_tests/' in which case the name is returned unchanged.
///
/// Examples:
/// macos/host_debug -> host_debug
/// ci/ios_release -> ci/ios_release
/// ci\host_profile -> ci\host_profile
/// web_tests/artifacts -> web_tests/artifacts
String mangleConfigName(Environment env, String name) {
final String osPrefix = _osPrefix(env);
if (name.startsWith(osPrefix)) {
return name.substring(osPrefix.length);
}
if (_doNotMangle(env, name)) {
return name;
}
throw ArgumentError(
'name argument "$name" must start with a valid platform name or "ci"',
);
}
/// Transform the mangled (CLI) name of a build into its true name in the build
/// config json file.
///
/// This does the reverse of [mangleConfigName] taking the operating system
/// name from `environment`.
///
/// Examples:
/// host_debug -> macos/host_debug
/// ci/ios_release -> ci/ios_release
/// ci\host_profile -> ci\host_profile
/// web_tests/artifacts -> web_tests/artifacts
String demangleConfigName(Environment env, String name) {
return _doNotMangle(env, name) ? name : '${_osPrefix(env)}$name';
}
/// Build the build target in the environment.
Future<int> runBuild(
Environment environment,
Build build, {
required bool enableRbe,
List<String> extraGnArgs = const <String>[],
List<Label> targets = const <Label>[],
int concurrency = 0,
RbeConfig rbeConfig = const RbeConfig(),
}) async {
final List<String> gnArgs = <String>[
if (!enableRbe) '--no-rbe',
...extraGnArgs,
];
// TODO(loic-sharma): Fetch dependencies if needed.
final BuildRunner buildRunner = BuildRunner(
platform: environment.platform,
processRunner: environment.processRunner,
abi: environment.abi,
engineSrcDir: environment.engine.srcDir,
build: build,
rbeConfig: rbeConfig,
concurrency: concurrency,
extraGnArgs: gnArgs,
runTests: false,
extraNinjaArgs: <String>[
...targets.map((Label label) => label.toNinjaLabel()),
// If the environment is verbose, pass the verbose flag to ninja.
if (environment.verbose) '--verbose',
],
);
Spinner? spinner;
void handler(RunnerEvent event) {
switch (event) {
case RunnerStart():
environment.logger.info('$event: ${event.command.join(' ')}');
environment.logger.status('$event ', newline: false);
spinner = environment.logger.startSpinner();
case RunnerProgress(done: true):
spinner?.finish();
spinner = null;
environment.logger.clearLine();
environment.logger.status(event);
case RunnerProgress(done: false):
{
spinner?.finish();
spinner = null;
final String percent = '${event.percent.toStringAsFixed(1)}%';
final String fraction = '(${event.completed}/${event.total})';
final String prefix = '[${event.name}] $percent $fraction ';
final String what = event.what;
environment.logger.clearLine();
environment.logger.status('$prefix$what', newline: false, fit: true);
}
default:
spinner?.finish();
spinner = null;
environment.logger.status(event);
}
}
final bool buildResult = await buildRunner.run(handler);
return buildResult ? 0 : 1;
}
/// Run a [build]'s GN step if the output directory is missing.
Future<bool> ensureBuildDir(
Environment environment,
Build build, {
List<String> extraGnArgs = const <String>[],
required bool enableRbe,
}) async {
// TODO(matanlurey): https://github.com/flutter/flutter/issues/148442.
final io.Directory buildDir = io.Directory(
p.join(
environment.engine.outDir.path,
build.ninja.config,
),
);
if (buildDir.existsSync()) {
return true;
}
final bool built = await _runGn(
environment,
build,
extraGnArgs: extraGnArgs,
enableRbe: enableRbe,
);
if (built && !buildDir.existsSync()) {
environment.logger.error(
'The specified build did not produce the expected output directory: '
'${buildDir.path}',
);
return false;
}
return built;
}
Future<bool> _runGn(
Environment environment,
Build build, {
List<String> extraGnArgs = const <String>[],
required bool enableRbe,
}) async {
final List<String> gnArgs = <String>[
if (!enableRbe) '--no-rbe',
...extraGnArgs,
];
final BuildRunner buildRunner = BuildRunner(
platform: environment.platform,
processRunner: environment.processRunner,
abi: environment.abi,
engineSrcDir: environment.engine.srcDir,
build: build,
extraGnArgs: gnArgs,
runNinja: false,
runGenerators: false,
runTests: false,
);
return buildRunner.run((RunnerEvent event) {
switch (event) {
case RunnerResult(ok: false):
environment.logger.error(event);
default:
}
});
}