diff --git a/engine/src/flutter/tools/pkg/engine_build_configs/bin/check.dart b/engine/src/flutter/tools/pkg/engine_build_configs/bin/check.dart index 54b96eb2f6444..e71c34421c7c1 100644 --- a/engine/src/flutter/tools/pkg/engine_build_configs/bin/check.dart +++ b/engine/src/flutter/tools/pkg/engine_build_configs/bin/check.dart @@ -137,6 +137,12 @@ void run( statusPrint('All build names must have a conforming prefix', success: buildNameErrors.isEmpty); indentedPrint(buildNameErrors); + // Check for duplicate archive paths in order to prevent builders from + // overwriting each other's artifacts in cloud storage. + final List duplicateArchives = checkForDuplicateArchives(configs); + statusPrint('Archive paths must be unique', success: duplicateArchives.isEmpty); + indentedPrint(duplicateArchives); + // If we have a successfully parsed .ci.yaml, perform additional checks. if (ciConfig == null) { return; @@ -228,6 +234,28 @@ List checkForDuplicateConfigs(Map configs) { return errors; } +// This check ensures that json files do not duplicate archive paths. +List checkForDuplicateArchives(Map configs) { + final RegExp zipPathPattern = RegExp(r'zip_archives/(.*\.zip)$'); + final List errors = []; + final Set archivePaths = {}; + _forEachBuild(configs, (String name, BuilderConfig config, Build build) { + for (final BuildArchive archive in build.archives) { + for (final String path in archive.includePaths) { + final RegExpMatch? match = zipPathPattern.firstMatch(path); + if (match == null) { + continue; + } + final String zipPath = match.group(1)!; + if (!archivePaths.add(zipPath)) { + errors.add('$zipPath is duplicated in $name\n'); + } + } + } + }); + return errors; +} + // This check ensures that builds are named in a way that is understood by // `et`. List checkForInvalidBuildNames(Map configs) { diff --git a/engine/src/flutter/tools/pkg/engine_build_configs/test/check_integration_test.dart b/engine/src/flutter/tools/pkg/engine_build_configs/test/check_integration_test.dart index e70792a876ac1..56e90b45c112c 100644 --- a/engine/src/flutter/tools/pkg/engine_build_configs/test/check_integration_test.dart +++ b/engine/src/flutter/tools/pkg/engine_build_configs/test/check_integration_test.dart @@ -214,4 +214,31 @@ void main() { expect(stderr.toString(), contains('❌ All builder files conform to release_build standards')); }); + + test('fails if archives.include_paths containts duplicates', () { + addConfig('android_debug', [ + { + 'name': 'ci/android_debug', + 'archives': [ + { + 'include_paths': ['out/android_debug/zip_archives/android-arm/artifacts.zip'], + }, + ], + }, + ]); + addConfig('android_debug2', [ + { + 'name': 'ci/android_debug2', + 'archives': [ + { + 'include_paths': ['out/android_debug2/zip_archives/android-arm/artifacts.zip'], + }, + ], + }, + ]); + + run(['--engine-src-path=${tmpFlutterEngineSrc.path}'], allowFailure: true); + + expect(stderr.toString(), contains('❌ Archive paths must be unique')); + }); }