diff --git a/CHANGELOG.md b/CHANGELOG.md index 606cc3d5..577bfb6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added `weak` linking setting for dependencies [#411](https://github.com/yonaskolb/XcodeGen/pull/411) @alvarhansen - Added `info` to targets for generating an `Info.plist` [#415](https://github.com/yonaskolb/XcodeGen/pull/415) @yonaskolb - Added `entitlements` to targets for generating an `.entitlement` file [#415](https://github.com/yonaskolb/XcodeGen/pull/415) @yonaskolb +- Added `sdk` dependency type for linking system frameworks within the SDK [#430](https://github.com/yonaskolb/XcodeGen/pull/430) @yonaskolb - Added `parallelizable` and `randomExecutionOrder` to `Scheme` test targets in an expanded form [#434](https://github.com/yonaskolb/XcodeGen/pull/434) @yonaskolb - Validate incorrect config setting definitions [#431](https://github.com/yonaskolb/XcodeGen/pull/431) @yonaskolb - Automatically set project `SDKROOT` if there is only a single platform within the project [#433](https://github.com/yonaskolb/XcodeGen/pull/433) @yonaskolb diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index 2cc75280..05bf8233 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -329,10 +329,9 @@ A dependency can be one of a 3 types: - `target: name` - links to another target - `framework: path` - links to a framework - `carthage: name` - helper for linking to a Carthage framework +- `sdk: name` - links to a dependency with the SDK. This can either be a relative path within the sdk root or a single filename that references a framework (.framework) or lib (.tbd) -**Embed options**: - -These only applied to `target` and `framework` dependencies. +**Linking options**: - [ ] **embed**: **Bool** - Whether to embed the dependency. Defaults to true for application target and false for non application targets. - [ ] **link**: **Bool** - Whether to link the dependency. Defaults to `true` depending on the type of the dependency and the type of the target (e.g. static libraries will only link to executables by default). @@ -363,6 +362,8 @@ targets: - target: MyFramework - framework: path/to/framework.framework - carthage: Result + - sdk: Contacts.framework + - sdk: libc++.tbd MyFramework: type: framework ``` diff --git a/Sources/ProjectSpec/Dependency.swift b/Sources/ProjectSpec/Dependency.swift index 1e01b5f5..722aae65 100644 --- a/Sources/ProjectSpec/Dependency.swift +++ b/Sources/ProjectSpec/Dependency.swift @@ -34,6 +34,7 @@ public struct Dependency: Equatable { case target case framework case carthage + case sdk } } @@ -49,6 +50,9 @@ extension Dependency: JSONObjectConvertible { } else if let carthage: String = jsonDictionary.json(atKeyPath: "carthage") { type = .carthage reference = carthage + } else if let sdk: String = jsonDictionary.json(atKeyPath: "sdk") { + type = .sdk + reference = sdk } else { throw SpecParsingError.invalidDependency(jsonDictionary) } diff --git a/Sources/ProjectSpec/SpecValidation.swift b/Sources/ProjectSpec/SpecValidation.swift index 793e4783..b45e7e4a 100644 --- a/Sources/ProjectSpec/SpecValidation.swift +++ b/Sources/ProjectSpec/SpecValidation.swift @@ -138,9 +138,25 @@ extension Project { for target in targets { for dependency in target.dependencies { - if dependency.type == .target, getProjectTarget(dependency.reference) == nil { - errors.append(.invalidTargetDependency(target: target.name, dependency: dependency.reference)) + switch dependency.type { + case .target: + if getProjectTarget(dependency.reference) == nil { + errors.append(.invalidTargetDependency(target: target.name, dependency: dependency.reference)) + } + case .sdk: + let path = Path(dependency.reference) + if !dependency.reference.contains("/") { + switch path.extension { + case "framework"?, + "tbd"?: + break + default: + errors.append(.invalidSDKDependency(target: target.name, dependency: dependency.reference)) + } + } + default: break } + } for source in target.sources { diff --git a/Sources/ProjectSpec/SpecValidationError.swift b/Sources/ProjectSpec/SpecValidationError.swift index 53e579eb..6c0acb7f 100644 --- a/Sources/ProjectSpec/SpecValidationError.swift +++ b/Sources/ProjectSpec/SpecValidationError.swift @@ -6,6 +6,7 @@ public struct SpecValidationError: Error, CustomStringConvertible { public enum ValidationError: Error, CustomStringConvertible { case invalidXcodeGenVersion(minimumVersion: Version, version: Version) + case invalidSDKDependency(target: String, dependency: String) case invalidTargetDependency(target: String, dependency: String) case invalidTargetSource(target: String, source: String) case invalidTargetConfigFile(target: String, configFile: String, config: String) @@ -27,6 +28,8 @@ public struct SpecValidationError: Error, CustomStringConvertible { switch self { case let .invalidXcodeGenVersion(minimumVersion, version): return "XcodeGen version is \(version), but minimum required version specified as \(minimumVersion)" + case let .invalidSDKDependency(target, dependency): + return "Target \(target.quoted) has invalid sdk dependency: \(dependency.quoted). It must be a full path or have the following extensions: .framework, .dylib, .tbd" case let .invalidTargetDependency(target, dependency): return "Target \(target.quoted) has invalid dependency: \(dependency.quoted)" case let .invalidTargetConfigFile(target, configFile, config): diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index 653e345b..fd622929 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -14,6 +14,7 @@ public class PBXProjGenerator { var targetObjects: [String: PBXTarget] = [:] var targetAggregateObjects: [String: PBXAggregateTarget] = [:] var targetFileReferences: [String: PBXFileReference] = [:] + var sdkFileReferences: [String: PBXFileReference] = [:] var carthageFrameworksByPlatform: [String: Set] = [:] var frameworkFiles: [PBXFileElement] = [] @@ -522,6 +523,38 @@ public class PBXProjGenerator { let buildPath = Path(dependency.reference).parent().string.quoted frameworkBuildPaths.insert(buildPath) + case .sdk: + + var dependencyPath = Path(dependency.reference) + if !dependency.reference.contains("/") { + switch dependencyPath.extension ?? "" { + case "framework": + dependencyPath = Path("System/Library/Frameworks") + dependencyPath + case "tbd": + dependencyPath = Path("usr/lib") + dependencyPath + default: break + } + } + + let fileReference: PBXFileReference + if let existingFileReferences = sdkFileReferences[dependency.reference] { + fileReference = existingFileReferences + } else { + fileReference = addObject( + PBXFileReference(sourceTree: .sdkRoot, + name: dependencyPath.lastComponent, + lastKnownFileType: Xcode.fileType(path: dependencyPath), + path: dependencyPath.string) + ) + sdkFileReferences[dependency.reference] = fileReference + frameworkFiles.append(fileReference) + } + + let buildFile = addObject( + PBXBuildFile(file: fileReference, + settings: getDependencyFrameworkSettings(dependency: dependency)) + ) + targetFrameworkBuildFiles.append(buildFile) case .carthage: guard target.type != .staticLibrary else { break } @@ -943,6 +976,8 @@ public class PBXProjGenerator { // don't want a dependency if it's going to be embedded or statically linked in a non-top level target // in .target check we filter out targets that will embed all of their dependencies switch dependency.type { + case .sdk: + dependencies[dependency.reference] = dependency case .framework, .carthage: if isTopLevel || dependency.embed == nil { dependencies[dependency.reference] = dependency diff --git a/Tests/Fixtures/TestProject/App_iOS/ViewController.swift b/Tests/Fixtures/TestProject/App_iOS/ViewController.swift index 4cdc9940..9a273598 100644 --- a/Tests/Fixtures/TestProject/App_iOS/ViewController.swift +++ b/Tests/Fixtures/TestProject/App_iOS/ViewController.swift @@ -1,10 +1,11 @@ import UIKit +import Contacts class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. + _ = CNContact() } override func didReceiveMemoryWarning() { diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj index 2369e4ff..b853737a 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ BF_47A75C8A7EF15658238E254C846C5C6B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_30676EBEE9BE54AA26CAE69BE744CAE8 /* Main.storyboard */; }; BF_47FBEFEA08B9642C1F711EDA77FC8C89 /* LocalizedStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = VG_FE6D89DA2D7E7F58340D566590FF221C /* LocalizedStoryboard.storyboard */; }; BF_47FF83A37355E90F93C0F5B2CFBCE317 /* Standalone.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_82E3C6C060C4487B5177509917C3FAE3 /* Standalone.swift */; }; + BF_486398284252AEE9001DD72E5E710D93 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_6A3D8067EBC18CB321826EB21FEBD094 /* libc++.tbd */; }; BF_4D56F3F4D081A77C11325B96DD34D8E1 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_261C31660333EF514356EFCBDB368EAB /* FrameworkFile.swift */; }; BF_4EBBAD70FA73DDC89BD933866B90DD08 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = FR_A957DAE2193BE1E970F452BFEFF3EBF6 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; }; BF_51D370314B5DA8E002A908021E459F50 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FR_A55A35F549FD72775C37ED05342812AA /* Assets.xcassets */; }; @@ -70,6 +71,7 @@ BF_90F0E9253F2768C312B65530931CD55A /* Empty.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_DBD29CA78CDBBEF69B2C39C6D23BBDA1 /* Empty.h */; }; BF_910C2D6055BD22643F042545CD21AA78 /* StaticLibrary_ObjC.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_D0BE69522DB875ADB041E9135E0767CA /* StaticLibrary_ObjC.h */; }; BF_93A4E1A93C7DB4289E526466D547E55E /* SomeFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_2F56FD7A1F7782467AC9F315B6133468 /* SomeFramework.framework */; }; + BF_9743BCF98E3EF021AB384652DA126416 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_9A09F93850576AF57455BE58FD53C42F /* Contacts.framework */; }; BF_9830FFD35765AACD86D1B619E22830D6 /* Headers in Headers */ = {isa = PBXBuildFile; fileRef = FR_3DD4DE355C21662A9169EE41C44F73E3 /* Headers */; settings = {ATTRIBUTES = (Public, ); }; }; BF_98DEE7FD578AA439550E0023A2ECE8D0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_25BDF725104C5E280D45CBF33F54C72C /* ViewController.swift */; }; BF_9A74AED05E2F61530BFA0F7D23AF0112 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = VG_EB676136B9F946373D4796800CC00AD4 /* Model.xcdatamodeld */; settings = {COMPILER_FLAGS = "-Werror"; }; }; @@ -91,6 +93,7 @@ BF_CD062A97959629BD672893FDAE89A1E9 /* NotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_BB49B398F9291781A60DA963A0BF168C /* NotificationController.swift */; }; BF_D0676C98017B6FDD96A733CB851645DE /* StaticLibrary_ObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = FR_C2A280C4FA602E6610BCFB820602B69E /* StaticLibrary_ObjC.m */; }; BF_D0D1D142403C75D11757CB7092E8D035 /* XPC Service.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = FR_1FA381C639CE4923ED6845790A5DF9D2 /* XPC Service.xpc */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + BF_D2532BE2FA7D664875AD6E29A22269C0 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FR_9A09F93850576AF57455BE58FD53C42F /* Contacts.framework */; }; BF_D3D64E2595369BBDEF03E07543AE2779 /* FrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR_261C31660333EF514356EFCBDB368EAB /* FrameworkFile.swift */; }; BF_D6588CD7B83034816FFD3A7DB10DAD78 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FR_16B791CFA2095097A17CC216977DF6EF /* Assets.xcassets */; }; BF_D7E271A6820E0A908736F44F99341DE1 /* Framework.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FR_EFD283107EDF836BF0D9F4EB3F9A0016 /* Framework.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -440,6 +443,7 @@ FR_640ADF15D2B92FDC35556B2E0934C21C /* App_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App_macOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; FR_654475F320EF1630562C841CA8B6938A /* Framework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FR_6643E0FE8DD9AAC71691A739070C6833 /* Model 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 3.xcdatamodel"; sourceTree = ""; }; + FR_6A3D8067EBC18CB321826EB21FEBD094 /* libc++.tbd */ = {isa = PBXFileReference; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; FR_752FB5DFFBC490CFB9742549A0C48527 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; FR_7532DD7B78451A5040048474AC4FBCCC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; FR_7C782E8FBF41DE8BB1E3789DF2C8C1F7 /* PushNotificationPayload.apns */ = {isa = PBXFileReference; lastKnownFileType = text; path = PushNotificationPayload.apns; sourceTree = ""; }; @@ -451,6 +455,7 @@ FR_8B770F475242D91FC20289A3B35CD165 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Result.framework; sourceTree = ""; }; FR_96127F4D9D804B89024AB846F0961621 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.module; path = module.modulemap; sourceTree = ""; }; FR_98BB4C8D33EB0E666C136ACD08B21EB3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + FR_9A09F93850576AF57455BE58FD53C42F /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; }; FR_9B4B00A3CDADD50167B2393562AEBAB2 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.module; path = module.modulemap; sourceTree = ""; }; FR_A0867B127ACF3ED382EB2FD5133D3EA8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; FR_A0DA89632C14F8DF99F15196E6EBB7D5 /* Framework2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework2.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -495,6 +500,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BF_D2532BE2FA7D664875AD6E29A22269C0 /* Contacts.framework in Frameworks */, BF_F8D73622DA7CFF30DB9AD17C08C63655 /* Framework2.framework in Frameworks */, BF_0378AD9857D61219363F74B2FA308B5A /* Framework.framework in Frameworks */, BF_B8E824A58BFE0B81E16BB26087FDC8B4 /* Result.framework in Frameworks */, @@ -515,9 +521,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BF_9743BCF98E3EF021AB384652DA126416 /* Contacts.framework in Frameworks */, BF_6E2D4086A22C12D516A06AE66DC48D16 /* Framework.framework in Frameworks */, BF_230439786C4C6849F488A5FADC6A42A5 /* Result.framework in Frameworks */, BF_36BCFE51A59D5EAE19684491C9F5427F /* StaticLibrary_ObjC.a in Frameworks */, + BF_486398284252AEE9001DD72E5E710D93 /* libc++.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -658,6 +666,8 @@ isa = PBXGroup; children = ( G_A1CE8BFBEAC6AFDC64D7068C3CE11421 /* Carthage */, + FR_9A09F93850576AF57455BE58FD53C42F /* Contacts.framework */, + FR_6A3D8067EBC18CB321826EB21FEBD094 /* libc++.tbd */, FR_2F56FD7A1F7782467AC9F315B6133468 /* SomeFramework.framework */, ); name = Frameworks; diff --git a/Tests/Fixtures/TestProject/project.yml b/Tests/Fixtures/TestProject/project.yml index 3b96d522..0f6111cc 100644 --- a/Tests/Fixtures/TestProject/project.yml +++ b/Tests/Fixtures/TestProject/project.yml @@ -43,6 +43,8 @@ targets: dependencies: - target: Framework_macOS - target: XPC Service + - sdk: Contacts.framework + - sdk: libc++.tbd App_iOS: type: application @@ -90,6 +92,7 @@ targets: - target: App_watchOS - target: iMessageApp - framework: Vendor/SomeFramework.framework + - sdk: Contacts.framework scheme: testTargets: - App_iOS_Tests diff --git a/Tests/XcodeGenKitTests/ProjectSpecTests.swift b/Tests/XcodeGenKitTests/ProjectSpecTests.swift index 21f0670f..7074d5f9 100644 --- a/Tests/XcodeGenKitTests/ProjectSpecTests.swift +++ b/Tests/XcodeGenKitTests/ProjectSpecTests.swift @@ -174,6 +174,20 @@ class ProjectSpecTests: XCTestCase { try expectValidationError(project, .invalidTargetSchemeConfigVariant(target: "target1", configVariant: "invalidVariant", configType: .debug)) } + $0.it("fails with invalid sdk dependency") { + var project = baseProject + project.targets = [Target( + name: "target1", + type: .application, + platform: .iOS, + dependencies: [Dependency(type: .sdk, reference: "invalidDependency")] + ) + ] + + try expectValidationError(project, .invalidSDKDependency(target: "target1", dependency: "invalidDependency")) + } + + $0.it("fails with invalid scheme") { var project = baseProject project.schemes = [Scheme( diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index 59eb720c..b19b77bc 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -147,12 +147,14 @@ class SpecLoadingTests: XCTestCase { ["target": "name", "embed": false], ["carthage": "name"], ["framework": "path", "weak": true], + ["sdk": "Contacts.framework"], ] let target = try Target(name: "test", jsonDictionary: targetDictionary) - try expect(target.dependencies.count) == 3 + try expect(target.dependencies.count) == 4 try expect(target.dependencies[0]) == Dependency(type: .target, reference: "name", embed: false) try expect(target.dependencies[1]) == Dependency(type: .carthage, reference: "name") try expect(target.dependencies[2]) == Dependency(type: .framework, reference: "path", weakLink: true) + try expect(target.dependencies[3]) == Dependency(type: .sdk, reference: "Contacts.framework") } $0.it("parses info plist") {