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:
parent
d6ae3e4783
commit
92f37c4390
|
@ -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
|
||||
- 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
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ unless git.modified_files.include?("CHANGELOG.md")
|
|||
Please include a CHANGELOG entry.
|
||||
You can find it at [CHANGELOG.md](https://github.com/tuist/tuist/blob/master/CHANGELOG.md).
|
||||
MESSAGE
|
||||
raise(message)
|
||||
warn(message)
|
||||
end
|
||||
|
||||
# Swiftlint
|
||||
|
|
|
@ -148,6 +148,9 @@ final class ProjectGenerator: ProjectGenerating {
|
|||
targets: nativeTargets)
|
||||
try schemesGenerator.generateTargetSchemes(project: project,
|
||||
generatedProject: generatedProject)
|
||||
try schemesGenerator.generateProjectScheme(project: project,
|
||||
generatedProject: generatedProject,
|
||||
graph: graph)
|
||||
|
||||
return generatedProject
|
||||
}
|
||||
|
|
|
@ -13,9 +13,27 @@ protocol SchemesGenerating {
|
|||
/// - Throws: A FatalError if the generation of the schemes fails.
|
||||
func generateTargetSchemes(project: Project,
|
||||
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 {
|
||||
|
||||
/// 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.
|
||||
let fileHandler: FileHandling
|
||||
|
||||
|
@ -41,6 +59,92 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
projectPath: generatedProject.path)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
|
@ -56,22 +160,22 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
let schemePath = schemesDirectory.appending(component: "\(target.name).xcscheme")
|
||||
|
||||
let scheme = XCScheme(name: target.name,
|
||||
lastUpgradeVersion: "1010",
|
||||
version: "1.3",
|
||||
buildAction: buildAction(target: target,
|
||||
lastUpgradeVersion: SchemesGenerator.defaultLastUpgradeVersion,
|
||||
version: SchemesGenerator.defaultVersion,
|
||||
buildAction: targetBuildAction(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath),
|
||||
testAction: testAction(target: target,
|
||||
testAction: targetTestAction(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath),
|
||||
launchAction: launchAction(target: target,
|
||||
launchAction: targetLaunchAction(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath),
|
||||
profileAction: profileAction(target: target,
|
||||
profileAction: targetProfileAction(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath),
|
||||
analyzeAction: analyzeAction(),
|
||||
archiveAction: archiveAction())
|
||||
analyzeAction: targetAnalyzeAction(),
|
||||
archiveAction: targetArchiveAction())
|
||||
try scheme.write(path: schemePath.path, override: true)
|
||||
}
|
||||
|
||||
|
@ -82,12 +186,12 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
/// - pbxTarget: Xcode native target.
|
||||
/// - projectPath: Path to the Xcode project.
|
||||
/// - Returns: Scheme test action.
|
||||
func testAction(target: Target,
|
||||
func targetTestAction(target: Target,
|
||||
pbxTarget: PBXNativeTarget,
|
||||
projectPath: AbsolutePath) -> XCScheme.TestAction? {
|
||||
var testables: [XCScheme.TestableReference] = []
|
||||
if target.product.testsBundle {
|
||||
let reference = buildableReference(target: target,
|
||||
let reference = targetBuildableReference(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath)
|
||||
let testable = XCScheme.TestableReference(skipped: false,
|
||||
|
@ -106,14 +210,14 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
/// - pbxTarget: Xcode native target.
|
||||
/// - projectPath: Path to the Xcode project.
|
||||
/// - Returns: Scheme build action.
|
||||
func buildAction(target: Target,
|
||||
func targetBuildAction(target: Target,
|
||||
pbxTarget: PBXNativeTarget,
|
||||
projectPath: AbsolutePath) -> XCScheme.BuildAction? {
|
||||
let buildFor: [XCScheme.BuildAction.Entry.BuildFor] = [
|
||||
.analyzing, .archiving, .profiling, .running, .testing
|
||||
]
|
||||
|
||||
let buildableReference = self.buildableReference(target: target,
|
||||
let buildableReference = targetBuildableReference(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath)
|
||||
var entries: [XCScheme.BuildAction.Entry] = []
|
||||
|
@ -131,12 +235,12 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
/// - pbxTarget: Xcode native target.
|
||||
/// - projectPath: Path to the Xcode project.
|
||||
/// - Returns: Scheme launch action.
|
||||
func launchAction(target: Target,
|
||||
func targetLaunchAction(target: Target,
|
||||
pbxTarget: PBXNativeTarget,
|
||||
projectPath: AbsolutePath) -> XCScheme.LaunchAction? {
|
||||
var buildableProductRunnable: XCScheme.BuildableProductRunnable?
|
||||
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 {
|
||||
buildableProductRunnable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference, runnableDebuggingMode: "0")
|
||||
} else {
|
||||
|
@ -158,12 +262,12 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
/// - pbxTarget: Xcode native target.
|
||||
/// - projectPath: Path to the Xcode project.
|
||||
/// - Returns: Scheme profile action.
|
||||
func profileAction(target: Target,
|
||||
func targetProfileAction(target: Target,
|
||||
pbxTarget: PBXNativeTarget,
|
||||
projectPath: AbsolutePath) -> XCScheme.ProfileAction? {
|
||||
var buildableProductRunnable: XCScheme.BuildableProductRunnable?
|
||||
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 {
|
||||
buildableProductRunnable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference, runnableDebuggingMode: "0")
|
||||
|
@ -182,7 +286,7 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
/// - pbxTarget: Xcode native target.
|
||||
/// - projectPath: Path to the Xcode project.
|
||||
/// - 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!
|
||||
return XCScheme.BuildableReference(referencedContainer: "container:\(projectName)",
|
||||
blueprint: pbxTarget,
|
||||
|
@ -194,14 +298,14 @@ final class SchemesGenerator: SchemesGenerating {
|
|||
/// Returns the scheme analyze action for a given target.
|
||||
///
|
||||
/// - Returns: Scheme analyze action.
|
||||
func analyzeAction() -> XCScheme.AnalyzeAction {
|
||||
func targetAnalyzeAction() -> XCScheme.AnalyzeAction {
|
||||
return XCScheme.AnalyzeAction(buildConfiguration: "Debug")
|
||||
}
|
||||
|
||||
/// Returns the scheme archive action for a given target.
|
||||
///
|
||||
/// - Returns: Scheme archive action.
|
||||
func archiveAction() -> XCScheme.ArchiveAction {
|
||||
func targetArchiveAction() -> XCScheme.ArchiveAction {
|
||||
return XCScheme.ArchiveAction(buildConfiguration: "Release",
|
||||
revealArchiveInOrganizer: true)
|
||||
}
|
||||
|
|
|
@ -164,9 +164,9 @@ final class TargetGenerator: TargetGenerating {
|
|||
graph: Graphing) throws {
|
||||
try targets.forEach { targetSpec in
|
||||
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 dependency = nativeTargets[dependencyName]!
|
||||
let dependency = nativeTargets[dependency.target.name]!
|
||||
_ = try target.addDependency(target: dependency)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ protocol Graphing: AnyObject {
|
|||
func linkableDependencies(path: AbsolutePath, name: String) throws -> [DependencyReference]
|
||||
func librariesPublicHeadersFolders(path: AbsolutePath, name: String) -> [AbsolutePath]
|
||||
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]
|
||||
|
||||
// MARK: - Depth First Search
|
||||
|
@ -91,14 +91,13 @@ class Graph: Graphing {
|
|||
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 {
|
||||
return []
|
||||
}
|
||||
|
||||
return targetNode.targetDependencies
|
||||
.filter { $0.path == path }
|
||||
.map(\.target.name)
|
||||
}
|
||||
|
||||
func staticLibraryDependencies(path: AbsolutePath, name: String) -> [DependencyReference] {
|
||||
|
|
|
@ -2,7 +2,7 @@ import Basic
|
|||
import Foundation
|
||||
import TuistCore
|
||||
|
||||
class Project: Equatable {
|
||||
class Project: Equatable, CustomStringConvertible {
|
||||
// MARK: - Attributes
|
||||
|
||||
/// Path to the folder that contains the project manifest.
|
||||
|
@ -73,6 +73,47 @@ class Project: Equatable {
|
|||
let settingsJSON: JSON? = try? json.get("settings")
|
||||
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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
return product == .dynamicLibrary || product == .staticLibrary || product == .framework
|
||||
}
|
||||
|
||||
/// Returns the product name including the extension.
|
||||
var productName: String {
|
||||
switch product {
|
||||
case .staticLibrary, .dynamicLibrary:
|
||||
|
@ -185,3 +189,17 @@ class Target: GraphInitiatable, Equatable {
|
|||
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})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,8 +3,13 @@ import Foundation
|
|||
|
||||
final class MockSchemesGenerator: SchemesGenerating {
|
||||
var generateTargetSchemesArgs: [(project: Project, generatedProject: GeneratedProject)] = []
|
||||
var generateProjectSchemeArgs: [(project: Project, generatedProject: GeneratedProject, graph: Graphing)] = []
|
||||
|
||||
func generateTargetSchemes(project: Project, generatedProject: GeneratedProject) throws {
|
||||
generateTargetSchemesArgs.append((project: project, generatedProject: generatedProject))
|
||||
}
|
||||
|
||||
func generateProjectScheme(project: Project, generatedProject: GeneratedProject, graph: Graphing) throws {
|
||||
generateProjectSchemeArgs.append((project: project, generatedProject: generatedProject, graph: graph))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,28 +14,91 @@ final class SchemeGeneratorTests: XCTestCase {
|
|||
super.setUp()
|
||||
subject = SchemesGenerator()
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
func test_testAction_when_notTestsTarget() {
|
||||
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 pbxTarget = PBXNativeTarget(name: "App")
|
||||
let projectPath = AbsolutePath("/project.xcodeproj")
|
||||
|
||||
let got = subject.testAction(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath)
|
||||
|
||||
|
||||
let got = subject.targetTestAction(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath)
|
||||
|
||||
XCTAssertEqual(got?.buildConfiguration, "Debug")
|
||||
XCTAssertEqual(got?.shouldUseLaunchSchemeArgsEnv, true)
|
||||
XCTAssertNil(got?.macroExpansion)
|
||||
XCTAssertEqual(got?.testables.count, 0)
|
||||
}
|
||||
|
||||
func test_testAction_when_testsTarget() {
|
||||
func test_targetTestAction_when_testsTarget() {
|
||||
let target = Target.test(name: "AppTests", product: .unitTests)
|
||||
let pbxTarget = PBXNativeTarget(name: "App")
|
||||
let projectPath = AbsolutePath("/project.xcodeproj")
|
||||
|
||||
let got = subject.testAction(target: target,
|
||||
let got = subject.targetTestAction(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath)
|
||||
|
||||
|
@ -52,12 +115,12 @@ final class SchemeGeneratorTests: XCTestCase {
|
|||
XCTAssertEqual(buildableReference?.buildableIdentifier, "primary")
|
||||
}
|
||||
|
||||
func test_buildAction() {
|
||||
func test_targetBuildAction() {
|
||||
let target = Target.test(name: "App", product: .app)
|
||||
let pbxTarget = PBXNativeTarget(name: "App")
|
||||
let projectPath = AbsolutePath("/project.xcodeproj")
|
||||
|
||||
let got = subject.buildAction(target: target,
|
||||
let got = subject.targetBuildAction(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath)
|
||||
|
||||
|
@ -75,11 +138,11 @@ final class SchemeGeneratorTests: XCTestCase {
|
|||
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 pbxTarget = PBXNativeTarget(name: "App")
|
||||
let projectPath = AbsolutePath("/project.xcodeproj")
|
||||
let got = subject.launchAction(target: target,
|
||||
let got = subject.targetLaunchAction(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath)
|
||||
|
||||
|
@ -94,13 +157,13 @@ final class SchemeGeneratorTests: XCTestCase {
|
|||
XCTAssertEqual(buildableReference?.buildableIdentifier, "primary")
|
||||
}
|
||||
|
||||
func test_launchAction_when_notRunnableTarget() {
|
||||
func test_targetLaunchAction_when_notRunnableTarget() {
|
||||
let target = Target.test(name: "Library",
|
||||
platform: .iOS,
|
||||
product: .dynamicLibrary)
|
||||
let pbxTarget = PBXNativeTarget(name: "App")
|
||||
let projectPath = AbsolutePath("/project.xcodeproj")
|
||||
let got = subject.launchAction(target: target,
|
||||
let got = subject.targetLaunchAction(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath)
|
||||
|
||||
|
@ -113,13 +176,13 @@ final class SchemeGeneratorTests: XCTestCase {
|
|||
XCTAssertEqual(got?.macroExpansion?.buildableIdentifier, "primary")
|
||||
}
|
||||
|
||||
func test_profileAction_when_runnableTarget() {
|
||||
func test_targetProfileAction_when_runnableTarget() {
|
||||
let target = Target.test(name: "App",
|
||||
platform: .iOS,
|
||||
product: .app)
|
||||
let pbxTarget = PBXNativeTarget(name: "App")
|
||||
let projectPath = AbsolutePath("/project.xcodeproj")
|
||||
let got = subject.profileAction(target: target,
|
||||
let got = subject.targetProfileAction(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath)
|
||||
|
||||
|
@ -145,13 +208,13 @@ final class SchemeGeneratorTests: XCTestCase {
|
|||
XCTAssertEqual(got?.enableTestabilityWhenProfilingTests, true)
|
||||
}
|
||||
|
||||
func test_profileAction_when_notRunnableTarget() {
|
||||
func test_targetProfileAction_when_notRunnableTarget() {
|
||||
let target = Target.test(name: "Library",
|
||||
platform: .iOS,
|
||||
product: .dynamicLibrary)
|
||||
let pbxTarget = PBXNativeTarget(name: "App")
|
||||
let projectPath = AbsolutePath("/project.xcodeproj")
|
||||
let got = subject.profileAction(target: target,
|
||||
let got = subject.targetProfileAction(target: target,
|
||||
pbxTarget: pbxTarget,
|
||||
projectPath: projectPath)
|
||||
|
||||
|
@ -177,14 +240,23 @@ final class SchemeGeneratorTests: XCTestCase {
|
|||
XCTAssertEqual(got?.macroExpansion?.buildableIdentifier, "primary")
|
||||
}
|
||||
|
||||
func test_analyzeAction() {
|
||||
let got = subject.analyzeAction()
|
||||
func test_targetAnalyzeAction() {
|
||||
let got = subject.targetAnalyzeAction()
|
||||
XCTAssertEqual(got.buildConfiguration, "Debug")
|
||||
}
|
||||
|
||||
func test_archiveAction() {
|
||||
let got = subject.archiveAction()
|
||||
func test_targetArchiveAction() {
|
||||
let got = subject.targetArchiveAction()
|
||||
XCTAssertEqual(got.buildConfiguration, "Release")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ final class GraphTests: XCTestCase {
|
|||
let graph = Graph.test(cache: cache)
|
||||
let dependencies = graph.targetDependencies(path: project.path,
|
||||
name: target.name)
|
||||
XCTAssertEqual(dependencies.first, "Dependency")
|
||||
XCTAssertEqual(dependencies.first?.target.name, "Dependency")
|
||||
}
|
||||
|
||||
func test_linkableDependencies_whenPrecompiled() throws {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -21,4 +21,20 @@ final class TargetTests: XCTestCase {
|
|||
let target = Target.test(name: "Test", product: .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])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import App
|
||||
|
||||
final class AppTests: XCTestCase {
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import Foundation
|
||||
|
||||
public class FrameworkClass {
|
||||
public init() {}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import Foundation
|
||||
import XCTest
|
||||
@testable import Framework
|
||||
|
||||
final class FrameworkTests: XCTestCase {
|
||||
var subject: FrameworkClass = FrameworkClass()
|
||||
}
|
|
@ -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>
|
|
@ -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")
|
||||
])
|
||||
])
|
Loading…
Reference in New Issue