Generate a scheme with all the project targets (#226)

* Generate project scheme

* Test the generation of the project scheme

* Sort the targets based on the dependencies between them

* Add acceptance tests

* Address comments

* Update changelog and fix Dangerfile
This commit is contained in:
Pedro Piñera Buendía 2019-02-18 08:52:07 -05:00 committed by GitHub
parent d6ae3e4783
commit 92f37c4390
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 554 additions and 49 deletions

View File

@ -13,6 +13,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
- Integration tests for `generate` command https://github.com/tuist/tuist/pull/208 by @marciniwanicki & @kwridan - Integration tests for `generate` command https://github.com/tuist/tuist/pull/208 by @marciniwanicki & @kwridan
- Frequently asked questions to the documentation https://github.com/tuist/tuist/pull/223/ by @pepibumur. - Frequently asked questions to the documentation https://github.com/tuist/tuist/pull/223/ by @pepibumur.
- Generate a scheme with all the project targets https://github.com/tuist/tuist/pull/226 by @pepibumur
### Removed ### Removed

View File

@ -8,7 +8,7 @@ unless git.modified_files.include?("CHANGELOG.md")
Please include a CHANGELOG entry. Please include a CHANGELOG entry.
You can find it at [CHANGELOG.md](https://github.com/tuist/tuist/blob/master/CHANGELOG.md). You can find it at [CHANGELOG.md](https://github.com/tuist/tuist/blob/master/CHANGELOG.md).
MESSAGE MESSAGE
raise(message) warn(message)
end end
# Swiftlint # Swiftlint

View File

@ -148,6 +148,9 @@ final class ProjectGenerator: ProjectGenerating {
targets: nativeTargets) targets: nativeTargets)
try schemesGenerator.generateTargetSchemes(project: project, try schemesGenerator.generateTargetSchemes(project: project,
generatedProject: generatedProject) generatedProject: generatedProject)
try schemesGenerator.generateProjectScheme(project: project,
generatedProject: generatedProject,
graph: graph)
return generatedProject return generatedProject
} }

View File

@ -13,9 +13,27 @@ protocol SchemesGenerating {
/// - 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 generateTargetSchemes(project: Project,
generatedProject: GeneratedProject) throws generatedProject: GeneratedProject) throws
/// Generates a project scheme to build & test the all the project targets.
///
/// - Parameters:
/// - project: Project manifest.
/// - generatedProject: Generated Xcode project.
/// - graph: Dependencies graph.
/// - Throws: An error if the generation of the scheme fails.
func generateProjectScheme(project: Project,
generatedProject: GeneratedProject,
graph: Graphing) throws
} }
final class SchemesGenerator: SchemesGenerating { final class SchemesGenerator: SchemesGenerating {
/// Default last upgrade version for generated schemes.
private static let defaultLastUpgradeVersion = "1010"
/// Default version for generated schemes.
private static let defaultVersion = "1.3"
/// Instance to interact with the file system. /// Instance to interact with the file system.
let fileHandler: FileHandling let fileHandler: FileHandling
@ -42,6 +60,92 @@ final class SchemesGenerator: SchemesGenerating {
} }
} }
/// Generates a project scheme to build & test the all the project targets.
///
/// - Parameters:
/// - project: Project manifest.
/// - generatedProject: Generated Xcode project.
/// - graph: Dependencies graph.
/// - Throws: An error if the generation of the scheme fails.
func generateProjectScheme(project: Project,
generatedProject: GeneratedProject,
graph: Graphing) throws {
let name = "\(project.name)-Project"
let schemesDirectory = try createSchemesDirectory(projectPath: generatedProject.path)
let path = schemesDirectory.appending(component: "\(name).xcscheme")
let scheme = XCScheme(name: name,
lastUpgradeVersion: SchemesGenerator.defaultLastUpgradeVersion,
version: SchemesGenerator.defaultVersion,
buildAction: projectBuildAction(project: project,
generatedProject: generatedProject,
graph: graph),
testAction: projectTestAction(project: project,
generatedProject: generatedProject))
try scheme.write(path: path.path, override: true)
}
/// Returns the build action for the project scheme.
///
/// - Parameters:
/// - project: Project manifest.
/// - generatedProject: Generated Xcode project.
/// - graph: Dependencies graph.
/// - Returns: Scheme build action.
func projectBuildAction(project: Project,
generatedProject: GeneratedProject,
graph: Graphing) -> XCScheme.BuildAction {
let targets = project.sortedTargetsForProjectScheme(graph: graph)
let entries: [XCScheme.BuildAction.Entry] = targets.map { (target) -> XCScheme.BuildAction.Entry in
let pbxTarget = generatedProject.targets[target.name]!
let buildableReference = targetBuildableReference(target: target,
pbxTarget: pbxTarget,
projectPath: generatedProject.path)
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,
buildFor: buildFor)
}
return XCScheme.BuildAction(buildActionEntries: entries,
parallelizeBuild: 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,
projectPath: generatedProject.path)
let testable = XCScheme.TestableReference(skipped: false,
buildableReference: reference)
testables.append(testable)
}
return XCScheme.TestAction(buildConfiguration: "Debug",
macroExpansion: nil,
testables: testables)
}
/// Generates the scheme for a given target. /// Generates the scheme for a given target.
/// ///
/// - Parameters: /// - Parameters:
@ -56,22 +160,22 @@ final class SchemesGenerator: SchemesGenerating {
let schemePath = schemesDirectory.appending(component: "\(target.name).xcscheme") let schemePath = schemesDirectory.appending(component: "\(target.name).xcscheme")
let scheme = XCScheme(name: target.name, let scheme = XCScheme(name: target.name,
lastUpgradeVersion: "1010", lastUpgradeVersion: SchemesGenerator.defaultLastUpgradeVersion,
version: "1.3", version: SchemesGenerator.defaultVersion,
buildAction: buildAction(target: target, buildAction: targetBuildAction(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath), projectPath: projectPath),
testAction: testAction(target: target, testAction: targetTestAction(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath), projectPath: projectPath),
launchAction: launchAction(target: target, launchAction: targetLaunchAction(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath), projectPath: projectPath),
profileAction: profileAction(target: target, profileAction: targetProfileAction(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath), projectPath: projectPath),
analyzeAction: analyzeAction(), analyzeAction: targetAnalyzeAction(),
archiveAction: archiveAction()) archiveAction: targetArchiveAction())
try scheme.write(path: schemePath.path, override: true) try scheme.write(path: schemePath.path, override: true)
} }
@ -82,12 +186,12 @@ final class SchemesGenerator: SchemesGenerating {
/// - pbxTarget: Xcode native target. /// - pbxTarget: Xcode native target.
/// - projectPath: Path to the Xcode project. /// - projectPath: Path to the Xcode project.
/// - Returns: Scheme test action. /// - Returns: Scheme test action.
func testAction(target: Target, func targetTestAction(target: Target,
pbxTarget: PBXNativeTarget, pbxTarget: PBXNativeTarget,
projectPath: AbsolutePath) -> XCScheme.TestAction? { projectPath: AbsolutePath) -> XCScheme.TestAction? {
var testables: [XCScheme.TestableReference] = [] var testables: [XCScheme.TestableReference] = []
if target.product.testsBundle { if target.product.testsBundle {
let reference = buildableReference(target: target, let reference = targetBuildableReference(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath) projectPath: projectPath)
let testable = XCScheme.TestableReference(skipped: false, let testable = XCScheme.TestableReference(skipped: false,
@ -106,14 +210,14 @@ final class SchemesGenerator: SchemesGenerating {
/// - pbxTarget: Xcode native target. /// - pbxTarget: Xcode native target.
/// - projectPath: Path to the Xcode project. /// - projectPath: Path to the Xcode project.
/// - Returns: Scheme build action. /// - Returns: Scheme build action.
func buildAction(target: Target, func targetBuildAction(target: Target,
pbxTarget: PBXNativeTarget, pbxTarget: PBXNativeTarget,
projectPath: AbsolutePath) -> XCScheme.BuildAction? { projectPath: AbsolutePath) -> XCScheme.BuildAction? {
let buildFor: [XCScheme.BuildAction.Entry.BuildFor] = [ let buildFor: [XCScheme.BuildAction.Entry.BuildFor] = [
.analyzing, .archiving, .profiling, .running, .testing .analyzing, .archiving, .profiling, .running, .testing
] ]
let buildableReference = self.buildableReference(target: target, let buildableReference = targetBuildableReference(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath) projectPath: projectPath)
var entries: [XCScheme.BuildAction.Entry] = [] var entries: [XCScheme.BuildAction.Entry] = []
@ -131,12 +235,12 @@ final class SchemesGenerator: SchemesGenerating {
/// - pbxTarget: Xcode native target. /// - pbxTarget: Xcode native target.
/// - projectPath: Path to the Xcode project. /// - projectPath: Path to the Xcode project.
/// - Returns: Scheme launch action. /// - Returns: Scheme launch action.
func launchAction(target: Target, func targetLaunchAction(target: Target,
pbxTarget: PBXNativeTarget, pbxTarget: PBXNativeTarget,
projectPath: AbsolutePath) -> XCScheme.LaunchAction? { projectPath: AbsolutePath) -> XCScheme.LaunchAction? {
var buildableProductRunnable: XCScheme.BuildableProductRunnable? var buildableProductRunnable: XCScheme.BuildableProductRunnable?
var macroExpansion: XCScheme.BuildableReference? var macroExpansion: XCScheme.BuildableReference?
let buildableReference = self.buildableReference(target: target, pbxTarget: pbxTarget, projectPath: projectPath) let buildableReference = targetBuildableReference(target: target, pbxTarget: pbxTarget, projectPath: projectPath)
if target.product.runnable { if target.product.runnable {
buildableProductRunnable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference, runnableDebuggingMode: "0") buildableProductRunnable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference, runnableDebuggingMode: "0")
} else { } else {
@ -158,12 +262,12 @@ final class SchemesGenerator: SchemesGenerating {
/// - pbxTarget: Xcode native target. /// - pbxTarget: Xcode native target.
/// - projectPath: Path to the Xcode project. /// - projectPath: Path to the Xcode project.
/// - Returns: Scheme profile action. /// - Returns: Scheme profile action.
func profileAction(target: Target, func targetProfileAction(target: Target,
pbxTarget: PBXNativeTarget, pbxTarget: PBXNativeTarget,
projectPath: AbsolutePath) -> XCScheme.ProfileAction? { projectPath: AbsolutePath) -> XCScheme.ProfileAction? {
var buildableProductRunnable: XCScheme.BuildableProductRunnable? var buildableProductRunnable: XCScheme.BuildableProductRunnable?
var macroExpansion: XCScheme.BuildableReference? var macroExpansion: XCScheme.BuildableReference?
let buildableReference = self.buildableReference(target: target, pbxTarget: pbxTarget, projectPath: projectPath) let buildableReference = targetBuildableReference(target: target, pbxTarget: pbxTarget, projectPath: projectPath)
if target.product.runnable { if target.product.runnable {
buildableProductRunnable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference, runnableDebuggingMode: "0") buildableProductRunnable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference, runnableDebuggingMode: "0")
@ -182,7 +286,7 @@ final class SchemesGenerator: SchemesGenerating {
/// - pbxTarget: Xcode native target. /// - pbxTarget: Xcode native target.
/// - projectPath: Path to the Xcode project. /// - projectPath: Path to the Xcode project.
/// - Returns: Buildable reference. /// - Returns: Buildable reference.
func buildableReference(target: Target, pbxTarget: PBXNativeTarget, projectPath: AbsolutePath) -> XCScheme.BuildableReference { func targetBuildableReference(target: Target, pbxTarget: PBXNativeTarget, projectPath: AbsolutePath) -> XCScheme.BuildableReference {
let projectName = projectPath.components.last! let projectName = projectPath.components.last!
return XCScheme.BuildableReference(referencedContainer: "container:\(projectName)", return XCScheme.BuildableReference(referencedContainer: "container:\(projectName)",
blueprint: pbxTarget, blueprint: pbxTarget,
@ -194,14 +298,14 @@ final class SchemesGenerator: SchemesGenerating {
/// Returns the scheme analyze action for a given target. /// Returns the scheme analyze action for a given target.
/// ///
/// - Returns: Scheme analyze action. /// - Returns: Scheme analyze action.
func analyzeAction() -> XCScheme.AnalyzeAction { func targetAnalyzeAction() -> XCScheme.AnalyzeAction {
return XCScheme.AnalyzeAction(buildConfiguration: "Debug") return XCScheme.AnalyzeAction(buildConfiguration: "Debug")
} }
/// Returns the scheme archive action for a given target. /// Returns the scheme archive action for a given target.
/// ///
/// - Returns: Scheme archive action. /// - Returns: Scheme archive action.
func archiveAction() -> XCScheme.ArchiveAction { func targetArchiveAction() -> XCScheme.ArchiveAction {
return XCScheme.ArchiveAction(buildConfiguration: "Release", return XCScheme.ArchiveAction(buildConfiguration: "Release",
revealArchiveInOrganizer: true) revealArchiveInOrganizer: true)
} }

View File

@ -164,9 +164,9 @@ final class TargetGenerator: TargetGenerating {
graph: Graphing) throws { graph: Graphing) throws {
try targets.forEach { targetSpec in try targets.forEach { targetSpec in
let dependencies = graph.targetDependencies(path: path, name: targetSpec.name) let dependencies = graph.targetDependencies(path: path, name: targetSpec.name)
try dependencies.forEach { dependencyName in try dependencies.forEach { dependency in
let target = nativeTargets[targetSpec.name]! let target = nativeTargets[targetSpec.name]!
let dependency = nativeTargets[dependencyName]! let dependency = nativeTargets[dependency.target.name]!
_ = try target.addDependency(target: dependency) _ = try target.addDependency(target: dependency)
} }
} }

View File

@ -46,7 +46,7 @@ protocol Graphing: AnyObject {
func linkableDependencies(path: AbsolutePath, name: String) throws -> [DependencyReference] func linkableDependencies(path: AbsolutePath, name: String) throws -> [DependencyReference]
func librariesPublicHeadersFolders(path: AbsolutePath, name: String) -> [AbsolutePath] func librariesPublicHeadersFolders(path: AbsolutePath, name: String) -> [AbsolutePath]
func embeddableFrameworks(path: AbsolutePath, name: String, system: Systeming) throws -> [DependencyReference] func embeddableFrameworks(path: AbsolutePath, name: String, system: Systeming) throws -> [DependencyReference]
func targetDependencies(path: AbsolutePath, name: String) -> [String] func targetDependencies(path: AbsolutePath, name: String) -> [TargetNode]
func staticLibraryDependencies(path: AbsolutePath, name: String) -> [DependencyReference] func staticLibraryDependencies(path: AbsolutePath, name: String) -> [DependencyReference]
// MARK: - Depth First Search // MARK: - Depth First Search
@ -91,14 +91,13 @@ class Graph: Graphing {
return cache.precompiledNodes.values.compactMap { $0 as? FrameworkNode } return cache.precompiledNodes.values.compactMap { $0 as? FrameworkNode }
} }
func targetDependencies(path: AbsolutePath, name: String) -> [String] { 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 {
return [] return []
} }
return targetNode.targetDependencies return targetNode.targetDependencies
.filter { $0.path == path } .filter { $0.path == path }
.map(\.target.name)
} }
func staticLibraryDependencies(path: AbsolutePath, name: String) -> [DependencyReference] { func staticLibraryDependencies(path: AbsolutePath, name: String) -> [DependencyReference] {

View File

@ -2,7 +2,7 @@ import Basic
import Foundation import Foundation
import TuistCore import TuistCore
class Project: Equatable { class Project: Equatable, CustomStringConvertible {
// MARK: - Attributes // MARK: - Attributes
/// Path to the folder that contains the project manifest. /// Path to the folder that contains the project manifest.
@ -74,6 +74,47 @@ class Project: Equatable {
settings = try settingsJSON.map({ try Settings(dictionary: $0, projectPath: path, fileHandler: fileHandler) }) settings = try settingsJSON.map({ try Settings(dictionary: $0, projectPath: path, fileHandler: fileHandler) })
} }
/// It returns the project targets sorted based on the target type and the dependencies between them.
/// The most dependent and non-tests targets are sorted first in the list.
///
/// - Parameter graph: Dependencies graph.
/// - Returns: Sorted targets.
func sortedTargetsForProjectScheme(graph: Graphing) -> [Target] {
return targets.sorted { (first, second) -> Bool in
// First criteria: Test bundles at the end
if first.product.testsBundle && !second.product.testsBundle {
return false
}
if !first.product.testsBundle && second.product.testsBundle {
return true
}
// Second criteria: Most dependent targets first.
let secondDependencies = graph.targetDependencies(path: self.path, name: second.name)
.filter({ $0.path == self.path })
.map({ $0.target.name })
let firstDependencies = graph.targetDependencies(path: self.path, name: first.name)
.filter({ $0.path == self.path })
.map({ $0.target.name })
if secondDependencies.contains(first.name) {
return true
} else if firstDependencies.contains(second.name) {
return false
// Third criteria: Name
} else {
return first.name < second.name
}
}
}
// MARK: - CustomStringConvertible
var description: String {
return self.name
}
// MARK: - Equatable // MARK: - Equatable
static func == (lhs: Project, rhs: Project) -> Bool { static func == (lhs: Project, rhs: Project) -> Bool {

View File

@ -130,10 +130,14 @@ class Target: GraphInitiatable, Equatable {
} }
} }
/// Return true if the target can be linked.
///
/// - Returns: True if the target can be linked from another target.
func isLinkable() -> Bool { func isLinkable() -> Bool {
return product == .dynamicLibrary || product == .staticLibrary || product == .framework return product == .dynamicLibrary || product == .staticLibrary || product == .framework
} }
/// Returns the product name including the extension.
var productName: String { var productName: String {
switch product { switch product {
case .staticLibrary, .dynamicLibrary: case .staticLibrary, .dynamicLibrary:
@ -185,3 +189,17 @@ class Target: GraphInitiatable, Equatable {
lhs.environment == rhs.environment lhs.environment == rhs.environment
} }
} }
extension Sequence where Element == Target {
/// Filters and returns only the targets that are test bundles.
var testBundles: [Target] {
return filter({ $0.product.testsBundle })
}
/// Filters and returns only the targets that are apps.
var apps: [Target] {
return filter({ $0.product == .app})
}
}

View File

@ -3,8 +3,13 @@ import Foundation
final class MockSchemesGenerator: SchemesGenerating { final class MockSchemesGenerator: SchemesGenerating {
var generateTargetSchemesArgs: [(project: Project, generatedProject: GeneratedProject)] = [] var generateTargetSchemesArgs: [(project: Project, generatedProject: GeneratedProject)] = []
var generateProjectSchemeArgs: [(project: Project, generatedProject: GeneratedProject, graph: Graphing)] = []
func generateTargetSchemes(project: Project, generatedProject: GeneratedProject) throws { func generateTargetSchemes(project: Project, generatedProject: GeneratedProject) throws {
generateTargetSchemesArgs.append((project: project, generatedProject: generatedProject)) 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

@ -15,14 +15,77 @@ final class SchemeGeneratorTests: XCTestCase {
subject = SchemesGenerator() subject = SchemesGenerator()
} }
func test_testAction_when_notTestsTarget() { func test_projectBuildAction() {
let app = Target.test(name: "App", product: .app)
let appTests = Target.test(name: "AppTests", product: .unitTests)
let appUITests = Target.test(name: "AppUITests", product: .uiTests)
let targets = [app, appTests, appUITests]
let project = Project.test(targets: targets)
let graphCache = GraphLoaderCache()
let graph = Graph.test(cache: graphCache)
let got = subject.projectBuildAction(project: project,
generatedProject: generatedProject(targets: targets),
graph: graph)
XCTAssertTrue(got.parallelizeBuild)
XCTAssertTrue(got.buildImplicitDependencies)
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.productName)
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.productName)
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.productName)
XCTAssertEqual(uiTestsEntry.buildableReference.blueprintName, appUITests.name)
XCTAssertEqual(uiTestsEntry.buildableReference.buildableIdentifier, "primary")
}
func test_projectTestAction() {
let app = Target.test(name: "App", product: .app)
let appTests = Target.test(name: "AppTests", product: .unitTests)
let targets = [app, appTests]
let project = Project.test(targets: targets)
let got = subject.projectTestAction(project: project,
generatedProject: generatedProject(targets: targets))
XCTAssertEqual(got.buildConfiguration, "Debug")
XCTAssertNil(got.macroExpansion)
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.productName)
XCTAssertEqual(testable?.buildableReference.blueprintName, appTests.name)
XCTAssertEqual(testable?.buildableReference.buildableIdentifier, "primary")
}
func test_targetTestAction_when_notTestsTarget() {
let target = Target.test(name: "AppTests", product: .app) let target = Target.test(name: "AppTests", product: .app)
let pbxTarget = PBXNativeTarget(name: "App") let pbxTarget = PBXNativeTarget(name: "App")
let projectPath = AbsolutePath("/project.xcodeproj") let projectPath = AbsolutePath("/project.xcodeproj")
let got = subject.testAction(target: target, let got = subject.targetTestAction(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath) projectPath: projectPath)
XCTAssertEqual(got?.buildConfiguration, "Debug") XCTAssertEqual(got?.buildConfiguration, "Debug")
XCTAssertEqual(got?.shouldUseLaunchSchemeArgsEnv, true) XCTAssertEqual(got?.shouldUseLaunchSchemeArgsEnv, true)
@ -30,12 +93,12 @@ final class SchemeGeneratorTests: XCTestCase {
XCTAssertEqual(got?.testables.count, 0) XCTAssertEqual(got?.testables.count, 0)
} }
func test_testAction_when_testsTarget() { func test_targetTestAction_when_testsTarget() {
let target = Target.test(name: "AppTests", product: .unitTests) let target = Target.test(name: "AppTests", product: .unitTests)
let pbxTarget = PBXNativeTarget(name: "App") let pbxTarget = PBXNativeTarget(name: "App")
let projectPath = AbsolutePath("/project.xcodeproj") let projectPath = AbsolutePath("/project.xcodeproj")
let got = subject.testAction(target: target, let got = subject.targetTestAction(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath) projectPath: projectPath)
@ -52,12 +115,12 @@ final class SchemeGeneratorTests: XCTestCase {
XCTAssertEqual(buildableReference?.buildableIdentifier, "primary") XCTAssertEqual(buildableReference?.buildableIdentifier, "primary")
} }
func test_buildAction() { func test_targetBuildAction() {
let target = Target.test(name: "App", product: .app) let target = Target.test(name: "App", product: .app)
let pbxTarget = PBXNativeTarget(name: "App") let pbxTarget = PBXNativeTarget(name: "App")
let projectPath = AbsolutePath("/project.xcodeproj") let projectPath = AbsolutePath("/project.xcodeproj")
let got = subject.buildAction(target: target, let got = subject.targetBuildAction(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath) projectPath: projectPath)
@ -75,11 +138,11 @@ final class SchemeGeneratorTests: XCTestCase {
XCTAssertEqual(got?.buildImplicitDependencies, true) XCTAssertEqual(got?.buildImplicitDependencies, true)
} }
func test_launchAction_when_runnableTarget() { func test_targetLaunchAction_when_runnableTarget() {
let target = Target.test(name: "App", product: .app, environment: ["a": "b"]) let target = Target.test(name: "App", product: .app, environment: ["a": "b"])
let pbxTarget = PBXNativeTarget(name: "App") let pbxTarget = PBXNativeTarget(name: "App")
let projectPath = AbsolutePath("/project.xcodeproj") let projectPath = AbsolutePath("/project.xcodeproj")
let got = subject.launchAction(target: target, let got = subject.targetLaunchAction(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath) projectPath: projectPath)
@ -94,13 +157,13 @@ final class SchemeGeneratorTests: XCTestCase {
XCTAssertEqual(buildableReference?.buildableIdentifier, "primary") XCTAssertEqual(buildableReference?.buildableIdentifier, "primary")
} }
func test_launchAction_when_notRunnableTarget() { func test_targetLaunchAction_when_notRunnableTarget() {
let target = Target.test(name: "Library", let target = Target.test(name: "Library",
platform: .iOS, platform: .iOS,
product: .dynamicLibrary) product: .dynamicLibrary)
let pbxTarget = PBXNativeTarget(name: "App") let pbxTarget = PBXNativeTarget(name: "App")
let projectPath = AbsolutePath("/project.xcodeproj") let projectPath = AbsolutePath("/project.xcodeproj")
let got = subject.launchAction(target: target, let got = subject.targetLaunchAction(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath) projectPath: projectPath)
@ -113,13 +176,13 @@ final class SchemeGeneratorTests: XCTestCase {
XCTAssertEqual(got?.macroExpansion?.buildableIdentifier, "primary") XCTAssertEqual(got?.macroExpansion?.buildableIdentifier, "primary")
} }
func test_profileAction_when_runnableTarget() { func test_targetProfileAction_when_runnableTarget() {
let target = Target.test(name: "App", let target = Target.test(name: "App",
platform: .iOS, platform: .iOS,
product: .app) product: .app)
let pbxTarget = PBXNativeTarget(name: "App") let pbxTarget = PBXNativeTarget(name: "App")
let projectPath = AbsolutePath("/project.xcodeproj") let projectPath = AbsolutePath("/project.xcodeproj")
let got = subject.profileAction(target: target, let got = subject.targetProfileAction(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath) projectPath: projectPath)
@ -145,13 +208,13 @@ final class SchemeGeneratorTests: XCTestCase {
XCTAssertEqual(got?.enableTestabilityWhenProfilingTests, true) XCTAssertEqual(got?.enableTestabilityWhenProfilingTests, true)
} }
func test_profileAction_when_notRunnableTarget() { func test_targetProfileAction_when_notRunnableTarget() {
let target = Target.test(name: "Library", let target = Target.test(name: "Library",
platform: .iOS, platform: .iOS,
product: .dynamicLibrary) product: .dynamicLibrary)
let pbxTarget = PBXNativeTarget(name: "App") let pbxTarget = PBXNativeTarget(name: "App")
let projectPath = AbsolutePath("/project.xcodeproj") let projectPath = AbsolutePath("/project.xcodeproj")
let got = subject.profileAction(target: target, let got = subject.targetProfileAction(target: target,
pbxTarget: pbxTarget, pbxTarget: pbxTarget,
projectPath: projectPath) projectPath: projectPath)
@ -177,14 +240,23 @@ final class SchemeGeneratorTests: XCTestCase {
XCTAssertEqual(got?.macroExpansion?.buildableIdentifier, "primary") XCTAssertEqual(got?.macroExpansion?.buildableIdentifier, "primary")
} }
func test_analyzeAction() { func test_targetAnalyzeAction() {
let got = subject.analyzeAction() let got = subject.targetAnalyzeAction()
XCTAssertEqual(got.buildConfiguration, "Debug") XCTAssertEqual(got.buildConfiguration, "Debug")
} }
func test_archiveAction() { func test_targetArchiveAction() {
let got = subject.archiveAction() let got = subject.targetArchiveAction()
XCTAssertEqual(got.buildConfiguration, "Release") XCTAssertEqual(got.buildConfiguration, "Release")
XCTAssertEqual(got.revealArchiveInOrganizer, true) XCTAssertEqual(got.revealArchiveInOrganizer, true)
} }
// MARK: - Private
private func generatedProject(targets: [Target]) -> GeneratedProject {
var pbxTargets: [String: PBXNativeTarget] = [:]
targets.forEach { pbxTargets[$0.name] = PBXNativeTarget(name: $0.name) }
let projectPath = AbsolutePath("/project.xcodeproj")
return GeneratedProject(path: projectPath, targets: pbxTargets)
}
} }

View File

@ -49,7 +49,7 @@ final class GraphTests: XCTestCase {
let graph = Graph.test(cache: cache) let graph = Graph.test(cache: cache)
let dependencies = graph.targetDependencies(path: project.path, let dependencies = graph.targetDependencies(path: project.path,
name: target.name) name: target.name)
XCTAssertEqual(dependencies.first, "Dependency") XCTAssertEqual(dependencies.first?.target.name, "Dependency")
} }
func test_linkableDependencies_whenPrecompiled() throws { func test_linkableDependencies_whenPrecompiled() throws {

View File

@ -0,0 +1,60 @@
import Foundation
import Basic
@testable import TuistKit
import XCTest
final class ProjectTests: XCTestCase {
func test_sortedTargetsForProjectScheme() {
let framework = Target.test(name: "Framework", product: .framework)
let app = Target.test(name: "App", product: .app)
let appTests = Target.test(name: "AppTets", product: .unitTests)
let frameworkTests = Target.test(name: "FrameworkTests", product: .unitTests)
let project = Project.test(targets: [
framework, app, appTests, frameworkTests
])
let graph = createGraph(project: project, dependencies: [
(target: framework, dependencies: []),
(target: frameworkTests, dependencies: [framework]),
(target: app, dependencies: [framework]),
(target: appTests, dependencies: [app]),
])
let got = project.sortedTargetsForProjectScheme(graph: graph)
XCTAssertEqual(got.count, 4)
XCTAssertEqual(got[0], framework)
XCTAssertEqual(got[1], app)
XCTAssertEqual(got[2], appTests)
XCTAssertEqual(got[3], frameworkTests)
}
// MARK: - Private
private func createTargetNodes(project: Project,
dependencies: [(target: Target, dependencies: [Target])]) -> [TargetNode] {
let nodesCache = Dictionary(uniqueKeysWithValues: dependencies.map {
($0.target.name, TargetNode(project: project,
target: $0.target,
dependencies: []))
})
return dependencies.map {
let node = nodesCache[$0.target.name]!
node.dependencies = $0.dependencies.map { nodesCache[$0.name]! }
return node
}
}
private func createGraph(project: Project,
dependencies: [(target: Target, dependencies: [Target])]) -> Graph {
let targetNodes = createTargetNodes(project: project, dependencies: dependencies)
let cache = GraphLoaderCache()
let graph = Graph.test(cache: cache)
targetNodes.forEach { cache.add(targetNode: $0) }
return graph
}
}

View File

@ -21,4 +21,20 @@ final class TargetTests: XCTestCase {
let target = Target.test(name: "Test", product: .app) let target = Target.test(name: "Test", product: .app)
XCTAssertEqual(target.productName, "Test.app") XCTAssertEqual(target.productName, "Test.app")
} }
func test_sequence_testBundles() {
let app = Target.test(product: .app)
let tests = Target.test(product: .unitTests)
let targets = [app, tests]
XCTAssertEqual(targets.testBundles, [tests])
}
func test_sequence_apps() {
let app = Target.test(product: .app)
let tests = Target.test(product: .unitTests)
let targets = [app, tests]
XCTAssertEqual(targets.apps, [app])
}
} }

View File

@ -0,0 +1,63 @@
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Xcode ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
### Xcode Patch ###
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcworkspace/contents.xcworkspacedata
/*.gcno
### Projects ###
*.xcodeproj
*.xcworkspace

View File

@ -0,0 +1,20 @@
import UIKit
import Framework
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var framework: FrameworkClass = FrameworkClass()
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let viewController = UIViewController()
viewController.view.backgroundColor = .white
window?.rootViewController = viewController
window?.makeKeyAndVisible()
return true
}
}

View File

@ -0,0 +1,8 @@
import Foundation
import XCTest
@testable import App
final class AppTests: XCTestCase {
}

View File

@ -0,0 +1,5 @@
import Foundation
public class FrameworkClass {
public init() {}
}

View File

@ -0,0 +1,7 @@
import Foundation
import XCTest
@testable import Framework
final class FrameworkTests: XCTestCase {
var subject: FrameworkClass = FrameworkClass()
}

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright ©. All rights reserved.</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,40 @@
import ProjectDescription
let project = Project(name: "App",
targets: [
Target(name: "App",
platform: .iOS,
product: .app,
bundleId: "io.tuist.app",
infoPlist: "Info.plist",
sources: "App/**",
dependencies: [
.target(name: "Framework")
]),
Target(name: "AppTests",
platform: .iOS,
product: .unitTests,
bundleId: "io.tuist.appTests",
infoPlist: "Info.plist",
sources: "AppTests/**",
dependencies: [
.target(name: "App")
]),
Target(name: "Framework",
platform: .iOS,
product: .framework,
bundleId: "io.tuist.framework",
infoPlist: "Info.plist",
sources: "Framework/**",
dependencies: [
]),
Target(name: "FrameworkTests",
platform: .iOS,
product: .unitTests,
bundleId: "io.tuist.frameworkTests",
infoPlist: "Info.plist",
sources: "FrameworkTests/**",
dependencies: [
.target(name: "Framework")
])
])