From d04986e0e34baaa64033a4ca1a05be2af820096f Mon Sep 17 00:00:00 2001 From: Jonathan Crooke Date: Sat, 24 Dec 2022 11:51:27 +0000 Subject: [PATCH] Permit scheme build stage to be optional --- Sources/ProjectSpec/Scheme.swift | 8 +-- Sources/ProjectSpec/SpecValidation.swift | 8 +-- Sources/XcodeGenKit/SchemeGenerator.swift | 54 +++++++++++-------- Tests/Fixtures/test_only.yml | 12 +++++ Tests/ProjectSpecTests/SpecLoadingTests.swift | 9 ++++ 5 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 Tests/Fixtures/test_only.yml diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift index 1b166871..900dc9c7 100644 --- a/Sources/ProjectSpec/Scheme.swift +++ b/Sources/ProjectSpec/Scheme.swift @@ -8,7 +8,7 @@ public typealias BuildType = XCScheme.BuildAction.Entry.BuildFor public struct Scheme: Equatable { public var name: String - public var build: Build + public var build: Build! public var run: Run? public var archive: Archive? public var analyze: Analyze? @@ -17,7 +17,7 @@ public struct Scheme: Equatable { public init( name: String, - build: Build, + build: Build? = nil, run: Run? = nil, test: Test? = nil, profile: Profile? = nil, @@ -712,7 +712,7 @@ extension Scheme: NamedJSONDictionaryConvertible { public init(name: String, jsonDictionary: JSONDictionary) throws { self.name = name - build = try jsonDictionary.json(atKeyPath: "build") + build = jsonDictionary.json(atKeyPath: "build") run = jsonDictionary.json(atKeyPath: "run") test = jsonDictionary.json(atKeyPath: "test") analyze = jsonDictionary.json(atKeyPath: "analyze") @@ -724,7 +724,7 @@ extension Scheme: NamedJSONDictionaryConvertible { extension Scheme: JSONEncodable { public func toJSONValue() -> Any { [ - "build": build.toJSONValue(), + "build": build?.toJSONValue(), "run": run?.toJSONValue(), "test": test?.toJSONValue(), "analyze": analyze?.toJSONValue(), diff --git a/Sources/ProjectSpec/SpecValidation.swift b/Sources/ProjectSpec/SpecValidation.swift index 376c0153..1314f515 100644 --- a/Sources/ProjectSpec/SpecValidation.swift +++ b/Sources/ProjectSpec/SpecValidation.swift @@ -182,9 +182,11 @@ extension Project { } for scheme in schemes { - errors.append( - contentsOf: scheme.build.targets.compactMap { validationError(for: $0.target, in: scheme, action: "build") } - ) + if let action = scheme.build { + errors.append(contentsOf: action.targets.compactMap { + validationError(for: $0.target, in: scheme, action: "build") + }) + } if let action = scheme.run, let config = action.config, getConfig(config) == nil { errors.append(.invalidSchemeConfig(scheme: scheme.name, config: config)) } diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index fb069c75..4c50e7d4 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -167,7 +167,7 @@ public class SchemeGenerator { let testBuildTargetEntries = try testBuildTargets.map(getBuildEntry) - let buildActionEntries: [XCScheme.BuildAction.Entry] = try scheme.build.targets.map(getBuildEntry) + let buildActionEntries: [XCScheme.BuildAction.Entry] = try scheme.build?.targets.map(getBuildEntry) ?? [] func getExecutionAction(_ action: Scheme.ExecutionAction) -> XCScheme.ExecutionAction { // ExecutionActions can require the use of build settings. Xcode allows the settings to come from a build or test target. @@ -183,27 +183,42 @@ public class SchemeGenerator { if let targetName = scheme.run?.executable { schemeTarget = project.getTarget(targetName) - } else { - guard let firstTarget = scheme.build.targets.first else { - throw SchemeGenerationError.missingBuildTargets(scheme.name) - } - let name = scheme.build.targets.first { $0.buildTypes.contains(.running) }?.target.name ?? firstTarget.target.name + } else if + let targets = scheme.build?.targets, + let firstTarget = targets.first + { + let name = targets.first { $0.buildTypes.contains(.running) }?.target.name ?? firstTarget.target.name schemeTarget = target ?? project.getTarget(name) + } else { + schemeTarget = nil } let shouldExecuteOnLaunch = schemeTarget?.shouldExecuteOnLaunch == true + let buildableReference: XCScheme.BuildableReference? + let buildAction: XCScheme.BuildAction? + let runnables: (launch: XCScheme.Runnable, profile: XCScheme.BuildableProductRunnable)? - let buildableReference = buildActionEntries.first(where: { $0.buildableReference.blueprintName == schemeTarget?.name })?.buildableReference ?? buildActionEntries.first!.buildableReference - let runnables = makeProductRunnables(for: schemeTarget, buildableReference: buildableReference) + if let buildableReferenceCandidate = buildActionEntries.first(where: { $0.buildableReference.blueprintName == schemeTarget?.name })?.buildableReference ?? buildActionEntries.first?.buildableReference + { + runnables = makeProductRunnables(for: schemeTarget, buildableReference: buildableReferenceCandidate) + + buildableReference = buildableReferenceCandidate + buildAction = scheme.build.flatMap { build in + XCScheme.BuildAction( + buildActionEntries: buildActionEntries, + preActions: build.preActions.map(getExecutionAction), + postActions: build.postActions.map(getExecutionAction), + parallelizeBuild: build.parallelizeBuild, + buildImplicitDependencies: build.buildImplicitDependencies, + runPostActionsOnFailure: build.runPostActionsOnFailure + ) + } + } else { + buildableReference = nil + buildAction = nil + runnables = nil + } - let buildAction = XCScheme.BuildAction( - buildActionEntries: buildActionEntries, - preActions: scheme.build.preActions.map(getExecutionAction), - postActions: scheme.build.postActions.map(getExecutionAction), - parallelizeBuild: scheme.build.parallelizeBuild, - buildImplicitDependencies: scheme.build.buildImplicitDependencies, - runPostActionsOnFailure: scheme.build.runPostActionsOnFailure - ) let testables: [XCScheme.TestableReference] = zip(testTargets, testBuildTargetEntries).map { testTarget, testBuildEntries in @@ -298,7 +313,7 @@ public class SchemeGenerator { } let launchAction = XCScheme.LaunchAction( - runnable: shouldExecuteOnLaunch ? runnables.launch : nil, + runnable: shouldExecuteOnLaunch ? runnables?.launch : nil, buildConfiguration: scheme.run?.config ?? defaultDebugConfig.name, preActions: scheme.run?.preActions.map(getExecutionAction) ?? [], postActions: scheme.run?.postActions.map(getExecutionAction) ?? [], @@ -321,7 +336,7 @@ public class SchemeGenerator { ) let profileAction = XCScheme.ProfileAction( - buildableProductRunnable: shouldExecuteOnLaunch ? runnables.profile : nil, + buildableProductRunnable: shouldExecuteOnLaunch ? runnables?.profile : nil, buildConfiguration: scheme.profile?.config ?? defaultReleaseConfig.name, preActions: scheme.profile?.preActions.map(getExecutionAction) ?? [], postActions: scheme.profile?.postActions.map(getExecutionAction) ?? [], @@ -403,7 +418,6 @@ enum SchemeGenerationError: Error, CustomStringConvertible { case missingTarget(TargetReference, projectPath: String) case missingPackage(String) case missingProject(String) - case missingBuildTargets(String) var description: String { switch self { @@ -411,8 +425,6 @@ enum SchemeGenerationError: Error, CustomStringConvertible { return "Unable to find target named \"\(target)\" in \"\(projectPath)\"" case .missingProject(let project): return "Unable to find project reference named \"\(project)\" in project.yml" - case .missingBuildTargets(let name): - return "Unable to find at least one build target in scheme \"\(name)\"" case .missingPackage(let package): return "Unable to find swift package named \"\(package)\" in project.yml" } diff --git a/Tests/Fixtures/test_only.yml b/Tests/Fixtures/test_only.yml new file mode 100644 index 00000000..a369bc27 --- /dev/null +++ b/Tests/Fixtures/test_only.yml @@ -0,0 +1,12 @@ +name: TestOnly +targets: + Target: + type: application + platform: iOS + sources: + - Target +schemes: + TestOnly: + test: + targets: + - Target diff --git a/Tests/ProjectSpecTests/SpecLoadingTests.swift b/Tests/ProjectSpecTests/SpecLoadingTests.swift index 5f7354ef..4c0f66a1 100644 --- a/Tests/ProjectSpecTests/SpecLoadingTests.swift +++ b/Tests/ProjectSpecTests/SpecLoadingTests.swift @@ -75,6 +75,15 @@ class SpecLoadingTests: XCTestCase { ] } + $0.it("accepts schemes that have no build phase") { + let path = fixturePath + "test_only.yml" + let project = try loadSpec(path: path) + try expect(project.name) == "TestOnly" + try expect(project.schemes.count) == 1 + try expect(project.schemes.first?.build).to.beNil() + try expect(project.schemes.first?.test?.targets.first?.name) == "Target" + } + $0.it("expands directories") { let path = fixturePath + "paths_test.yml" let project = try loadSpec(path: path)