Skip to content

Commit 932c17b

Browse files
authored
Support post-submit builds for experimental branches. (#4679)
Towards flutter/flutter#168738. I believe this is the last PR, we can do some validation testing after it (and it's friends) merge. /cc @vashworth
1 parent 99e66c5 commit 932c17b

3 files changed

Lines changed: 179 additions & 38 deletions

File tree

app_dart/lib/src/service/luci_build_service.dart

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,37 @@ class LuciBuildService {
188188
return builds;
189189
}
190190

191+
/// Checks if [proposedVersion] exists as a recipe branch.
192+
///
193+
/// If it does not, logs and falls back to [CipdVersion.defaultRecipe].
194+
Future<CipdVersion> _getAndCheckRecipeVersion({
195+
required RepositorySlug slug,
196+
required String branch,
197+
}) async {
198+
if (slug != Config.flutterSlug) {
199+
log.debug('Using default recipe: $slug is not flutter/flutter');
200+
return _config.defaultRecipeBundleRef;
201+
}
202+
if (branch == Config.defaultBranch(Config.flutterSlug)) {
203+
log.debug('Using default recipe: $branch is the default branch');
204+
return _config.defaultRecipeBundleRef;
205+
}
206+
final proposed = CipdVersion(branch: branch);
207+
final branches = await _gerritService.branches(
208+
'flutter-review.googlesource.com',
209+
'recipes',
210+
filterRegex: 'flutter-.*|fuchsia.*',
211+
);
212+
if (branches.contains(proposed.version)) {
213+
return proposed;
214+
}
215+
log.warn(
216+
'Falling back to default recipe, could not find "${proposed.version}" '
217+
'in $branches.',
218+
);
219+
return _config.defaultRecipeBundleRef;
220+
}
221+
191222
/// Schedules presubmit [targets] on BuildBucket for [pullRequest].
192223
///
193224
/// [engineArtifacts] determines how framework tests download and use the Flutter engine by
@@ -205,33 +236,10 @@ class LuciBuildService {
205236
final batchRequestList = <bbv2.BatchRequest_Request>[];
206237
final commitSha = pullRequest.head!.sha!;
207238
final isFusion = pullRequest.base!.repo!.slug() == Config.flutterSlug;
208-
final CipdVersion cipdVersion;
209-
{
210-
final baseRef = pullRequest.base!.ref!;
211-
212-
// If this isn't flutter/flutter *OR* it's flutter/flutter master, use the default CIPD recipe.
213-
// We don't create CIPD recipes for other repositories (see https://github.com/flutter/flutter/issues/164592).
214-
if (!isFusion ||
215-
Config.defaultBranch(pullRequest.base!.repo!.slug()) == baseRef) {
216-
cipdVersion = CipdVersion.defaultRecipe;
217-
} else {
218-
final proposedVersion = CipdVersion(branch: pullRequest.base!.ref!);
219-
final branches = await _gerritService.branches(
220-
'flutter-review.googlesource.com',
221-
'recipes',
222-
filterRegex: 'flutter-.*|fuchsia.*',
223-
);
224-
if (branches.contains(proposedVersion.version)) {
225-
cipdVersion = proposedVersion;
226-
} else {
227-
log.warn(
228-
'Falling back to default recipe, could not find '
229-
'"${proposedVersion.version}" in $branches.',
230-
);
231-
cipdVersion = _config.defaultRecipeBundleRef;
232-
}
233-
}
234-
}
239+
final cipdVersion = await _getAndCheckRecipeVersion(
240+
slug: pullRequest.base!.repo!.slug(),
241+
branch: pullRequest.base!.ref!,
242+
);
235243

236244
final checkRuns = <github.CheckRun>[];
237245
for (var target in targets) {
@@ -844,8 +852,11 @@ class LuciBuildService {
844852
processedProperties['git_branch'] = commit.branch;
845853
processedProperties['git_repo'] = commit.slug.name;
846854

847-
final cipdExe = 'refs/heads/${commit.branch}';
848-
processedProperties['exe_cipd_version'] = cipdExe;
855+
final cipdVersion = await _getAndCheckRecipeVersion(
856+
slug: commit.slug,
857+
branch: commit.branch,
858+
);
859+
processedProperties['exe_cipd_version'] = cipdVersion.version;
849860

850861
final isFusion = commit.slug == Config.flutterSlug;
851862
if (isFusion) {
@@ -862,15 +873,20 @@ class LuciBuildService {
862873
// Prod build bucket, built during the merge queue.
863874
'flutter_realm': '',
864875
});
876+
} else if (commit.branch != Config.defaultBranch(Config.flutterSlug)) {
877+
// Experimental branches do not have:
878+
// - A merge queue that prebuilds binaries for the current SHA
879+
// - A flutter_release_builder pre-step
880+
//
881+
// ... so, just re-use the binaries that were built in presubmit.
882+
processedProperties['flutter_realm'] = 'flutter_archives_v2';
865883
}
866884
}
867885
final propertiesStruct = bbv2.Struct.create();
868886
propertiesStruct.mergeFromProto3Json(processedProperties);
869887

870888
final requestedDimensions = target.getDimensions();
871-
872-
final executable = bbv2.Executable(cipdVersion: cipdExe);
873-
889+
final executable = bbv2.Executable(cipdVersion: cipdVersion.version);
874890
log.info(
875891
'Constructing the postsubmit schedule build request for ${target.name} on commit ${commit.sha}.',
876892
);

app_dart/lib/src/service/luci_build_service/cipd_version.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ final class CipdVersion {
1414
/// The default recipe to use
1515
static const defaultRecipe = CipdVersion(branch: 'main');
1616

17-
/// The version string, in the format, `refs/head/{{branch}}`.
17+
/// The version string, in the format, `refs/heads/{{branch}}`.
1818
final String version;
1919

2020
@override

app_dart/test/service/luci_build_service/schedule_prod_builds_test.dart

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,19 @@ void main() {
4343
late MockGithubChecksUtil mockGithubChecksUtil;
4444
late FakeFirestoreService firestore;
4545
late FakePubSub pubSub;
46+
late FakeGerritService gerrit;
4647

4748
setUp(() {
4849
mockBuildBucketClient = MockBuildBucketClient();
4950
mockGithubChecksUtil = MockGithubChecksUtil();
5051
firestore = FakeFirestoreService();
5152
pubSub = FakePubSub();
53+
gerrit = FakeGerritService();
5254

5355
luci = LuciBuildService(
5456
cache: CacheService(inMemory: true),
5557
config: FakeConfig(),
56-
gerritService: FakeGerritService(),
58+
gerritService: gerrit,
5759
buildBucketClient: mockBuildBucketClient,
5860
githubChecksUtil: mockGithubChecksUtil,
5961
pubsub: pubSub,
@@ -248,7 +250,11 @@ void main() {
248250
});
249251

250252
test('schedules a post-submit build inside of flutter/flutter', () async {
251-
final commit = generateFirestoreCommit(1, branch: 'main', repo: 'flutter');
253+
final commit = generateFirestoreCommit(
254+
1,
255+
branch: 'master',
256+
repo: 'flutter',
257+
);
252258

253259
when(mockBuildBucketClient.listBuilders(any)).thenAnswer((_) async {
254260
return bbv2.ListBuildersResponse(
@@ -318,7 +324,7 @@ void main() {
318324
expect(scheduleBuild.properties.fields, {
319325
'dependencies': bbv2.Value(listValue: bbv2.ListValue()),
320326
'bringup': bbv2.Value(boolValue: false),
321-
'git_branch': bbv2.Value(stringValue: 'main'),
327+
'git_branch': bbv2.Value(stringValue: 'master'),
322328
'git_repo': bbv2.Value(stringValue: 'flutter'),
323329
'exe_cipd_version': bbv2.Value(stringValue: 'refs/heads/main'),
324330
'os': bbv2.Value(stringValue: 'debian-10.12'),
@@ -343,7 +349,11 @@ void main() {
343349
});
344350

345351
test('schedules a post-submit build that is a > attempt #1', () async {
346-
final commit = generateFirestoreCommit(1, branch: 'main', repo: 'flutter');
352+
final commit = generateFirestoreCommit(
353+
1,
354+
branch: 'master',
355+
repo: 'flutter',
356+
);
347357

348358
when(mockBuildBucketClient.listBuilders(any)).thenAnswer((_) async {
349359
return bbv2.ListBuildersResponse(
@@ -413,7 +423,7 @@ void main() {
413423
expect(scheduleBuild.properties.fields, {
414424
'dependencies': bbv2.Value(listValue: bbv2.ListValue()),
415425
'bringup': bbv2.Value(boolValue: false),
416-
'git_branch': bbv2.Value(stringValue: 'main'),
426+
'git_branch': bbv2.Value(stringValue: 'master'),
417427
'git_repo': bbv2.Value(stringValue: 'flutter'),
418428
'exe_cipd_version': bbv2.Value(stringValue: 'refs/heads/main'),
419429
'os': bbv2.Value(stringValue: 'debian-10.12'),
@@ -459,6 +469,8 @@ void main() {
459469
);
460470
});
461471

472+
gerrit.branchesValue = ['refs/heads/flutter-0.42-candidate.0'];
473+
462474
await expectLater(
463475
luci.schedulePostsubmitBuilds(
464476
commit: commit.toRef(),
@@ -541,6 +553,119 @@ void main() {
541553
);
542554
});
543555

556+
// Regression test for https://github.com/flutter/flutter/issues/168738.
557+
test('schedules a post-submit experimental branch build', () async {
558+
final commit = generateFirestoreCommit(
559+
1,
560+
// Notably not a release-candidate branch and not "master".
561+
branch: 'ios-experimental',
562+
repo: 'flutter',
563+
);
564+
565+
when(mockBuildBucketClient.listBuilders(any)).thenAnswer((_) async {
566+
return bbv2.ListBuildersResponse(
567+
builders: [
568+
bbv2.BuilderItem(
569+
id: bbv2.BuilderID(
570+
bucket: 'prod',
571+
project: 'flutter',
572+
builder: 'Linux 1',
573+
),
574+
),
575+
],
576+
);
577+
});
578+
579+
// Notably we don't require a recipe branch, and fallback to main.
580+
gerrit.branchesValue = [];
581+
582+
await expectLater(
583+
luci.schedulePostsubmitBuilds(
584+
commit: commit.toRef(),
585+
toBeScheduled: [
586+
PendingTask(
587+
target: generateTarget(
588+
1,
589+
properties: {
590+
'recipe': 'devicelab/devicelab',
591+
'os': 'debian-10.12',
592+
},
593+
slug: Config.flutterSlug,
594+
),
595+
taskName: generateFirestoreTask(1, commitSha: commit.sha).taskName,
596+
priority: LuciBuildService.kDefaultPriority,
597+
currentAttempt: 1,
598+
),
599+
],
600+
),
601+
completion(isEmpty),
602+
);
603+
604+
final bbv2.ScheduleBuildRequest scheduleBuild;
605+
{
606+
final batchRequest = bbv2.BatchRequest().createEmptyInstance();
607+
batchRequest.mergeFromProto3Json(pubSub.messages.single);
608+
609+
expect(batchRequest.requests, hasLength(1));
610+
scheduleBuild = batchRequest.requests.single.scheduleBuild;
611+
}
612+
613+
expect(
614+
scheduleBuild.builder,
615+
isA<bbv2.BuilderID>()
616+
.having((b) => b.bucket, 'bucket', 'prod')
617+
.having((b) => b.builder, 'builder', 'Linux 1'),
618+
);
619+
620+
expect(
621+
scheduleBuild.notify.pubsubTopic,
622+
'projects/flutter-dashboard/topics/build-bucket-postsubmit',
623+
);
624+
625+
expect(
626+
PostsubmitUserData.fromBytes(scheduleBuild.notify.userData),
627+
PostsubmitUserData(
628+
taskId: fs.TaskId.parse('1_task1_1'),
629+
checkRunId: null /* Uses batch backfiller */,
630+
),
631+
);
632+
633+
expect(
634+
scheduleBuild.properties.fields,
635+
isNot(contains('flutter_prebuilt_engine_version')),
636+
reason: 'Experimental branches use default engine SHA resolution',
637+
);
638+
639+
expect(scheduleBuild.properties.fields, {
640+
'dependencies': bbv2.Value(listValue: bbv2.ListValue()),
641+
'bringup': bbv2.Value(boolValue: false),
642+
'git_branch': bbv2.Value(stringValue: 'ios-experimental'),
643+
'git_repo': bbv2.Value(stringValue: 'flutter'),
644+
'exe_cipd_version': bbv2.Value(stringValue: 'refs/heads/main'),
645+
'os': bbv2.Value(stringValue: 'debian-10.12'),
646+
'recipe': bbv2.Value(stringValue: 'devicelab/devicelab'),
647+
'is_fusion': bbv2.Value(stringValue: 'true'),
648+
649+
// Experimental branches use the presubmit bucket for engine builds.
650+
'flutter_realm': bbv2.Value(stringValue: 'flutter_archives_v2'),
651+
});
652+
653+
expect(scheduleBuild.dimensions, [
654+
isA<bbv2.RequestedDimension>()
655+
.having((d) => d.key, 'key', 'os')
656+
.having((d) => d.value, 'value', 'debian-10.12'),
657+
]);
658+
659+
verifyNever(
660+
mockGithubChecksUtil.createCheckRun(
661+
any,
662+
Config.packagesSlug,
663+
any,
664+
'Linux 1',
665+
),
666+
);
667+
});
668+
544669
test('does not run a non-existent builder', () async {
545670
final commit = generateFirestoreCommit(1, branch: 'main', repo: 'flutter');
546671

0 commit comments

Comments
 (0)