mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Adds a runner to engine_build_configs (flutter/engine#50342)
This PR adds an API to the `engine_build_configs` package that executes an engine build config locally . This API is a building block for an eventual command line tool to streamline workflows in the engine repo. In particular, it will allow the command line tool to execute the same config, build, and test commands that are run on CI.
This commit is contained in:
parent
b56621e3ae
commit
5a34bc8fa5
47
engine/src/flutter/tools/pkg/engine_build_configs/README.md
Normal file
47
engine/src/flutter/tools/pkg/engine_build_configs/README.md
Normal file
@ -0,0 +1,47 @@
|
||||
## Overview
|
||||
|
||||
This package contains libraries and example code for working with the engine_v2
|
||||
build config json files that live under `flutter/ci/builders`.
|
||||
|
||||
* `lib/src/build_config.dart`: Contains the Dart object representations of the
|
||||
build config json files.
|
||||
* `lib/src/build_config_loader.dart`: Contains a helper class for loading all
|
||||
of the build configuration json files in a directory tree into the Dart
|
||||
objects.
|
||||
* `lib/src/build_config_runner.dart`: Contains classes that run a loaded build
|
||||
config on the local machine.
|
||||
|
||||
There is some example code using these APIs under the `bin/` directory.
|
||||
|
||||
* `bin/check.dart`: Checks the validity of the build config json files. This
|
||||
runs on CI in pre and post submit in `ci/check_build_configs.sh` through
|
||||
`ci/builders/linux_unopt.json`.
|
||||
* `bin/run.dart`: Runs one build from a build configuration on the local
|
||||
machine. It doesn't run generators or tests, and it isn't run on CI.
|
||||
|
||||
## Usage
|
||||
|
||||
### `run.dart` usage:
|
||||
|
||||
|
||||
```
|
||||
$ dart bin/run.dart [build config name] [build name]
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
$ dart bin/run.dart mac_unopt host_debug_unopt
|
||||
```
|
||||
|
||||
The build config names are the names of the json files under ci/builders.
|
||||
The build names are the "name" fields of the maps in the list of "builds".
|
||||
|
||||
### `check.dart` usage:
|
||||
|
||||
```
|
||||
$ dart bin/check.dart [/path/to/engine/src]
|
||||
```
|
||||
|
||||
The path to the engine source is optional when the current working directory is
|
||||
inside of an engine checkout.
|
||||
147
engine/src/flutter/tools/pkg/engine_build_configs/bin/run.dart
Normal file
147
engine/src/flutter/tools/pkg/engine_build_configs/bin/run.dart
Normal file
@ -0,0 +1,147 @@
|
||||
// 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:engine_repo_tools/engine_repo_tools.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process_runner/process_runner.dart';
|
||||
|
||||
// This is an example of how to use the APIs of this library to parse and
|
||||
// execute the build configurations json files under ci/builders.
|
||||
//
|
||||
// Usage:
|
||||
// $ dart bin/run.dart [build config name] [build name]
|
||||
// For example:
|
||||
// $ dart bin/run.dart mac_unopt host_debug_unopt
|
||||
//
|
||||
// The build config names are the names of the json files under ci/builders
|
||||
// The build names are the "name" fields of the maps in the list of "builds".
|
||||
|
||||
void main(List<String> args) async {
|
||||
final String? configName;
|
||||
final String? buildName;
|
||||
if (args.length >= 2) {
|
||||
configName = args[0];
|
||||
buildName = args[1];
|
||||
} else {
|
||||
io.stderr.writeln(r'''
|
||||
Usage:
|
||||
$ dart bin/run.dart [build config name] [build name]
|
||||
|
||||
For example:
|
||||
|
||||
$ dart bin/run.dart mac_unopt host_debug_unopt
|
||||
|
||||
The build config names are the names of the json files under ci/builders.
|
||||
The build names are the "name" fields of the maps in the list of "builds".
|
||||
''');
|
||||
io.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the engine repo.
|
||||
final Engine engine;
|
||||
try {
|
||||
engine = Engine.findWithin();
|
||||
} catch (e) {
|
||||
io.stderr.writeln(e);
|
||||
io.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find and parse the engine build configs.
|
||||
final io.Directory buildConfigsDir = io.Directory(p.join(
|
||||
engine.flutterDir.path, 'ci', 'builders',
|
||||
));
|
||||
final BuildConfigLoader loader = BuildConfigLoader(
|
||||
buildConfigsDir: buildConfigsDir,
|
||||
);
|
||||
|
||||
// Treat it as an error if no build configs were found. The caller likely
|
||||
// expected to find some.
|
||||
final Map<String, BuildConfig> configs = loader.configs;
|
||||
if (configs.isEmpty) {
|
||||
io.stderr.writeln(
|
||||
'Error: No build configs found under ${buildConfigsDir.path}',
|
||||
);
|
||||
io.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (loader.errors.isNotEmpty) {
|
||||
loader.errors.forEach(io.stderr.writeln);
|
||||
io.exitCode = 1;
|
||||
}
|
||||
|
||||
// Check the parsed build configs for validity.
|
||||
final BuildConfig? targetConfig = configs[configName];
|
||||
if (targetConfig == null) {
|
||||
io.stderr.writeln('Build config "$configName" not found.');
|
||||
io.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
final List<String> buildConfigErrors = targetConfig.check(configName);
|
||||
if (buildConfigErrors.isNotEmpty) {
|
||||
io.stderr.writeln('Errors in "$configName":');
|
||||
for (final String error in buildConfigErrors) {
|
||||
io.stderr.writeln(' $error');
|
||||
}
|
||||
io.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalBuild? targetBuild;
|
||||
for (int i = 0; i < targetConfig.builds.length; i++) {
|
||||
final GlobalBuild build = targetConfig.builds[i];
|
||||
if (build.name == buildName) {
|
||||
targetBuild = build;
|
||||
}
|
||||
}
|
||||
if (targetBuild == null) {
|
||||
io.stderr.writeln(
|
||||
'Target build not found. No build called $buildName in $configName',
|
||||
);
|
||||
io.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
final GlobalBuildRunner buildRunner = GlobalBuildRunner(
|
||||
platform: const LocalPlatform(),
|
||||
processRunner: ProcessRunner(),
|
||||
engineSrcDir: engine.srcDir,
|
||||
build: targetBuild,
|
||||
runGenerators: false,
|
||||
runTests: false,
|
||||
);
|
||||
void handler(RunnerEvent event) {
|
||||
switch (event) {
|
||||
case RunnerStart():
|
||||
io.stdout.writeln('$event: ${event.command.join(' ')}');
|
||||
case RunnerProgress(done: true):
|
||||
io.stdout.writeln(event);
|
||||
case RunnerProgress(done: false): {
|
||||
final int width = io.stdout.terminalColumns;
|
||||
final String percent = '${event.percent.toStringAsFixed(1)}%';
|
||||
final String fraction = '(${event.completed}/${event.total})';
|
||||
final String prefix = '[${event.name}] $percent $fraction ';
|
||||
final int remainingSpace = width - prefix.length;
|
||||
final String what;
|
||||
if (remainingSpace >= event.what.length) {
|
||||
what = event.what;
|
||||
} else {
|
||||
what = event.what.substring(event.what.length - remainingSpace + 1);
|
||||
}
|
||||
final String spaces = ' ' * width;
|
||||
io.stdout.write('$spaces\r'); // Erase the old line.
|
||||
io.stdout.write('$prefix$what\r'); // Print the new line.
|
||||
}
|
||||
default:
|
||||
io.stdout.writeln(event);
|
||||
}
|
||||
}
|
||||
final bool buildResult = await buildRunner.run(handler);
|
||||
io.exitCode = buildResult ? 0 : 1;
|
||||
}
|
||||
@ -22,3 +22,4 @@ library;
|
||||
|
||||
export 'src/build_config.dart';
|
||||
export 'src/build_config_loader.dart';
|
||||
export 'src/build_config_runner.dart';
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
// This library parses Engine build config data out of the "Engine v2" build
|
||||
// config JSON files with the format described at:
|
||||
@ -287,6 +288,10 @@ final class GlobalBuild extends BuildConfigBase {
|
||||
/// .gclient file before `gclient sync` is run.
|
||||
final Map<String, Object?> gclientVariables;
|
||||
|
||||
/// Returns true if platform is capable of executing this build and false
|
||||
/// otherwise.
|
||||
bool canRunOn(Platform platform) => _canRunOn(droneDimensions, platform);
|
||||
|
||||
@override
|
||||
List<String> check(String path) {
|
||||
final List<String> errors = <String>[];
|
||||
@ -601,6 +606,10 @@ final class GlobalTest extends BuildConfigBase {
|
||||
/// A list of dictionaries representing scripts and parameters to run them
|
||||
final List<TestTask> tasks;
|
||||
|
||||
/// Returns true if platform is capable of executing this build and false
|
||||
/// otherwise.
|
||||
bool canRunOn(Platform platform) => _canRunOn(droneDimensions, platform);
|
||||
|
||||
@override
|
||||
List<String> check(String path) {
|
||||
final List<String> errors = <String>[];
|
||||
@ -744,6 +753,18 @@ final class GlobalArchive extends BuildConfigBase {
|
||||
final String realm;
|
||||
}
|
||||
|
||||
bool _canRunOn(List<String> droneDimensions, Platform platform) {
|
||||
String? os;
|
||||
for (final String dimension in droneDimensions) {
|
||||
os ??= switch (dimension.split('=')) {
|
||||
['os', 'Linux'] => Platform.linux,
|
||||
['os', final String win] when win.startsWith('Windows') => Platform.windows,
|
||||
['os', final String mac] when mac.startsWith('Mac') => Platform.macOS,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
return os == platform.operatingSystem;
|
||||
}
|
||||
|
||||
void appendTypeError(
|
||||
Map<String, Object?> map,
|
||||
@ -766,7 +787,6 @@ void appendTypeError(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
List<T>? objListOfJson<T>(
|
||||
Map<String, Object?> map,
|
||||
String field,
|
||||
|
||||
@ -0,0 +1,560 @@
|
||||
// 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:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io' as io show Directory, Process;
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process_runner/process_runner.dart';
|
||||
|
||||
import 'build_config.dart';
|
||||
|
||||
/// The base clase for events generated by a command.
|
||||
sealed class RunnerEvent {
|
||||
RunnerEvent(this.name, this.command, this.timestamp);
|
||||
|
||||
/// The name of the task or command.
|
||||
final String name;
|
||||
|
||||
/// The command and its arguments.
|
||||
final List<String> command;
|
||||
|
||||
/// When the event happened.
|
||||
final DateTime timestamp;
|
||||
}
|
||||
|
||||
/// A [RunnerEvent] representing the start of a command.
|
||||
final class RunnerStart extends RunnerEvent {
|
||||
RunnerStart(super.name, super.command, super.timestamp);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '[${_timestamp(timestamp)}][$name]: STARTING';
|
||||
}
|
||||
}
|
||||
|
||||
/// A [RunnerEvent] representing the progress of a started command.
|
||||
final class RunnerProgress extends RunnerEvent {
|
||||
RunnerProgress(
|
||||
super.name, super.command, super.timestamp,
|
||||
this.what, this.completed, this.total, this.done,
|
||||
) : percent = (completed * 1000) / total;
|
||||
|
||||
/// What a command is currently working on, for example a build target or
|
||||
/// the name of a test.
|
||||
final String what;
|
||||
|
||||
/// The number of steps completed.
|
||||
final int completed;
|
||||
|
||||
/// The total number of steps in the task.
|
||||
final int total;
|
||||
|
||||
/// How close is the task to being completed, for example the proportion of
|
||||
/// build targets that have finished building.
|
||||
final double percent;
|
||||
|
||||
/// Whether the command is finished and this is the final progress event.
|
||||
final bool done;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final String ts = '[${_timestamp(timestamp)}]';
|
||||
final String pct = '${percent.toStringAsFixed(1)}%';
|
||||
return '$ts[$name]: $pct ($completed/$total) $what';
|
||||
}
|
||||
}
|
||||
|
||||
/// A [RunnerEvent] representing the result of a command.
|
||||
final class RunnerResult extends RunnerEvent {
|
||||
RunnerResult(super.name, super.command, super.timestamp, this.result);
|
||||
|
||||
/// The resuilt of running the command.
|
||||
final ProcessRunnerResult result;
|
||||
|
||||
/// Whether the command was successful.
|
||||
late final bool ok = result.exitCode == 0;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (ok) {
|
||||
return '[${_timestamp(timestamp)}][$name]: OK';
|
||||
}
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
buffer.writeln('[$timestamp][$name]: FAILED');
|
||||
buffer.writeln('COMMAND:\n${command.join(' ')}');
|
||||
buffer.writeln('STDOUT:\n${result.stdout}');
|
||||
buffer.writeln('STDERR:\n${result.stderr}');
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
final class RunnerError extends RunnerEvent {
|
||||
RunnerError(super.name, super.command, super.timestamp, this.error);
|
||||
|
||||
/// An error message.
|
||||
final String error;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '[${_timestamp(timestamp)}][$name]: ERROR: $error';
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of a callback that handles [RunnerEvent]s while a [Runner]
|
||||
/// is executing its `run()` method.
|
||||
typedef RunnerEventHandler = void Function(RunnerEvent);
|
||||
|
||||
/// An abstract base clase for running the various tasks that a build config
|
||||
/// specifies. Derived classes implement the `run()` method.
|
||||
sealed class Runner {
|
||||
Runner(this.platform, this.processRunner, this.engineSrcDir, this.dryRun);
|
||||
|
||||
/// Information about the platform that hosts the runner.
|
||||
final Platform platform;
|
||||
|
||||
/// Runs the subprocesses required to run the element of the build config.
|
||||
final ProcessRunner processRunner;
|
||||
|
||||
/// The src/ directory of the engine checkout.
|
||||
final io.Directory engineSrcDir;
|
||||
|
||||
/// Whether only a dry run is required. Subprocesses will not be spawned.
|
||||
final bool dryRun;
|
||||
|
||||
/// Uses the [processRunner] to run the commands specified by the build
|
||||
/// config.
|
||||
Future<bool> run(RunnerEventHandler eventHandler);
|
||||
|
||||
String _interpreter(String language) {
|
||||
// Force python to be python3.
|
||||
if (language.startsWith('python')) {
|
||||
return 'python3';
|
||||
}
|
||||
|
||||
// If the language is 'dart', return the Dart binary that is running this
|
||||
// program.
|
||||
if (language == 'dart') {
|
||||
return platform.executable;
|
||||
}
|
||||
|
||||
// Otherwise use the language verbatim as the interpreter.
|
||||
return language;
|
||||
}
|
||||
}
|
||||
|
||||
final ProcessRunnerResult _dryRunResult = ProcessRunnerResult(
|
||||
0, // exit code.
|
||||
<int>[], // stdout.
|
||||
<int>[], // stderr.
|
||||
<int>[], // combined,
|
||||
pid: 0, // pid,
|
||||
);
|
||||
|
||||
/// The [Runner] for a [GlobalBuild].
|
||||
///
|
||||
/// Runs the specified `gn` and `ninja` commands, followed by generator tasks,
|
||||
/// and finally tests.
|
||||
final class GlobalBuildRunner extends Runner {
|
||||
GlobalBuildRunner({
|
||||
Platform? platform,
|
||||
ProcessRunner? processRunner,
|
||||
required io.Directory engineSrcDir,
|
||||
required this.build,
|
||||
this.extraGnArgs = const <String>[],
|
||||
this.extraNinjaArgs = const <String>[],
|
||||
this.extraTestArgs = const <String>[],
|
||||
this.runGn = true,
|
||||
this.runNinja = true,
|
||||
this.runGenerators = true,
|
||||
this.runTests = true,
|
||||
bool dryRun = false,
|
||||
}) : super(
|
||||
platform ?? const LocalPlatform(),
|
||||
processRunner ?? ProcessRunner(),
|
||||
engineSrcDir,
|
||||
dryRun,
|
||||
);
|
||||
|
||||
/// The [GlobalBuild] to run.
|
||||
final GlobalBuild build;
|
||||
|
||||
/// Extra arguments to append to the `gn` command.
|
||||
final List<String> extraGnArgs;
|
||||
|
||||
/// Extra arguments to append to the `ninja` command.
|
||||
final List<String> extraNinjaArgs;
|
||||
|
||||
/// Extra arguments to append to *all* test commands.
|
||||
final List<String> extraTestArgs;
|
||||
|
||||
/// Whether to run the GN step. Defaults to true.
|
||||
final bool runGn;
|
||||
|
||||
/// Whether to run the ninja step. Defaults to true.
|
||||
final bool runNinja;
|
||||
|
||||
/// Whether to run the generators. Defaults to true.
|
||||
final bool runGenerators;
|
||||
|
||||
/// Whether to run the test step. Defaults to true.
|
||||
final bool runTests;
|
||||
|
||||
@override
|
||||
Future<bool> run(RunnerEventHandler eventHandler) async {
|
||||
if (!build.canRunOn(platform)) {
|
||||
eventHandler(RunnerError(
|
||||
build.name, <String>[], DateTime.now(),
|
||||
'Build with drone_dimensions "{${build.droneDimensions.join(',')}}" '
|
||||
'cannot run on platform ${platform.operatingSystem}',
|
||||
));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (runGn) {
|
||||
if (!await _runGn(eventHandler)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (runNinja) {
|
||||
if (!await _runNinja(eventHandler)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (runGenerators) {
|
||||
if (!await _runGenerators(eventHandler)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (runTests) {
|
||||
if (!await _runTests(eventHandler)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// GN arguments from the build config that can be overridden by extraGnArgs.
|
||||
static const List<(String, String)> _overridableArgs = <(String, String)>[
|
||||
('--lto', '--no-lto'),
|
||||
('--rbe', '--no-rbe'),
|
||||
('--goma', '--no-goma'),
|
||||
];
|
||||
|
||||
// extraGnArgs overrides the build config args.
|
||||
late final Set<String> _mergedGnArgs = () {
|
||||
// Put the union of the build config args and extraGnArgs in gnArgs.
|
||||
final Set<String> gnArgs = Set<String>.of(build.gn);
|
||||
gnArgs.addAll(extraGnArgs);
|
||||
|
||||
// If extraGnArgs contains an arg, remove its opposite from gnArgs.
|
||||
for (final (String, String) arg in _overridableArgs) {
|
||||
if (extraGnArgs.contains(arg.$1)) {
|
||||
gnArgs.remove(arg.$2);
|
||||
}
|
||||
if (extraGnArgs.contains(arg.$2)) {
|
||||
gnArgs.remove(arg.$1);
|
||||
}
|
||||
}
|
||||
|
||||
return gnArgs;
|
||||
}();
|
||||
|
||||
Future<bool> _runGn(RunnerEventHandler eventHandler) async {
|
||||
final String gnPath = p.join(engineSrcDir.path, 'flutter', 'tools', 'gn');
|
||||
final Set<String> gnArgs = _mergedGnArgs;
|
||||
final List<String> command = <String>[gnPath, ...gnArgs];
|
||||
eventHandler(RunnerStart('${build.name}: GN', command, DateTime.now()));
|
||||
final ProcessRunnerResult processResult;
|
||||
if (dryRun) {
|
||||
processResult = _dryRunResult;
|
||||
} else {
|
||||
processResult = await processRunner.runProcess(
|
||||
command,
|
||||
workingDirectory: engineSrcDir,
|
||||
failOk: true,
|
||||
);
|
||||
}
|
||||
final RunnerResult result = RunnerResult(
|
||||
'${build.name}: GN', command, DateTime.now(), processResult,
|
||||
);
|
||||
eventHandler(result);
|
||||
return result.ok;
|
||||
}
|
||||
|
||||
// TODO(zanderso): This should start and stop RBE when it is an --rbe build.
|
||||
Future<bool> _runNinja(RunnerEventHandler eventHandler) async {
|
||||
final String ninjaPath = p.join(
|
||||
engineSrcDir.path, 'flutter', 'third_party', 'ninja', 'ninja',
|
||||
);
|
||||
final String outDir = p.join(engineSrcDir.path, 'out', build.ninja.config);
|
||||
final List<String> command = <String>[
|
||||
ninjaPath,
|
||||
'-C', outDir,
|
||||
if (_isGomaOrRbe) ...<String>['-j', '200'],
|
||||
...extraNinjaArgs,
|
||||
...build.ninja.targets,
|
||||
];
|
||||
eventHandler(RunnerStart('${build.name}: ninja', command, DateTime.now()));
|
||||
|
||||
final ProcessRunnerResult processResult;
|
||||
if (dryRun) {
|
||||
processResult = _dryRunResult;
|
||||
} else {
|
||||
final io.Process process = await processRunner.processManager.start(
|
||||
command,
|
||||
workingDirectory: engineSrcDir.path,
|
||||
);
|
||||
final List<int> stderrOutput = <int>[];
|
||||
final Completer<void> stdoutComplete = Completer<void>();
|
||||
final Completer<void> stderrComplete = Completer<void>();
|
||||
|
||||
process.stdout
|
||||
.transform<String>(const Utf8Decoder())
|
||||
.transform(const LineSplitter())
|
||||
.listen(
|
||||
(String line) {
|
||||
_ninjaProgress(eventHandler, command, line);
|
||||
},
|
||||
onDone: () async => stdoutComplete.complete(),
|
||||
);
|
||||
|
||||
process.stderr.listen(
|
||||
stderrOutput.addAll,
|
||||
onDone: () async => stderrComplete.complete(),
|
||||
);
|
||||
|
||||
await Future.wait<void>(<Future<void>>[
|
||||
stdoutComplete.future, stderrComplete.future,
|
||||
]);
|
||||
final int exitCode = await process.exitCode;
|
||||
|
||||
processResult = ProcessRunnerResult(
|
||||
exitCode,
|
||||
<int>[], // stdout.
|
||||
stderrOutput, // stderr.
|
||||
stderrOutput, // combined,
|
||||
pid: process.pid, // pid,
|
||||
);
|
||||
}
|
||||
|
||||
final RunnerResult result = RunnerResult(
|
||||
'${build.name}: ninja', command, DateTime.now(), processResult,
|
||||
);
|
||||
eventHandler(result);
|
||||
return result.ok;
|
||||
}
|
||||
|
||||
// Parse lines of the form '[6232/6269] LINK ./accessibility_unittests'.
|
||||
void _ninjaProgress(
|
||||
RunnerEventHandler eventHandler,
|
||||
List<String> command,
|
||||
String line,
|
||||
) {
|
||||
// Grab the '[6232/6269]' part.
|
||||
final String maybeProgress = line.split(' ')[0];
|
||||
if (maybeProgress.length < 3 ||
|
||||
maybeProgress[0] != '[' ||
|
||||
maybeProgress[maybeProgress.length - 1] != ']') {
|
||||
return;
|
||||
}
|
||||
// Extract the two numbers by stripping the '[' and ']' and splitting on
|
||||
// the '/'.
|
||||
final List<String> progress = maybeProgress
|
||||
.substring(1, maybeProgress.length - 1)
|
||||
.split('/');
|
||||
if (progress.length < 2) {
|
||||
return;
|
||||
}
|
||||
final int? completed = int.tryParse(progress[0]);
|
||||
final int? total = int.tryParse(progress[1]);
|
||||
if (completed == null || total == null) {
|
||||
return;
|
||||
}
|
||||
eventHandler(RunnerProgress(
|
||||
'${build.name}: ninja',
|
||||
command,
|
||||
DateTime.now(),
|
||||
line.replaceFirst(maybeProgress, '').trim(),
|
||||
completed,
|
||||
total,
|
||||
completed == total, // True when done.
|
||||
));
|
||||
}
|
||||
|
||||
late final bool _isGoma = build.gn.contains('--goma') ||
|
||||
extraGnArgs.contains('--goma');
|
||||
late final bool _isRbe = build.gn.contains('--rbe') ||
|
||||
extraGnArgs.contains('--rbe');
|
||||
late final bool _isGomaOrRbe = _isGoma || _isRbe;
|
||||
|
||||
Future<bool> _runGenerators(RunnerEventHandler eventHandler) async {
|
||||
for (final BuildTask task in build.generators) {
|
||||
final BuildTaskRunner runner = BuildTaskRunner(
|
||||
processRunner: processRunner,
|
||||
platform: platform,
|
||||
engineSrcDir: engineSrcDir,
|
||||
task: task,
|
||||
dryRun: dryRun,
|
||||
);
|
||||
if (!await runner.run(eventHandler)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> _runTests(RunnerEventHandler eventHandler) async {
|
||||
for (final BuildTest test in build.tests) {
|
||||
final BuildTestRunner runner = BuildTestRunner(
|
||||
processRunner: processRunner,
|
||||
platform: platform,
|
||||
engineSrcDir: engineSrcDir,
|
||||
test: test,
|
||||
extraTestArgs: extraTestArgs,
|
||||
dryRun: dryRun,
|
||||
);
|
||||
if (!await runner.run(eventHandler)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// The [Runner] for a [BuildTask] of a generator of a [GlobalBuild].
|
||||
final class BuildTaskRunner extends Runner {
|
||||
BuildTaskRunner({
|
||||
Platform? platform,
|
||||
ProcessRunner? processRunner,
|
||||
required io.Directory engineSrcDir,
|
||||
required this.task,
|
||||
bool dryRun = false,
|
||||
}) : super(
|
||||
platform ?? const LocalPlatform(),
|
||||
processRunner ?? ProcessRunner(),
|
||||
engineSrcDir,
|
||||
dryRun,
|
||||
);
|
||||
|
||||
/// The task to run.
|
||||
final BuildTask task;
|
||||
|
||||
@override
|
||||
Future<bool> run(RunnerEventHandler eventHandler) async {
|
||||
final String interpreter = _interpreter(task.language);
|
||||
for (final String script in task.scripts) {
|
||||
final List<String> command = <String>[
|
||||
if (interpreter.isNotEmpty) interpreter,
|
||||
script,
|
||||
...task.parameters,
|
||||
];
|
||||
eventHandler(RunnerStart(task.name, command, DateTime.now()));
|
||||
final ProcessRunnerResult processResult;
|
||||
if (dryRun) {
|
||||
processResult = _dryRunResult;
|
||||
} else {
|
||||
processResult = await processRunner.runProcess(
|
||||
command,
|
||||
workingDirectory: engineSrcDir,
|
||||
failOk: true,
|
||||
);
|
||||
}
|
||||
final RunnerResult result = RunnerResult(
|
||||
task.name, command, DateTime.now(), processResult,
|
||||
);
|
||||
eventHandler(result);
|
||||
if (!result.ok) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// The [Runner] for a [BuildTest] of a [GlobalBuild].
|
||||
final class BuildTestRunner extends Runner {
|
||||
BuildTestRunner({
|
||||
Platform? platform,
|
||||
ProcessRunner? processRunner,
|
||||
required io.Directory engineSrcDir,
|
||||
required this.test,
|
||||
this.extraTestArgs = const <String>[],
|
||||
bool dryRun = false,
|
||||
}) : super(
|
||||
platform ?? const LocalPlatform(),
|
||||
processRunner ?? ProcessRunner(),
|
||||
engineSrcDir,
|
||||
dryRun,
|
||||
);
|
||||
|
||||
/// The test to run.
|
||||
final BuildTest test;
|
||||
|
||||
/// Extra arguments to append to the test command.
|
||||
final List<String> extraTestArgs;
|
||||
|
||||
@override
|
||||
Future<bool> run(RunnerEventHandler eventHandler) async {
|
||||
final String interpreter = _interpreter(test.language);
|
||||
final List<String> command = <String>[
|
||||
if (interpreter.isNotEmpty) interpreter,
|
||||
test.script,
|
||||
...test.parameters,
|
||||
...extraTestArgs,
|
||||
];
|
||||
eventHandler(RunnerStart(test.name, command, DateTime.now()));
|
||||
final ProcessRunnerResult processResult;
|
||||
if (dryRun) {
|
||||
processResult = _dryRunResult;
|
||||
} else {
|
||||
// TODO(zanderso): We could detect here that we're running e.g. C++ unit
|
||||
// tests via run_tests.py, and parse the stdout to generate RunnerProgress
|
||||
// events.
|
||||
processResult = await processRunner.runProcess(
|
||||
command,
|
||||
workingDirectory: engineSrcDir,
|
||||
failOk: true,
|
||||
printOutput: true,
|
||||
);
|
||||
}
|
||||
final RunnerResult result = RunnerResult(
|
||||
test.name, command, DateTime.now(), processResult,
|
||||
);
|
||||
eventHandler(result);
|
||||
return result.ok;
|
||||
}
|
||||
}
|
||||
|
||||
String _timestamp(DateTime time) {
|
||||
String threeDigits(int n) {
|
||||
return switch (n) {
|
||||
>= 100 => '$n',
|
||||
>= 10 => '0$n',
|
||||
_ => '00$n',
|
||||
};
|
||||
}
|
||||
|
||||
String twoDigits(int n) {
|
||||
return switch (n) {
|
||||
>= 10 => '$n',
|
||||
_ => '0$n',
|
||||
};
|
||||
}
|
||||
|
||||
final String y = time.year.toString();
|
||||
final String m = twoDigits(time.month);
|
||||
final String d = twoDigits(time.day);
|
||||
final String hh = twoDigits(time.hour);
|
||||
final String mm = twoDigits(time.minute);
|
||||
final String ss = twoDigits(time.second);
|
||||
final String ms = threeDigits(time.millisecond);
|
||||
return '$y-$m-${d}T$hh:$mm:$ss.$ms${time.isUtc ? 'Z' : ''}';
|
||||
}
|
||||
@ -30,6 +30,8 @@ dev_dependencies:
|
||||
async_helper: any
|
||||
expect: any
|
||||
litetest: any
|
||||
process_fakes:
|
||||
path: ../process_fakes
|
||||
smith: any
|
||||
|
||||
dependency_overrides:
|
||||
|
||||
@ -8,85 +8,7 @@ import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:litetest/litetest.dart';
|
||||
|
||||
const String buildConfigJson = '''
|
||||
{
|
||||
"builds": [
|
||||
{
|
||||
"archives": [
|
||||
{
|
||||
"name": "build_name",
|
||||
"base_path": "base/path",
|
||||
"type": "gcs",
|
||||
"include_paths": ["include/path"],
|
||||
"realm": "archive_realm"
|
||||
}
|
||||
],
|
||||
"drone_dimensions": ["dimension"],
|
||||
"gclient_variables": {
|
||||
"variable": false
|
||||
},
|
||||
"gn": ["--gn-arg"],
|
||||
"name": "build_name",
|
||||
"ninja": {
|
||||
"config": "build_name",
|
||||
"targets": ["ninja_target"]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"language": "python3",
|
||||
"name": "build_name tests",
|
||||
"parameters": ["--test-params"],
|
||||
"script": "test/script.py",
|
||||
"contexts": ["context"]
|
||||
}
|
||||
],
|
||||
"generators": {
|
||||
"tasks": [
|
||||
{
|
||||
"name": "generator_task",
|
||||
"parameters": ["--gen-param"],
|
||||
"scripts": ["gen/script.py"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"generators": {
|
||||
"tasks": [
|
||||
{
|
||||
"name": "global generator task",
|
||||
"parameters": ["--global-gen-param"],
|
||||
"script": "global/gen_script.dart",
|
||||
"language": "dart"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"name": "global test",
|
||||
"recipe": "engine_v2/tester_engine",
|
||||
"drone_dimensions": ["dimension"],
|
||||
"gclient_variables": {
|
||||
"variable": false
|
||||
},
|
||||
"dependencies": ["dependency"],
|
||||
"test_dependencies": [
|
||||
{
|
||||
"dependency": "test_dependency",
|
||||
"version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603"
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"name": "global test task",
|
||||
"parameters": ["--test-parameter"],
|
||||
"script": "global/test/script.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
''';
|
||||
import 'fixtures.dart' as fixtures;
|
||||
|
||||
int main() {
|
||||
test('BuildConfigLoader can load a build config', () {
|
||||
@ -97,7 +19,7 @@ int main() {
|
||||
'linux_test_build.json',
|
||||
);
|
||||
buildConfigFile.create(recursive: true);
|
||||
buildConfigFile.writeAsStringSync(buildConfigJson);
|
||||
buildConfigFile.writeAsStringSync(fixtures.buildConfigJson);
|
||||
|
||||
final BuildConfigLoader loader = BuildConfigLoader(
|
||||
buildConfigsDir: buildConfigsDir,
|
||||
|
||||
@ -0,0 +1,447 @@
|
||||
// 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:convert' as convert;
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:engine_build_configs/src/build_config.dart';
|
||||
import 'package:engine_build_configs/src/build_config_runner.dart';
|
||||
import 'package:engine_repo_tools/engine_repo_tools.dart';
|
||||
import 'package:litetest/litetest.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process_fakes/process_fakes.dart';
|
||||
import 'package:process_runner/process_runner.dart';
|
||||
|
||||
import 'fixtures.dart' as fixtures;
|
||||
|
||||
void main() {
|
||||
// Find the engine repo.
|
||||
final Engine engine;
|
||||
try {
|
||||
engine = Engine.findWithin();
|
||||
} catch (e) {
|
||||
io.stderr.writeln(e);
|
||||
io.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
final BuildConfig buildConfig = BuildConfig.fromJson(
|
||||
path: 'linux_test_config',
|
||||
map: convert.jsonDecode(fixtures.buildConfigJson) as Map<String, Object?>,
|
||||
);
|
||||
|
||||
test('BuildTaskRunner runs the right commands', () async {
|
||||
final BuildTask generator = buildConfig.builds[0].generators[0];
|
||||
final BuildTaskRunner taskRunner = BuildTaskRunner(
|
||||
platform: FakePlatform(operatingSystem: Platform.linux),
|
||||
processRunner: ProcessRunner(
|
||||
// dryRun should not try to spawn any processes.
|
||||
processManager: FakeProcessManager(),
|
||||
),
|
||||
engineSrcDir: engine.srcDir,
|
||||
task: generator,
|
||||
dryRun: true,
|
||||
);
|
||||
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||
void handler(RunnerEvent event) => events.add(event);
|
||||
final bool runResult = await taskRunner.run(handler);
|
||||
|
||||
expect(runResult, isTrue);
|
||||
expect(events[0] is RunnerStart, isTrue);
|
||||
expect(events[0].name, equals('generator_task'));
|
||||
expect(events[0].command[0], contains('python3'));
|
||||
expect(events[0].command[1], contains('gen/script.py'));
|
||||
expect(events[0].command[2], contains('--gen-param'));
|
||||
expect(events[1] is RunnerResult, isTrue);
|
||||
expect(events[1].name, equals('generator_task'));
|
||||
});
|
||||
|
||||
test('BuildTestRunner runs the right commands', () async {
|
||||
final BuildTest test = buildConfig.builds[0].tests[0];
|
||||
final BuildTestRunner testRunner = BuildTestRunner(
|
||||
platform: FakePlatform(operatingSystem: Platform.linux),
|
||||
processRunner: ProcessRunner(
|
||||
// dryRun should not try to spawn any processes.
|
||||
processManager: FakeProcessManager(),
|
||||
),
|
||||
engineSrcDir: engine.srcDir,
|
||||
test: test,
|
||||
dryRun: true,
|
||||
);
|
||||
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||
void handler(RunnerEvent event) => events.add(event);
|
||||
final bool runResult = await testRunner.run(handler);
|
||||
|
||||
expect(runResult, isTrue);
|
||||
|
||||
// Check that the events for the tests are correct.
|
||||
expect(events[0] is RunnerStart, isTrue);
|
||||
expect(events[0].name, equals('build_name tests'));
|
||||
expect(events[0].command[0], contains('python3'));
|
||||
expect(events[0].command[1], contains('test/script.py'));
|
||||
expect(events[0].command[2], contains('--test-params'));
|
||||
expect(events[1] is RunnerResult, isTrue);
|
||||
expect(events[1].name, equals('build_name tests'));
|
||||
});
|
||||
|
||||
test('GlobalBuildRunner runs the right commands', () async {
|
||||
final GlobalBuild targetBuild = buildConfig.builds[0];
|
||||
final GlobalBuildRunner buildRunner = GlobalBuildRunner(
|
||||
platform: FakePlatform(operatingSystem: Platform.linux),
|
||||
processRunner: ProcessRunner(
|
||||
// dryRun should not try to spawn any processes.
|
||||
processManager: FakeProcessManager(),
|
||||
),
|
||||
engineSrcDir: engine.srcDir,
|
||||
build: targetBuild,
|
||||
dryRun: true,
|
||||
);
|
||||
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||
void handler(RunnerEvent event) => events.add(event);
|
||||
final bool runResult = await buildRunner.run(handler);
|
||||
|
||||
final String buildName = targetBuild.name;
|
||||
|
||||
expect(runResult, isTrue);
|
||||
|
||||
// Check that the events for the GN command are correct.
|
||||
expect(events[0] is RunnerStart, isTrue);
|
||||
expect(events[0].name, equals('$buildName: GN'));
|
||||
expect(events[0].command[0], contains('flutter/tools/gn'));
|
||||
for (final String gnArg in targetBuild.gn) {
|
||||
expect(events[0].command.contains(gnArg), isTrue);
|
||||
}
|
||||
expect(events[1] is RunnerResult, isTrue);
|
||||
expect(events[1].name, equals('$buildName: GN'));
|
||||
|
||||
// Check that the events for the Ninja command are correct.
|
||||
expect(events[2] is RunnerStart, isTrue);
|
||||
expect(events[2].name, equals('$buildName: ninja'));
|
||||
expect(events[2].command[0], contains('ninja'));
|
||||
final String configPath = '${engine.srcDir.path}/out/${targetBuild.ninja.config}';
|
||||
expect(events[2].command.contains(configPath), isTrue);
|
||||
for (final String target in targetBuild.ninja.targets) {
|
||||
expect(events[2].command.contains(target), isTrue);
|
||||
}
|
||||
expect(events[3] is RunnerResult, isTrue);
|
||||
expect(events[3].name, equals('$buildName: ninja'));
|
||||
|
||||
// Check that the events for generators are correct.
|
||||
expect(events[4] is RunnerStart, isTrue);
|
||||
expect(events[4].name, equals('generator_task'));
|
||||
expect(events[4].command[0], contains('python3'));
|
||||
expect(events[4].command[1], contains('gen/script.py'));
|
||||
expect(events[4].command[2], contains('--gen-param'));
|
||||
expect(events[5] is RunnerResult, isTrue);
|
||||
expect(events[5].name, equals('generator_task'));
|
||||
|
||||
// Check that the events for the tests are correct.
|
||||
expect(events[6] is RunnerStart, isTrue);
|
||||
expect(events[6].name, equals('$buildName tests'));
|
||||
expect(events[6].command[0], contains('python3'));
|
||||
expect(events[6].command[1], contains('test/script.py'));
|
||||
expect(events[6].command[2], contains('--test-params'));
|
||||
expect(events[7] is RunnerResult, isTrue);
|
||||
expect(events[7].name, equals('$buildName tests'));
|
||||
});
|
||||
|
||||
test('GlobalBuildRunner extra args are propagated correctly', () async {
|
||||
final GlobalBuild targetBuild = buildConfig.builds[0];
|
||||
final GlobalBuildRunner buildRunner = GlobalBuildRunner(
|
||||
platform: FakePlatform(operatingSystem: Platform.linux),
|
||||
processRunner: ProcessRunner(
|
||||
// dryRun should not try to spawn any processes.
|
||||
processManager: FakeProcessManager(),
|
||||
),
|
||||
engineSrcDir: engine.srcDir,
|
||||
build: targetBuild,
|
||||
extraGnArgs: <String>['--extra-gn-arg'],
|
||||
extraNinjaArgs: <String>['--extra-ninja-arg'],
|
||||
extraTestArgs: <String>['--extra-test-arg'],
|
||||
dryRun: true,
|
||||
);
|
||||
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||
void handler(RunnerEvent event) => events.add(event);
|
||||
final bool runResult = await buildRunner.run(handler);
|
||||
|
||||
final String buildName = targetBuild.name;
|
||||
|
||||
expect(runResult, isTrue);
|
||||
|
||||
// Check that the events for the GN command are correct.
|
||||
expect(events[0] is RunnerStart, isTrue);
|
||||
expect(events[0].name, equals('$buildName: GN'));
|
||||
expect(events[0].command.contains('--extra-gn-arg'), isTrue);
|
||||
|
||||
// Check that the events for the Ninja command are correct.
|
||||
expect(events[2] is RunnerStart, isTrue);
|
||||
expect(events[2].name, equals('$buildName: ninja'));
|
||||
expect(events[2].command.contains('--extra-ninja-arg'), isTrue);
|
||||
|
||||
// Check that the events for the tests are correct.
|
||||
expect(events[6] is RunnerStart, isTrue);
|
||||
expect(events[6].name, equals('$buildName tests'));
|
||||
expect(events[6].command.contains('--extra-test-arg'), isTrue);
|
||||
});
|
||||
|
||||
test('GlobalBuildRunner passes large -j for a goma build', () async {
|
||||
final GlobalBuild targetBuild = buildConfig.builds[0];
|
||||
final GlobalBuildRunner buildRunner = GlobalBuildRunner(
|
||||
platform: FakePlatform(operatingSystem: Platform.linux),
|
||||
processRunner: ProcessRunner(
|
||||
// dryRun should not try to spawn any processes.
|
||||
processManager: FakeProcessManager(),
|
||||
),
|
||||
engineSrcDir: engine.srcDir,
|
||||
build: targetBuild,
|
||||
extraGnArgs: <String>['--goma'],
|
||||
dryRun: true,
|
||||
);
|
||||
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||
void handler(RunnerEvent event) => events.add(event);
|
||||
final bool runResult = await buildRunner.run(handler);
|
||||
|
||||
final String buildName = targetBuild.name;
|
||||
|
||||
expect(runResult, isTrue);
|
||||
|
||||
// Check that the events for the Ninja command are correct.
|
||||
expect(events[2] is RunnerStart, isTrue);
|
||||
expect(events[2].name, equals('$buildName: ninja'));
|
||||
expect(events[2].command.contains('-j'), isTrue);
|
||||
expect(events[2].command.contains('200'), isTrue);
|
||||
});
|
||||
|
||||
test('GlobalBuildRunner passes large -j for an rbe build', () async {
|
||||
final GlobalBuild targetBuild = buildConfig.builds[0];
|
||||
final GlobalBuildRunner buildRunner = GlobalBuildRunner(
|
||||
platform: FakePlatform(operatingSystem: Platform.linux),
|
||||
processRunner: ProcessRunner(
|
||||
// dryRun should not try to spawn any processes.
|
||||
processManager: FakeProcessManager(),
|
||||
),
|
||||
engineSrcDir: engine.srcDir,
|
||||
build: targetBuild,
|
||||
extraGnArgs: <String>['--rbe'],
|
||||
dryRun: true,
|
||||
);
|
||||
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||
void handler(RunnerEvent event) => events.add(event);
|
||||
final bool runResult = await buildRunner.run(handler);
|
||||
|
||||
final String buildName = targetBuild.name;
|
||||
|
||||
expect(runResult, isTrue);
|
||||
|
||||
// Check that the events for the Ninja command are correct.
|
||||
expect(events[2] is RunnerStart, isTrue);
|
||||
expect(events[2].name, equals('$buildName: ninja'));
|
||||
expect(events[2].command.contains('-j'), isTrue);
|
||||
expect(events[2].command.contains('200'), isTrue);
|
||||
});
|
||||
|
||||
test('GlobalBuildRunner skips GN when runGn is false', () async {
|
||||
final GlobalBuild targetBuild = buildConfig.builds[0];
|
||||
final GlobalBuildRunner buildRunner = GlobalBuildRunner(
|
||||
platform: FakePlatform(operatingSystem: Platform.linux),
|
||||
processRunner: ProcessRunner(
|
||||
// dryRun should not try to spawn any processes.
|
||||
processManager: FakeProcessManager(),
|
||||
),
|
||||
engineSrcDir: engine.srcDir,
|
||||
build: targetBuild,
|
||||
runGn: false,
|
||||
dryRun: true,
|
||||
);
|
||||
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||
void handler(RunnerEvent event) => events.add(event);
|
||||
final bool runResult = await buildRunner.run(handler);
|
||||
|
||||
final String buildName = targetBuild.name;
|
||||
|
||||
expect(runResult, isTrue);
|
||||
|
||||
// Check that the events for the Ninja command are correct.
|
||||
expect(events[0] is RunnerStart, isTrue);
|
||||
expect(events[0].name, equals('$buildName: ninja'));
|
||||
expect(events[0].command[0], contains('ninja'));
|
||||
final String configPath = '${engine.srcDir.path}/out/${targetBuild.ninja.config}';
|
||||
expect(events[0].command.contains(configPath), isTrue);
|
||||
for (final String target in targetBuild.ninja.targets) {
|
||||
expect(events[0].command.contains(target), isTrue);
|
||||
}
|
||||
expect(events[1] is RunnerResult, isTrue);
|
||||
expect(events[1].name, equals('$buildName: ninja'));
|
||||
});
|
||||
|
||||
test('GlobalBuildRunner skips Ninja when runNinja is false', () async {
|
||||
final GlobalBuild targetBuild = buildConfig.builds[0];
|
||||
final GlobalBuildRunner buildRunner = GlobalBuildRunner(
|
||||
platform: FakePlatform(operatingSystem: Platform.linux),
|
||||
processRunner: ProcessRunner(
|
||||
// dryRun should not try to spawn any processes.
|
||||
processManager: FakeProcessManager(),
|
||||
),
|
||||
engineSrcDir: engine.srcDir,
|
||||
build: targetBuild,
|
||||
runNinja: false,
|
||||
dryRun: true,
|
||||
);
|
||||
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||
void handler(RunnerEvent event) => events.add(event);
|
||||
final bool runResult = await buildRunner.run(handler);
|
||||
|
||||
final String buildName = targetBuild.name;
|
||||
|
||||
expect(runResult, isTrue);
|
||||
|
||||
// Check that the events for the GN command are correct.
|
||||
expect(events[0] is RunnerStart, isTrue);
|
||||
expect(events[0].name, equals('$buildName: GN'));
|
||||
expect(events[0].command[0], contains('flutter/tools/gn'));
|
||||
for (final String gnArg in targetBuild.gn) {
|
||||
expect(events[0].command.contains(gnArg), isTrue);
|
||||
}
|
||||
expect(events[1] is RunnerResult, isTrue);
|
||||
expect(events[1].name, equals('$buildName: GN'));
|
||||
|
||||
// Check that the events for generators are correct.
|
||||
expect(events[2] is RunnerStart, isTrue);
|
||||
expect(events[2].name, equals('generator_task'));
|
||||
expect(events[2].command[0], contains('python3'));
|
||||
expect(events[2].command[1], contains('gen/script.py'));
|
||||
expect(events[2].command[2], contains('--gen-param'));
|
||||
expect(events[3] is RunnerResult, isTrue);
|
||||
expect(events[3].name, equals('generator_task'));
|
||||
});
|
||||
|
||||
test('GlobalBuildRunner skips generators when runGenerators is false', () async {
|
||||
final GlobalBuild targetBuild = buildConfig.builds[0];
|
||||
final GlobalBuildRunner buildRunner = GlobalBuildRunner(
|
||||
platform: FakePlatform(operatingSystem: Platform.linux),
|
||||
processRunner: ProcessRunner(
|
||||
// dryRun should not try to spawn any processes.
|
||||
processManager: FakeProcessManager(),
|
||||
),
|
||||
engineSrcDir: engine.srcDir,
|
||||
build: targetBuild,
|
||||
runGenerators: false,
|
||||
dryRun: true,
|
||||
);
|
||||
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||
void handler(RunnerEvent event) => events.add(event);
|
||||
final bool runResult = await buildRunner.run(handler);
|
||||
|
||||
final String buildName = targetBuild.name;
|
||||
|
||||
expect(runResult, isTrue);
|
||||
|
||||
// Check that the events for the Ninja command are correct.
|
||||
expect(events[2] is RunnerStart, isTrue);
|
||||
expect(events[2].name, equals('$buildName: ninja'));
|
||||
expect(events[2].command[0], contains('ninja'));
|
||||
final String configPath = '${engine.srcDir.path}/out/${targetBuild.ninja.config}';
|
||||
expect(events[2].command.contains(configPath), isTrue);
|
||||
for (final String target in targetBuild.ninja.targets) {
|
||||
expect(events[2].command.contains(target), isTrue);
|
||||
}
|
||||
expect(events[3] is RunnerResult, isTrue);
|
||||
expect(events[3].name, equals('$buildName: ninja'));
|
||||
|
||||
// Check that the events for the tests are correct.
|
||||
expect(events[4] is RunnerStart, isTrue);
|
||||
expect(events[4].name, equals('$buildName tests'));
|
||||
expect(events[4].command[0], contains('python3'));
|
||||
expect(events[4].command[1], contains('test/script.py'));
|
||||
expect(events[4].command[2], contains('--test-params'));
|
||||
expect(events[5] is RunnerResult, isTrue);
|
||||
expect(events[5].name, equals('$buildName tests'));
|
||||
});
|
||||
|
||||
test('GlobalBuildRunner skips tests when runTests is false', () async {
|
||||
final GlobalBuild targetBuild = buildConfig.builds[0];
|
||||
final GlobalBuildRunner buildRunner = GlobalBuildRunner(
|
||||
platform: FakePlatform(operatingSystem: Platform.linux),
|
||||
processRunner: ProcessRunner(
|
||||
// dryRun should not try to spawn any processes.
|
||||
processManager: FakeProcessManager(),
|
||||
),
|
||||
engineSrcDir: engine.srcDir,
|
||||
build: targetBuild,
|
||||
runTests: false,
|
||||
dryRun: true,
|
||||
);
|
||||
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||
void handler(RunnerEvent event) => events.add(event);
|
||||
final bool runResult = await buildRunner.run(handler);
|
||||
|
||||
expect(runResult, isTrue);
|
||||
|
||||
// Check that the events for generators are correct.
|
||||
expect(events[4] is RunnerStart, isTrue);
|
||||
expect(events[4].name, equals('generator_task'));
|
||||
expect(events[4].command[0], contains('python3'));
|
||||
expect(events[4].command[1], contains('gen/script.py'));
|
||||
expect(events[4].command[2], contains('--gen-param'));
|
||||
expect(events[5] is RunnerResult, isTrue);
|
||||
expect(events[5].name, equals('generator_task'));
|
||||
|
||||
expect(events.length, equals(6));
|
||||
});
|
||||
|
||||
test('GlobalBuildRunner extraGnArgs overrides build config args', () async {
|
||||
final GlobalBuild targetBuild = buildConfig.builds[0];
|
||||
final GlobalBuildRunner buildRunner = GlobalBuildRunner(
|
||||
platform: FakePlatform(operatingSystem: Platform.linux),
|
||||
processRunner: ProcessRunner(
|
||||
// dryRun should not try to spawn any processes.
|
||||
processManager: FakeProcessManager(),
|
||||
),
|
||||
engineSrcDir: engine.srcDir,
|
||||
build: targetBuild,
|
||||
extraGnArgs: <String>['--no-lto', '--no-goma', '--rbe'],
|
||||
dryRun: true,
|
||||
);
|
||||
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||
void handler(RunnerEvent event) => events.add(event);
|
||||
final bool runResult = await buildRunner.run(handler);
|
||||
|
||||
final String buildName = targetBuild.name;
|
||||
|
||||
expect(runResult, isTrue);
|
||||
|
||||
// Check that the events for the GN command are correct.
|
||||
expect(events[0] is RunnerStart, isTrue);
|
||||
expect(events[0].name, equals('$buildName: GN'));
|
||||
expect(events[0].command[0], contains('flutter/tools/gn'));
|
||||
expect(events[0].command.contains('--no-lto'), isTrue);
|
||||
expect(events[0].command.contains('--no-goma'), isTrue);
|
||||
expect(events[0].command.contains('--rbe'), isTrue);
|
||||
expect(events[0].command.contains('--lto'), isFalse);
|
||||
expect(events[0].command.contains('--goma'), isFalse);
|
||||
expect(events[0].command.contains('--no-rbe'), isFalse);
|
||||
expect(events[1] is RunnerResult, isTrue);
|
||||
expect(events[1].name, equals('$buildName: GN'));
|
||||
});
|
||||
|
||||
test('GlobalBuildRunner canRun returns false on OS mismatch', () async {
|
||||
final GlobalBuild targetBuild = buildConfig.builds[0];
|
||||
final GlobalBuildRunner buildRunner = GlobalBuildRunner(
|
||||
platform: FakePlatform(operatingSystem: Platform.macOS),
|
||||
processRunner: ProcessRunner(
|
||||
// dryRun should not try to spawn any processes.
|
||||
processManager: FakeProcessManager(),
|
||||
),
|
||||
engineSrcDir: engine.srcDir,
|
||||
build: targetBuild,
|
||||
dryRun: true,
|
||||
);
|
||||
final List<RunnerEvent> events = <RunnerEvent>[];
|
||||
void handler(RunnerEvent event) => events.add(event);
|
||||
final bool runResult = await buildRunner.run(handler);
|
||||
|
||||
expect(runResult, isFalse);
|
||||
expect(events[0] is RunnerError, isTrue);
|
||||
});
|
||||
}
|
||||
@ -6,92 +6,15 @@ import 'dart:convert' as convert;
|
||||
|
||||
import 'package:engine_build_configs/src/build_config.dart';
|
||||
import 'package:litetest/litetest.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
const String buildConfigJson = '''
|
||||
{
|
||||
"builds": [
|
||||
{
|
||||
"archives": [
|
||||
{
|
||||
"name": "build_name",
|
||||
"base_path": "base/path",
|
||||
"type": "gcs",
|
||||
"include_paths": ["include/path"],
|
||||
"realm": "archive_realm"
|
||||
}
|
||||
],
|
||||
"drone_dimensions": ["dimension"],
|
||||
"gclient_variables": {
|
||||
"variable": false
|
||||
},
|
||||
"gn": ["--gn-arg"],
|
||||
"name": "build_name",
|
||||
"ninja": {
|
||||
"config": "build_name",
|
||||
"targets": ["ninja_target"]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"language": "python3",
|
||||
"name": "build_name tests",
|
||||
"parameters": ["--test-params"],
|
||||
"script": "test/script.py",
|
||||
"contexts": ["context"]
|
||||
}
|
||||
],
|
||||
"generators": {
|
||||
"tasks": [
|
||||
{
|
||||
"name": "generator_task",
|
||||
"parameters": ["--gen-param"],
|
||||
"scripts": ["gen/script.py"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"generators": {
|
||||
"tasks": [
|
||||
{
|
||||
"name": "global generator task",
|
||||
"parameters": ["--global-gen-param"],
|
||||
"script": "global/gen_script.dart",
|
||||
"language": "dart"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"name": "global test",
|
||||
"recipe": "engine_v2/tester_engine",
|
||||
"drone_dimensions": ["dimension"],
|
||||
"gclient_variables": {
|
||||
"variable": false
|
||||
},
|
||||
"dependencies": ["dependency"],
|
||||
"test_dependencies": [
|
||||
{
|
||||
"dependency": "test_dependency",
|
||||
"version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603"
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"name": "global test task",
|
||||
"parameters": ["--test-parameter"],
|
||||
"script": "global/test/script.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
''';
|
||||
import 'fixtures.dart' as fixtures;
|
||||
|
||||
int main() {
|
||||
test('BuildConfig parser works', () {
|
||||
final BuildConfig buildConfig = BuildConfig.fromJson(
|
||||
path: 'linux_test_config',
|
||||
map: convert.jsonDecode(buildConfigJson) as Map<String, Object?>,
|
||||
map: convert.jsonDecode(fixtures.buildConfigJson) as Map<String, Object?>,
|
||||
);
|
||||
expect(buildConfig.valid, isTrue);
|
||||
expect(buildConfig.errors, isNull);
|
||||
@ -99,10 +22,18 @@ int main() {
|
||||
|
||||
final GlobalBuild globalBuild = buildConfig.builds[0];
|
||||
expect(globalBuild.name, equals('build_name'));
|
||||
expect(globalBuild.gn.length, equals(1));
|
||||
expect(globalBuild.gn.length, equals(4));
|
||||
expect(globalBuild.gn[0], equals('--gn-arg'));
|
||||
expect(globalBuild.droneDimensions.length, equals(1));
|
||||
expect(globalBuild.droneDimensions[0], equals('dimension'));
|
||||
expect(globalBuild.droneDimensions[0], equals('os=Linux'));
|
||||
expect(
|
||||
globalBuild.canRunOn(FakePlatform(operatingSystem: Platform.linux)),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
globalBuild.canRunOn(FakePlatform(operatingSystem: Platform.macOS)),
|
||||
isFalse,
|
||||
);
|
||||
|
||||
final BuildNinja ninja = globalBuild.ninja;
|
||||
expect(ninja.config, equals('build_name'));
|
||||
@ -148,7 +79,15 @@ int main() {
|
||||
expect(globalTest.name, equals('global test'));
|
||||
expect(globalTest.recipe, equals('engine_v2/tester_engine'));
|
||||
expect(globalTest.droneDimensions.length, equals(1));
|
||||
expect(globalTest.droneDimensions[0], equals('dimension'));
|
||||
expect(globalTest.droneDimensions[0], equals('os=Linux'));
|
||||
expect(
|
||||
globalTest.canRunOn(FakePlatform(operatingSystem: Platform.linux)),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
globalTest.canRunOn(FakePlatform(operatingSystem: Platform.macOS)),
|
||||
isFalse,
|
||||
);
|
||||
expect(globalTest.dependencies.length, equals(1));
|
||||
expect(globalTest.dependencies[0], equals('dependency'));
|
||||
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
// 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.
|
||||
|
||||
const String buildConfigJson = '''
|
||||
{
|
||||
"builds": [
|
||||
{
|
||||
"archives": [
|
||||
{
|
||||
"name": "build_name",
|
||||
"base_path": "base/path",
|
||||
"type": "gcs",
|
||||
"include_paths": ["include/path"],
|
||||
"realm": "archive_realm"
|
||||
}
|
||||
],
|
||||
"drone_dimensions": [
|
||||
"os=Linux"
|
||||
],
|
||||
"gclient_variables": {
|
||||
"variable": false
|
||||
},
|
||||
"gn": ["--gn-arg", "--lto", "--goma", "--no-rbe"],
|
||||
"name": "build_name",
|
||||
"ninja": {
|
||||
"config": "build_name",
|
||||
"targets": ["ninja_target"]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"language": "python3",
|
||||
"name": "build_name tests",
|
||||
"parameters": ["--test-params"],
|
||||
"script": "test/script.py",
|
||||
"contexts": ["context"]
|
||||
}
|
||||
],
|
||||
"generators": {
|
||||
"tasks": [
|
||||
{
|
||||
"name": "generator_task",
|
||||
"language": "python",
|
||||
"parameters": ["--gen-param"],
|
||||
"scripts": ["gen/script.py"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"generators": {
|
||||
"tasks": [
|
||||
{
|
||||
"name": "global generator task",
|
||||
"parameters": ["--global-gen-param"],
|
||||
"script": "global/gen_script.dart",
|
||||
"language": "dart"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"name": "global test",
|
||||
"recipe": "engine_v2/tester_engine",
|
||||
"drone_dimensions": [
|
||||
"os=Linux"
|
||||
],
|
||||
"gclient_variables": {
|
||||
"variable": false
|
||||
},
|
||||
"dependencies": ["dependency"],
|
||||
"test_dependencies": [
|
||||
{
|
||||
"dependency": "test_dependency",
|
||||
"version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603"
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"name": "global test task",
|
||||
"parameters": ["--test-parameter"],
|
||||
"script": "global/test/script.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
''';
|
||||
Loading…
x
Reference in New Issue
Block a user