-
Notifications
You must be signed in to change notification settings - Fork 6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Towards flutter/flutter#132807. This is needed so the engine tool can map the names of CI builders listed in `.ci.yaml` to the names of the configurations in the build config json files in `ci/builders`.
- Loading branch information
Showing
5 changed files
with
400 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
// 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 'package:yaml/yaml.dart' as y; | ||
|
||
// This file contains classes for parsing information about CI configuration | ||
// from the .ci.yaml file at the root of the flutter/engine repository. | ||
// The meanings of the sections and fields are documented at: | ||
// | ||
// https://github.com/flutter/cocoon/blob/main/CI_YAML.md | ||
// | ||
// The classes here don't parse every possible field, but rather only those that | ||
// are useful for working locally in the engine repo. | ||
|
||
const String _targetsField = 'targets'; | ||
const String _nameField = 'name'; | ||
const String _recipeField = 'recipe'; | ||
const String _propertiesField = 'properties'; | ||
const String _configNameField = 'config_name'; | ||
|
||
/// A class containing the information deserialized from the .ci.yaml file. | ||
/// | ||
/// The file contains three sections. "enabled_branches", "platform_properties", | ||
/// and "targets". The "enabled_branches" section is not meaningful when working | ||
/// locally. The configurations listed in the "targets" section inherit | ||
/// properties listed in the "platform_properties" section depending on their | ||
/// names. The configurations listed in the "targets" section are the names, | ||
/// recipes, build configs, etc. of the builders in CI. | ||
class CiConfig { | ||
/// Builds a [CiConfig] instance from parsed yaml data. | ||
/// | ||
/// If the yaml was malformed, then `CiConfig.valid` will be false, and | ||
/// `CiConfig.error` will be populated with an informative error message. | ||
/// Otherwise, `CiConfig.ciTargets` will contain a mapping from target name | ||
/// to [CiTarget] instance. | ||
factory CiConfig.fromYaml(y.YamlNode yaml) { | ||
if (yaml is! y.YamlMap) { | ||
final String error = yaml.span.message('Expected a map'); | ||
return CiConfig._error(error); | ||
} | ||
final y.YamlMap ymap = yaml; | ||
final y.YamlNode? targetsNode = ymap.nodes[_targetsField]; | ||
if (targetsNode == null) { | ||
final String error = ymap.span.message('Expected a "$_targetsField" key'); | ||
return CiConfig._error(error); | ||
} | ||
if (targetsNode is! y.YamlList) { | ||
final String error = targetsNode.span.message( | ||
'Expected "$_targetsField" to be a list.', | ||
); | ||
return CiConfig._error(error); | ||
} | ||
final y.YamlList targetsList = targetsNode; | ||
|
||
final Map<String, CiTarget> result = <String, CiTarget>{}; | ||
for (final y.YamlNode yamlTarget in targetsList.nodes) { | ||
final CiTarget target = CiTarget.fromYaml(yamlTarget); | ||
if (!target.valid) { | ||
return CiConfig._error(target.error); | ||
} | ||
result[target.name] = target; | ||
} | ||
|
||
return CiConfig._(ciTargets: result); | ||
} | ||
|
||
CiConfig._({ | ||
required this.ciTargets, | ||
}) : error = null; | ||
|
||
CiConfig._error( | ||
this.error, | ||
) : ciTargets = <String, CiTarget>{}; | ||
|
||
/// Information about CI builder configurations, which .ci.yaml calls | ||
/// "targets". | ||
final Map<String, CiTarget> ciTargets; | ||
|
||
/// An error message when this instance is invalid. | ||
final String? error; | ||
|
||
/// Whether this is a valid instance. | ||
late final bool valid = error == null; | ||
} | ||
|
||
/// Information about the configuration of a builder on CI, which .ci.yaml | ||
/// calls a "target". | ||
class CiTarget { | ||
/// Builds a [CiTarget] from parsed yaml data. | ||
/// | ||
/// If the yaml was malformed then `CiTarget.valid` is false and | ||
/// `CiTarget.error` contains a useful error message. Otherwise, the other | ||
/// fields contain information about the target. | ||
factory CiTarget.fromYaml(y.YamlNode yaml) { | ||
if (yaml is! y.YamlMap) { | ||
final String error = yaml.span.message('Expected a map.'); | ||
return CiTarget._error(error); | ||
} | ||
final y.YamlMap targetMap = yaml; | ||
final String? name = _stringOfNode(targetMap.nodes[_nameField]); | ||
if (name == null) { | ||
final String error = targetMap.span.message( | ||
'Expected map to contain a string value for key "$_nameField".', | ||
); | ||
return CiTarget._error(error); | ||
} | ||
|
||
final String? recipe = _stringOfNode(targetMap.nodes[_recipeField]); | ||
if (recipe == null) { | ||
final String error = targetMap.span.message( | ||
'Expected map to contain a string value for key "$_recipeField".', | ||
); | ||
return CiTarget._error(error); | ||
} | ||
|
||
final y.YamlNode? propertiesNode = targetMap.nodes[_propertiesField]; | ||
if (propertiesNode == null) { | ||
final String error = targetMap.span.message( | ||
'Expected map to contain a string value for key "$_propertiesField".', | ||
); | ||
return CiTarget._error(error); | ||
} | ||
final CiTargetProperties properties = CiTargetProperties.fromYaml( | ||
propertiesNode, | ||
); | ||
if (!properties.valid) { | ||
return CiTarget._error(properties.error); | ||
} | ||
|
||
return CiTarget._( | ||
name: name, | ||
recipe: recipe, | ||
properties: properties, | ||
); | ||
} | ||
|
||
CiTarget._({ | ||
required this.name, | ||
required this.recipe, | ||
required this.properties, | ||
}) : error = null; | ||
|
||
CiTarget._error( | ||
this.error, | ||
) : name = '', | ||
recipe = '', | ||
properties = CiTargetProperties._error('Invalid'); | ||
|
||
/// The name of the builder in CI. | ||
final String name; | ||
|
||
/// The CI recipe used to run the build. | ||
final String recipe; | ||
|
||
/// The properties of the build or builder. | ||
final CiTargetProperties properties; | ||
|
||
/// An error message when this instance is invalid. | ||
final String? error; | ||
|
||
/// Whether this is a valid instance. | ||
late final bool valid = error == null; | ||
} | ||
|
||
/// Various properties of a [CiTarget]. | ||
class CiTargetProperties { | ||
/// Builds a [CiTargetProperties] instance from parsed yaml data. | ||
/// | ||
/// If the yaml was malformed then `CiTargetProperties.valid` is false and | ||
/// `CiTargetProperties.error` contains a useful error message. Otherwise, the | ||
/// other fields contain information about the target properties. | ||
factory CiTargetProperties.fromYaml(y.YamlNode yaml) { | ||
if (yaml is! y.YamlMap) { | ||
final String error = yaml.span.message( | ||
'Expected "$_propertiesField" to be a map.', | ||
); | ||
return CiTargetProperties._error(error); | ||
} | ||
final y.YamlMap propertiesMap = yaml; | ||
final String? configName = _stringOfNode( | ||
propertiesMap.nodes[_configNameField], | ||
); | ||
return CiTargetProperties._( | ||
configName: configName ?? '', | ||
); | ||
} | ||
|
||
CiTargetProperties._({ | ||
required this.configName, | ||
}) : error = null; | ||
|
||
CiTargetProperties._error( | ||
this.error, | ||
) : configName = ''; | ||
|
||
/// The name of the build configuration. If the containing [CiTarget] instance | ||
/// is using the engine_v2 recipes, then this name is the same as the name | ||
/// of the build config json file under ci/builders. | ||
final String configName; | ||
|
||
/// An error message when this instance is invalid. | ||
final String? error; | ||
|
||
/// Whether this is a valid instance. | ||
late final bool valid = error == null; | ||
} | ||
|
||
String? _stringOfNode(y.YamlNode? stringNode) { | ||
if (stringNode == null) { | ||
return null; | ||
} | ||
if (stringNode is! y.YamlScalar) { | ||
return null; | ||
} | ||
final y.YamlScalar stringScalar = stringNode; | ||
if (stringScalar.value is! String) { | ||
return null; | ||
} | ||
return stringScalar.value as String; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.