Skip to content

Commit

Permalink
Supporting bundle target dependencies that reside in different projec…
Browse files Browse the repository at this point in the history
…ts (#348)

Part of: #290

- Previously only bundle target dependencies within the same projects worked
- This was due to leveraging `targetDependencies` method on `Graph` which only returns the target dependencies with the same project
- Updating `Graph` to include a new method to return all bundle resource dependencies

Test Plan:

- Run unit tests via `swift test` and verify they pass

Note: bundle resources are not yet exposed to the manifests
  • Loading branch information
kwridan committed May 16, 2019
1 parent 216028b commit a236717
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
- Fixing files getting mistaken for folders https://github.com/tuist/tuist/pull/338 by @kwridan
- Updating init template to avoid warnings https://github.com/tuist/tuist/pull/339 by @kwridan
- Fixing generation failures due to asset catalog & `**/*.png` glob patterns handling https://github.com/tuist/tuist/pull/346 by @kwridan
- Supporting bundle target dependencies that reside in different projects (in `TuistGenerator`) https://github.com/tuist/tuist/pull/348 by @kwridan

## 0.14.0

Expand Down
4 changes: 1 addition & 3 deletions Sources/TuistGenerator/Generator/BuildPhaseGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,7 @@ final class BuildPhaseGenerator: BuildPhaseGenerating {
fileElements: ProjectFileElements,
pbxproj: PBXProj,
resourcesBuildPhase: PBXResourcesBuildPhase) {
let dependencies = graph.targetDependencies(path: path, name: target.name)
let bundles = dependencies.filter { $0.target.product == .bundle }

let bundles = graph.resourceBundleDependencies(path: path, name: target.name)
let refs = bundles.compactMap { fileElements.product(name: $0.target.productNameWithExtension) }
refs.forEach {
let pbxBuildFile = PBXBuildFile(file: $0)
Expand Down
10 changes: 10 additions & 0 deletions Sources/TuistGenerator/Graph/Graph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ protocol Graphing: AnyObject {
func embeddableFrameworks(path: AbsolutePath, name: String, system: Systeming) throws -> Set<DependencyReference>
func targetDependencies(path: AbsolutePath, name: String) -> [TargetNode]
func staticDependencies(path: AbsolutePath, name: String) -> [DependencyReference]
func resourceBundleDependencies(path: AbsolutePath, name: String) -> [TargetNode]

// MARK: - Depth First Search

Expand Down Expand Up @@ -126,6 +127,15 @@ class Graph: Graphing {
.map(DependencyReference.product)
}

func resourceBundleDependencies(path: AbsolutePath, name: String) -> [TargetNode] {
guard let targetNode = findTargetNode(path: path, name: name) else {
return []
}

return targetNode.targetDependencies
.filter { $0.target.product == .bundle }
}

func linkableDependencies(path: AbsolutePath, name: String) throws -> [DependencyReference] {
guard let targetNode = findTargetNode(path: path, name: name) else {
return []
Expand Down
50 changes: 41 additions & 9 deletions Tests/TuistGeneratorTests/Generator/BuildPhaseGeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,37 +129,36 @@ final class BuildPhaseGeneratorTests: XCTestCase {
let pbxTarget = PBXNativeTarget(name: "Test")
let pbxproj = PBXProj()
pbxproj.add(object: pbxTarget)

let fileElements = ProjectFileElements()
let path = AbsolutePath("/test/file.swift")

let sourceFileReference = PBXFileReference(sourceTree: .group, name: "Test")
fileElements.elements[path] = sourceFileReference

let headerPath = AbsolutePath("/test.h")
let headers = Headers.test(public: [path], private: [], project: [])

let headerFileReference = PBXFileReference()
fileElements.elements[headerPath] = headerFileReference

let target = Target.test(sources: ["/test/file.swift"],
headers: headers)

try subject.generateBuildPhases(path: tmpDir.path,
target: target,
graph: Graph.test(),
pbxTarget: pbxTarget,
fileElements: fileElements,
pbxproj: pbxproj,
sourceRootPath: tmpDir.path)

let firstBuildPhase: PBXBuildPhase? = pbxTarget.buildPhases.first
XCTAssertNotNil(firstBuildPhase)
XCTAssertTrue(firstBuildPhase is PBXHeadersBuildPhase)

let secondBuildPhase: PBXBuildPhase? = pbxTarget.buildPhases[1]
XCTAssertTrue(secondBuildPhase is PBXSourcesBuildPhase)

}

func test_generateResourcesBuildPhase_whenLocalizedFile() throws {
Expand Down Expand Up @@ -276,6 +275,39 @@ final class BuildPhaseGeneratorTests: XCTestCase {
])
}

func test_generateResourceBundle_fromProjectDependency() throws {
// Given
let bundle = Target.test(name: "Bundle1", product: .bundle)
let projectA = Project.test(path: "/path/a")

let app = Target.test(name: "App", product: .app)
let projectB = Project.test(path: "/path/b")

let graph = Graph.create(projects: [projectA, projectB],
dependencies: [
(project: projectA, target: bundle, dependencies: []),
(project: projectB, target: app, dependencies: [bundle]),
])

let pbxproj = PBXProj()
let nativeTarget = PBXNativeTarget(name: "Test")
let fileElements = createProductFileElements(for: [bundle])

// When
try subject.generateResourcesBuildPhase(path: projectB.path,
target: app,
graph: graph,
pbxTarget: nativeTarget,
fileElements: fileElements,
pbxproj: pbxproj)

// Then
let resourcePhase = try nativeTarget.resourcesBuildPhase()
XCTAssertEqual(resourcePhase?.files?.compactMap { $0.file?.nameOrPath }, [
"Bundle1",
])
}

// MARK: - Helpers

private func createProductFileElements(for targets: [Target]) -> ProjectFileElements {
Expand Down
44 changes: 44 additions & 0 deletions Tests/TuistGeneratorTests/Graph/GraphTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,48 @@ final class GraphTests: XCTestCase {
// Then
XCTAssertEqual(got, [AbsolutePath("/test/modules")])
}

func test_resourceBundleDependencies_fromTargetDependency() {
// Given
let bundle = Target.test(name: "Bundle1", product: .bundle)
let app = Target.test(name: "App", product: .bundle)
let projectA = Project.test(path: "/path/a")

let graph = Graph.create(project: projectA,
dependencies: [
(target: bundle, dependencies: []),
(target: app, dependencies: [bundle]),
])

// When
let result = graph.resourceBundleDependencies(path: projectA.path, name: app.name)

// Then
XCTAssertEqual(result.map(\.target.name), [
"Bundle1",
])
}

func test_resourceBundleDependencies_fromProjectDependency() {
// Given
let bundle = Target.test(name: "Bundle1", product: .bundle)
let projectA = Project.test(path: "/path/a")

let app = Target.test(name: "App", product: .app)
let projectB = Project.test(path: "/path/b")

let graph = Graph.create(projects: [projectA, projectB],
dependencies: [
(project: projectA, target: bundle, dependencies: []),
(project: projectB, target: app, dependencies: [bundle]),
])

// When
let result = graph.resourceBundleDependencies(path: projectB.path, name: app.name)

// Then
XCTAssertEqual(result.map(\.target.name), [
"Bundle1",
])
}
}
38 changes: 34 additions & 4 deletions Tests/TuistGeneratorTests/Graph/TestData/Graph+TestData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,19 @@ extension Graph {
entryNodes: entryNodes)
}

/// Creates a test dependency graph for targets within a single project
///
/// Note: For the purposes of testing, to reduce complexity of resolving dependencies
/// The `dependencies` property is used to define the dependencies explicitly.
/// All targets need to be listed even if they don't have any dependencies.
static func create(project: Project,
dependencies: [(target: Target, dependencies: [Target])]) -> Graph {
let targetNodes = createTargetNodes(project: project, dependencies: dependencies)
let depenenciesWithProject = dependencies.map { (
project: project,
target: $0.target,
dependencies: $0.dependencies
) }
let targetNodes = createTargetNodes(dependencies: depenenciesWithProject)

let cache = GraphLoaderCache()
let graph = Graph.test(name: project.name,
Expand All @@ -29,10 +39,30 @@ extension Graph {
return graph
}

private static func createTargetNodes(project: Project,
dependencies: [(target: Target, dependencies: [Target])]) -> [TargetNode] {
/// Creates a test dependency graph for targets within a multiple projects
///
/// Note: For the purposes of testing, to reduce complexity of resolving dependencies
/// The `dependencies` property is used to define the dependencies explicitly.
/// All targets need to be listed even if they don't have any dependencies.
static func create(projects: [Project],
dependencies: [(project: Project, target: Target, dependencies: [Target])]) -> Graph {
let targetNodes = createTargetNodes(dependencies: dependencies)

let cache = GraphLoaderCache()
let graph = Graph.test(name: projects.first?.name ?? "Test",
entryPath: projects.first?.path ?? "/test/path",
cache: cache,
entryNodes: targetNodes)

targetNodes.forEach { cache.add(targetNode: $0) }
projects.forEach { cache.add(project: $0) }

return graph
}

private static func createTargetNodes(dependencies: [(project: Project, target: Target, dependencies: [Target])]) -> [TargetNode] {
let nodesCache = Dictionary(uniqueKeysWithValues: dependencies.map {
($0.target.name, TargetNode(project: project,
($0.target.name, TargetNode(project: $0.project,
target: $0.target,
dependencies: []))
})
Expand Down

0 comments on commit a236717

Please sign in to comment.