From 03076f40eb8e23ddcd51cb3405f606150e311f32 Mon Sep 17 00:00:00 2001 From: Kohki Miki Date: Fri, 8 Nov 2019 02:03:26 +0900 Subject: [PATCH] Support Static Frameworks for Carthage dependencies (#688) --- CHANGELOG.md | 1 + Docs/ProjectSpec.md | 9 +- Sources/ProjectSpec/Dependency.swift | 15 +- .../CarthageDependencyResolver.swift | 11 +- Sources/XcodeGenKit/PBXProjGenerator.swift | 42 +++-- Tests/PerformanceTests/TestProject.swift | 10 +- Tests/ProjectSpecTests/ProjectSpecTests.swift | 2 +- Tests/ProjectSpecTests/SpecLoadingTests.swift | 12 +- .../CarthageDependencyResolverTests.swift | 12 +- .../ProjectGeneratorTests.swift | 144 +++++++++++++++++- .../SourceGeneratorTests.swift | 8 +- 11 files changed, 224 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d92a2cd8..dcffde09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Next Version #### Added +- Add Carthage static framework dependencies support. [#688](https://github.com/yonaskolb/XcodeGen/pull/688) @giginet - Added `--no-env` option to disable environment variables expansion [#704](https://github.com/yonaskolb/XcodeGen/pull/704) @rcari #### Fixed diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index b6438a36..b265edfc 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -397,6 +397,9 @@ This only applies to `framework` dependencies. Implicit framework dependencies a **Carthage Dependency** +- [ ] **findFrameworks**: **Bool** - Whether to find Carthage frameworks automatically. Defaults to `true` . +- [ ] **linkType**: **String** - Dependency link type. This value should be `dynamic` or `static`. Default value is `dynamic` . + Carthage frameworks are expected to be in `CARTHAGE_BUILD_PATH/PLATFORM/FRAMEWORK.framework` where: - `CARTHAGE_BUILD_PATH` = `options.carthageBuildPath` or `Carthage/Build` by default @@ -408,6 +411,9 @@ Xcodegen uses `.version` files generated by Carthage in order for this framework If any applications contain carthage dependencies within itself or any dependent targets, a carthage copy files script is automatically added to the application containing all the relevant frameworks. A `FRAMEWORK_SEARCH_PATHS` setting is also automatically added +Carthage officially supports static frameworks. In this case, frameworks are expected to be in `CARTHAGE_BUILD_PATH/PLATFORM/Static/FRAMEWORK.framework`. +You can specify `linkType` to `static` to integrate static ones. + ```yaml targets: MyTarget: @@ -415,7 +421,8 @@ targets: - target: MyFramework - framework: path/to/framework.framework - carthage: Result - findFrameworks: true + findFrameworks: false + linkType: static - sdk: Contacts.framework - sdk: libc++.tbd - sdk: libz.dylib diff --git a/Sources/ProjectSpec/Dependency.swift b/Sources/ProjectSpec/Dependency.swift index a31c0bd3..9958eb2e 100644 --- a/Sources/ProjectSpec/Dependency.swift +++ b/Sources/ProjectSpec/Dependency.swift @@ -32,11 +32,18 @@ public struct Dependency: Equatable { self.implicit = implicit self.weakLink = weakLink } + + public enum CarthageLinkType: String { + case dynamic + case `static` + + public static let `default` = dynamic + } public enum DependencyType: Equatable { case target case framework - case carthage(findFrameworks: Bool?) + case carthage(findFrameworks: Bool?, linkType: CarthageLinkType) case sdk(root: String?) case package(product: String?) } @@ -59,7 +66,8 @@ extension Dependency: JSONObjectConvertible { reference = framework } else if let carthage: String = jsonDictionary.json(atKeyPath: "carthage") { let findFrameworks: Bool? = jsonDictionary.json(atKeyPath: "findFrameworks") - type = .carthage(findFrameworks: findFrameworks) + let carthageLinkType: CarthageLinkType = (jsonDictionary.json(atKeyPath: "linkType") as String?).flatMap(CarthageLinkType.init(rawValue:)) ?? .default + type = .carthage(findFrameworks: findFrameworks, linkType: carthageLinkType) reference = carthage } else if let sdk: String = jsonDictionary.json(atKeyPath: "sdk") { let sdkRoot: String? = jsonDictionary.json(atKeyPath: "root") @@ -112,11 +120,12 @@ extension Dependency: JSONEncodable { dict["target"] = reference case .framework: dict["framework"] = reference - case .carthage(let findFrameworks): + case .carthage(let findFrameworks, let linkType): dict["carthage"] = reference if let findFrameworks = findFrameworks { dict["findFrameworks"] = findFrameworks } + dict["linkType"] = linkType.rawValue case .sdk: dict["sdk"] = reference case .package: diff --git a/Sources/XcodeGenKit/CarthageDependencyResolver.swift b/Sources/XcodeGenKit/CarthageDependencyResolver.swift index 08827261..686ff32b 100644 --- a/Sources/XcodeGenKit/CarthageDependencyResolver.swift +++ b/Sources/XcodeGenKit/CarthageDependencyResolver.swift @@ -31,8 +31,13 @@ public class CarthageDependencyResolver { } /// Carthage's build path for the given platform - func buildPath(for platform: Platform) -> String { - return "\(buildPath)/\(platform.carthageName)" + func buildPath(for platform: Platform, linkType: Dependency.CarthageLinkType) -> String { + switch linkType { + case .static: + return "\(buildPath)/\(platform.carthageName)/Static" + case .dynamic: + return "\(buildPath)/\(platform.carthageName)" + } } /// Fetches all carthage dependencies for a given target @@ -53,7 +58,7 @@ public class CarthageDependencyResolver { let nonExistentDependencies = target.dependencies.filter { !frameworks.contains($0) } for dependency in nonExistentDependencies { switch dependency.type { - case .carthage(let findFrameworks): + case .carthage(let findFrameworks, _): let findFrameworks = findFrameworks ?? project.options.findCarthageFrameworks if findFrameworks { relatedDependencies(for: dependency, in: target.platform) diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index b3466a01..e1a5d75b 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -601,13 +601,13 @@ public class PBXProjGenerator { ) targetFrameworkBuildFiles.append(buildFile) - case .carthage(let findFrameworks): + case .carthage(let findFrameworks, let linkType): let findFrameworks = findFrameworks ?? project.options.findCarthageFrameworks let allDependencies = findFrameworks ? carthageResolver.relatedDependencies(for: dependency, in: target.platform) : [dependency] allDependencies.forEach { dependency in - var platformPath = Path(carthageResolver.buildPath(for: target.platform)) + var platformPath = Path(carthageResolver.buildPath(for: target.platform, linkType: linkType)) var frameworkPath = platformPath + dependency.reference if frameworkPath.extension == nil { frameworkPath = Path(frameworkPath.string + ".framework") @@ -615,7 +615,7 @@ public class PBXProjGenerator { let fileReference = self.sourceGenerator.getFileReference(path: frameworkPath, inPath: platformPath) self.carthageFrameworksByPlatform[target.platform.carthageName, default: []].insert(fileReference) - + if dependency.link ?? (target.type != .staticLibrary) { let buildFile = self.addObject( PBXBuildFile(file: fileReference, settings: getDependencyFrameworkSettings(dependency: dependency)) @@ -651,17 +651,22 @@ public class PBXProjGenerator { } for dependency in carthageDependencies { - + let embed = dependency.embed ?? target.shouldEmbedCarthageDependencies - var platformPath = Path(carthageResolver.buildPath(for: target.platform)) + var platformPath = Path(carthageResolver.buildPath(for: target.platform, linkType: dependency.carthageLinkType ?? .default)) var frameworkPath = platformPath + dependency.reference if frameworkPath.extension == nil { frameworkPath = Path(frameworkPath.string + ".framework") } let fileReference = sourceGenerator.getFileReference(path: frameworkPath, inPath: platformPath) - if embed { + if dependency.carthageLinkType == .static { + let embedFile = addObject( + PBXBuildFile(file: fileReference, settings: getDependencyFrameworkSettings(dependency: dependency)) + ) + targetFrameworkBuildFiles.append(embedFile) + } else if embed { if directlyEmbedCarthage { let embedFile = addObject( PBXBuildFile(file: fileReference, settings: getEmbedSettings(dependency: dependency, codeSign: dependency.codeSign ?? true)) @@ -762,7 +767,7 @@ public class PBXProjGenerator { if !carthageFrameworksToEmbed.isEmpty { let inputPaths = carthageFrameworksToEmbed - .map { "$(SRCROOT)/\(carthageResolver.buildPath(for: target.platform))/\($0)\($0.contains(".") ? "" : ".framework")" } + .map { "$(SRCROOT)/\(carthageResolver.buildPath(for: target.platform, linkType: .dynamic))/\($0)\($0.contains(".") ? "" : ".framework")" } let outputPaths = carthageFrameworksToEmbed .map { "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/\($0)\($0.contains(".") ? "" : ".framework")" } let carthageExecutable = carthageResolver.executable @@ -925,8 +930,16 @@ public class PBXProjGenerator { // set Carthage search paths let configFrameworkBuildPaths: [String] if !carthageDependencies.isEmpty { - let carthagePlatformBuildPath = "$(PROJECT_DIR)/" + carthageResolver.buildPath(for: target.platform) - configFrameworkBuildPaths = [carthagePlatformBuildPath] + frameworkBuildPaths.sorted() + var carthagePlatformBuildPaths: [String] = [] + if carthageDependencies.contains(where: { $0.carthageLinkType == .static }) { + let carthagePlatformBuildPath = "$(PROJECT_DIR)/" + carthageResolver.buildPath(for: target.platform, linkType: .static) + carthagePlatformBuildPaths.append(carthagePlatformBuildPath) + } + if carthageDependencies.contains(where: { $0.carthageLinkType == .dynamic }) { + let carthagePlatformBuildPath = "$(PROJECT_DIR)/" + carthageResolver.buildPath(for: target.platform, linkType: .dynamic) + carthagePlatformBuildPaths.append(carthagePlatformBuildPath) + } + configFrameworkBuildPaths = carthagePlatformBuildPaths + frameworkBuildPaths.sorted() } else { configFrameworkBuildPaths = frameworkBuildPaths.sorted() } @@ -1083,3 +1096,14 @@ extension PBXFileElement { } } } + +private extension Dependency { + var carthageLinkType: Dependency.CarthageLinkType? { + switch type { + case .carthage(_, let linkType): + return linkType + default: + return nil + } + } +} diff --git a/Tests/PerformanceTests/TestProject.swift b/Tests/PerformanceTests/TestProject.swift index bfc74779..0eac0c01 100644 --- a/Tests/PerformanceTests/TestProject.swift +++ b/Tests/PerformanceTests/TestProject.swift @@ -39,8 +39,8 @@ extension Project { dependencies: [ Dependency(type: .target, reference: "Framework_\(platform)"), Dependency(type: .target, reference: "Framework2_\(platform)"), - Dependency(type: .carthage(findFrameworks: false), reference: "Alamofire"), - Dependency(type: .carthage(findFrameworks: false), reference: "BrightFutures"), + Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "Alamofire"), + Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "BrightFutures"), ], scheme: scheme ) @@ -74,7 +74,7 @@ extension Project { TargetSource(path: "Framework_\(platform)"), ], dependencies: [ - Dependency(type: .carthage(findFrameworks: false), reference: "Alamofire"), + Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "Alamofire"), ], scheme: scheme ) @@ -90,8 +90,8 @@ extension Project { sources: [TargetSource(path: "Framework2_\(platform)")], dependencies: [ Dependency(type: .target, reference: "Framework_\(platform)"), - Dependency(type: .carthage(findFrameworks: false), reference: "Alamofire"), - Dependency(type: .carthage(findFrameworks: false), reference: "BrightFutures"), + Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "Alamofire"), + Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "BrightFutures"), ], scheme: scheme ) diff --git a/Tests/ProjectSpecTests/ProjectSpecTests.swift b/Tests/ProjectSpecTests/ProjectSpecTests.swift index 7744872d..eb80e2c1 100644 --- a/Tests/ProjectSpecTests/ProjectSpecTests.swift +++ b/Tests/ProjectSpecTests/ProjectSpecTests.swift @@ -298,7 +298,7 @@ class ProjectSpecTests: XCTestCase { buildPhase: .resources, headerVisibility: .private, createIntermediateGroups: true)], - dependencies: [Dependency(type: .carthage(findFrameworks: true), + dependencies: [Dependency(type: .carthage(findFrameworks: true, linkType: .dynamic), reference: "reference", embed: true, codeSign: true, diff --git a/Tests/ProjectSpecTests/SpecLoadingTests.swift b/Tests/ProjectSpecTests/SpecLoadingTests.swift index 09cb3067..cc217140 100644 --- a/Tests/ProjectSpecTests/SpecLoadingTests.swift +++ b/Tests/ProjectSpecTests/SpecLoadingTests.swift @@ -371,6 +371,7 @@ class SpecLoadingTests: XCTestCase { targetDictionary["dependencies"] = [ ["target": "name", "embed": false], ["carthage": "name", "findFrameworks": true], + ["carthage": "name", "findFrameworks": true, "linkType": "static"], ["framework": "path", "weak": true], ["sdk": "Contacts.framework"], [ @@ -379,12 +380,13 @@ class SpecLoadingTests: XCTestCase { ], ] let target = try Target(name: "test", jsonDictionary: targetDictionary) - try expect(target.dependencies.count) == 5 + try expect(target.dependencies.count) == 6 try expect(target.dependencies[0]) == Dependency(type: .target, reference: "name", embed: false) - try expect(target.dependencies[1]) == Dependency(type: .carthage(findFrameworks: true), reference: "name") - try expect(target.dependencies[2]) == Dependency(type: .framework, reference: "path", weakLink: true) - try expect(target.dependencies[3]) == Dependency(type: .sdk(root: nil), reference: "Contacts.framework") - try expect(target.dependencies[4]) == Dependency(type: .sdk(root: "DEVELOPER_DIR"), reference: "Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework") + try expect(target.dependencies[1]) == Dependency(type: .carthage(findFrameworks: true, linkType: .dynamic), reference: "name") + try expect(target.dependencies[2]) == Dependency(type: .carthage(findFrameworks: true, linkType: .static), reference: "name") + try expect(target.dependencies[3]) == Dependency(type: .framework, reference: "path", weakLink: true) + try expect(target.dependencies[4]) == Dependency(type: .sdk(root: nil), reference: "Contacts.framework") + try expect(target.dependencies[5]) == Dependency(type: .sdk(root: "DEVELOPER_DIR"), reference: "Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework") } $0.it("parses info plist") { diff --git a/Tests/XcodeGenKitTests/CarthageDependencyResolverTests.swift b/Tests/XcodeGenKitTests/CarthageDependencyResolverTests.swift index 8dfe99e5..3ac09893 100644 --- a/Tests/XcodeGenKitTests/CarthageDependencyResolverTests.swift +++ b/Tests/XcodeGenKitTests/CarthageDependencyResolverTests.swift @@ -55,7 +55,7 @@ class CarthageDependencyResolverTests: XCTestCase { } try allPlatforms.forEach { platform in - try expect(expectedByPlatform[platform]) == resolver.buildPath(for: platform) + try expect(expectedByPlatform[platform]) == resolver.buildPath(for: platform, linkType: .dynamic) } } } @@ -70,7 +70,7 @@ class CarthageDependencyResolverTests: XCTestCase { let options = SpecOptions(carthageBuildPath: carthageBuildPath.string) let resolver = CarthageDependencyResolver(project: makeTestProject(options: options)) - let dependency = Dependency(type: .carthage(findFrameworks: true), reference: "CarthageTestFixture") + let dependency = Dependency(type: .carthage(findFrameworks: true, linkType: .dynamic), reference: "CarthageTestFixture") let expectedDependencies: [Platform: [String]] = [ .macOS: ["DependencyFixtureB", "DependencyFixtureA", "CarthageTestFixture"], .watchOS: ["DependencyFixtureA", "DependencyFixtureB", "CarthageTestFixture"], @@ -87,7 +87,7 @@ class CarthageDependencyResolverTests: XCTestCase { $0.it("returns the main dependency when no related dependencies are found") { let resolver = CarthageDependencyResolver(project: makeTestProject()) - let dependency = Dependency(type: .carthage(findFrameworks: true), reference: "RandomDependency") + let dependency = Dependency(type: .carthage(findFrameworks: true, linkType: .dynamic), reference: "RandomDependency") let related = resolver.relatedDependencies(for: dependency, in: .iOS) @@ -96,7 +96,7 @@ class CarthageDependencyResolverTests: XCTestCase { $0.it("de-duplicates dependencies") { let resolver = CarthageDependencyResolver(project: makeTestProject()) - let dependency = Dependency(type: .carthage(findFrameworks: true), reference: "ReactiveSwift") + let dependency = Dependency(type: .carthage(findFrameworks: true, linkType: .dynamic), reference: "ReactiveSwift") let related = resolver.relatedDependencies(for: dependency, in: .iOS) @@ -114,7 +114,7 @@ class CarthageDependencyResolverTests: XCTestCase { $0.it("overrides the findFrameworks dependency global flag when specified") { let options = SpecOptions(carthageBuildPath: carthageBuildPath.string, findCarthageFrameworks: true) - let dependency = Dependency(type: .carthage(findFrameworks: false), reference: dependencyFixtureName) + let dependency = Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: dependencyFixtureName) let resolver = CarthageDependencyResolver(project: makeTestProject(options: options)) let target = Target(name: "1", type: .application, platform: .iOS, dependencies: [dependency]) @@ -126,7 +126,7 @@ class CarthageDependencyResolverTests: XCTestCase { $0.it("fetches all carthage dependencies for a given target, sorted alphabetically") { let unsortedDependencyReferences = ["RxSwift", "RxCocoa", "RxBlocking", "RxTest", "RxAtomic"] let dependencies = unsortedDependencyReferences.map { - Dependency(type: .carthage(findFrameworks: false), reference: $0) + Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: $0) } let nonCarthageDependencies = unsortedDependencyReferences.map { Dependency(type: .target, reference: $0) } let target = Target(name: "1", type: .application, platform: .iOS, dependencies: dependencies + nonCarthageDependencies) diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index 8f1aa6ef..49d3dce1 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -452,7 +452,7 @@ class ProjectGeneratorTests: XCTestCase { Dependency(type: .framework, reference: "FrameworkZ.framework", link: true), Dependency(type: .target, reference: iosFrameworkX.name /* , link: false */ ), Dependency(type: .framework, reference: "FrameworkX.framework" /* , link: false */ ), - Dependency(type: .carthage(findFrameworks: false), reference: "CarthageZ"), + Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "CarthageZ"), ] ) expectedResourceFiles[staticLibrary.name] = Set() @@ -479,10 +479,10 @@ class ProjectGeneratorTests: XCTestCase { dependencies: [ Dependency(type: .target, reference: resourceBundle.name), Dependency(type: .framework, reference: "FrameworkC.framework"), - Dependency(type: .carthage(findFrameworks: false), reference: "CarthageA"), + Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "CarthageA"), // Statically linked, so don't embed into test Dependency(type: .target, reference: staticLibrary.name), - Dependency(type: .carthage(findFrameworks: false), reference: "CarthageB", embed: false), + Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "CarthageB", embed: false), ] ) expectedResourceFiles[iosFrameworkA.name] = Set() @@ -507,7 +507,7 @@ class ProjectGeneratorTests: XCTestCase { Dependency(type: .framework, reference: "FrameworkD.framework"), // Embedded into framework, so don't embed into test Dependency(type: .framework, reference: "FrameworkE.framework", embed: true), - Dependency(type: .carthage(findFrameworks: false), reference: "CarthageC", embed: true), + Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "CarthageC", embed: true), // Statically linked, so don't embed into test Dependency(type: .framework, reference: "FrameworkF.framework", embed: false), ] @@ -539,7 +539,7 @@ class ProjectGeneratorTests: XCTestCase { dependencies: [ Dependency(type: .target, reference: app.name), Dependency(type: .target, reference: iosFrameworkB.name), - Dependency(type: .carthage(findFrameworks: false), reference: "CarthageD"), + Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "CarthageD"), ], directlyEmbedCarthageDependencies: false ) @@ -1004,6 +1004,70 @@ class ProjectGeneratorTests: XCTestCase { // generated plist should not be in buildsettings try expect(targetConfig.buildSettings["INFOPLIST_FILE"] as? String) == predefinedPlistPath } + + describe("Carthage dependencies") { + $0.context("with static dependency") { + $0.it("should set dependencies") { + let app = Target( + name: "MyApp", + type: .application, + platform: .iOS, + dependencies: [ + Dependency(type: .carthage(findFrameworks: true, linkType: .static), reference: "MyStaticFramework"), + ] + ) + let project = Project(name: "test", targets: [app]) + let pbxProject = try project.generatePbxProj() + + let target = pbxProject.nativeTargets.first! + let configuration = target.buildConfigurationList!.buildConfigurations.first! + try expect(configuration.buildSettings["FRAMEWORK_SEARCH_PATHS"] as? [String]) == ["$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS/Static"] + let frameworkBuildPhase = try target.frameworksBuildPhase() + guard let files = frameworkBuildPhase?.files, let file = files.first else { + return XCTFail("frameworkBuildPhase should have files") + } + try expect(file.file?.nameOrPath) == "MyStaticFramework.framework" + + try expect(target.carthageCopyFrameworkBuildPhase).beNil() + } + } + + $0.context("with mixed dependencies") { + $0.it("should set dependencies") { + let app = Target( + name: "MyApp", + type: .application, + platform: .iOS, + dependencies: [ + Dependency(type: .carthage(findFrameworks: true, linkType: .dynamic), reference: "MyDynamicFramework"), + Dependency(type: .carthage(findFrameworks: true, linkType: .static), reference: "MyStaticFramework"), + ] + ) + let project = Project(name: "test", targets: [app]) + let pbxProject = try project.generatePbxProj() + + let target = pbxProject.nativeTargets.first! + let configuration = target.buildConfigurationList!.buildConfigurations.first! + try expect(configuration.buildSettings["FRAMEWORK_SEARCH_PATHS"] as? [String]) == ["$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS/Static", "$(PROJECT_DIR)/Carthage/Build/iOS"] + let frameworkBuildPhase = try target.frameworksBuildPhase() + guard let files = frameworkBuildPhase?.files else { + return XCTFail("frameworkBuildPhase should have files") + } + guard let dynamicFramework = files.first(where: { $0.file?.nameOrPath == "MyDynamicFramework.framework" }) else { + return XCTFail("Framework Build Phase should have Dynamic Framework") + } + guard let _ = files.first(where: { $0.file?.nameOrPath == "MyStaticFramework.framework" }) else { + return XCTFail("Framework Build Phase should have Static Framework") + } + + guard let copyCarthagePhase = target.carthageCopyFrameworkBuildPhase else { + return XCTFail("Carthage Build Phase should be exist") + } + try expect(copyCarthagePhase.inputPaths) == [dynamicFramework.file?.fullPath(sourceRoot: Path("$(SRCROOT)"))?.string] + try expect(copyCarthagePhase.outputPaths) == ["$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/\(dynamicFramework.file!.path!)"] + } + } + } $0.it("generate info.plist doesn't generate CFBundleExecutable for targets with type bundle") { let plist = Plist(path: "Info.plist", attributes: [:]) @@ -1080,6 +1144,76 @@ class ProjectGeneratorTests: XCTestCase { } } } + + describe("Carthage dependencies") { + $0.context("with static dependency") { + $0.it("should set dependencies") { + let app = Target( + name: "MyApp", + type: .application, + platform: .iOS, + dependencies: [ + Dependency(type: .carthage(findFrameworks: true, linkType: .static), reference: "MyStaticFramework"), + ] + ) + let project = Project(name: "test", targets: [app]) + let pbxProject = try project.generatePbxProj() + + let target = pbxProject.nativeTargets.first! + let configuration = target.buildConfigurationList!.buildConfigurations.first! + try expect(configuration.buildSettings["FRAMEWORK_SEARCH_PATHS"] as? [String]) == ["$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS/Static"] + let frameworkBuildPhase = try target.frameworksBuildPhase() + guard let files = frameworkBuildPhase?.files, let file = files.first else { + return XCTFail("frameworkBuildPhase should have files") + } + try expect(file.file?.nameOrPath) == "MyStaticFramework.framework" + + try expect(target.carthageCopyFrameworkBuildPhase).beNil() + } + } + + $0.context("with mixed dependencies") { + $0.it("should set dependencies") { + let app = Target( + name: "MyApp", + type: .application, + platform: .iOS, + dependencies: [ + Dependency(type: .carthage(findFrameworks: true, linkType: .dynamic), reference: "MyDynamicFramework"), + Dependency(type: .carthage(findFrameworks: true, linkType: .static), reference: "MyStaticFramework"), + ] + ) + let project = Project(name: "test", targets: [app]) + let pbxProject = try project.generatePbxProj() + + let target = pbxProject.nativeTargets.first! + let configuration = target.buildConfigurationList!.buildConfigurations.first! + try expect(configuration.buildSettings["FRAMEWORK_SEARCH_PATHS"] as? [String]) == ["$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS/Static", "$(PROJECT_DIR)/Carthage/Build/iOS"] + let frameworkBuildPhase = try target.frameworksBuildPhase() + guard let files = frameworkBuildPhase?.files else { + return XCTFail("frameworkBuildPhase should have files") + } + guard let dynamicFramework = files.first(where: { $0.file?.nameOrPath == "MyDynamicFramework.framework" }) else { + return XCTFail("Framework Build Phase should have Dynamic Framework") + } + guard let _ = files.first(where: { $0.file?.nameOrPath == "MyStaticFramework.framework" }) else { + return XCTFail("Framework Build Phase should have Static Framework") + } + + guard let copyCarthagePhase = target.carthageCopyFrameworkBuildPhase else { + return XCTFail("Carthage Build Phase should be exist") + } + try expect(copyCarthagePhase.inputPaths) == [dynamicFramework.file?.fullPath(sourceRoot: Path("$(SRCROOT)"))?.string] + try expect(copyCarthagePhase.outputPaths) == ["$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/\(dynamicFramework.file!.path!)"] + } + } + } } } } + +private extension PBXTarget { + var carthageCopyFrameworkBuildPhase: PBXShellScriptBuildPhase? { + return buildPhases.first(where: { $0.name() == "Carthage" }) as? PBXShellScriptBuildPhase + } +} diff --git a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift index 16671bd8..294fb272 100644 --- a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift @@ -612,9 +612,9 @@ class SourceGeneratorTests: XCTestCase { """ try createDirectories(directories) - let watchTarget = Target(name: "Watch", type: .watch2App, platform: .watchOS, sources: ["A"], dependencies: [Dependency(type: .carthage(findFrameworks: false), reference: "Alamofire_watch")]) + let watchTarget = Target(name: "Watch", type: .watch2App, platform: .watchOS, sources: ["A"], dependencies: [Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "Alamofire_watch")]) let watchDependency = Dependency(type: .target, reference: "Watch") - let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["A"], dependencies: [Dependency(type: .carthage(findFrameworks: false), reference: "Alamofire"), watchDependency]) + let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["A"], dependencies: [Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "Alamofire"), watchDependency]) let project = Project(basePath: directoryPath, name: "Test", targets: [target, watchTarget]) let pbxProj = try project.generatePbxProj() @@ -633,7 +633,7 @@ class SourceGeneratorTests: XCTestCase { """ try createDirectories(directories) - let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["A", "P", "S"], dependencies: [Dependency(type: .carthage(findFrameworks: false), reference: "Alamofire")]) + let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["A", "P", "S"], dependencies: [Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "Alamofire")]) let project = Project(basePath: directoryPath, name: "Test", targets: [target]) let pbxProj = try project.generatePbxProj() @@ -656,7 +656,7 @@ class SourceGeneratorTests: XCTestCase { """ try createDirectories(directories) - let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"], dependencies: [Dependency(type: .carthage(findFrameworks: false), reference: "Alamofire")]) + let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"], dependencies: [Dependency(type: .carthage(findFrameworks: false, linkType: .dynamic), reference: "Alamofire")]) let project = Project(basePath: directoryPath, name: "Test", targets: [target]) let pbxProj = try project.generatePbxProj()