blob: 98ba51b592b70755e4c1dc47a71e9f9328fa2e70 [file] [log] [blame]
// 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 '../base/common.dart';
import '../base/file_system.dart';
import '../base/template.dart';
import '../base/version.dart';
import '../plugins.dart';
import '../project.dart';
import 'swift_packages.dart';
/// Swift Package Manager is a dependency management solution for iOS and macOS
/// applications.
///
/// See also:
/// * https://www.swift.org/documentation/package-manager/ - documentation on
/// Swift Package Manager.
/// * https://developer.apple.com/documentation/packagedescription/package -
/// documentation on Swift Package Manager manifest file, Package.swift.
class SwiftPackageManager {
const SwiftPackageManager({
required FileSystem fileSystem,
required TemplateRenderer templateRenderer,
}) : _fileSystem = fileSystem,
_templateRenderer = templateRenderer;
final FileSystem _fileSystem;
final TemplateRenderer _templateRenderer;
static const String _defaultFlutterPluginsSwiftPackageName = 'FlutterGeneratedPluginSwiftPackage';
static final SwiftPackageSupportedPlatform iosSwiftPackageSupportedPlatform = SwiftPackageSupportedPlatform(
platform: SwiftPackagePlatform.ios,
version: Version(12, 0, null),
);
static final SwiftPackageSupportedPlatform macosSwiftPackageSupportedPlatform = SwiftPackageSupportedPlatform(
platform: SwiftPackagePlatform.macos,
version: Version(10, 14, null),
);
/// Creates a Swift Package called 'FlutterGeneratedPluginSwiftPackage' that
/// has dependencies on Flutter plugins that are compatible with Swift
/// Package Manager.
Future<void> generatePluginsSwiftPackage(
List<Plugin> plugins,
SupportedPlatform platform,
XcodeBasedProject project,
) async {
_validatePlatform(platform);
final (
List<SwiftPackagePackageDependency> packageDependencies,
List<SwiftPackageTargetDependency> targetDependencies
) = _dependenciesForPlugins(plugins, platform);
// If there aren't any Swift Package plugins and the project hasn't been
// migrated yet, don't generate a Swift package or migrate the app since
// it's not needed. If the project has already been migrated, regenerate
// the Package.swift even if there are no dependencies in case there
// were dependencies previously.
if (packageDependencies.isEmpty && !project.flutterPluginSwiftPackageInProjectSettings) {
return;
}
final SwiftPackageSupportedPlatform swiftSupportedPlatform;
if (platform == SupportedPlatform.ios) {
swiftSupportedPlatform = iosSwiftPackageSupportedPlatform;
} else {
swiftSupportedPlatform = macosSwiftPackageSupportedPlatform;
}
// FlutterGeneratedPluginSwiftPackage must be statically linked to ensure
// any dynamic dependencies are linked to Runner and prevent undefined symbols.
final SwiftPackageProduct generatedProduct = SwiftPackageProduct(
name: _defaultFlutterPluginsSwiftPackageName,
targets: <String>[_defaultFlutterPluginsSwiftPackageName],
libraryType: SwiftPackageLibraryType.static,
);
final SwiftPackageTarget generatedTarget = SwiftPackageTarget.defaultTarget(
name: _defaultFlutterPluginsSwiftPackageName,
dependencies: targetDependencies,
);
final SwiftPackage pluginsPackage = SwiftPackage(
manifest: project.flutterPluginSwiftPackageManifest,
name: _defaultFlutterPluginsSwiftPackageName,
platforms: <SwiftPackageSupportedPlatform>[swiftSupportedPlatform],
products: <SwiftPackageProduct>[generatedProduct],
dependencies: packageDependencies,
targets: <SwiftPackageTarget>[generatedTarget],
templateRenderer: _templateRenderer,
);
pluginsPackage.createSwiftPackage();
}
(List<SwiftPackagePackageDependency>, List<SwiftPackageTargetDependency>) _dependenciesForPlugins(
List<Plugin> plugins,
SupportedPlatform platform,
) {
final List<SwiftPackagePackageDependency> packageDependencies =
<SwiftPackagePackageDependency>[];
final List<SwiftPackageTargetDependency> targetDependencies =
<SwiftPackageTargetDependency>[];
for (final Plugin plugin in plugins) {
final String? pluginSwiftPackageManifestPath = plugin.pluginSwiftPackageManifestPath(
_fileSystem,
platform.name,
);
if (plugin.platforms[platform.name] == null ||
pluginSwiftPackageManifestPath == null ||
!_fileSystem.file(pluginSwiftPackageManifestPath).existsSync()) {
continue;
}
packageDependencies.add(SwiftPackagePackageDependency(
name: plugin.name,
path: _fileSystem.file(pluginSwiftPackageManifestPath).parent.path,
));
// The target dependency product name is hyphen separated because it's
// the dependency's library name, which Swift Package Manager will
// automatically use as the CFBundleIdentifier if linked dynamically. The
// CFBundleIdentifier cannot contain underscores.
targetDependencies.add(SwiftPackageTargetDependency.product(
name: plugin.name.replaceAll('_', '-'),
packageName: plugin.name,
));
}
return (packageDependencies, targetDependencies);
}
/// Validates the platform is either iOS or macOS, otherwise throw an error.
static void _validatePlatform(SupportedPlatform platform) {
if (platform != SupportedPlatform.ios &&
platform != SupportedPlatform.macos) {
throwToolExit(
'The platform ${platform.name} is not compatible with Swift Package Manager. '
'Only iOS and macOS are allowed.',
);
}
}
/// If the project's IPHONEOS_DEPLOYMENT_TARGET/MACOSX_DEPLOYMENT_TARGET is
/// higher than the FlutterGeneratedPluginSwiftPackage's default
/// SupportedPlatform, increase the SupportedPlatform to match the project's
/// deployment target.
///
/// This is done for the use case of a plugin requiring a higher iOS/macOS
/// version than FlutterGeneratedPluginSwiftPackage.
///
/// Swift Package Manager emits an error if a dependency isn’t compatible
/// with the top-level package’s deployment version. The deployment target of
/// a package’s dependencies must be lower than or equal to the top-level
/// package’s deployment target version for a particular platform.
///
/// To still be able to use the plugin, the user can increase the Xcode
/// project's iOS/macOS deployment target and this will then increase the
/// deployment target for FlutterGeneratedPluginSwiftPackage.
static void updateMinimumDeployment({
required XcodeBasedProject project,
required SupportedPlatform platform,
required String deploymentTarget,
}) {
final Version? projectDeploymentTargetVersion = Version.parse(deploymentTarget);
final SwiftPackageSupportedPlatform defaultPlatform;
final SwiftPackagePlatform packagePlatform;
if (platform == SupportedPlatform.ios) {
defaultPlatform = iosSwiftPackageSupportedPlatform;
packagePlatform = SwiftPackagePlatform.ios;
} else {
defaultPlatform = macosSwiftPackageSupportedPlatform;
packagePlatform = SwiftPackagePlatform.macos;
}
if (projectDeploymentTargetVersion == null ||
projectDeploymentTargetVersion <= defaultPlatform.version ||
!project.flutterPluginSwiftPackageManifest.existsSync()) {
return;
}
final String manifestContents = project.flutterPluginSwiftPackageManifest.readAsStringSync();
final String oldSupportedPlatform = defaultPlatform.format();
final String newSupportedPlatform = SwiftPackageSupportedPlatform(
platform: packagePlatform,
version: projectDeploymentTargetVersion,
).format();
project.flutterPluginSwiftPackageManifest.writeAsStringSync(
manifestContents.replaceFirst(oldSupportedPlatform, newSupportedPlatform),
);
}
}