Skip to content

Commit

Permalink
Transitively Link Static Dependency's Dynamic Dependencies Correctly (#…
Browse files Browse the repository at this point in the history
…484)

Resolves #455

### Short description 📝

> When a static dependency has a dynamic dependencies, those dynamic dependencies need to be linked at the point the static dependency is linked.

This PR fixes the issue where a static dependency's dynamic libraries were linking incorrectly.

### Solution 📦

As we already traverse the graph for static dependencies, we can add a step to this process to check each static dependency's `targetDependencies` for dynamic libraries and add them as a reference adjacent to where the original static dependency is referenced.

### Implementation 👩‍💻👨‍💻

- [X] Update `linkableDependencies` in `Graph.swift` to reference a static dependency's dynamic libraries
- [X] Update `GraphTests` to check the test case:
-  A (App) -> B (Static) -> C (Dynamic)
-  Expected: A -> C
- [X] Update `GraphTests` to check the test case:
-  A (App) -> B (Dynamic) -> C (Static) -> D (Static) -> E (Dynamic) 
-  Expected: A -> B
-  Expected: B -> (C, D, E)
-  [X] Update `GraphTests` to check the test case:
-  A (App) -> B (Dynamic) -> C (Dynamic) -> D (Static) -> E (Static) -> F (Dynamic)
-  Expected: B -> C

### Test Plan
- Run `tuist generate` inside `fixtures/ios_app_with_static_frameworks`
- Check that the `App` target has `D` inside `Build Phases -> Link Binary With Libraries`
  • Loading branch information
adamkhazi committed Aug 21, 2019
1 parent 71a3ec0 commit cbc4780
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,9 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/

## Next

### Fixed
- Transitively link static dependency's dynamic dependencies correctly https://github.com/tuist/tuist/pull/484 by @adamkhazi

## 0.17.0

### Added
Expand Down
17 changes: 12 additions & 5 deletions Sources/TuistGenerator/Graph/Graph.swift
Expand Up @@ -220,13 +220,21 @@ class Graph: Graphing {

references = references.union(precompiledLibrariesAndFrameworks)

// Static libraries and frameworks
// Static libraries and frameworks / Static libraries' dynamic libraries

if targetNode.target.canLinkStaticProducts() {
let staticLibraries = findAll(targetNode: targetNode, test: isStaticLibrary, skip: isFramework)
.map { DependencyReference.product(target: $0.target.name) }
let staticLibraryTargetNodes = findAll(targetNode: targetNode, test: isStaticLibrary, skip: isFramework)
let staticLibraries = staticLibraryTargetNodes.map {
DependencyReference.product(target: $0.target.name)
}

let staticDependenciesDynamicLibraries = staticLibraryTargetNodes.flatMap {
$0.targetDependencies
.filter(or(isFramework, isDynamicLibrary))
.map { DependencyReference.product(target: $0.target.name) }
}

references = references.union(staticLibraries)
references = references.union(staticLibraries + staticDependenciesDynamicLibraries)
}

// Link dynamic libraries and frameworks
Expand All @@ -236,7 +244,6 @@ class Graph: Graphing {
.map { DependencyReference.product(target: $0.target.name) }

references = references.union(dynamicLibrariesAndFrameworks)

return Array(references).sorted()
}

Expand Down
106 changes: 106 additions & 0 deletions Tests/TuistGeneratorTests/Graph/GraphTests.swift
Expand Up @@ -127,6 +127,112 @@ final class GraphTests: XCTestCase {
XCTAssertTrue(frameworkGot.contains(.product(target: "StaticDependency")))
}

func test_linkableDependencies_transitiveDynamicLibrariesOneStaticHop() throws {
// Given
let staticFramework = Target.test(name: "StaticFramework",
product: .staticFramework,
dependencies: [])
let dynamicFramework = Target.test(name: "DynamicFramework",
product: .framework,
dependencies: [])

let app = Target.test(name: "App", product: .app)

let projectA = Project.test(path: "/path/a")

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

// When
let result = try graph.linkableDependencies(path: projectA.path, name: app.name, system: system)

// Then
XCTAssertEqual(result, [DependencyReference.product(target: "DynamicFramework"),
DependencyReference.product(target: "StaticFramework")])
}

func test_linkableDependencies_transitiveDynamicLibrariesThreeHops() throws {
// Given
let dynamicFramework1 = Target.test(name: "DynamicFramework1",
product: .framework,
dependencies: [])
let dynamicFramework2 = Target.test(name: "DynamicFramework2",
product: .framework,
dependencies: [])
let staticFramework1 = Target.test(name: "StaticFramework1",
product: .staticLibrary,
dependencies: [])
let staticFramework2 = Target.test(name: "StaticFramework2",
product: .staticLibrary,
dependencies: [])

let app = Target.test(name: "App", product: .app)

let projectA = Project.test(path: "/path/a")

let graph = Graph.create(project: projectA,
dependencies: [
(target: app, dependencies: [dynamicFramework1]),
(target: dynamicFramework1, dependencies: [staticFramework1]),
(target: staticFramework1, dependencies: [staticFramework2]),
(target: staticFramework2, dependencies: [dynamicFramework2]),
(target: dynamicFramework2, dependencies: []),
])

// When
let appResult = try graph.linkableDependencies(path: projectA.path, name: app.name, system: system)
let dynamicFramework1Result = try graph.linkableDependencies(path: projectA.path, name: dynamicFramework1.name, system: system)

// Then
XCTAssertEqual(appResult, [DependencyReference.product(target: "DynamicFramework1")])
XCTAssertEqual(dynamicFramework1Result, [DependencyReference.product(target: "DynamicFramework2"),
DependencyReference.product(target: "StaticFramework1"),
DependencyReference.product(target: "StaticFramework2")])
}

func test_linkableDependencies_transitiveDynamicLibrariesCheckNoDuplicatesInParentDynamic() throws {
// Given
let dynamicFramework1 = Target.test(name: "DynamicFramework1",
product: .framework,
dependencies: [])
let dynamicFramework2 = Target.test(name: "DynamicFramework2",
product: .framework,
dependencies: [])
let dynamicFramework3 = Target.test(name: "DynamicFramework3",
product: .framework,
dependencies: [])
let staticFramework1 = Target.test(name: "StaticFramework1",
product: .staticLibrary,
dependencies: [])
let staticFramework2 = Target.test(name: "StaticFramework2",
product: .staticLibrary,
dependencies: [])

let app = Target.test(name: "App", product: .app)

let projectA = Project.test(path: "/path/a")

let graph = Graph.create(project: projectA,
dependencies: [
(target: app, dependencies: [dynamicFramework1]),
(target: dynamicFramework1, dependencies: [dynamicFramework2]),
(target: dynamicFramework2, dependencies: [staticFramework1]),
(target: staticFramework1, dependencies: [staticFramework2]),
(target: staticFramework2, dependencies: [dynamicFramework3]),
(target: dynamicFramework3, dependencies: []),
])

// When
let dynamicFramework1Result = try graph.linkableDependencies(path: projectA.path, name: dynamicFramework1.name, system: system)

// Then
XCTAssertEqual(dynamicFramework1Result, [DependencyReference.product(target: "DynamicFramework2")])
}

func test_linkableDependencies_transitiveSDKDependenciesStatic() throws {
// Given
let staticFrameworkA = Target.test(name: "StaticFrameworkA",
Expand Down

0 comments on commit cbc4780

Please sign in to comment.