From e4844e927c5862f3546bebc672803f671653ed8c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 16 Sep 2019 00:53:21 +0900 Subject: [PATCH 01/14] Fix default value JSON encoding --- Sources/ProjectSpec/Scheme.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index dcc9d82b..4f859a52 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -370,7 +370,9 @@ extension Scheme.Test.TestTarget: JSONObjectConvertible { extension Scheme.Test.TestTarget: JSONEncodable { public func toJSONValue() -> Any { - if !randomExecutionOrder && !parallelizable { + if randomExecutionOrder == Scheme.Test.TestTarget.randomExecutionOrderDefault, + parallelizable == Scheme.Test.TestTarget.parallelizableDefault, + externalProject == nil { return name } From a2835ef2992e08cebab37fba52b93fbf1a59f26d Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 16 Sep 2019 11:00:32 +0900 Subject: [PATCH 02/14] Add debugEnabled option for run and test --- Sources/ProjectSpec/Scheme.swift | 21 +++++++++++++++++++-- Sources/XcodeGenKit/SchemeGenerator.swift | 2 ++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index f60a26da..cc53b13a 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -69,6 +69,7 @@ public struct Scheme: Equatable { public struct Run: BuildAction { public static let disableMainThreadCheckerDefault = false + public static let debugEnabledDefault = true public var config: String? public var commandLineArguments: [String: Bool] @@ -78,6 +79,7 @@ public struct Scheme: Equatable { public var disableMainThreadChecker: Bool public var language: String? public var region: String? + public var debugEnabled: Bool public init( config: String, @@ -87,7 +89,8 @@ public struct Scheme: Equatable { environmentVariables: [XCScheme.EnvironmentVariable] = [], disableMainThreadChecker: Bool = disableMainThreadCheckerDefault, language: String? = nil, - region: String? = nil + region: String? = nil, + debugEnabled: Bool = debugEnabledDefault ) { self.config = config self.commandLineArguments = commandLineArguments @@ -97,12 +100,14 @@ public struct Scheme: Equatable { self.disableMainThreadChecker = disableMainThreadChecker self.language = language self.region = region + self.debugEnabled = debugEnabled } } public struct Test: BuildAction { public static let gatherCoverageDataDefault = false public static let disableMainThreadCheckerDefault = false + public static let debugEnabledDefault = true public var config: String? public var gatherCoverageData: Bool @@ -114,6 +119,7 @@ public struct Scheme: Equatable { public var environmentVariables: [XCScheme.EnvironmentVariable] public var language: String? public var region: String? + public var debugEnabled: Bool public struct TestTarget: Equatable, ExpressibleByStringLiteral { public static let randomExecutionOrderDefault = false @@ -156,7 +162,8 @@ public struct Scheme: Equatable { postActions: [ExecutionAction] = [], environmentVariables: [XCScheme.EnvironmentVariable] = [], language: String? = nil, - region: String? = nil + region: String? = nil, + debugEnabled: Bool = debugEnabledDefault ) { self.config = config self.gatherCoverageData = gatherCoverageData @@ -168,6 +175,7 @@ public struct Scheme: Equatable { self.environmentVariables = environmentVariables self.language = language self.region = region + self.debugEnabled = debugEnabled } public var shouldUseLaunchSchemeArgsEnv: Bool { @@ -275,6 +283,7 @@ extension Scheme.Run: JSONObjectConvertible { disableMainThreadChecker = jsonDictionary.json(atKeyPath: "disableMainThreadChecker") ?? Scheme.Run.disableMainThreadCheckerDefault language = jsonDictionary.json(atKeyPath: "language") region = jsonDictionary.json(atKeyPath: "region") + debugEnabled = jsonDictionary.json(atKeyPath: "debugEnabled") ?? Scheme.Run.debugEnabledDefault } } @@ -294,6 +303,9 @@ extension Scheme.Run: JSONEncodable { dict["disableMainThreadChecker"] = disableMainThreadChecker } + if debugEnabled != Scheme.Run.debugEnabledDefault { + dict["debugEnabled"] = debugEnabled + } return dict } } @@ -323,6 +335,7 @@ extension Scheme.Test: JSONObjectConvertible { environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary) language = jsonDictionary.json(atKeyPath: "language") region = jsonDictionary.json(atKeyPath: "region") + debugEnabled = jsonDictionary.json(atKeyPath: "debugEnabled") ?? Scheme.Test.debugEnabledDefault } } @@ -347,6 +360,10 @@ extension Scheme.Test: JSONEncodable { dict["disableMainThreadChecker"] = disableMainThreadChecker } + if debugEnabled != Scheme.Run.debugEnabledDefault { + dict["debugEnabled"] = debugEnabled + } + return dict } } diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index 2af0b16e..e1076dae 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -152,6 +152,7 @@ public class SchemeGenerator { testables: testables, preActions: scheme.test?.preActions.map(getExecutionAction) ?? [], postActions: scheme.test?.postActions.map(getExecutionAction) ?? [], + selectedDebuggerIdentifier: (scheme.test?.debugEnabled ?? Scheme.Test.debugEnabledDefault) ? XCScheme.defaultDebugger : "", shouldUseLaunchSchemeArgsEnv: scheme.test?.shouldUseLaunchSchemeArgsEnv ?? true, codeCoverageEnabled: scheme.test?.gatherCoverageData ?? Scheme.Test.gatherCoverageDataDefault, disableMainThreadChecker: scheme.test?.disableMainThreadChecker ?? Scheme.Test.disableMainThreadCheckerDefault, @@ -167,6 +168,7 @@ public class SchemeGenerator { preActions: scheme.run?.preActions.map(getExecutionAction) ?? [], postActions: scheme.run?.postActions.map(getExecutionAction) ?? [], macroExpansion: shouldExecuteOnLaunch ? nil : buildableReference, + selectedDebuggerIdentifier: (scheme.run?.debugEnabled ?? Scheme.Run.debugEnabledDefault) ? XCScheme.defaultDebugger : "", disableMainThreadChecker: scheme.run?.disableMainThreadChecker ?? Scheme.Run.disableMainThreadCheckerDefault, commandlineArguments: launchCommandLineArgs, environmentVariables: launchVariables, From 6b2cd0a9167613012aa1066fe2edbf3f271f09b9 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 16 Sep 2019 11:05:30 +0900 Subject: [PATCH 03/14] Add test case for debugEnabled --- .../SchemeGeneratorTests.swift | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift index ac84c0c1..8bf0d7db 100644 --- a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift @@ -87,6 +87,9 @@ class SchemeGeneratorTests: XCTestCase { try expect(xcscheme.profileAction?.buildConfiguration) == "Release" try expect(xcscheme.analyzeAction?.buildConfiguration) == "Debug" try expect(xcscheme.archiveAction?.buildConfiguration) == "Release" + + try expect(xcscheme.launchAction?.selectedDebuggerIdentifier) == XCScheme.defaultDebugger + try expect(xcscheme.testAction?.selectedDebuggerIdentifier) == XCScheme.defaultDebugger } $0.it("sets environment variables for a scheme") { @@ -174,6 +177,26 @@ class SchemeGeneratorTests: XCTestCase { try expect(xcscheme.profileAction?.environmentVariables) == variables } + $0.it("generate scheme without debugger") { + let scheme = Scheme( + name: "TestScheme", + build: Scheme.Build(targets: [buildTarget]), + run: Scheme.Run(config: "Debug", debugEnabled: false) + ) + let project = Project( + name: "test", + targets: [app, framework], + schemes: [scheme] + ) + let xcodeProject = try project.generateXcodeProject() + + guard let xcscheme = xcodeProject.sharedData?.schemes.first else { + throw failure("Scheme not found") + } + + try expect(xcscheme.launchAction?.selectedDebuggerIdentifier) == "" + } + $0.it("generates pre and post actions for target schemes") { var target = app target.scheme = TargetScheme( From 758e807c612b68e5ba20512905abd864e235a2c5 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 16 Sep 2019 11:14:10 +0900 Subject: [PATCH 04/14] Update ProjectSpec.md --- Docs/ProjectSpec.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index c87cf910..a2801e01 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -687,6 +687,7 @@ The different actions share some properties: - [ ] **disableMainThreadChecker**: **Bool** - `run` and `test` actions can define a boolean that indicates that this scheme should disable the Main Thread Checker. This defaults to false - [ ] **language**: **String** - `run` and `test` actions can define a language that is used for Application Language - [ ] **region**: **String** - `run` and `test` actions can define a language that is used for Application Region +- [ ] **debugEnabled**: **Bool** - `run` and `test` actions can define a whether debugger should be used. ### Execution Action From a550dceb2140c76bd7fae670f462f50eac4938f8 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 16 Sep 2019 11:14:21 +0900 Subject: [PATCH 05/14] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9381cfc..825fd0a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Added `includes` to `sources` for a Target. This follows the same glob-style as `excludes` but functions as a way to only include files that match a specified pattern. Useful if you only want a certain file type, for example specifying `**/*.swift`. [#637](https://github.com/yonaskolb/XcodeGen/pull/637) @bclymer - Support `dylib` SDK. [#650](https://github.com/yonaskolb/XcodeGen/pull/650) - Added `language` and `region` options for `run` and `test` scheme [#654](https://github.com/yonaskolb/XcodeGen/pull/654) +- Added `debugEnabled` option for `run` and `test` scheme [#657](https://github.com/yonaskolb/XcodeGen/pull/657) #### Fixed From fa8c71569a0a34b8618734a535d9721f6fea78b8 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 16 Sep 2019 15:35:24 +0900 Subject: [PATCH 06/14] Check only path to avoid slow equability check --- Sources/XcodeGenKit/SourceGenerator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index 7d99f59b..548d4483 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -244,7 +244,7 @@ class SourceGenerator { if let cachedGroup = groupsByPath[path] { for child in children { // only add the children that aren't already in the cachedGroup - if !cachedGroup.children.contains(child) { + if !cachedGroup.children.contains(where: { $0.path == child.path }) { cachedGroup.children.append(child) } } From 0dc4a8de417084b08f800d63effc134da835f80a Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 21 Sep 2019 20:00:40 +0900 Subject: [PATCH 07/14] Copy cachedGroup.children and write back only once --- Sources/XcodeGenKit/SourceGenerator.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index 548d4483..4b96e9ea 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -242,12 +242,14 @@ class SourceGenerator { let groupReference: PBXGroup if let cachedGroup = groupsByPath[path] { + var cachedGroupChildren = cachedGroup.children for child in children { // only add the children that aren't already in the cachedGroup - if !cachedGroup.children.contains(where: { $0.path == child.path }) { - cachedGroup.children.append(child) + if !cachedGroupChildren.contains(where: { $0.path == child.path }) { + cachedGroupChildren.append(child) } } + cachedGroup.children = cachedGroupChildren groupReference = cachedGroup } else { From e278a706feb7e97f0d1e0c7235705319080d1368 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 21 Sep 2019 20:03:09 +0900 Subject: [PATCH 08/14] Add comment --- Sources/XcodeGenKit/SourceGenerator.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index 4b96e9ea..21e9e980 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -245,6 +245,7 @@ class SourceGenerator { var cachedGroupChildren = cachedGroup.children for child in children { // only add the children that aren't already in the cachedGroup + // Check equality by path because XcodeProj.PBXObject.== is very slow. if !cachedGroupChildren.contains(where: { $0.path == child.path }) { cachedGroupChildren.append(child) } From 2def6138c434f0750d6ba3ba68a587bf4a7d7df3 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 21 Sep 2019 20:22:58 +0900 Subject: [PATCH 09/14] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9381cfc..acd35f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ #### Fixed - Expand template variable in Array of Any [#651](https://github.com/yonaskolb/XcodeGen/pull/651) @kateinoigakukun +- Significantly improve performance when running with a large number files. [#658](https://github.com/yonaskolb/XcodeGen/pull/658) @kateinoigakukun ## 2.7.0 From d3bf42f2ca4ec3d2c1724f5a1cc9c9048e36c8c9 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 22 Sep 2019 19:30:22 +0900 Subject: [PATCH 10/14] Check sourceTree also to ensure they have same root --- Sources/XcodeGenKit/SourceGenerator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index 21e9e980..35e1ab3b 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -245,8 +245,8 @@ class SourceGenerator { var cachedGroupChildren = cachedGroup.children for child in children { // only add the children that aren't already in the cachedGroup - // Check equality by path because XcodeProj.PBXObject.== is very slow. - if !cachedGroupChildren.contains(where: { $0.path == child.path }) { + // Check equality by path and sourceTree because XcodeProj.PBXObject.== is very slow. + if !cachedGroupChildren.contains(where: { $0.path == child.path && $0.sourceTree == child.sourceTree }) { cachedGroupChildren.append(child) } } From 28466593137eb444820e68275527bd70161b749d Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 22 Sep 2019 21:14:15 +0900 Subject: [PATCH 11/14] Set selectedLauncherIdentifier when debug disabled --- Sources/XcodeGenKit/SchemeGenerator.swift | 1 + Tests/XcodeGenKitTests/SchemeGeneratorTests.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index e1076dae..0de02729 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -169,6 +169,7 @@ public class SchemeGenerator { postActions: scheme.run?.postActions.map(getExecutionAction) ?? [], macroExpansion: shouldExecuteOnLaunch ? nil : buildableReference, selectedDebuggerIdentifier: (scheme.run?.debugEnabled ?? Scheme.Run.debugEnabledDefault) ? XCScheme.defaultDebugger : "", + selectedLauncherIdentifier: (scheme.run?.debugEnabled ?? Scheme.Run.debugEnabledDefault) ? XCScheme.defaultLauncher : "Xcode.IDEFoundation.Launcher.PosixSpawn", disableMainThreadChecker: scheme.run?.disableMainThreadChecker ?? Scheme.Run.disableMainThreadCheckerDefault, commandlineArguments: launchCommandLineArgs, environmentVariables: launchVariables, diff --git a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift index 8bf0d7db..be00176f 100644 --- a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift @@ -195,6 +195,7 @@ class SchemeGeneratorTests: XCTestCase { } try expect(xcscheme.launchAction?.selectedDebuggerIdentifier) == "" + try expect(xcscheme.launchAction?.selectedLauncherIdentifier) == "Xcode.IDEFoundation.Launcher.PosixSpawn" } $0.it("generates pre and post actions for target schemes") { From e3ca38a63575f174338d1643fd632ef3be374ddb Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 22 Sep 2019 21:37:31 +0900 Subject: [PATCH 12/14] Update ProjectSpec.md --- Docs/ProjectSpec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index a2801e01..d9749612 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -687,7 +687,7 @@ The different actions share some properties: - [ ] **disableMainThreadChecker**: **Bool** - `run` and `test` actions can define a boolean that indicates that this scheme should disable the Main Thread Checker. This defaults to false - [ ] **language**: **String** - `run` and `test` actions can define a language that is used for Application Language - [ ] **region**: **String** - `run` and `test` actions can define a language that is used for Application Region -- [ ] **debugEnabled**: **Bool** - `run` and `test` actions can define a whether debugger should be used. +- [ ] **debugEnabled**: **Bool** - `run` and `test` actions can define a whether debugger should be used. This defaults to true. ### Execution Action From 30fc642b04eca07ee8339deb76bf583b59ae3e62 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 23 Sep 2019 00:23:11 +0900 Subject: [PATCH 13/14] Introduce ProjectName/Target syntax to reference target --- Sources/ProjectSpec/Project.swift | 45 ++++++++- Sources/ProjectSpec/Scheme.swift | 111 +++++++++++++-------- Sources/ProjectSpec/SpecParsingError.swift | 3 + Sources/ProjectSpec/SpecValidation.swift | 5 +- Sources/ProjectSpec/TargetScheme.swift | 2 +- Sources/XcodeGenKit/SchemeGenerator.swift | 28 +++--- 6 files changed, 138 insertions(+), 56 deletions(-) diff --git a/Sources/ProjectSpec/Project.swift b/Sources/ProjectSpec/Project.swift index adfdf029..ee97e2e6 100644 --- a/Sources/ProjectSpec/Project.swift +++ b/Sources/ProjectSpec/Project.swift @@ -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, ] } } diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index 4f859a52..243bd5da 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -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), diff --git a/Sources/ProjectSpec/SpecParsingError.swift b/Sources/ProjectSpec/SpecParsingError.swift index 5d48d4d0..9c252dca 100644 --- a/Sources/ProjectSpec/SpecParsingError.swift +++ b/Sources/ProjectSpec/SpecParsingError.swift @@ -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)" } diff --git a/Sources/ProjectSpec/SpecValidation.swift b/Sources/ProjectSpec/SpecValidation.swift index 8d081256..eae613a6 100644 --- a/Sources/ProjectSpec/SpecValidation.swift +++ b/Sources/ProjectSpec/SpecValidation.swift @@ -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 { diff --git a/Sources/ProjectSpec/TargetScheme.swift b/Sources/ProjectSpec/TargetScheme.swift index c4c4673e..2bb18622 100644 --- a/Sources/ProjectSpec/TargetScheme.swift +++ b/Sources/ProjectSpec/TargetScheme.swift @@ -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 { diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index 8f214e88..e9f2bddf 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -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, From 5f02e6883bc204d5ffc97883c14e8243702f554f Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 23 Sep 2019 00:23:51 +0900 Subject: [PATCH 14/14] Fix test cases for external project --- Tests/XcodeGenKitTests/ProjectSpecTests.swift | 12 ++++----- .../SchemeGeneratorTests.swift | 12 +++++---- Tests/XcodeGenKitTests/SpecLoadingTests.swift | 25 ++++++++----------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/Tests/XcodeGenKitTests/ProjectSpecTests.swift b/Tests/XcodeGenKitTests/ProjectSpecTests.swift index 362a657e..19daf4d3 100644 --- a/Tests/XcodeGenKitTests/ProjectSpecTests.swift +++ b/Tests/XcodeGenKitTests/ProjectSpecTests.swift @@ -201,7 +201,7 @@ class ProjectSpecTests: XCTestCase { var project = baseProject project.schemes = [Scheme( name: "scheme1", - build: .init(targets: [.init(target: "invalidTarget")]), + build: .init(targets: [.init(target: .init(name: "invalidTarget", location: .local))]), run: .init(config: "debugInvalid"), archive: .init(config: "releaseInvalid") )] @@ -249,7 +249,7 @@ class ProjectSpecTests: XCTestCase { attributes: [:] ) project.aggregateTargets = [aggregatedTarget] - let buildTarget = Scheme.BuildTarget(target: "target1") + let buildTarget = Scheme.BuildTarget(target: .init(name: "target1")) let scheme = Scheme(name: "target1-Scheme", build: Scheme.Build(targets: [buildTarget])) project.schemes = [scheme] try project.validate() @@ -332,7 +332,7 @@ class ProjectSpecTests: XCTestCase { name: nil, outputFiles: ["bar"], outputFilesCompilerFlags: ["foo"])], - scheme: TargetScheme(testTargets: [Scheme.Test.TestTarget(name: "test target", + scheme: TargetScheme(testTargets: [Scheme.Test.TestTarget(targetReference: .init(name: "test target"), randomExecutionOrder: false, parallelizable: false)], configVariants: ["foo"], @@ -370,7 +370,7 @@ class ProjectSpecTests: XCTestCase { shell: "/bin/bash", runOnlyWhenInstalling: true, showEnvVars: false)], - scheme: TargetScheme(testTargets: [Scheme.Test.TestTarget(name: "test target", + scheme: TargetScheme(testTargets: [Scheme.Test.TestTarget(targetReference: .init(name: "test target"), randomExecutionOrder: false, parallelizable: false)], configVariants: ["foo"], @@ -398,7 +398,7 @@ class ProjectSpecTests: XCTestCase { groups: ["config-setting-group"])], groups: ["setting-group"])], schemes: [Scheme(name: "scheme", - build: Scheme.Build(targets: [Scheme.BuildTarget(target: "foo", + build: Scheme.Build(targets: [Scheme.BuildTarget(target: .init(name: "foo"), buildTypes: [.archiving, .analyzing])], parallelizeBuild: false, buildImplicitDependencies: false, @@ -425,7 +425,7 @@ class ProjectSpecTests: XCTestCase { randomExecutionOrder: false, parallelizable: false, commandLineArguments: ["foo": true], - targets: [Scheme.Test.TestTarget(name: "foo", + targets: [Scheme.Test.TestTarget(targetReference: .init(name: "foo"), randomExecutionOrder: false, parallelizable: false)], preActions: [Scheme.ExecutionAction(name: "preAction", diff --git a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift index cf82460a..9d828fd4 100644 --- a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift @@ -37,7 +37,7 @@ class SchemeGeneratorTests: XCTestCase { func testSchemes() { describe { - let buildTarget = Scheme.BuildTarget(target: app.name) + let buildTarget = Scheme.BuildTarget(target: .init(name: app.name, location: .local)) $0.it("generates scheme") { let preAction = Scheme.ExecutionAction(name: "Script", script: "echo Starting", settingsTarget: app.name) let scheme = Scheme( @@ -209,8 +209,9 @@ class SchemeGeneratorTests: XCTestCase { try! writer.writeXcodeProject(xcodeProject) try! writer.writePlists() } - let externalProject = fixturePath + "scheme_test/TestProject.xcodeproj" - let target = Scheme.BuildTarget(target: "ExternalTarget", externalProject: externalProject.string) + let externalProjectPath = fixturePath + "scheme_test/TestProject.xcodeproj" + let externalProject = ExternalProject(name: "ExternalProject", path: externalProjectPath.string) + let target = Scheme.BuildTarget(target: .init(name: "ExternalTarget", location: .project("ExternalProject"))) let scheme = Scheme( name: "ExternalProjectScheme", build: Scheme.Build(targets: [target]) @@ -218,7 +219,8 @@ class SchemeGeneratorTests: XCTestCase { let project = Project( name: "test", targets: [], - schemes: [scheme] + schemes: [scheme], + externalProjects: [externalProject] ) let xcodeProject = try project.generateXcodeProject() guard let xcscheme = xcodeProject.sharedData?.schemes.first else { @@ -227,7 +229,7 @@ class SchemeGeneratorTests: XCTestCase { try expect(xcscheme.buildAction?.buildActionEntries.count) == 1 let buildableReference = xcscheme.buildAction?.buildActionEntries.first?.buildableReference try expect(buildableReference?.blueprintName) == "ExternalTarget" - try expect(buildableReference?.referencedContainer) == "container:\(externalProject.string)" + try expect(buildableReference?.referencedContainer) == "container:\(externalProjectPath.string)" } } diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index ed2f027a..011a5d44 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -748,10 +748,7 @@ class SpecLoadingTests: XCTestCase { "Target4": ["testing": true], "Target5": ["testing": false], "Target6": ["test", "analyze"], - "Target7": [ - "externalProject": "ExternalProject.xcodeproj", - "types": ["run"], - ], + "ExternalProject/Target7": ["run"], ], "preActions": [ [ @@ -766,8 +763,7 @@ class SpecLoadingTests: XCTestCase { "targets": [ "Target1", [ - "name": "Target2", - "externalProject": "ExternalProject.xcodeproj", + "name": "ExternalProject/Target2", "parallelizable": true, "randomExecutionOrder": true, "skippedTests": ["Test/testExample()"], @@ -779,13 +775,13 @@ class SpecLoadingTests: XCTestCase { ] let scheme = try Scheme(name: "Scheme", jsonDictionary: schemeDictionary) let expectedTargets: [Scheme.BuildTarget] = [ - Scheme.BuildTarget(target: "Target1", buildTypes: BuildType.all), - Scheme.BuildTarget(target: "Target2", buildTypes: [.testing, .analyzing]), - Scheme.BuildTarget(target: "Target3", buildTypes: []), - Scheme.BuildTarget(target: "Target4", buildTypes: [.testing]), - Scheme.BuildTarget(target: "Target5", buildTypes: []), - Scheme.BuildTarget(target: "Target6", buildTypes: [.testing, .analyzing]), - Scheme.BuildTarget(target: "Target7", externalProject: "ExternalProject.xcodeproj", buildTypes: [.running]), + Scheme.BuildTarget(target: .init(name: "Target1"), buildTypes: BuildType.all), + Scheme.BuildTarget(target: .init(name: "Target2"), buildTypes: [.testing, .analyzing]), + Scheme.BuildTarget(target: .init(name: "Target3"), buildTypes: []), + Scheme.BuildTarget(target: .init(name: "Target4"), buildTypes: [.testing]), + Scheme.BuildTarget(target: .init(name: "Target5"), buildTypes: []), + Scheme.BuildTarget(target: .init(name: "Target6"), buildTypes: [.testing, .analyzing]), + Scheme.BuildTarget(target: .init(name: "Target7", location: .project("ExternalProject")), buildTypes: [.running]), ] try expect(scheme.name) == "Scheme" try expect(scheme.build.targets) == expectedTargets @@ -803,8 +799,7 @@ class SpecLoadingTests: XCTestCase { targets: [ "Target1", Scheme.Test.TestTarget( - name: "Target2", - externalProject: "ExternalProject.xcodeproj", + targetReference: .init(name: "Target2", location: .project("ExternalProject")), randomExecutionOrder: true, parallelizable: true, skippedTests: ["Test/testExample()"]