Introduce ProjectName/Target syntax to reference target

This commit is contained in:
Yuta Saito
2019-09-23 00:23:11 +09:00
parent e4844e927c
commit 30fc642b04
6 changed files with 138 additions and 56 deletions
+44 -1
View File
@@ -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,
]
}
}
+71 -40
View File
@@ -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)"
}
+3 -2
View File
@@ -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 {
+1 -1
View File
@@ -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 {
+16 -12
View File
@@ -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,