Skip to content

Commit bc8c522

Browse files
[tool] Ensure that injected dependency overrides are sorted (#8542)
Updates `make-deps-path-based` to manually output dependency overrides in sorted order, as `YamlMap` apparently doesn't have a defined output ordering, leading to CI analysis failures in some cases. Fixes flutter/flutter#160810
1 parent 65177cb commit bc8c522

File tree

2 files changed

+113
-30
lines changed

2 files changed

+113
-30
lines changed

script/tool/lib/src/make_deps_path_based_command.dart

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,6 @@ class MakeDepsPathBasedCommand extends PackageCommand {
199199
.where(
200200
(String packageName) => localDependencies.containsKey(packageName))
201201
.toList();
202-
// Sort the combined list to avoid sort_pub_dependencies lint violations.
203-
packagesToOverride.sort();
204202

205203
if (packagesToOverride.isEmpty) {
206204
return false;
@@ -219,34 +217,60 @@ class MakeDepsPathBasedCommand extends PackageCommand {
219217
final YamlEditor editablePubspec = YamlEditor(pubspecContents);
220218
final YamlNode root = editablePubspec.parseAt(<String>[]);
221219
const String dependencyOverridesKey = 'dependency_overrides';
222-
// Ensure that there's a `dependencyOverridesKey` entry to update.
223-
if ((root as YamlMap)[dependencyOverridesKey] == null) {
224-
editablePubspec.update(<String>[dependencyOverridesKey], YamlMap());
225-
}
226-
for (final String packageName in packagesToOverride) {
227-
// Find the relative path from the common base to the local package.
228-
final List<String> repoRelativePathComponents = path.split(path
229-
.relative(localDependencies[packageName]!.path, from: repoRootPath));
230-
editablePubspec.update(<String>[
231-
dependencyOverridesKey,
232-
packageName
233-
], <String, String>{
234-
'path': p.posix.joinAll(<String>[
220+
221+
// Add in any existing overrides, then sort the combined list to avoid
222+
// sort_pub_dependencies lint violations.
223+
final YamlMap? existingOverrides =
224+
(root as YamlMap)[dependencyOverridesKey] as YamlMap?;
225+
final List<String> allDependencyOverrides = <String>{
226+
...existingOverrides?.keys.cast<String>() ?? <String>[],
227+
...packagesToOverride,
228+
}.toList();
229+
allDependencyOverrides.sort();
230+
231+
// Build a fresh overrides section, to ensure that new entries are sorted
232+
// with any existing entries. This does mean comments will be removed, but
233+
// since this command's changes will always be reverted anyway, that
234+
// shouldn't be an issue, whereas lint violations will cause analysis
235+
// failures.
236+
// This uses string concatenation rather than YamlMap because the latter
237+
// doesn't guarantee any particular output order for its entries.
238+
final List<String> newOverrideLines = <String>[];
239+
for (final String packageName in allDependencyOverrides) {
240+
final String overrideValue;
241+
if (packagesToOverride.contains(packageName)) {
242+
// Create a new override.
243+
244+
// Find the relative path from the common base to the local package.
245+
final List<String> repoRelativePathComponents = path.split(
246+
path.relative(localDependencies[packageName]!.path,
247+
from: repoRootPath));
248+
final String pathValue = p.posix.joinAll(<String>[
235249
...relativeBasePathComponents,
236250
...repoRelativePathComponents,
237-
])
238-
});
251+
]);
252+
overrideValue = '{path: $pathValue}';
253+
} else {
254+
// Re-use the pre-existing override.
255+
overrideValue = existingOverrides![packageName].toString();
256+
}
257+
newOverrideLines.add(' $packageName: $overrideValue');
239258
}
240-
241-
// Add the warning if it's not already there.
259+
// Create an empty section as a placeholder to replace.
260+
editablePubspec.update(<String>[dependencyOverridesKey], YamlMap());
242261
String newContent = editablePubspec.toString();
243-
if (!newContent.contains(_dependencyOverrideWarningComment)) {
244-
newContent = newContent.replaceFirst('$dependencyOverridesKey:', '''
245262

246-
$_dependencyOverrideWarningComment
247-
$dependencyOverridesKey:
263+
// Add the warning if it's not already there.
264+
final String warningComment =
265+
newContent.contains(_dependencyOverrideWarningComment)
266+
? ''
267+
: '$_dependencyOverrideWarningComment\n';
268+
// Replace the placeholder with the new section.
269+
newContent =
270+
newContent.replaceFirst(RegExp('$dependencyOverridesKey:\\s*{}\\n'), '''
271+
$warningComment$dependencyOverridesKey:
272+
${newOverrideLines.join('\n')}
248273
''');
249-
}
250274

251275
// Write the new pubspec.
252276
package.pubspecFile.writeAsStringSync(newContent);

script/tool/test/make_deps_path_based_command_test.dart

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,20 @@ ${devDependencies.map((String dep) => ' $dep: $constraint').join('\n')}
9393
''');
9494
}
9595

96+
/// Adds a 'dependency_overrides:' section with entries for each package in
97+
/// [overrides] to [package].
98+
void addDependencyOverridesSection(
99+
RepositoryPackage package, Iterable<String> overrides,
100+
{String path = '../'}) {
101+
final String originalContent = package.pubspecFile.readAsStringSync();
102+
package.pubspecFile.writeAsStringSync('''
103+
$originalContent
104+
105+
dependency_overrides:
106+
${overrides.map((String dep) => ' $dep:\n path: $path').join('\n')}
107+
''');
108+
}
109+
96110
Map<String, String> getDependencyOverrides(RepositoryPackage package) {
97111
final Pubspec pubspec = package.parsePubspec();
98112
return pubspec.dependencyOverrides.map((String name, Dependency dep) =>
@@ -424,12 +438,57 @@ ${devDependencies.map((String dep) => ' $dep: $constraint').join('\n')}
424438
' Modified packages/bar/bar_android/pubspec.yaml',
425439
' Modified packages/foo/pubspec.yaml',
426440
]));
427-
expect(simplePackageUpdatedContent,
428-
simplePackage.pubspecFile.readAsStringSync());
429-
expect(appFacingPackageUpdatedContent,
430-
pluginAppFacing.pubspecFile.readAsStringSync());
431-
expect(implementationPackageUpdatedContent,
432-
pluginImplementation.pubspecFile.readAsStringSync());
441+
expect(simplePackage.pubspecFile.readAsStringSync(),
442+
simplePackageUpdatedContent);
443+
expect(pluginAppFacing.pubspecFile.readAsStringSync(),
444+
appFacingPackageUpdatedContent);
445+
expect(pluginImplementation.pubspecFile.readAsStringSync(),
446+
implementationPackageUpdatedContent);
447+
});
448+
449+
test('sorts with existing overrides', () async {
450+
final RepositoryPackage simplePackage =
451+
createFakePackage('foo', packagesDir, isFlutter: true);
452+
final Directory pluginGroup = packagesDir.childDirectory('bar');
453+
454+
createFakePackage('bar_platform_interface', pluginGroup, isFlutter: true);
455+
final RepositoryPackage pluginImplementation =
456+
createFakePlugin('bar_android', pluginGroup);
457+
final RepositoryPackage pluginAppFacing =
458+
createFakePlugin('bar', pluginGroup);
459+
460+
addDependencies(simplePackage, <String>[
461+
'bar',
462+
'bar_android',
463+
'bar_platform_interface',
464+
]);
465+
addDependencies(pluginAppFacing, <String>[
466+
'bar_platform_interface',
467+
'bar_android',
468+
]);
469+
addDependencies(pluginImplementation, <String>[
470+
'bar_platform_interface',
471+
]);
472+
addDependencyOverridesSection(
473+
simplePackage,
474+
<String>['bar_android'],
475+
path: '../bar/bar_android',
476+
);
477+
478+
await runCapturingPrint(runner, <String>[
479+
'make-deps-path-based',
480+
'--target-dependencies=bar,bar_platform_interface'
481+
]);
482+
final String simplePackageUpdatedContent =
483+
simplePackage.pubspecFile.readAsStringSync();
484+
485+
expect(
486+
simplePackageUpdatedContent.split('\n'),
487+
containsAllInOrder(<Matcher>[
488+
contains(' bar:'),
489+
contains(' bar_android:'),
490+
contains(' bar_platform_interface:'),
491+
]));
433492
});
434493

435494
group('target-dependencies-with-non-breaking-updates', () {

0 commit comments

Comments
 (0)