blob: 2e057e93f4597815f5718de78c3b47954debf1d1 [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 show Directory, exitCode, stderr;
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';
// Usage:
// $ dart bin/check.dart [/path/to/engine/src]
void main(List<String> args) {
final String? engineSrcPath;
if (args.isNotEmpty) {
engineSrcPath = args[0];
} else {
engineSrcPath = null;
}
// Find the engine repo.
final Engine engine;
try {
engine = Engine.findWithin(engineSrcPath);
} 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, BuilderConfig> 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 List<String> invalidErrors = checkForInvalidConfigs(configs);
if (invalidErrors.isNotEmpty) {
invalidErrors.forEach(io.stderr.writeln);
io.exitCode = 1;
}
// We require all builds within a builder config to be uniquely named.
final List<String> duplicateErrors = checkForDuplicateConfigs(configs);
if (duplicateErrors.isNotEmpty) {
duplicateErrors.forEach(io.stderr.writeln);
io.exitCode = 1;
}
// We require all builds to be named in a way that is understood by et.
final List<String> buildNameErrors = checkForInvalidBuildNames(configs);
if (buildNameErrors.isNotEmpty) {
buildNameErrors.forEach(io.stderr.writeln);
io.exitCode = 1;
}
}
// This check ensures that all the json files were deserialized without errors.
List<String> checkForInvalidConfigs(Map<String, BuilderConfig> configs) {
final List<String> errors = <String>[];
for (final String name in configs.keys) {
final BuilderConfig buildConfig = configs[name]!;
final List<String> buildConfigErrors = buildConfig.check(name);
if (buildConfigErrors.isNotEmpty) {
errors.add('Errors in ${buildConfig.path}:');
}
for (final String error in buildConfigErrors) {
errors.add(' $error');
}
}
return errors;
}
// Thjs check ensures that json files do not contain builds with duplicate
// names.
List<String> checkForDuplicateConfigs(Map<String, BuilderConfig> configs) {
final List<String> errors = <String>[];
final Map<String, Set<String>> builderBuildSet = <String, Set<String>>{};
_forEachBuild(configs, (String name, BuilderConfig config, Build build) {
final Set<String> builds = builderBuildSet.putIfAbsent(name, () => <String>{});
if (builds.contains(build.name)) {
errors.add('${build.name} is duplicated in $name\n');
} else {
builds.add(build.name);
}
});
return errors;
}
// This check ensures that builds are named in a way that is understood by
// `et`.
List<String> checkForInvalidBuildNames(Map<String, BuilderConfig> configs) {
final List<String> errors = <String>[];
// In local_engine.json, allowed OS names are linux, macos, and windows.
final List<String> osNames = <String>[
Platform.linux, Platform.macOS, Platform.windows,
].expand((String s) => <String>['$s/', '$s\\']).toList();
// In all other build json files, allowed prefix names are ci and web_tests.
final List<String> ciNames = <String>[
'ci', 'web_tests'
].expand((String s) => <String>['$s/', '$s\\']).toList();
_forEachBuild(configs, (String name, BuilderConfig config, Build build) {
final List<String> goodPrefixes = name.contains('local_engine')
? osNames
: ciNames;
if (!goodPrefixes.any(build.name.startsWith)) {
if (name.contains('local_engine')) {
// TODO(zanderso): Check these builds as well after local_engine is
// fixed.
// https://github.com/flutter/flutter/issues/145263
return;
}
errors.add(
'${build.name} in $name must start with one of '
'{${goodPrefixes.join(', ')}}',
);
}
});
return errors;
}
void _forEachBuild(
Map<String, BuilderConfig> configs,
void Function(String configName, BuilderConfig config, Build build) fn,
) {
for (final String builderName in configs.keys) {
final BuilderConfig builderConfig = configs[builderName]!;
for (final Build build in builderConfig.builds) {
fn(builderName, builderConfig, build);
}
}
}