// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:file_testing/file_testing.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'; 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 '../../src/common.dart'; const _doubleIndent = ' '; void main() { const supportedPlatforms = [ FlutterDarwinPlatform.ios, FlutterDarwinPlatform.macos, ]; group('SwiftPackageManager', () { for (final platform in supportedPlatforms) { group('for ${platform.name}', () { group('generatePluginsSwiftPackage', () { testWithoutContext('skip if no dependencies and not already migrated', () async { final fs = MemoryFileSystem(); final project = FakeXcodeProject(platform: platform.name, fileSystem: fs); final spm = SwiftPackageManager( fileSystem: fs, templateRenderer: const MustacheTemplateRenderer(), ); await spm.generatePluginsSwiftPackage([], platform, project); expect(project.flutterPluginSwiftPackageManifest.existsSync(), isFalse); }); testWithoutContext('generate if no dependencies and already migrated', () async { final fs = MemoryFileSystem(); final project = FakeXcodeProject(platform: platform.name, fileSystem: fs); project.xcodeProjectInfoFile.createSync(recursive: true); project.xcodeProjectInfoFile.writeAsStringSync(''' ' 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };'; '''); final spm = SwiftPackageManager( fileSystem: fs, templateRenderer: const MustacheTemplateRenderer(), ); await spm.generatePluginsSwiftPackage([], platform, project); final supportedPlatform = platform == FlutterDarwinPlatform.ios ? '.iOS("13.0")' : '.macOS("10.15")'; expect(project.flutterPluginSwiftPackageManifest.existsSync(), isTrue); 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: [ $_doubleIndent ], targets: [ .target( name: "FlutterGeneratedPluginSwiftPackage" ) ] ) '''); }); testWithoutContext('generate with single dependency', () async { final fs = MemoryFileSystem(); 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: {platform.name: FakePluginPlatform()}, pluginSwiftPackagePath: validPlugin1Directory.path, ); final spm = SwiftPackageManager( fileSystem: fs, templateRenderer: const MustacheTemplateRenderer(), ); await spm.generatePluginsSwiftPackage([validPlugin1], 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'), exists); expect( project.relativeSwiftPackagesDirectory.childLink('valid_plugin_1').targetSync(), validPlugin1Directory.path, ); 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") ], targets: [ .target( name: "FlutterGeneratedPluginSwiftPackage", dependencies: [ .product(name: "valid-plugin-1", package: "valid_plugin_1") ] ) ] ) '''); }); testWithoutContext('generate with multiple dependencies', () async { final fs = MemoryFileSystem(); final project = FakeXcodeProject(platform: platform.name, fileSystem: fs); final nonPlatformCompatiblePlugin = FakePlugin( name: 'invalid_plugin_due_to_incompatible_platform', platforms: {}, pluginSwiftPackagePath: '/some/path', ); final pluginSwiftPackageManifestIsNull = FakePlugin( name: 'invalid_plugin_due_to_null_plugin_swift_package_path', platforms: {platform.name: FakePluginPlatform()}, pluginSwiftPackagePath: null, ); final pluginSwiftPackageManifestNotExists = FakePlugin( name: 'invalid_plugin_due_to_plugin_swift_package_path_does_not_exist', platforms: {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: {platform.name: FakePluginPlatform()}, pluginSwiftPackagePath: validPlugin1Directory.path, ); final Directory validPlugin2Directory = fs.directory( '/.pub-cache/plugins/valid_plugin_2', ); validPlugin2Directory.childFile('Package.swift').createSync(recursive: true); final validPlugin2 = FakePlugin( name: 'valid_plugin_2', platforms: {platform.name: FakePluginPlatform()}, pluginSwiftPackagePath: validPlugin2Directory.path, ); final spm = SwiftPackageManager( fileSystem: fs, templateRenderer: const MustacheTemplateRenderer(), ); await spm.generatePluginsSwiftPackage( [ nonPlatformCompatiblePlugin, pluginSwiftPackageManifestIsNull, pluginSwiftPackageManifestNotExists, validPlugin1, validPlugin2, ], 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'), exists); expect( project.relativeSwiftPackagesDirectory.childLink('valid_plugin_1').targetSync(), validPlugin1Directory.path, ); expect(project.relativeSwiftPackagesDirectory.childLink('valid_plugin_2'), exists); expect( project.relativeSwiftPackagesDirectory.childLink('valid_plugin_2').targetSync(), validPlugin2Directory.path, ); 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"), .package(name: "valid_plugin_2", path: "../.packages/valid_plugin_2") ], targets: [ .target( name: "FlutterGeneratedPluginSwiftPackage", dependencies: [ .product(name: "valid-plugin-1", package: "valid_plugin_1"), .product(name: "valid-plugin-2", package: "valid_plugin_2") ] ) ] ) '''); }); testWithoutContext('generates FlutterFramework', () async { final fs = MemoryFileSystem(); final project = FakeXcodeProject(platform: platform.name, fileSystem: fs); project.xcodeProjectInfoFile.createSync(recursive: true); project.xcodeProjectInfoFile.writeAsStringSync(''' ' 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };'; '''); final spm = SwiftPackageManager( fileSystem: fs, templateRenderer: const MustacheTemplateRenderer(), ); await spm.generatePluginsSwiftPackage([], platform, project); final supportedPlatform = platform == FlutterDarwinPlatform.ios ? '.iOS("13.0")' : '.macOS("10.15")'; final File manifest = project.flutterFrameworkSwiftPackageDirectory.childFile( 'Package.swift', ); expect(manifest.existsSync(), isTrue); expect(manifest.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: "FlutterFramework", platforms: [ $supportedPlatform ], products: [ .library(name: "FlutterFramework", targets: ["FlutterFramework"]) ], dependencies: [ $_doubleIndent ], targets: [ .target( name: "FlutterFramework" ) ] ) '''); final File soureFile = project.flutterFrameworkSwiftPackageDirectory .childDirectory('Sources') .childDirectory('FlutterFramework') .childFile('FlutterFramework.swift'); expect(soureFile.existsSync(), isTrue); expect(soureFile.readAsStringSync(), ''' // // Generated file. Do not edit. // '''); }); }); group('updateMinimumDeployment', () { testWithoutContext('return if invalid deploymentTarget', () { final fs = MemoryFileSystem(); final project = FakeXcodeProject(platform: platform.name, fileSystem: fs); final supportedPlatform = platform == FlutterDarwinPlatform.ios ? '.iOS("13.0")' : '.macOS("10.15")'; project.flutterPluginSwiftPackageManifest.createSync(recursive: true); project.flutterPluginSwiftPackageManifest.writeAsStringSync(supportedPlatform); SwiftPackageManager.updateMinimumDeployment( project: project, platform: platform, deploymentTarget: '', ); expect( project.flutterPluginSwiftPackageManifest.readAsLinesSync(), contains(supportedPlatform), ); }); testWithoutContext('return if deploymentTarget is lower than default', () { final fs = MemoryFileSystem(); final project = FakeXcodeProject(platform: platform.name, fileSystem: fs); final supportedPlatform = platform == FlutterDarwinPlatform.ios ? '.iOS("13.0")' : '.macOS("10.15")'; project.flutterPluginSwiftPackageManifest.createSync(recursive: true); project.flutterPluginSwiftPackageManifest.writeAsStringSync(supportedPlatform); SwiftPackageManager.updateMinimumDeployment( project: project, platform: platform, deploymentTarget: '9.0', ); expect( project.flutterPluginSwiftPackageManifest.readAsLinesSync(), contains(supportedPlatform), ); }); testWithoutContext('return if deploymentTarget is same than default', () { final fs = MemoryFileSystem(); final project = FakeXcodeProject(platform: platform.name, fileSystem: fs); final supportedPlatform = platform == FlutterDarwinPlatform.ios ? '.iOS("13.0")' : '.macOS("10.15")'; project.flutterPluginSwiftPackageManifest.createSync(recursive: true); project.flutterPluginSwiftPackageManifest.writeAsStringSync(supportedPlatform); SwiftPackageManager.updateMinimumDeployment( project: project, platform: platform, deploymentTarget: platform == FlutterDarwinPlatform.ios ? '13.0' : '10.15', ); expect( project.flutterPluginSwiftPackageManifest.readAsLinesSync(), contains(supportedPlatform), ); }); testWithoutContext('update if deploymentTarget is higher than default', () { final fs = MemoryFileSystem(); final project = FakeXcodeProject(platform: platform.name, fileSystem: fs); final supportedPlatform = platform == FlutterDarwinPlatform.ios ? '.iOS("13.0")' : '.macOS("10.15")'; project.flutterPluginSwiftPackageManifest.createSync(recursive: true); project.flutterPluginSwiftPackageManifest.writeAsStringSync(supportedPlatform); SwiftPackageManager.updateMinimumDeployment( project: project, platform: platform, deploymentTarget: '14.0', ); expect( project.flutterPluginSwiftPackageManifest.readAsLinesSync().contains( supportedPlatform, ), isFalse, ); expect( project.flutterPluginSwiftPackageManifest.readAsLinesSync(), contains(platform == FlutterDarwinPlatform.ios ? '.iOS("14.0")' : '.macOS("14.0")'), ); }); }); }); } }); } class FakeXcodeProject extends Fake implements IosProject { FakeXcodeProject({required MemoryFileSystem fileSystem, required String platform}) : hostAppRoot = fileSystem.directory('app_name').childDirectory(platform); @override Directory hostAppRoot; @override Directory get xcodeProject => hostAppRoot.childDirectory('$hostAppProjectName.xcodeproj'); @override File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj'); @override String hostAppProjectName = 'Runner'; @override Directory get flutterSwiftPackagesDirectory => hostAppRoot.childDirectory('Flutter').childDirectory('ephemeral').childDirectory('Packages'); @override Directory get relativeSwiftPackagesDirectory => flutterSwiftPackagesDirectory.childDirectory('.packages'); @override Directory get flutterPluginSwiftPackageDirectory => flutterSwiftPackagesDirectory.childDirectory('FlutterGeneratedPluginSwiftPackage'); @override File get flutterPluginSwiftPackageManifest => flutterPluginSwiftPackageDirectory.childFile('Package.swift'); @override Directory get flutterFrameworkSwiftPackageDirectory => relativeSwiftPackagesDirectory.childDirectory('FlutterFramework'); @override bool get flutterPluginSwiftPackageInProjectSettings { return xcodeProjectInfoFile.existsSync() && xcodeProjectInfoFile.readAsStringSync().contains('FlutterGeneratedPluginSwiftPackage'); } } class FakePlugin extends Fake implements Plugin { FakePlugin({required this.name, required this.platforms, required String? pluginSwiftPackagePath}) : _pluginSwiftPackagePath = pluginSwiftPackagePath; final String? _pluginSwiftPackagePath; @override final String name; @override final Map platforms; @override String? pluginSwiftPackagePath(FileSystem fileSystem, String platform) { return _pluginSwiftPackagePath; } @override String? pluginSwiftPackageManifestPath(FileSystem fileSystem, String platform) { if (_pluginSwiftPackagePath == null) { return null; } return '$_pluginSwiftPackagePath/Package.swift'; } } class FakePluginPlatform extends Fake implements PluginPlatform {}