Files
XcodeGen/Sources/ProjectSpec/Target.swift
T
Keith Smiley 08de6077d9 Add option for not linking dependencies
This adds a new attribute to Dependency that allows consumers to choose
to not link a dependency. This is useful for if you have this dependency
tree with static libraries:

App -> A -> Shared
App -> B -> Shared

Where A and B both share a static library dependency, that is finally
linked into App. If Shared is added to the link phase of A and B, you
end up with duplicate symbols during the link phase. With this change
consumers could set link: False on A and B's dependency on Shared, this
way Shared will get build before A and B, but not linked.
2017-10-25 15:55:49 -07:00

268 lines
9.5 KiB
Swift

//
// Target.swift
// XcodeGen
//
// Created by Yonas Kolb on 20/7/17.
//
//
import Foundation
import xcproj
import JSONUtilities
public struct Target {
public var name: String
public var type: PBXProductType
public var platform: Platform
public var settings: Settings
public var sources: [String]
public var dependencies: [Dependency]
public var prebuildScripts: [BuildScript]
public var postbuildScripts: [BuildScript]
public var configFiles: [String: String]
public var scheme: TargetScheme?
public var filename: String {
var name = self.name
if let fileExtension = type.fileExtension {
name += ".\(fileExtension)"
}
return name
}
public init(name: String, type: PBXProductType, platform: Platform, settings: Settings = .empty, configFiles: [String: String] = [:], sources: [String] = [], dependencies: [Dependency] = [], prebuildScripts: [BuildScript] = [], postbuildScripts: [BuildScript] = [], scheme: TargetScheme? = nil) {
self.name = name
self.type = type
self.platform = platform
self.settings = settings
self.configFiles = configFiles
self.sources = sources
self.dependencies = dependencies
self.prebuildScripts = prebuildScripts
self.postbuildScripts = postbuildScripts
self.scheme = scheme
}
}
extension Target: CustomStringConvertible {
public var description: String {
return "\(platform.emoji) \(type): \(name)"
}
}
extension Target {
static func generateCrossPlaformTargets(jsonDictionary: JSONDictionary) throws -> JSONDictionary {
guard let targetsDictionary: [String: JSONDictionary] = jsonDictionary["targets"] as? [String: JSONDictionary] else {
return jsonDictionary
}
let platformReplacement = "$platform"
var crossPlatformTargets: [String: JSONDictionary] = [:]
for (targetName, target) in targetsDictionary {
if let platforms = target["platform"] as? [String] {
for platform in platforms {
var platformTarget = target
func replacePlatform(_ dictionary: JSONDictionary) -> JSONDictionary {
var replaced = dictionary
for (key, value) in dictionary {
switch value {
case let dictionary as JSONDictionary:
replaced[key] = replacePlatform(dictionary)
case let string as String:
replaced[key] = string.replacingOccurrences(of: platformReplacement, with: platform)
case let array as [JSONDictionary]:
replaced[key] = array.map(replacePlatform)
case let array as [String]:
replaced[key] = array.map { $0.replacingOccurrences(of: platformReplacement, with: platform) }
default: break
}
}
return replaced
}
platformTarget = replacePlatform(platformTarget)
platformTarget["platform"] = platform
let platformSuffix = platformTarget["platformSuffix"] as? String ?? "_\(platform)"
let platformPrefix = platformTarget["platformPrefix"] as? String ?? ""
let newTargetName = platformPrefix + targetName + platformSuffix
var settings = platformTarget["settings"] as? JSONDictionary ?? [:]
if settings["configs"] != nil || settings["groups"] != nil || settings["base"] != nil {
var base = settings["base"] as? JSONDictionary ?? [:]
if base["PRODUCT_NAME"] == nil {
base["PRODUCT_NAME"] = targetName
}
settings["base"] = base
} else {
if settings["PRODUCT_NAME"] == nil {
settings["PRODUCT_NAME"] = targetName
}
}
platformTarget["settings"] = settings
crossPlatformTargets[newTargetName] = platformTarget
}
} else {
crossPlatformTargets[targetName] = target
}
}
var merged = jsonDictionary
merged["targets"] = crossPlatformTargets
return merged
}
}
extension Target: Equatable {
public static func ==(lhs: Target, rhs: Target) -> Bool {
return lhs.name == rhs.name &&
lhs.type == rhs.type &&
lhs.platform == rhs.platform &&
lhs.settings == rhs.settings &&
lhs.configFiles == rhs.configFiles &&
lhs.sources == rhs.sources &&
lhs.dependencies == rhs.dependencies &&
lhs.prebuildScripts == rhs.prebuildScripts &&
lhs.postbuildScripts == rhs.postbuildScripts &&
lhs.scheme == rhs.scheme
}
}
public struct TargetScheme {
public let testTargets: [String]
public let configVariants: [String]
public init(testTargets: [String] = [], configVariants: [String] = []) {
self.testTargets = testTargets
self.configVariants = configVariants
}
}
extension TargetScheme: Equatable {
public static func ==(lhs: TargetScheme, rhs: TargetScheme) -> Bool {
return lhs.testTargets == rhs.testTargets &&
lhs.configVariants == rhs.configVariants
}
}
extension TargetScheme: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
testTargets = jsonDictionary.json(atKeyPath: "testTargets") ?? []
configVariants = jsonDictionary.json(atKeyPath: "configVariants") ?? []
}
}
extension Target: NamedJSONDictionaryConvertible {
public init(name: String, jsonDictionary: JSONDictionary) throws {
self.name = jsonDictionary.json(atKeyPath: "name") ?? name
let typeString: String = try jsonDictionary.json(atKeyPath: "type")
if let type = PBXProductType(string: typeString) {
self.type = type
} else {
throw ProjectSpecError.unknownTargetType(typeString)
}
let platformString: String = try jsonDictionary.json(atKeyPath: "platform")
if let platform = Platform(rawValue: platformString) {
self.platform = platform
} else {
throw ProjectSpecError.unknownTargetPlatform(platformString)
}
settings = jsonDictionary.json(atKeyPath: "settings") ?? .empty
configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:]
if let source: String = jsonDictionary.json(atKeyPath: "sources") {
sources = [source]
} else {
sources = jsonDictionary.json(atKeyPath: "sources") ?? []
}
if jsonDictionary["dependencies"] == nil {
dependencies = []
} else {
dependencies = try jsonDictionary.json(atKeyPath: "dependencies", invalidItemBehaviour: .fail)
}
prebuildScripts = jsonDictionary.json(atKeyPath: "prebuildScripts") ?? []
postbuildScripts = jsonDictionary.json(atKeyPath: "postbuildScripts") ?? []
scheme = jsonDictionary.json(atKeyPath: "scheme")
}
}
public struct Dependency: Equatable {
public var type: DependencyType
public var reference: String
public var embed: Bool?
public var codeSign: Bool = true
public var removeHeaders: Bool = true
public var link: Bool = true
public init(type: DependencyType, reference: String, embed: Bool? = nil) {
self.type = type
self.reference = reference
self.embed = embed
}
public enum DependencyType {
case target
case framework
case carthage
}
public static func ==(lhs: Dependency, rhs: Dependency) -> Bool {
return lhs.reference == rhs.reference &&
lhs.type == rhs.type &&
lhs.codeSign == rhs.codeSign &&
lhs.removeHeaders == rhs.removeHeaders &&
lhs.embed == rhs.embed &&
lhs.link == rhs.link
}
public var buildSettings: [String: Any] {
var attributes: [String] = []
if codeSign {
attributes.append("CodeSignOnCopy")
}
if removeHeaders {
attributes.append("RemoveHeadersOnCopy")
}
return ["ATTRIBUTES": attributes]
}
}
extension Dependency: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
if let target: String = jsonDictionary.json(atKeyPath: "target") {
type = .target
reference = target
} else if let framework: String = jsonDictionary.json(atKeyPath: "framework") {
type = .framework
reference = framework
} else if let carthage: String = jsonDictionary.json(atKeyPath: "carthage") {
type = .carthage
reference = carthage
} else {
throw ProjectSpecError.invalidDependency(jsonDictionary)
}
embed = jsonDictionary.json(atKeyPath: "embed")
if let bool: Bool = jsonDictionary.json(atKeyPath: "link") {
link = bool
}
if let bool: Bool = jsonDictionary.json(atKeyPath: "codeSign") {
codeSign = bool
}
if let bool: Bool = jsonDictionary.json(atKeyPath: "removeHeaders") {
removeHeaders = bool
}
}
}