diff --git a/CHANGELOG.md b/CHANGELOG.md index 61deea73..d5c1a46c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Added support for DocC Catalogs [#1091](https://github.com/yonaskolb/XcodeGen/pull/1091) @brevansio - Added support for "driver-extension" and "system-extension" product types [#1092](https://github.com/yonaskolb/XcodeGen/issues/1092) @vgorloff +- Add support for conditionally linking dependencies for specific platforms [#1087](https://github.com/yonaskolb/XcodeGen/pull/1087) @daltonclaybrook + +### Changed +- **Breaking**: Rename the `platform` field on `Dependency` to `platformFilter` [#1087](https://github.com/yonaskolb/XcodeGen/pull/1087) @daltonclaybrook ## 2.23.1 diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index b13ae18a..4d57e3f3 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -430,7 +430,8 @@ A dependency can be one of a 6 types: - [ ] **codeSign**: **Bool** - Whether the `codeSignOnCopy` setting is applied when embedding framework. Defaults to true - [ ] **removeHeaders**: **Bool** - Whether the `removeHeadersOnCopy` setting is applied when embedding the framework. Defaults to true - [ ] **weak**: **Bool** - Whether the `Weak` setting is applied when linking the framework. Defaults to false -- [ ] **platform**: **String** - Add dependency to selected platforms. Available platforms are: **iOS**, **macOS** and **all**. Defaults is **all** +- [ ] **platformFilter**: **String** - This field is specific to Mac Catalyst. It corresponds to the "Platforms" dropdown in the Frameworks & Libraries section of Target settings in Xcode. Available options are: **iOS**, **macOS** and **all**. Defaults is **all** +- [ ] **platforms**: **[[Platform](#platform)]** - List of platforms this dependency should apply to. Defaults to all applicable platforms. **Implicit Framework options**: diff --git a/Sources/ProjectSpec/Dependency.swift b/Sources/ProjectSpec/Dependency.swift index 726e1e1a..795d0ed6 100644 --- a/Sources/ProjectSpec/Dependency.swift +++ b/Sources/ProjectSpec/Dependency.swift @@ -5,7 +5,7 @@ public struct Dependency: Equatable { public static let removeHeadersDefault = true public static let implicitDefault = false public static let weakLinkDefault = false - public static let platformDefault: Platform = .all + public static let platformFilterDefault: PlatformFilter = .all public var type: DependencyType public var reference: String @@ -15,7 +15,8 @@ public struct Dependency: Equatable { public var link: Bool? public var implicit: Bool = implicitDefault public var weakLink: Bool = weakLinkDefault - public var platform: Platform = platformDefault + public var platformFilter: PlatformFilter = platformFilterDefault + public var platforms: Set? public init( type: DependencyType, @@ -25,7 +26,8 @@ public struct Dependency: Equatable { link: Bool? = nil, implicit: Bool = implicitDefault, weakLink: Bool = weakLinkDefault, - platform: Platform = platformDefault + platformFilter: PlatformFilter = platformFilterDefault, + platforms: Set? = nil ) { self.type = type self.reference = reference @@ -34,10 +36,11 @@ public struct Dependency: Equatable { self.link = link self.implicit = implicit self.weakLink = weakLink - self.platform = platform + self.platformFilter = platformFilter + self.platforms = platforms } - public enum Platform: String, Equatable { + public enum PlatformFilter: String, Equatable { case all case iOS case macOS @@ -123,10 +126,14 @@ extension Dependency: JSONObjectConvertible { weakLink = bool } - if let platformString: String = jsonDictionary.json(atKeyPath: "platform"), let platform = Platform(rawValue: platformString) { - self.platform = platform + if let platformFilterString: String = jsonDictionary.json(atKeyPath: "platformFilter"), let platformFilter = PlatformFilter(rawValue: platformFilterString) { + self.platformFilter = platformFilter } else { - self.platform = .all + self.platformFilter = .all + } + + if let platforms: [ProjectSpec.Platform] = jsonDictionary.json(atKeyPath: "platforms") { + self.platforms = Set(platforms) } } } @@ -137,6 +144,7 @@ extension Dependency: JSONEncodable { "embed": embed, "codeSign": codeSign, "link": link, + "platforms": platforms?.map(\.rawValue).sorted() ] if removeHeaders != Dependency.removeHeadersDefault { diff --git a/Sources/ProjectSpec/Target.swift b/Sources/ProjectSpec/Target.swift index 851aa4ef..60752f44 100644 --- a/Sources/ProjectSpec/Target.swift +++ b/Sources/ProjectSpec/Target.swift @@ -301,7 +301,12 @@ extension Target: NamedJSONDictionaryConvertible { if jsonDictionary["dependencies"] == nil { dependencies = [] } else { - dependencies = try jsonDictionary.json(atKeyPath: "dependencies", invalidItemBehaviour: .fail) + let dependencies: [Dependency] = try jsonDictionary.json(atKeyPath: "dependencies", invalidItemBehaviour: .fail) + self.dependencies = dependencies.filter { [platform] dependency -> Bool in + // If unspecified, all platforms are supported + guard let platforms = dependency.platforms else { return true } + return platforms.contains(platform) + } } if jsonDictionary["info"] != nil { diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index cc12d2f6..3b4d4b18 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -751,7 +751,7 @@ public class PBXProjGenerator { for dependency in targetDependencies { let embed = dependency.embed ?? target.shouldEmbedDependencies - let platform = makePlatform(for: dependency.platform) + let platform = makePlatformFilter(for: dependency.platformFilter) switch dependency.type { case .target: @@ -1320,8 +1320,8 @@ public class PBXProjGenerator { } } - private func makePlatform(for platform: Dependency.Platform) -> String? { - switch platform { + private func makePlatformFilter(for filter: Dependency.PlatformFilter) -> String? { + switch filter { case .all: return nil case .macOS: diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj index 4e2f4355..0e634a03 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -71,6 +71,7 @@ 47D1F439B8E6D287B3F3E8D1 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A58A16491CDDF968B0D56DE /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; }; 47FC57B04A3AD83359F433EA /* StaticLibrary_ObjC.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5A2B916A11DCC2565241359F /* StaticLibrary_ObjC.h */; }; 49A4B8937BB5520B36EA33F0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 814D72C2B921F60B759C2D4B /* Main.storyboard */; }; + 4C1504A05321046B3ED7A839 /* Framework2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB055761199DF36DB0C629A6 /* Framework2.framework */; }; 4CB673A7C0C11E04F8544BDB /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDB2B6A77D39CD5602F2125F /* Contacts.framework */; }; 4DA7140FF84DBF39961F3409 /* NetworkSystemExtension.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 2049B6DD2AFE85F9DC9F3EB3 /* NetworkSystemExtension.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4F6481557E2BEF8D749C37E3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 187E665975BB5611AF0F27E1 /* main.m */; }; @@ -125,6 +126,7 @@ A1588BF3BFFE1DF7409CBA10 /* Framework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A9274BE42A03DC5DA1FAD04 /* Framework.framework */; }; A1AEAAB53EAEDA1C307871FA /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB178D03E75929F3F5B10C56 /* Result.framework */; }; A59B3F08914812573AFF6C2D /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FD4A16C7B8FEB7F97F3CBE3F /* libz.dylib */; }; + A7438C77A05D83E7016CF044 /* Framework2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0DC40025AB59B688E758829 /* Framework2.framework */; }; A7D1A9942302569A9515696A /* Result.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D296BB7355994040E197A1EE /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A90C4C147AD175DB9F7B5114 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CD22B8CD2E91BB97CC534E /* main.swift */; }; A949422315536EACDF8DD78A /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B785B1161553A7DD6DA4255 /* NetworkExtension.framework */; }; @@ -223,6 +225,13 @@ remoteGlobalIDString = 0867B0DACEF28C11442DE8F7; remoteInfo = App_iOS; }; + 45907115465077029040BF29 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8B9A14DC280CCE013CC86440; + remoteInfo = Framework2_tvOS; + }; 469D922BE967C6D52ED84552 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */; @@ -244,6 +253,13 @@ remoteGlobalIDString = 13E8C5AB873CEE21E18E552F; remoteInfo = StaticLibrary_ObjC_iOS; }; + 59BFAC272F73B46E97B74426 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6ED01BC471A8C3642258E178; + remoteInfo = Framework2_watchOS; + }; 610412261F48A0A36C32FC5C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0FBAE303E3CFC2ABAC876A77 /* Project object */; @@ -800,6 +816,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4C1504A05321046B3ED7A839 /* Framework2.framework in Frameworks */, A1AEAAB53EAEDA1C307871FA /* Result.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -808,6 +825,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A7438C77A05D83E7016CF044 /* Framework2.framework in Frameworks */, 9DF5931DAD58C35B830A0A75 /* Result.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1716,6 +1734,7 @@ buildRules = ( ); dependencies = ( + D6C733BEB62EAA62CCC68556 /* PBXTargetDependency */, CE96B0951433713033A03DCD /* PBXTargetDependency */, ); name = Framework_tvOS; @@ -1805,6 +1824,7 @@ buildRules = ( ); dependencies = ( + 0C99705018337CE91AA34CBA /* PBXTargetDependency */, 35DF16CA4A1F88140CF69620 /* PBXTargetDependency */, ); name = Framework_watchOS; @@ -2783,6 +2803,11 @@ target = 1C26A6A0BC446191F311D470 /* iMessageExtension */; targetProxy = C8FD369800D87311EC532712 /* PBXContainerItemProxy */; }; + 0C99705018337CE91AA34CBA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6ED01BC471A8C3642258E178 /* Framework2_watchOS */; + targetProxy = 59BFAC272F73B46E97B74426 /* PBXContainerItemProxy */; + }; 0D33D01C71E8002A07F02122 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 208179651927D1138D19B5AD /* App_watchOS */; @@ -2909,6 +2934,11 @@ target = 0867B0DACEF28C11442DE8F7 /* App_iOS */; targetProxy = 3A81C6D6875469889D53A2C5 /* PBXContainerItemProxy */; }; + D6C733BEB62EAA62CCC68556 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8B9A14DC280CCE013CC86440 /* Framework2_tvOS */; + targetProxy = 45907115465077029040BF29 /* PBXContainerItemProxy */; + }; E84285243DE0BB361A708079 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = AE3F93DB94E7208F2F1D9A78 /* Framework_iOS */; diff --git a/Tests/Fixtures/TestProject/project.yml b/Tests/Fixtures/TestProject/project.yml index 784d14da..10bbabff 100644 --- a/Tests/Fixtures/TestProject/project.yml +++ b/Tests/Fixtures/TestProject/project.yml @@ -119,16 +119,16 @@ targets: PRODUCT_BUNDLE_IDENTIFIER: com.project.app dependencies: - target: Framework_iOS - platform: all + platformFilter: all - target: StaticLibrary_ObjC_iOS - carthage: Result - platform: macOS + platformFilter: macOS - carthage: SwiftyJSON linkType: static - platform: iOS + platformFilter: iOS - target: Framework2_iOS weak: true - platform: iOS + platformFilter: iOS - target: App_watchOS - target: iMessageApp - sdk: Contacts.framework @@ -284,6 +284,8 @@ targets: dependencies: - carthage: Result - target: StaticLibrary_ObjC_${platform} + - target: Framework2_${platform} + platforms: [tvOS, watchOS] Framework2: type: framework diff --git a/Tests/ProjectSpecTests/SpecLoadingTests.swift b/Tests/ProjectSpecTests/SpecLoadingTests.swift index d302e8b3..2282a8fa 100644 --- a/Tests/ProjectSpecTests/SpecLoadingTests.swift +++ b/Tests/ProjectSpecTests/SpecLoadingTests.swift @@ -376,9 +376,9 @@ class SpecLoadingTests: XCTestCase { $0.it("parses target dependencies") { var targetDictionary = validTarget targetDictionary["dependencies"] = [ - ["target": "name", "embed": false, "platform": "all"], - ["target": "project/name", "embed": false, "platform": "macOS"], - ["carthage": "name", "findFrameworks": true, "platform": "iOS"], + ["target": "name", "embed": false, "platformFilter": "all"], + ["target": "project/name", "embed": false, "platformFilter": "macOS"], + ["carthage": "name", "findFrameworks": true, "platformFilter": "iOS"], ["carthage": "name", "findFrameworks": true, "linkType": "static"], ["framework": "path", "weak": true], ["sdk": "Contacts.framework"], @@ -386,16 +386,19 @@ class SpecLoadingTests: XCTestCase { "sdk": "Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework", "root": "DEVELOPER_DIR", ], + ["target": "conditionalMatch", "platforms": ["iOS"]], + ["target": "conditionalMiss", "platforms": ["watchOS"]], ] let target = try Target(name: "test", jsonDictionary: targetDictionary) - try expect(target.dependencies.count) == 7 - try expect(target.dependencies[0]) == Dependency(type: .target, reference: "name", embed: false, platform: .all) - try expect(target.dependencies[1]) == Dependency(type: .target, reference: "project/name", embed: false, platform: .macOS) - try expect(target.dependencies[2]) == Dependency(type: .carthage(findFrameworks: true, linkType: .dynamic), reference: "name", platform: .iOS) + try expect(target.dependencies.count) == 8 + try expect(target.dependencies[0]) == Dependency(type: .target, reference: "name", embed: false, platformFilter: .all) + try expect(target.dependencies[1]) == Dependency(type: .target, reference: "project/name", embed: false, platformFilter: .macOS) + try expect(target.dependencies[2]) == Dependency(type: .carthage(findFrameworks: true, linkType: .dynamic), reference: "name", platformFilter: .iOS) try expect(target.dependencies[3]) == Dependency(type: .carthage(findFrameworks: true, linkType: .static), reference: "name") try expect(target.dependencies[4]) == Dependency(type: .framework, reference: "path", weakLink: true) try expect(target.dependencies[5]) == Dependency(type: .sdk(root: nil), reference: "Contacts.framework") try expect(target.dependencies[6]) == Dependency(type: .sdk(root: "DEVELOPER_DIR"), reference: "Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework") + try expect(target.dependencies[7]) == Dependency(type: .target, reference: "conditionalMatch", platforms: [.iOS]) } $0.it("parses info plist") { diff --git a/Tests/XcodeGenKitTests/PBXProjGeneratorTests.swift b/Tests/XcodeGenKitTests/PBXProjGeneratorTests.swift index 93280d7b..359b6ce5 100644 --- a/Tests/XcodeGenKitTests/PBXProjGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/PBXProjGeneratorTests.swift @@ -354,9 +354,9 @@ class PBXProjGeneratorTests: XCTestCase { let target1 = Target(name: "TestAll", type: .application, platform: .iOS, sources: ["Sources"]) let target2 = Target(name: "TestiOS", type: .application, platform: .iOS, sources: ["Sources"]) let target3 = Target(name: "TestmacOS", type: .application, platform: .iOS, sources: ["Sources"]) - let dependency1 = Dependency(type: .target, reference: "TestAll", platform: .all) - let dependency2 = Dependency(type: .target, reference: "TestiOS", platform: .iOS) - let dependency3 = Dependency(type: .target, reference: "TestmacOS", platform: .macOS) + let dependency1 = Dependency(type: .target, reference: "TestAll", platformFilter: .all) + let dependency2 = Dependency(type: .target, reference: "TestiOS", platformFilter: .iOS) + let dependency3 = Dependency(type: .target, reference: "TestmacOS", platformFilter: .macOS) let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"], dependencies: [dependency1, dependency2, dependency3]) let project = Project(basePath: directoryPath, name: "Test", targets: [target, target1, target2, target3])