Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 2e94a52

Browse files
committed
Starts a .ci.yaml parser
1 parent 3557277 commit 2e94a52

File tree

5 files changed

+379
-0
lines changed

5 files changed

+379
-0
lines changed

.ci.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ platform_properties:
4444
]
4545
device_type: none
4646
os: Windows-10
47+
4748
# The current android emulator config names can be found here:
4849
# https://chromium.googlesource.com/chromium/src.git/+/HEAD/tools/android/avd/proto
4950
# You may use those names for the android_virtual_device version.

tools/engine_tool/pubspec.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,11 @@ dependency_overrides:
6666
path: ../../third_party/pkg/process_runner
6767
smith:
6868
path: ../../../third_party/dart/pkg/smith
69+
source_span:
70+
path: ../../../third_party/dart/third_party/pkg/source_span
71+
string_scanner:
72+
path: ../../../third_party/dart/third_party/pkg/string_scanner
73+
term_glyph:
74+
path: ../../../third_party/dart/third_party/pkg/term_glyph
75+
yaml:
76+
path: ../../../third_party/dart/third_party/pkg/yaml
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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:yaml/yaml.dart' as y;
6+
7+
// This file contains classes for parsing information about CI configuration
8+
// from the .ci.yaml file at the root of the flutter/engine repository.
9+
// The meanings of the sections and fields are documented at:
10+
//
11+
// https://github.com/flutter/cocoon/blob/main/CI_YAML.md
12+
//
13+
// The classes here don't parse every possible field, but rather only those that
14+
// are useful for working locally in the engine repo.
15+
16+
const String _targetsField = 'targets';
17+
const String _nameField = 'name';
18+
const String _recipeField = 'recipe';
19+
const String _propertiesField = 'properties';
20+
const String _configNameField = 'config_name';
21+
22+
/// A class containing the information deserialized from the .ci.yaml file.
23+
///
24+
/// The file contains three sections. "enabled_branches", "platform_properties",
25+
/// and "targets". The "enabled_branches" section is not meaningful when working
26+
/// locally. The configurations listed in the "targets" section inherit
27+
/// properties listed in the "platform_properties" section depending on their
28+
/// names. The configurations listed in the "targets" section are the names,
29+
/// recipes, build configs, etc. of the builders in CI.
30+
class CiConfig {
31+
/// Builds a [CiConfig] instance from parsed yaml data.
32+
///
33+
/// If the yaml was malformed, then `CiConfig.valid` will be false, and
34+
/// `CiConfig.error` will be populated with an informative error message.
35+
/// Otherwise, `CiConfig.ciTargets` will contain a mapping from target name
36+
/// to [CiTarget] instance.
37+
factory CiConfig.fromYaml(y.YamlNode yaml) {
38+
if (yaml is! y.YamlMap) {
39+
final String error = yaml.span.message('Expected a map');
40+
return CiConfig._error(error);
41+
}
42+
final y.YamlMap ymap = yaml;
43+
final y.YamlNode? targetsNode = ymap.nodes[_targetsField];
44+
if (targetsNode == null) {
45+
final String error = ymap.span.message('Expected a "$_targetsField" key');
46+
return CiConfig._error(error);
47+
}
48+
if (targetsNode is! y.YamlList) {
49+
final String error = targetsNode.span.message(
50+
'Expected "$_targetsField" to be a list.',
51+
);
52+
return CiConfig._error(error);
53+
}
54+
final y.YamlList targetsList = targetsNode;
55+
56+
final Map<String, CiTarget> result = <String, CiTarget>{};
57+
for (final y.YamlNode yamlTarget in targetsList.nodes) {
58+
final CiTarget target = CiTarget.fromYaml(yamlTarget);
59+
if (!target.valid) {
60+
return CiConfig._error(target.error);
61+
}
62+
result[target.name] = target;
63+
}
64+
65+
return CiConfig._(ciTargets: result);
66+
}
67+
68+
CiConfig._({
69+
required this.ciTargets,
70+
}) : error = null;
71+
72+
CiConfig._error(
73+
this.error,
74+
) : ciTargets = <String, CiTarget>{};
75+
76+
/// Information about CI builder configurations, which .ci.yaml calls
77+
/// "targets".
78+
final Map<String, CiTarget> ciTargets;
79+
80+
/// An error message when this instance is invalid.
81+
final String? error;
82+
83+
/// Whether this is a valid instance.
84+
late final bool valid = error == null;
85+
}
86+
87+
/// Information about the configuration of a builder on CI, which .ci.yaml
88+
/// calls a "target".
89+
class CiTarget {
90+
/// Builds a [CiTarget] from parsed yaml data.
91+
///
92+
/// If the yaml was malformed then `CiTarget.valid` is false and
93+
/// `CiTarget.error` contains a useful error message. Otherwise, the other
94+
/// fields contain information about the target.
95+
factory CiTarget.fromYaml(y.YamlNode yaml) {
96+
if (yaml is! y.YamlMap) {
97+
final String error = yaml.span.message('Expected a map.');
98+
return CiTarget._error(error);
99+
}
100+
final y.YamlMap targetMap = yaml;
101+
final String? name = _stringOfNode(targetMap.nodes[_nameField]);
102+
if (name == null) {
103+
final String error = targetMap.span.message(
104+
'Expected map to contain a string value for key "$_nameField".',
105+
);
106+
return CiTarget._error(error);
107+
}
108+
109+
final String? recipe = _stringOfNode(targetMap.nodes[_recipeField]);
110+
if (recipe == null) {
111+
final String error = targetMap.span.message(
112+
'Expected map to contain a string value for key "$_recipeField".',
113+
);
114+
return CiTarget._error(error);
115+
}
116+
117+
final y.YamlNode? propertiesNode = targetMap.nodes[_propertiesField];
118+
if (propertiesNode == null) {
119+
final String error = targetMap.span.message(
120+
'Expected map to contain a string value for key "$_propertiesField".',
121+
);
122+
return CiTarget._error(error);
123+
}
124+
final CiTargetProperties properties = CiTargetProperties.fromYaml(
125+
propertiesNode,
126+
);
127+
if (!properties.valid) {
128+
return CiTarget._error(properties.error);
129+
}
130+
131+
return CiTarget._(
132+
name: name,
133+
recipe: recipe,
134+
properties: properties,
135+
);
136+
}
137+
138+
CiTarget._({
139+
required this.name,
140+
required this.recipe,
141+
required this.properties,
142+
}) : error = null;
143+
144+
CiTarget._error(
145+
this.error,
146+
) : name = '',
147+
recipe = '',
148+
properties = CiTargetProperties._error('Invalid');
149+
150+
/// The name of the builder in CI.
151+
final String name;
152+
153+
/// The CI recipe used to run the build.
154+
final String recipe;
155+
156+
/// The properties of the build or builder.
157+
final CiTargetProperties properties;
158+
159+
/// An error message when this instance is invalid.
160+
final String? error;
161+
162+
/// Whether this is a valid instance.
163+
late final bool valid = error == null;
164+
}
165+
166+
/// Various properties of a [CiTarget].
167+
class CiTargetProperties {
168+
/// Builds a [CiTargetProperties] instance from parsed yaml data.
169+
///
170+
/// If the yaml was malformed then `CiTargetProperties.valid` is false and
171+
/// `CiTargetProperties.error` contains a useful error message. Otherwise, the
172+
/// other fields contain information about the target properties.
173+
factory CiTargetProperties.fromYaml(y.YamlNode yaml) {
174+
if (yaml is! y.YamlMap) {
175+
final String error = yaml.span.message(
176+
'Expected "$_propertiesField" to be a map.',
177+
);
178+
return CiTargetProperties._error(error);
179+
}
180+
final y.YamlMap propertiesMap = yaml;
181+
final String? configName = _stringOfNode(
182+
propertiesMap.nodes[_configNameField],
183+
);
184+
return CiTargetProperties._(
185+
configName: configName ?? '',
186+
);
187+
}
188+
189+
CiTargetProperties._({
190+
required this.configName,
191+
}) : error = null;
192+
193+
CiTargetProperties._error(
194+
this.error,
195+
) : configName = '';
196+
197+
/// The name of the build configuration. If the containing [CiTarget] instance
198+
/// is using the engine_v2 recipes, then this name is the same as the name
199+
/// of the build config json file under ci/builders.
200+
final String configName;
201+
202+
/// An error message when this instance is invalid.
203+
final String? error;
204+
205+
/// Whether this is a valid instance.
206+
late final bool valid = error == null;
207+
}
208+
209+
String? _stringOfNode(y.YamlNode? stringNode) {
210+
if (stringNode == null) {
211+
return null;
212+
}
213+
if (stringNode is! y.YamlScalar) {
214+
return null;
215+
}
216+
final y.YamlScalar stringScalar = stringNode;
217+
if (stringScalar.value is! String) {
218+
return null;
219+
}
220+
return stringScalar.value as String;
221+
}

tools/pkg/engine_build_configs/pubspec.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dependencies:
2525
path: any
2626
platform: any
2727
process_runner: any
28+
yaml: any
2829

2930
dev_dependencies:
3031
async_helper: any
@@ -33,6 +34,7 @@ dev_dependencies:
3334
process_fakes:
3435
path: ../process_fakes
3536
smith: any
37+
source_span: any
3638

3739
dependency_overrides:
3840
args:
@@ -61,3 +63,11 @@ dependency_overrides:
6163
path: ../../../third_party/pkg/process_runner
6264
smith:
6365
path: ../../../../third_party/dart/pkg/smith
66+
source_span:
67+
path: ../../../../third_party/dart/third_party/pkg/source_span
68+
string_scanner:
69+
path: ../../../../third_party/dart/third_party/pkg/string_scanner
70+
term_glyph:
71+
path: ../../../../third_party/dart/third_party/pkg/term_glyph
72+
yaml:
73+
path: ../../../../third_party/dart/third_party/pkg/yaml

0 commit comments

Comments
 (0)