blob: 501fc9ebf930bb2d4a3337bdb6e5ec855e425c75 [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/file_system.dart';
import '../base/template.dart';
import '../base/version.dart';
/// Swift toolchain version included with Xcode 15.0.
const String minimumSwiftToolchainVersion = '5.9';
const String _swiftPackageTemplate = '''
// swift-tools-version: {{swiftToolsVersion}}
// 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: "{{packageName}}",
{{#platforms}}
platforms: [
{{platforms}}
],
{{/platforms}}
products: [
{{products}}
],
dependencies: [
{{dependencies}}
],
targets: [
{{targets}}
]
)
''';
const String _swiftPackageSourceTemplate = '''
//
// Generated file. Do not edit.
//
''';
const String _singleIndent = ' ';
const String _doubleIndent = '$_singleIndent$_singleIndent';
/// A Swift Package is reusable code that can be shared across projects and
/// with other developers in iOS and macOS applications. A Swift Package
/// requires a Package.swift. This class handles the formatting and creation of
/// a Package.swift.
///
/// See https://developer.apple.com/documentation/packagedescription/package
/// for more information about Swift Packages and Package.swift.
class SwiftPackage {
SwiftPackage({
required File manifest,
required String name,
required List<SwiftPackageSupportedPlatform> platforms,
required List<SwiftPackageProduct> products,
required List<SwiftPackagePackageDependency> dependencies,
required List<SwiftPackageTarget> targets,
required TemplateRenderer templateRenderer,
}) : _manifest = manifest,
_name = name,
_platforms = platforms,
_products = products,
_dependencies = dependencies,
_targets = targets,
_templateRenderer = templateRenderer;
/// [File] for Package.swift.
final File _manifest;
/// The name of the Swift package.
final String _name;
/// The list of minimum versions for platforms supported by the package.
final List<SwiftPackageSupportedPlatform> _platforms;
/// The list of products that this package vends and that clients can use.
final List<SwiftPackageProduct> _products;
/// The list of package dependencies.
final List<SwiftPackagePackageDependency> _dependencies;
/// The list of targets that are part of this package.
final List<SwiftPackageTarget> _targets;
final TemplateRenderer _templateRenderer;
/// Context for the [_swiftPackageTemplate] template.
Map<String, Object> get _templateContext {
return <String, Object>{
'swiftToolsVersion': minimumSwiftToolchainVersion,
'packageName': _name,
// Supported platforms can't be empty, so only include if not null.
'platforms': _formatPlatforms() ?? false,
'products': _formatProducts(),
'dependencies': _formatDependencies(),
'targets': _formatTargets(),
};
}
/// Create a Package.swift using settings from [_templateContext].
void createSwiftPackage() {
// Swift Packages require at least one source file per non-binary target,
// whether it be in Swift or Objective C. If the target does not have any
// files yet, create an empty Swift file.
for (final SwiftPackageTarget target in _targets) {
if (target.targetType == SwiftPackageTargetType.binaryTarget) {
continue;
}
final Directory targetDirectory = _manifest.parent
.childDirectory('Sources')
.childDirectory(target.name);
if (!targetDirectory.existsSync() || targetDirectory.listSync().isEmpty) {
final File requiredSwiftFile = targetDirectory.childFile(
'${target.name}.swift',
);
requiredSwiftFile.createSync(recursive: true);
requiredSwiftFile.writeAsStringSync(_swiftPackageSourceTemplate);
}
}
final String renderedTemplate = _templateRenderer.renderString(
_swiftPackageTemplate,
_templateContext,
);
_manifest.createSync(recursive: true);
_manifest.writeAsStringSync(renderedTemplate);
}
String? _formatPlatforms() {
if (_platforms.isEmpty) {
return null;
}
final List<String> platformStrings = _platforms
.map((SwiftPackageSupportedPlatform platform) => platform.format())
.toList();
return platformStrings.join(',\n$_doubleIndent');
}
String _formatProducts() {
if (_products.isEmpty) {
return '';
}
final List<String> libraries = _products
.map((SwiftPackageProduct product) => product.format())
.toList();
return libraries.join(',\n$_doubleIndent');
}
String _formatDependencies() {
if (_dependencies.isEmpty) {
return '';
}
final List<String> packages = _dependencies
.map((SwiftPackagePackageDependency dependency) => dependency.format())
.toList();
return packages.join(',\n$_doubleIndent');
}
String _formatTargets() {
if (_targets.isEmpty) {
return '';
}
final List<String> targetList =
_targets.map((SwiftPackageTarget target) => target.format()).toList();
return targetList.join(',\n$_doubleIndent');
}
}
enum SwiftPackagePlatform {
ios(name: '.iOS'),
macos(name: '.macOS'),
tvos(name: '.tvOS'),
watchos(name: '.watchOS');
const SwiftPackagePlatform({required this.name});
final String name;
}
/// A platform that the Swift package supports.
///
/// Representation of SupportedPlatform from
/// https://developer.apple.com/documentation/packagedescription/supportedplatform.
class SwiftPackageSupportedPlatform {
SwiftPackageSupportedPlatform({
required this.platform,
required this.version,
});
final SwiftPackagePlatform platform;
final Version version;
String format() {
// platforms: [
// .macOS("10.14"),
// .iOS("12.0"),
// ],
return '${platform.name}("$version")';
}
}
/// Types of library linking.
///
/// Representation of Product.Library.LibraryType from
/// https://developer.apple.com/documentation/packagedescription/product/library/librarytype.
enum SwiftPackageLibraryType {
dynamic(name: '.dynamic'),
static(name: '.static');
const SwiftPackageLibraryType({required this.name});
final String name;
}
/// An externally visible build artifact that's available to clients of the
/// package.
///
/// Representation of Product from
/// https://developer.apple.com/documentation/packagedescription/product.
class SwiftPackageProduct {
SwiftPackageProduct({
required this.name,
required this.targets,
this.libraryType,
});
final String name;
final SwiftPackageLibraryType? libraryType;
final List<String> targets;
String format() {
// products: [
// .library(name: "FlutterGeneratedPluginSwiftPackage", targets: ["FlutterGeneratedPluginSwiftPackage"]),
// .library(name: "FlutterDependenciesPackage", type: .dynamic, targets: ["FlutterDependenciesPackage"]),
// ],
String targetsString = '';
if (targets.isNotEmpty) {
final List<String> quotedTargets =
targets.map((String target) => '"$target"').toList();
targetsString = ', targets: [${quotedTargets.join(', ')}]';
}
String libraryTypeString = '';
if (libraryType != null) {
libraryTypeString = ', type: ${libraryType!.name}';
}
return '.library(name: "$name"$libraryTypeString$targetsString)';
}
}
/// A package dependency of a Swift package.
///
/// Representation of Package.Dependency from
/// https://developer.apple.com/documentation/packagedescription/package/dependency.
class SwiftPackagePackageDependency {
SwiftPackagePackageDependency({
required this.name,
required this.path,
});
final String name;
final String path;
String format() {
// dependencies: [
// .package(name: "image_picker_ios", path: "/path/to/packages/image_picker/image_picker_ios/ios/image_picker_ios"),
// ],
return '.package(name: "$name", path: "$path")';
}
}
/// Type of Target constructor.
///
/// See https://developer.apple.com/documentation/packagedescription/target for
/// more information.
enum SwiftPackageTargetType {
target(name: '.target'),
binaryTarget(name: '.binaryTarget');
const SwiftPackageTargetType({required this.name});
final String name;
}
/// A building block of a Swift Package that contains a set of source files
/// that Swift Package Manager compiles into a module.
///
/// Representation of Target from
/// https://developer.apple.com/documentation/packagedescription/target.
class SwiftPackageTarget {
SwiftPackageTarget.defaultTarget({
required this.name,
this.dependencies,
}) : path = null,
targetType = SwiftPackageTargetType.target;
SwiftPackageTarget.binaryTarget({
required this.name,
required String relativePath,
}) : path = relativePath,
dependencies = null,
targetType = SwiftPackageTargetType.binaryTarget;
final String name;
final String? path;
final List<SwiftPackageTargetDependency>? dependencies;
final SwiftPackageTargetType targetType;
String format() {
// targets: [
// .binaryTarget(
// name: "Flutter",
// path: "Flutter.xcframework"
// ),
// .target(
// name: "FlutterGeneratedPluginSwiftPackage",
// dependencies: [
// .target(name: "Flutter"),
// .product(name: "image_picker_ios", package: "image_picker_ios")
// ]
// ),
// ]
const String targetIndent = _doubleIndent;
const String targetDetailsIndent = '$_doubleIndent$_singleIndent';
final List<String> targetDetails = <String>[];
final String nameString = 'name: "$name"';
targetDetails.add(nameString);
if (path != null) {
final String pathString = 'path: "$path"';
targetDetails.add(pathString);
}
if (dependencies != null && dependencies!.isNotEmpty) {
final List<String> targetDependencies = dependencies!
.map((SwiftPackageTargetDependency dependency) => dependency.format())
.toList();
final String dependenciesString = '''
dependencies: [
${targetDependencies.join(",\n")}
$targetDetailsIndent]''';
targetDetails.add(dependenciesString);
}
return '''
${targetType.name}(
$targetDetailsIndent${targetDetails.join(",\n$targetDetailsIndent")}
$targetIndent)''';
}
}
/// Type of Target.Dependency constructor.
///
/// See https://developer.apple.com/documentation/packagedescription/target/dependency
/// for more information.
enum SwiftPackageTargetDependencyType {
product(name: '.product'),
target(name: '.target');
const SwiftPackageTargetDependencyType({required this.name});
final String name;
}
/// A dependency for the Target on a product from a package dependency or from
/// another Target in the same package.
///
/// Representation of Target.Dependency from
/// https://developer.apple.com/documentation/packagedescription/target/dependency.
class SwiftPackageTargetDependency {
SwiftPackageTargetDependency.product({
required this.name,
required String packageName,
}) : package = packageName,
dependencyType = SwiftPackageTargetDependencyType.product;
SwiftPackageTargetDependency.target({
required this.name,
}) : package = null,
dependencyType = SwiftPackageTargetDependencyType.target;
final String name;
final String? package;
final SwiftPackageTargetDependencyType dependencyType;
String format() {
// dependencies: [
// .target(name: "Flutter"),
// .product(name: "image_picker_ios", package: "image_picker_ios")
// ]
if (dependencyType == SwiftPackageTargetDependencyType.product) {
return '$_doubleIndent$_doubleIndent${dependencyType.name}(name: "$name", package: "$package")';
}
return '$_doubleIndent$_doubleIndent${dependencyType.name}(name: "$name")';
}
}