@@ -6,16 +6,15 @@ import 'package:file/file.dart';
66import 'package:git/git.dart' ;
77import 'package:path/path.dart' as p;
88import 'package:pub_semver/pub_semver.dart' ;
9+ import 'package:yaml/yaml.dart' ;
10+ import 'package:yaml_edit/yaml_edit.dart' ;
911
1012import 'common/core.dart' ;
1113import 'common/git_version_finder.dart' ;
1214import 'common/package_command.dart' ;
1315import 'common/repository_package.dart' ;
1416
1517const 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