Skip to content

Commit 79fbb7a

Browse files
[tool] Include examples when pathifying deps (#3393)
Improves the `make-deps-path-based` command: - When adding an override to a package, also adds it to that package's examples. This is needed for integration tests of app-facing packages of federated plugins when adding features to implementation packages, for instance. - Switches from string-based rewrites to `yaml_edit` to make it more robust (necessary to do the above without major restructuring) - Improves the auto-added comment since reviewers new to the repository are often confused by the overrides the first time they encounter them and think their inclusion in the PR is a mistake. Fixes flutter/flutter#111091
1 parent c0a5352 commit 79fbb7a

File tree

2 files changed

+205
-104
lines changed

2 files changed

+205
-104
lines changed

script/tool/lib/src/make_deps_path_based_command.dart

Lines changed: 85 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@ import 'package:file/file.dart';
66
import 'package:git/git.dart';
77
import 'package:path/path.dart' as p;
88
import 'package:pub_semver/pub_semver.dart';
9+
import 'package:yaml/yaml.dart';
10+
import 'package:yaml_edit/yaml_edit.dart';
911

1012
import 'common/core.dart';
1113
import 'common/git_version_finder.dart';
1214
import 'common/package_command.dart';
1315
import 'common/repository_package.dart';
1416

1517
const int _exitPackageNotFound = 3;
16-
const int _exitCannotUpdatePubspec = 4;
17-
18-
enum _RewriteOutcome { changed, noChangesNeeded, alreadyChanged }
1918

2019
/// Converts all dependencies on target packages to path-based dependencies.
2120
///
@@ -50,8 +49,12 @@ class MakeDepsPathBasedCommand extends PackageCommand {
5049
'target-dependencies-with-non-breaking-updates';
5150

5251
// The comment to add to temporary dependency overrides.
52+
//
53+
// Includes a reference to the docs so that reviewers who aren't familiar with
54+
// the federated plugin change process don't think it's a mistake.
5355
static const String _dependencyOverrideWarningComment =
54-
'# FOR TESTING ONLY. DO NOT MERGE.';
56+
'# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.\n'
57+
'# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins';
5558

5659
@override
5760
final String name = 'make-deps-path-based';
@@ -80,17 +83,10 @@ class MakeDepsPathBasedCommand extends PackageCommand {
8083
for (final File pubspec in await _getAllPubspecs()) {
8184
final String displayPath = p.posix.joinAll(
8285
path.split(path.relative(pubspec.absolute.path, from: repoRootPath)));
83-
final _RewriteOutcome outcome = await _addDependencyOverridesIfNecessary(
84-
pubspec, localDependencyPackages);
85-
switch (outcome) {
86-
case _RewriteOutcome.changed:
87-
print(' Modified $displayPath');
88-
break;
89-
case _RewriteOutcome.alreadyChanged:
90-
print(' Skipped $displayPath - Already rewritten');
91-
break;
92-
case _RewriteOutcome.noChangesNeeded:
93-
break;
86+
final bool changed = await _addDependencyOverridesIfNecessary(
87+
RepositoryPackage(pubspec.parent), localDependencyPackages);
88+
if (changed) {
89+
print(' Modified $displayPath');
9490
}
9591
}
9692
}
@@ -137,69 +133,94 @@ class MakeDepsPathBasedCommand extends PackageCommand {
137133
/// If [pubspecFile] has any dependencies on packages in [localDependencies],
138134
/// adds dependency_overrides entries to redirect them to the local version
139135
/// using path-based dependencies.
140-
Future<_RewriteOutcome> _addDependencyOverridesIfNecessary(File pubspecFile,
141-
Map<String, RepositoryPackage> localDependencies) async {
142-
final String pubspecContents = pubspecFile.readAsStringSync();
143-
final Pubspec pubspec = Pubspec.parse(pubspecContents);
144-
// Fail if there are any dependency overrides already, other than ones
145-
// created by this script. If support for that is needed at some point, it
146-
// can be added, but currently it's not and relying on that makes the logic
147-
// here much simpler.
148-
if (pubspec.dependencyOverrides.isNotEmpty) {
149-
if (pubspecContents.contains(_dependencyOverrideWarningComment)) {
150-
return _RewriteOutcome.alreadyChanged;
151-
}
152-
printError(
153-
'Packages with dependency overrides are not currently supported.');
154-
throw ToolExit(_exitCannotUpdatePubspec);
155-
}
136+
///
137+
/// Returns true if any overrides were added.
138+
///
139+
/// If [additionalPackagesToOverride] are provided, they will get
140+
/// dependency_overrides even if there is no direct dependency. This is
141+
/// useful for overriding transitive dependencies.
142+
Future<bool> _addDependencyOverridesIfNecessary(
143+
RepositoryPackage package,
144+
Map<String, RepositoryPackage> localDependencies, {
145+
Iterable<String> additionalPackagesToOverride = const <String>{},
146+
}) async {
147+
final String pubspecContents = package.pubspecFile.readAsStringSync();
156148

149+
// Determine the dependencies to be overridden.
150+
final Pubspec pubspec = Pubspec.parse(pubspecContents);
157151
final Iterable<String> combinedDependencies = <String>[
158152
...pubspec.dependencies.keys,
159153
...pubspec.devDependencies.keys,
154+
...additionalPackagesToOverride,
160155
];
161156
final List<String> packagesToOverride = combinedDependencies
162157
.where(
163158
(String packageName) => localDependencies.containsKey(packageName))
164159
.toList();
165160
// Sort the combined list to avoid sort_pub_dependencies lint violations.
166161
packagesToOverride.sort();
167-
if (packagesToOverride.isNotEmpty) {
168-
final String commonBasePath = packagesDir.path;
169-
// Find the relative path to the common base.
170-
final int packageDepth = path
171-
.split(path.relative(pubspecFile.parent.absolute.path,
172-
from: commonBasePath))
173-
.length;
174-
final List<String> relativeBasePathComponents =
175-
List<String>.filled(packageDepth, '..');
176-
// This is done via strings rather than by manipulating the Pubspec and
177-
// then re-serialiazing so that it's a localized change, rather than
178-
// rewriting the whole file (e.g., destroying comments), which could be
179-
// more disruptive for local use.
180-
String newPubspecContents = '''
181-
$pubspecContents
162+
163+
if (packagesToOverride.isEmpty) {
164+
return false;
165+
}
166+
167+
// Find the relative path to the common base.
168+
final String commonBasePath = packagesDir.path;
169+
final int packageDepth = path
170+
.split(path.relative(package.directory.absolute.path,
171+
from: commonBasePath))
172+
.length;
173+
final List<String> relativeBasePathComponents =
174+
List<String>.filled(packageDepth, '..');
175+
176+
// Add the overrides.
177+
final YamlEditor editablePubspec = YamlEditor(pubspecContents);
178+
final YamlNode root = editablePubspec.parseAt(<String>[]);
179+
const String dependencyOverridesKey = 'dependency_overrides';
180+
// Ensure that there's a `dependencyOverridesKey` entry to update.
181+
if ((root as YamlMap)[dependencyOverridesKey] == null) {
182+
editablePubspec.update(<String>[dependencyOverridesKey], YamlMap());
183+
}
184+
for (final String packageName in packagesToOverride) {
185+
// Find the relative path from the common base to the local package.
186+
final List<String> repoRelativePathComponents = path.split(path.relative(
187+
localDependencies[packageName]!.path,
188+
from: commonBasePath));
189+
editablePubspec.update(<String>[
190+
dependencyOverridesKey,
191+
packageName
192+
], <String, String>{
193+
'path': p.posix.joinAll(<String>[
194+
...relativeBasePathComponents,
195+
...repoRelativePathComponents,
196+
])
197+
});
198+
}
199+
200+
// Add the warning if it's not already there.
201+
String newContent = editablePubspec.toString();
202+
if (!newContent.contains(_dependencyOverrideWarningComment)) {
203+
newContent = newContent.replaceFirst('$dependencyOverridesKey:', '''
182204
183205
$_dependencyOverrideWarningComment
184-
dependency_overrides:
185-
''';
186-
for (final String packageName in packagesToOverride) {
187-
// Find the relative path from the common base to the local package.
188-
final List<String> repoRelativePathComponents = path.split(
189-
path.relative(localDependencies[packageName]!.path,
190-
from: commonBasePath));
191-
newPubspecContents += '''
192-
$packageName:
193-
path: ${p.posix.joinAll(<String>[
194-
...relativeBasePathComponents,
195-
...repoRelativePathComponents,
196-
])}
197-
''';
198-
}
199-
pubspecFile.writeAsStringSync(newPubspecContents);
200-
return _RewriteOutcome.changed;
206+
$dependencyOverridesKey:
207+
''');
201208
}
202-
return _RewriteOutcome.noChangesNeeded;
209+
210+
// Write the new pubspec.
211+
package.pubspecFile.writeAsStringSync(newContent);
212+
213+
// Update any examples. This is important for cases like integration tests
214+
// of app-facing packages in federated plugins, where the app-facing
215+
// package depends directly on the implementation packages, but the
216+
// example app doesn't. Since integration tests are run in the example app,
217+
// it needs the overrides in order for tests to pass.
218+
for (final RepositoryPackage example in package.getExamples()) {
219+
_addDependencyOverridesIfNecessary(example, localDependencies,
220+
additionalPackagesToOverride: packagesToOverride);
221+
}
222+
223+
return true;
203224
}
204225

205226
/// Returns all pubspecs anywhere under the packages directory.

0 commit comments

Comments
 (0)