diff --git a/CHANGELOG.md b/CHANGELOG.md index 526c9ccb..b46154ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Next Version +#### Added +- Add `onlyCopyFilesOnInstall` option to targets for the Embed Files build phase. [#912](https://github.com/yonaskolb/XcodeGen/pull/912) @jsorge + #### Fixed - Treat all directories with known UTI as file wrapper. [#896](https://github.com/yonaskolb/XcodeGen/pull/896) @KhaosT diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index 908eeebc..cfbe0987 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -239,6 +239,7 @@ Settings are merged in the following order: groups, base, configs. - [ ] **transitivelyLinkDependencies**: **Bool** - If this is not specified the value from the project set in [Options](#options)`.transitivelyLinkDependencies` will be used. - [ ] **directlyEmbedCarthageDependencies**: **Bool** - If this is `true` Carthage dependencies will be embedded using an `Embed Frameworks` build phase instead of the `copy-frameworks` script. Defaults to `true` for all targets except iOS/tvOS/watchOS Applications. - [ ] **requiresObjCLinking**: **Bool** - If this is `true` any targets that link to this target will have `-ObjC` added to their `OTHER_LDFLAGS`. This is required if a static library has any catagories or extensions on Objective-C code. See [this guide](https://pewpewthespells.com/blog/objc_linker_flags.html#objc) for more details. Defaults to `true` if `type` is `library.static`. If you are 100% sure you don't have catagories or extensions on Objective-C code (pure Swift with no use of Foundation/UIKit) you can set this to `false`, otherwise it's best to leave it alone. +- [ ]**onlyCopyFilesOnInstall**: **Bool** – If this is `true`, the `Embed Frameworks` build phase will have the "Copy only when installing" chekbox checked. Defaults to `false`. - [ ] **preBuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *before* any other build phases - [ ] **postCompileScripts**: **[[Build Script](#build-script)]** - Build scripts that run after the Compile Sources phase - [ ] **postBuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *after* any other build phases diff --git a/Sources/ProjectSpec/Target.swift b/Sources/ProjectSpec/Target.swift index 78daceaa..c746129b 100644 --- a/Sources/ProjectSpec/Target.swift +++ b/Sources/ProjectSpec/Target.swift @@ -46,6 +46,7 @@ public struct Target: ProjectTarget { public var deploymentTarget: Version? public var attributes: [String: Any] public var productName: String + public var onlyCopyFilesOnInstall: Bool public var isLegacy: Bool { legacy != nil @@ -83,7 +84,8 @@ public struct Target: ProjectTarget { buildRules: [BuildRule] = [], scheme: TargetScheme? = nil, legacy: LegacyTarget? = nil, - attributes: [String: Any] = [:] + attributes: [String: Any] = [:], + onlyCopyFilesOnInstall: Bool = false ) { self.name = name self.type = type @@ -106,6 +108,7 @@ public struct Target: ProjectTarget { self.scheme = scheme self.legacy = legacy self.attributes = attributes + self.onlyCopyFilesOnInstall = onlyCopyFilesOnInstall } } @@ -309,6 +312,7 @@ extension Target: NamedJSONDictionaryConvertible { scheme = jsonDictionary.json(atKeyPath: "scheme") legacy = jsonDictionary.json(atKeyPath: "legacy") attributes = jsonDictionary.json(atKeyPath: "attributes") ?? [:] + onlyCopyFilesOnInstall = jsonDictionary.json(atKeyPath: "onlyCopyFilesOnInstall") ?? false } } @@ -340,6 +344,10 @@ extension Target: JSONEncodable { dict["productName"] = productName } + if onlyCopyFilesOnInstall { + dict["onlyCopyFilesOnInstall"] = true + } + return dict } } diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index 665eb23c..8049cdc1 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -1076,12 +1076,15 @@ public class PBXProjGenerator { copyFrameworksReferences += getBuildFilesForPhase(.frameworks) if !copyFrameworksReferences.isEmpty { + let copyFilesActionMask: UInt = 8 let copyFilesPhase = addObject( PBXCopyFilesBuildPhase( dstPath: "", dstSubfolderSpec: .frameworks, name: "Embed Frameworks", - files: copyFrameworksReferences + buildActionMask: target.onlyCopyFilesOnInstall ? copyFilesActionMask : PBXBuildPhase.defaultBuildActionMask, + files: copyFrameworksReferences, + runOnlyForDeploymentPostprocessing: target.onlyCopyFilesOnInstall ? true : false ) ) diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj index 0a10a985..7fe07ec4 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -453,7 +453,7 @@ }; FE78CC3322C9C2DB1D64EAAA /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; + buildActionMask = 8; dstPath = ""; dstSubfolderSpec = 10; files = ( @@ -462,7 +462,7 @@ B2D43A31C184E34EF9CB743C /* Framework.framework in Embed Frameworks */, ); name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; + runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ diff --git a/Tests/Fixtures/TestProject/project.yml b/Tests/Fixtures/TestProject/project.yml index 6b30f3fc..d055950e 100644 --- a/Tests/Fixtures/TestProject/project.yml +++ b/Tests/Fixtures/TestProject/project.yml @@ -119,6 +119,7 @@ targets: - sdk: Contacts.framework - bundle: BundleX.bundle - target: AnotherProject/ExternalTarget + onlyCopyFilesOnInstall: true scheme: testTargets: - App_iOS_Tests diff --git a/Tests/ProjectSpecTests/SpecLoadingTests.swift b/Tests/ProjectSpecTests/SpecLoadingTests.swift index a5ef71f8..82e5a182 100644 --- a/Tests/ProjectSpecTests/SpecLoadingTests.swift +++ b/Tests/ProjectSpecTests/SpecLoadingTests.swift @@ -1011,6 +1011,13 @@ class SpecLoadingTests: XCTestCase { try expect(scheme.test) == expectedTest } + $0.it("parses copy files on install") { + var targetSource = validTarget + targetSource["onlyCopyFilesOnInstall"] = true + let target = try Target(name: "Embed Frameworks", jsonDictionary: targetSource) + try expect(target.onlyCopyFilesOnInstall) == true + } + $0.it("parses settings") { let project = try Project(path: fixturePath + "settings_test.yml") let buildSettings: BuildSettings = ["SETTING": "value"] diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index c7fcbba2..b48e2b96 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -772,6 +772,34 @@ class ProjectGeneratorTests: XCTestCase { } } + $0.it("copies files only on install in the Embed Frameworks step") { + let app = Target( + name: "App", + type: .application, + platform: .iOS, + // Embeds it's frameworks, so they shouldn't embed in AppTest + dependencies: [ + Dependency(type: .framework, reference: "FrameworkA.framework"), + Dependency(type: .framework, reference: "FrameworkB.framework", embed: false), + ], + onlyCopyFilesOnInstall: true + ) + + let project = Project(name: "test",targets: [app]) + let pbxProject = try project.generatePbxProj() + let nativeTarget = try unwrap(pbxProject.nativeTargets.first(where: { $0.name == app.name })) + let buildPhases = nativeTarget.buildPhases + + let embedFrameworkPhase = pbxProject + .copyFilesBuildPhases + .filter { buildPhases.contains($0) } + .first { $0.dstSubfolderSpec == .frameworks } + + let phase = try unwrap(embedFrameworkPhase) + try expect(phase.buildActionMask) == 8 + try expect(phase.runOnlyForDeploymentPostprocessing) == true + } + $0.it("sets -ObjC for targets that depend on requiresObjCLinking targets") { let requiresObjCLinking = Target( name: "requiresObjCLinking",