mirror of
https://github.com/yonaskolb/XcodeGen.git
synced 2026-03-18 20:02:25 +00:00
Introduce ProjectName/Target syntax to reference target
This commit is contained in:
@@ -28,9 +28,15 @@ public struct Project: BuildSettingsContainer {
|
||||
public var fileGroups: [String]
|
||||
public var configFiles: [String: String]
|
||||
public var include: [String] = []
|
||||
public var externalProjects: [ExternalProject] = [] {
|
||||
didSet {
|
||||
externalProjectsMap = Dictionary(uniqueKeysWithValues: externalProjects.map { ($0.name, $0) })
|
||||
}
|
||||
}
|
||||
|
||||
private var targetsMap: [String: Target]
|
||||
private var aggregateTargetsMap: [String: AggregateTarget]
|
||||
private var externalProjectsMap: [String: ExternalProject]
|
||||
|
||||
public init(
|
||||
basePath: Path = "",
|
||||
@@ -44,7 +50,8 @@ public struct Project: BuildSettingsContainer {
|
||||
options: SpecOptions = SpecOptions(),
|
||||
fileGroups: [String] = [],
|
||||
configFiles: [String: String] = [:],
|
||||
attributes: [String: Any] = [:]
|
||||
attributes: [String: Any] = [:],
|
||||
externalProjects: [ExternalProject] = []
|
||||
) {
|
||||
self.basePath = basePath
|
||||
self.name = name
|
||||
@@ -60,6 +67,12 @@ public struct Project: BuildSettingsContainer {
|
||||
self.fileGroups = fileGroups
|
||||
self.configFiles = configFiles
|
||||
self.attributes = attributes
|
||||
self.externalProjects = externalProjects
|
||||
externalProjectsMap = Dictionary(uniqueKeysWithValues: self.externalProjects.map { ($0.name, $0) })
|
||||
}
|
||||
|
||||
public func getExternalProject(_ projectName: String) -> ExternalProject? {
|
||||
return externalProjectsMap[projectName]
|
||||
}
|
||||
|
||||
public func getTarget(_ targetName: String) -> Target? {
|
||||
@@ -155,6 +168,7 @@ extension Project {
|
||||
configs.map { Config(name: $0, type: ConfigType(rawValue: $1)) }.sorted { $0.name < $1.name }
|
||||
targets = try jsonDictionary.json(atKeyPath: "targets").sorted { $0.name < $1.name }
|
||||
aggregateTargets = try jsonDictionary.json(atKeyPath: "aggregateTargets").sorted { $0.name < $1.name }
|
||||
externalProjects = try jsonDictionary.json(atKeyPath: "externalProjects").sorted { $0.name < $1.name }
|
||||
schemes = try jsonDictionary.json(atKeyPath: "schemes")
|
||||
fileGroups = jsonDictionary.json(atKeyPath: "fileGroups") ?? []
|
||||
configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:]
|
||||
@@ -167,6 +181,7 @@ extension Project {
|
||||
}
|
||||
targetsMap = Dictionary(uniqueKeysWithValues: targets.map { ($0.name, $0) })
|
||||
aggregateTargetsMap = Dictionary(uniqueKeysWithValues: aggregateTargets.map { ($0.name, $0) })
|
||||
externalProjectsMap = Dictionary(uniqueKeysWithValues: externalProjects.map { ($0.name, $0) })
|
||||
}
|
||||
|
||||
static func resolveProject(jsonDictionary: JSONDictionary) throws -> JSONDictionary {
|
||||
@@ -241,6 +256,7 @@ extension Project: JSONEncodable {
|
||||
let configsPairs = configs.map { ($0.name, $0.type?.rawValue) }
|
||||
let aggregateTargetsPairs = aggregateTargets.map { ($0.name, $0.toJSONValue()) }
|
||||
let schemesPairs = schemes.map { ($0.name, $0.toJSONValue()) }
|
||||
let externalProjectsPairs = externalProjects.map { ($0.name, $0.toJSONValue()) }
|
||||
|
||||
return [
|
||||
"name": name,
|
||||
@@ -255,6 +271,33 @@ extension Project: JSONEncodable {
|
||||
"aggregateTargets": Dictionary(uniqueKeysWithValues: aggregateTargetsPairs),
|
||||
"schemes": Dictionary(uniqueKeysWithValues: schemesPairs),
|
||||
"settingGroups": settingGroups.mapValues { $0.toJSONValue() },
|
||||
"externalProjects": externalProjectsPairs,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct ExternalProject {
|
||||
public let name: String
|
||||
public let path: String
|
||||
|
||||
public init(name: String, path: String) {
|
||||
self.name = name
|
||||
self.path = path
|
||||
}
|
||||
}
|
||||
|
||||
extension ExternalProject: NamedJSONDictionaryConvertible {
|
||||
public init(name: String, jsonDictionary: JSONDictionary) throws {
|
||||
self.name = name
|
||||
self.path = try jsonDictionary.json(atKeyPath: "path")
|
||||
}
|
||||
}
|
||||
|
||||
extension ExternalProject: JSONEncodable {
|
||||
public func toJSONValue() -> Any {
|
||||
return [
|
||||
"path": path,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,32 +119,33 @@ public struct Scheme: Equatable {
|
||||
public static let randomExecutionOrderDefault = false
|
||||
public static let parallelizableDefault = false
|
||||
|
||||
public let name: String
|
||||
public var externalProject: String?
|
||||
public var name: String { return targetReference.name }
|
||||
public let targetReference: TargetReference
|
||||
public var randomExecutionOrder: Bool
|
||||
public var parallelizable: Bool
|
||||
public var skippedTests: [String]
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
externalProject: String? = nil,
|
||||
targetReference: TargetReference,
|
||||
randomExecutionOrder: Bool = randomExecutionOrderDefault,
|
||||
parallelizable: Bool = parallelizableDefault,
|
||||
skippedTests: [String] = []
|
||||
) {
|
||||
self.name = name
|
||||
self.externalProject = externalProject
|
||||
self.targetReference = targetReference
|
||||
self.randomExecutionOrder = randomExecutionOrder
|
||||
self.parallelizable = parallelizable
|
||||
self.skippedTests = skippedTests
|
||||
}
|
||||
|
||||
public init(stringLiteral value: String) {
|
||||
name = value
|
||||
externalProject = nil
|
||||
randomExecutionOrder = false
|
||||
parallelizable = false
|
||||
skippedTests = []
|
||||
do {
|
||||
targetReference = try TargetReference(string: value)
|
||||
randomExecutionOrder = false
|
||||
parallelizable = false
|
||||
skippedTests = []
|
||||
} catch {
|
||||
fatalError(SpecParsingError.invalidTargetReference(value).description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,14 +236,56 @@ public struct Scheme: Equatable {
|
||||
}
|
||||
|
||||
public struct BuildTarget: Equatable {
|
||||
public var target: String
|
||||
public var externalProject: String?
|
||||
public var target: TargetReference
|
||||
public var buildTypes: [BuildType]
|
||||
|
||||
public init(target: String, externalProject: String? = nil, buildTypes: [BuildType] = BuildType.all) {
|
||||
public init(target: TargetReference, buildTypes: [BuildType] = BuildType.all) {
|
||||
self.target = target
|
||||
self.buildTypes = buildTypes
|
||||
self.externalProject = externalProject
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct TargetReference: Equatable {
|
||||
public let name: String
|
||||
public let location: Location
|
||||
|
||||
public enum Location: Equatable {
|
||||
case local
|
||||
case project(String)
|
||||
}
|
||||
|
||||
public init(name: String, location: Location = .local) {
|
||||
self.name = name
|
||||
self.location = location
|
||||
}
|
||||
}
|
||||
|
||||
extension TargetReference {
|
||||
public init(string: String) throws {
|
||||
let paths = string.split(separator: "/")
|
||||
guard paths.count <= 2 && !paths.isEmpty else {
|
||||
throw SpecParsingError.invalidTargetReference(string)
|
||||
}
|
||||
switch paths.count {
|
||||
case 2:
|
||||
location = .project(String(paths[0]))
|
||||
name = String(paths[1])
|
||||
case 1:
|
||||
location = .local
|
||||
name = String(paths[0])
|
||||
default: fatalError("unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TargetReference {
|
||||
public func toString() -> String {
|
||||
switch location {
|
||||
case .local: return name
|
||||
case .project(let projectPath):
|
||||
return "\(projectPath)/\(name)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,7 +357,7 @@ extension Scheme.Test: JSONObjectConvertible {
|
||||
if let targets = jsonDictionary["targets"] as? [Any] {
|
||||
self.targets = try targets.compactMap { target in
|
||||
if let string = target as? String {
|
||||
return TestTarget(name: string)
|
||||
return TestTarget(stringLiteral: string)
|
||||
} else if let dictionary = target as? JSONDictionary {
|
||||
return try TestTarget(jsonDictionary: dictionary)
|
||||
} else {
|
||||
@@ -360,8 +403,7 @@ extension Scheme.Test: JSONEncodable {
|
||||
extension Scheme.Test.TestTarget: JSONObjectConvertible {
|
||||
|
||||
public init(jsonDictionary: JSONDictionary) throws {
|
||||
name = try jsonDictionary.json(atKeyPath: "name")
|
||||
externalProject = jsonDictionary.json(atKeyPath: "externalProject")
|
||||
targetReference = try TargetReference(string: jsonDictionary.json(atKeyPath: "name"))
|
||||
randomExecutionOrder = jsonDictionary.json(atKeyPath: "randomExecutionOrder") ?? Scheme.Test.TestTarget.randomExecutionOrderDefault
|
||||
parallelizable = jsonDictionary.json(atKeyPath: "parallelizable") ?? Scheme.Test.TestTarget.parallelizableDefault
|
||||
skippedTests = jsonDictionary.json(atKeyPath: "skippedTests") ?? []
|
||||
@@ -371,13 +413,12 @@ extension Scheme.Test.TestTarget: JSONObjectConvertible {
|
||||
extension Scheme.Test.TestTarget: JSONEncodable {
|
||||
public func toJSONValue() -> Any {
|
||||
if randomExecutionOrder == Scheme.Test.TestTarget.randomExecutionOrderDefault,
|
||||
parallelizable == Scheme.Test.TestTarget.parallelizableDefault,
|
||||
externalProject == nil {
|
||||
return name
|
||||
parallelizable == Scheme.Test.TestTarget.parallelizableDefault {
|
||||
return targetReference.toString()
|
||||
}
|
||||
|
||||
var dict: JSONDictionary = [
|
||||
"name": name,
|
||||
"name": targetReference.toString(),
|
||||
]
|
||||
|
||||
if randomExecutionOrder != Scheme.Test.TestTarget.randomExecutionOrderDefault {
|
||||
@@ -386,9 +427,6 @@ extension Scheme.Test.TestTarget: JSONEncodable {
|
||||
if parallelizable != Scheme.Test.TestTarget.parallelizableDefault {
|
||||
dict["parallelizable"] = parallelizable
|
||||
}
|
||||
if let externalProject = externalProject {
|
||||
dict["externalProject"] = externalProject
|
||||
}
|
||||
|
||||
return dict
|
||||
}
|
||||
@@ -491,10 +529,9 @@ extension Scheme.Build: JSONObjectConvertible {
|
||||
public init(jsonDictionary: JSONDictionary) throws {
|
||||
let targetDictionary: JSONDictionary = try jsonDictionary.json(atKeyPath: "targets")
|
||||
var targets: [Scheme.BuildTarget] = []
|
||||
for (target, possibleBuildTypesOrDict) in targetDictionary {
|
||||
for (targetRepr, possibleBuildTypes) in targetDictionary {
|
||||
let buildTypes: [BuildType]
|
||||
var externalProject: String? = nil
|
||||
if let string = possibleBuildTypesOrDict as? String {
|
||||
if let string = possibleBuildTypes as? String {
|
||||
switch string {
|
||||
case "all": buildTypes = BuildType.all
|
||||
case "none": buildTypes = []
|
||||
@@ -502,23 +539,17 @@ extension Scheme.Build: JSONObjectConvertible {
|
||||
case "indexing": buildTypes = [.testing, .analyzing, .archiving]
|
||||
default: buildTypes = BuildType.all
|
||||
}
|
||||
} else if let enabledDictionary = possibleBuildTypesOrDict as? [String: Bool] {
|
||||
} else if let enabledDictionary = possibleBuildTypes as? [String: Bool] {
|
||||
buildTypes = enabledDictionary.filter { $0.value }.compactMap { BuildType.from(jsonValue: $0.key) }
|
||||
} else if let array = possibleBuildTypesOrDict as? [String] {
|
||||
} else if let array = possibleBuildTypes as? [String] {
|
||||
buildTypes = array.compactMap(BuildType.from)
|
||||
} else if let dict = possibleBuildTypesOrDict as? [String: Any] {
|
||||
if let array = dict["types"] as? [String] {
|
||||
buildTypes = array.compactMap(BuildType.from)
|
||||
} else {
|
||||
buildTypes = BuildType.all
|
||||
}
|
||||
externalProject = dict["externalProject"] as? String
|
||||
} else {
|
||||
buildTypes = BuildType.all
|
||||
}
|
||||
targets.append(Scheme.BuildTarget(target: target, externalProject: externalProject, buildTypes: buildTypes))
|
||||
let target = try TargetReference(string: targetRepr)
|
||||
targets.append(Scheme.BuildTarget(target: target, buildTypes: buildTypes))
|
||||
}
|
||||
self.targets = targets.sorted { $0.target < $1.target }
|
||||
self.targets = targets.sorted { $0.target.name < $1.target.name }
|
||||
preActions = try jsonDictionary.json(atKeyPath: "preActions")?.map(Scheme.ExecutionAction.init) ?? []
|
||||
postActions = try jsonDictionary.json(atKeyPath: "postActions")?.map(Scheme.ExecutionAction.init) ?? []
|
||||
parallelizeBuild = jsonDictionary.json(atKeyPath: "parallelizeBuild") ?? Scheme.Build.parallelizeBuildDefault
|
||||
@@ -528,7 +559,7 @@ extension Scheme.Build: JSONObjectConvertible {
|
||||
|
||||
extension Scheme.Build: JSONEncodable {
|
||||
public func toJSONValue() -> Any {
|
||||
let targetPairs = targets.map { ($0.target, $0.buildTypes.map { $0.toJSONValue() }) }
|
||||
let targetPairs = targets.map { ($0.target.toString(), $0.buildTypes.map { $0.toJSONValue() }) }
|
||||
|
||||
var dict: JSONDictionary = [
|
||||
"targets": Dictionary(uniqueKeysWithValues: targetPairs),
|
||||
|
||||
@@ -5,6 +5,7 @@ public enum SpecParsingError: Error, CustomStringConvertible {
|
||||
case unknownTargetPlatform(String)
|
||||
case invalidDependency([String: Any])
|
||||
case invalidSourceBuildPhase(String)
|
||||
case invalidTargetReference(String)
|
||||
case invalidVersion(String)
|
||||
|
||||
public var description: String {
|
||||
@@ -17,6 +18,8 @@ public enum SpecParsingError: Error, CustomStringConvertible {
|
||||
return "Unknown Target dependency: \(dependency)"
|
||||
case let .invalidSourceBuildPhase(error):
|
||||
return "Invalid Source Build Phase: \(error)"
|
||||
case let .invalidTargetReference(targetReference):
|
||||
return "Invalid Target Reference Syntax: \(targetReference)"
|
||||
case let .invalidVersion(version):
|
||||
return "Invalid version: \(version)"
|
||||
}
|
||||
|
||||
@@ -170,8 +170,9 @@ extension Project {
|
||||
|
||||
for scheme in schemes {
|
||||
for buildTarget in scheme.build.targets {
|
||||
if getProjectTarget(buildTarget.target) == nil && buildTarget.externalProject == nil {
|
||||
errors.append(.invalidSchemeTarget(scheme: scheme.name, target: buildTarget.target))
|
||||
guard buildTarget.target.location == .local else { continue }
|
||||
if getProjectTarget(buildTarget.target.name) == nil {
|
||||
errors.append(.invalidSchemeTarget(scheme: scheme.name, target: buildTarget.target.name))
|
||||
}
|
||||
}
|
||||
if let action = scheme.run, let config = action.config, getConfig(config) == nil {
|
||||
|
||||
@@ -42,7 +42,7 @@ extension TargetScheme: JSONObjectConvertible {
|
||||
if let targets = jsonDictionary["testTargets"] as? [Any] {
|
||||
testTargets = try targets.compactMap { target in
|
||||
if let string = target as? String {
|
||||
return .init(name: string)
|
||||
return .init(stringLiteral: string)
|
||||
} else if let dictionary = target as? JSONDictionary {
|
||||
return try .init(jsonDictionary: dictionary)
|
||||
} else {
|
||||
|
||||
@@ -77,32 +77,36 @@ public class SchemeGenerator {
|
||||
|
||||
func getBuildEntry(_ buildTarget: Scheme.BuildTarget) throws -> XCScheme.BuildAction.Entry {
|
||||
let pbxProj: PBXProj
|
||||
let projectFilename: String
|
||||
if let externalProject = buildTarget.externalProject {
|
||||
pbxProj = try XcodeProj(pathString: externalProject).pbxproj
|
||||
projectFilename = externalProject
|
||||
} else {
|
||||
let projectFilePath: String
|
||||
switch buildTarget.target.location {
|
||||
case .project(let project):
|
||||
guard let externalProject = self.project.getExternalProject(project) else {
|
||||
fatalError("Unable to find external project named \"\(project)\" in project.yml")
|
||||
}
|
||||
pbxProj = try XcodeProj(pathString: externalProject.path).pbxproj
|
||||
projectFilePath = externalProject.path
|
||||
case .local:
|
||||
pbxProj = self.pbxProj
|
||||
projectFilename = "\(self.project.name).xcodeproj"
|
||||
projectFilePath = "\(self.project.name).xcodeproj"
|
||||
}
|
||||
|
||||
guard let pbxTarget = pbxProj.targets(named: buildTarget.target).first else {
|
||||
guard let pbxTarget = pbxProj.targets(named: buildTarget.target.name).first else {
|
||||
fatalError("Unable to find target named \"\(buildTarget.target)\" in \"PBXProj.targets\"")
|
||||
}
|
||||
|
||||
let buildableName = pbxTarget.productNameWithExtension() ?? pbxTarget.name
|
||||
let buildableReference = XCScheme.BuildableReference(
|
||||
referencedContainer: "container:\(projectFilename)",
|
||||
referencedContainer: "container:\(projectFilePath)",
|
||||
blueprint: pbxTarget,
|
||||
buildableName: buildableName,
|
||||
blueprintName: buildTarget.target
|
||||
blueprintName: buildTarget.target.name
|
||||
)
|
||||
return XCScheme.BuildAction.Entry(buildableReference: buildableReference, buildFor: buildTarget.buildTypes)
|
||||
}
|
||||
|
||||
let testTargets = scheme.test?.targets ?? []
|
||||
let testBuildTargets = testTargets.map {
|
||||
Scheme.BuildTarget(target: $0.name, externalProject: $0.externalProject, buildTypes: BuildType.testOnly)
|
||||
Scheme.BuildTarget(target: TargetReference(name: $0.name, location: .local), buildTypes: BuildType.testOnly)
|
||||
}
|
||||
|
||||
let testBuildTargetEntries = try testBuildTargets.map(getBuildEntry)
|
||||
@@ -119,7 +123,7 @@ public class SchemeGenerator {
|
||||
return XCScheme.ExecutionAction(scriptText: action.script, title: action.name, environmentBuildable: environmentBuildable)
|
||||
}
|
||||
|
||||
let target = project.getTarget(scheme.build.targets.first!.target)
|
||||
let target = project.getTarget(scheme.build.targets.first!.target.name)
|
||||
let shouldExecuteOnLaunch = target?.type.isExecutable == true
|
||||
|
||||
let buildableReference = buildActionEntries.first!.buildableReference
|
||||
@@ -217,7 +221,7 @@ extension Scheme {
|
||||
public init(name: String, target: Target, targetScheme: TargetScheme, debugConfig: String, releaseConfig: String) {
|
||||
self.init(
|
||||
name: name,
|
||||
build: .init(targets: [Scheme.BuildTarget(target: target.name)]),
|
||||
build: .init(targets: [Scheme.BuildTarget(target: TargetReference(name: target.name, location: .local))]),
|
||||
run: .init(
|
||||
config: debugConfig,
|
||||
commandLineArguments: targetScheme.commandLineArguments,
|
||||
|
||||
Reference in New Issue
Block a user