Skip to content

Commit cd0e4b1

Browse files
authored
Fix .ci.yaml validation for Fusion (the monorepo) (#4137)
Closes flutter/flutter#160707. Fixes a bug where `DEPS` was accidentally (or purposefully, there were no tests to verify) not checked for framework builds, and then expands on the validation by adding a check for `engine/**` for framework builds, adding a check nobody uses `runIfNot`, which is extremely broken and should not be used, and then lastly refactors the code and adds tests for the next shmuck to want to change this.
1 parent d868cd1 commit cd0e4b1

4 files changed

Lines changed: 402 additions & 27 deletions

File tree

app_dart/lib/src/model/ci_yaml/ci_yaml.dart

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ class CiYaml {
343343
/// 5. [pb.Target] should not depend on self
344344
/// 6. [pb.Target] cannot have more than 1 dependency
345345
/// 7. [pb.Target] should depend on target that already exist in depedency graph, and already recorded in map [targetGraph]
346-
/// 8. [pb.Target] has an empty runIf or the runIf includes `.ci.yaml` and `DEPS if on the engine repo.
346+
/// 8. [pb.Target] has an empty runIf or the runIf includes `.ci.yaml` `DEPS`, and `engine/**` if validating the framework.
347347
void _validate(pb.SchedulerConfig schedulerConfig, String branch, {pb.SchedulerConfig? totSchedulerConfig}) {
348348
if (schedulerConfig.targets.isEmpty) {
349349
throw const FormatException('Scheduler config must have at least 1 target');
@@ -394,26 +394,18 @@ class CiYaml {
394394

395395
// Verify runIf includes foundational files.
396396
if (target.runIf.isNotEmpty) {
397-
if (isFusion && type == CiType.fusionEngine) {
398-
// Look in different locations if fusion && engine ci.yaml
399-
if (!target.runIf.contains('engine/src/flutter/.ci.yaml')) {
400-
exceptions.add(
401-
'ERROR: ${target.name} is missing `engine/src/flutter/.ci.yaml` in runIf',
402-
);
403-
}
404-
if (!target.runIf.contains('DEPS')) {
405-
exceptions.add('ERROR: ${target.name} is missing `DEPS` in runIf');
406-
}
397+
if (isFusion) {
398+
_verifyTargetFusionRepo(target, exceptions);
407399
} else {
408-
// not fusion or not engine in fusion.
409-
if (!target.runIf.contains('.ci.yaml')) {
410-
exceptions.add('ERROR: ${target.name} is missing `.ci.yaml` in runIf');
411-
}
412-
if (slug == Config.engineSlug && !target.runIf.contains('DEPS')) {
413-
exceptions.add('ERROR: ${target.name} is missing `DEPS` in runIf');
414-
}
400+
_verifyTargetClassicRepo(target, exceptions);
415401
}
416402
}
403+
404+
// Verify runIfNot is never used, as it is a very confusing feature.
405+
// https://github.com/flutter/flutter/issues/147182
406+
if (target.runIfNot.isNotEmpty) {
407+
exceptions.add('ERROR: ${target.name}: Do not use runIfNot.');
408+
}
417409
}
418410

419411
/// Check the dependencies for the current target if it is viable and to
@@ -429,6 +421,61 @@ class CiYaml {
429421
_checkExceptions(exceptions);
430422
}
431423

424+
void _verifyTargetClassicRepo(pb.Target target, List<String> exceptions) {
425+
// These serve mostly as documentation.
426+
assert(!isFusion, 'Expected to be called in a non-monorepo');
427+
assert(target.runIf.isNotEmpty, 'Expected to be called when non-empty');
428+
429+
// 1. Every target must depend on .ci.yaml at the root of the repo.
430+
// TODO(matanlurey): Can this be inferred instead in the .ci.yaml parser?
431+
// See https://github.com/flutter/flutter/issues/160874.
432+
if (!target.runIf.contains('.ci.yaml')) {
433+
exceptions.add('ERROR: ${target.name} is missing `.ci.yaml` in runIf');
434+
}
435+
436+
// 2. The engine repo must additionally depend on DEPS.
437+
// TODO(matanlurey): Can this be inferred instead in the .ci.yaml parser?
438+
// See https://github.com/flutter/flutter/issues/160874.
439+
if (slug == Config.engineSlug && !target.runIf.contains('DEPS')) {
440+
exceptions.add('ERROR: ${target.name} is missing `DEPS` in runIf');
441+
}
442+
}
443+
444+
void _verifyTargetFusionRepo(pb.Target target, List<String> exceptions) {
445+
// These serve mostly as documentation.
446+
assert(isFusion, 'Expected to be called in a fusion monorepo');
447+
assert(target.runIf.isNotEmpty, 'Expected to be called when non-empty');
448+
assert(slug == Config.flutterSlug, 'Expected to be in the combined repo');
449+
450+
// 1. Every target must depend on .ci.yaml.
451+
// The path depends on whether the framework or engine are being validated;
452+
// while both belong in the same (mono)repo, they have separate .ci.yaml
453+
// files located in different paths.
454+
//
455+
// TODO(matanlurey): Can this be inferred instead in the .ci.yaml parser?
456+
// See https://github.com/flutter/flutter/issues/160874.
457+
final ciYamlPath = switch (type) {
458+
CiType.fusionEngine => 'engine/src/flutter/.ci.yaml',
459+
_ => '.ci.yaml',
460+
};
461+
if (!target.runIf.contains(ciYamlPath)) {
462+
exceptions.add('ERROR: ${target.name} is missing `$ciYamlPath` in runIf');
463+
}
464+
465+
// 2. Every target must depend on DEPS.
466+
// TODO(matanlurey): Can this be inferred instead in the .ci.yaml parser?
467+
// See https://github.com/flutter/flutter/issues/160874.
468+
if (!target.runIf.contains('DEPS')) {
469+
exceptions.add('ERROR: ${target.name} is missing `DEPS` in runIf');
470+
}
471+
472+
// 3. The framework tree must depend on engine/**.
473+
final isFrameworkCiYaml = type != CiType.fusionEngine;
474+
if (isFrameworkCiYaml && !target.runIf.contains('engine/**')) {
475+
exceptions.add('ERROR: ${target.name} is missing `engine/**` in runIf');
476+
}
477+
}
478+
432479
void _checkExceptions(List<String> exceptions) {
433480
if (exceptions.isNotEmpty) {
434481
final String fullException = exceptions.reduce((String exception, _) => '$exception\n');

0 commit comments

Comments
 (0)