Add requiresObjCLinking to Target

Allows a Target to indicate that any target that links to it needs to set `-ObjC` in `OTHER_LDFLAGS`.
This commit is contained in:
Brentley Jones 2018-07-20 13:28:11 -05:00
parent d6b4566b3c
commit 15af4fd1d9
7 changed files with 136 additions and 0 deletions

View File

@ -4,6 +4,7 @@
#### Added
- Added `showEnvVars` to build scripts to disable printing the environment [351](https://github.com/yonaskolb/XcodeGen/pull/351) @keith
- Added `requiresObjCLinking` to `target` [354](https://github.com/yonaskolb/XcodeGen/pull/353) @brentleyjones
#### Fixed
- Sort files using localizedStandardCompare [341](https://github.com/yonaskolb/XcodeGen/pull/341) @rohitpal440

View File

@ -164,9 +164,11 @@ Settings are merged in the following order: groups, base, configs.
- [ ] **settings**: **[Settings](#settings)** - Target specific build settings. Default platform and product type settings will be applied first before any custom settings defined here. Other context dependant settings will be set automatically as well:
- `INFOPLIST_FILE`: If it doesn't exist your sources will be searched for `Info.plist` files and the first one found will be used for this setting
- `FRAMEWORK_SEARCH_PATHS`: If carthage dependencies are used, the platform build path will be added to this setting
- `OTHER_LDFLAGS`: See `requiresObjCLinking` below
- [ ] **dependencies**: **[[Dependency](#dependency)]** - Dependencies for the target
- [ ] **templates**: **[String]** - A list of target templates that will be merged in order
- [ ] **transitivelyLinkDependencies**: **Bool** - If this is not specified the value from the project set in [Options](#options)`.transitivelyLinkDependencies` will be used.
- [ ] **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.
- [ ] **prebuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *before* any other build phases
- [ ] **postbuildScripts**: **[[Build Script](#build-script)]** - Build scripts that run *after* any other build phases
- [ ] **buildRules**: **[[Build Rule](#build-rule)]** - Custom build rules

View File

@ -29,6 +29,7 @@ public struct Target: ProjectTarget {
public var sources: [TargetSource]
public var dependencies: [Dependency]
public var transitivelyLinkDependencies: Bool?
public var requiresObjCLinking: Bool?
public var prebuildScripts: [BuildScript]
public var postbuildScripts: [BuildScript]
public var buildRules: [BuildRule]
@ -61,6 +62,7 @@ public struct Target: ProjectTarget {
sources: [TargetSource] = [],
dependencies: [Dependency] = [],
transitivelyLinkDependencies: Bool? = nil,
requiresObjCLinking: Bool? = nil,
prebuildScripts: [BuildScript] = [],
postbuildScripts: [BuildScript] = [],
buildRules: [BuildRule] = [],
@ -77,6 +79,7 @@ public struct Target: ProjectTarget {
self.sources = sources
self.dependencies = dependencies
self.transitivelyLinkDependencies = transitivelyLinkDependencies
self.requiresObjCLinking = requiresObjCLinking
self.prebuildScripts = prebuildScripts
self.postbuildScripts = postbuildScripts
self.buildRules = buildRules
@ -196,6 +199,7 @@ extension Target: Equatable {
lhs.platform == rhs.platform &&
lhs.deploymentTarget == rhs.deploymentTarget &&
lhs.transitivelyLinkDependencies == rhs.transitivelyLinkDependencies &&
lhs.requiresObjCLinking == rhs.requiresObjCLinking &&
lhs.settings == rhs.settings &&
lhs.configFiles == rhs.configFiles &&
lhs.sources == rhs.sources &&
@ -268,6 +272,7 @@ extension Target: NamedJSONDictionaryConvertible {
dependencies = try jsonDictionary.json(atKeyPath: "dependencies", invalidItemBehaviour: .fail)
}
transitivelyLinkDependencies = jsonDictionary.json(atKeyPath: "transitivelyLinkDependencies")
requiresObjCLinking = jsonDictionary.json(atKeyPath: "requiresObjCLinking")
prebuildScripts = jsonDictionary.json(atKeyPath: "prebuildScripts") ?? []
postbuildScripts = jsonDictionary.json(atKeyPath: "postbuildScripts") ?? []

View File

@ -418,6 +418,7 @@ public class PBXProjGenerator {
var plistPath: Path?
var searchForPlist = true
var anyDependencyRequiresObjCLinking = false
var dependencies: [String] = []
var targetFrameworkBuildFiles: [String] = []
@ -464,6 +465,11 @@ public class PBXProjGenerator {
PBXBuildFile(fileRef: dependencyBuildFile.object.fileRef!)
)
targetFrameworkBuildFiles.append(buildFile.reference)
if !anyDependencyRequiresObjCLinking
&& dependencyTarget.requiresObjCLinking ?? (dependencyTarget.type == .staticLibrary) {
anyDependencyRequiresObjCLinking = true
}
}
let embed = dependency.embed ?? (!dependencyTarget.type.isLibrary && (target.type.isApp
@ -722,6 +728,20 @@ public class PBXProjGenerator {
}
}
// objc linkage
if anyDependencyRequiresObjCLinking {
let otherLinkingFlags = "OTHER_LDFLAGS"
let objCLinking = "-ObjC"
if var array = buildSettings[otherLinkingFlags] as? [String] {
array.append(objCLinking)
buildSettings[otherLinkingFlags] = array
} else if let string = buildSettings[otherLinkingFlags] as? String {
buildSettings[otherLinkingFlags] = [string, objCLinking]
} else {
buildSettings[otherLinkingFlags] = ["$(inherited)", objCLinking]
}
}
// set Carthage search paths
if !carthageDependencies.isEmpty {
let frameworkSearchPaths = "FRAMEWORK_SEARCH_PATHS"

View File

@ -1442,6 +1442,10 @@
);
INFOPLIST_FILE = App_iOS/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
);
PRODUCT_BUNDLE_IDENTIFIER = com.project.app;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -1652,6 +1656,10 @@
);
INFOPLIST_FILE = App_iOS/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
);
PRODUCT_BUNDLE_IDENTIFIER = com.project.app;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -2083,6 +2091,10 @@
);
INFOPLIST_FILE = App_iOS/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
);
PRODUCT_BUNDLE_IDENTIFIER = com.project.app;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -2232,6 +2244,10 @@
);
INFOPLIST_FILE = App_iOS/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
);
PRODUCT_BUNDLE_IDENTIFIER = com.project.app;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -2795,6 +2811,10 @@
);
INFOPLIST_FILE = App_iOS/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
);
PRODUCT_BUNDLE_IDENTIFIER = com.project.app;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -2953,6 +2973,10 @@
);
INFOPLIST_FILE = App_iOS/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
);
PRODUCT_BUNDLE_IDENTIFIER = com.project.app;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";

View File

@ -51,6 +51,7 @@ targets:
PRODUCT_BUNDLE_IDENTIFIER: com.project.app
dependencies:
- target: Framework_iOS
- target: StaticLibrary_ObjC
- carthage: Alamofire
- target: App_watchOS
- target: iMessageApp

View File

@ -562,6 +562,89 @@ class ProjectGeneratorTests: XCTestCase {
}
}
}
$0.it("sets -ObjC for targets that depend on requiresObjCLinking targets") {
let requiresObjCLinking = Target(
name: "requiresObjCLinking",
type: .staticLibrary,
platform: .iOS,
dependencies: [],
requiresObjCLinking: true
)
let doesntRequireObjCLinking = Target(
name: "doesntRequireObjCLinking",
type: .staticLibrary,
platform: .iOS,
dependencies: [],
requiresObjCLinking: false
)
let implicitlyRequiresObjCLinking = Target(
name: "implicitlyRequiresObjCLinking",
type: .staticLibrary,
platform: .iOS,
sources: [TargetSource(path: "StaticLibrary_ObjC/StaticLibrary_ObjC.m")],
dependencies: []
)
let framework = Target(
name: "framework",
type: .framework,
platform: .iOS,
dependencies: [Dependency(type: .target, reference: requiresObjCLinking.name, link: false)]
)
let app1 = Target(
name: "app1",
type: .application,
platform: .iOS,
dependencies: [Dependency(type: .target, reference: requiresObjCLinking.name)]
)
let app2 = Target(
name: "app2",
type: .application,
platform: .iOS,
dependencies: [Dependency(type: .target, reference: doesntRequireObjCLinking.name)]
)
let app3 = Target(
name: "app3",
type: .application,
platform: .iOS,
dependencies: [Dependency(type: .target, reference: implicitlyRequiresObjCLinking.name)]
)
let targets = [requiresObjCLinking, doesntRequireObjCLinking, implicitlyRequiresObjCLinking, framework, app1, app2, app3]
let project = Project(
basePath: fixturePath + "TestProject",
name: "test",
targets: targets,
options: SpecOptions()
)
let pbxProj = try project.generatePbxProj()
func buildSettings(for target: Target) throws -> BuildSettings {
guard let nativeTarget = pbxProj.objects.targets(named: target.name).first?.object,
let buildConfigList = nativeTarget.buildConfigurationList,
let buildConfigs = pbxProj.objects.configurationLists.getReference(buildConfigList),
let buildConfigReference = buildConfigs.buildConfigurations.first,
let buildConfig = pbxProj.objects.buildConfigurations.getReference(buildConfigReference) else {
throw failure("XCBuildConfiguration not found for Target \(target.name.quoted)")
}
return buildConfig.buildSettings
}
let frameworkOtherLinkerSettings = try buildSettings(for: framework)["OTHER_LDFLAGS"] as? [String] ?? []
let app1OtherLinkerSettings = try buildSettings(for: app1)["OTHER_LDFLAGS"] as? [String] ?? []
let app2OtherLinkerSettings = try buildSettings(for: app2)["OTHER_LDFLAGS"] as? [String] ?? []
let app3OtherLinkerSettings = try buildSettings(for: app3)["OTHER_LDFLAGS"] as? [String] ?? []
try expect(frameworkOtherLinkerSettings.contains("-ObjC")) == false
try expect(app1OtherLinkerSettings.contains("-ObjC")) == true
try expect(app2OtherLinkerSettings.contains("-ObjC")) == false
try expect(app3OtherLinkerSettings.contains("-ObjC")) == true
}
$0.it("generates run scripts") {
var scriptSpec = project