Add plugin version to SwiftPM package symlink directory (#183668)
SwiftPM caches package's Package.swift manifest in
`$HOME/Library/Caches/org.swift.swiftpm/manifests`. Due to this caching,
when pub changes versions, SwiftPM doesn't always detect the
Package.manifest has changed.
This PR changes the symlink directory to use the plugin's directory
basename, which has the version in it:
```diff
- .packages/plugin_a -> /$HOME/.pub-cache/hosted/pub.dev/plugin_a-1.0.0/ios/plugin_a
+ .packages/plugin_a-1.0.0 -> /$HOME/.pub-cache/hosted/pub.dev/plugin_a-1.0.0/ios/plugin_a
```
This forces Xcode to re-cache the manifest.
This also refactors a few places we fetch plugins by saving them to the
project rather than re-finding them each time. This is currently limited
to the `XcodeBasedProject`.
Fixes https://github.com/flutter/flutter/issues/182099
Fixes https://github.com/flutter/flutter/issues/183226
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel
on [Discord].
**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.
<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart
index 6fd3714..854bffe 100644
--- a/packages/flutter_tools/lib/src/flutter_plugins.dart
+++ b/packages/flutter_tools/lib/src/flutter_plugins.dart
@@ -1347,6 +1347,8 @@
swiftPackageManager: SwiftPackageManager(
fileSystem: globals.fs,
templateRenderer: globals.templateRenderer,
+ processUtils: globals.processUtils,
+ config: globals.config,
),
fileSystem: globals.fs,
featureFlags: featureFlags,
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 9920b92..76cce95 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -23,7 +23,6 @@
import '../device.dart';
import '../features.dart';
import '../flutter_manifest.dart';
-import '../flutter_plugins.dart';
import '../globals.dart' as globals;
import '../macos/cocoapod_utils.dart';
import '../macos/swift_package_manager.dart';
@@ -1163,7 +1162,7 @@
for (final module in missingModules) {
if (await _isPluginSwiftPackageOnly(
platform: platform,
- project: project,
+ project: xcodeProject,
pluginName: module,
fileSystem: fileSystem,
)) {
@@ -1224,11 +1223,11 @@
/// Returns true if a Package.swift is found for the plugin and a podspec is not.
Future<bool> _isPluginSwiftPackageOnly({
required FlutterDarwinPlatform platform,
- required FlutterProject project,
+ required XcodeBasedProject project,
required String pluginName,
required FileSystem fileSystem,
}) async {
- final List<Plugin> plugins = await findPlugins(project);
+ final List<Plugin> plugins = await project.getPlugins();
final Plugin? matched = plugins
.where(
(Plugin plugin) =>
diff --git a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart
index 53ada63..5a676b7 100644
--- a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart
+++ b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart
@@ -60,6 +60,8 @@
final swiftPackageManager = SwiftPackageManager(
fileSystem: globals.localFileSystem,
templateRenderer: globals.templateRenderer,
+ processUtils: globals.processUtils,
+ config: globals.config,
);
final FlutterDarwinPlatform platform = xcodeProject is IosProject
? FlutterDarwinPlatform.ios
diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart
index 0e14719..7245c76 100644
--- a/packages/flutter_tools/lib/src/macos/cocoapods.dart
+++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart
@@ -18,7 +18,6 @@
import '../base/version.dart';
import '../build_info.dart';
import '../cache.dart';
-import '../flutter_plugins.dart';
import '../ios/xcodeproj.dart';
import '../migrations/cocoapods_script_symlink.dart';
import '../migrations/cocoapods_toolchain_directory_migration.dart';
@@ -524,7 +523,7 @@
if (matches.isEmpty) {
return null;
}
- final List<Plugin> plugins = await findPlugins(xcodeProject.parent);
+ final List<Plugin> plugins = await xcodeProject.getPlugins();
for (final match in matches) {
final String? missingPlugin = match.group(1);
final String? requiringPlugin = match.group(2);
diff --git a/packages/flutter_tools/lib/src/macos/swift_package_manager.dart b/packages/flutter_tools/lib/src/macos/swift_package_manager.dart
index 1f98d63..763026b 100644
--- a/packages/flutter_tools/lib/src/macos/swift_package_manager.dart
+++ b/packages/flutter_tools/lib/src/macos/swift_package_manager.dart
@@ -2,8 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:collection/collection.dart';
+
+import '../base/common.dart';
+import '../base/config.dart';
import '../base/error_handling_io.dart';
import '../base/file_system.dart';
+import '../base/process.dart';
import '../base/template.dart';
import '../base/version.dart';
import '../darwin/darwin.dart';
@@ -36,11 +41,17 @@
const SwiftPackageManager({
required FileSystem fileSystem,
required TemplateRenderer templateRenderer,
+ required ProcessUtils processUtils,
+ required Config config,
}) : _fileSystem = fileSystem,
- _templateRenderer = templateRenderer;
+ _templateRenderer = templateRenderer,
+ _processUtils = processUtils,
+ _config = config;
final FileSystem _fileSystem;
final TemplateRenderer _templateRenderer;
+ final ProcessUtils _processUtils;
+ final Config _config;
/// Creates a Swift Package called 'FlutterGeneratedPluginSwiftPackage' that
/// has dependencies on Flutter plugins that are compatible with Swift
@@ -129,20 +140,49 @@
platform.name,
);
String? packagePath = plugin.pluginSwiftPackagePath(_fileSystem, platform.name);
+ final File? manifest = packagePath != null
+ ? _fileSystem.file(pluginSwiftPackageManifestPath)
+ : null;
if (plugin.platforms[platform.name] == null ||
- pluginSwiftPackageManifestPath == null ||
packagePath == null ||
- !_fileSystem.file(pluginSwiftPackageManifestPath).existsSync()) {
+ manifest == null ||
+ !manifest.existsSync()) {
continue;
}
- final Link pluginSymlink = symlinkDirectory.childLink(plugin.name);
+ // Use the plugin basename as the symlink plugin directory name since the basename has the
+ // version number in it. This will make the symlink name change when the plugin version
+ // changes, which forces Xcode to re-process the package manifest.
+ final String basename = _fileSystem.directory(plugin.path).basename;
+
+ // Check if the plugin has a dependency on another Flutter plugin.
+ // If the plugin has a dependency on another plugin, copy the plugin to the SourcePackages
+ // cache directory and update the manifest to use the versioned path.
+ final String manifestContent = manifest.readAsStringSync();
+ final List<({String original, String replacement})> pluginDependencies =
+ _getPluginDependencies(manifestContent, plugins);
+ if (pluginDependencies.isNotEmpty) {
+ packagePath = _copyPluginAndUpdateManifest(
+ plugin: plugin,
+ pluginDependencies: pluginDependencies,
+ pathRelativeTo: pathRelativeTo,
+ basename: basename,
+ platform: platform,
+ manifestContent: manifestContent,
+ );
+ }
+
+ final Link pluginSymlink = symlinkDirectory.childLink(basename);
ErrorHandlingFileSystem.deleteIfExists(pluginSymlink);
pluginSymlink.createSync(packagePath);
- packagePath = pluginSymlink.path;
- packagePath = _fileSystem.path.relative(packagePath, from: pathRelativeTo);
+ final String packageRelativePath = _fileSystem.path.relative(
+ pluginSymlink.path,
+ from: pathRelativeTo,
+ );
- packageDependencies.add(SwiftPackagePackageDependency(name: plugin.name, path: packagePath));
+ packageDependencies.add(
+ SwiftPackagePackageDependency(name: plugin.name, path: packageRelativePath),
+ );
// The target dependency product name is hyphen separated because it's
// the dependency's library name, which Swift Package Manager will
@@ -158,6 +198,118 @@
return (packageDependencies, targetDependencies);
}
+ /// Checks if the plugin has a dependency on another Flutter plugin and returns a list of paths
+ /// that should be replaced in the [manifestContent].
+ ///
+ /// Plugins can declare a SwiftPM dependency on another plugin like this:
+ /// ```swift
+ /// dependencies: [
+ /// .package(name: "plugin_1", path: "../plugin_1")
+ /// ]
+ /// ```
+ ///
+ /// However, plugins are symlinked in the [XcodeBasedProject.relativeSwiftPackagesDirectory]
+ /// using the plugin's basename as the symlink name. The basename for non-path dependencies
+ /// includes the version number, e.g. "plugin_1-1.0.0". To make the relative path in the
+ /// manifest match the symlink path, we need to replace the path in the manifest with the
+ /// symlink path.
+ ///
+ /// For example, the manifest would need to updated to:
+ /// ```swift
+ /// dependencies: [
+ /// .package(name: "plugin_1", path: "../plugin_1-1.0.0")
+ /// ]
+ /// ```
+ List<({String original, String replacement})> _getPluginDependencies(
+ String manifestContent,
+ List<Plugin> plugins,
+ ) {
+ final dependencyPattern = RegExp(r'"\.\.\/([^"]+)"');
+ final Iterable<Match> matches = dependencyPattern.allMatches(manifestContent);
+ final List<({String original, String replacement})> pluginDependencies = [];
+ if (matches.isNotEmpty) {
+ for (final match in matches) {
+ final String? path = match.group(0);
+ final String? name = match.group(1);
+ if (path == null || name == null) {
+ continue;
+ }
+ final Plugin? pluginDependency = plugins.firstWhereOrNull((plugin) => plugin.name == name);
+ if (pluginDependency == null) {
+ continue;
+ }
+ final newPath = '"../${_fileSystem.directory(pluginDependency.path).basename}"';
+ if (path == newPath) {
+ continue;
+ }
+ pluginDependencies.add((original: path, replacement: newPath));
+ }
+ }
+ return pluginDependencies;
+ }
+
+ /// Copy the [plugin] to the build directory and update the manifest to use the versioned path.
+ /// Returns the path to the copied plugin.
+ ///
+ /// Plugin must be copied first so that the original plugin in pub cache is not modified.
+ ///
+ /// Throws a [ToolExit] if the plugin cannot be copied or the manifest cannot be updated.
+ String _copyPluginAndUpdateManifest({
+ required Plugin plugin,
+ required List<({String original, String replacement})> pluginDependencies,
+ required String pathRelativeTo,
+ required String basename,
+ required FlutterDarwinPlatform platform,
+ required String manifestContent,
+ }) {
+ final String destination = _fileSystem
+ .directory(
+ _fileSystem.path.join(
+ platform.buildDirectory(config: _config, fileSystem: _fileSystem),
+ 'SourcePackages',
+ basename,
+ ),
+ )
+ .absolute
+ .path;
+ final RunResult result = _processUtils.runSync([
+ 'rsync',
+ '-8', // Avoid mangling filenames with encodings that do not match the current locale.
+ '-av', // Archive mode and verbose: preserve permissions, ownership, timestamps, etc.
+ '--delete', // Delete files in the destination that are not in the source.
+ plugin.path,
+ destination,
+ ]);
+ if (result.exitCode != 0) {
+ throwToolExit('Failed to copy plugin ${plugin.name}: \n${result.stdout}\n${result.stderr}');
+ }
+
+ final String? packagePath = plugin.pluginSwiftPackagePath(
+ _fileSystem,
+ platform.name,
+ overridePath: destination,
+ );
+ if (packagePath == null) {
+ throwToolExit('Failed to find path to Package.swift for plugin ${plugin.name}');
+ }
+ final File copiedManifest = _fileSystem.directory(packagePath).childFile('Package.swift');
+ if (!copiedManifest.existsSync()) {
+ throwToolExit(
+ 'Failed to find path to copied Package.swift at ${copiedManifest.path}:\n'
+ 'rsync stdout: \n${result.stdout}\nrsync stderr: \n${result.stderr}',
+ );
+ }
+ var newManifestContent = manifestContent;
+ for (final dependency in pluginDependencies) {
+ newManifestContent = newManifestContent.replaceAll(
+ dependency.original,
+ dependency.replacement,
+ );
+ }
+ copiedManifest.writeAsStringSync(newManifestContent);
+ return packagePath;
+ }
+
/// Returns Flutter framework dependencies for the `FlutterGeneratedPluginSwiftPackage`.
(SwiftPackagePackageDependency, SwiftPackageTargetDependency) _dependencyForFlutterFramework({
required String pathRelativeTo,
diff --git a/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart b/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart
index 2fa7d30..56c3b05 100644
--- a/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart
+++ b/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart
@@ -16,6 +16,7 @@
import '../ios/plist_parser.dart';
import '../ios/xcodeproj.dart';
import '../macos/swift_package_manager.dart';
+import '../plugins.dart';
import '../project.dart';
/// Swift Package Manager integration requires changes to the Xcode project's
@@ -131,12 +132,7 @@
/// the example app.
///
/// If the app is not an example app or the plugin cannot be found, this will return null.
- late final ({String name, String path})? _examplePlugin = _loadPluginFromExampleProject(
- xcodeProject: _xcodeProject,
- fileSystem: _fileSystem,
- logger: logger,
- platform: _platform,
- );
+ late final ({String name, String path})? _examplePlugin;
void restoreFromBackup(SchemeInfo? schemeInfo) {
if (backupProjectSettings.existsSync()) {
@@ -178,6 +174,13 @@
throw Exception('Xcode project not found.');
}
+ _examplePlugin = await _loadPluginFromExampleProject(
+ xcodeProject: _xcodeProject,
+ fileSystem: _fileSystem,
+ logger: logger,
+ platform: _platform,
+ );
+
schemeInfo = await _getSchemeFile();
// Check for specific strings in the xcscheme and pbxproj to see if the
@@ -1203,12 +1206,12 @@
/// path relative to ios/macos directory the [xcodeProject] is in.
///
/// If the [xcodeProject] is not within an example app or the plugin can't be found, return null.
- static ({String name, String path})? _loadPluginFromExampleProject({
+ Future<({String name, String path})?> _loadPluginFromExampleProject({
required XcodeBasedProject xcodeProject,
required FileSystem fileSystem,
required Logger logger,
required FlutterDarwinPlatform platform,
- }) {
+ }) async {
try {
final FlutterProject flutterProject = xcodeProject.parent;
if (flutterProject.directory.path.endsWith('example') &&
@@ -1218,28 +1221,30 @@
);
if (parentProject.isPlugin && parentProject.hasExampleApp) {
final String pluginName = parentProject.manifest.appName;
- final Link linkedPlugin = xcodeProject.relativeSwiftPackagesDirectory.childLink(
- pluginName,
- );
- if (linkedPlugin.existsSync()) {
- final String absolutePath = linkedPlugin.targetSync();
- final String relativePath;
- switch (platform) {
- case FlutterDarwinPlatform.ios:
- relativePath = fileSystem.path.relative(
- absolutePath,
- from: xcodeProject.hostAppRoot.path,
- );
- case FlutterDarwinPlatform.macos:
- // The path is relative to the "Flutter" [managedDirectory] on macOS because the
- // Flutter `PBXGroup` in macOS pbxproj files uses `path` instead of `name`.
- relativePath = fileSystem.path.relative(
- absolutePath,
- from: xcodeProject.managedDirectory.path,
- );
- }
- return (name: pluginName, path: relativePath);
+ final List<Plugin> plugins = await xcodeProject.getPlugins();
+ final String? absolutePath = plugins
+ .where((plugin) => plugin.name == pluginName)
+ .firstOrNull
+ ?.pluginSwiftPackagePath(fileSystem, platform.name);
+ if (absolutePath == null) {
+ return null;
}
+ final String relativePath;
+ switch (platform) {
+ case FlutterDarwinPlatform.ios:
+ relativePath = fileSystem.path.relative(
+ absolutePath,
+ from: xcodeProject.hostAppRoot.path,
+ );
+ case FlutterDarwinPlatform.macos:
+ // The path is relative to the "Flutter" [managedDirectory] on macOS because the
+ // Flutter `PBXGroup` in macOS pbxproj files uses `path` instead of `name`.
+ relativePath = fileSystem.path.relative(
+ absolutePath,
+ from: xcodeProject.managedDirectory.path,
+ );
+ }
+ return (name: pluginName, path: relativePath);
}
}
} on Exception catch (e) {
diff --git a/packages/flutter_tools/lib/src/xcode_project.dart b/packages/flutter_tools/lib/src/xcode_project.dart
index 921336d..6b44132 100644
--- a/packages/flutter_tools/lib/src/xcode_project.dart
+++ b/packages/flutter_tools/lib/src/xcode_project.dart
@@ -75,6 +75,18 @@
FlutterDarwinPlatform get darwinPlatform;
+ /// Cached list of [Plugin]s for the [FlutterProject].
+ List<Plugin>? _plugins;
+
+ /// Returns the list of [Plugin]s for the [FlutterProject].
+ ///
+ /// On the first call, this will find plugins in the project.
+ /// On subsequent calls, this will return the cached list of plugins.
+ Future<List<Plugin>> getPlugins() async {
+ _plugins ??= await findPlugins(parent);
+ return _plugins!;
+ }
+
/// The default 'Info.plist' file of the host app. The developer can change this location in Xcode.
File get defaultHostInfoPlist =>
hostAppRoot.childDirectory(_defaultHostAppName).childFile('Info.plist');
@@ -560,9 +572,8 @@
/// Returns a list of targets and their associated plugin (if found) that exclude arm64 architecture.
Future<List<({String target, String? plugin})>> _targetsExcludingArm(String buildSettings) async {
final Map<String, List<String>> cocoapodsDependencyGraph = _cocoapodsDependencyGraph();
- final List<Plugin> allPlugins = await findPlugins(parent);
final pluginNames = <String>{
- for (final Plugin plugin in allPlugins)
+ for (final Plugin plugin in await getPlugins())
if (plugin.platforms.containsKey(IOSPlugin.kConfigKey)) plugin.name,
};
final targetHeaderPattern = RegExp(
diff --git a/packages/flutter_tools/test/general.shard/ios/mac_test.dart b/packages/flutter_tools/test/general.shard/ios/mac_test.dart
index 77ee4bb..a16b28f 100644
--- a/packages/flutter_tools/test/general.shard/ios/mac_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/mac_test.dart
@@ -19,6 +19,8 @@
import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/ios/xcresult.dart';
+import 'package:flutter_tools/src/platform_plugins.dart';
+import 'package:flutter_tools/src/plugins.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart';
import 'package:unified_analytics/unified_analytics.dart';
@@ -28,7 +30,6 @@
import '../../src/context.dart';
import '../../src/fake_process_manager.dart';
import '../../src/fakes.dart';
-import '../../src/package_config.dart';
import '../../src/throwing_pub.dart';
void main() {
@@ -639,18 +640,31 @@
),
);
final fs = MemoryFileSystem.test();
- final project = FakeFlutterProject(fileSystem: fs);
+ fs
+ .file('path/to/plugin_1_name/ios/plugin_1_name/Package.swift')
+ .createSync(recursive: true);
+ fs
+ .file('path/to/plugin_2_name/ios/plugin_2_name/Package.swift')
+ .createSync(recursive: true);
+ final project = FakeFlutterProject(
+ fileSystem: fs,
+ plugins: <Plugin>[
+ FakePlugin(
+ name: 'plugin_1_name',
+ platforms: <String, PluginPlatform>{
+ 'ios': const IOSPlugin(name: 'plugin_1_name', classPrefix: ''),
+ },
+ ),
+ FakePlugin(
+ name: 'plugin_2_name',
+ platforms: <String, PluginPlatform>{
+ 'ios': const IOSPlugin(name: 'plugin_1_name', classPrefix: ''),
+ },
+ ),
+ ],
+ );
project.ios.podfile.createSync(recursive: true);
project.manifest = FakeFlutterManifest();
- final pluginNames = <String>['plugin_1_name', 'plugin_2_name'];
- project.manifest.dependencies.addAll(pluginNames);
- createFakePlugins(project, fs, pluginNames);
- fs.systemTempDirectory
- .childFile('cache/plugin_1_name/ios/plugin_1_name/Package.swift')
- .createSync(recursive: true);
- fs.systemTempDirectory
- .childFile('cache/plugin_2_name/ios/plugin_2_name/Package.swift')
- .createSync(recursive: true);
await diagnoseXcodeBuildFailure(
buildResult,
logger: logger,
@@ -1088,40 +1102,13 @@
});
}
-void createFakePlugins(
- FlutterProject flutterProject,
- FileSystem fileSystem,
- List<String> pluginNames,
-) {
- const pluginYamlTemplate = '''
- flutter:
- plugin:
- platforms:
- ios:
- pluginClass: PLUGIN_CLASS
- macos:
- pluginClass: PLUGIN_CLASS
- ''';
-
- final Directory fakePubCache = fileSystem.systemTempDirectory.childDirectory('cache');
- writePackageConfigFiles(
- directory: flutterProject.directory,
- mainLibName: 'my_app',
- packages: <String, String>{
- for (final String name in pluginNames) name: fakePubCache.childDirectory(name).path,
- },
- );
- for (final name in pluginNames) {
- final Directory pluginDirectory = fakePubCache.childDirectory(name);
- pluginDirectory.childFile('pubspec.yaml')
- ..createSync(recursive: true)
- ..writeAsStringSync(pluginYamlTemplate.replaceAll('PLUGIN_CLASS', name));
- }
-}
-
class FakeIosProject extends Fake implements IosProject {
- FakeIosProject({required MemoryFileSystem fileSystem, this.usesSwiftPackageManager = false})
- : hostAppRoot = fileSystem.directory('app_name').childDirectory('ios');
+ FakeIosProject({
+ required MemoryFileSystem fileSystem,
+ this.usesSwiftPackageManager = false,
+ List<Plugin> plugins = const <Plugin>[],
+ }) : hostAppRoot = fileSystem.directory('app_name').childDirectory('ios'),
+ _plugins = plugins;
@override
Directory hostAppRoot;
@@ -1140,6 +1127,13 @@
@override
final bool usesSwiftPackageManager;
+
+ final List<Plugin> _plugins;
+
+ @override
+ Future<List<Plugin>> getPlugins() async {
+ return _plugins;
+ }
}
class FakeFlutterProject extends Fake implements FlutterProject {
@@ -1147,10 +1141,12 @@
required this.fileSystem,
this.usesSwiftPackageManager = false,
this.isModule = false,
- });
+ List<Plugin> plugins = const <Plugin>[],
+ }) : _plugins = plugins;
final MemoryFileSystem fileSystem;
final bool usesSwiftPackageManager;
+ final List<Plugin> _plugins;
@override
late final Directory directory = fileSystem.directory('app_name');
@@ -1168,6 +1164,7 @@
late final IosProject ios = FakeIosProject(
fileSystem: fileSystem,
usesSwiftPackageManager: usesSwiftPackageManager,
+ plugins: _plugins,
);
@override
@@ -1249,3 +1246,23 @@
return <String>['xcrun', 'xcodebuild'];
}
}
+
+class FakePlugin extends Fake implements Plugin {
+ FakePlugin({required this.name, this.platforms = const <String, PluginPlatform>{}});
+
+ @override
+ final String name;
+
+ @override
+ final Map<String, PluginPlatform> platforms;
+
+ @override
+ String? pluginSwiftPackageManifestPath(FileSystem fileSystem, String platform) {
+ return 'path/to/$name/$platform/$name/Package.swift';
+ }
+
+ @override
+ String? pluginPodspecPath(FileSystem fileSystem, String platform) {
+ return 'path/to/$name/$platform/$name.podspec';
+ }
+}
diff --git a/packages/flutter_tools/test/general.shard/macos/swift_package_manager_test.dart b/packages/flutter_tools/test/general.shard/macos/swift_package_manager_test.dart
index 482fa65..782df53 100644
--- a/packages/flutter_tools/test/general.shard/macos/swift_package_manager_test.dart
+++ b/packages/flutter_tools/test/general.shard/macos/swift_package_manager_test.dart
@@ -5,6 +5,9 @@
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
+import 'package:flutter_tools/src/base/config.dart';
+import 'package:flutter_tools/src/base/logger.dart' show BufferLogger;
+import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/darwin/darwin.dart';
import 'package:flutter_tools/src/isolated/mustache_template.dart';
import 'package:flutter_tools/src/macos/swift_package_manager.dart';
@@ -14,6 +17,7 @@
import 'package:test/fake.dart';
import '../../src/common.dart';
+import '../../src/fake_process_manager.dart';
const _doubleIndent = ' ';
@@ -29,11 +33,15 @@
group('generatePluginsSwiftPackage', () {
testWithoutContext('skip if no dependencies and not already migrated', () async {
final fs = MemoryFileSystem();
+ final processManager = FakeProcessManager.any();
+ final logger = BufferLogger.test();
final project = FakeXcodeProject(platform: platform.name, fileSystem: fs);
final spm = SwiftPackageManager(
fileSystem: fs,
templateRenderer: const MustacheTemplateRenderer(),
+ processUtils: ProcessUtils(processManager: processManager, logger: logger),
+ config: FakeConfig(),
);
await spm.generatePluginsSwiftPackage(<Plugin>[], platform, project);
@@ -42,6 +50,8 @@
testWithoutContext('generate if no dependencies and already migrated', () async {
final fs = MemoryFileSystem();
+ final processManager = FakeProcessManager.any();
+ final logger = BufferLogger.test();
final project = FakeXcodeProject(platform: platform.name, fileSystem: fs);
project.xcodeProjectInfoFile.createSync(recursive: true);
project.xcodeProjectInfoFile.writeAsStringSync('''
@@ -51,6 +61,8 @@
final spm = SwiftPackageManager(
fileSystem: fs,
templateRenderer: const MustacheTemplateRenderer(),
+ processUtils: ProcessUtils(processManager: processManager, logger: logger),
+ config: FakeConfig(),
);
await spm.generatePluginsSwiftPackage(<Plugin>[], platform, project);
@@ -128,6 +140,8 @@
'generate if no dependencies, no Flutter dependency, and already migrated',
() async {
final fs = MemoryFileSystem();
+ final processManager = FakeProcessManager.any();
+ final logger = BufferLogger.test();
final project = FakeXcodeProject(platform: platform.name, fileSystem: fs);
project.xcodeProjectInfoFile.createSync(recursive: true);
project.xcodeProjectInfoFile.writeAsStringSync('''
@@ -137,6 +151,8 @@
final spm = SwiftPackageManager(
fileSystem: fs,
templateRenderer: const MustacheTemplateRenderer(),
+ processUtils: ProcessUtils(processManager: processManager, logger: logger),
+ config: FakeConfig(),
);
await spm.generatePluginsSwiftPackage(
<Plugin>[],
@@ -181,21 +197,22 @@
testWithoutContext('generate with single dependency', () async {
final fs = MemoryFileSystem();
+ final processManager = FakeProcessManager.any();
+ final logger = BufferLogger.test();
final project = FakeXcodeProject(platform: platform.name, fileSystem: fs);
- final Directory validPlugin1Directory = fs.directory(
- '/local/path/to/plugins/valid_plugin_1',
- );
- validPlugin1Directory.childFile('Package.swift').createSync(recursive: true);
-
final validPlugin1 = FakePlugin(
name: 'valid_plugin_1',
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
- pluginSwiftPackagePath: validPlugin1Directory.path,
);
+ fs
+ .file('${validPlugin1.path}/${platform.name}/${validPlugin1.name}/Package.swift')
+ .createSync(recursive: true);
final spm = SwiftPackageManager(
fileSystem: fs,
templateRenderer: const MustacheTemplateRenderer(),
+ processUtils: ProcessUtils(processManager: processManager, logger: logger),
+ config: FakeConfig(),
);
await spm.generatePluginsSwiftPackage(<Plugin>[validPlugin1], platform, project);
@@ -203,10 +220,13 @@
? '.iOS("13.0")'
: '.macOS("10.15")';
expect(project.flutterPluginSwiftPackageManifest.existsSync(), isTrue);
- expect(project.relativeSwiftPackagesDirectory.childLink('valid_plugin_1'), exists);
expect(
- project.relativeSwiftPackagesDirectory.childLink('valid_plugin_1').targetSync(),
- validPlugin1Directory.path,
+ project.relativeSwiftPackagesDirectory.childLink('valid_plugin_1-1.0.0'),
+ exists,
+ );
+ expect(
+ project.relativeSwiftPackagesDirectory.childLink('valid_plugin_1-1.0.0').targetSync(),
+ '${validPlugin1.path}/${platform.name}/valid_plugin_1',
);
expect(project.flutterPluginSwiftPackageManifest.readAsStringSync(), '''
// swift-tools-version: 5.9
@@ -226,7 +246,7 @@
.library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"])
],
dependencies: [
- .package(name: "valid_plugin_1", path: "../.packages/valid_plugin_1"),
+ .package(name: "valid_plugin_1", path: "../.packages/valid_plugin_1-1.0.0"),
.package(name: "FlutterFramework", path: "../.packages/FlutterFramework")
],
targets: [
@@ -244,47 +264,44 @@
testWithoutContext('generate with multiple dependencies', () async {
final fs = MemoryFileSystem();
+ final processManager = FakeProcessManager.any();
+ final logger = BufferLogger.test();
final project = FakeXcodeProject(platform: platform.name, fileSystem: fs);
final nonPlatformCompatiblePlugin = FakePlugin(
name: 'invalid_plugin_due_to_incompatible_platform',
platforms: <String, PluginPlatform>{},
- pluginSwiftPackagePath: '/some/path',
);
final pluginSwiftPackageManifestIsNull = FakePlugin(
name: 'invalid_plugin_due_to_null_plugin_swift_package_path',
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
- pluginSwiftPackagePath: null,
+ hasSwiftPackage: false,
);
final pluginSwiftPackageManifestNotExists = FakePlugin(
name: 'invalid_plugin_due_to_plugin_swift_package_path_does_not_exist',
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
- pluginSwiftPackagePath: '/some/path',
);
- final Directory validPlugin1Directory = fs.directory(
- '/local/path/to/plugins/valid_plugin_1',
- );
- validPlugin1Directory.childFile('Package.swift').createSync(recursive: true);
final validPlugin1 = FakePlugin(
name: 'valid_plugin_1',
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
- pluginSwiftPackagePath: validPlugin1Directory.path,
);
-
- final Directory validPlugin2Directory = fs.directory(
- '/.pub-cache/plugins/valid_plugin_2',
- );
- validPlugin2Directory.childFile('Package.swift').createSync(recursive: true);
+ fs
+ .file('${validPlugin1.path}/${platform.name}/${validPlugin1.name}/Package.swift')
+ .createSync(recursive: true);
final validPlugin2 = FakePlugin(
name: 'valid_plugin_2',
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
- pluginSwiftPackagePath: validPlugin2Directory.path,
);
+ fs
+ .file('${validPlugin2.path}/${platform.name}/${validPlugin2.name}/Package.swift')
+ .createSync(recursive: true);
final spm = SwiftPackageManager(
fileSystem: fs,
templateRenderer: const MustacheTemplateRenderer(),
+ processUtils: ProcessUtils(processManager: processManager, logger: logger),
+ config: FakeConfig(),
);
await spm.generatePluginsSwiftPackage(
<Plugin>[
@@ -302,15 +319,21 @@
? '.iOS("13.0")'
: '.macOS("10.15")';
expect(project.flutterPluginSwiftPackageManifest.existsSync(), isTrue);
- expect(project.relativeSwiftPackagesDirectory.childLink('valid_plugin_1'), exists);
expect(
- project.relativeSwiftPackagesDirectory.childLink('valid_plugin_1').targetSync(),
- validPlugin1Directory.path,
+ project.relativeSwiftPackagesDirectory.childLink('valid_plugin_1-1.0.0'),
+ exists,
);
- expect(project.relativeSwiftPackagesDirectory.childLink('valid_plugin_2'), exists);
expect(
- project.relativeSwiftPackagesDirectory.childLink('valid_plugin_2').targetSync(),
- validPlugin2Directory.path,
+ project.relativeSwiftPackagesDirectory.childLink('valid_plugin_1-1.0.0').targetSync(),
+ '${validPlugin1.path}/${platform.name}/valid_plugin_1',
+ );
+ expect(
+ project.relativeSwiftPackagesDirectory.childLink('valid_plugin_2-1.0.0'),
+ exists,
+ );
+ expect(
+ project.relativeSwiftPackagesDirectory.childLink('valid_plugin_2-1.0.0').targetSync(),
+ '${validPlugin2.path}/${platform.name}/valid_plugin_2',
);
expect(project.flutterPluginSwiftPackageManifest.readAsStringSync(), '''
// swift-tools-version: 5.9
@@ -330,8 +353,8 @@
.library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"])
],
dependencies: [
- .package(name: "valid_plugin_1", path: "../.packages/valid_plugin_1"),
- .package(name: "valid_plugin_2", path: "../.packages/valid_plugin_2"),
+ .package(name: "valid_plugin_1", path: "../.packages/valid_plugin_1-1.0.0"),
+ .package(name: "valid_plugin_2", path: "../.packages/valid_plugin_2-1.0.0"),
.package(name: "FlutterFramework", path: "../.packages/FlutterFramework")
],
targets: [
@@ -347,6 +370,195 @@
)
''');
});
+
+ testWithoutContext('generate with plugin with dependency on plugin', () async {
+ final fs = MemoryFileSystem();
+ final logger = BufferLogger.test();
+ final project = FakeXcodeProject(platform: platform.name, fileSystem: fs);
+
+ final buildSourcePackagesPath = '/build/${platform.name}/SourcePackages';
+ final validPlugin1 = FakePlugin(
+ name: 'valid_plugin_1',
+ platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
+ );
+ const plugin1ManifestContents = '''
+// swift-tools-version: 5.9
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "valid_plugin_1",
+ platforms: [
+ .iOS("13.0")
+ ],
+ products: [
+ .library(name: "valid-plugin-1", targets: ["valid-plugin-1"])
+ ],
+ dependencies: [
+ .package(name: "valid_plugin_2", path: "../valid_plugin_2"),
+ .package(name: "FlutterFramework", path: "../FlutterFramework"),
+ .package(name: "valid_plugin_3", path: "../valid_plugin_3")
+ ],
+ targets: [
+ .target(
+ name: "valid-plugin-1",
+ dependencies: [
+ .product(name: "valid-plugin-2", package: "valid_plugin_2"),
+ .product(name: "FlutterFramework", package: "FlutterFramework"),
+ .product(name: "valid-plugin-3", package: "valid_plugin_3")
+ ],
+ path: "../Classes",
+ )
+ ]
+)
+''';
+ final File plugin1ManifestFile = fs.file(
+ '${validPlugin1.path}/${platform.name}/${validPlugin1.name}/Package.swift',
+ );
+ plugin1ManifestFile
+ ..createSync(recursive: true)
+ ..writeAsStringSync(plugin1ManifestContents);
+ final File plugin1CopiedManifest = fs.file(
+ '$buildSourcePackagesPath/valid_plugin_1-1.0.0/${platform.name}/valid_plugin_1/Package.swift',
+ );
+
+ final validPlugin2 = FakePlugin(
+ name: 'valid_plugin_2',
+ platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
+ );
+ fs
+ .file('${validPlugin2.path}/${platform.name}/${validPlugin2.name}/Package.swift')
+ .createSync(recursive: true);
+
+ final validPlugin3 = FakePlugin(
+ name: 'valid_plugin_3',
+ platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
+ );
+ fs
+ .file('${validPlugin3.path}/${platform.name}/${validPlugin3.name}/Package.swift')
+ .createSync(recursive: true);
+
+ final processManager = FakeProcessManager.list([
+ FakeCommand(
+ command: [
+ 'rsync',
+ '-8',
+ '-av',
+ '--delete',
+ validPlugin1.path,
+ '$buildSourcePackagesPath/valid_plugin_1-1.0.0',
+ ],
+ onRun: (_) {
+ plugin1CopiedManifest
+ ..createSync(recursive: true)
+ ..writeAsStringSync(plugin1ManifestContents);
+ },
+ ),
+ ]);
+
+ final spm = SwiftPackageManager(
+ fileSystem: fs,
+ templateRenderer: const MustacheTemplateRenderer(),
+ processUtils: ProcessUtils(processManager: processManager, logger: logger),
+ config: FakeConfig(),
+ );
+ await spm.generatePluginsSwiftPackage(
+ <Plugin>[validPlugin1, validPlugin2, validPlugin3],
+ platform,
+ project,
+ );
+
+ final supportedPlatform = platform == FlutterDarwinPlatform.ios
+ ? '.iOS("13.0")'
+ : '.macOS("10.15")';
+ expect(project.flutterPluginSwiftPackageManifest.existsSync(), isTrue);
+ expect(
+ project.relativeSwiftPackagesDirectory.childLink('valid_plugin_1-1.0.0'),
+ exists,
+ );
+ expect(
+ project.relativeSwiftPackagesDirectory.childLink('valid_plugin_1-1.0.0').targetSync(),
+ '$buildSourcePackagesPath/valid_plugin_1-1.0.0/${platform.name}/valid_plugin_1',
+ );
+ expect(plugin1CopiedManifest.readAsStringSync(), '''
+// swift-tools-version: 5.9
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "valid_plugin_1",
+ platforms: [
+ .iOS("13.0")
+ ],
+ products: [
+ .library(name: "valid-plugin-1", targets: ["valid-plugin-1"])
+ ],
+ dependencies: [
+ .package(name: "valid_plugin_2", path: "../valid_plugin_2-1.0.0"),
+ .package(name: "FlutterFramework", path: "../FlutterFramework"),
+ .package(name: "valid_plugin_3", path: "../valid_plugin_3-1.0.0")
+ ],
+ targets: [
+ .target(
+ name: "valid-plugin-1",
+ dependencies: [
+ .product(name: "valid-plugin-2", package: "valid_plugin_2"),
+ .product(name: "FlutterFramework", package: "FlutterFramework"),
+ .product(name: "valid-plugin-3", package: "valid_plugin_3")
+ ],
+ path: "../Classes",
+ )
+ ]
+)
+''');
+ expect(
+ project.relativeSwiftPackagesDirectory.childLink('valid_plugin_2-1.0.0'),
+ exists,
+ );
+ expect(
+ project.relativeSwiftPackagesDirectory.childLink('valid_plugin_2-1.0.0').targetSync(),
+ '${validPlugin2.path}/${platform.name}/valid_plugin_2',
+ );
+ expect(project.flutterPluginSwiftPackageManifest.readAsStringSync(), '''
+// swift-tools-version: 5.9
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+//
+// Generated file. Do not edit.
+//
+
+import PackageDescription
+
+let package = Package(
+ name: "FlutterGeneratedPluginSwiftPackage",
+ platforms: [
+ $supportedPlatform
+ ],
+ products: [
+ .library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"])
+ ],
+ dependencies: [
+ .package(name: "valid_plugin_1", path: "../.packages/valid_plugin_1-1.0.0"),
+ .package(name: "valid_plugin_2", path: "../.packages/valid_plugin_2-1.0.0"),
+ .package(name: "valid_plugin_3", path: "../.packages/valid_plugin_3-1.0.0"),
+ .package(name: "FlutterFramework", path: "../.packages/FlutterFramework")
+ ],
+ targets: [
+ .target(
+ name: "FlutterGeneratedPluginSwiftPackage",
+ dependencies: [
+ .product(name: "valid-plugin-1", package: "valid_plugin_1"),
+ .product(name: "valid-plugin-2", package: "valid_plugin_2"),
+ .product(name: "valid-plugin-3", package: "valid_plugin_3"),
+ .product(name: "FlutterFramework", package: "FlutterFramework")
+ ]
+ )
+ ]
+)
+''');
+ expect(processManager, hasNoRemainingExpectations);
+ });
});
});
}
@@ -397,29 +609,43 @@
}
class FakePlugin extends Fake implements Plugin {
- FakePlugin({required this.name, required this.platforms, required String? pluginSwiftPackagePath})
- : _pluginSwiftPackagePath = pluginSwiftPackagePath;
-
- final String? _pluginSwiftPackagePath;
+ FakePlugin({required this.name, required this.platforms, this.hasSwiftPackage = true})
+ : path = '/local/path/to/plugins/$name-1.0.0';
@override
final String name;
@override
+ final String path;
+
+ @override
final Map<String, PluginPlatform> platforms;
+ final bool hasSwiftPackage;
+
@override
String? pluginSwiftPackagePath(FileSystem fileSystem, String platform, {String? overridePath}) {
- return _pluginSwiftPackagePath;
+ if (!hasSwiftPackage) {
+ return null;
+ }
+ if (overridePath != null) {
+ return '$overridePath/$platform/$name';
+ }
+ return '$path/$platform/$name';
}
@override
String? pluginSwiftPackageManifestPath(FileSystem fileSystem, String platform) {
- if (_pluginSwiftPackagePath == null) {
+ if (!hasSwiftPackage) {
return null;
}
- return '$_pluginSwiftPackagePath/Package.swift';
+ return '$path/$platform/$name/Package.swift';
}
}
class FakePluginPlatform extends Fake implements PluginPlatform {}
+
+class FakeConfig extends Fake implements Config {
+ @override
+ Object? getValue(String key) => null;
+}
diff --git a/packages/flutter_tools/test/general.shard/migrations/swift_package_manager_integration_migration_test.dart b/packages/flutter_tools/test/general.shard/migrations/swift_package_manager_integration_migration_test.dart
index dcde409..53a7450 100644
--- a/packages/flutter_tools/test/general.shard/migrations/swift_package_manager_integration_migration_test.dart
+++ b/packages/flutter_tools/test/general.shard/migrations/swift_package_manager_integration_migration_test.dart
@@ -12,6 +12,7 @@
import 'package:flutter_tools/src/ios/plist_parser.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/migrations/swift_package_manager_integration_migration.dart';
+import 'package:flutter_tools/src/plugins.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart';
@@ -874,8 +875,11 @@
fileSystem: memoryFileSystem,
logger: testLogger,
projectDir: memoryFileSystem.currentDirectory
- .childDirectory('my_app')
- .childDirectory('example'),
+ .childDirectory(pluginName)
+ .childDirectory('example/ios'),
+ plugins: [
+ FakePlugin(name: pluginName, swiftPackagePath: '/$pluginName/ios/$pluginName'),
+ ],
);
_createProjectFiles(project, FlutterDarwinPlatform.ios, isExampleApp: true);
project.xcodeProjectInfoFile.writeAsStringSync('78DABEA22ED26510000E7860');
@@ -910,8 +914,11 @@
fileSystem: memoryFileSystem,
logger: testLogger,
projectDir: memoryFileSystem.currentDirectory
- .childDirectory('my_app')
- .childDirectory('example'),
+ .childDirectory(pluginName)
+ .childDirectory('example/ios'),
+ plugins: [
+ FakePlugin(name: pluginName, swiftPackagePath: '/$pluginName/ios/$pluginName'),
+ ],
);
_createProjectFiles(project, FlutterDarwinPlatform.ios, isExampleApp: true);
project.xcodeProjectInfoFile.writeAsStringSync('784666492D4C4C64000A1A5F');
@@ -1285,8 +1292,14 @@
fileSystem: memoryFileSystem,
logger: testLogger,
projectDir: memoryFileSystem.currentDirectory
- .childDirectory('my_app')
- .childDirectory('example'),
+ .childDirectory(pluginName)
+ .childDirectory('example/${platform.name}'),
+ plugins: [
+ FakePlugin(
+ name: pluginName,
+ swiftPackagePath: '/$pluginName/${platform.name}/$pluginName',
+ ),
+ ],
);
_createProjectFiles(project, platform, isExampleApp: true);
@@ -1954,8 +1967,14 @@
fileSystem: memoryFileSystem,
logger: testLogger,
projectDir: memoryFileSystem.currentDirectory
- .childDirectory('my_app')
- .childDirectory('example'),
+ .childDirectory(pluginName)
+ .childDirectory('example/${platform.name}'),
+ plugins: [
+ FakePlugin(
+ name: pluginName,
+ swiftPackagePath: '/$pluginName/${platform.name}/$pluginName',
+ ),
+ ],
);
_createProjectFiles(project, platform, isExampleApp: true);
@@ -3194,8 +3213,14 @@
fileSystem: memoryFileSystem,
logger: testLogger,
projectDir: memoryFileSystem.currentDirectory
- .childDirectory('my_app')
- .childDirectory('example'),
+ .childDirectory(pluginName)
+ .childDirectory('example/${platform.name}'),
+ plugins: [
+ FakePlugin(
+ name: pluginName,
+ swiftPackagePath: '/$pluginName/${platform.name}/$pluginName',
+ ),
+ ],
);
_createProjectFiles(project, platform, isExampleApp: true);
@@ -3256,8 +3281,14 @@
fileSystem: memoryFileSystem,
logger: testLogger,
projectDir: memoryFileSystem.currentDirectory
- .childDirectory('my_app')
- .childDirectory('example'),
+ .childDirectory(pluginName)
+ .childDirectory('example/${platform.name}'),
+ plugins: [
+ FakePlugin(
+ name: pluginName,
+ swiftPackagePath: '/$pluginName/${platform.name}/$pluginName',
+ ),
+ ],
);
_createProjectFiles(project, platform, isExampleApp: true);
@@ -3417,12 +3448,6 @@
ios:
pluginClass: MyPlugin
''');
- xcodeProject.relativeSwiftPackagesDirectory
- .childLink(pluginName)
- .createSync(
- pluginProjectDir.childDirectory(platform.name).childDirectory(pluginName).path,
- recursive: true,
- );
}
}
@@ -4409,11 +4434,13 @@
required this.logger,
this.usesSwiftPackageManager = true,
Directory? projectDir,
+ List<FakePlugin> plugins = const <FakePlugin>[],
}) : hostAppRoot = projectDir ?? fileSystem.directory('app_name').childDirectory(platform),
parent = FakeFlutterProject(
fileSystem: fileSystem,
- appName: projectDir != null ? projectDir.basename : 'app_name',
- );
+ appName: projectDir != null ? projectDir.parent.basename : 'app_name',
+ ),
+ _plugins = plugins;
final Logger logger;
late XcodeProjectInfo? _projectInfo = XcodeProjectInfo(
@@ -4429,6 +4456,13 @@
@override
FakeFlutterProject parent;
+ final List<FakePlugin> _plugins;
+
+ @override
+ Future<List<Plugin>> getPlugins() async {
+ return _plugins;
+ }
+
@override
Directory get xcodeProject => hostAppRoot.childDirectory('$hostAppProjectName.xcodeproj');
@@ -4469,13 +4503,6 @@
}
@override
- Directory get relativeSwiftPackagesDirectory => hostAppRoot
- .childDirectory('Flutter')
- .childDirectory('ephemeral')
- .childDirectory('Packages')
- .childDirectory('.packages');
-
- @override
Directory get flutterFrameworkSwiftPackageDirectory => hostAppRoot
.childDirectory('Flutter')
.childDirectory('ephemeral')
@@ -4544,6 +4571,20 @@
}
}
+class FakePlugin extends Fake implements Plugin {
+ FakePlugin({required this.name, required this.swiftPackagePath});
+
+ final String swiftPackagePath;
+
+ @override
+ final String name;
+
+ @override
+ String? pluginSwiftPackagePath(FileSystem fileSystem, String platform, {String? overridePath}) {
+ return swiftPackagePath;
+ }
+}
+
class FakeConfig extends Fake implements Config {
@override
Object? getValue(String key) => null;