diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c97512..79403fe4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Fixed error message output for `minimumXcodeGenVersion`. [#967](https://github.com/yonaskolb/XcodeGen/pull/967) @joshwalker - Remove force-unwrapping causing crash for `LegacyTarget`s [#982](https://github.com/yonaskolb/XcodeGen/pull/982) @jcolicchio - Fixed a race condition in an internal JSON decoder, which would occasionally fail with an error like `Parsing project spec failed: Error Domain=Unspecified error Code=0`. [#995](https://github.com/yonaskolb/XcodeGen/pull/995) @elliottwilliams +- Fixed issue where frameworks with `MACH_O_TYPE: staticlib` were being incorrectly embedded. [#1003](https://github.com/yonaskolb/XcodeGen/pull/1003) @mrabiciu #### Internal - Updated to Yams 4.0.0 [#984](https://github.com/yonaskolb/XcodeGen/pull/984) @swiftty diff --git a/Sources/ProjectSpec/Linkage.swift b/Sources/ProjectSpec/Linkage.swift index fec8023e..cceda53e 100644 --- a/Sources/ProjectSpec/Linkage.swift +++ b/Sources/ProjectSpec/Linkage.swift @@ -7,10 +7,10 @@ public enum Linkage { case none } -extension PBXProductType { +extension Target { public var defaultLinkage: Linkage { - switch self { + switch type { case .none, .appExtension, .application, @@ -36,8 +36,12 @@ extension PBXProductType { .xpcService: return .none case .framework, .xcFramework: - // TODO: This should check `MACH_O_TYPE` in case this is a "Static Framework" - return .dynamic + // Check the MACH_O_TYPE for "Static Framework" + if settings.buildSettings.machOType == "staticlib" { + return .static + } else { + return .dynamic + } case .dynamicLibrary: return .dynamic case .staticLibrary, .staticFramework: @@ -45,3 +49,10 @@ extension PBXProductType { } } } + +private extension BuildSettings { + + var machOType: String? { + self["MACH_O_TYPE"] as? String + } +} diff --git a/Sources/ProjectSpec/XCProjExtensions.swift b/Sources/ProjectSpec/XCProjExtensions.swift index 089bb405..c6e194c2 100644 --- a/Sources/ProjectSpec/XCProjExtensions.swift +++ b/Sources/ProjectSpec/XCProjExtensions.swift @@ -54,17 +54,16 @@ extension PBXProductType { } /// Function to determine when a dependendency should be embedded into the target - public func shouldEmbed(_ dependencyType: PBXProductType) -> Bool { - switch dependencyType { - case .staticLibrary, .staticFramework: - // Some dependendencies should not be embed, independently of the target type + public func shouldEmbed(_ dependencyTarget: Target) -> Bool { + switch dependencyTarget.defaultLinkage { + case .static: + // Static dependencies should never embed return false - - default: + case .dynamic, .none: if isApp { - // If target is an app, all dependencies should be embed (except for the ones mentioned above) + // If target is an app, all dependencies should be embed (unless they're static) return true - } else if isTest, [.framework, .bundle].contains(dependencyType) { + } else if isTest, [.framework, .bundle].contains(dependencyTarget.type) { // If target is test, some dependencies should be embed (depending on their type) return true } else { diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index 52060d93..5be56cce 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -690,7 +690,7 @@ public class PBXProjGenerator { } func processTargetDependency(_ dependency: Dependency, dependencyTarget: Target, embedFileReference: PBXFileElement?) { - let dependencyLinkage = dependencyTarget.type.defaultLinkage + let dependencyLinkage = dependencyTarget.defaultLinkage let link = dependency.link ?? ((dependencyLinkage == .dynamic && target.type != .staticLibrary) || (dependencyLinkage == .static && target.type.isExecutable)) @@ -707,7 +707,7 @@ public class PBXProjGenerator { } } - let embed = dependency.embed ?? target.type.shouldEmbed(dependencyTarget.type) + let embed = dependency.embed ?? target.type.shouldEmbed(dependencyTarget) if embed { let embedFile = addObject( PBXBuildFile( diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index 81c12d34..0a189ea5 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -786,6 +786,102 @@ class ProjectGeneratorTests: XCTestCase { try expect(copyFilesPhases.count) == expectedCopyFilesPhasesCount } } + + $0.it("ensures static frameworks are not embedded by default") { + + let app = Target( + name: "App", + type: .application, + platform: .iOS, + dependencies: [ + Dependency(type: .target, reference: "DynamicFramework"), + Dependency(type: .target, reference: "DynamicFrameworkNotEmbedded", embed: false), + Dependency(type: .target, reference: "StaticFramework"), + Dependency(type: .target, reference: "StaticFrameworkExplicitlyEmbedded", embed: true), + Dependency(type: .target, reference: "StaticFramework2"), + Dependency(type: .target, reference: "StaticFramework2ExplicitlyEmbedded", embed: true), + Dependency(type: .target, reference: "StaticLibrary"), + ] + ) + + let targets = [ + app, + Target( + name: "DynamicFramework", + type: .framework, + platform: .iOS + ), + Target( + name: "DynamicFrameworkNotEmbedded", + type: .framework, + platform: .iOS + ), + Target( + name: "StaticFramework", + type: .framework, + platform: .iOS, + settings: Settings(buildSettings: ["MACH_O_TYPE": "staticlib"]) + ), + Target( + name: "StaticFrameworkExplicitlyEmbedded", + type: .framework, + platform: .iOS, + settings: Settings(buildSettings: ["MACH_O_TYPE": "staticlib"]) + ), + Target( + name: "StaticFramework2", + type: .staticFramework, + platform: .iOS + ), + Target( + name: "StaticFramework2ExplicitlyEmbedded", + type: .staticFramework, + platform: .iOS + ), + Target( + name: "StaticLibrary", + type: .staticLibrary, + platform: .iOS + ), + ] + + let expectedLinkedFiles = Set([ + "DynamicFramework.framework", + "DynamicFrameworkNotEmbedded.framework", + "StaticFramework.framework", + "StaticFrameworkExplicitlyEmbedded.framework", + "StaticFramework2.framework", + "StaticFramework2ExplicitlyEmbedded.framework", + "libStaticLibrary.a", + ]) + + let expectedEmbeddedFrameworks = Set([ + "DynamicFramework.framework", + "StaticFrameworkExplicitlyEmbedded.framework", + "StaticFramework2ExplicitlyEmbedded.framework" + ]) + + let project = Project( + name: "test", + targets: targets + ) + let pbxProject = try project.generatePbxProj() + + let appTarget = try unwrap(pbxProject.nativeTargets.first(where: { $0.name == app.name })) + let buildPhases = appTarget.buildPhases + let frameworkPhases = pbxProject.frameworksBuildPhases.filter { buildPhases.contains($0) } + let copyFilesPhases = pbxProject.copyFilesBuildPhases.filter { buildPhases.contains($0) } + let embedFrameworkPhase = copyFilesPhases.first { $0.dstSubfolderSpec == .frameworks } + + // Ensure all targets are linked + let linkFrameworks = (frameworkPhases[0].files ?? []).compactMap { $0.file?.nameOrPath } + let linkPackages = (frameworkPhases[0].files ?? []).compactMap { $0.product?.productName } + try expect(Set(linkFrameworks + linkPackages)) == expectedLinkedFiles + + // Ensure only dynamic frameworks are embedded (unless there's an explicit override) + let embeddedFrameworks = Set((embedFrameworkPhase?.files ?? []).compactMap { $0.file?.nameOrPath }) + try expect(embeddedFrameworks) == expectedEmbeddedFrameworks + } $0.it("copies files only on install in the Embed Frameworks step") { let app = Target(