// 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:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/isolated/mustache_template.dart'; import 'package:flutter_tools/src/macos/swift_packages.dart'; import '../../src/common.dart'; const _doubleIndent = ' '; void main() { group('SwiftPackage', () { testWithoutContext('createSwiftPackage also creates source file for each default target', () { final fs = MemoryFileSystem(); final File swiftPackageFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Package.swift', ); const target1Name = 'Target1'; const target2Name = 'Target2'; final File target1SourceFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target1Name/$target1Name.swift', ); final File target2SourceFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target2Name/$target2Name.swift', ); final swiftPackage = SwiftPackage( manifest: swiftPackageFile, name: 'FlutterGeneratedPluginSwiftPackage', platforms: [], products: [], dependencies: [], targets: [ SwiftPackageTarget.defaultTarget(name: target1Name), SwiftPackageTarget.defaultTarget(name: 'Target2'), ], templateRenderer: const MustacheTemplateRenderer(), ); swiftPackage.createSwiftPackage(); expect(swiftPackageFile.existsSync(), isTrue); expect(target1SourceFile.existsSync(), isTrue); expect(target2SourceFile.existsSync(), isTrue); }); testWithoutContext('createSwiftPackage also creates source file for binary target', () { final fs = MemoryFileSystem(); final File swiftPackageFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Package.swift', ); final swiftPackage = SwiftPackage( manifest: swiftPackageFile, name: 'FlutterGeneratedPluginSwiftPackage', platforms: [], products: [], dependencies: [], targets: [ SwiftPackageTarget.binaryTarget(name: 'BinaryTarget', relativePath: ''), ], templateRenderer: const MustacheTemplateRenderer(), ); swiftPackage.createSwiftPackage(); expect(swiftPackageFile.existsSync(), isTrue); expect( fs.systemTempDirectory .childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Sources/BinaryTarget/BinaryTarget.swift', ) .existsSync(), isFalse, ); }); testWithoutContext('createSwiftPackage does not creates source file if already exists', () { final fs = MemoryFileSystem(); final File swiftPackageFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Package.swift', ); const target1Name = 'Target1'; const target2Name = 'Target2'; final File target1SourceFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target1Name/$target1Name.swift', ); final File target2SourceFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target2Name/$target2Name.swift', ); fs.systemTempDirectory .childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target1Name/SomeSourceFile.swift', ) .createSync(recursive: true); fs.systemTempDirectory .childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target2Name/SomeSourceFile.swift', ) .createSync(recursive: true); final swiftPackage = SwiftPackage( manifest: swiftPackageFile, name: 'FlutterGeneratedPluginSwiftPackage', platforms: [], products: [], dependencies: [], targets: [ SwiftPackageTarget.defaultTarget(name: target1Name), SwiftPackageTarget.defaultTarget(name: 'Target2'), ], templateRenderer: const MustacheTemplateRenderer(), ); swiftPackage.createSwiftPackage(); expect(swiftPackageFile.existsSync(), isTrue); expect(target1SourceFile.existsSync(), isFalse); expect(target2SourceFile.existsSync(), isFalse); }); testWithoutContext( 'createSwiftPackage does not creates source file if generateEmptySources is false', () { final fs = MemoryFileSystem(); final File swiftPackageFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Package.swift', ); const target1Name = 'Target1'; const target2Name = 'Target2'; final File target1SourceFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target1Name/$target1Name.swift', ); final File target2SourceFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target2Name/$target2Name.swift', ); final swiftPackage = SwiftPackage( manifest: swiftPackageFile, name: 'FlutterGeneratedPluginSwiftPackage', platforms: [], products: [], dependencies: [], targets: [ SwiftPackageTarget.defaultTarget(name: target1Name), SwiftPackageTarget.defaultTarget(name: 'Target2'), ], templateRenderer: const MustacheTemplateRenderer(), ); swiftPackage.createSwiftPackage(generateEmptySources: false); expect(swiftPackageFile.existsSync(), isTrue); expect(target1SourceFile.existsSync(), isFalse); expect(target2SourceFile.existsSync(), isFalse); }, ); group('create Package.swift from template', () { testWithoutContext('with none in each field', () { final fs = MemoryFileSystem(); final File swiftPackageFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Package.swift', ); final swiftPackage = SwiftPackage( manifest: swiftPackageFile, name: 'FlutterGeneratedPluginSwiftPackage', platforms: [], products: [], dependencies: [], targets: [], templateRenderer: const MustacheTemplateRenderer(), ); swiftPackage.createSwiftPackage(); expect(swiftPackageFile.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", products: [ $_doubleIndent ], dependencies: [ $_doubleIndent ], targets: [ $_doubleIndent ] ) '''); }); testWithoutContext('with single in each field', () { final fs = MemoryFileSystem(); final File swiftPackageFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Package.swift', ); final swiftPackage = SwiftPackage( manifest: swiftPackageFile, name: 'FlutterGeneratedPluginSwiftPackage', platforms: [ SwiftPackageSupportedPlatform( platform: SwiftPackagePlatform.ios, version: Version(12, 0, null), ), ], products: [ SwiftPackageProduct.library(name: 'Product1', targets: ['Target1']), ], dependencies: [ SwiftPackagePackageDependency(name: 'Dependency1', path: '/path/to/dependency1'), ], targets: [ SwiftPackageTarget.defaultTarget( name: 'Target1', dependencies: [ SwiftPackageTargetDependency.product( name: 'TargetDependency1', packageName: 'TargetDependency1Package', ), ], ), ], templateRenderer: const MustacheTemplateRenderer(), ); swiftPackage.createSwiftPackage(); expect(swiftPackageFile.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: [ .iOS("12.0") ], products: [ .library(name: "Product1", targets: ["Target1"]) ], dependencies: [ .package(name: "Dependency1", path: "/path/to/dependency1") ], targets: [ .target( name: "Target1", dependencies: [ .product(name: "TargetDependency1", package: "TargetDependency1Package") ] ) ] ) '''); }); testWithoutContext('with multiple in each field', () { final fs = MemoryFileSystem(); final File swiftPackageFile = fs.systemTempDirectory.childFile( 'Packages/FlutterGeneratedPluginSwiftPackage/Package.swift', ); final swiftPackage = SwiftPackage( manifest: swiftPackageFile, name: 'FlutterGeneratedPluginSwiftPackage', platforms: [ SwiftPackageSupportedPlatform( platform: SwiftPackagePlatform.ios, version: Version(12, 0, null), ), SwiftPackageSupportedPlatform( platform: SwiftPackagePlatform.macos, version: Version(10, 14, null), ), ], products: [ SwiftPackageProduct.library(name: 'Product1', targets: ['Target1']), SwiftPackageProduct.library(name: 'Product2', targets: ['Target2']), ], dependencies: [ SwiftPackagePackageDependency(name: 'Dependency1', path: '/path/to/dependency1'), SwiftPackagePackageDependency(name: 'Dependency2', path: '/path/to/dependency2'), ], targets: [ SwiftPackageTarget.binaryTarget(name: 'Target1', relativePath: '/path/to/target1'), SwiftPackageTarget.defaultTarget( name: 'Target2', dependencies: [ SwiftPackageTargetDependency.target(name: 'TargetDependency1'), SwiftPackageTargetDependency.product( name: 'TargetDependency2', packageName: 'TargetDependency2Package', ), ], ), ], templateRenderer: const MustacheTemplateRenderer(), ); swiftPackage.createSwiftPackage(); expect(swiftPackageFile.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: [ .iOS("12.0"), .macOS("10.14") ], products: [ .library(name: "Product1", targets: ["Target1"]), .library(name: "Product2", targets: ["Target2"]) ], dependencies: [ .package(name: "Dependency1", path: "/path/to/dependency1"), .package(name: "Dependency2", path: "/path/to/dependency2") ], targets: [ .binaryTarget( name: "Target1", path: "/path/to/target1" ), .target( name: "Target2", dependencies: [ .target(name: "TargetDependency1"), .product(name: "TargetDependency2", package: "TargetDependency2Package") ] ) ] ) '''); }); }); }); testWithoutContext('Format SwiftPackageSupportedPlatform', () { final supportedPlatform = SwiftPackageSupportedPlatform( platform: SwiftPackagePlatform.ios, version: Version(17, 0, null), ); expect(supportedPlatform.format(), '.iOS("17.0")'); }); testWithoutContext('SwiftPackageSupportedPlatform.fromJson parsed iOS', () { final SwiftPackageSupportedPlatform? supportedPlatform = SwiftPackageSupportedPlatform.fromJson( {'platformName': 'ios', 'version': '13.0'}, ); expect(supportedPlatform, isNotNull); expect(supportedPlatform?.format(), '.iOS("13.0")'); }); testWithoutContext('SwiftPackageSupportedPlatform.fromJson parsed macOS', () { final SwiftPackageSupportedPlatform? supportedPlatform = SwiftPackageSupportedPlatform.fromJson( {'platformName': 'macos', 'version': '10.15'}, ); expect(supportedPlatform, isNotNull); expect(supportedPlatform?.format(), '.macOS("10.15")'); }); testWithoutContext('SwiftPackageSupportedPlatform.fromJson returns null when invalid', () { final SwiftPackageSupportedPlatform? invalidVersion = SwiftPackageSupportedPlatform.fromJson({ 'platformName': 'ios', 'version': 'v15', }); expect(invalidVersion, isNull); final SwiftPackageSupportedPlatform? invalidPlatform = SwiftPackageSupportedPlatform.fromJson({ 'platformName': 'asdf', 'version': '13.0', }); expect(invalidPlatform, isNull); final SwiftPackageSupportedPlatform? invalidJson = SwiftPackageSupportedPlatform.fromJson({ 'name': 'ios', 'version': '13.0', }); expect(invalidJson, isNull); }); group('Format SwiftPackageProduct', () { testWithoutContext('without targets and libraryType', () { final product = SwiftPackageProduct.library(name: 'ProductName', targets: []); expect(product.format(), '.library(name: "ProductName")'); }); testWithoutContext('with targets', () { final singleProduct = SwiftPackageProduct.library( name: 'ProductName', targets: ['Target1'], ); expect(singleProduct.format(), '.library(name: "ProductName", targets: ["Target1"])'); final multipleProducts = SwiftPackageProduct.library( name: 'ProductName', targets: ['Target1', 'Target2'], ); expect( multipleProducts.format(), '.library(name: "ProductName", targets: ["Target1", "Target2"])', ); }); testWithoutContext('with libraryType', () { final product = SwiftPackageProduct.library( name: 'ProductName', targets: [], libraryType: SwiftPackageLibraryType.dynamic, ); expect(product.format(), '.library(name: "ProductName", type: .dynamic)'); }); testWithoutContext('with targets and libraryType', () { final product = SwiftPackageProduct.library( name: 'ProductName', targets: ['Target1', 'Target2'], libraryType: SwiftPackageLibraryType.dynamic, ); expect( product.format(), '.library(name: "ProductName", type: .dynamic, targets: ["Target1", "Target2"])', ); }); testWithoutContext('as executable', () { final product = SwiftPackageProduct.executable( name: 'ProductName', targets: ['Target1'], ); expect(product.format(), '.executable(name: "ProductName", targets: ["Target1"])'); }); testWithoutContext('as plugin', () { final product = SwiftPackageProduct.plugin(name: 'ProductName', targets: ['Target1']); expect(product.format(), '.plugin(name: "ProductName", targets: ["Target1"])'); }); }); testWithoutContext('Format SwiftPackagePackageDependency', () { final supportedPlatform = SwiftPackagePackageDependency( name: 'DependencyName', path: '/path/to/dependency', ); expect( supportedPlatform.format(), '.package(name: "DependencyName", path: "/path/to/dependency")', ); }); group('Format SwiftPackageTarget', () { testWithoutContext('as default target with multiple SwiftPackageTargetDependency', () { final product = SwiftPackageTarget.defaultTarget( name: 'ProductName', dependencies: [ SwiftPackageTargetDependency.target(name: 'Dependency1'), SwiftPackageTargetDependency.product( name: 'Dependency2', packageName: 'Dependency2Package', ), ], ); expect(product.format(), ''' .target( name: "ProductName", dependencies: [ .target(name: "Dependency1"), .product(name: "Dependency2", package: "Dependency2Package") ] )'''); }); testWithoutContext('as default target with no SwiftPackageTargetDependency', () { final product = SwiftPackageTarget.defaultTarget(name: 'ProductName'); expect(product.format(), ''' .target( name: "ProductName" )'''); }); testWithoutContext('as binaryTarget', () { final product = SwiftPackageTarget.binaryTarget( name: 'ProductName', relativePath: '/path/to/target', ); expect(product.format(), ''' .binaryTarget( name: "ProductName", path: "/path/to/target" )'''); }); testWithoutContext('as executable', () { final product = SwiftPackageTarget.executableTarget( name: 'ProductName', dependencies: [ SwiftPackageTargetDependency.target(name: 'Dependency1'), ], path: '/path/to/target', ); expect(product.format(), ''' .executableTarget( name: "ProductName", dependencies: [ .target(name: "Dependency1") ], path: "/path/to/target" )'''); }); testWithoutContext('as plugin', () { final product = SwiftPackageTarget.pluginTarget( name: 'ProductName', commandCapability: SwiftPackageCommandCapability( verb: 'some-command', description: 'Some description', ), ); expect(product.format(), ''' .plugin( name: "ProductName", capability: .command( intent: .custom(verb: "some-command", description: "Some description"), permissions: [ .writeToPackageDirectory(reason: "Some description"), ] ) )'''); }); testWithoutContext('as testTarget', () { final product = SwiftPackageTarget.testTarget( name: 'ProductName', dependencies: [ SwiftPackageTargetDependency.target(name: 'Dependency1'), ], ); expect(product.format(), ''' .testTarget( name: "ProductName", dependencies: [ .target(name: "Dependency1") ] )'''); }); }); group('Format SwiftPackageTargetDependency', () { testWithoutContext('with only name', () { final targetDependency = SwiftPackageTargetDependency.target(name: 'DependencyName'); expect(targetDependency.format(), ' .target(name: "DependencyName")'); }); testWithoutContext('with name and package', () { final targetDependency = SwiftPackageTargetDependency.product( name: 'DependencyName', packageName: 'PackageName', ); expect( targetDependency.format(), ' .product(name: "DependencyName", package: "PackageName")', ); }); }); }