Make Scheme Generation Methods More Generic (#730)

Addresses https://github.com/tuist/tuist/issues/667

### Short description 📝

In order to support custom workspace schemes, we need to make the current scheme generation functions more generic and decouple it from the `Project` type. We now make graph look ups in the project scheme generation functions so that in a follow up pull request it will be easy to add custom workspace scheme support.

*Note that this pull request should make no user facing changes.* 

I've pasted the steps listed in #667 below and marked the items completed by this pull request:
- [X] Changing the functions in the SchemeGenerator to use the graph for look ups by default (as this is the main difference between the project and workspace schemes)
- [X] Update the scheme model (in TuistGenerator) to start referencing targets using the .project(...)
- [ ] Expose workspace scheme type in the project manifest (ProjectDescription)

### Solution 📦

I've added a new `TargetReference` type in place of simple target names of type `String`. `TargetReference` has the name of a target and the relative path to its project. This `TargetReference` type works well for both project scheme generation and workspace scheme generation (in the follow up pull request). This new type enables functions in the scheme generator to do graph lookups to any target in the graph. 

### Implementation 👩‍💻👨‍💻

- [X] Add `TargetReference` type to model
- [X] Change instances of simple target `String` types to `TargetReference` within the `Scheme` type in the model
- [X] Update `GeneratorModelLoader` to convert simple target name `String`s into `TargetReference` types by propagating the project path
- [X] Update instances where a simple target `String` type is expected to work with the name property in `TargetReference`
- [X] Update tests in `SchemesGeneratorTests`, `ProjectGeneratorTests`, `GeneratorModelLoaderTests`
This commit is contained in:
Adam Khazi 2019-12-03 12:37:57 +00:00 committed by GitHub
parent b0290e7711
commit 37e44f5a09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 940 additions and 671 deletions

View File

@ -20,6 +20,8 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
- Support for paths relative to root https://github.com/tuist/tuist/pull/727 by @pepibumur. - Support for paths relative to root https://github.com/tuist/tuist/pull/727 by @pepibumur.
- Replace `Sheme.testAction.targets` type from `String` to `TestableTarget` is a description of target that adds to the `TestAction`, you can specify execution tests parallelizable, random execution order or skip tests https://github.com/tuist/tuist/pull/728 by @rowwingman. - Replace `Sheme.testAction.targets` type from `String` to `TestableTarget` is a description of target that adds to the `TestAction`, you can specify execution tests parallelizable, random execution order or skip tests https://github.com/tuist/tuist/pull/728 by @rowwingman.
- Galaxy manifest model https://github.com/tuist/tuist/pull/729 by @pepibumur. - Galaxy manifest model https://github.com/tuist/tuist/pull/729 by @pepibumur.
- Make scheme generation methods more generic by @adamkhazi @kwridan.
## 0.19.0 ## 0.19.0

View File

@ -101,6 +101,7 @@ public protocol Graphing: AnyObject, Encodable {
func librariesSearchPaths(path: AbsolutePath, name: String) -> [AbsolutePath] func librariesSearchPaths(path: AbsolutePath, name: String) -> [AbsolutePath]
func librariesSwiftIncludePaths(path: AbsolutePath, name: String) -> [AbsolutePath] func librariesSwiftIncludePaths(path: AbsolutePath, name: String) -> [AbsolutePath]
func embeddableFrameworks(path: AbsolutePath, name: String) throws -> [DependencyReference] func embeddableFrameworks(path: AbsolutePath, name: String) throws -> [DependencyReference]
func target(path: AbsolutePath, name: String) throws -> TargetNode?
func targetDependencies(path: AbsolutePath, name: String) -> [TargetNode] func targetDependencies(path: AbsolutePath, name: String) -> [TargetNode]
func staticDependencies(path: AbsolutePath, name: String) -> [DependencyReference] func staticDependencies(path: AbsolutePath, name: String) -> [DependencyReference]
func resourceBundleDependencies(path: AbsolutePath, name: String) -> [TargetNode] func resourceBundleDependencies(path: AbsolutePath, name: String) -> [TargetNode]
@ -185,6 +186,10 @@ public class Graph: Graphing {
public var targets: [TargetNode] { public var targets: [TargetNode] {
return cache.targetNodes.flatMap { $0.value.values } return cache.targetNodes.flatMap { $0.value.values }
} }
public func target(path: AbsolutePath, name: String) -> TargetNode? {
return findTargetNode(path: path, name: name)
}
public func targetDependencies(path: AbsolutePath, name: String) -> [TargetNode] { public func targetDependencies(path: AbsolutePath, name: String) -> [TargetNode] {
guard let targetNode = findTargetNode(path: path, name: name) else { guard let targetNode = findTargetNode(path: path, name: name) else {

View File

@ -66,13 +66,13 @@ public class ExecutionAction: Equatable {
public let title: String public let title: String
public let scriptText: String public let scriptText: String
public let target: String? public let target: TargetReference?
// MARK: - Init // MARK: - Init
public init(title: String, public init(title: String,
scriptText: String, scriptText: String,
target: String?) { target: TargetReference?) {
self.title = title self.title = title
self.scriptText = scriptText self.scriptText = scriptText
self.target = target self.target = target
@ -85,16 +85,30 @@ public class ExecutionAction: Equatable {
} }
} }
public struct TargetReference: Equatable {
public var projectPath: AbsolutePath
public var name: String
public static func project(path: AbsolutePath, target: String) -> TargetReference {
return .init(projectPath: path, name: target)
}
public init(projectPath: AbsolutePath, name: String) {
self.projectPath = projectPath
self.name = name
}
}
public class BuildAction: Equatable { public class BuildAction: Equatable {
// MARK: - Attributes // MARK: - Attributes
public let targets: [String] public let targets: [TargetReference]
public let preActions: [ExecutionAction] public let preActions: [ExecutionAction]
public let postActions: [ExecutionAction] public let postActions: [ExecutionAction]
// MARK: - Init // MARK: - Init
public init(targets: [String] = [], public init(targets: [TargetReference] = [],
preActions: [ExecutionAction] = [], preActions: [ExecutionAction] = [],
postActions: [ExecutionAction] = []) { postActions: [ExecutionAction] = []) {
self.targets = targets self.targets = targets
@ -118,7 +132,7 @@ public class TestAction: Equatable {
public let arguments: Arguments? public let arguments: Arguments?
public let configurationName: String public let configurationName: String
public let coverage: Bool public let coverage: Bool
public let codeCoverageTargets: [String] public let codeCoverageTargets: [TargetReference]
public let preActions: [ExecutionAction] public let preActions: [ExecutionAction]
public let postActions: [ExecutionAction] public let postActions: [ExecutionAction]
@ -128,7 +142,7 @@ public class TestAction: Equatable {
arguments: Arguments? = nil, arguments: Arguments? = nil,
configurationName: String, configurationName: String,
coverage: Bool = false, coverage: Bool = false,
codeCoverageTargets: [String] = [], codeCoverageTargets: [TargetReference] = [],
preActions: [ExecutionAction] = [], preActions: [ExecutionAction] = [],
postActions: [ExecutionAction] = []) { postActions: [ExecutionAction] = []) {
self.targets = targets self.targets = targets
@ -153,13 +167,13 @@ public class TestAction: Equatable {
} }
} }
public struct TestableTarget: Equatable, Hashable { public struct TestableTarget: Equatable {
public let target: String public let target: TargetReference
public let isSkipped: Bool public let isSkipped: Bool
public let isParallelizable: Bool public let isParallelizable: Bool
public let isRandomExecutionOrdering: Bool public let isRandomExecutionOrdering: Bool
public init(target: String, skipped: Bool = false, parallelizable: Bool = false, randomExecutionOrdering: Bool = false) { public init(target: TargetReference, skipped: Bool = false, parallelizable: Bool = false, randomExecutionOrdering: Bool = false) {
self.target = target self.target = target
self.isSkipped = skipped self.isSkipped = skipped
self.isParallelizable = parallelizable self.isParallelizable = parallelizable
@ -171,13 +185,13 @@ public class RunAction: Equatable {
// MARK: - Attributes // MARK: - Attributes
public let configurationName: String public let configurationName: String
public let executable: String? public let executable: TargetReference?
public let arguments: Arguments? public let arguments: Arguments?
// MARK: - Init // MARK: - Init
public init(configurationName: String, public init(configurationName: String,
executable: String? = nil, executable: TargetReference? = nil,
arguments: Arguments? = nil) { arguments: Arguments? = nil) {
self.configurationName = configurationName self.configurationName = configurationName
self.executable = executable self.executable = executable

View File

@ -12,7 +12,7 @@ public extension Arguments {
public extension RunAction { public extension RunAction {
static func test(configurationName: String = BuildConfiguration.debug.name, static func test(configurationName: String = BuildConfiguration.debug.name,
executable: String? = "App", executable: TargetReference? = TargetReference(projectPath: "/Project", name: "App"),
arguments: Arguments? = Arguments.test()) -> RunAction { arguments: Arguments? = Arguments.test()) -> RunAction {
return RunAction(configurationName: configurationName, return RunAction(configurationName: configurationName,
executable: executable, executable: executable,
@ -21,11 +21,11 @@ public extension RunAction {
} }
public extension TestAction { public extension TestAction {
static func test(targets: [TestableTarget] = [TestableTarget(target: "AppTests")], static func test(targets: [TestableTarget] = [TestableTarget(target: TargetReference(projectPath: "/Project", name: "AppTests"))],
arguments: Arguments? = Arguments.test(), arguments: Arguments? = Arguments.test(),
configurationName: String = BuildConfiguration.debug.name, configurationName: String = BuildConfiguration.debug.name,
coverage: Bool = false, coverage: Bool = false,
codeCoverageTargets: [String] = [], codeCoverageTargets: [TargetReference] = [],
preActions: [ExecutionAction] = [], preActions: [ExecutionAction] = [],
postActions: [ExecutionAction] = []) -> TestAction { postActions: [ExecutionAction] = []) -> TestAction {
return TestAction(targets: targets, return TestAction(targets: targets,
@ -39,7 +39,7 @@ public extension TestAction {
} }
public extension BuildAction { public extension BuildAction {
static func test(targets: [String] = ["App"], static func test(targets: [TargetReference] = [TargetReference(projectPath: "/Project", name: "App")],
preActions: [ExecutionAction] = [], preActions: [ExecutionAction] = [],
postActions: [ExecutionAction] = []) -> BuildAction { postActions: [ExecutionAction] = []) -> BuildAction {
return BuildAction(targets: targets, preActions: preActions, postActions: postActions) return BuildAction(targets: targets, preActions: preActions, postActions: postActions)

View File

@ -38,7 +38,7 @@ final class ProjectGenerator: ProjectGenerating {
let configGenerator: ConfigGenerating let configGenerator: ConfigGenerating
/// Generator for the project schemes. /// Generator for the project schemes.
let schemesGenerator: SchemesGenerating let schemesGenerator: SchemesGenerator
/// Generator for the project derived files. /// Generator for the project derived files.
let derivedFileGenerator: DerivedFileGenerating let derivedFileGenerator: DerivedFileGenerating
@ -54,7 +54,7 @@ final class ProjectGenerator: ProjectGenerating {
/// - derivedFileGenerator: Generator for the project derived files. /// - derivedFileGenerator: Generator for the project derived files.
init(targetGenerator: TargetGenerating = TargetGenerator(), init(targetGenerator: TargetGenerating = TargetGenerator(),
configGenerator: ConfigGenerating = ConfigGenerator(), configGenerator: ConfigGenerating = ConfigGenerator(),
schemesGenerator: SchemesGenerating = SchemesGenerator(), schemesGenerator: SchemesGenerator = SchemesGenerator(),
derivedFileGenerator: DerivedFileGenerating = DerivedFileGenerator()) { derivedFileGenerator: DerivedFileGenerating = DerivedFileGenerator()) {
self.targetGenerator = targetGenerator self.targetGenerator = targetGenerator
self.configGenerator = configGenerator self.configGenerator = configGenerator
@ -103,9 +103,7 @@ final class ProjectGenerator: ProjectGenerating {
groups: groups, groups: groups,
pbxproj: pbxproj, pbxproj: pbxproj,
sourceRootPath: sourceRootPath) sourceRootPath: sourceRootPath)
let configurationList = try configGenerator.generateProjectConfig(project: project, let configurationList = try configGenerator.generateProjectConfig(project: project, pbxproj: pbxproj, fileElements: fileElements)
pbxproj: pbxproj,
fileElements: fileElements)
let pbxProject = try generatePbxproject(project: project, let pbxProject = try generatePbxproject(project: project,
configurationList: configurationList, configurationList: configurationList,
groups: groups, groups: groups,
@ -252,7 +250,7 @@ final class ProjectGenerator: ProjectGenerating {
workspace: XCWorkspace, workspace: XCWorkspace,
pbxproj: PBXProj, pbxproj: PBXProj,
project: Project, project: Project,
graph _: Graphing) throws -> GeneratedProject { graph: Graphing) throws -> GeneratedProject {
var generatedProject: GeneratedProject! var generatedProject: GeneratedProject!
try FileHandler.shared.inTemporaryDirectory { temporaryPath in try FileHandler.shared.inTemporaryDirectory { temporaryPath in
@ -263,9 +261,11 @@ final class ProjectGenerator: ProjectGenerating {
generatedProject = GeneratedProject(pbxproj: pbxproj, generatedProject = GeneratedProject(pbxproj: pbxproj,
path: temporaryPath, path: temporaryPath,
targets: nativeTargets, targets: nativeTargets,
name: xcodeprojPath.components.last!) name: xcodeprojPath.basename)
try writeSchemes(project: project, try writeSchemes(project: project,
generatedProject: generatedProject) generatedProject: generatedProject,
xcprojectPath: temporaryPath,
graph: graph)
try FileHandler.shared.replace(xcodeprojPath, with: temporaryPath) try FileHandler.shared.replace(xcodeprojPath, with: temporaryPath)
} }
@ -280,9 +280,13 @@ final class ProjectGenerator: ProjectGenerating {
} }
private func writeSchemes(project: Project, private func writeSchemes(project: Project,
generatedProject: GeneratedProject) throws { generatedProject: GeneratedProject,
try schemesGenerator.generateTargetSchemes(project: project, xcprojectPath: AbsolutePath,
generatedProject: generatedProject) graph: Graphing) throws {
try schemesGenerator.generateProjectSchemes(project: project,
xcprojectPath: xcprojectPath,
generatedProject: generatedProject,
graph: graph)
} }
private func determineProjectConstants(graph: Graphing) throws -> ProjectConstants { private func determineProjectConstants(graph: Graphing) throws -> ProjectConstants {

View File

@ -10,10 +10,14 @@ protocol SchemesGenerating {
/// ///
/// - Parameters: /// - Parameters:
/// - project: Project manifest. /// - project: Project manifest.
/// - xcprojectPath: Path to the Xcode project.
/// - generatedProject: Generated Xcode project. /// - generatedProject: Generated Xcode project.
/// - graph: Tuist graph.
/// - Throws: A FatalError if the generation of the schemes fails. /// - Throws: A FatalError if the generation of the schemes fails.
func generateTargetSchemes(project: Project, func generateProjectSchemes(project: Project,
generatedProject: GeneratedProject) throws xcprojectPath: AbsolutePath,
generatedProject: GeneratedProject,
graph: Graphing) throws
} }
// swiftlint:disable:next type_body_length // swiftlint:disable:next type_body_length
@ -23,57 +27,93 @@ final class SchemesGenerator: SchemesGenerating {
/// Default version for generated schemes. /// Default version for generated schemes.
private static let defaultVersion = "1.3" private static let defaultVersion = "1.3"
/// Generates the schemes for the project manifest. /// Generate schemes for a project.
/// ///
/// - Parameters: /// - Parameters:
/// - project: Project manifest. /// - project: Project manifest.
/// - generatedProject: Generated Xcode project. /// - xcprojectPath: Path to project's .xcodeproj.
/// - Throws: A FatalError if the generation of the schemes fails. /// - generatedProject: Generated Project
func generateTargetSchemes(project: Project, generatedProject: GeneratedProject) throws { /// - graph: Tuist graph.
/// Generate scheme from manifest func generateProjectSchemes(project: Project,
xcprojectPath: AbsolutePath,
generatedProject: GeneratedProject,
graph: Graphing) throws {
/// Generate custom schemes from manifest
try project.schemes.forEach { scheme in try project.schemes.forEach { scheme in
try generateScheme(scheme: scheme, project: project, generatedProject: generatedProject) try generateScheme(scheme: scheme,
xcPath: xcprojectPath,
path: project.path,
graph: graph,
generatedProjects: [project.path: generatedProject])
} }
/// Generate scheme for every targets in Project that is not defined in Manifest /// Generate default schemes for targets in Project that are not defined in Manifest
let buildConfiguration = defaultDebugBuildConfigurationName(in: project) let buildConfiguration = defaultDebugBuildConfigurationName(in: project)
try project.targets.forEach { target in let userDefinedSchemes = Set(project.schemes.map(\.name))
let defaultSchemeTargets = project.targets.filter { !userDefinedSchemes.contains($0.name) }
try defaultSchemeTargets.forEach { target in
let scheme = createDefaultScheme(target: target, project: project, buildConfiguration: buildConfiguration)
try generateScheme(scheme: scheme,
xcPath: xcprojectPath,
path: project.path,
graph: graph,
generatedProjects: [project.path: generatedProject])
if !project.schemes.contains(where: { $0.name == target.name }) {
let scheme = Scheme(name: target.name,
shared: true,
buildAction: BuildAction(targets: [target.name]),
testAction: TestAction(targets: [TestableTarget(target: target.name)], configurationName: buildConfiguration),
runAction: RunAction(configurationName: buildConfiguration,
executable: target.productName,
arguments: Arguments(environment: target.environment)))
try generateScheme(scheme: scheme,
project: project,
generatedProject: generatedProject)
}
} }
} }
/// Generates the scheme. private func createDefaultScheme(target: Target, project: Project, buildConfiguration: String) -> Scheme {
let targetReference = TargetReference.project(path: project.path, target: target.name)
let testTargets = target.product.testsBundle ? [TestableTarget(target: targetReference)] : []
return Scheme(name: target.name,
shared: true,
buildAction: BuildAction(targets: [targetReference]),
testAction: TestAction(targets: testTargets, configurationName: buildConfiguration),
runAction: RunAction(configurationName: buildConfiguration, executable: targetReference, arguments: Arguments(environment: target.environment)))
}
/// Generate schemes for a project or workspace.
/// ///
/// - Parameters: /// - Parameters:
/// - scheme: Scheme manifest. /// - scheme: Project scheme.
/// - project: Project manifest. /// - xcPath: Path to workspace's .xcworkspace or project's .xcodeproj.
/// - generatedProject: Generated Xcode project. /// - path: Path to workspace or project folder.
/// - Throws: An error if the generation fails. /// - graph: Tuist graph.
func generateScheme(scheme: Scheme, /// - generatedProjects: Project paths mapped to generated projects.
project: Project, private func generateScheme(scheme: Scheme,
generatedProject: GeneratedProject) throws { xcPath: AbsolutePath,
let schemesDirectory = try createSchemesDirectory(projectPath: generatedProject.path, shared: scheme.shared) path: AbsolutePath,
let schemePath = schemesDirectory.appending(component: "\(scheme.name).xcscheme") graph: Graphing,
generatedProjects: [AbsolutePath: GeneratedProject]) throws {
let generatedBuildAction = schemeBuildAction(scheme: scheme, project: project, generatedProject: generatedProject) let schemeDirectory = try createSchemesDirectory(path: xcPath, shared: scheme.shared)
let generatedTestAction = schemeTestAction(scheme: scheme, project: project, generatedProject: generatedProject) let schemePath = schemeDirectory.appending(component: "\(scheme.name).xcscheme")
let generatedLaunchAction = schemeLaunchAction(scheme: scheme, project: project, generatedProject: generatedProject)
let generatedProfileAction = schemeProfileAction(scheme: scheme, project: project, generatedProject: generatedProject) let generatedBuildAction = try schemeBuildAction(scheme: scheme,
let generatedArchiveAction = schemeArchiveAction(scheme: scheme, project: project, generatedProject: generatedProject) graph: graph,
rootPath: path,
generatedProjects: generatedProjects)
let generatedTestAction = try schemeTestAction(scheme: scheme,
graph: graph,
rootPath: path,
generatedProjects: generatedProjects)
let generatedLaunchAction = try schemeLaunchAction(scheme: scheme,
graph: graph,
rootPath: path,
generatedProjects: generatedProjects)
let generatedProfileAction = try schemeProfileAction(scheme: scheme,
graph: graph,
rootPath: path,
generatedProjects: generatedProjects)
let generatedAnalyzeAction = try schemeAnalyzeAction(scheme: scheme,
graph: graph,
rootPath: path,
generatedProjects: generatedProjects)
let generatedArchiveAction = try schemeArchiveAction(scheme: scheme,
graph: graph,
rootPath: path,
generatedProjects: generatedProjects)
let scheme = XCScheme(name: scheme.name, let scheme = XCScheme(name: scheme.name,
lastUpgradeVersion: SchemesGenerator.defaultLastUpgradeVersion, lastUpgradeVersion: SchemesGenerator.defaultLastUpgradeVersion,
@ -82,158 +122,96 @@ final class SchemesGenerator: SchemesGenerating {
testAction: generatedTestAction, testAction: generatedTestAction,
launchAction: generatedLaunchAction, launchAction: generatedLaunchAction,
profileAction: generatedProfileAction, profileAction: generatedProfileAction,
analyzeAction: schemeAnalyzeAction(for: project), analyzeAction: generatedAnalyzeAction,
archiveAction: generatedArchiveAction) archiveAction: generatedArchiveAction)
try scheme.write(path: schemePath.path, override: true) try scheme.write(path: schemePath.path, override: true)
} }
/// Returns the build action for the project scheme. /// Generates the scheme build action.
/// ///
/// - Parameters: /// - Parameters:
/// - project: Project manifest. /// - scheme: Scheme manifest.
/// - generatedProject: Generated Xcode project. /// - graph: Tuist graph.
/// - graph: Dependencies graph. /// - rootPath: Path to the project or workspace.
/// - generatedProjects: Project paths mapped to generated projects.
/// - Returns: Scheme build action. /// - Returns: Scheme build action.
func projectBuildAction(project: Project, func schemeBuildAction(scheme: Scheme,
generatedProject: GeneratedProject, graph: Graphing,
graph: Graphing) -> XCScheme.BuildAction { rootPath: AbsolutePath,
let targets = project.sortedTargetsForProjectScheme(graph: graph) generatedProjects: [AbsolutePath: GeneratedProject]) throws -> XCScheme.BuildAction? {
let entries: [XCScheme.BuildAction.Entry] = targets.map { (target) -> XCScheme.BuildAction.Entry in guard let buildAction = scheme.buildAction else { return nil }
let pbxTarget = generatedProject.targets[target.name]! let buildFor: [XCScheme.BuildAction.Entry.BuildFor] = [
let buildableReference = targetBuildableReference(target: target, .analyzing, .archiving, .profiling, .running, .testing,
pbxTarget: pbxTarget, ]
projectName: generatedProject.name)
var buildFor: [XCScheme.BuildAction.Entry.BuildFor] = []
if target.product.testsBundle {
buildFor.append(.testing)
} else {
buildFor.append(contentsOf: [.analyzing, .archiving, .profiling, .running, .testing])
}
return XCScheme.BuildAction.Entry(buildableReference: buildableReference, var entries: [XCScheme.BuildAction.Entry] = []
buildFor: buildFor) var preActions: [XCScheme.ExecutionAction] = []
var postActions: [XCScheme.ExecutionAction] = []
try buildAction.targets.forEach { buildActionTarget in
guard let buildableReference = try createBuildableReference(targetReference: buildActionTarget,
graph: graph,
rootPath: rootPath,
generatedProjects: generatedProjects) else { return }
entries.append(XCScheme.BuildAction.Entry(buildableReference: buildableReference, buildFor: buildFor))
}
preActions = try buildAction.preActions.map {
try schemeExecutionAction(action: $0, graph: graph, generatedProjects: generatedProjects, rootPath: rootPath)
}
postActions = try buildAction.postActions.map {
try schemeExecutionAction(action: $0, graph: graph, generatedProjects: generatedProjects, rootPath: rootPath)
} }
return XCScheme.BuildAction(buildActionEntries: entries, return XCScheme.BuildAction(buildActionEntries: entries,
preActions: preActions,
postActions: postActions,
parallelizeBuild: true, parallelizeBuild: true,
buildImplicitDependencies: true) buildImplicitDependencies: true)
} }
/// Generates the test action for the project scheme.
///
/// - Parameters:
/// - project: Project manifest.
/// - generatedProject: Generated Xcode project.
/// - Returns: Scheme test action.
func projectTestAction(project: Project,
generatedProject: GeneratedProject) -> XCScheme.TestAction {
var testables: [XCScheme.TestableReference] = []
let testTargets = project.targets.filter { $0.product.testsBundle }
testTargets.forEach { target in
let pbxTarget = generatedProject.targets[target.name]!
let reference = targetBuildableReference(target: target,
pbxTarget: pbxTarget,
projectName: generatedProject.name)
let testable = XCScheme.TestableReference(skipped: false,
buildableReference: reference)
testables.append(testable)
}
let buildConfiguration = defaultDebugBuildConfigurationName(in: project)
return XCScheme.TestAction(buildConfiguration: buildConfiguration,
macroExpansion: nil,
testables: testables)
}
/// Generates the scheme archive action.
/// - Parameter scheme: Scheme manifest.
/// - Parameter project: Project manifest.
/// - Parameter generatedProject: Generated Xcode project.
/// - Returns: Scheme archive action.
func schemeArchiveAction(scheme: Scheme,
project: Project,
generatedProject: GeneratedProject) -> XCScheme.ArchiveAction {
guard let archiveAction = scheme.archiveAction else {
return defaultSchemeArchiveAction(for: project)
}
return XCScheme.ArchiveAction(buildConfiguration: archiveAction.configurationName,
revealArchiveInOrganizer: archiveAction.revealArchiveInOrganizer,
customArchiveName: archiveAction.customArchiveName,
preActions: schemeExecutionActions(actions: archiveAction.preActions,
project: project,
generatedProject: generatedProject),
postActions: schemeExecutionActions(actions: archiveAction.postActions,
project: project,
generatedProject: generatedProject))
}
/// Generates the array of BuildableReference for targets that the
/// coverage report should be generated for them.
///
/// - Parameters:
/// - testAction: test actions.
/// - project: Project manifest.
/// - generatedProject: Generated Xcode project.
/// - Returns: Array of buildable references.
private func testCoverageTargetReferences(testAction: TestAction, project: Project, generatedProject: GeneratedProject) -> [XCScheme.BuildableReference] {
var codeCoverageTargets: [XCScheme.BuildableReference] = []
testAction.codeCoverageTargets.forEach { name in
guard let target = project.targets.first(where: { $0.name == name }) else { return }
guard let pbxTarget = generatedProject.targets[name] else { return }
let reference = self.targetBuildableReference(target: target,
pbxTarget: pbxTarget,
projectName: generatedProject.name)
codeCoverageTargets.append(reference)
}
return codeCoverageTargets
}
/// Generates the scheme test action. /// Generates the scheme test action.
/// ///
/// - Parameters: /// - Parameters:
/// - scheme: Scheme manifest. /// - scheme: Scheme manifest.
/// - project: Project manifest. /// - graph: Tuist graph.
/// - generatedProject: Generated Xcode project. /// - rootPath: Root path to either project or workspace.
/// - generatedProjects: Project paths mapped to generated projects.
/// - Returns: Scheme test action. /// - Returns: Scheme test action.
func schemeTestAction(scheme: Scheme, func schemeTestAction(scheme: Scheme,
project: Project, graph: Graphing,
generatedProject: GeneratedProject) -> XCScheme.TestAction? { rootPath: AbsolutePath,
generatedProjects: [AbsolutePath: GeneratedProject]) throws -> XCScheme.TestAction? {
guard let testAction = scheme.testAction else { return nil } guard let testAction = scheme.testAction else { return nil }
var testables: [XCScheme.TestableReference] = [] var testables: [XCScheme.TestableReference] = []
var preActions: [XCScheme.ExecutionAction] = [] var preActions: [XCScheme.ExecutionAction] = []
var postActions: [XCScheme.ExecutionAction] = [] var postActions: [XCScheme.ExecutionAction] = []
testAction.targets.forEach { test in try testAction.targets.forEach { testableTarget in
let targetName = test.target guard let reference = try createBuildableReference(targetReference: testableTarget.target,
guard let target = project.targets.first(where: { $0.name == targetName }), target.product.testsBundle else { return } graph: graph,
guard let pbxTarget = generatedProject.targets[targetName] else { return } rootPath: rootPath,
generatedProjects: generatedProjects) else { return }
let reference = self.targetBuildableReference(target: target,
pbxTarget: pbxTarget, let testable = XCScheme.TestableReference(skipped: testableTarget.isSkipped,
projectName: generatedProject.name) parallelizable: testableTarget.isParallelizable,
randomExecutionOrdering: testableTarget.isRandomExecutionOrdering,
let testable = XCScheme.TestableReference(skipped: test.isSkipped,
parallelizable: test.isParallelizable,
randomExecutionOrdering: test.isRandomExecutionOrdering,
buildableReference: reference) buildableReference: reference)
testables.append(testable) testables.append(testable)
} }
preActions = schemeExecutionActions(actions: testAction.preActions, preActions = try testAction.preActions.map { try schemeExecutionAction(action: $0,
project: project, graph: graph,
generatedProject: generatedProject) generatedProjects: generatedProjects,
rootPath: rootPath) }
postActions = schemeExecutionActions(actions: testAction.postActions, postActions = try testAction.postActions.map { try schemeExecutionAction(action: $0,
project: project, graph: graph,
generatedProject: generatedProject) generatedProjects: generatedProjects,
rootPath: rootPath) }
var args: XCScheme.CommandLineArguments? var args: XCScheme.CommandLineArguments?
var environments: [XCScheme.EnvironmentVariable]? var environments: [XCScheme.EnvironmentVariable]?
@ -242,8 +220,13 @@ final class SchemesGenerator: SchemesGenerating {
args = XCScheme.CommandLineArguments(arguments: commandlineArgruments(arguments.launch)) args = XCScheme.CommandLineArguments(arguments: commandlineArgruments(arguments.launch))
environments = environmentVariables(arguments.environment) environments = environmentVariables(arguments.environment)
} }
let codeCoverageTargets = testCoverageTargetReferences(testAction: testAction, project: project, generatedProject: generatedProject) let codeCoverageTargets = try testAction.codeCoverageTargets.compactMap {
try testCoverageTargetReferences(target: $0,
graph: graph,
generatedProjects: generatedProjects,
rootPath: rootPath)
}
let onlyGenerateCoverageForSpecifiedTargets = codeCoverageTargets.count > 0 ? true : nil let onlyGenerateCoverageForSpecifiedTargets = codeCoverageTargets.count > 0 ? true : nil
@ -261,75 +244,35 @@ final class SchemesGenerator: SchemesGenerating {
commandlineArguments: args, commandlineArguments: args,
environmentVariables: environments) environmentVariables: environments)
} }
/// Generates the scheme build action.
///
/// - Parameters:
/// - scheme: Scheme manifest.
/// - project: Project manifest.
/// - generatedProject: Generated Xcode project.
/// - Returns: Scheme build action.
func schemeBuildAction(scheme: Scheme,
project: Project,
generatedProject: GeneratedProject) -> XCScheme.BuildAction? {
guard let buildAction = scheme.buildAction else { return nil }
let buildFor: [XCScheme.BuildAction.Entry.BuildFor] = [
.analyzing, .archiving, .profiling, .running, .testing,
]
var entries: [XCScheme.BuildAction.Entry] = []
var preActions: [XCScheme.ExecutionAction] = []
var postActions: [XCScheme.ExecutionAction] = []
buildAction.targets.forEach { name in
guard let target = project.targets.first(where: { $0.name == name }) else { return }
guard let pbxTarget = generatedProject.targets[name] else { return }
let buildableReference = self.targetBuildableReference(target: target,
pbxTarget: pbxTarget,
projectName: generatedProject.name)
entries.append(XCScheme.BuildAction.Entry(buildableReference: buildableReference, buildFor: buildFor))
}
preActions = schemeExecutionActions(actions: buildAction.preActions,
project: project,
generatedProject: generatedProject)
postActions = schemeExecutionActions(actions: buildAction.postActions,
project: project,
generatedProject: generatedProject)
return XCScheme.BuildAction(buildActionEntries: entries,
preActions: preActions,
postActions: postActions,
parallelizeBuild: true,
buildImplicitDependencies: true)
}
/// Generates the scheme launch action. /// Generates the scheme launch action.
/// ///
/// - Parameters: /// - Parameters:
/// - scheme: Scheme manifest. /// - scheme: Scheme manifest.
/// - project: Project manifest. /// - graph: Tuist graph.
/// - generatedProject: Generated Xcode project. /// - rootPath: Root path to either project or workspace.
/// - generatedProjects: Project paths mapped to generated projects.
/// - Returns: Scheme launch action. /// - Returns: Scheme launch action.
func schemeLaunchAction(scheme: Scheme, func schemeLaunchAction(scheme: Scheme,
project: Project, graph: Graphing,
generatedProject: GeneratedProject) -> XCScheme.LaunchAction? { rootPath: AbsolutePath,
guard var target = project.targets.first(where: { $0.name == scheme.buildAction?.targets.first }) else { return nil } generatedProjects: [AbsolutePath: GeneratedProject]) throws -> XCScheme.LaunchAction? {
guard var target = try defaultTargetReference(scheme: scheme) else { return nil }
if let executable = scheme.runAction?.executable { if let executable = scheme.runAction?.executable {
guard let runableTarget = project.targets.first(where: { $0.name == executable }) else { return nil } target = executable
target = runableTarget
} }
guard let pbxTarget = generatedProject.targets[target.name] else { return nil } guard let targetNode = try graph.target(path: target.projectPath, name: target.name) else { return nil }
guard let buildableReference = try createBuildableReference(targetReference: target,
graph: graph,
rootPath: rootPath,
generatedProjects: generatedProjects) else { return nil }
var buildableProductRunnable: XCScheme.BuildableProductRunnable? var buildableProductRunnable: XCScheme.BuildableProductRunnable?
var macroExpansion: XCScheme.BuildableReference? var macroExpansion: XCScheme.BuildableReference?
let buildableReference = targetBuildableReference(target: target, pbxTarget: pbxTarget, projectName: generatedProject.name)
if target.product.runnable { if targetNode.target.product.runnable {
buildableProductRunnable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference, runnableDebuggingMode: "0") buildableProductRunnable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference, runnableDebuggingMode: "0")
} else { } else {
macroExpansion = buildableReference macroExpansion = buildableReference
@ -343,127 +286,276 @@ final class SchemesGenerator: SchemesGenerating {
environments = environmentVariables(arguments.environment) environments = environmentVariables(arguments.environment)
} }
let buildConfiguration = scheme.runAction?.configurationName ?? defaultDebugBuildConfigurationName(in: project) let buildConfiguration = scheme.runAction?.configurationName ?? defaultDebugBuildConfigurationName(in: targetNode.project)
return XCScheme.LaunchAction(runnable: buildableProductRunnable, return XCScheme.LaunchAction(runnable: buildableProductRunnable,
buildConfiguration: buildConfiguration, buildConfiguration: buildConfiguration,
macroExpansion: macroExpansion, macroExpansion: macroExpansion,
commandlineArguments: commandlineArguments, commandlineArguments: commandlineArguments,
environmentVariables: environments) environmentVariables: environments)
} }
/// Generates the scheme profile action for a given target. /// Generates the scheme profile action for a given target.
/// ///
/// - Parameters: /// - Parameters:
/// - target: Target manifest. /// - scheme: Target manifest.
/// - pbxTarget: Xcode native target. /// - graph: Tuist graph.
/// - projectName: Project name with .xcodeproj extension. /// - rootPath: Root path to either project or workspace.
/// - generatedProjects: Project paths mapped to generated projects.
/// - Returns: Scheme profile action. /// - Returns: Scheme profile action.
func schemeProfileAction(scheme: Scheme, func schemeProfileAction(scheme: Scheme,
project: Project, graph: Graphing,
generatedProject: GeneratedProject) -> XCScheme.ProfileAction? { rootPath: AbsolutePath,
guard var target = project.targets.first(where: { $0.name == scheme.buildAction?.targets.first }) else { return nil } generatedProjects: [AbsolutePath: GeneratedProject]) throws -> XCScheme.ProfileAction? {
guard var target = try defaultTargetReference(scheme: scheme) else { return nil }
if let executable = scheme.runAction?.executable { if let executable = scheme.runAction?.executable {
guard let runableTarget = project.targets.first(where: { $0.name == executable }) else { return nil } target = executable
target = runableTarget
} }
guard let targetNode = try graph.target(path: target.projectPath, name: target.name) else { return nil }
guard let pbxTarget = generatedProject.targets[target.name] else { return nil } guard let buildableReference = try createBuildableReference(targetReference: target,
graph: graph,
rootPath: rootPath,
generatedProjects: generatedProjects) else { return nil }
var buildableProductRunnable: XCScheme.BuildableProductRunnable? var buildableProductRunnable: XCScheme.BuildableProductRunnable?
var macroExpansion: XCScheme.BuildableReference? var macroExpansion: XCScheme.BuildableReference?
let buildableReference = targetBuildableReference(target: target, pbxTarget: pbxTarget, projectName: generatedProject.name)
if targetNode.target.product.runnable {
if target.product.runnable {
buildableProductRunnable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference, runnableDebuggingMode: "0") buildableProductRunnable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference, runnableDebuggingMode: "0")
} else { } else {
macroExpansion = buildableReference macroExpansion = buildableReference
} }
let buildConfiguration = defaultReleaseBuildConfigurationName(in: project) let buildConfiguration = defaultReleaseBuildConfigurationName(in: targetNode.project)
return XCScheme.ProfileAction(buildableProductRunnable: buildableProductRunnable, return XCScheme.ProfileAction(buildableProductRunnable: buildableProductRunnable,
buildConfiguration: buildConfiguration, buildConfiguration: buildConfiguration,
macroExpansion: macroExpansion) macroExpansion: macroExpansion)
} }
/// Returns the scheme analyze action.
///
/// - Parameters:
/// - scheme: Scheme manifest.
/// - graph: Tuist graph.
/// - rootPath: Root path to either project or workspace.
/// - generatedProjects: Project paths mapped to generated projects.
/// - Returns: Scheme analyze action.
func schemeAnalyzeAction(scheme: Scheme,
graph: Graphing,
rootPath: AbsolutePath,
generatedProjects: [AbsolutePath: GeneratedProject]) throws -> XCScheme.AnalyzeAction? {
guard let target = try defaultTargetReference(scheme: scheme),
let targetNode = try graph.target(path: target.projectPath, name: target.name) else { return nil }
let buildConfiguration = defaultDebugBuildConfigurationName(in: targetNode.project)
return XCScheme.AnalyzeAction(buildConfiguration: buildConfiguration)
}
/// Generates the scheme archive action.
///
/// - Parameters:
/// - scheme: Scheme manifest.
/// - graph: Tuist graph.
/// - rootPath: Root path to either project or workspace.
/// - generatedProjects: Project paths mapped to generated projects.
/// - Returns: Scheme archive action.
func schemeArchiveAction(scheme: Scheme,
graph: Graphing,
rootPath: AbsolutePath,
generatedProjects: [AbsolutePath: GeneratedProject]) throws -> XCScheme.ArchiveAction? {
guard let target = try defaultTargetReference(scheme: scheme),
let targetNode = try graph.target(path: target.projectPath, name: target.name) else { return nil }
guard let archiveAction = scheme.archiveAction else {
return defaultSchemeArchiveAction(for: targetNode.project)
}
let preActions = try archiveAction.preActions.map {
try schemeExecutionAction(action: $0, graph: graph, generatedProjects: generatedProjects, rootPath: rootPath)
}
let postActions = try archiveAction.postActions.map {
try schemeExecutionAction(action: $0, graph: graph, generatedProjects: generatedProjects, rootPath: rootPath)
}
return XCScheme.ArchiveAction(buildConfiguration: archiveAction.configurationName,
revealArchiveInOrganizer: archiveAction.revealArchiveInOrganizer,
customArchiveName: archiveAction.customArchiveName,
preActions: preActions,
postActions: postActions)
}
func schemeExecutionAction(action: ExecutionAction,
graph: Graphing,
generatedProjects: [AbsolutePath: GeneratedProject],
rootPath: AbsolutePath) throws -> XCScheme.ExecutionAction {
guard let targetReference = action.target,
let targetNode = try graph.target(path: targetReference.projectPath, name: targetReference.name),
let generatedProject = generatedProjects[targetReference.projectPath] else {
return schemeExecutionAction(action: action)
}
return schemeExecutionAction(action: action,
target: targetNode.target,
generatedProject: generatedProject)
}
private func schemeExecutionAction(action: ExecutionAction) -> XCScheme.ExecutionAction {
return XCScheme.ExecutionAction(scriptText: action.scriptText,
title: action.title,
environmentBuildable: nil)
}
/// Returns the scheme pre/post actions. /// Returns the scheme pre/post actions.
/// ///
/// - Parameters: /// - Parameters:
/// - actions: pre/post action manifest. /// - action: pre/post action manifest.
/// - project: Project manifest. /// - target: Project manifest.
/// - generatedProject: Generated Xcode project. /// - generatedProject: Generated Xcode project.
/// - Returns: Scheme actions. /// - Returns: Scheme actions.
func schemeExecutionActions(actions: [ExecutionAction], private func schemeExecutionAction(action: ExecutionAction,
project: Project, target: Target,
generatedProject: GeneratedProject) -> [XCScheme.ExecutionAction] { generatedProject: GeneratedProject) -> XCScheme.ExecutionAction {
/// Return Buildable Reference for Scheme Action /// Return Buildable Reference for Scheme Action
func schemeBuildableReference(targetName: String?, project: Project, generatedProject: GeneratedProject) -> XCScheme.BuildableReference? { func schemeBuildableReference(target: Target, generatedProject: GeneratedProject) -> XCScheme.BuildableReference? {
guard let targetName = targetName else { return nil } guard let pbxTarget = generatedProject.targets[target.name] else { return nil }
guard let target = project.targets.first(where: { $0.name == targetName }) else { return nil }
guard let pbxTarget = generatedProject.targets[targetName] else { return nil } return targetBuildableReference(target: target,
pbxTarget: pbxTarget,
return targetBuildableReference(target: target, pbxTarget: pbxTarget, projectName: generatedProject.name) projectPath: generatedProject.name)
} }
var schemeActions: [XCScheme.ExecutionAction] = [] let schemeAction = XCScheme.ExecutionAction(scriptText: action.scriptText,
actions.forEach { action in title: action.title,
let schemeAction = XCScheme.ExecutionAction(scriptText: action.scriptText, environmentBuildable: nil)
title: action.title,
environmentBuildable: nil)
schemeAction.environmentBuildable = schemeBuildableReference(targetName: action.target, schemeAction.environmentBuildable = schemeBuildableReference(target: target,
project: project, generatedProject: generatedProject)
generatedProject: generatedProject) return schemeAction
schemeActions.append(schemeAction)
}
return schemeActions
} }
// MARK: - Helpers
private func resolveRelativeProjectPath(targetNode: TargetNode,
generatedProject: GeneratedProject,
rootPath: AbsolutePath) -> RelativePath {
let xcodeProjectPath = targetNode.path.appending(component: generatedProject.name)
return xcodeProjectPath.relative(to: rootPath)
}
/// Creates a target buildable refernece for a target
///
/// - Parameters:
/// - targetReference: The target reference.
/// - graph: Tuist graph.
/// - rootPath: Path to the project or workspace.
/// - generatedProjects: Project paths mapped to generated projects.
private func createBuildableReference(targetReference: TargetReference,
graph: Graphing,
rootPath: AbsolutePath,
generatedProjects: [AbsolutePath: GeneratedProject]) throws -> XCScheme.BuildableReference? {
let projectPath = targetReference.projectPath
guard let target = try graph.target(path: projectPath, name: targetReference.name) else { return nil }
guard let generatedProject = generatedProjects[projectPath] else { return nil }
guard let pbxTarget = generatedProject.targets[targetReference.name] else { return nil }
let relativeXcodeProjectPath = resolveRelativeProjectPath(targetNode: target,
generatedProject: generatedProject,
rootPath: rootPath)
return targetBuildableReference(target: target.target,
pbxTarget: pbxTarget,
projectPath: relativeXcodeProjectPath.pathString)
}
/// Generates the array of BuildableReference for targets that the
/// coverage report should be generated for them.
///
/// - Parameters:
/// - target: test actions.
/// - graph: tuist graph.
/// - generatedProjects: Generated Xcode projects.
/// - rootPath: Root path to workspace or project.
/// - Returns: Array of buildable references.
private func testCoverageTargetReferences(target: TargetReference,
graph: Graphing,
generatedProjects: [AbsolutePath: GeneratedProject],
rootPath: AbsolutePath) throws -> XCScheme.BuildableReference? {
return try createBuildableReference(targetReference: target,
graph: graph,
rootPath: rootPath,
generatedProjects: generatedProjects)
}
/// Creates the directory where the schemes are stored inside the project.
/// If the directory exists it does not re-create it.
///
/// - Parameters:
/// - path: Path to the Xcode workspace or project.
/// - shared: Scheme should be shared or not
/// - Returns: Path to the schemes directory.
/// - Throws: A FatalError if the creation of the directory fails.
private func createSchemesDirectory(path: AbsolutePath, shared: Bool = true) throws -> AbsolutePath {
let schemePath: AbsolutePath
if shared {
schemePath = path.appending(RelativePath("xcshareddata/xcschemes"))
} else {
let username = NSUserName()
schemePath = path.appending(RelativePath("xcuserdata/\(username).xcuserdatad/xcschemes"))
}
if !FileHandler.shared.exists(schemePath) {
try FileHandler.shared.createFolder(schemePath)
}
return schemePath
}
/// Returns the scheme commandline argument passed on launch /// Returns the scheme commandline argument passed on launch
/// ///
/// - Parameters: /// - Parameters:
/// - environments: commandline argument keys. /// - environments: commandline argument keys.
/// - Returns: XCScheme.CommandLineArguments.CommandLineArgument. /// - Returns: XCScheme.CommandLineArguments.CommandLineArgument.
func commandlineArgruments(_ arguments: [String: Bool]) -> [XCScheme.CommandLineArguments.CommandLineArgument] { private func commandlineArgruments(_ arguments: [String: Bool]) -> [XCScheme.CommandLineArguments.CommandLineArgument] {
return arguments.map { key, enabled in return arguments.map { key, enabled in
XCScheme.CommandLineArguments.CommandLineArgument(name: key, enabled: enabled) XCScheme.CommandLineArguments.CommandLineArgument(name: key, enabled: enabled)
} }
} }
/// Returns the scheme environment variables /// Returns the scheme environment variables
/// ///
/// - Parameters: /// - Parameters:
/// - environments: environment variables /// - environments: environment variables
/// - Returns: XCScheme.EnvironmentVariable. /// - Returns: XCScheme.EnvironmentVariable.
func environmentVariables(_ environments: [String: String]) -> [XCScheme.EnvironmentVariable] { private func environmentVariables(_ environments: [String: String]) -> [XCScheme.EnvironmentVariable] {
return environments.map { key, value in return environments.map { key, value in
XCScheme.EnvironmentVariable(variable: key, value: value, enabled: true) XCScheme.EnvironmentVariable(variable: key, value: value, enabled: true)
} }
} }
private func defaultDebugBuildConfigurationName(in project: Project) -> String {
let debugConfiguration = project.settings.defaultDebugBuildConfiguration()
let buildConfiguration = debugConfiguration ?? project.settings.configurations.keys.first
return buildConfiguration?.name ?? BuildConfiguration.debug.name
}
/// Returns the scheme buildable reference for a given target. /// Returns the scheme buildable reference for a given target.
/// ///
/// - Parameters: /// - Parameters:
/// - target: Target manifest. /// - target: Target manifest.
/// - pbxTarget: Xcode native target. /// - pbxTarget: Xcode native target.
/// - projectName: Project name with the .xcodeproj extension. /// - projectPath: Project name with the .xcodeproj extension.
/// - Returns: Buildable reference. /// - Returns: Buildable reference.
func targetBuildableReference(target: Target, pbxTarget: PBXNativeTarget, projectName: String) -> XCScheme.BuildableReference { private func targetBuildableReference(target: Target,
return XCScheme.BuildableReference(referencedContainer: "container:\(projectName)", pbxTarget: PBXNativeTarget,
projectPath: String) -> XCScheme.BuildableReference {
return XCScheme.BuildableReference(referencedContainer: "container:\(projectPath)",
blueprint: pbxTarget, blueprint: pbxTarget,
buildableName: target.productNameWithExtension, buildableName: target.productNameWithExtension,
blueprintName: target.name, blueprintName: target.name,
buildableIdentifier: "primary") buildableIdentifier: "primary")
} }
/// Returns the scheme analyze action
///
/// - Returns: Scheme analyze action.
func schemeAnalyzeAction(for project: Project) -> XCScheme.AnalyzeAction {
let buildConfiguration = defaultDebugBuildConfigurationName(in: project)
return XCScheme.AnalyzeAction(buildConfiguration: buildConfiguration)
}
/// Returns the scheme archive action /// Returns the scheme archive action
/// ///
/// - Returns: Scheme archive action. /// - Returns: Scheme archive action.
@ -472,40 +564,15 @@ final class SchemesGenerator: SchemesGenerating {
return XCScheme.ArchiveAction(buildConfiguration: buildConfiguration, return XCScheme.ArchiveAction(buildConfiguration: buildConfiguration,
revealArchiveInOrganizer: true) revealArchiveInOrganizer: true)
} }
/// Creates the directory where the schemes are stored inside the project.
/// If the directory exists it does not re-create it.
///
/// - Parameters:
/// - projectPath: Path to the Xcode project.
/// - shared: Scheme should be shared or not
/// - Returns: Path to the schemes directory.
/// - Throws: A FatalError if the creation of the directory fails.
private func createSchemesDirectory(projectPath: AbsolutePath, shared: Bool = true) throws -> AbsolutePath {
var path: AbsolutePath!
if shared {
path = projectPath.appending(RelativePath("xcshareddata/xcschemes"))
} else {
let username = NSUserName()
path = projectPath.appending(RelativePath("xcuserdata/\(username).xcuserdatad/xcschemes"))
}
if !FileHandler.shared.exists(path) {
try FileHandler.shared.createFolder(path)
}
return path
}
private func defaultDebugBuildConfigurationName(in project: Project) -> String {
let debugConfiguration = project.settings.defaultDebugBuildConfiguration()
let buildConfiguration = debugConfiguration ?? project.settings.configurations.keys.first
return buildConfiguration?.name ?? BuildConfiguration.debug.name
}
private func defaultReleaseBuildConfigurationName(in project: Project) -> String { private func defaultReleaseBuildConfigurationName(in project: Project) -> String {
let releaseConfiguration = project.settings.defaultReleaseBuildConfiguration() let releaseConfiguration = project.settings.defaultReleaseBuildConfiguration()
let buildConfiguration = releaseConfiguration ?? project.settings.configurations.keys.first let buildConfiguration = releaseConfiguration ?? project.settings.configurations.keys.first
return buildConfiguration?.name ?? BuildConfiguration.release.name return buildConfiguration?.name ?? BuildConfiguration.release.name
} }
private func defaultTargetReference(scheme: Scheme) throws -> TargetReference? {
return scheme.buildAction?.targets.first
}
} }

View File

@ -66,8 +66,8 @@ private extension SchemeLinter {
for scheme in schemes { for scheme in schemes {
for target in scheme.testAction?.codeCoverageTargets ?? [] { for target in scheme.testAction?.codeCoverageTargets ?? [] {
if !targetNames.contains(target) { if !targetNames.contains(target.name) {
issues.append(missingCodeCoverageTargetIssue(missingTargetName: target, schemaName: scheme.name)) issues.append(missingCodeCoverageTargetIssue(missingTargetName: target.name, schemaName: scheme.name))
} }
} }
} }

View File

@ -225,7 +225,7 @@ extension TuistCore.Project {
generatorPaths: generatorPaths) generatorPaths: generatorPaths)
} }
let schemes = manifest.schemes.map { TuistCore.Scheme.from(manifest: $0) } let schemes = manifest.schemes.map { TuistCore.Scheme.from(manifest: $0, projectPath: path) }
let additionalFiles = try manifest.additionalFiles.flatMap { let additionalFiles = try manifest.additionalFiles.flatMap {
try TuistCore.FileElement.from(manifest: $0, try TuistCore.FileElement.from(manifest: $0,
@ -273,6 +273,7 @@ extension TuistCore.Project {
} }
extension TuistCore.Target { extension TuistCore.Target {
// swiftlint:disable:next function_body_length
static func from(manifest: ProjectDescription.Target, static func from(manifest: ProjectDescription.Target,
path: AbsolutePath, path: AbsolutePath,
generatorPaths: GeneratorPaths) throws -> TuistCore.Target { generatorPaths: GeneratorPaths) throws -> TuistCore.Target {
@ -569,13 +570,13 @@ extension TuistCore.Dependency {
} }
extension TuistCore.Scheme { extension TuistCore.Scheme {
static func from(manifest: ProjectDescription.Scheme) -> TuistCore.Scheme { static func from(manifest: ProjectDescription.Scheme, projectPath: AbsolutePath) -> TuistCore.Scheme {
let name = manifest.name let name = manifest.name
let shared = manifest.shared let shared = manifest.shared
let buildAction = manifest.buildAction.map { TuistCore.BuildAction.from(manifest: $0) } let buildAction = manifest.buildAction.map { TuistCore.BuildAction.from(manifest: $0, projectPath: projectPath) }
let testAction = manifest.testAction.map { TuistCore.TestAction.from(manifest: $0) } let testAction = manifest.testAction.map { TuistCore.TestAction.from(manifest: $0, projectPath: projectPath) }
let runAction = manifest.runAction.map { TuistCore.RunAction.from(manifest: $0) } let runAction = manifest.runAction.map { TuistCore.RunAction.from(manifest: $0, projectPath: projectPath) }
let archiveAction = manifest.archiveAction.map { TuistCore.ArchiveAction.from(manifest: $0) } let archiveAction = manifest.archiveAction.map { TuistCore.ArchiveAction.from(manifest: $0, projectPath: projectPath) }
return Scheme(name: name, return Scheme(name: name,
shared: shared, shared: shared,
@ -587,24 +588,26 @@ extension TuistCore.Scheme {
} }
extension TuistCore.BuildAction { extension TuistCore.BuildAction {
static func from(manifest: ProjectDescription.BuildAction) -> TuistCore.BuildAction { static func from(manifest: ProjectDescription.BuildAction, projectPath: AbsolutePath) -> TuistCore.BuildAction {
let preActions = manifest.preActions.map { TuistCore.ExecutionAction.from(manifest: $0) } let preActions = manifest.preActions.map { TuistCore.ExecutionAction.from(manifest: $0, projectPath: projectPath) }
let postActions = manifest.postActions.map { TuistCore.ExecutionAction.from(manifest: $0) } let postActions = manifest.postActions.map { TuistCore.ExecutionAction.from(manifest: $0, projectPath: projectPath) }
let targets: [TuistCore.TargetReference] = manifest.targets.map {
return BuildAction(targets: manifest.targets, preActions: preActions, postActions: postActions) .project(path: projectPath, target: $0)
}
return TuistCore.BuildAction(targets: targets, preActions: preActions, postActions: postActions)
} }
} }
extension TuistCore.TestAction { extension TuistCore.TestAction {
static func from(manifest: ProjectDescription.TestAction) -> TuistCore.TestAction { static func from(manifest: ProjectDescription.TestAction, projectPath: AbsolutePath) -> TuistCore.TestAction {
let targets = manifest.targets.map { TuistCore.TestableTarget.from(manifest: $0) } let targets = manifest.targets.map { TuistCore.TestableTarget.from(manifest: $0, projectPath: projectPath) }
let arguments = manifest.arguments.map { TuistCore.Arguments.from(manifest: $0) } let arguments = manifest.arguments.map { TuistCore.Arguments.from(manifest: $0) }
let configurationName = manifest.configurationName let configurationName = manifest.configurationName
let coverage = manifest.coverage let coverage = manifest.coverage
let codeCoverageTargets = manifest.codeCoverageTargets let codeCoverageTargets = manifest.codeCoverageTargets.map { TuistCore.TargetReference(projectPath: projectPath, name: $0) }
let preActions = manifest.preActions.map { TuistCore.ExecutionAction.from(manifest: $0) } let preActions = manifest.preActions.map { TuistCore.ExecutionAction.from(manifest: $0, projectPath: projectPath) }
let postActions = manifest.postActions.map { TuistCore.ExecutionAction.from(manifest: $0) } let postActions = manifest.postActions.map { TuistCore.ExecutionAction.from(manifest: $0, projectPath: projectPath) }
return TestAction(targets: targets, return TestAction(targets: targets,
arguments: arguments, arguments: arguments,
configurationName: configurationName, configurationName: configurationName,
@ -616,8 +619,8 @@ extension TuistCore.TestAction {
} }
extension TuistCore.TestableTarget { extension TuistCore.TestableTarget {
static func from(manifest: ProjectDescription.TestableTarget) -> TuistCore.TestableTarget { static func from(manifest: ProjectDescription.TestableTarget, projectPath: AbsolutePath) -> TuistCore.TestableTarget {
return TestableTarget(target: manifest.target, return TestableTarget(target: TuistCore.TargetReference(projectPath: projectPath, name: manifest.target),
skipped: manifest.isSkipped, skipped: manifest.isSkipped,
parallelizable: manifest.isParallelizable, parallelizable: manifest.isParallelizable,
randomExecutionOrdering: manifest.isRandomExecutionOrdering) randomExecutionOrdering: manifest.isRandomExecutionOrdering)
@ -625,24 +628,28 @@ extension TuistCore.TestableTarget {
} }
extension TuistCore.RunAction { extension TuistCore.RunAction {
static func from(manifest: ProjectDescription.RunAction) -> TuistCore.RunAction { static func from(manifest: ProjectDescription.RunAction, projectPath: AbsolutePath) -> TuistCore.RunAction {
let configurationName = manifest.configurationName let configurationName = manifest.configurationName
let executable = manifest.executable
let arguments = manifest.arguments.map { TuistCore.Arguments.from(manifest: $0) } let arguments = manifest.arguments.map { TuistCore.Arguments.from(manifest: $0) }
var executableResolved: TuistCore.TargetReference?
if let executable = manifest.executable {
executableResolved = TargetReference(projectPath: projectPath, name: executable)
}
return RunAction(configurationName: configurationName, return RunAction(configurationName: configurationName,
executable: executable, executable: executableResolved,
arguments: arguments) arguments: arguments)
} }
} }
extension TuistCore.ArchiveAction { extension TuistCore.ArchiveAction {
static func from(manifest: ProjectDescription.ArchiveAction) -> TuistCore.ArchiveAction { static func from(manifest: ProjectDescription.ArchiveAction, projectPath: AbsolutePath) -> TuistCore.ArchiveAction {
let configurationName = manifest.configurationName let configurationName = manifest.configurationName
let revealArchiveInOrganizer = manifest.revealArchiveInOrganizer let revealArchiveInOrganizer = manifest.revealArchiveInOrganizer
let customArchiveName = manifest.customArchiveName let customArchiveName = manifest.customArchiveName
let preActions = manifest.preActions.map { TuistCore.ExecutionAction.from(manifest: $0) } let preActions = manifest.preActions.map { TuistCore.ExecutionAction.from(manifest: $0, projectPath: projectPath) }
let postActions = manifest.postActions.map { TuistCore.ExecutionAction.from(manifest: $0) } let postActions = manifest.postActions.map { TuistCore.ExecutionAction.from(manifest: $0, projectPath: projectPath) }
return TuistCore.ArchiveAction(configurationName: configurationName, return TuistCore.ArchiveAction(configurationName: configurationName,
revealArchiveInOrganizer: revealArchiveInOrganizer, revealArchiveInOrganizer: revealArchiveInOrganizer,
@ -653,8 +660,10 @@ extension TuistCore.ArchiveAction {
} }
extension TuistCore.ExecutionAction { extension TuistCore.ExecutionAction {
static func from(manifest: ProjectDescription.ExecutionAction) -> TuistCore.ExecutionAction { static func from(manifest: ProjectDescription.ExecutionAction,
return ExecutionAction(title: manifest.title, scriptText: manifest.scriptText, target: manifest.target) projectPath: AbsolutePath) -> TuistCore.ExecutionAction {
let targetReference: TuistCore.TargetReference? = manifest.target.map { .project(path: projectPath, target: $0) }
return ExecutionAction(title: manifest.title, scriptText: manifest.scriptText, target: targetReference)
} }
} }

View File

@ -1,17 +1,13 @@
import Foundation import Foundation
import Basic
import TuistCore import TuistCore
import TuistCoreTesting import TuistCoreTesting
@testable import TuistGenerator @testable import TuistGenerator
final class MockSchemesGenerator: SchemesGenerating { final class MockSchemesGenerator: SchemesGenerating {
var generateTargetSchemesArgs: [(project: Project, generatedProject: GeneratedProject)] = [] var generateProjectSchemeArgs: [(project: Project, xcprojectPath: AbsolutePath, generatedProject: GeneratedProject, graph: Graphing)] = []
var generateProjectSchemeArgs: [(project: Project, generatedProject: GeneratedProject, graph: Graphing)] = []
func generateProjectSchemes(project: Project, xcprojectPath: AbsolutePath, generatedProject: GeneratedProject, graph: Graphing) throws {
func generateTargetSchemes(project: Project, generatedProject: GeneratedProject) throws { generateProjectSchemeArgs.append((project: project, xcprojectPath: xcprojectPath, generatedProject: generatedProject, graph: graph))
generateTargetSchemesArgs.append((project: project, generatedProject: generatedProject))
}
func generateProjectScheme(project: Project, generatedProject: GeneratedProject, graph: Graphing) throws {
generateProjectSchemeArgs.append((project: project, generatedProject: generatedProject, graph: graph))
} }
} }

View File

@ -52,7 +52,7 @@ final class ProjectGeneratorTests: TuistUnitTestCase {
// Given // Given
let temporaryPath = try self.temporaryPath() let temporaryPath = try self.temporaryPath()
let target = Target.test(name: "Target", platform: .iOS, product: .framework) let target = Target.test(name: "Target", platform: .iOS, product: .framework)
let sharedScheme = Scheme.test(name: "Target-Scheme", shared: true, buildAction: BuildAction(targets: ["Target"])) let sharedScheme = Scheme.test(name: "Target-Scheme", shared: true, buildAction: BuildAction(targets: [TargetReference(projectPath: temporaryPath, name: "Target")]))
let targets = [target] let targets = [target]
let project = Project.test(path: temporaryPath, name: "Project", targets: targets, schemes: [sharedScheme]) let project = Project.test(path: temporaryPath, name: "Project", targets: targets, schemes: [sharedScheme])
@ -79,7 +79,7 @@ final class ProjectGeneratorTests: TuistUnitTestCase {
// Given // Given
let temporaryPath = try self.temporaryPath() let temporaryPath = try self.temporaryPath()
let target = Target.test(name: "Target", platform: .iOS, product: .framework) let target = Target.test(name: "Target", platform: .iOS, product: .framework)
let localScheme = Scheme.test(name: "Target-Local", shared: false, buildAction: BuildAction(targets: ["Target"])) let localScheme = Scheme.test(name: "Target-Local", shared: false, buildAction: BuildAction(targets: [TargetReference(projectPath: temporaryPath, name: "Target")]))
let targets = [target] let targets = [target]
let project = Project.test(path: temporaryPath, name: "Project", targets: targets, schemes: [localScheme]) let project = Project.test(path: temporaryPath, name: "Project", targets: targets, schemes: [localScheme])

View File

@ -9,374 +9,518 @@ import XCTest
@testable import TuistGenerator @testable import TuistGenerator
@testable import TuistSupportTesting @testable import TuistSupportTesting
final class SchemeGeneratorTests: XCTestCase { final class SchemesGeneratorTests: XCTestCase {
var subject: SchemesGenerator! var subject: SchemesGenerator!
override func setUp() { override func setUp() {
super.setUp() super.setUp()
subject = SchemesGenerator() subject = SchemesGenerator()
} }
// MARK: - Build Action Tests
func test_projectBuildAction() { func test_schemeBuildAction_whenSingleProject() throws {
// Given
let projectPath = AbsolutePath("/somepath/Workspace/Projects/Project")
let scheme = Scheme.test(buildAction: BuildAction(targets: [TargetReference(projectPath: projectPath, name: "App")]))
let app = Target.test(name: "App", product: .app) let app = Target.test(name: "App", product: .app)
let appTests = Target.test(name: "AppTests", product: .unitTests) let targets = [app]
let appUITests = Target.test(name: "AppUITests", product: .uiTests)
let targets = [app, appTests, appUITests] let project = Project.test(path: projectPath)
let graph = Graph.create(dependencies: [(project: project, target: app, dependencies: [])])
// Then
let got = try subject.schemeBuildAction(scheme: scheme,
graph: graph,
rootPath: AbsolutePath("/somepath/Workspace"),
generatedProjects: [projectPath:
generatedProject(targets: targets, projectPath: "\(projectPath)/project.xcodeproj")])
let project = Project.test(targets: targets) // When
let graphCache = GraphLoaderCache() let result = try XCTUnwrap(got)
let graph = Graph.test(cache: graphCache) XCTAssertEqual(result.buildActionEntries.count, 1)
let entry = try XCTUnwrap(result.buildActionEntries.first)
let buildableReference = entry.buildableReference
XCTAssertEqual(entry.buildFor, [.analyzing, .archiving, .profiling, .running, .testing])
let got = subject.projectBuildAction(project: project, XCTAssertEqual(buildableReference.referencedContainer, "container:Projects/Project/project.xcodeproj")
generatedProject: generatedProject(targets: targets), XCTAssertEqual(buildableReference.buildableName, "App.app")
graph: graph) XCTAssertEqual(buildableReference.blueprintName, "App")
XCTAssertEqual(buildableReference.buildableIdentifier, "primary")
XCTAssertTrue(got.parallelizeBuild) XCTAssertEqual(result.parallelizeBuild, true)
XCTAssertTrue(got.buildImplicitDependencies) XCTAssertEqual(result.buildImplicitDependencies, true)
XCTAssertEqual(got.buildActionEntries.count, 3)
let appEntry = got.buildActionEntries[0]
let testsEntry = got.buildActionEntries[1]
let uiTestsEntry = got.buildActionEntries[2]
XCTAssertEqual(appEntry.buildFor, [.analyzing, .archiving, .profiling, .running, .testing])
XCTAssertEqual(appEntry.buildableReference.referencedContainer, "container:project.xcodeproj")
XCTAssertEqual(appEntry.buildableReference.buildableName, app.productNameWithExtension)
XCTAssertEqual(appEntry.buildableReference.blueprintName, app.name)
XCTAssertEqual(appEntry.buildableReference.buildableIdentifier, "primary")
XCTAssertEqual(testsEntry.buildFor, [.testing])
XCTAssertEqual(testsEntry.buildableReference.referencedContainer, "container:project.xcodeproj")
XCTAssertEqual(testsEntry.buildableReference.buildableName, appTests.productNameWithExtension)
XCTAssertEqual(testsEntry.buildableReference.blueprintName, appTests.name)
XCTAssertEqual(testsEntry.buildableReference.buildableIdentifier, "primary")
XCTAssertEqual(uiTestsEntry.buildFor, [.testing])
XCTAssertEqual(uiTestsEntry.buildableReference.referencedContainer, "container:project.xcodeproj")
XCTAssertEqual(uiTestsEntry.buildableReference.buildableName, appUITests.productNameWithExtension)
XCTAssertEqual(uiTestsEntry.buildableReference.blueprintName, appUITests.name)
XCTAssertEqual(uiTestsEntry.buildableReference.buildableIdentifier, "primary")
} }
func test_schemeBuildAction_whenMultipleProject() throws {
// Given
let projectAPath = AbsolutePath("/somepath/Workspace/Projects/ProjectA")
let projectBPath = AbsolutePath("/somepath/Workspace/Projects/ProjectB")
let buildAction = BuildAction(targets: [
TargetReference(projectPath: projectAPath, name: "FrameworkA"),
TargetReference(projectPath: projectBPath, name: "FrameworkB")
])
let scheme = Scheme.test(buildAction: buildAction)
let frameworkA = Target.test(name: "FrameworkA", product: .staticFramework)
let frameworkB = Target.test(name: "FrameworkB", product: .staticFramework)
let targets = [frameworkA, frameworkB]
let projectA = Project.test(path: projectAPath)
let projectB = Project.test(path: projectBPath)
let graph = Graph.create(dependencies: [
(project: projectA, target: frameworkA, dependencies: []),
(project: projectB, target: frameworkB, dependencies: [])
])
// Then
let got = try subject.schemeBuildAction(scheme: scheme,
graph: graph,
rootPath: AbsolutePath("/somepath/Workspace"),
generatedProjects: [
projectAPath: generatedProject(targets: targets, projectPath: "\(projectAPath)/project.xcodeproj"),
projectBPath: generatedProject(targets: targets, projectPath: "\(projectBPath)/project.xcodeproj")
])
func test_projectTestAction() { // When
let app = Target.test(name: "App", product: .app) let result = try XCTUnwrap(got)
let appTests = Target.test(name: "AppTests", product: .unitTests) XCTAssertEqual(result.buildActionEntries.count, 2)
let targets = [app, appTests]
let project = Project.test(targets: targets) let firstEntry = try XCTUnwrap(result.buildActionEntries[0])
let firstBuildableReference = firstEntry.buildableReference
XCTAssertEqual(firstEntry.buildFor, [.analyzing, .archiving, .profiling, .running, .testing])
let secondEntry = try XCTUnwrap(result.buildActionEntries[1])
let secondBuildableReference = secondEntry.buildableReference
XCTAssertEqual(secondEntry.buildFor, [.analyzing, .archiving, .profiling, .running, .testing])
let got = subject.projectTestAction(project: project, XCTAssertEqual(firstBuildableReference.referencedContainer, "container:Projects/ProjectA/project.xcodeproj")
generatedProject: generatedProject(targets: targets)) XCTAssertEqual(firstBuildableReference.buildableName, "FrameworkA.framework")
XCTAssertEqual(firstBuildableReference.blueprintName, "FrameworkA")
XCTAssertEqual(firstBuildableReference.buildableIdentifier, "primary")
XCTAssertEqual(secondBuildableReference.referencedContainer, "container:Projects/ProjectB/project.xcodeproj")
XCTAssertEqual(secondBuildableReference.buildableName, "FrameworkB.framework")
XCTAssertEqual(secondBuildableReference.blueprintName, "FrameworkB")
XCTAssertEqual(secondBuildableReference.buildableIdentifier, "primary")
XCTAssertEqual(got.buildConfiguration, "Debug") XCTAssertEqual(result.parallelizeBuild, true)
XCTAssertNil(got.macroExpansion) XCTAssertEqual(result.buildImplicitDependencies, true)
XCTAssertEqual(got.testables.count, 1)
let testable = got.testables.first
XCTAssertEqual(testable?.skipped, false)
XCTAssertEqual(testable?.buildableReference.referencedContainer, "container:project.xcodeproj")
XCTAssertEqual(testable?.buildableReference.buildableName, appTests.productNameWithExtension)
XCTAssertEqual(testable?.buildableReference.blueprintName, appTests.name)
XCTAssertEqual(testable?.buildableReference.buildableIdentifier, "primary")
} }
func test_schemeTestAction_when_notTestsTarget() { func test_schemeBuildAction_with_executionAction() throws {
let scheme = Scheme.test() // Given
let project = Project.test() let projectPath = AbsolutePath("/somepath/Project")
let generatedProject = GeneratedProject.test()
let got = subject.schemeTestAction(scheme: scheme, project: project, generatedProject: generatedProject)
XCTAssertEqual(got?.buildConfiguration, "Debug")
XCTAssertEqual(got?.shouldUseLaunchSchemeArgsEnv, false)
XCTAssertNil(got?.macroExpansion)
XCTAssertEqual(got?.testables.count, 0)
}
func test_schemeTestAction_when_testsTarget() {
let target = Target.test(name: "App", product: .app) let target = Target.test(name: "App", product: .app)
let testTarget = Target.test(name: "AppTests", product: .unitTests)
let preAction = ExecutionAction(title: "Pre Action", scriptText: "echo Pre Actions", target: TargetReference(projectPath: projectPath, name: "App"))
let postAction = ExecutionAction(title: "Post Action", scriptText: "echo Post Actions", target: TargetReference(projectPath: projectPath, name: "App"))
let buildAction = BuildAction.test(targets: [TargetReference(projectPath: projectPath, name: "Library")], preActions: [preAction], postActions: [postAction])
let testAction = TestAction.test(arguments: nil) let scheme = Scheme.test(name: "App", shared: true, buildAction: buildAction)
let scheme = Scheme.test(name: "AppTests", testAction: testAction) let project = Project.test(path: projectPath, targets: [target])
let project = Project.test(targets: [target, testTarget]) let graph = Graph.create(dependencies: [
(project: project, target: target, dependencies: [])
])
let pbxTarget = PBXNativeTarget(name: "App") // When
let pbxTestTarget = PBXNativeTarget(name: "AppTests", productType: .unitTestBundle) let got = try subject.schemeBuildAction(scheme: scheme,
let generatedProject = GeneratedProject.test(targets: ["App": pbxTarget, "AppTests": pbxTestTarget]) graph: graph,
rootPath: projectPath,
let got = subject.schemeTestAction(scheme: scheme, project: project, generatedProject: generatedProject) generatedProjects: createGeneratedProjects(projects: [project]))
XCTAssertEqual(got?.buildConfiguration, "Debug")
XCTAssertEqual(got?.shouldUseLaunchSchemeArgsEnv, true)
XCTAssertNil(got?.macroExpansion)
let testable = got?.testables.first
let buildableReference = testable?.buildableReference
XCTAssertEqual(testable?.skipped, false)
XCTAssertEqual(buildableReference?.referencedContainer, "container:project.xcodeproj")
XCTAssertEqual(buildableReference?.buildableName, "AppTests.xctest")
XCTAssertEqual(buildableReference?.blueprintName, "AppTests")
XCTAssertEqual(buildableReference?.buildableIdentifier, "primary")
}
func test_schemeTestAction_with_executionAction() {
let testTarget = Target.test(name: "AppTests", product: .unitTests)
let preAction = ExecutionAction(title: "Pre Action", scriptText: "echo Pre Actions", target: "AppTests")
let postAction = ExecutionAction(title: "Post Action", scriptText: "echo Post Actions", target: "AppTests")
let testAction = TestAction.test(targets: [TestableTarget(target: "AppTests")], preActions: [preAction], postActions: [postAction])
let scheme = Scheme.test(name: "AppTests", shared: true, testAction: testAction)
let project = Project.test(targets: [testTarget])
let pbxTestTarget = PBXNativeTarget(name: "AppTests", productType: .unitTestBundle)
let generatedProject = GeneratedProject.test(targets: ["AppTests": pbxTestTarget])
let got = subject.schemeTestAction(scheme: scheme, project: project, generatedProject: generatedProject)
// Then
// Pre Action // Pre Action
XCTAssertEqual(got?.preActions.first?.title, "Pre Action") XCTAssertEqual(got?.preActions.first?.title, "Pre Action")
XCTAssertEqual(got?.preActions.first?.scriptText, "echo Pre Actions") XCTAssertEqual(got?.preActions.first?.scriptText, "echo Pre Actions")
let preBuildableReference = got?.preActions.first?.environmentBuildable let preBuildableReference = got?.preActions.first?.environmentBuildable
XCTAssertEqual(preBuildableReference?.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(preBuildableReference?.referencedContainer, "container:Project.xcodeproj")
XCTAssertEqual(preBuildableReference?.buildableName, "AppTests.xctest") XCTAssertEqual(preBuildableReference?.buildableName, "App.app")
XCTAssertEqual(preBuildableReference?.blueprintName, "AppTests") XCTAssertEqual(preBuildableReference?.blueprintName, "App")
XCTAssertEqual(preBuildableReference?.buildableIdentifier, "primary") XCTAssertEqual(preBuildableReference?.buildableIdentifier, "primary")
// Post Action // Post Action
XCTAssertEqual(got?.postActions.first?.title, "Post Action") XCTAssertEqual(got?.postActions.first?.title, "Post Action")
XCTAssertEqual(got?.postActions.first?.scriptText, "echo Post Actions") XCTAssertEqual(got?.postActions.first?.scriptText, "echo Post Actions")
let postBuildableReference = got?.postActions.first?.environmentBuildable let postBuildableReference = got?.postActions.first?.environmentBuildable
XCTAssertEqual(postBuildableReference?.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(postBuildableReference?.referencedContainer, "container:Project.xcodeproj")
XCTAssertEqual(postBuildableReference?.buildableName, "AppTests.xctest") XCTAssertEqual(postBuildableReference?.buildableName, "App.app")
XCTAssertEqual(postBuildableReference?.blueprintName, "AppTests") XCTAssertEqual(postBuildableReference?.blueprintName, "App")
XCTAssertEqual(postBuildableReference?.buildableIdentifier, "primary") XCTAssertEqual(postBuildableReference?.buildableIdentifier, "primary")
}
// MARK: - Test Action Tests
func test_schemeTestAction_when_testsTarget() throws {
// Given
let target = Target.test(name: "App", product: .app)
let testTarget = Target.test(name: "AppTests", product: .unitTests)
let project = Project.test(targets: [target, testTarget])
let testAction = TestAction.test(targets: [TestableTarget(target: TargetReference(projectPath: project.path, name: "AppTests"))],
arguments: nil)
let scheme = Scheme.test(name: "AppTests", testAction: testAction)
let generatedProjects = createGeneratedProjects(projects: [project])
let graph = Graph.create(dependencies: [(project: project, target: target, dependencies: []),
(project: project, target: testTarget, dependencies: [target])])
// When
let got = try subject.schemeTestAction(scheme: scheme, graph: graph, rootPath: project.path, generatedProjects: generatedProjects)
// Then
let result = try XCTUnwrap(got)
XCTAssertEqual(result.buildConfiguration, "Debug")
XCTAssertEqual(result.shouldUseLaunchSchemeArgsEnv, true)
XCTAssertNil(result.macroExpansion)
let testable = try XCTUnwrap(result.testables.first)
let buildableReference = testable.buildableReference
XCTAssertEqual(testable.skipped, false)
XCTAssertEqual(buildableReference.referencedContainer, "container:Project.xcodeproj")
XCTAssertEqual(buildableReference.buildableName, "AppTests.xctest")
XCTAssertEqual(buildableReference.blueprintName, "AppTests")
XCTAssertEqual(buildableReference.buildableIdentifier, "primary")
} }
func test_schemeTestAction_with_codeCoverageTargets() { func test_schemeTestAction_with_codeCoverageTargets() throws {
// Given
let projectPath = AbsolutePath("/somepath/Project")
let target = Target.test(name: "App", product: .app) let target = Target.test(name: "App", product: .app)
let testTarget = Target.test(name: "AppTests", product: .unitTests) let testTarget = Target.test(name: "AppTests", product: .unitTests)
let testAction = TestAction.test(targets: [TestableTarget(target: "AppTests")], coverage: true, codeCoverageTargets: ["App"]) let testAction = TestAction.test(targets: [TestableTarget(target: TargetReference(projectPath: projectPath, name: "AppTests"))],
let buildAction = BuildAction.test(targets: ["App"]) coverage: true,
codeCoverageTargets: [TargetReference(projectPath: projectPath, name: "App")])
let buildAction = BuildAction.test(targets: [TargetReference(projectPath: projectPath, name: "App")])
let scheme = Scheme.test(name: "AppTests", shared: true, buildAction: buildAction, testAction: testAction) let scheme = Scheme.test(name: "AppTests", shared: true, buildAction: buildAction, testAction: testAction)
let project = Project.test(targets: [target, testTarget])
let project = Project.test(path: projectPath, targets: [target, testTarget])
let graph = Graph.create(dependencies: [(project: project, target: target, dependencies: []),
(project: project, target: testTarget, dependencies: [target])])
// When
let got = try subject.schemeTestAction(scheme: scheme, graph: graph, rootPath: AbsolutePath("/somepath/Workspace"), generatedProjects: createGeneratedProjects(projects: [project]))
// Then
let result = try XCTUnwrap(got)
let codeCoverageTargetsBuildableReference = try XCTUnwrap(result.codeCoverageTargets)
let pbxTarget = PBXNativeTarget(name: "App", productType: .application) XCTAssertEqual(result.onlyGenerateCoverageForSpecifiedTargets, true)
let pbxTestTarget = PBXNativeTarget(name: "AppTests", productType: .unitTestBundle) XCTAssertEqual(codeCoverageTargetsBuildableReference.count, 1)
let generatedProject = GeneratedProject.test(targets: ["AppTests": pbxTestTarget, "App": pbxTarget]) XCTAssertEqual(codeCoverageTargetsBuildableReference.first?.buildableName, "App.app")
}
func test_schemeTestAction_when_notTestsTarget() throws {
// Given
let scheme = Scheme.test()
let project = Project.test()
let generatedProject = GeneratedProject.test()
let graph = Graph.create(dependencies: [])
let got = subject.schemeTestAction(scheme: scheme, project: project, generatedProject: generatedProject) // Then
let got = try subject.schemeTestAction(scheme: scheme, graph: graph, rootPath: project.path, generatedProjects: [project.path: generatedProject])
let codeCoverageTargetsBuildableReference = got?.codeCoverageTargets // When
let result = try XCTUnwrap(got)
XCTAssertEqual(got?.onlyGenerateCoverageForSpecifiedTargets, true) XCTAssertEqual(result.buildConfiguration, "Debug")
XCTAssertEqual(codeCoverageTargetsBuildableReference?.count, 1) XCTAssertEqual(result.shouldUseLaunchSchemeArgsEnv, false)
XCTAssertEqual(codeCoverageTargetsBuildableReference?.first?.buildableName, "App.app") XCTAssertNil(result.macroExpansion)
XCTAssertEqual(result.testables.count, 0)
} }
func test_schemeTestAction_with_testable_info() {
func test_schemeTestAction_with_testable_info() throws {
// Given
let target = Target.test(name: "App", product: .app) let target = Target.test(name: "App", product: .app)
let testTarget = Target.test(name: "AppTests", product: .unitTests) let testTarget = Target.test(name: "AppTests", product: .unitTests)
let project = Project.test(targets: [target, testTarget])
let testableTarget = TestableTarget(target: "AppTests", skipped: false, parallelizable: true, randomExecutionOrdering: true) let testableTarget = TestableTarget(target: TargetReference(projectPath: project.path, name: "AppTests"),
skipped: false,
parallelizable: true,
randomExecutionOrdering: true)
let testAction = TestAction.test(targets: [testableTarget]) let testAction = TestAction.test(targets: [testableTarget])
let buildAction = BuildAction.test(targets: ["App"]) let buildAction = BuildAction.test(targets: [TargetReference(projectPath: project.path, name: "App")])
let scheme = Scheme.test(name: "AppTests", shared: true, buildAction: buildAction, testAction: testAction) let scheme = Scheme.test(name: "AppTests", shared: true, buildAction: buildAction, testAction: testAction)
let project = Project.test(targets: [target, testTarget]) let graph = Graph.create(dependencies: [(project: project, target: target, dependencies: []),
(project: project, target: testTarget, dependencies: [testTarget])])
let pbxTarget = PBXNativeTarget(name: "App", productType: .application) // When
let pbxTestTarget = PBXNativeTarget(name: "AppTests", productType: .unitTestBundle) let got = try subject.schemeTestAction(scheme: scheme, graph: graph, rootPath: project.path, generatedProjects: createGeneratedProjects(projects: [project]))
let generatedProject = GeneratedProject.test(targets: ["AppTests": pbxTestTarget, "App": pbxTarget])
let got = subject.schemeTestAction(scheme: scheme, project: project, generatedProject: generatedProject) // Then
let testableTargetReference = got!.testables[0] let testableTargetReference = got!.testables[0]
XCTAssertEqual(testableTargetReference.skipped, false) XCTAssertEqual(testableTargetReference.skipped, false)
XCTAssertEqual(testableTargetReference.parallelizable, true) XCTAssertEqual(testableTargetReference.parallelizable, true)
XCTAssertEqual(testableTargetReference.randomExecutionOrdering, true) XCTAssertEqual(testableTargetReference.randomExecutionOrdering, true)
} }
func test_schemeBuildAction() { func test_schemeBuildAction() throws {
let target = Target.test(name: "App", product: .app) let target = Target.test(name: "App", product: .app)
let pbxTarget = PBXNativeTarget(name: "App") let testTarget = Target.test(name: "AppTests", product: .unitTests)
let project = Project.test(targets: [target, testTarget])
let testAction = TestAction.test(targets: [TestableTarget(target: TargetReference(projectPath: project.path, name: "AppTests"))],
arguments: nil)
let scheme = Scheme.test(name: "AppTests", testAction: testAction)
let generatedProjects = createGeneratedProjects(projects: [project])
let graph = Graph.create(dependencies: [(project: project, target: target, dependencies: []),
(project: project, target: testTarget, dependencies: [target])])
// When
let got = try subject.schemeTestAction(scheme: scheme, graph: graph, rootPath: project.path, generatedProjects: generatedProjects)
// Then
let result = try XCTUnwrap(got)
XCTAssertEqual(result.buildConfiguration, "Debug")
XCTAssertEqual(result.shouldUseLaunchSchemeArgsEnv, true)
XCTAssertNil(result.macroExpansion)
let testable = try XCTUnwrap(result.testables.first)
let buildableReference = testable.buildableReference
let scheme = Scheme.test(name: "App") XCTAssertEqual(testable.skipped, false)
let project = Project.test(targets: [target]) XCTAssertEqual(buildableReference.referencedContainer, "container:Project.xcodeproj")
let generatedProject = GeneratedProject.test(targets: ["App": pbxTarget]) XCTAssertEqual(buildableReference.buildableName, "AppTests.xctest")
XCTAssertEqual(buildableReference.blueprintName, "AppTests")
let got = subject.schemeBuildAction(scheme: scheme, project: project, generatedProject: generatedProject) XCTAssertEqual(buildableReference.buildableIdentifier, "primary")
XCTAssertEqual(got?.buildActionEntries.count, 1)
let entry = got?.buildActionEntries.first
let buildableReference = entry?.buildableReference
XCTAssertEqual(entry?.buildFor, [.analyzing, .archiving, .profiling, .running, .testing])
XCTAssertEqual(buildableReference?.referencedContainer, "container:project.xcodeproj")
XCTAssertEqual(buildableReference?.buildableName, "App.app")
XCTAssertEqual(buildableReference?.blueprintName, "App")
XCTAssertEqual(buildableReference?.buildableIdentifier, "primary")
XCTAssertEqual(got?.parallelizeBuild, true)
XCTAssertEqual(got?.buildImplicitDependencies, true)
} }
func test_schemeTestAction_with_executionAction() throws {
// Given
let projectPath = AbsolutePath("/somepath/Project")
let testTarget = Target.test(name: "AppTests", product: .unitTests)
func test_schemeBuildAction_with_executionAction() { let preAction = ExecutionAction(title: "Pre Action", scriptText: "echo Pre Actions", target: TargetReference(projectPath: projectPath, name: "AppTests"))
let target = Target.test(name: "App", product: .app) let postAction = ExecutionAction(title: "Post Action", scriptText: "echo Post Actions", target: TargetReference(projectPath: projectPath, name: "AppTests"))
let pbxTarget = PBXNativeTarget(name: "App") let testAction = TestAction.test(targets: [TestableTarget(target: TargetReference(projectPath: projectPath, name: "AppTests"))], preActions: [preAction], postActions: [postAction])
let preAction = ExecutionAction(title: "Pre Action", scriptText: "echo Pre Actions", target: "App") let scheme = Scheme.test(name: "AppTests", shared: true, testAction: testAction)
let postAction = ExecutionAction(title: "Post Action", scriptText: "echo Post Actions", target: "App") let project = Project.test(path: projectPath, targets: [testTarget])
let buildAction = BuildAction.test(targets: ["Library"], preActions: [preAction], postActions: [postAction])
let scheme = Scheme.test(name: "App", shared: true, buildAction: buildAction) let generatedProjects = createGeneratedProjects(projects: [project])
let project = Project.test(targets: [target]) let graph = Graph.create(dependencies: [(project: project, target: testTarget, dependencies: [])])
let generatedProject = GeneratedProject.test(targets: ["App": pbxTarget])
let got = subject.schemeBuildAction(scheme: scheme, project: project, generatedProject: generatedProject) // When
let got = try subject.schemeTestAction(scheme: scheme, graph: graph, rootPath: project.path, generatedProjects: generatedProjects)
// Then
// Pre Action // Pre Action
XCTAssertEqual(got?.preActions.first?.title, "Pre Action") let result = try XCTUnwrap(got)
XCTAssertEqual(got?.preActions.first?.scriptText, "echo Pre Actions") XCTAssertEqual(result.preActions.first?.title, "Pre Action")
XCTAssertEqual(result.preActions.first?.scriptText, "echo Pre Actions")
let preBuildableReference = got?.preActions.first?.environmentBuildable let preBuildableReference = try XCTUnwrap(result.preActions.first?.environmentBuildable)
XCTAssertEqual(preBuildableReference?.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(preBuildableReference.referencedContainer, "container:Project.xcodeproj")
XCTAssertEqual(preBuildableReference?.buildableName, "App.app") XCTAssertEqual(preBuildableReference.buildableName, "AppTests.xctest")
XCTAssertEqual(preBuildableReference?.blueprintName, "App") XCTAssertEqual(preBuildableReference.blueprintName, "AppTests")
XCTAssertEqual(preBuildableReference?.buildableIdentifier, "primary") XCTAssertEqual(preBuildableReference.buildableIdentifier, "primary")
// Post Action // Post Action
XCTAssertEqual(got?.postActions.first?.title, "Post Action") XCTAssertEqual(result.postActions.first?.title, "Post Action")
XCTAssertEqual(got?.postActions.first?.scriptText, "echo Post Actions") XCTAssertEqual(result.postActions.first?.scriptText, "echo Post Actions")
let postBuildableReference = got?.postActions.first?.environmentBuildable let postBuildableReference = try XCTUnwrap(result.postActions.first?.environmentBuildable)
XCTAssertEqual(postBuildableReference?.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(postBuildableReference.referencedContainer, "container:Project.xcodeproj")
XCTAssertEqual(postBuildableReference?.buildableName, "App.app") XCTAssertEqual(postBuildableReference.buildableName, "AppTests.xctest")
XCTAssertEqual(postBuildableReference?.blueprintName, "App") XCTAssertEqual(postBuildableReference.blueprintName, "AppTests")
XCTAssertEqual(postBuildableReference?.buildableIdentifier, "primary") XCTAssertEqual(postBuildableReference.buildableIdentifier, "primary")
} }
// MARK: - Launch Action Tests
func test_schemeLaunchAction_when_runnableTarget() { func test_schemeLaunchAction() throws {
let target = Target.test(name: "App", product: .app, environment: ["a": "b"]) // Given
let pbxTarget = PBXNativeTarget(name: "App") let projectPath = AbsolutePath("/somepath/Workspace/Projects/Project")
let scheme = Scheme.test(runAction: RunAction.test(arguments: Arguments.test(environment: ["a": "b"])))
let project = Project.test(path: AbsolutePath("/project.xcodeproj"), targets: [target])
let generatedProject = GeneratedProject.test(targets: ["App": pbxTarget])
let got = subject.schemeLaunchAction(scheme: scheme, project: project, generatedProject: generatedProject) let buildAction = BuildAction.test(targets: [TargetReference(projectPath: projectPath, name: "App")])
let runAction = RunAction.test(configurationName: "Release",
executable: TargetReference(projectPath: projectPath, name: "App"),
arguments: Arguments(environment:["a": "b"], launch: ["some": true]))
let scheme = Scheme.test(buildAction: buildAction, runAction: runAction)
let app = Target.test(name: "App", product: .app, environment: ["a": "b"])
let project = Project.test(path: projectPath, targets: [app])
let graph = Graph.create(dependencies: [(project: project, target: app, dependencies: [])])
// When
let got = try subject.schemeLaunchAction(scheme: scheme,
graph: graph,
rootPath: AbsolutePath("/somepath/Workspace"),
generatedProjects: createGeneratedProjects(projects: [project]))
// Then
let result = try XCTUnwrap(got)
XCTAssertNil(result.macroExpansion)
XCTAssertNil(got?.macroExpansion) let buildableReference = try XCTUnwrap(result.runnable?.buildableReference)
let buildableReference = got?.runnable?.buildableReference XCTAssertEqual(result.buildConfiguration, "Release")
XCTAssertEqual(result.environmentVariables, [XCScheme.EnvironmentVariable(variable: "a", value: "b", enabled: true)])
XCTAssertEqual(got?.buildConfiguration, "Debug") XCTAssertEqual(buildableReference.referencedContainer, "container:Projects/Project/Project.xcodeproj")
XCTAssertEqual(got?.environmentVariables, [XCScheme.EnvironmentVariable(variable: "a", value: "b", enabled: true)]) XCTAssertEqual(buildableReference.buildableName, "App.app")
XCTAssertEqual(buildableReference?.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(buildableReference.blueprintName, "App")
XCTAssertEqual(buildableReference?.buildableName, "App.app") XCTAssertEqual(buildableReference.buildableIdentifier, "primary")
XCTAssertEqual(buildableReference?.blueprintName, "App")
XCTAssertEqual(buildableReference?.buildableIdentifier, "primary")
} }
func test_schemeLaunchAction_when_notRunnableTarget() { func test_schemeLaunchAction_when_notRunnableTarget() throws {
// Given
let projectPath = AbsolutePath("/somepath/Project")
let target = Target.test(name: "Library", platform: .iOS, product: .dynamicLibrary) let target = Target.test(name: "Library", platform: .iOS, product: .dynamicLibrary)
let pbxTarget = PBXNativeTarget(name: "App")
let buildAction = BuildAction.test(targets: ["Library"]) let buildAction = BuildAction.test(targets: [TargetReference(projectPath: projectPath, name: "Library")])
let testAction = TestAction.test(targets: [TestableTarget(target: "Library")]) let testAction = TestAction.test(targets: [TestableTarget(target: TargetReference(projectPath: projectPath, name: "Library"))])
let scheme = Scheme.test(name: "Library", buildAction: buildAction, testAction: testAction, runAction: nil) let scheme = Scheme.test(name: "Library", buildAction: buildAction, testAction: testAction, runAction: nil)
let project = Project.test(path: projectPath, targets: [target])
let graph = Graph.create(dependencies: [(project: project, target: target, dependencies: [])])
let project = Project.test(path: AbsolutePath("/project.xcodeproj"), targets: [target]) // When
let generatedProject = GeneratedProject.test(targets: ["Library": pbxTarget]) let got = try subject.schemeLaunchAction(scheme: scheme,
graph: graph,
rootPath: projectPath,
generatedProjects: createGeneratedProjects(projects: [project]))
let got = subject.schemeLaunchAction(scheme: scheme, project: project, generatedProject: generatedProject) // Then
let result = try XCTUnwrap(got)
XCTAssertNil(result.runnable?.buildableReference)
XCTAssertNil(got?.runnable?.buildableReference) XCTAssertEqual(result.buildConfiguration, "Debug")
XCTAssertEqual(result.macroExpansion?.referencedContainer, "container:Project.xcodeproj")
XCTAssertEqual(got?.buildConfiguration, "Debug") XCTAssertEqual(result.macroExpansion?.buildableName, "libLibrary.dylib")
XCTAssertEqual(got?.macroExpansion?.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(result.macroExpansion?.blueprintName, "Library")
XCTAssertEqual(got?.macroExpansion?.buildableName, "libLibrary.dylib") XCTAssertEqual(result.macroExpansion?.buildableIdentifier, "primary")
XCTAssertEqual(got?.macroExpansion?.blueprintName, "Library")
XCTAssertEqual(got?.macroExpansion?.buildableIdentifier, "primary")
} }
// MARK: - Profile Action Tests
func test_schemeProfileAction_when_runnableTarget() { func test_schemeProfileAction_when_runnableTarget() throws {
// Given
let projectPath = AbsolutePath("/somepath/Project")
let target = Target.test(name: "App", platform: .iOS, product: .app) let target = Target.test(name: "App", platform: .iOS, product: .app)
let scheme = Scheme.test()
let pbxTarget = PBXNativeTarget(name: "App") let appTargetReference = TargetReference(projectPath: projectPath, name: "App")
let project = Project.test(path: AbsolutePath("/project.xcodeproj"), targets: [target]) let buildAction = BuildAction.test(targets: [appTargetReference])
let generatedProject = GeneratedProject.test(targets: ["App": pbxTarget]) let testAction = TestAction.test(targets: [TestableTarget(target: appTargetReference)])
let runAction = RunAction.test(configurationName: "Release", executable: appTargetReference, arguments: nil)
let scheme = Scheme.test(name: "App", buildAction: buildAction, testAction: testAction, runAction: runAction)
let project = Project.test(path: projectPath, targets: [target])
let graph = Graph.create(dependencies: [(project: project, target: target, dependencies: [])])
// When
let got = try subject.schemeProfileAction(scheme: scheme,
graph: graph,
rootPath: projectPath,
generatedProjects: createGeneratedProjects(projects: [project]))
let got = subject.schemeProfileAction(scheme: scheme, project: project, generatedProject: generatedProject) // Then
let result = try XCTUnwrap(got)
let buildable = try XCTUnwrap(result.buildableProductRunnable?.buildableReference)
let buildable = got?.buildableProductRunnable?.buildableReference XCTAssertNil(result.macroExpansion)
XCTAssertEqual(result.buildableProductRunnable?.runnableDebuggingMode, "0")
XCTAssertEqual(buildable.referencedContainer, "container:Project.xcodeproj")
XCTAssertEqual(buildable.buildableName, target.productNameWithExtension)
XCTAssertEqual(buildable.blueprintName, target.name)
XCTAssertEqual(buildable.buildableIdentifier, "primary")
XCTAssertNil(got?.macroExpansion) XCTAssertEqual(result.buildConfiguration, "Release")
XCTAssertEqual(got?.buildableProductRunnable?.runnableDebuggingMode, "0") XCTAssertEqual(result.preActions, [])
XCTAssertEqual(buildable?.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(result.postActions, [])
XCTAssertEqual(buildable?.buildableName, target.productNameWithExtension) XCTAssertEqual(result.shouldUseLaunchSchemeArgsEnv, true)
XCTAssertEqual(buildable?.blueprintName, target.name) XCTAssertEqual(result.savedToolIdentifier, "")
XCTAssertEqual(buildable?.buildableIdentifier, "primary") XCTAssertEqual(result.ignoresPersistentStateOnLaunch, false)
XCTAssertEqual(result.useCustomWorkingDirectory, false)
XCTAssertEqual(got?.buildConfiguration, "Release") XCTAssertEqual(result.debugDocumentVersioning, true)
XCTAssertEqual(got?.preActions, []) XCTAssertNil(result.commandlineArguments)
XCTAssertEqual(got?.postActions, []) XCTAssertNil(result.environmentVariables)
XCTAssertEqual(got?.shouldUseLaunchSchemeArgsEnv, true) XCTAssertEqual(result.enableTestabilityWhenProfilingTests, true)
XCTAssertEqual(got?.savedToolIdentifier, "")
XCTAssertEqual(got?.ignoresPersistentStateOnLaunch, false)
XCTAssertEqual(got?.useCustomWorkingDirectory, false)
XCTAssertEqual(got?.debugDocumentVersioning, true)
XCTAssertNil(got?.commandlineArguments)
XCTAssertNil(got?.environmentVariables)
XCTAssertEqual(got?.enableTestabilityWhenProfilingTests, true)
} }
func test_schemeProfileAction_when_notRunnableTarget() { func test_schemeProfileAction_when_notRunnableTarget() throws {
// Given
let projectPath = AbsolutePath("/somepath/Project")
let target = Target.test(name: "Library", platform: .iOS, product: .dynamicLibrary) let target = Target.test(name: "Library", platform: .iOS, product: .dynamicLibrary)
let buildAction = BuildAction.test(targets: ["Library"]) let buildAction = BuildAction.test(targets: [TargetReference(projectPath: projectPath, name: "Library")])
let testAction = TestAction.test(targets: [TestableTarget(target: "Library")]) let testAction = TestAction.test(targets: [TestableTarget(target: TargetReference(projectPath: projectPath, name: "Library"))])
let scheme = Scheme.test(name: "Library", buildAction: buildAction, testAction: testAction, runAction: nil) let scheme = Scheme.test(name: "Library", buildAction: buildAction, testAction: testAction, runAction: nil)
let project = Project.test(path: AbsolutePath("/project.xcodeproj"), targets: [target]) let project = Project.test(path: projectPath, targets: [target])
let pbxTarget = PBXNativeTarget(name: "Library") let graph = Graph.create(dependencies: [(project: project, target: target, dependencies: [])])
let generatedProject = GeneratedProject.test(targets: ["Library": pbxTarget])
let got = subject.schemeProfileAction(scheme: scheme, project: project, generatedProject: generatedProject)
let buildable = got?.buildableProductRunnable?.buildableReference // When
let got = try subject.schemeProfileAction(scheme: scheme,
graph: graph,
rootPath: projectPath,
generatedProjects: createGeneratedProjects(projects: [project]))
// Then
let result = try XCTUnwrap(got)
let buildable = result.buildableProductRunnable?.buildableReference
XCTAssertNil(buildable) XCTAssertNil(buildable)
XCTAssertEqual(got?.buildConfiguration, "Release") XCTAssertEqual(result.buildConfiguration, "Release")
XCTAssertEqual(got?.preActions, []) XCTAssertEqual(result.preActions, [])
XCTAssertEqual(got?.postActions, []) XCTAssertEqual(result.postActions, [])
XCTAssertEqual(got?.shouldUseLaunchSchemeArgsEnv, true) XCTAssertEqual(result.shouldUseLaunchSchemeArgsEnv, true)
XCTAssertEqual(got?.savedToolIdentifier, "") XCTAssertEqual(result.savedToolIdentifier, "")
XCTAssertEqual(got?.ignoresPersistentStateOnLaunch, false) XCTAssertEqual(result.ignoresPersistentStateOnLaunch, false)
XCTAssertEqual(got?.useCustomWorkingDirectory, false) XCTAssertEqual(result.useCustomWorkingDirectory, false)
XCTAssertEqual(got?.debugDocumentVersioning, true) XCTAssertEqual(result.debugDocumentVersioning, true)
XCTAssertNil(got?.commandlineArguments) XCTAssertNil(result.commandlineArguments)
XCTAssertNil(got?.environmentVariables) XCTAssertNil(result.environmentVariables)
XCTAssertEqual(got?.enableTestabilityWhenProfilingTests, true) XCTAssertEqual(result.enableTestabilityWhenProfilingTests, true)
XCTAssertEqual(got?.buildConfiguration, "Release") XCTAssertEqual(result.buildConfiguration, "Release")
XCTAssertEqual(got?.macroExpansion?.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(result.macroExpansion?.referencedContainer, "container:Project.xcodeproj")
XCTAssertEqual(got?.macroExpansion?.buildableName, "libLibrary.dylib") XCTAssertEqual(result.macroExpansion?.buildableName, "libLibrary.dylib")
XCTAssertEqual(got?.macroExpansion?.blueprintName, "Library") XCTAssertEqual(result.macroExpansion?.blueprintName, "Library")
XCTAssertEqual(got?.macroExpansion?.buildableIdentifier, "primary") XCTAssertEqual(result.macroExpansion?.buildableIdentifier, "primary")
} }
func test_schemeAnalyzeAction() { func test_schemeAnalyzeAction() throws {
let got = subject.schemeAnalyzeAction(for: .test()) // Given
XCTAssertEqual(got.buildConfiguration, "Debug") let projectPath = AbsolutePath("/Project")
let target = Target.test(name: "App", platform: .iOS, product: .app)
let buildAction = BuildAction.test(targets: [TargetReference(projectPath: projectPath, name: "App")])
let scheme = Scheme.test(buildAction: buildAction)
let project = Project.test(path: projectPath, targets: [target])
let graph = Graph.create(dependencies: [(project: project, target: target, dependencies: [])])
// When
let got = try subject.schemeAnalyzeAction(scheme: scheme,
graph: graph,
rootPath: project.path,
generatedProjects: createGeneratedProjects(projects: [project]))
// Then
let result = try XCTUnwrap(got)
XCTAssertEqual(result.buildConfiguration, "Debug")
} }
func test_defaultSchemeArchiveAction() { func test_defaultSchemeArchiveAction() {
@ -385,27 +529,43 @@ final class SchemeGeneratorTests: XCTestCase {
XCTAssertEqual(got.revealArchiveInOrganizer, true) XCTAssertEqual(got.revealArchiveInOrganizer, true)
} }
func test_schemeArchiveAction() { func test_schemeArchiveAction() throws {
// Given
let projectPath = AbsolutePath("/Project")
let target = Target.test(name: "App", platform: .iOS, product: .app) let target = Target.test(name: "App", platform: .iOS, product: .app)
let scheme = Scheme.test(archiveAction: ArchiveAction.test(configurationName: "Beta Release", let buildAction = BuildAction.test(targets: [TargetReference(projectPath: projectPath, name: "App")])
revealArchiveInOrganizer: true, let archiveAction = ArchiveAction.test(configurationName: "Beta Release",
customArchiveName: "App [Beta]")) revealArchiveInOrganizer: true,
let pbxTarget = PBXNativeTarget(name: "App") customArchiveName: "App [Beta]")
let project = Project.test(path: AbsolutePath("/project.xcodeproj"), targets: [target]) let scheme = Scheme.test(buildAction: buildAction, archiveAction: archiveAction)
let generatedProject = GeneratedProject.test(targets: ["App": pbxTarget])
let project = Project.test(path: projectPath, targets: [target])
let graph = Graph.create(dependencies: [(project: project, target: target, dependencies: [])])
let got = subject.schemeArchiveAction(scheme: scheme, project: project, generatedProject: generatedProject) // When
let got = try subject.schemeArchiveAction(scheme: scheme,
graph: graph,
rootPath: project.path,
generatedProjects: createGeneratedProjects(projects: [project]))
XCTAssertEqual(got.buildConfiguration, "Beta Release") // Then
XCTAssertEqual(got.customArchiveName, "App [Beta]") let result = try XCTUnwrap(got)
XCTAssertEqual(got.revealArchiveInOrganizer, true) XCTAssertEqual(result.buildConfiguration, "Beta Release")
XCTAssertEqual(result.customArchiveName, "App [Beta]")
XCTAssertEqual(result.revealArchiveInOrganizer, true)
} }
// MARK: - Private private func createGeneratedProjects(projects: [Project]) -> [AbsolutePath: GeneratedProject] {
return Dictionary(uniqueKeysWithValues: projects.map {
private func generatedProject(targets: [Target]) -> GeneratedProject { ($0.path, generatedProject(targets: $0.targets,
projectPath: $0.path.appending(component: "\($0.name).xcodeproj").pathString))
})
}
private func generatedProject(targets: [Target], projectPath: String = "/Project.xcodeproj") -> GeneratedProject {
var pbxTargets: [String: PBXNativeTarget] = [:] var pbxTargets: [String: PBXNativeTarget] = [:]
targets.forEach { pbxTargets[$0.name] = PBXNativeTarget(name: $0.name) } targets.forEach { pbxTargets[$0.name] = PBXNativeTarget(name: $0.name) }
return GeneratedProject(pbxproj: .init(), path: AbsolutePath("/project.xcodeproj"), targets: pbxTargets, name: "project.xcodeproj") let path = AbsolutePath(projectPath)
return GeneratedProject(pbxproj: .init(), path: path, targets: pbxTargets, name: path.basename)
} }
} }

View File

@ -539,11 +539,13 @@ class GeneratorModelLoaderTest: TuistUnitTestCase {
// Given // Given
let manifest = SchemeManifest.test(name: "Scheme", let manifest = SchemeManifest.test(name: "Scheme",
shared: false) shared: false)
let projectPath = AbsolutePath("/somepath/Project")
// When // When
let model = TuistCore.Scheme.from(manifest: manifest) let model = TuistCore.Scheme.from(manifest: manifest, projectPath: projectPath)
// Then // Then
assert(scheme: model, matches: manifest) assert(scheme: model, matches: manifest, path: projectPath)
} }
func test_scheme_withActions() throws { func test_scheme_withActions() throws {
@ -564,11 +566,14 @@ class GeneratorModelLoaderTest: TuistUnitTestCase {
buildAction: buildAction, buildAction: buildAction,
testAction: testAction, testAction: testAction,
runAction: runActions) runAction: runActions)
let projectPath = AbsolutePath("/somepath/Project")
// When // When
let model = TuistCore.Scheme.from(manifest: manifest) let model = TuistCore.Scheme.from(manifest: manifest, projectPath: projectPath)
// Then // Then
assert(scheme: model, matches: manifest) assert(scheme: model, matches: manifest, path: projectPath)
} }
func test_generatorModelLoaderError_type() { func test_generatorModelLoaderError_type() {
@ -791,16 +796,17 @@ class GeneratorModelLoaderTest: TuistUnitTestCase {
func assert(scheme: TuistCore.Scheme, func assert(scheme: TuistCore.Scheme,
matches manifest: ProjectDescription.Scheme, matches manifest: ProjectDescription.Scheme,
path: AbsolutePath,
file: StaticString = #file, file: StaticString = #file,
line: UInt = #line) { line: UInt = #line) {
XCTAssertEqual(scheme.name, manifest.name, file: file, line: line) XCTAssertEqual(scheme.name, manifest.name, file: file, line: line)
XCTAssertEqual(scheme.shared, manifest.shared, file: file, line: line) XCTAssertEqual(scheme.shared, manifest.shared, file: file, line: line)
optionalAssert(scheme.buildAction, manifest.buildAction) { optionalAssert(scheme.buildAction, manifest.buildAction) {
assert(buildAction: $0, matches: $1, file: file, line: line) assert(buildAction: $0, matches: $1, path: path, file: file, line: line)
} }
optionalAssert(scheme.testAction, manifest.testAction) { optionalAssert(scheme.testAction, manifest.testAction) {
assert(testAction: $0, matches: $1, file: file, line: line) assert(testAction: $0, matches: $1, path: path, file: file, line: line)
} }
optionalAssert(scheme.runAction, manifest.runAction) { optionalAssert(scheme.runAction, manifest.runAction) {
@ -810,16 +816,19 @@ class GeneratorModelLoaderTest: TuistUnitTestCase {
func assert(buildAction: TuistCore.BuildAction, func assert(buildAction: TuistCore.BuildAction,
matches manifest: ProjectDescription.BuildAction, matches manifest: ProjectDescription.BuildAction,
path: AbsolutePath,
file: StaticString = #file, file: StaticString = #file,
line: UInt = #line) { line: UInt = #line) {
XCTAssertEqual(buildAction.targets, manifest.targets, file: file, line: line) XCTAssertEqual(buildAction.targets, manifest.targets.map { TargetReference(projectPath: path, name: $0) }, file: file, line: line)
} }
func assert(testAction: TuistCore.TestAction, func assert(testAction: TuistCore.TestAction,
matches manifest: ProjectDescription.TestAction, matches manifest: ProjectDescription.TestAction,
path: AbsolutePath,
file: StaticString = #file, file: StaticString = #file,
line: UInt = #line) { line: UInt = #line) {
let targets = manifest.targets.map { TestableTarget.from(manifest: $0) }
let targets = manifest.targets.map { TestableTarget.from(manifest: $0, projectPath: path) }
XCTAssertEqual(testAction.targets, targets, file: file, line: line) XCTAssertEqual(testAction.targets, targets, file: file, line: line)
XCTAssertTrue(testAction.configurationName == manifest.configurationName, file: file, line: line) XCTAssertTrue(testAction.configurationName == manifest.configurationName, file: file, line: line)
XCTAssertEqual(testAction.coverage, manifest.coverage, file: file, line: line) XCTAssertEqual(testAction.coverage, manifest.coverage, file: file, line: line)
@ -832,10 +841,13 @@ class GeneratorModelLoaderTest: TuistUnitTestCase {
matches manifest: ProjectDescription.RunAction, matches manifest: ProjectDescription.RunAction,
file: StaticString = #file, file: StaticString = #file,
line: UInt = #line) { line: UInt = #line) {
XCTAssertEqual(runAction.executable, manifest.executable, file: file, line: line) var runActionExecutable: String?
if let executable = runAction.executable { runActionExecutable = executable.name }
XCTAssertEqual(runActionExecutable, manifest.executable, file: file, line: line)
XCTAssertTrue(runAction.configurationName == manifest.configurationName, file: file, line: line) XCTAssertTrue(runAction.configurationName == manifest.configurationName, file: file, line: line)
optionalAssert(runAction.arguments, manifest.arguments) { optionalAssert(runAction.arguments, manifest.arguments) {
assert(arguments: $0, matches: $1, file: file, line: line) self.assert(arguments: $0, matches: $1, file: file, line: line)
} }
} }