Ensuring target product names are consistent with Xcode (#323)

Resolves https://github.com/tuist/tuist/issues/254

- When creating new Xcode projects manually from the UI, the product names do not include the extension
- Renaming existing `productName` references to `productNameWithExtension`

Test Plan:

- Ensure unit tests pass via `swift test`
- Ensure acceptance tests pass via `bundle exec rake features`
This commit is contained in:
Kas 2019-04-12 19:39:31 +01:00 committed by GitHub
parent a03a4e932b
commit fcbbfb14ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 128 additions and 50 deletions

View File

@ -16,6 +16,8 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
### Fixed ### Fixed
- Ensuring target product names are consistent with Xcode https://github.com/tuist/tuist/pull/323 by @kwridan
## 0.13.0 ## 0.13.0
### Added ### Added

View File

@ -179,7 +179,7 @@ final class ConfigGenerator: ConfigGenerating {
settings["TEST_TARGET_NAME"] = "\(app.target.name)" settings["TEST_TARGET_NAME"] = "\(app.target.name)"
if target.product == .unitTests { if target.product == .unitTests {
settings["TEST_HOST"] = "$(BUILT_PRODUCTS_DIR)/\(app.target.productName)/\(app.target.name)" settings["TEST_HOST"] = "$(BUILT_PRODUCTS_DIR)/\(app.target.productNameWithExtension)/\(app.target.name)"
settings["BUNDLE_LOADER"] = "$(TEST_HOST)" settings["BUNDLE_LOADER"] = "$(TEST_HOST)"
} }
} }

View File

@ -115,7 +115,7 @@ class ProjectFileElements {
func targetProducts(target: Target) -> Set<String> { func targetProducts(target: Target) -> Set<String> {
var products: Set<String> = Set() var products: Set<String> = Set()
products.insert(target.productName) products.insert(target.productNameWithExtension)
return products return products
} }
@ -213,7 +213,7 @@ class ProjectFileElements {
try dependencies.forEach { node in try dependencies.forEach { node in
if let targetNode = node as? TargetNode { if let targetNode = node as? TargetNode {
// Product name // Product name
let productName = targetNode.target.productName let productName = targetNode.target.productNameWithExtension
if self.products[productName] != nil { return } if self.products[productName] != nil { return }
/// The dependency belongs to the same project and its product /// The dependency belongs to the same project and its product

View File

@ -254,7 +254,7 @@ final class SchemesGenerator: SchemesGenerating {
func targetBuildableReference(target: Target, pbxTarget: PBXNativeTarget, projectName: String) -> XCScheme.BuildableReference { func targetBuildableReference(target: Target, pbxTarget: PBXNativeTarget, projectName: String) -> XCScheme.BuildableReference {
return XCScheme.BuildableReference(referencedContainer: "container:\(projectName)", return XCScheme.BuildableReference(referencedContainer: "container:\(projectName)",
blueprint: pbxTarget, blueprint: pbxTarget,
buildableName: target.productName, buildableName: target.productNameWithExtension,
blueprintName: target.name, blueprintName: target.name,
buildableIdentifier: "primary") buildableIdentifier: "primary")
} }

View File

@ -54,7 +54,7 @@ final class TargetGenerator: TargetGenerating {
graph: Graphing, graph: Graphing,
system: Systeming = System()) throws -> PBXNativeTarget { system: Systeming = System()) throws -> PBXNativeTarget {
/// Products reference. /// Products reference.
let productFileReference = fileElements.products[target.productName]! let productFileReference = fileElements.products[target.productNameWithExtension]!
/// Target /// Target
let pbxTarget = PBXNativeTarget(name: target.name, let pbxTarget = PBXNativeTarget(name: target.name,
@ -63,7 +63,7 @@ final class TargetGenerator: TargetGenerating {
buildRules: [], buildRules: [],
dependencies: [], dependencies: [],
productInstallPath: nil, productInstallPath: nil,
productName: target.productName, productName: target.name,
product: productFileReference, product: productFileReference,
productType: target.product.xcodeValue) productType: target.product.xcodeValue)
pbxproj.add(object: pbxTarget) pbxproj.add(object: pbxTarget)

View File

@ -122,7 +122,7 @@ class Graph: Graphing {
return targetNode.targetDependencies return targetNode.targetDependencies
.filter(isStaticLibrary) .filter(isStaticLibrary)
.map(\.target.productName) .map(\.target.productNameWithExtension)
.map(DependencyReference.product) .map(DependencyReference.product)
} }
@ -149,7 +149,7 @@ class Graph: Graphing {
let staticLibraries = findAll(targetNode: targetNode, test: isStaticLibrary, skip: isFramework) let staticLibraries = findAll(targetNode: targetNode, test: isStaticLibrary, skip: isFramework)
.lazy .lazy
.map(\.target.productName) .map(\.target.productNameWithExtension)
.map(DependencyReference.product) .map(DependencyReference.product)
references.append(contentsOf: staticLibraries) references.append(contentsOf: staticLibraries)
@ -159,7 +159,7 @@ class Graph: Graphing {
let dynamicLibrariesAndFrameworks = targetNode.targetDependencies let dynamicLibrariesAndFrameworks = targetNode.targetDependencies
.filter(or(isFramework, isDynamicLibrary)) .filter(or(isFramework, isDynamicLibrary))
.map(\.target.productName) .map(\.target.productNameWithExtension)
.map(DependencyReference.product) .map(DependencyReference.product)
references.append(contentsOf: dynamicLibrariesAndFrameworks) references.append(contentsOf: dynamicLibrariesAndFrameworks)
@ -224,7 +224,7 @@ class Graph: Graphing {
/// Other targets' frameworks. /// Other targets' frameworks.
let otherTargetFrameworks = findAll(targetNode: targetNode, test: isFramework) let otherTargetFrameworks = findAll(targetNode: targetNode, test: isFramework)
.lazy .lazy
.map(\.target.productName) .map(\.target.productNameWithExtension)
.map(DependencyReference.product) .map(DependencyReference.product)
references.append(contentsOf: otherTargetFrameworks) references.append(contentsOf: otherTargetFrameworks)

View File

@ -68,7 +68,7 @@ public class Target: Equatable {
} }
/// Returns the product name including the extension. /// Returns the product name including the extension.
var productName: String { var productNameWithExtension: String {
switch product { switch product {
case .staticLibrary, .dynamicLibrary: case .staticLibrary, .dynamicLibrary:
return "lib\(name).\(product.xcodeValue.fileExtension!)" return "lib\(name).\(product.xcodeValue.fileExtension!)"

View File

@ -39,19 +39,19 @@ final class SchemeGeneratorTests: XCTestCase {
XCTAssertEqual(appEntry.buildFor, [.analyzing, .archiving, .profiling, .running, .testing]) XCTAssertEqual(appEntry.buildFor, [.analyzing, .archiving, .profiling, .running, .testing])
XCTAssertEqual(appEntry.buildableReference.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(appEntry.buildableReference.referencedContainer, "container:project.xcodeproj")
XCTAssertEqual(appEntry.buildableReference.buildableName, app.productName) XCTAssertEqual(appEntry.buildableReference.buildableName, app.productNameWithExtension)
XCTAssertEqual(appEntry.buildableReference.blueprintName, app.name) XCTAssertEqual(appEntry.buildableReference.blueprintName, app.name)
XCTAssertEqual(appEntry.buildableReference.buildableIdentifier, "primary") XCTAssertEqual(appEntry.buildableReference.buildableIdentifier, "primary")
XCTAssertEqual(testsEntry.buildFor, [.testing]) XCTAssertEqual(testsEntry.buildFor, [.testing])
XCTAssertEqual(testsEntry.buildableReference.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(testsEntry.buildableReference.referencedContainer, "container:project.xcodeproj")
XCTAssertEqual(testsEntry.buildableReference.buildableName, appTests.productName) XCTAssertEqual(testsEntry.buildableReference.buildableName, appTests.productNameWithExtension)
XCTAssertEqual(testsEntry.buildableReference.blueprintName, appTests.name) XCTAssertEqual(testsEntry.buildableReference.blueprintName, appTests.name)
XCTAssertEqual(testsEntry.buildableReference.buildableIdentifier, "primary") XCTAssertEqual(testsEntry.buildableReference.buildableIdentifier, "primary")
XCTAssertEqual(uiTestsEntry.buildFor, [.testing]) XCTAssertEqual(uiTestsEntry.buildFor, [.testing])
XCTAssertEqual(uiTestsEntry.buildableReference.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(uiTestsEntry.buildableReference.referencedContainer, "container:project.xcodeproj")
XCTAssertEqual(uiTestsEntry.buildableReference.buildableName, appUITests.productName) XCTAssertEqual(uiTestsEntry.buildableReference.buildableName, appUITests.productNameWithExtension)
XCTAssertEqual(uiTestsEntry.buildableReference.blueprintName, appUITests.name) XCTAssertEqual(uiTestsEntry.buildableReference.blueprintName, appUITests.name)
XCTAssertEqual(uiTestsEntry.buildableReference.buildableIdentifier, "primary") XCTAssertEqual(uiTestsEntry.buildableReference.buildableIdentifier, "primary")
} }
@ -73,7 +73,7 @@ final class SchemeGeneratorTests: XCTestCase {
XCTAssertEqual(testable?.skipped, false) XCTAssertEqual(testable?.skipped, false)
XCTAssertEqual(testable?.buildableReference.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(testable?.buildableReference.referencedContainer, "container:project.xcodeproj")
XCTAssertEqual(testable?.buildableReference.buildableName, appTests.productName) XCTAssertEqual(testable?.buildableReference.buildableName, appTests.productNameWithExtension)
XCTAssertEqual(testable?.buildableReference.blueprintName, appTests.name) XCTAssertEqual(testable?.buildableReference.blueprintName, appTests.name)
XCTAssertEqual(testable?.buildableReference.buildableIdentifier, "primary") XCTAssertEqual(testable?.buildableReference.buildableIdentifier, "primary")
} }
@ -186,7 +186,7 @@ final class SchemeGeneratorTests: XCTestCase {
XCTAssertNil(got?.macroExpansion) XCTAssertNil(got?.macroExpansion)
XCTAssertEqual(got?.buildableProductRunnable?.runnableDebuggingMode, "0") XCTAssertEqual(got?.buildableProductRunnable?.runnableDebuggingMode, "0")
XCTAssertEqual(buildable?.referencedContainer, "container:project.xcodeproj") XCTAssertEqual(buildable?.referencedContainer, "container:project.xcodeproj")
XCTAssertEqual(buildable?.buildableName, target.productName) XCTAssertEqual(buildable?.buildableName, target.productNameWithExtension)
XCTAssertEqual(buildable?.blueprintName, target.name) XCTAssertEqual(buildable?.blueprintName, target.name)
XCTAssertEqual(buildable?.buildableIdentifier, "primary") XCTAssertEqual(buildable?.buildableIdentifier, "primary")

View File

@ -5,46 +5,69 @@ import XCTest
@testable import TuistGenerator @testable import TuistGenerator
final class TargetGeneratorTests: XCTestCase { final class TargetGeneratorTests: XCTestCase {
var path: AbsolutePath!
var subject: TargetGenerator! var subject: TargetGenerator!
var pbxproj: PBXProj!
var pbxProject: PBXProject!
var fileElements: ProjectFileElements!
override func setUp() { override func setUp() {
super.setUp() super.setUp()
path = AbsolutePath("/test")
pbxproj = PBXProj()
pbxProject = createPbxProject(pbxproj: pbxproj)
fileElements = ProjectFileElements([:], playgrounds: MockPlaygrounds())
subject = TargetGenerator() subject = TargetGenerator()
} }
func test_generateTarget_productName() throws {
// Given
let target = Target.test(name: "MyFramework",
product: .framework)
let project = Project.test(path: path, targets: [target])
let graph = Graph.test()
let groups = ProjectGroups.generate(project: project,
pbxproj: pbxproj,
sourceRootPath: path,
playgrounds: MockPlaygrounds())
try fileElements.generateProjectFiles(project: project,
graph: graph,
groups: groups,
pbxproj: pbxproj,
sourceRootPath: path)
// When
let generatedTarget = try subject.generateTarget(target: target,
pbxproj: pbxproj,
pbxProject: pbxProject,
groups: groups,
fileElements: fileElements,
path: path,
sourceRootPath: path,
options: GenerationOptions(),
graph: graph)
// Then
XCTAssertEqual(generatedTarget.productName, "MyFramework")
XCTAssertEqual(generatedTarget.productNameWithExtension(), "MyFramework.framework")
XCTAssertEqual(generatedTarget.productType, .framework)
}
func test_generateTargetDependencies() throws { func test_generateTargetDependencies() throws {
let pbxproj = PBXProj() // Given
let path = AbsolutePath("/test")
let targetA = Target.test(name: "TargetA") let targetA = Target.test(name: "TargetA")
let targetB = Target.test(name: "TargetB") let targetB = Target.test(name: "TargetB")
let nativeTargetA = PBXNativeTarget(name: "TargetA") let nativeTargetA = createNativeTarget(for: targetA)
let nativeTargetB = PBXNativeTarget(name: "TargetB") let nativeTargetB = createNativeTarget(for: targetB)
pbxproj.add(object: nativeTargetA) let graph = createGraph(project: .test(path: path),
pbxproj.add(object: nativeTargetB) dependencies: [
let configList = XCConfigurationList(buildConfigurations: []) (target: targetA, dependencies: [targetB]),
pbxproj.add(object: configList) (target: targetB, dependencies: []),
let mainGroup = PBXGroup() ])
pbxproj.add(object: mainGroup)
let project = Project.test(path: path, // When
name: "Project",
targets: [targetA, targetB])
let pbxProject = PBXProject(name: "Project",
buildConfigurationList: configList,
compatibilityVersion: "0",
mainGroup: mainGroup)
pbxproj.add(object: pbxProject)
let graphCache = GraphLoaderCache()
let targetBNode = TargetNode(project: project,
target: targetA,
dependencies: [])
let targetANode = TargetNode(project: project,
target: targetA,
dependencies: [targetBNode])
let graph = Graph.test(cache: graphCache)
graphCache.targetNodes[path] = [
"TargetA": targetANode,
"TargetB": targetBNode,
]
try subject.generateTargetDependencies(path: path, try subject.generateTargetDependencies(path: path,
targets: [targetA, targetB], targets: [targetA, targetB],
nativeTargets: [ nativeTargets: [
@ -52,6 +75,59 @@ final class TargetGeneratorTests: XCTestCase {
"TargetB": nativeTargetB, "TargetB": nativeTargetB,
], ],
graph: graph) graph: graph)
XCTAssertNotNil(nativeTargetA.dependencies.first)
// Then
XCTAssertEqual(nativeTargetA.dependencies.map(\.name), [
"TargetB",
])
}
// MARK: - Helpers
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: []))
})
dependencies.forEach {
let node = nodesCache[$0.target.name]!
node.dependencies = $0.dependencies.map { nodesCache[$0.name]! }
}
return dependencies.map { nodesCache[$0.target.name]! }
}
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
}
private func createNativeTarget(for target: Target) -> PBXNativeTarget {
let nativeTarget = PBXNativeTarget(name: target.name)
pbxproj.add(object: nativeTarget)
return nativeTarget
}
private func createPbxProject(pbxproj: PBXProj) -> PBXProject {
let configList = XCConfigurationList(buildConfigurations: [])
pbxproj.add(object: configList)
let mainGroup = PBXGroup()
pbxproj.add(object: mainGroup)
let pbxProject = PBXProject(name: "Project",
buildConfigurationList: configList,
compatibilityVersion: "0",
mainGroup: mainGroup)
pbxproj.add(object: pbxProject)
return pbxProject
} }
} }

View File

@ -19,17 +19,17 @@ final class TargetTests: XCTestCase {
func test_productName_when_staticLibrary() { func test_productName_when_staticLibrary() {
let target = Target.test(name: "Test", product: .staticLibrary) let target = Target.test(name: "Test", product: .staticLibrary)
XCTAssertEqual(target.productName, "libTest.a") XCTAssertEqual(target.productNameWithExtension, "libTest.a")
} }
func test_productName_when_dynamicLibrary() { func test_productName_when_dynamicLibrary() {
let target = Target.test(name: "Test", product: .dynamicLibrary) let target = Target.test(name: "Test", product: .dynamicLibrary)
XCTAssertEqual(target.productName, "libTest.dylib") XCTAssertEqual(target.productNameWithExtension, "libTest.dylib")
} }
func test_productName_when_app() { func test_productName_when_app() {
let target = Target.test(name: "Test", product: .app) let target = Target.test(name: "Test", product: .app)
XCTAssertEqual(target.productName, "Test.app") XCTAssertEqual(target.productNameWithExtension, "Test.app")
} }
func test_sequence_testBundles() { func test_sequence_testBundles() {