diff --git a/Sources/TuistCache/GraphMappers/CacheGraphMutator.swift b/Sources/TuistCache/GraphMappers/CacheGraphMutator.swift index b0e5a6727..33eb24628 100644 --- a/Sources/TuistCache/GraphMappers/CacheGraphMutator.swift +++ b/Sources/TuistCache/GraphMappers/CacheGraphMutator.swift @@ -94,6 +94,10 @@ class CacheGraphMutator: CacheGraphMutating { return } + // Transitive bundles + // get all the transitive bundles + // declare them as direct dependencies. + // If the target cannot be replaced with its associated .(xc)framework we return guard !sources.contains(targetDependency.target.name), let precompiledFrameworkPath = precompiledFrameworkPath(target: targetDependency, precompiledFrameworks: precompiledFrameworks, diff --git a/Sources/TuistCore/Graph/Graph.swift b/Sources/TuistCore/Graph/Graph.swift index 14faeab46..7512a9ef3 100644 --- a/Sources/TuistCore/Graph/Graph.swift +++ b/Sources/TuistCore/Graph/Graph.swift @@ -353,7 +353,7 @@ public class Graph: Encodable, Equatable { /// - Parameters: /// - path: Path to the directory where the project that defines the target is located. /// - name: Name of the target. - public func embeddableFrameworks(path: AbsolutePath, name: String) throws -> [GraphDependencyReference] { + public func embeddableFrameworks(path: AbsolutePath, name: String) -> [GraphDependencyReference] { guard let targetNode = findTargetNode(path: path, name: name), canEmbedProducts(targetNode: targetNode) else { @@ -382,7 +382,7 @@ public class Graph: Encodable, Equatable { // Exclude any products embed in unit test host apps if targetNode.target.product == .unitTests { if let hostApp = hostApplication(for: targetNode) { - references.subtract(try embeddableFrameworks(path: hostApp.path, name: hostApp.name)) + references.subtract(embeddableFrameworks(path: hostApp.path, name: hostApp.name)) } else { references = [] } @@ -417,8 +417,8 @@ public class Graph: Encodable, Equatable { try self.linkableDependencies(path: project.path, name: $0.name) } - let embeddableDependencies = try project.targets.flatMap { - try self.embeddableFrameworks(path: project.path, name: $0.name) + let embeddableDependencies = project.targets.flatMap { + self.embeddableFrameworks(path: project.path, name: $0.name) } let copyProductDependencies = project.targets.flatMap { @@ -446,7 +446,7 @@ public class Graph: Encodable, Equatable { .filter { validProducts.contains($0.target.product) } } - public func appClipsDependency(path: AbsolutePath, name: String) -> TargetNode? { + public func appClipDependencies(path: AbsolutePath, name: String) -> TargetNode? { guard let targetNode = findTargetNode(path: path, name: name) else { return nil } diff --git a/Sources/TuistCore/Graph/GraphDependencyReference.swift b/Sources/TuistCore/Graph/GraphDependencyReference.swift index 255b4db94..8594bf337 100644 --- a/Sources/TuistCore/Graph/GraphDependencyReference.swift +++ b/Sources/TuistCore/Graph/GraphDependencyReference.swift @@ -10,7 +10,6 @@ public enum GraphDependencyReference: Equatable, Comparable, Hashable { ) case library( path: AbsolutePath, - binaryPath: AbsolutePath, linking: BinaryLinking, architectures: [BinaryArchitecture], product: Product @@ -40,7 +39,6 @@ public enum GraphDependencyReference: Equatable, Comparable, Hashable { product: frameworkNode.product) } else if let libraryNode = precompiledNode as? LibraryNode { self = .library(path: libraryNode.path, - binaryPath: libraryNode.binaryPath, linking: libraryNode.linking, architectures: libraryNode.architectures, product: libraryNode.product) @@ -56,7 +54,7 @@ public enum GraphDependencyReference: Equatable, Comparable, Hashable { public func hash(into hasher: inout Hasher) { switch self { - case let .library(path, _, _, _, _): + case let .library(path, _, _, _): hasher.combine(path) case let .framework(path, _, _, _, _, _, _, _): hasher.combine(path) @@ -78,7 +76,7 @@ public enum GraphDependencyReference: Equatable, Comparable, Hashable { switch self { case let .framework(path, _, _, _, _, _, _, _): return path - case let .library(path, _, _, _, _): + case let .library(path, _, _, _): return path case let .xcframework(path, _, _, _): return path @@ -93,7 +91,7 @@ public enum GraphDependencyReference: Equatable, Comparable, Hashable { return lhsPath < rhsPath case let (.xcframework(lhsPath, _, _, _), .xcframework(rhsPath, _, _, _)): return lhsPath < rhsPath - case let (.library(lhsPath, _, _, _, _), .library(rhsPath, _, _, _, _)): + case let (.library(lhsPath, _, _, _), .library(rhsPath, _, _, _)): return lhsPath < rhsPath case let (.product(lhsTarget, lhsProductName), .product(rhsTarget, rhsProductName)): if lhsTarget == rhsTarget { diff --git a/Sources/TuistCore/Graph/GraphTraverser.swift b/Sources/TuistCore/Graph/GraphTraverser.swift index 8c3362882..dd46ebb22 100644 --- a/Sources/TuistCore/Graph/GraphTraverser.swift +++ b/Sources/TuistCore/Graph/GraphTraverser.swift @@ -3,39 +3,92 @@ import TSCBasic public final class GraphTraverser: GraphTraversing { private let graph: Graph + public var name: String { graph.name } + public var hasPackages: Bool { !graph.packages.isEmpty } + public var path: AbsolutePath { graph.entryPath } + public var workspace: Workspace { graph.workspace } + public var projects: [AbsolutePath: Project] + public init(graph: Graph) { self.graph = graph + projects = Dictionary(uniqueKeysWithValues: graph.projects.map { ($0.path, $0) }) } - public func target(path: AbsolutePath, name: String) -> Target? { - graph.target(path: path, name: name).map(\.target) + public func target(path: AbsolutePath, name: String) -> ValueGraphTarget? { + graph.target(path: path, name: name).map { + ValueGraphTarget(path: $0.path, target: $0.target, project: $0.project) + } } - public func targets(at path: AbsolutePath) -> [Target] { - graph.targets(at: path).map(\.target) + public func targets(at path: AbsolutePath) -> Set { + Set(graph.targets(at: path).map { + ValueGraphTarget(path: $0.path, target: $0.target, project: $0.project) + }) } - public func directTargetDependencies(path: AbsolutePath, name: String) -> [Target] { - graph.targetDependencies(path: path, name: name).map(\.target) + public func directTargetDependencies(path: AbsolutePath, name: String) -> Set { + Set(graph.targetDependencies(path: path, name: name).map { + ValueGraphTarget(path: $0.path, target: $0.target, project: $0.project) + }) } - public func appExtensionDependencies(path: AbsolutePath, name: String) -> [Target] { - graph.appExtensionDependencies(path: path, name: name).map(\.target) + public func appExtensionDependencies(path: AbsolutePath, name: String) -> Set { + Set(graph.appExtensionDependencies(path: path, name: name).map { + ValueGraphTarget(path: $0.path, target: $0.target, project: $0.project) + }) } - public func resourceBundleDependencies(path: AbsolutePath, name: String) -> [Target] { - graph.resourceBundleDependencies(path: path, name: name).map(\.target) + public func resourceBundleDependencies(path: AbsolutePath, name: String) -> Set { + Set(graph.resourceBundleDependencies(path: path, name: name).map { + ValueGraphTarget(path: $0.path, target: $0.target, project: $0.project) + }) } - public func testTargetsDependingOn(path: AbsolutePath, name: String) -> [Target] { - graph.testTargetsDependingOn(path: path, name: name).map(\.target) + public func testTargetsDependingOn(path: AbsolutePath, name: String) -> Set { + Set(graph.testTargetsDependingOn(path: path, name: name).map { + ValueGraphTarget(path: $0.path, target: $0.target, project: $0.project) + }) } - public func directStaticDependencies(path: AbsolutePath, name: String) -> [GraphDependencyReference] { - graph.staticDependencies(path: path, name: name) + public func directStaticDependencies(path: AbsolutePath, name: String) -> Set { + Set(graph.staticDependencies(path: path, name: name)) } - public func appClipsDependency(path: AbsolutePath, name: String) -> Target? { - graph.appClipsDependency(path: path, name: name).map(\.target) + public func appClipDependencies(path: AbsolutePath, name: String) -> ValueGraphTarget? { + graph.appClipDependencies(path: path, name: name).map { ValueGraphTarget(path: $0.path, target: $0.target, project: $0.project) } + } + + public func embeddableFrameworks(path: AbsolutePath, name: String) -> Set { + Set(graph.embeddableFrameworks(path: path, name: name)) + } + + public func linkableDependencies(path: AbsolutePath, name: String) throws -> Set { + try Set(graph.linkableDependencies(path: path, name: name)) + } + + public func copyProductDependencies(path: AbsolutePath, name: String) -> Set { + guard let target = graph.target(path: path, name: name) else { return Set() } + return Set(graph.copyProductDependencies(path: path, target: target.target)) + } + + public func librariesPublicHeadersFolders(path: AbsolutePath, name: String) -> Set { + Set(graph.librariesPublicHeadersFolders(path: path, name: name)) + } + + public func librariesSearchPaths(path: AbsolutePath, name: String) -> Set { + Set(graph.librariesSearchPaths(path: path, name: name)) + } + + public func librariesSwiftIncludePaths(path: AbsolutePath, name: String) -> Set { + Set(graph.librariesSwiftIncludePaths(path: path, name: name)) + } + + public func runPathSearchPaths(path: AbsolutePath, name: String) -> Set { + Set(graph.runPathSearchPaths(path: path, name: name)) + } + + public func hostTargetFor(path: AbsolutePath, name: String) -> ValueGraphTarget? { + graph.hostTargetNodeFor(path: path, name: name) + .map { ValueGraphTarget(path: $0.path, target: $0.target, project: $0.project) } } } diff --git a/Sources/TuistCore/GraphTraverser/GraphTraversing.swift b/Sources/TuistCore/GraphTraverser/GraphTraversing.swift index 3d290ec9b..4cb2f49d8 100644 --- a/Sources/TuistCore/GraphTraverser/GraphTraversing.swift +++ b/Sources/TuistCore/GraphTraverser/GraphTraversing.swift @@ -2,49 +2,114 @@ import Foundation import TSCBasic public protocol GraphTraversing { + /// Graph name + var name: String { get } + + /// Returns true if the project has package dependencies. + var hasPackages: Bool { get } + + /// The path to the directory from where the graph has been loaded. + var path: AbsolutePath { get } + + /// Returns the graph's workspace. + var workspace: Workspace { get } + + /// Returns the graph projects. + var projects: [AbsolutePath: Project] { get } + /// It returns the target with the given name in the project that is defined in the given directory path. /// - Parameters: /// - path: Path to the directory that contains the definition of the project with the target is defined. /// - name: Name of the target. - func target(path: AbsolutePath, name: String) -> Target? + func target(path: AbsolutePath, name: String) -> ValueGraphTarget? /// It returns the targets of the project defined in the directory at the given path. /// - Parameter path: Path to the directory that contains the definition of the project. - func targets(at path: AbsolutePath) -> [Target] + func targets(at path: AbsolutePath) -> Set /// Given a project directory and target name, it returns all its direct target dependencies. /// - Parameters: /// - path: Path to the directory that contains the project. /// - name: Target name. - func directTargetDependencies(path: AbsolutePath, name: String) -> [Target] + func directTargetDependencies(path: AbsolutePath, name: String) -> Set /// Given a project directory and a target name, it returns all the dependencies that are extensions. /// - Parameters: /// - path: Path to the directory that contains the project. /// - name: Target name. - func appExtensionDependencies(path: AbsolutePath, name: String) -> [Target] + func appExtensionDependencies(path: AbsolutePath, name: String) -> Set /// Returns the transitive resource bundle dependencies for the given target. /// - Parameters: /// - path: Path to the directory where the project that defines the target is located. /// - name: Name of the target. - func resourceBundleDependencies(path: AbsolutePath, name: String) -> [Target] + func resourceBundleDependencies(path: AbsolutePath, name: String) -> Set /// Returns the list of test targets that depend on the one with the given name at the given path. /// - Parameters: /// - path: Path to the directory that contains the project definition. /// - name: Name of the target whose dependant test targets will be returned. - func testTargetsDependingOn(path: AbsolutePath, name: String) -> [Target] + func testTargetsDependingOn(path: AbsolutePath, name: String) -> Set /// Returns all non-transitive target static dependencies for the given target. /// - Parameters: /// - path: Path to the directory where the project that defines the target is located. /// - name: Name of the target. - func directStaticDependencies(path: AbsolutePath, name: String) -> [GraphDependencyReference] + func directStaticDependencies(path: AbsolutePath, name: String) -> Set /// Given a project directory and a target name, it returns an appClips dependency. /// - Parameters: /// - path: Path to the directory that contains the project. /// - name: Target name. - func appClipsDependency(path: AbsolutePath, name: String) -> Target? + func appClipDependencies(path: AbsolutePath, name: String) -> ValueGraphTarget? + + /// Given a project directory and a target name, it returns the list of dependencies that need to be embedded into the target product. + /// - Parameters: + /// - path: Path to the directory that contains the project. + /// - name: Target name. + func embeddableFrameworks(path: AbsolutePath, name: String) -> Set + + /// Given a project directory and a target name, it returns the list of dependencies that need to be linked from the target. + /// - Parameters: + /// - path: Path to the directory that contains the project. + /// - name: Target name. + func linkableDependencies(path: AbsolutePath, name: String) throws -> Set + + /// Given a project directory and a target name, it returns a list of dependencies that need to be included in a copy files build phase + /// + /// - Parameters: + /// - path: Path to the directory that contains the project. + /// - name: Target name + func copyProductDependencies(path: AbsolutePath, name: String) -> Set + + /// Given a project directory and a target name, it returns the list of header folders that should be exposed to the target. + /// - Parameters: + /// - path: Path to the directory that contains the project. + /// - name: Target name + func librariesPublicHeadersFolders(path: AbsolutePath, name: String) -> Set + + /// Given a project directory and a target name, it returns the list of library folders that should be exposed to the target. + /// - Parameters: + /// - path: Path to the directory that contains the project. + /// - name: Target name. + func librariesSearchPaths(path: AbsolutePath, name: String) -> Set + + /// Given a project directory and a target name, it returns the list of foldres with Swift modules that should be expoed to the target. + /// - Parameters: + /// - path: Path to the directory that contains the project. + /// - name: Target name. + func librariesSwiftIncludePaths(path: AbsolutePath, name: String) -> Set + + /// Returns all runpath search paths of the given target + /// Currently applied only to test targets with no host application + /// - Parameters: + /// - path; Path to the directory where the project that defines the target + /// - name: Name of the target + func runPathSearchPaths(path: AbsolutePath, name: String) -> Set + + /// It returns the host target for the given target. + /// - Parameters: + /// - path; Path to the directory where the project that defines the target + /// - name: Name of the target + func hostTargetFor(path: AbsolutePath, name: String) -> ValueGraphTarget? } diff --git a/Sources/TuistCore/Models/Product.swift b/Sources/TuistCore/Models/Product.swift index 265905cd1..c77213bee 100644 --- a/Sources/TuistCore/Models/Product.swift +++ b/Sources/TuistCore/Models/Product.swift @@ -174,6 +174,10 @@ public enum Product: String, CustomStringConvertible, CaseIterable, Encodable { [.framework, .staticFramework].contains(self) } + public var isDynamic: Bool { + [.framework, .dynamicLibrary].contains(self) + } + public var xcodeValue: PBXProductType { switch self { case .app: diff --git a/Sources/TuistCore/ValueGraph/ValueGraph.swift b/Sources/TuistCore/ValueGraph/ValueGraph.swift index 8c606da22..0e87acb4e 100644 --- a/Sources/TuistCore/ValueGraph/ValueGraph.swift +++ b/Sources/TuistCore/ValueGraph/ValueGraph.swift @@ -9,6 +9,9 @@ public struct ValueGraph: Equatable { /// The path where the graph has been loaded from. public let path: AbsolutePath + /// Graph's workspace. + public let workspace: Workspace + /// A dictionary where the keys are the paths to the directories where the projects are defined, /// and the values are the projects defined in the directories. public let projects: [AbsolutePath: Project] @@ -26,6 +29,7 @@ public struct ValueGraph: Equatable { public init(name: String, path: AbsolutePath, + workspace: Workspace, projects: [AbsolutePath: Project], packages: [AbsolutePath: [String: Package]], targets: [AbsolutePath: [String: Target]], @@ -33,6 +37,7 @@ public struct ValueGraph: Equatable { { self.name = name self.path = path + self.workspace = workspace self.projects = projects self.packages = packages self.targets = targets @@ -42,6 +47,7 @@ public struct ValueGraph: Equatable { public init(graph: Graph) { name = graph.name path = graph.entryPath + workspace = graph.workspace projects = graph.projects.reduce(into: [AbsolutePath: Project]()) { $0[$1.path] = $1 } packages = graph.packages.reduce(into: [AbsolutePath: [String: Package]]()) { acc, package in var packages = acc[package.path, default: [:]] @@ -88,10 +94,12 @@ public struct ValueGraph: Equatable { return .target(name: node.name, path: node.path) case let node as FrameworkNode: return .framework(path: node.path, + binaryPath: node.binaryPath, dsymPath: node.dsymPath, bcsymbolmapPaths: node.bcsymbolmapPaths, linking: node.linking, - architectures: node.architectures) + architectures: node.architectures, + isCarthage: node.isCarthage) case let node as XCFrameworkNode: return .xcframework(path: node.path, infoPlist: node.infoPlist, diff --git a/Sources/TuistCore/ValueGraph/ValueGraphDependency.swift b/Sources/TuistCore/ValueGraph/ValueGraphDependency.swift index bc665d61b..f7c736c65 100644 --- a/Sources/TuistCore/ValueGraph/ValueGraphDependency.swift +++ b/Sources/TuistCore/ValueGraph/ValueGraphDependency.swift @@ -13,10 +13,12 @@ public enum ValueGraphDependency: Hashable { /// A dependency that represents a pre-compiled framework. case framework( path: AbsolutePath, + binaryPath: AbsolutePath, dsymPath: AbsolutePath?, bcsymbolmapPaths: [AbsolutePath], linking: BinaryLinking, - architectures: [BinaryArchitecture] + architectures: [BinaryArchitecture], + isCarthage: Bool ) /// A dependency that represents a pre-compiled library. @@ -45,7 +47,7 @@ public enum ValueGraphDependency: Hashable { case let .xcframework(path, _, _, _): hasher.combine("xcframework") hasher.combine(path) - case let .framework(path, _, _, _, _): + case let .framework(path, _, _, _, _, _, _): hasher.combine("framework") hasher.combine(path) case let .library(path, _, _, _, _): @@ -70,4 +72,28 @@ public enum ValueGraphDependency: Hashable { hasher.combine(path) } } + + public var isTarget: Bool { + switch self { + case .xcframework: return false + case .framework: return false + case .library: return false + case .packageProduct: return false + case .target: return true + case .sdk: return false + case .cocoapods: return false + } + } + + public var isPrecompiled: Bool { + switch self { + case .xcframework: return true + case .framework: return true + case .library: return true + case .packageProduct: return false + case .target: return false + case .sdk: return false + case .cocoapods: return false + } + } } diff --git a/Sources/TuistCore/ValueGraph/ValueGraphTarget.swift b/Sources/TuistCore/ValueGraph/ValueGraphTarget.swift new file mode 100644 index 000000000..141749fe6 --- /dev/null +++ b/Sources/TuistCore/ValueGraph/ValueGraphTarget.swift @@ -0,0 +1,27 @@ +import Foundation +import TSCBasic + +public struct ValueGraphTarget: Equatable, Hashable, Comparable, CustomDebugStringConvertible, CustomStringConvertible { + /// Path to the directory that contains the project where the target is defined. + public let path: AbsolutePath + + /// Target representation. + public let target: Target + + /// Project that contains the target. + public let project: Project + + public static func < (lhs: ValueGraphTarget, rhs: ValueGraphTarget) -> Bool { + (lhs.path, lhs.target) < (rhs.path, rhs.target) + } + + // MARK: - CustomDebugStringConvertible/CustomStringConvertible + + public var debugDescription: String { + description + } + + public var description: String { + "Target '\(target.name)' at path '\(project.path)'" + } +} diff --git a/Sources/TuistCore/ValueGraph/ValueGraphTraverser.swift b/Sources/TuistCore/ValueGraph/ValueGraphTraverser.swift index f18d35d81..e6eea2fc3 100644 --- a/Sources/TuistCore/ValueGraph/ValueGraphTraverser.swift +++ b/Sources/TuistCore/ValueGraph/ValueGraphTraverser.swift @@ -3,41 +3,51 @@ import TSCBasic import TuistSupport public class ValueGraphTraverser: GraphTraversing { + public var name: String { graph.name } + public var hasPackages: Bool { !graph.packages.flatMap(\.value).isEmpty } + public var path: AbsolutePath { graph.path } + public var workspace: Workspace { graph.workspace } + public var projects: [AbsolutePath: Project] { graph.projects } + private let graph: ValueGraph public required init(graph: ValueGraph) { self.graph = graph } - public func target(path: AbsolutePath, name: String) -> Target? { - graph.targets[path]?[name] + public func target(path: AbsolutePath, name: String) -> ValueGraphTarget? { + guard let project = graph.projects[path], let target = graph.targets[path]?[name] else { return nil } + return ValueGraphTarget(path: path, target: target, project: project) } - public func targets(at path: AbsolutePath) -> [Target] { + public func targets(at path: AbsolutePath) -> Set { + guard let project = graph.projects[path] else { return Set() } guard let targets = graph.targets[path] else { return [] } - return Array(targets.values).sorted() + return Set(targets.values.map { ValueGraphTarget(path: path, target: $0, project: project) }) } - public func directTargetDependencies(path: AbsolutePath, name: String) -> [Target] { + public func directTargetDependencies(path: AbsolutePath, name: String) -> Set { guard let dependencies = graph.dependencies[.target(name: name, path: path)] else { return [] } - return dependencies.flatMap { (dependency) -> [Target] in + guard let project = graph.projects[path] else { return Set() } + + return Set(dependencies.flatMap { (dependency) -> [ValueGraphTarget] in guard case let ValueGraphDependency.target(dependencyName, dependencyPath) = dependency else { return [] } guard let projectDependencies = graph.targets[dependencyPath], let dependencyTarget = projectDependencies[dependencyName] else { return [] } - return [dependencyTarget] - }.sorted() + return [ValueGraphTarget(path: path, target: dependencyTarget, project: project)] + }) } - public func resourceBundleDependencies(path: AbsolutePath, name: String) -> [Target] { + public func resourceBundleDependencies(path: AbsolutePath, name: String) -> Set { guard let target = graph.targets[path]?[name] else { return [] } guard target.supportsResources else { return [] } let canHostResources: (ValueGraphDependency) -> Bool = { - self.target(from: $0)?.supportsResources == true + self.target(from: $0)?.target.supportsResources == true } let isBundle: (ValueGraphDependency) -> Bool = { - self.target(from: $0)?.product == .bundle + self.target(from: $0)?.target.product == .bundle } let bundles = filterDependencies(from: .target(name: name, path: path), @@ -45,39 +55,42 @@ public class ValueGraphTraverser: GraphTraversing { skip: canHostResources) let bundleTargets = bundles.compactMap(target(from:)) - return bundleTargets.sorted() + return Set(bundleTargets) } - public func testTargetsDependingOn(path: AbsolutePath, name: String) -> [Target] { - graph.targets[path]?.values + public func testTargetsDependingOn(path: AbsolutePath, name: String) -> Set { + guard let project = graph.projects[path] else { return Set() } + + return Set(graph.targets[path]?.values .filter { $0.product.testsBundle } .filter { graph.dependencies[.target(name: $0.name, path: path)]?.contains(.target(name: name, path: path)) == true } - .sorted() ?? [] + .map { ValueGraphTarget(path: path, target: $0, project: project) } ?? []) } - public func target(from dependency: ValueGraphDependency) -> Target? { + public func target(from dependency: ValueGraphDependency) -> ValueGraphTarget? { guard case let ValueGraphDependency.target(name, path) = dependency else { return nil } - return graph.targets[path]?[name] + guard let target = graph.targets[path]?[name] else { return nil } + guard let project = graph.projects[path] else { return nil } + return ValueGraphTarget(path: path, target: target, project: project) } - public func appExtensionDependencies(path: AbsolutePath, name: String) -> [Target] { + public func appExtensionDependencies(path: AbsolutePath, name: String) -> Set { let validProducts: [Product] = [ .appExtension, .stickerPackExtension, .watch2Extension, .messagesExtension, ] - return directTargetDependencies(path: path, name: name) - .filter { validProducts.contains($0.product) } - .sorted() + return Set(directTargetDependencies(path: path, name: name) + .filter { validProducts.contains($0.target.product) }) } - public func appClipsDependency(path: AbsolutePath, name: String) -> Target? { + public func appClipDependencies(path: AbsolutePath, name: String) -> ValueGraphTarget? { directTargetDependencies(path: path, name: name) - .first { $0.product == .appClip } + .first { $0.target.product == .appClip } } - public func directStaticDependencies(path: AbsolutePath, name: String) -> [GraphDependencyReference] { - graph.dependencies[.target(name: name, path: path)]? + public func directStaticDependencies(path: AbsolutePath, name: String) -> Set { + Set(graph.dependencies[.target(name: name, path: path)]? .compactMap { (dependency: ValueGraphDependency) -> (path: AbsolutePath, name: String)? in guard case let ValueGraphDependency.target(name, path) = dependency else { return nil @@ -86,8 +99,7 @@ public class ValueGraphTraverser: GraphTraversing { } .compactMap { graph.targets[$0.path]?[$0.name] } .filter { $0.product.isStatic } - .map { .product(target: $0.name, productName: $0.productNameWithExtension) } - .sorted() ?? [] + .map { .product(target: $0.name, productName: $0.productNameWithExtension) } ?? []) } /// It traverses the depdency graph and returns all the dependencies. @@ -105,15 +117,213 @@ public class ValueGraphTraverser: GraphTraversing { return references } + public func embeddableFrameworks(path: AbsolutePath, name: String) -> Set { + guard let target = self.target(path: path, name: name), canEmbedProducts(target: target.target) else { return Set() } + + var references: Set = Set([]) + + /// Precompiled frameworks + let precompiledFrameworks = filterDependencies(from: .target(name: name, path: path), + test: isDependencyPrecompiledDynamicAndLinkable, + skip: canDependencyEmbedProducts) + .lazy + .compactMap(dependencyReference) + references.formUnion(precompiledFrameworks) + + /// Other targets' frameworks. + let otherTargetFrameworks = filterDependencies(from: .target(name: name, path: path), + test: isDependencyDynamicTarget, + skip: canDependencyEmbedProducts) + .lazy + .compactMap(dependencyReference) + references.formUnion(otherTargetFrameworks) + + // Exclude any products embed in unit test host apps + if target.target.product == .unitTests { + if let hostApp = hostApplication(path: path, name: name) { + references.subtract(embeddableFrameworks(path: hostApp.path, name: hostApp.target.name)) + } else { + references = Set() + } + } + + return references + } + + public func linkableDependencies(path: AbsolutePath, name: String) throws -> Set { + guard let target = self.target(path: path, name: name) else { return Set() } + + var references = Set() + + // System libraries and frameworks + if target.target.canLinkStaticProducts() { + let transitiveSystemLibraries = transitiveStaticTargets(from: .target(name: name, path: path)) + .flatMap { (dependency) -> [GraphDependencyReference] in + let dependencies = self.graph.dependencies[dependency, default: []] + return dependencies.compactMap { dependencyDependency -> GraphDependencyReference? in + guard case let ValueGraphDependency.sdk(_, path, status, source) = dependencyDependency else { return nil } + return .sdk(path: path, status: status, source: source) + } + } + references.formUnion(transitiveSystemLibraries) + } + + // AppClip dependencies + if target.target.isAppClip { + let path = try SDKNode.appClip(status: .required).path + references.formUnion([GraphDependencyReference.sdk(path: path, + status: .required, + source: .system)]) + } + + // Direct system libraries and frameworks + let directSystemLibrariesAndFrameworks = graph.dependencies[.target(name: name, path: path), default: []] + .compactMap { dependency -> GraphDependencyReference? in + guard case let ValueGraphDependency.sdk(_, path, status, source) = dependency else { return nil } + return .sdk(path: path, status: status, source: source) + } + references.formUnion(directSystemLibrariesAndFrameworks) + + // Precompiled libraries and frameworks + let precompiledLibrariesAndFrameworks = graph.dependencies[.target(name: name, path: path), default: []] + .lazy + .filter(\.isPrecompiled) + .compactMap(dependencyReference) + references.formUnion(precompiledLibrariesAndFrameworks) + + // Static libraries and frameworks / Static libraries' dynamic libraries + if target.target.canLinkStaticProducts() { + var transitiveStaticTargets = self.transitiveStaticTargets(from: .target(name: name, path: path)) + + // Exclude any static products linked in a host application + if target.target.product == .unitTests { + if let hostApp = hostApplication(path: path, name: name) { + transitiveStaticTargets.subtract(self.transitiveStaticTargets(from: .target(name: hostApp.target.name, path: hostApp.project.path))) + } + } + + let transitiveStaticTargetReferences = transitiveStaticTargets.compactMap(dependencyReference) + + let staticDependenciesDynamicLibrariesAndFrameworks = transitiveStaticTargets.flatMap { (dependency) -> [GraphDependencyReference] in + self.graph.dependencies[dependency, default: []] + .lazy + .filter(\.isTarget) + .filter(isDependencyDynamicTarget) + .compactMap(dependencyReference) + } + + references.formUnion(transitiveStaticTargetReferences + staticDependenciesDynamicLibrariesAndFrameworks) + } + + // Link dynamic libraries and frameworks + let dynamicLibrariesAndFrameworks = graph.dependencies[.target(name: name, path: path), default: []] + .filter(or(isDependencyDynamicLibrary, isDependencyFramework)) + .compactMap(dependencyReference) + references.formUnion(dynamicLibrariesAndFrameworks) + + return references + } + + public func copyProductDependencies(path: AbsolutePath, name: String) -> Set { + guard let target = self.target(path: path, name: name) else { return Set() } + + var dependencies = Set() + + if target.target.product.isStatic { + dependencies.formUnion(directStaticDependencies(path: path, name: name)) + } + + dependencies.formUnion(resourceBundleDependencies(path: path, name: name).map(targetProductReference)) + + return Set(dependencies) + } + + public func librariesPublicHeadersFolders(path: AbsolutePath, name: String) -> Set { + let dependencies = graph.dependencies[.target(name: name, path: path), default: []] + let libraryPublicHeaders = dependencies.compactMap { dependency -> AbsolutePath? in + guard case let ValueGraphDependency.library(_, publicHeaders, _, _, _) = dependency else { return nil } + return publicHeaders + } + return Set(libraryPublicHeaders) + } + + public func librariesSearchPaths(path: AbsolutePath, name: String) -> Set { + let dependencies = graph.dependencies[.target(name: name, path: path), default: []] + let libraryPaths = dependencies.compactMap { dependency -> AbsolutePath? in + guard case let ValueGraphDependency.library(path, _, _, _, _) = dependency else { return nil } + return path + } + return Set(libraryPaths.compactMap { $0.removingLastComponent() }) + } + + public func librariesSwiftIncludePaths(path: AbsolutePath, name: String) -> Set { + let dependencies = graph.dependencies[.target(name: name, path: path), default: []] + let librarySwiftModuleMapPaths = dependencies.compactMap { dependency -> AbsolutePath? in + guard case let ValueGraphDependency.library(_, _, _, _, swiftModuleMapPath) = dependency else { return nil } + return swiftModuleMapPath + } + return Set(librarySwiftModuleMapPaths.compactMap { $0.removingLastComponent() }) + } + + public func runPathSearchPaths(path: AbsolutePath, name: String) -> Set { + guard let target = target(path: path, name: name), + canEmbedProducts(target: target.target), + target.target.product == .unitTests, + hostApplication(path: path, name: name) == nil + else { + return Set() + } + + var references: Set = Set([]) + + let from = ValueGraphDependency.target(name: name, path: path) + let precompiledFramewoksPaths = filterDependencies(from: from, + test: isDependencyPrecompiledDynamicAndLinkable, + skip: canDependencyEmbedProducts) + .lazy + .compactMap { (dependency: ValueGraphDependency) -> AbsolutePath? in + switch dependency { + case let .xcframework(path, _, _, _): return path + case let .framework(path, _, _, _, _, _, _): return path + case .library: return nil + case .packageProduct: return nil + case .target: return nil + case .sdk: return nil + case .cocoapods: return nil + } + } + .map(\.parentDirectory) + + references.formUnion(precompiledFramewoksPaths) + return references + } + + public func hostTargetFor(path: AbsolutePath, name: String) -> ValueGraphTarget? { + guard let targets = graph.targets[path] else { return nil } + guard let project = graph.projects[path] else { return nil } + + return targets.values.compactMap { (target) -> ValueGraphTarget? in + let dependencies = self.graph.dependencies[.target(name: target.name, path: path), default: Set()] + let dependsOnTarget = dependencies.contains(where: { dependency in + guard case let ValueGraphDependency.target(_name, _path) = dependency else { return false } + return _name == name && _path == path + }) + let valueGraphTarget = ValueGraphTarget(path: path, target: target, project: project) + return dependsOnTarget ? valueGraphTarget : nil + }.first + } + + // MARK: - Internal + /// The method collects the dependencies that are selected by the provided test closure. /// The skip closure allows skipping the traversing of a specific dependendency branch. /// - Parameters: /// - from: Dependency from which the traverse is done. /// - test: If the closure returns true, the dependency is included. /// - skip: If the closure returns false, the traversing logic doesn't traverse the dependencies from that dependency. - public func filterDependencies(from rootDependency: ValueGraphDependency, - test: (ValueGraphDependency) -> Bool = { _ in true }, - skip: (ValueGraphDependency) -> Bool = { _ in false }) -> Set + func filterDependencies(from rootDependency: ValueGraphDependency, + test: (ValueGraphDependency) -> Bool = { _ in true }, + skip: (ValueGraphDependency) -> Bool = { _ in false }) -> Set { var stack = Stack() @@ -150,4 +360,144 @@ public class ValueGraphTraverser: GraphTraversing { return references } + + func transitiveStaticTargets(from dependency: ValueGraphDependency) -> Set { + filterDependencies(from: dependency, + test: isDependencyStaticTarget, + skip: canDependencyLinkStaticProducts) + } + + func targetProductReference(target: ValueGraphTarget) -> GraphDependencyReference { + .product(target: target.target.name, productName: target.target.productNameWithExtension) + } + + func isDependencyPrecompiledLibrary(dependency: ValueGraphDependency) -> Bool { + switch dependency { + case .xcframework: return true + case .framework: return true + case .library: return true + case .packageProduct: return false + case .target: return false + case .sdk: return false + case .cocoapods: return false + } + } + + func isDependencyPrecompiledFramework(dependency: ValueGraphDependency) -> Bool { + switch dependency { + case .xcframework: return true + case .framework: return true + case .library: return false + case .packageProduct: return false + case .target: return false + case .sdk: return false + case .cocoapods: return false + } + } + + func isDependencyStaticTarget(dependency: ValueGraphDependency) -> Bool { + guard case let ValueGraphDependency.target(name, path) = dependency, + let target = self.target(path: path, name: name) else { return false } + return target.target.product.isStatic + } + + func isDependencyDynamicLibrary(dependency: ValueGraphDependency) -> Bool { + guard case let ValueGraphDependency.target(name, path) = dependency, + let target = self.target(path: path, name: name) else { return false } + return target.target.product == .dynamicLibrary + } + + func isDependencyFramework(dependency: ValueGraphDependency) -> Bool { + guard case let ValueGraphDependency.target(name, path) = dependency, + let target = self.target(path: path, name: name) else { return false } + return target.target.product == .framework + } + + func isDependencyDynamicTarget(dependency: ValueGraphDependency) -> Bool { + switch dependency { + case .xcframework: return false + case .framework: return false + case .library: return false + case .packageProduct: return false + case let .target(name, path): + guard let target = self.target(path: path, name: name) else { return false } + return target.target.product.isDynamic + case .sdk: return false + case .cocoapods: return false + } + } + + func isDependencyPrecompiledDynamicAndLinkable(dependency: ValueGraphDependency) -> Bool { + switch dependency { + case let .xcframework(_, _, _, linking): return linking == .dynamic + case let .framework(_, _, _, _, linking, _, _): return linking == .dynamic + case .library: return false + case .packageProduct: return false + case .target: return false + case .sdk: return false + case .cocoapods: return false + } + } + + func canDependencyEmbedProducts(dependency: ValueGraphDependency) -> Bool { + guard case let ValueGraphDependency.target(name, path) = dependency, + let target = self.target(path: path, name: name) else { return false } + return canEmbedProducts(target: target.target) + } + + func canDependencyLinkStaticProducts(dependency: ValueGraphDependency) -> Bool { + guard case let ValueGraphDependency.target(name, path) = dependency, + let target = self.target(path: path, name: name) else { return false } + return target.target.canLinkStaticProducts() + } + + func hostApplication(path: AbsolutePath, name: String) -> ValueGraphTarget? { + directTargetDependencies(path: path, name: name) + .first(where: { $0.target.product == .app }) + } + + func canEmbedProducts(target: Target) -> Bool { + let validProducts: [Product] = [ + .app, + .unitTests, + .uiTests, + .watch2Extension, + ] + return validProducts.contains(target.product) + } + + func dependencyReference(dependency: ValueGraphDependency) -> GraphDependencyReference? { + switch dependency { + case .cocoapods: + return nil + case let .framework(path, binaryPath, dsymPath, bcsymbolmapPaths, linking, architectures, isCarthage): + return .framework(path: path, + binaryPath: binaryPath, + isCarthage: isCarthage, + dsymPath: dsymPath, + bcsymbolmapPaths: bcsymbolmapPaths, + linking: linking, + architectures: architectures, + product: (linking == .static) ? .staticFramework : .framework) + case let .library(path, _, linking, architectures, _): + return .library(path: path, + linking: linking, + architectures: architectures, + product: (linking == .static) ? .staticLibrary : .dynamicLibrary) + case .packageProduct: + return nil + case let .sdk(_, path, status, source): + return .sdk(path: path, + status: status, + source: source) + case let .target(name, path): + guard let target = self.target(path: path, name: name) else { return nil } + return .product(target: target.target.name, productName: target.target.productNameWithExtension) + case let .xcframework(path, infoPlist, primaryBinaryPath, _): + return .xcframework(path: path, + infoPlist: infoPlist, + primaryBinaryPath: primaryBinaryPath, + binaryPath: primaryBinaryPath) + } + } } diff --git a/Sources/TuistCoreTesting/Graph/GraphDependencyReference+TestData.swift b/Sources/TuistCoreTesting/Graph/GraphDependencyReference+TestData.swift index 2a8a450fe..6bdfa868e 100644 --- a/Sources/TuistCoreTesting/Graph/GraphDependencyReference+TestData.swift +++ b/Sources/TuistCoreTesting/Graph/GraphDependencyReference+TestData.swift @@ -27,7 +27,8 @@ public extension GraphDependencyReference { path: AbsolutePath = "/frameworks/tuist.xcframework", infoPlist: XCFrameworkInfoPlist = .test(), primaryBinaryPath: AbsolutePath = "/frameworks/tuist.xcframework/ios-arm64/tuist", - binaryPath: AbsolutePath = "/frameworks/tuist.xcframework/ios-arm64/tuist" + binaryPath: AbsolutePath = "/frameworks/tuist.xcframework/ios-arm64/tuist", + linking _: BinaryLinking = .dynamic ) -> GraphDependencyReference { GraphDependencyReference.xcframework(path: path, infoPlist: infoPlist, @@ -36,13 +37,11 @@ public extension GraphDependencyReference { } static func testLibrary(path: AbsolutePath = "/libraries/library.a", - binaryPath: AbsolutePath = "/libraries/library.a", linking: BinaryLinking = .static, architectures: [BinaryArchitecture] = [BinaryArchitecture.arm64], product: Product = .staticLibrary) -> GraphDependencyReference { GraphDependencyReference.library(path: path, - binaryPath: binaryPath, linking: linking, architectures: architectures, product: product) diff --git a/Sources/TuistCoreTesting/GraphTraverser/MockGraphTraverser.swift b/Sources/TuistCoreTesting/GraphTraverser/MockGraphTraverser.swift index 9b0149c8e..f067bf829 100644 --- a/Sources/TuistCoreTesting/GraphTraverser/MockGraphTraverser.swift +++ b/Sources/TuistCoreTesting/GraphTraverser/MockGraphTraverser.swift @@ -3,13 +3,63 @@ import TSCBasic @testable import TuistCore final class MockGraphTraverser: GraphTraversing { + var invokedNameGetter = false + var invokedNameGetterCount = 0 + var stubbedName: String! = "" + + var name: String { + invokedNameGetter = true + invokedNameGetterCount += 1 + return stubbedName + } + + var invokedHasPackagesGetter = false + var invokedHasPackagesGetterCount = 0 + var stubbedHasPackages: Bool! = false + + var hasPackages: Bool { + invokedHasPackagesGetter = true + invokedHasPackagesGetterCount += 1 + return stubbedHasPackages + } + + var invokedPathGetter = false + var invokedPathGetterCount = 0 + var stubbedPath: AbsolutePath! + + var path: AbsolutePath { + invokedPathGetter = true + invokedPathGetterCount += 1 + return stubbedPath + } + + var invokedWorkspaceGetter = false + var invokedWorkspaceGetterCount = 0 + var stubbedWorkspace: Workspace! + + var workspace: Workspace { + invokedWorkspaceGetter = true + invokedWorkspaceGetterCount += 1 + return stubbedWorkspace + } + + var invokedProjectsGetter = false + var invokedProjectsGetterCount = 0 + var stubbedProjects: [AbsolutePath: Project]! = [:] + + var projects: [AbsolutePath: Project] { + invokedProjectsGetter = true + invokedProjectsGetterCount += 1 + return stubbedProjects + } + var invokedTarget = false var invokedTargetCount = 0 var invokedTargetParameters: (path: AbsolutePath, name: String)? var invokedTargetParametersList = [(path: AbsolutePath, name: String)]() - var stubbedTargetResult: Target! + var stubbedTargetResult: ValueGraphTarget! - func target(path: AbsolutePath, name: String) -> Target? { + func target(path: AbsolutePath, name: String) -> ValueGraphTarget? { invokedTarget = true invokedTargetCount += 1 invokedTargetParameters = (path, name) @@ -21,9 +71,9 @@ final class MockGraphTraverser: GraphTraversing { var invokedTargetsCount = 0 var invokedTargetsParameters: (path: AbsolutePath, Void)? var invokedTargetsParametersList = [(path: AbsolutePath, Void)]() - var stubbedTargetsResult: [Target]! = [] + var stubbedTargetsResult: Set! = [] - func targets(at path: AbsolutePath) -> [Target] { + func targets(at path: AbsolutePath) -> Set { invokedTargets = true invokedTargetsCount += 1 invokedTargetsParameters = (path, ()) @@ -35,9 +85,9 @@ final class MockGraphTraverser: GraphTraversing { var invokedDirectTargetDependenciesCount = 0 var invokedDirectTargetDependenciesParameters: (path: AbsolutePath, name: String)? var invokedDirectTargetDependenciesParametersList = [(path: AbsolutePath, name: String)]() - var stubbedDirectTargetDependenciesResult: [Target]! = [] + var stubbedDirectTargetDependenciesResult: Set! = [] - func directTargetDependencies(path: AbsolutePath, name: String) -> [Target] { + func directTargetDependencies(path: AbsolutePath, name: String) -> Set { invokedDirectTargetDependencies = true invokedDirectTargetDependenciesCount += 1 invokedDirectTargetDependenciesParameters = (path, name) @@ -49,9 +99,9 @@ final class MockGraphTraverser: GraphTraversing { var invokedAppExtensionDependenciesCount = 0 var invokedAppExtensionDependenciesParameters: (path: AbsolutePath, name: String)? var invokedAppExtensionDependenciesParametersList = [(path: AbsolutePath, name: String)]() - var stubbedAppExtensionDependenciesResult: [Target]! = [] + var stubbedAppExtensionDependenciesResult: Set! = [] - func appExtensionDependencies(path: AbsolutePath, name: String) -> [Target] { + func appExtensionDependencies(path: AbsolutePath, name: String) -> Set { invokedAppExtensionDependencies = true invokedAppExtensionDependenciesCount += 1 invokedAppExtensionDependenciesParameters = (path, name) @@ -63,9 +113,9 @@ final class MockGraphTraverser: GraphTraversing { var invokedResourceBundleDependenciesCount = 0 var invokedResourceBundleDependenciesParameters: (path: AbsolutePath, name: String)? var invokedResourceBundleDependenciesParametersList = [(path: AbsolutePath, name: String)]() - var stubbedResourceBundleDependenciesResult: [Target]! = [] + var stubbedResourceBundleDependenciesResult: Set! = [] - func resourceBundleDependencies(path: AbsolutePath, name: String) -> [Target] { + func resourceBundleDependencies(path: AbsolutePath, name: String) -> Set { invokedResourceBundleDependencies = true invokedResourceBundleDependenciesCount += 1 invokedResourceBundleDependenciesParameters = (path, name) @@ -77,9 +127,9 @@ final class MockGraphTraverser: GraphTraversing { var invokedTestTargetsDependingOnCount = 0 var invokedTestTargetsDependingOnParameters: (path: AbsolutePath, name: String)? var invokedTestTargetsDependingOnParametersList = [(path: AbsolutePath, name: String)]() - var stubbedTestTargetsDependingOnResult: [Target]! = [] + var stubbedTestTargetsDependingOnResult: Set! = [] - func testTargetsDependingOn(path: AbsolutePath, name: String) -> [Target] { + func testTargetsDependingOn(path: AbsolutePath, name: String) -> Set { invokedTestTargetsDependingOn = true invokedTestTargetsDependingOnCount += 1 invokedTestTargetsDependingOnParameters = (path, name) @@ -91,9 +141,9 @@ final class MockGraphTraverser: GraphTraversing { var invokedDirectStaticDependenciesCount = 0 var invokedDirectStaticDependenciesParameters: (path: AbsolutePath, name: String)? var invokedDirectStaticDependenciesParametersList = [(path: AbsolutePath, name: String)]() - var stubbedDirectStaticDependenciesResult: [GraphDependencyReference]! = [] + var stubbedDirectStaticDependenciesResult: Set! = [] - func directStaticDependencies(path: AbsolutePath, name: String) -> [GraphDependencyReference] { + func directStaticDependencies(path: AbsolutePath, name: String) -> Set { invokedDirectStaticDependencies = true invokedDirectStaticDependenciesCount += 1 invokedDirectStaticDependenciesParameters = (path, name) @@ -101,7 +151,133 @@ final class MockGraphTraverser: GraphTraversing { return stubbedDirectStaticDependenciesResult } - func appClipsDependency(path _: AbsolutePath, name _: String) -> Target? { - nil + var invokedAppClipDependencies = false + var invokedAppClipDependenciesCount = 0 + var invokedAppClipDependenciesParameters: (path: AbsolutePath, name: String)? + var invokedAppClipDependenciesParametersList = [(path: AbsolutePath, name: String)]() + var stubbedAppClipDependenciesResult: ValueGraphTarget! + + func appClipDependencies(path: AbsolutePath, name: String) -> ValueGraphTarget? { + invokedAppClipDependencies = true + invokedAppClipDependenciesCount += 1 + invokedAppClipDependenciesParameters = (path, name) + invokedAppClipDependenciesParametersList.append((path, name)) + return stubbedAppClipDependenciesResult + } + + var invokedEmbeddableFrameworks = false + var invokedEmbeddableFrameworksCount = 0 + var invokedEmbeddableFrameworksParameters: (path: AbsolutePath, name: String)? + var invokedEmbeddableFrameworksParametersList = [(path: AbsolutePath, name: String)]() + var stubbedEmbeddableFrameworksResult: Set! = [] + + func embeddableFrameworks(path: AbsolutePath, name: String) -> Set { + invokedEmbeddableFrameworks = true + invokedEmbeddableFrameworksCount += 1 + invokedEmbeddableFrameworksParameters = (path, name) + invokedEmbeddableFrameworksParametersList.append((path, name)) + return stubbedEmbeddableFrameworksResult + } + + var invokedLinkableDependencies = false + var invokedLinkableDependenciesCount = 0 + var invokedLinkableDependenciesParameters: (path: AbsolutePath, name: String)? + var invokedLinkableDependenciesParametersList = [(path: AbsolutePath, name: String)]() + var stubbedLinkableDependenciesError: Error? + var stubbedLinkableDependenciesResult: Set! = [] + + func linkableDependencies(path: AbsolutePath, name: String) throws -> Set { + invokedLinkableDependencies = true + invokedLinkableDependenciesCount += 1 + invokedLinkableDependenciesParameters = (path, name) + invokedLinkableDependenciesParametersList.append((path, name)) + if let error = stubbedLinkableDependenciesError { + throw error + } + return stubbedLinkableDependenciesResult + } + + var invokedCopyProductDependencies = false + var invokedCopyProductDependenciesCount = 0 + var invokedCopyProductDependenciesParameters: (path: AbsolutePath, name: String)? + var invokedCopyProductDependenciesParametersList = [(path: AbsolutePath, name: String)]() + var stubbedCopyProductDependenciesResult: Set! = [] + + func copyProductDependencies(path: AbsolutePath, name: String) -> Set { + invokedCopyProductDependencies = true + invokedCopyProductDependenciesCount += 1 + invokedCopyProductDependenciesParameters = (path, name) + invokedCopyProductDependenciesParametersList.append((path, name)) + return stubbedCopyProductDependenciesResult + } + + var invokedLibrariesPublicHeadersFolders = false + var invokedLibrariesPublicHeadersFoldersCount = 0 + var invokedLibrariesPublicHeadersFoldersParameters: (path: AbsolutePath, name: String)? + var invokedLibrariesPublicHeadersFoldersParametersList = [(path: AbsolutePath, name: String)]() + var stubbedLibrariesPublicHeadersFoldersResult: Set! = [] + + func librariesPublicHeadersFolders(path: AbsolutePath, name: String) -> Set { + invokedLibrariesPublicHeadersFolders = true + invokedLibrariesPublicHeadersFoldersCount += 1 + invokedLibrariesPublicHeadersFoldersParameters = (path, name) + invokedLibrariesPublicHeadersFoldersParametersList.append((path, name)) + return stubbedLibrariesPublicHeadersFoldersResult + } + + var invokedLibrariesSearchPaths = false + var invokedLibrariesSearchPathsCount = 0 + var invokedLibrariesSearchPathsParameters: (path: AbsolutePath, name: String)? + var invokedLibrariesSearchPathsParametersList = [(path: AbsolutePath, name: String)]() + var stubbedLibrariesSearchPathsResult: Set! = [] + + func librariesSearchPaths(path: AbsolutePath, name: String) -> Set { + invokedLibrariesSearchPaths = true + invokedLibrariesSearchPathsCount += 1 + invokedLibrariesSearchPathsParameters = (path, name) + invokedLibrariesSearchPathsParametersList.append((path, name)) + return stubbedLibrariesSearchPathsResult + } + + var invokedLibrariesSwiftIncludePaths = false + var invokedLibrariesSwiftIncludePathsCount = 0 + var invokedLibrariesSwiftIncludePathsParameters: (path: AbsolutePath, name: String)? + var invokedLibrariesSwiftIncludePathsParametersList = [(path: AbsolutePath, name: String)]() + var stubbedLibrariesSwiftIncludePathsResult: Set! = [] + + func librariesSwiftIncludePaths(path: AbsolutePath, name: String) -> Set { + invokedLibrariesSwiftIncludePaths = true + invokedLibrariesSwiftIncludePathsCount += 1 + invokedLibrariesSwiftIncludePathsParameters = (path, name) + invokedLibrariesSwiftIncludePathsParametersList.append((path, name)) + return stubbedLibrariesSwiftIncludePathsResult + } + + var invokedRunPathSearchPaths = false + var invokedRunPathSearchPathsCount = 0 + var invokedRunPathSearchPathsParameters: (path: AbsolutePath, name: String)? + var invokedRunPathSearchPathsParametersList = [(path: AbsolutePath, name: String)]() + var stubbedRunPathSearchPathsResult: Set! = [] + + func runPathSearchPaths(path: AbsolutePath, name: String) -> Set { + invokedRunPathSearchPaths = true + invokedRunPathSearchPathsCount += 1 + invokedRunPathSearchPathsParameters = (path, name) + invokedRunPathSearchPathsParametersList.append((path, name)) + return stubbedRunPathSearchPathsResult + } + + var invokedHostTargetFor = false + var invokedHostTargetForCount = 0 + var invokedHostTargetForParameters: (path: AbsolutePath, name: String)? + var invokedHostTargetForParametersList = [(path: AbsolutePath, name: String)]() + var stubbedHostTargetForResult: ValueGraphTarget! + + func hostTargetFor(path: AbsolutePath, name: String) -> ValueGraphTarget? { + invokedHostTargetFor = true + invokedHostTargetForCount += 1 + invokedHostTargetForParameters = (path, name) + invokedHostTargetForParametersList.append((path, name)) + return stubbedHostTargetForResult } } diff --git a/Sources/TuistCoreTesting/ValueGraph/ValueGraph+TestData.swift b/Sources/TuistCoreTesting/ValueGraph/ValueGraph+TestData.swift index 538dc5fbc..af200b052 100644 --- a/Sources/TuistCoreTesting/ValueGraph/ValueGraph+TestData.swift +++ b/Sources/TuistCoreTesting/ValueGraph/ValueGraph+TestData.swift @@ -4,7 +4,8 @@ import TuistCore public extension ValueGraph { static func test(name: String = "graph", - path: AbsolutePath, + path: AbsolutePath = .root, + workspace: Workspace = .test(), projects: [AbsolutePath: Project] = [:], packages: [AbsolutePath: [String: Package]] = [:], targets: [AbsolutePath: [String: Target]] = [:], @@ -12,6 +13,7 @@ public extension ValueGraph { { ValueGraph(name: name, path: path, + workspace: workspace, projects: projects, packages: packages, targets: targets, diff --git a/Sources/TuistCoreTesting/ValueGraph/ValueGraphDependency+TestData.swift b/Sources/TuistCoreTesting/ValueGraph/ValueGraphDependency+TestData.swift new file mode 100644 index 000000000..240d71d2b --- /dev/null +++ b/Sources/TuistCoreTesting/ValueGraph/ValueGraphDependency+TestData.swift @@ -0,0 +1,76 @@ +import Foundation +import TSCBasic + +@testable import TuistCore + +public extension ValueGraphDependency { + static func testCocoapods(path: AbsolutePath = .root) -> ValueGraphDependency { + ValueGraphDependency.cocoapods(path: path) + } + + static func testFramework(path: AbsolutePath = AbsolutePath.root.appending(component: "Test.framework"), + binaryPath: AbsolutePath = AbsolutePath.root.appending(RelativePath("Test.framework/Test")), + dsymPath: AbsolutePath? = nil, + bcsymbolmapPaths: [AbsolutePath] = [], + linking: BinaryLinking = .dynamic, + architectures: [BinaryArchitecture] = [.armv7], + isCarthage: Bool = false) -> ValueGraphDependency + { + ValueGraphDependency.framework(path: path, + binaryPath: binaryPath, + dsymPath: dsymPath, + bcsymbolmapPaths: bcsymbolmapPaths, + linking: linking, + architectures: architectures, + isCarthage: isCarthage) + } + + static func testXCFramework(path: AbsolutePath = AbsolutePath.root.appending(RelativePath("Test.xcframework")), + infoPlist: XCFrameworkInfoPlist = .test(), + primaryBinaryPath: AbsolutePath = AbsolutePath.root.appending(RelativePath("Test.xcframework/Test")), + linking: BinaryLinking = .dynamic) -> ValueGraphDependency + { + .xcframework(path: path, + infoPlist: infoPlist, + primaryBinaryPath: primaryBinaryPath, + linking: linking) + } + + static func testTarget(name: String = "Test", + path: AbsolutePath = .root) -> ValueGraphDependency + { + .target(name: name, + path: path) + } + + static func testSDK(name: String = "XCTest", + path: AbsolutePath = AbsolutePath.root.appending(RelativePath("XCTest.framework")), + status: SDKStatus = .required, + source: SDKSource = .system) -> ValueGraphDependency + { + .sdk(name: name, + path: path, + status: status, + source: source) + } + + static func testLibrary(path: AbsolutePath = AbsolutePath.root.appending(RelativePath("libTuist.a")), + publicHeaders: AbsolutePath = AbsolutePath.root.appending(RelativePath("headers")), + linking: BinaryLinking = .dynamic, + architectures: [BinaryArchitecture] = [.armv7], + swiftModuleMap: AbsolutePath? = nil) -> ValueGraphDependency + { + .library(path: path, + publicHeaders: publicHeaders, + linking: linking, + architectures: architectures, + swiftModuleMap: swiftModuleMap) + } + + static func testPackageProduct(path: AbsolutePath = .root, + product: String = "Tuist") -> ValueGraphDependency + { + .packageProduct(path: path, + product: product) + } +} diff --git a/Sources/TuistCoreTesting/ValueGraph/ValueGraphTarget+TestData.swift b/Sources/TuistCoreTesting/ValueGraph/ValueGraphTarget+TestData.swift new file mode 100644 index 000000000..cd47762d7 --- /dev/null +++ b/Sources/TuistCoreTesting/ValueGraph/ValueGraphTarget+TestData.swift @@ -0,0 +1,15 @@ +import Foundation +import TSCBasic + +@testable import TuistCore + +public extension ValueGraphTarget { + static func test(path: AbsolutePath = .root, + target: Target = .test(), + project: Project = .test()) -> ValueGraphTarget + { + ValueGraphTarget(path: path, + target: target, + project: project) + } +} diff --git a/Sources/TuistGenerator/Generator/BuildPhaseGenerator.swift b/Sources/TuistGenerator/Generator/BuildPhaseGenerator.swift index 5c098033f..e638099b2 100644 --- a/Sources/TuistGenerator/Generator/BuildPhaseGenerator.swift +++ b/Sources/TuistGenerator/Generator/BuildPhaseGenerator.swift @@ -325,8 +325,10 @@ final class BuildPhaseGenerator: BuildPhaseGenerating { pbxproj: PBXProj, resourcesBuildPhase: PBXResourcesBuildPhase) { - let bundles = graphTraverser.resourceBundleDependencies(path: path, name: target.name) - let refs = bundles.compactMap { fileElements.product(target: $0.name) } + let bundles = graphTraverser + .resourceBundleDependencies(path: path, name: target.name) + .sorted() + let refs = bundles.compactMap { fileElements.product(target: $0.target.name) } refs.forEach { let pbxBuildFile = PBXBuildFile(file: $0) @@ -342,14 +344,14 @@ final class BuildPhaseGenerator: BuildPhaseGenerating { fileElements: ProjectFileElements, pbxproj: PBXProj) throws { - let appExtensions = graphTraverser.appExtensionDependencies(path: path, name: target.name) + let appExtensions = graphTraverser.appExtensionDependencies(path: path, name: target.name).sorted() guard !appExtensions.isEmpty else { return } let appExtensionsBuildPhase = PBXCopyFilesBuildPhase(dstSubfolderSpec: .plugins, name: "Embed App Extensions") pbxproj.add(object: appExtensionsBuildPhase) pbxTarget.buildPhases.append(appExtensionsBuildPhase) - let refs = appExtensions.compactMap { fileElements.product(target: $0.name) } + let refs = appExtensions.compactMap { fileElements.product(target: $0.target.name) } refs.forEach { let pbxBuildFile = PBXBuildFile(file: $0, settings: ["ATTRIBUTES": ["RemoveHeadersOnCopy"]]) @@ -366,7 +368,7 @@ final class BuildPhaseGenerator: BuildPhaseGenerating { pbxproj: PBXProj) throws { let targetDependencies = graphTraverser.directTargetDependencies(path: path, name: target.name) - let watchApps = targetDependencies.filter { $0.product == .watch2App } + let watchApps = targetDependencies.filter { $0.target.product == .watch2App } guard !watchApps.isEmpty else { return } let embedWatchAppBuildPhase = PBXCopyFilesBuildPhase(dstPath: "$(CONTENTS_FOLDER_PATH)/Watch", @@ -375,7 +377,7 @@ final class BuildPhaseGenerator: BuildPhaseGenerating { pbxproj.add(object: embedWatchAppBuildPhase) pbxTarget.buildPhases.append(embedWatchAppBuildPhase) - let refs = watchApps.compactMap { fileElements.product(target: $0.name) } + let refs = watchApps.compactMap { fileElements.product(target: $0.target.name) } refs.forEach { let pbxBuildFile = PBXBuildFile(file: $0, settings: ["ATTRIBUTES": ["RemoveHeadersOnCopy"]]) @@ -395,7 +397,7 @@ final class BuildPhaseGenerator: BuildPhaseGenerating { return } - guard let appClips = graphTraverser.appClipsDependency(path: path, name: target.name) else { + guard let appClips = graphTraverser.appClipDependencies(path: path, name: target.name) else { return } @@ -405,7 +407,7 @@ final class BuildPhaseGenerator: BuildPhaseGenerating { pbxproj.add(object: embedAppClipsBuildPhase) pbxTarget.buildPhases.append(embedAppClipsBuildPhase) - let refs = fileElements.product(target: appClips.name) + let refs = fileElements.product(target: appClips.target.name) let pbxBuildFile = PBXBuildFile(file: refs, settings: ["ATTRIBUTES": ["RemoveHeadersOnCopy"]]) pbxproj.add(object: pbxBuildFile) diff --git a/Sources/TuistGenerator/Generator/ConfigGenerator.swift b/Sources/TuistGenerator/Generator/ConfigGenerator.swift index 142ff73ca..21749f854 100644 --- a/Sources/TuistGenerator/Generator/ConfigGenerator.swift +++ b/Sources/TuistGenerator/Generator/ConfigGenerator.swift @@ -225,22 +225,22 @@ final class ConfigGenerator: ConfigGenerating { } let targetDependencies = graphTraverser.directTargetDependencies(path: projectPath, name: target.name) - let appDependency = targetDependencies.first { $0.product.canHostTests() } + let appDependency = targetDependencies.first { $0.target.product.canHostTests() } guard let app = appDependency else { return [:] } var settings: SettingsDictionary = [:] - settings["TEST_TARGET_NAME"] = .string("\(app.name)") + settings["TEST_TARGET_NAME"] = .string("\(app.target.name)") if target.product == .unitTests { - var testHostPath = "$(BUILT_PRODUCTS_DIR)/\(app.productNameWithExtension)" + var testHostPath = "$(BUILT_PRODUCTS_DIR)/\(app.target.productNameWithExtension)" if target.platform == .macOS { testHostPath += "/Contents/MacOS" } - settings["TEST_HOST"] = .string("\(testHostPath)/\(app.productName)") + settings["TEST_HOST"] = .string("\(testHostPath)/\(app.target.productName)") settings["BUNDLE_LOADER"] = "$(TEST_HOST)" } @@ -287,12 +287,12 @@ final class ConfigGenerator: ConfigGenerating { } let targetDependencies = graphTraverser.directTargetDependencies(path: projectPath, name: target.name) - guard let watchExtension = targetDependencies.first(where: { $0.product == .watch2Extension }) else { + guard let watchExtension = targetDependencies.first(where: { $0.target.product == .watch2Extension }) else { return [:] } return [ - "IBSC_MODULE": .string(watchExtension.productName), + "IBSC_MODULE": .string(watchExtension.target.productName), ] } } diff --git a/Sources/TuistGenerator/Generator/LinkGenerator.swift b/Sources/TuistGenerator/Generator/LinkGenerator.swift index 88cc2d292..7f71a9633 100644 --- a/Sources/TuistGenerator/Generator/LinkGenerator.swift +++ b/Sources/TuistGenerator/Generator/LinkGenerator.swift @@ -338,7 +338,7 @@ final class LinkGenerator: LinkGenerating { switch dependency { case let .framework(path, _, _, _, _, _, _, _): try addBuildFile(path) - case let .library(path, _, _, _, _): + case let .library(path, _, _, _): try addBuildFile(path) case let .xcframework(path, _, _, _): try addBuildFile(path) diff --git a/Sources/TuistGenerator/Generator/ProjectFileElements.swift b/Sources/TuistGenerator/Generator/ProjectFileElements.swift index 93c9ec10a..8c634cca0 100644 --- a/Sources/TuistGenerator/Generator/ProjectFileElements.swift +++ b/Sources/TuistGenerator/Generator/ProjectFileElements.swift @@ -198,7 +198,7 @@ class ProjectFileElements { try generatePrecompiled(path) case let .framework(path, _, _, _, _, _, _, _): try generatePrecompiled(path) - case let .library(path, _, _, _, _): + case let .library(path, _, _, _): try generatePrecompiled(path) case let .sdk(sdkNodePath, _, _): generateSDKFileElement(sdkNodePath: sdkNodePath, diff --git a/Tests/TuistCoreTests/Graph/GraphTests.swift b/Tests/TuistCoreTests/Graph/GraphTests.swift index 8b20ba188..6a9ca7636 100644 --- a/Tests/TuistCoreTests/Graph/GraphTests.swift +++ b/Tests/TuistCoreTests/Graph/GraphTests.swift @@ -8,971 +8,7 @@ import XCTest @testable import TuistSupport @testable import TuistSupportTesting -final class GraphErrorTests: XCTestCase { - func test_description_when_unsupportedFileExtension() { - let error = GraphError.unsupportedFileExtension("type") - let description = "Could't obtain product file extension for product type: type" - XCTAssertEqual(error.description, description) - } - - func test_type_when_unsupportedFileExtension() { - let error = GraphError.unsupportedFileExtension("type") - XCTAssertEqual(error.type, .bug) - } -} - final class GraphTests: TuistUnitTestCase { - func test_frameworks() throws { - let framework = FrameworkNode.test(path: AbsolutePath("/path/to/framework.framework")) - let graph = Graph.test(precompiled: [framework]) - XCTAssertTrue(graph.frameworks.contains(framework)) - } - - func test_targetDependencies() throws { - let target = Target.test(name: "Main") - let dependency = Target.test(name: "Dependency", product: .staticLibrary) - let project = Project.test(targets: [target, dependency]) - let dependencyNode = TargetNode(project: project, - target: dependency, - dependencies: []) - let targetNode = TargetNode(project: project, - target: target, - dependencies: [dependencyNode]) - let graph = Graph.test(targets: [targetNode.path: [targetNode]]) - let dependencies = graph.targetDependencies(path: project.path, - name: target.name) - XCTAssertEqual(dependencies.first?.target.name, "Dependency") - } - - func test_testTargetsDependingOn() throws { - // given - let target = Target.test(name: "Main") - let dependentTarget = Target.test(name: "Dependency", product: .staticLibrary) - let testTarget1 = Target.test(name: "MainTests1", product: .unitTests) - let testTarget2 = Target.test(name: "MainTests2", product: .unitTests) - let testTarget3 = Target.test(name: "MainTests3", product: .unitTests) - let testTargets = [testTarget1, testTarget2, testTarget3] - let project = Project.test(targets: [target, dependentTarget] + testTargets) - - let dependencyNode = TargetNode(project: project, target: dependentTarget, dependencies: []) - let targetNode = TargetNode(project: project, target: target, dependencies: [dependencyNode]) - let testsNodes = testTargets.map { TargetNode(project: project, target: $0, dependencies: [targetNode]) } - - let targets = testsNodes.reduce(into: [project.path: [targetNode, dependencyNode]]) { - $0[project.path]?.append($1) - } - let graph = Graph.test(projects: [project], targets: targets) - - // when - let testDependencies = graph.testTargetsDependingOn(path: project.path, name: target.name) - - // then - let testDependenciesNames = try XCTUnwrap(testDependencies).map(\.name) - XCTAssertEqual(testDependenciesNames.count, 3) - XCTAssertEqual(testDependenciesNames, ["MainTests1", "MainTests2", "MainTests3"]) - } - - func test_linkableDependencies_whenPrecompiled() throws { - let target = Target.test(name: "Main") - let precompiledNode = FrameworkNode.test(path: AbsolutePath("/test/test.framework")) - let project = Project.test(targets: [target]) - let targetNode = TargetNode(project: project, - target: target, - dependencies: [precompiledNode]) - let graph = Graph.test(targets: [targetNode.path: [targetNode]]) - - let got = try graph.linkableDependencies(path: project.path, name: target.name) - XCTAssertEqual(got.first, GraphDependencyReference(precompiledNode: precompiledNode)) - } - - func test_linkableDependencies_whenALibraryTarget() throws { - let target = Target.test(name: "Main") - let dependency = Target.test(name: "Dependency", product: .staticLibrary) - let project = Project.test(targets: [target]) - let dependencyNode = TargetNode(project: project, - target: dependency, - dependencies: []) - let targetNode = TargetNode(project: project, - target: target, - dependencies: [dependencyNode]) - let graph = Graph.test(projects: [project], targets: [ - project.path: [dependencyNode, targetNode], - ]) - - let got = try graph.linkableDependencies(path: project.path, name: target.name) - XCTAssertEqual(got.first, .product(target: "Dependency", productName: "libDependency.a")) - } - - func test_linkableDependencies_whenAFrameworkTarget() throws { - let target = Target.test(name: "Main") - let dependency = Target.test(name: "Dependency", product: .framework) - let staticDependency = Target.test(name: "StaticDependency", product: .staticLibrary) - let project = Project.test(targets: [target]) - - let staticDependencyNode = TargetNode(project: project, - target: staticDependency, - dependencies: []) - let dependencyNode = TargetNode(project: project, - target: dependency, - dependencies: [staticDependencyNode]) - let targetNode = TargetNode(project: project, - target: target, - dependencies: [dependencyNode]) - - let graph = Graph.test(projects: [project], targets: [project.path: [targetNode, dependencyNode, staticDependencyNode]]) - let got = try graph.linkableDependencies(path: project.path, - name: target.name) - XCTAssertEqual(got.count, 1) - XCTAssertEqual(got.first, .product(target: "Dependency", productName: "Dependency.framework")) - - let frameworkGot = try graph.linkableDependencies(path: project.path, name: dependency.name) - - XCTAssertEqual(frameworkGot.count, 1) - XCTAssertTrue(frameworkGot.contains(.product(target: "StaticDependency", productName: "libStaticDependency.a"))) - } - - 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) - - // Then - XCTAssertEqual(result, [GraphDependencyReference.product(target: "DynamicFramework", productName: "DynamicFramework.framework"), - GraphDependencyReference.product(target: "StaticFramework", productName: "StaticFramework.framework")]) - } - - 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) - let dynamicFramework1Result = try graph.linkableDependencies(path: projectA.path, name: dynamicFramework1.name) - - // Then - XCTAssertEqual(appResult, [ - GraphDependencyReference.product(target: "DynamicFramework1", productName: "DynamicFramework1.framework"), - ]) - XCTAssertEqual(dynamicFramework1Result, [ - GraphDependencyReference.product(target: "DynamicFramework2", productName: "DynamicFramework2.framework"), - GraphDependencyReference.product(target: "StaticFramework1", productName: "libStaticFramework1.a"), - GraphDependencyReference.product(target: "StaticFramework2", productName: "libStaticFramework2.a"), - ]) - } - - 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) - - // Then - XCTAssertEqual(dynamicFramework1Result, [GraphDependencyReference.product(target: "DynamicFramework2", productName: "DynamicFramework2.framework")]) - } - - func test_linkableDependencies_transitiveSDKDependenciesStatic() throws { - // Given - let staticFrameworkA = Target.test(name: "StaticFrameworkA", - product: .staticFramework, - dependencies: [.sdk(name: "some.framework", status: .optional)]) - let staticFrameworkB = Target.test(name: "StaticFrameworkB", - product: .staticFramework, - 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: [staticFrameworkB]), - (target: staticFrameworkB, dependencies: [staticFrameworkA]), - (target: staticFrameworkA, dependencies: []), - ]) - - // When - let result = try graph.linkableDependencies(path: projectA.path, name: app.name) - - // Then - XCTAssertEqual(result.compactMap(sdkDependency), [ - SDKPathAndStatus(name: "some.framework", status: .optional), - ]) - } - - func test_linkableDependencies_transitiveSDKDependenciesDynamic() throws { - // Given - let staticFramework = Target.test(name: "StaticFramework", - product: .staticFramework, - dependencies: [.sdk(name: "some.framework", status: .optional)]) - 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: [dynamicFramework]), - (target: dynamicFramework, dependencies: [staticFramework]), - (target: staticFramework, dependencies: []), - ]) - - // When - let appResult = try graph.linkableDependencies(path: projectA.path, name: app.name) - let dynamicResult = try graph.linkableDependencies(path: projectA.path, name: dynamicFramework.name) - - // Then - XCTAssertEqual(appResult.compactMap(sdkDependency), []) - XCTAssertEqual(dynamicResult.compactMap(sdkDependency), - [SDKPathAndStatus(name: "some.framework", status: .optional)]) - } - - func test_linkableDependencies_transitiveSDKDependenciesNotDuplicated() throws { - // Given - let staticFramework = Target.test(name: "StaticFramework", - product: .staticFramework, - dependencies: [.sdk(name: "some.framework", status: .optional)]) - let app = Target.test(name: "App", - product: .app, - dependencies: [.sdk(name: "some.framework", status: .optional)]) - - let projectA = Project.test(path: "/path/a") - - let graph = Graph.create(project: projectA, - dependencies: [ - (target: app, dependencies: [staticFramework]), - (target: staticFramework, dependencies: []), - ]) - - // When - let result = try graph.linkableDependencies(path: projectA.path, name: app.name) - - // Then - XCTAssertEqual(result.compactMap(sdkDependency), [SDKPathAndStatus(name: "some.framework", status: .optional)]) - } - - func test_linkableDependencies_transitiveSDKDependenciesImmediateDependencies() throws { - // Given - let staticFramework = Target.test(name: "StaticFrameworkA", - product: .staticFramework, - dependencies: [.sdk(name: "thingone.framework", status: .optional), - .sdk(name: "thingtwo.framework", status: .required)]) - - let projectA = Project.test(path: "/path/a") - - let graph = Graph.create(project: projectA, - dependencies: [ - (target: staticFramework, dependencies: []), - ]) - - // When - let result = try graph.linkableDependencies(path: projectA.path, name: staticFramework.name) - - // Then - XCTAssertEqual(result.compactMap(sdkDependency), - [SDKPathAndStatus(name: "thingone.framework", status: .optional), - SDKPathAndStatus(name: "thingtwo.framework", status: .required)]) - } - - func test_linkableDependencies_NoTransitiveSDKDependenciesForStaticFrameworks() throws { - // Given - let staticFrameworkA = Target.test(name: "StaticFrameworkA", - product: .staticFramework, - dependencies: [.sdk(name: "ThingOne.framework", status: .optional)]) - let staticFrameworkB = Target.test(name: "StaticFrameworkB", - product: .staticFramework, - dependencies: [.sdk(name: "ThingTwo.framework", status: .optional)]) - - let projectA = Project.test(path: "/path/a") - - let graph = Graph.create(project: projectA, - dependencies: [ - (target: staticFrameworkA, dependencies: [staticFrameworkB]), - (target: staticFrameworkB, dependencies: []), - ]) - - // When - let result = try graph.linkableDependencies(path: projectA.path, name: staticFrameworkA.name) - - // Then - XCTAssertEqual(result.compactMap(sdkDependency), - [SDKPathAndStatus(name: "ThingOne.framework", status: .optional)]) - } - - func test_linkableDependencies_when_watchExtension() throws { - // Given - let frameworkA = Target.test(name: "FrameworkA", product: .framework) - let frameworkB = Target.test(name: "FrameworkB", product: .framework) - let watchExtension = Target.test(name: "WatchExtension", product: .watch2Extension) - let project = Project.test(targets: [watchExtension, frameworkA, frameworkB]) - - let graph = Graph.create(project: project, - dependencies: [ - (target: watchExtension, dependencies: [frameworkA]), - (target: frameworkA, dependencies: [frameworkB]), - (target: frameworkB, dependencies: []), - ]) - - // When - let result = try graph.linkableDependencies(path: project.path, name: watchExtension.name) - - // Then - XCTAssertEqual(result, [ - .product(target: "FrameworkA", productName: "FrameworkA.framework"), - ]) - } - - func test_linkableDependencies_when_watchExtension_staticDependency() throws { - // Given - let frameworkA = Target.test(name: "FrameworkA", product: .staticFramework) - let frameworkB = Target.test(name: "FrameworkB", product: .framework) - let watchExtension = Target.test(name: "WatchExtension", product: .watch2Extension) - let project = Project.test(targets: [watchExtension, frameworkA, frameworkB]) - - let graph = Graph.create(project: project, - dependencies: [ - (target: watchExtension, dependencies: [frameworkA]), - (target: frameworkA, dependencies: [frameworkB]), - (target: frameworkB, dependencies: []), - ]) - - // When - let result = try graph.linkableDependencies(path: project.path, name: watchExtension.name) - - // Then - XCTAssertEqual(result, [ - .product(target: "FrameworkA", productName: "FrameworkA.framework"), - .product(target: "FrameworkB", productName: "FrameworkB.framework"), - ]) - } - - func test_linkableDependencies_whenHostedTestTarget_withCommonStaticProducts() throws { - // Given - let staticFramework = Target.test(name: "StaticFramework", - product: .staticFramework) - - let app = Target.test(name: "App", product: .app) - let tests = Target.test(name: "AppTests", product: .unitTests) - let projectA = Project.test(path: "/path/a") - - let graph = Graph.create(project: projectA, - dependencies: [ - (target: app, dependencies: [staticFramework]), - (target: staticFramework, dependencies: []), - (target: tests, dependencies: [app, staticFramework]), - ]) - - // When - let result = try graph.linkableDependencies(path: projectA.path, name: tests.name) - - // Then - XCTAssertTrue(result.isEmpty) - } - - func test_linkableDependencies_whenHostedTestTarget_withCommonDynamicProducts() throws { - // Given - let framework = Target.test(name: "Framework", - product: .framework) - - let app = Target.test(name: "App", product: .app) - let tests = Target.test(name: "AppTests", product: .unitTests) - let projectA = Project.test(path: "/path/a") - - let graph = Graph.create(project: projectA, - dependencies: [ - (target: app, dependencies: [framework]), - (target: framework, dependencies: []), - (target: tests, dependencies: [app, framework]), - ]) - - // When - let result = try graph.linkableDependencies(path: projectA.path, name: tests.name) - - // Then - XCTAssertEqual(result, [ - .product(target: "Framework", productName: "Framework.framework"), - ]) - } - - func test_linkableDependencies_whenHostedTestTarget_doNotIncludeRedundantDependencies() throws { - // Given - let framework = Target.test(name: "Framework", - product: .framework) - - let app = Target.test(name: "App", product: .app) - let tests = Target.test(name: "AppTests", product: .unitTests) - let projectA = Project.test(path: "/path/a") - - let graph = Graph.create(project: projectA, - dependencies: [ - (target: app, dependencies: [framework]), - (target: framework, dependencies: []), - (target: tests, dependencies: [app]), - ]) - - // When - let result = try graph.linkableDependencies(path: projectA.path, name: tests.name) - - // Then - XCTAssertTrue(result.isEmpty) - } - - func test_linkableDependencies_when_appClipSDKNode() throws { - // Given - let target = Target.test(name: "AppClip", product: .appClip) - let projectA = Project.test(path: "/path/a") - - let graph = Graph.create(project: projectA, - dependencies: [(target: target, dependencies: [])]) - - // When - let linkableModules = try graph.linkableDependencies(path: projectA.path, name: target.name) - - // Then - XCTAssertEqual(linkableModules, [.sdk(path: try SDKNode.appClip(status: .required).path, status: .required, source: .system)]) - } - - func test_librariesPublicHeaders() throws { - let target = Target.test(name: "Main") - let publicHeadersPath = AbsolutePath("/test/public/") - let precompiledNode = LibraryNode.test(path: AbsolutePath("/test/test.a"), - publicHeaders: publicHeadersPath) - let project = Project.test(targets: [target]) - let targetNode = TargetNode(project: project, - target: target, - dependencies: [precompiledNode]) - let graph = Graph.test(projects: [project], - precompiled: [precompiledNode], - targets: [project.path: [targetNode]]) - let got = graph.librariesPublicHeadersFolders(path: project.path, - name: target.name) - XCTAssertEqual(got.first, publicHeadersPath) - } - - func test_embeddableFrameworks_when_targetIsNotApp() throws { - // Given - let target = Target.test(name: "Main", product: .framework) - let dependency = Target.test(name: "Dependency", product: .framework) - let project = Project.test(targets: [target]) - let dependencyNode = TargetNode(project: project, - target: dependency, - dependencies: []) - let targetNode = TargetNode(project: project, - target: target, - dependencies: [dependencyNode]) - let graph = Graph.test(projects: [project], targets: [ - project.path: [targetNode, dependencyNode], - ]) - system.succeedCommand([], output: "dynamically linked") - - // When - let got = try graph.embeddableFrameworks(path: project.path, - name: target.name) - - // Then - XCTAssertNil(got.first) - } - - func test_embeddableFrameworks_when_dependencyIsATarget() throws { - // Given - let target = Target.test(name: "Main") - let dependency = Target.test(name: "Dependency", product: .framework) - let project = Project.test(targets: [target]) - let dependencyNode = TargetNode(project: project, - target: dependency, - dependencies: []) - let targetNode = TargetNode(project: project, - target: target, - dependencies: [dependencyNode]) - let graph = Graph.test(projects: [project], - targets: [project.path: [targetNode, dependencyNode]]) - - // When - let got = try graph.embeddableFrameworks(path: project.path, - name: target.name) - - // Then - XCTAssertEqual(got.first, GraphDependencyReference.product(target: "Dependency", productName: "Dependency.framework")) - } - - func test_embeddableFrameworks_when_dependencyIsAFramework() throws { - // Given - let frameworkPath = AbsolutePath("/test/test.framework") - let target = Target.test(name: "Main", platform: .iOS) - let frameworkNode = FrameworkNode.test(path: frameworkPath) - let project = Project.test(targets: [target]) - let targetNode = TargetNode(project: project, - target: target, - dependencies: [frameworkNode]) - let graph = Graph.test(projects: [project], - precompiled: [frameworkNode], - targets: [project.path: [targetNode]]) - - // When - let got = try graph.embeddableFrameworks(path: project.path, name: target.name) - - // Then - XCTAssertEqual(got.first, GraphDependencyReference(precompiledNode: frameworkNode)) - } - - func test_embeddableFrameworks_when_transitiveXCFrameworks() throws { - // Given - let app = Target.test(name: "App", platform: .iOS, product: .app) - let project = Project.test(targets: [app]) - - let dNode = XCFrameworkNode.test(path: "/xcframeworks/d.xcframework") - let cNode = XCFrameworkNode.test(path: "/xcframeworks/c.xcframework", dependencies: [.xcframework(dNode)]) - let appNode = TargetNode.test(target: app, dependencies: [cNode]) - - let cache = GraphLoaderCache() - cache.add(targetNode: appNode) - cache.add(precompiledNode: dNode) - cache.add(precompiledNode: cNode) - let graph = Graph.test(entryNodes: [appNode], - projects: [project], - precompiled: [cNode, dNode], - targets: [project.path: [appNode]]) - - // When - let got = try graph.embeddableFrameworks(path: project.path, name: app.name) - - // Then - XCTAssertEqual(got, [ - GraphDependencyReference(precompiledNode: cNode), - GraphDependencyReference(precompiledNode: dNode), - ]) - } - - func test_embeddableFrameworks_when_dependencyIsATransitiveFramework() throws { - let target = Target.test(name: "Main") - let dependency = Target.test(name: "Dependency", product: .framework) - let project = Project.test(targets: [target]) - - let frameworkPath = AbsolutePath("/test/test.framework") - let frameworkNode = FrameworkNode.test(path: frameworkPath) - - let dependencyNode = TargetNode( - project: project, - target: dependency, - dependencies: [frameworkNode] - ) - let targetNode = TargetNode( - project: project, - target: target, - dependencies: [dependencyNode] - ) - let graph = Graph.test(projects: [project], - precompiled: [frameworkNode], - targets: [project.path: [targetNode, dependencyNode]]) - - let got = try graph.embeddableFrameworks(path: project.path, name: target.name) - - XCTAssertEqual(got, [ - GraphDependencyReference.product(target: "Dependency", productName: "Dependency.framework"), - GraphDependencyReference(precompiledNode: frameworkNode), - ]) - } - - func test_embeddableFrameworks_when_precompiledStaticFramework() throws { - // Given - let target = Target.test(name: "Main") - let project = Project.test(targets: [target]) - - let frameworkNode = FrameworkNode.test(path: "/test/StaticFramework.framework", linking: .static) - let targetNode = TargetNode( - project: project, - target: target, - dependencies: [frameworkNode] - ) - - let graph = Graph.test(projects: [project], - precompiled: [frameworkNode], - targets: [project.path: [targetNode]]) - - // When - let result = try graph.embeddableFrameworks(path: project.path, name: target.name) - - // Then - XCTAssertTrue(result.isEmpty) - } - - func test_embeddableFrameworks_when_watchExtension() throws { - // Given - let frameworkA = Target.test(name: "FrameworkA", product: .framework) - let frameworkB = Target.test(name: "FrameworkB", product: .framework) - let watchExtension = Target.test(name: "WatchExtension", product: .watch2Extension) - let project = Project.test(targets: [watchExtension, frameworkA, frameworkB]) - - let graph = Graph.create(project: project, - dependencies: [ - (target: watchExtension, dependencies: [frameworkA]), - (target: frameworkA, dependencies: [frameworkB]), - (target: frameworkB, dependencies: []), - ]) - - // When - let result = try graph.embeddableFrameworks(path: project.path, name: watchExtension.name) - - // Then - XCTAssertEqual(result, [ - .product(target: "FrameworkA", productName: "FrameworkA.framework"), - .product(target: "FrameworkB", productName: "FrameworkB.framework"), - ]) - } - - func test_embeddableFrameworks_ordered() throws { - // Given - let dependencyNames = (0 ..< 10).shuffled().map { "Dependency\($0)" } - let target = Target.test(name: "Main", product: .app) - let project = Project.test(targets: [target]) - let dependencyNodes = dependencyNames.map { - TargetNode(project: project, - target: Target.test(name: $0, product: .framework), - dependencies: []) - } - let targetNode = TargetNode(project: project, - target: target, - dependencies: dependencyNodes) - let targetNodes = dependencyNodes.reduce(into: [project.path: [targetNode]]) { $0[project.path]?.append($1) } - let graph = Graph.test(projects: [project], targets: targetNodes) - - // When - let got = try graph.embeddableFrameworks(path: project.path, name: target.name) - - // Then - let expected = dependencyNames.sorted().map { GraphDependencyReference.product(target: $0, productName: "\($0).framework") } - XCTAssertEqual(got, expected) - } - - func test_embeddableDependencies_whenHostedTestTarget() throws { - // Given - let framework = Target.test(name: "Framework", - product: .framework) - - let app = Target.test(name: "App", product: .app) - let tests = Target.test(name: "AppTests", product: .unitTests) - let projectA = Project.test(path: "/path/a") - - let graph = Graph.create(project: projectA, - dependencies: [ - (target: app, dependencies: [framework]), - (target: framework, dependencies: []), - (target: tests, dependencies: [app]), - ]) - - // When - let result = try graph.embeddableFrameworks(path: projectA.path, name: tests.name) - - // Then - XCTAssertTrue(result.isEmpty) - } - - func test_embeddableDependencies_when_nonHostedTestTarget_dynamic_dependencies() throws { - // Given - let precompiledNode = mockDynamicFrameworkNode(at: AbsolutePath("/test/test.framework")) - let unitTests = Target.test(name: "AppUnitTests", product: .unitTests) - let project = Project.test(path: "/path/a") - let target = Target.test(name: "LocallyBuiltFramework", product: .framework) - let targetNode = TargetNode(project: project, - target: target, - dependencies: []) - - let unitTestsNode = TargetNode(project: project, target: unitTests, dependencies: [precompiledNode, targetNode]) - - let cache = GraphLoaderCache() - cache.add(project: project) - cache.add(precompiledNode: precompiledNode) - cache.add(targetNode: unitTestsNode) - - let graph = Graph( - name: "Graph", - entryPath: project.path, - cache: cache, - entryNodes: [unitTestsNode], - workspace: Workspace.test() - ) - - // When - let result = try graph.embeddableFrameworks(path: project.path, name: unitTests.name) - - // Then - XCTAssertTrue(result.isEmpty) - } - - func test_embeddableDependencies_whenHostedTestTarget_transitiveDepndencies() throws { - // Given - let framework = Target.test(name: "Framework", - product: .framework) - - let staticFramework = Target.test(name: "StaticFramework", - product: .framework) - - let app = Target.test(name: "App", product: .app) - let tests = Target.test(name: "AppTests", product: .unitTests) - let projectA = Project.test(path: "/path/a") - - let graph = Graph.create(project: projectA, - dependencies: [ - (target: app, dependencies: [staticFramework]), - (target: framework, dependencies: []), - (target: staticFramework, dependencies: [framework]), - (target: tests, dependencies: [app, staticFramework]), - ]) - - // When - let result = try graph.embeddableFrameworks(path: projectA.path, name: tests.name) - - // Then - XCTAssertTrue(result.isEmpty) - } - - func test_embeddableDependencies_whenUITest_andAppPrecompiledDepndencies() throws { - // Given - let precompiledNode = mockDynamicFrameworkNode(at: AbsolutePath("/test/test.framework")) - let app = Target.test(name: "App", product: .app) - let uiTests = Target.test(name: "AppUITests", product: .uiTests) - let project = Project.test(path: "/path/a") - - let appNode = TargetNode(project: project, target: app, dependencies: [precompiledNode]) - let uiTestsNode = TargetNode(project: project, target: uiTests, dependencies: [appNode]) - - let cache = GraphLoaderCache() - cache.add(project: project) - cache.add(precompiledNode: precompiledNode) - cache.add(targetNode: appNode) - cache.add(targetNode: uiTestsNode) - - let graph = Graph( - name: "Graph", - entryPath: project.path, - cache: cache, - entryNodes: [appNode, uiTestsNode], - workspace: Workspace.test() - ) - - // When - let result = try graph.embeddableFrameworks(path: project.path, name: uiTests.name) - - // Then - XCTAssertTrue(result.isEmpty) - } - - func test_runPathSearchPaths() throws { - // Given - let precompiledNode = mockDynamicFrameworkNode(at: AbsolutePath("/test/test.framework")) - let precompiledNodeB = mockDynamicFrameworkNode(at: AbsolutePath("/test/test.framework")) - let unitTests = Target.test(name: "AppUnitTests", product: .unitTests) - let project = Project.test(path: "/path/a") - - let unitTestsNode = TargetNode(project: project, target: unitTests, dependencies: [precompiledNode, precompiledNodeB]) - - let cache = GraphLoaderCache() - cache.add(project: project) - cache.add(precompiledNode: precompiledNode) - cache.add(precompiledNode: precompiledNodeB) - cache.add(targetNode: unitTestsNode) - - let graph = Graph( - name: "Graph", - entryPath: project.path, - cache: cache, - entryNodes: [unitTestsNode], - workspace: Workspace.test(path: project.path) - ) - - // When - let got = graph.runPathSearchPaths(path: project.path, name: unitTests.name) - - // Then - XCTAssertEqual( - got, - [AbsolutePath("/path/to")] - ) - } - - func test_runPathSearchPaths_when_unit_tests_with_hosted_target() throws { - // Given - let precompiledNode = mockDynamicFrameworkNode(at: AbsolutePath("/test/test.framework")) - let app = Target.test(name: "App", product: .app) - let unitTests = Target.test(name: "AppUnitTests", product: .unitTests) - let project = Project.test(path: "/path/a") - - let appNode = TargetNode(project: project, target: app, dependencies: [precompiledNode]) - let unitTestsNode = TargetNode(project: project, target: unitTests, dependencies: [appNode, precompiledNode]) - - let cache = GraphLoaderCache() - cache.add(project: project) - cache.add(targetNode: appNode) - cache.add(precompiledNode: precompiledNode) - cache.add(targetNode: unitTestsNode) - - let graph = Graph( - name: "Graph", - entryPath: project.path, - cache: cache, - entryNodes: [unitTestsNode], - workspace: Workspace.test() - ) - - // When - let got = graph.runPathSearchPaths(path: project.path, name: unitTests.name) - - // Then - XCTAssertEmpty(got) - } - - func test_librariesSearchPaths() throws { - // Given - let target = Target.test(name: "Main") - let precompiledNode = LibraryNode.test(path: "/test/test.a", publicHeaders: "/test/public/") - let project = Project.test(targets: [target]) - let targetNode = TargetNode(project: project, - target: target, - dependencies: [precompiledNode]) - let graph = Graph.test(projects: [project], - precompiled: [precompiledNode], - targets: [project.path: [targetNode]]) - - // When - let got = graph.librariesSearchPaths(path: project.path, - name: target.name) - - // Then - XCTAssertEqual(got, [AbsolutePath("/test")]) - } - - func test_librariesSwiftIncludePaths() throws { - // Given - let target = Target.test(name: "Main") - let precompiledNodeA = LibraryNode.test(path: "/test/test.a", swiftModuleMap: "/test/modules/test.swiftmodulemap") - let precompiledNodeB = LibraryNode.test(path: "/test/another.a", swiftModuleMap: nil) - let project = Project.test(targets: [target]) - let targetNode = TargetNode(project: project, - target: target, - dependencies: [precompiledNodeA, precompiledNodeB]) - let graph = Graph.test(projects: [project], - precompiled: [precompiledNodeA, precompiledNodeB], - targets: [project.path: [targetNode]]) - - // When - let got = graph.librariesSwiftIncludePaths(path: project.path, - name: target.name) - - // Then - XCTAssertEqual(got, [AbsolutePath("/test/modules")]) - } - - func test_hostTargetNode_watchApp() { - // Given - let app = Target.test(name: "App", platform: .iOS, product: .app) - let watchApp = Target.test(name: "WatchApp", platform: .watchOS, product: .watch2App) - let project = Project.test(path: "/path/a") - - let graph = Graph.create(project: project, - dependencies: [ - (target: app, dependencies: [watchApp]), - (target: watchApp, dependencies: []), - ]) - - // When - let result = graph.hostTargetNodeFor(path: project.path, name: "WatchApp") - - // Then - XCTAssertEqual(result?.target, app) - } - - func test_hostTargetNode_watchAppExtension() { - // Given - let watchApp = Target.test(name: "WatchApp", platform: .watchOS, product: .watch2App) - let watchAppExtension = Target.test(name: "WatchAppExtension", platform: .watchOS, product: .watch2Extension) - let project = Project.test(path: "/path/a") - - let graph = Graph.create(project: project, - dependencies: [ - (target: watchApp, dependencies: [watchAppExtension]), - (target: watchAppExtension, dependencies: []), - ]) - - // When - let result = graph.hostTargetNodeFor(path: project.path, name: "WatchAppExtension") - - // Then - XCTAssertEqual(result?.target, watchApp) - } - func test_encode() { // Given System.shared = System() @@ -1025,222 +61,4 @@ final class GraphTests: TuistUnitTestCase { // Then XCTAssertEncodableEqualToJson(graph, expected) } - - func test_appExtensionDependencies_when_dependencyIsAppExtension() throws { - let target = Target.test(name: "Main") - let dependency = Target.test(name: "AppExtension", product: .appExtension) - let project = Project.test(targets: [target]) - let dependencyNode = TargetNode(project: project, - target: dependency, - dependencies: []) - let targetNode = TargetNode(project: project, - target: target, - dependencies: [dependencyNode]) - let graph = Graph.test(projects: [project], targets: [ - project.path: [targetNode, dependencyNode], - ]) - - let got = graph.appExtensionDependencies(path: project.path, name: target.name) - - XCTAssertEqual(got.first?.name, "AppExtension") - } - - func test_appExtensionDependencies_when_dependencyIsStickerPackExtension() throws { - let target = Target.test(name: "Main") - let dependency = Target.test(name: "StickerPackExtension", product: .stickerPackExtension) - let project = Project.test(targets: [target]) - let dependencyNode = TargetNode(project: project, - target: dependency, - dependencies: []) - let targetNode = TargetNode(project: project, - target: target, - dependencies: [dependencyNode]) - let graph = Graph.test(projects: [project], targets: [ - project.path: [targetNode, dependencyNode], - ]) - - let got = graph.appExtensionDependencies(path: project.path, name: target.name) - - XCTAssertEqual(got.first?.name, "StickerPackExtension") - } - - func test_appExtensionDependencies_when_dependencyIsMessageExtension() throws { - // Given - let app = Target.test(name: "App", product: .app) - let messageExtension = Target.test(name: "MessageExtension", product: .messagesExtension) - let project = Project.test(targets: [app, messageExtension]) - - let graph = Graph.create(project: project, - dependencies: [ - (target: app, dependencies: [messageExtension]), - (target: messageExtension, dependencies: []), - ]) - - // When - let result = graph.appExtensionDependencies(path: project.path, name: app.name) - - // Then - XCTAssertEqual(result.map(\.name), [ - "MessageExtension", - ]) - } - - 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", - ]) - } - - func test_resourceBundleDependencies_transitivelyViaSingleStaticFramework() { - // Given - let bundle = Target.test(name: "ResourceBundle", product: .bundle) - let staticFramework = Target.test(name: "StaticFramework", product: .staticFramework) - let projectA = Project.test(path: "/path/a", targets: [staticFramework, bundle]) - - let app = Target.test(name: "App", product: .app) - let projectB = Project.test(path: "/path/b", targets: [app]) - - let graph = Graph.create(projects: [projectA, projectB], - dependencies: [ - (project: projectB, target: app, dependencies: [staticFramework]), - (project: projectA, target: staticFramework, dependencies: [bundle]), - (project: projectA, target: bundle, dependencies: []), - ]) - - // When - let result = graph.resourceBundleDependencies(path: projectB.path, name: app.name) - - // Then - XCTAssertEqual(result.map(\.target.name), [ - "ResourceBundle", - ]) - } - - func test_resourceBundleDependencies_transitivelyViaMultipleStaticFrameworks() { - // Given - let bundle1 = Target.test(name: "ResourceBundle1", product: .bundle) - let bundle2 = Target.test(name: "ResourceBundle2", product: .bundle) - let staticFramework1 = Target.test(name: "StaticFramework1", product: .staticFramework) - let staticFramework2 = Target.test(name: "StaticFramework2", product: .staticFramework) - let projectA = Project.test(path: "/path/a", targets: [staticFramework1, staticFramework2, bundle1, bundle2]) - - let app = Target.test(name: "App", product: .app) - let projectB = Project.test(path: "/path/b", targets: [app]) - - let graph = Graph.create(projects: [projectA, projectB], - dependencies: [ - (project: projectB, target: app, dependencies: [staticFramework1]), - (project: projectA, target: staticFramework1, dependencies: [staticFramework2, bundle1]), - (project: projectA, target: staticFramework2, dependencies: [bundle2]), - (project: projectA, target: bundle1, dependencies: []), - (project: projectA, target: bundle2, dependencies: []), - ]) - - // When - let result = graph.resourceBundleDependencies(path: projectB.path, name: app.name) - - // Then - XCTAssertEqual(result.map(\.target.name), [ - "ResourceBundle1", - "ResourceBundle2", - ]) - } - - func test_resourceBundleDependencies_transitivelyToDynamicFramework() { - // Given - let bundle = Target.test(name: "ResourceBundle", product: .bundle) - let staticFramework1 = Target.test(name: "StaticFramework1", product: .staticFramework) - let staticFramework2 = Target.test(name: "StaticFramework2", product: .staticFramework) - let dynamicFramework = Target.test(name: "DynamicFramework", product: .framework) - let projectA = Project.test(path: "/path/a", targets: [dynamicFramework, staticFramework1, staticFramework2, bundle]) - - let app = Target.test(name: "App", product: .app) - let projectB = Project.test(path: "/path/b", targets: [app]) - - let graph = Graph.create(projects: [projectA, projectB], - dependencies: [ - (project: projectB, target: app, dependencies: [dynamicFramework]), - (project: projectA, target: dynamicFramework, dependencies: [staticFramework2]), - (project: projectA, target: staticFramework1, dependencies: [staticFramework2]), - (project: projectA, target: staticFramework2, dependencies: [bundle]), - (project: projectA, target: bundle, dependencies: []), - ]) - - // When - let appResults = graph.resourceBundleDependencies(path: projectB.path, name: app.name) - let dynamicFrameworkResults = graph.resourceBundleDependencies(path: projectA.path, name: dynamicFramework.name) - let staticFramework1Results = graph.resourceBundleDependencies(path: projectA.path, name: staticFramework1.name) - let staticFramework2Results = graph.resourceBundleDependencies(path: projectA.path, name: staticFramework2.name) - - // Then - XCTAssertEqual(appResults.map(\.target.name), []) - XCTAssertEqual(dynamicFrameworkResults.map(\.target.name), [ - "ResourceBundle", - ]) - XCTAssertEqual(staticFramework1Results.map(\.target.name), []) - XCTAssertEqual(staticFramework2Results.map(\.target.name), []) - } - - // MARK: - Helpers - - private func mockDynamicFrameworkNode(at path: AbsolutePath) -> FrameworkNode { - let precompiledNode = FrameworkNode.test() - let binaryPath = path.appending(component: path.basenameWithoutExt) - system.succeedCommand("/usr/bin/file", - binaryPath.pathString, - output: "dynamically linked") - return precompiledNode - } - - private func sdkDependency(from dependency: GraphDependencyReference) -> SDKPathAndStatus? { - switch dependency { - case let .sdk(path, status, _): - return SDKPathAndStatus(name: path.basename, status: status) - default: - return nil - } - } -} - -private struct SDKPathAndStatus: Equatable { - var name: String - var status: SDKStatus } diff --git a/Tests/TuistCoreTests/ValueGraph/ValueGraphDependencyTests.swift b/Tests/TuistCoreTests/ValueGraph/ValueGraphDependencyTests.swift new file mode 100644 index 000000000..89da6e299 --- /dev/null +++ b/Tests/TuistCoreTests/ValueGraph/ValueGraphDependencyTests.swift @@ -0,0 +1,28 @@ +import Foundation +import TSCBasic +import XCTest + +@testable import TuistCore +@testable import TuistSupportTesting + +final class ValueGraphDependencyTests: TuistUnitTestCase { + func test_isTarget() { + XCTAssertFalse(ValueGraphDependency.testXCFramework().isTarget) + XCTAssertFalse(ValueGraphDependency.testFramework().isTarget) + XCTAssertFalse(ValueGraphDependency.testLibrary().isTarget) + XCTAssertFalse(ValueGraphDependency.testPackageProduct().isTarget) + XCTAssertTrue(ValueGraphDependency.testTarget().isTarget) + XCTAssertFalse(ValueGraphDependency.testSDK().isTarget) + XCTAssertFalse(ValueGraphDependency.testCocoapods().isTarget) + } + + func test_isPrecompiled() { + XCTAssertTrue(ValueGraphDependency.testXCFramework().isPrecompiled) + XCTAssertTrue(ValueGraphDependency.testFramework().isPrecompiled) + XCTAssertTrue(ValueGraphDependency.testLibrary().isPrecompiled) + XCTAssertFalse(ValueGraphDependency.testPackageProduct().isPrecompiled) + XCTAssertFalse(ValueGraphDependency.testTarget().isPrecompiled) + XCTAssertFalse(ValueGraphDependency.testSDK().isPrecompiled) + XCTAssertFalse(ValueGraphDependency.testCocoapods().isPrecompiled) + } +} diff --git a/Tests/TuistCoreTests/ValueGraph/ValueGraphTargetTests.swift b/Tests/TuistCoreTests/ValueGraph/ValueGraphTargetTests.swift new file mode 100644 index 000000000..fc2f2683c --- /dev/null +++ b/Tests/TuistCoreTests/ValueGraph/ValueGraphTargetTests.swift @@ -0,0 +1,16 @@ +import Foundation +import TSCBasic +import XCTest + +@testable import TuistCore +@testable import TuistCoreTesting +@testable import TuistSupportTesting + +final class ValueGraphTargetTests: TuistUnitTestCase { + func test_comparable() { + XCTAssertTrue(ValueGraphTarget.test(target: Target.test(name: "a")) < ValueGraphTarget.test(target: Target.test(name: "b"))) + XCTAssertFalse(ValueGraphTarget.test(target: Target.test(name: "b")) < ValueGraphTarget.test(target: Target.test(name: "a"))) + XCTAssertTrue(ValueGraphTarget.test(path: "/a", target: Target.test(name: "a")) < ValueGraphTarget.test(path: "/b", target: Target.test(name: "a"))) + XCTAssertFalse(ValueGraphTarget.test(path: "/b", target: Target.test(name: "a")) < ValueGraphTarget.test(path: "/a", target: Target.test(name: "a"))) + } +} diff --git a/Tests/TuistCoreTests/ValueGraph/ValueGraphTests.swift b/Tests/TuistCoreTests/ValueGraph/ValueGraphTests.swift index 21665c379..a2bad17cd 100644 --- a/Tests/TuistCoreTests/ValueGraph/ValueGraphTests.swift +++ b/Tests/TuistCoreTests/ValueGraph/ValueGraphTests.swift @@ -106,32 +106,40 @@ final class ValueGraphTests: TuistUnitTestCase { XCTAssertEqual(valueGraph.dependencies[.target(name: aTarget.name, path: aNode.path)]? .contains(.sdk(name: xctestNode.name, path: xctestNode.path, status: xctestNode.status, source: xctestNode.source)), true) // Then: A -> BFramework - XCTAssertEqual(valueGraph.dependencies[.target(name: aTarget.name, path: aNode.path)]? - .contains(.framework(path: bFrameworkNode.path, + XCTAssertEqual(valueGraph.dependencies[.target(name: aTarget.name, path: aNode.path), default: []] + .contains(.framework(path: bFrameworkPath, + binaryPath: bFrameworkNode.binaryPath, dsymPath: bFrameworkNode.dsymPath, bcsymbolmapPaths: bFrameworkNode.bcsymbolmapPaths, linking: bFrameworkNode.linking, - architectures: bFrameworkNode.architectures)), true) + architectures: bFrameworkNode.architectures, + isCarthage: bFrameworkNode.isCarthage)), true) // Then: A -> Package XCTAssertEqual(valueGraph.dependencies[.target(name: aTarget.name, path: aNode.path)]? .contains(.packageProduct(path: packageProduct.path, product: packageProduct.product)), true) // Then: BFramework -> AFramework XCTAssertEqual(valueGraph.dependencies[.framework(path: bFrameworkNode.path, + binaryPath: bFrameworkNode.binaryPath, dsymPath: bFrameworkNode.dsymPath, bcsymbolmapPaths: bFrameworkNode.bcsymbolmapPaths, linking: bFrameworkNode.linking, - architectures: bFrameworkNode.architectures)]? + architectures: bFrameworkNode.architectures, + isCarthage: bFrameworkNode.isCarthage), default: []] .contains(.framework(path: aFrameworkNode.path, + binaryPath: aFrameworkNode.binaryPath, dsymPath: aFrameworkNode.dsymPath, bcsymbolmapPaths: aFrameworkNode.bcsymbolmapPaths, linking: aFrameworkNode.linking, - architectures: aFrameworkNode.architectures)), true) + architectures: aFrameworkNode.architectures, + isCarthage: aFrameworkNode.isCarthage)), true) // then: AFramework XCTAssertNotNil(valueGraph.dependencies[.framework(path: aFrameworkNode.path, + binaryPath: aFrameworkNode.binaryPath, dsymPath: aFrameworkNode.dsymPath, bcsymbolmapPaths: aFrameworkNode.bcsymbolmapPaths, linking: aFrameworkNode.linking, - architectures: aFrameworkNode.architectures)]) + architectures: aFrameworkNode.architectures, + isCarthage: aFrameworkNode.isCarthage)]) // Then: XCTest XCTAssertNotNil(valueGraph.dependencies[.sdk(name: xctestNode.name, path: xctestNode.path, status: xctestNode.status, source: xctestNode.source)]) diff --git a/Tests/TuistCoreTests/ValueGraph/ValueGraphTraverserTests.swift b/Tests/TuistCoreTests/ValueGraph/ValueGraphTraverserTests.swift index 54ac341d3..87f3ac9f7 100644 --- a/Tests/TuistCoreTests/ValueGraph/ValueGraphTraverserTests.swift +++ b/Tests/TuistCoreTests/ValueGraph/ValueGraphTraverserTests.swift @@ -1,48 +1,81 @@ import Foundation import TSCBasic -import TuistCore import TuistSupport import XCTest +@testable import TuistCore @testable import TuistCoreTesting @testable import TuistSupportTesting final class ValueGraphTraverserTests: TuistUnitTestCase { func test_target() { // Given + let path = AbsolutePath.root let app = Target.test(name: "App", product: .app) let framework = Target.test(name: "Framework", product: .framework) - let graph = ValueGraph.test(path: "/", targets: [ - "/": ["App": app, "Framework": framework], - ]) - let subject = ValueGraphTraverser(graph: graph) + let project = Project.test(path: path) + + // Given: Value Graph + let valueGraph = ValueGraph.test(path: path, + projects: [path: project], + targets: [ + "/": ["App": app, "Framework": framework], + ]) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Value Graph + let frameworkNode = TargetNode.test(project: project, target: framework) + let appNode = TargetNode.test(project: project, target: app) + let graph = Graph.test(entryPath: project.path, + entryNodes: [appNode, frameworkNode], + targets: [project.path: [appNode, frameworkNode]]) + let graphTraverser = GraphTraverser(graph: graph) // When let got = subject.target(path: "/", name: "App") + let gotGraph = graphTraverser.target(path: "/", name: "App") // Then - XCTAssertEqual(got, app) + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.map(\.target), app) } func test_targets() { // Given + let path = AbsolutePath.root let app = Target.test(name: "App", product: .app) + let project = Project.test(path: path) let framework = Target.test(name: "Framework", product: .framework) - let graph = ValueGraph.test(path: "/", targets: [ - "/": ["App": app, "Framework": framework], - ]) - let subject = ValueGraphTraverser(graph: graph) + + // When: Value Graph + let valueGraph = ValueGraph.test(path: path, + projects: [path: project], + targets: [ + path: ["App": app, "Framework": framework], + ]) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When: Graph + let frameworkNode = TargetNode.test(project: project, target: framework) + let appNode = TargetNode.test(project: project, target: app) + let graph = Graph.test(entryPath: project.path, + entryNodes: [appNode, frameworkNode], + targets: [project.path: [appNode, frameworkNode]]) + let graphTraverser = GraphTraverser(graph: graph) // When - let got = subject.targets(at: "/") + let gotGraph = graphTraverser.targets(at: path).sorted() + let got = subject.targets(at: path).sorted() // Then - XCTAssertEqual(got, [app, framework]) + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.map(\.target), [app, framework]) } func test_testTargetsDependingOn() { // Given let path = AbsolutePath.root + let project = Project.test(path: path) let framework = Target.test(name: "Framework", product: .framework) let dependantFramework = Target.test(name: "DependantFramework", product: .framework) let unitTests = Target.test(name: "UnitTests", product: .unitTests) @@ -58,21 +91,38 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: uiTests.name, path: path): Set([.target(name: framework.name, path: path)]), .target(name: dependantFramework.name, path: path): Set([.target(name: framework.name, path: path)]), ] - let graph = ValueGraph.test(path: path, - targets: targets, - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + + // Given: Value Graph + let valueGraph = ValueGraph.test(path: path, + projects: [path: project], + targets: targets, + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let frameworkNode = TargetNode.test(project: project, target: framework) + let dependantFrameworkNode = TargetNode.test(project: project, target: dependantFramework, dependencies: [frameworkNode]) + let unitTestsNode = TargetNode.test(project: project, target: unitTests, dependencies: [frameworkNode]) + let uiTestsNode = TargetNode.test(project: project, target: uiTests, dependencies: [frameworkNode]) + + let graph = Graph.test(entryPath: project.path, + entryNodes: [frameworkNode, dependantFrameworkNode, unitTestsNode, uiTestsNode], + targets: [project.path: [frameworkNode, dependantFrameworkNode, unitTestsNode, uiTestsNode]]) + let graphTraverser = GraphTraverser(graph: graph) // When - let got = subject.testTargetsDependingOn(path: path, name: framework.name) + let gotGraph = graphTraverser.testTargetsDependingOn(path: path, name: framework.name).sorted() + let got = subject.testTargetsDependingOn(path: path, name: framework.name).sorted() // Then - XCTAssertEqual(got, [uiTests, unitTests]) + XCTAssertEqual(gotGraph, got) + XCTAssertEqual(got.map(\.target), [uiTests, unitTests]) } func test_directStaticDependencies() { // Given let path = AbsolutePath.root + let project = Project.test(path: path) let framework = Target.test(name: "Framework", product: .framework) let staticLibrary = Target.test(name: "StaticLibrary", product: .staticLibrary) let targets: [AbsolutePath: [String: Target]] = [ @@ -82,15 +132,28 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { let dependencies: [ValueGraphDependency: Set] = [ .target(name: framework.name, path: path): Set([.target(name: staticLibrary.name, path: path)]), ] - let graph = ValueGraph.test(path: path, - targets: targets, - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + + // Given: Value Graph + let valueGraph = ValueGraph.test(path: path, + targets: targets, + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let staticLibraryNode = TargetNode.test(project: project, target: staticLibrary) + let frameworkNode = TargetNode.test(project: project, target: framework, dependencies: [staticLibraryNode]) + + let graph = Graph.test(entryPath: project.path, + entryNodes: [frameworkNode, staticLibraryNode], + targets: [project.path: [frameworkNode, staticLibraryNode]]) + let graphTraverser = GraphTraverser(graph: graph) // When - let got = subject.directStaticDependencies(path: path, name: framework.name) + let got = subject.directStaticDependencies(path: path, name: framework.name).sorted() + let gotGraph = graphTraverser.directStaticDependencies(path: path, name: framework.name).sorted() // Then + XCTAssertEqual(gotGraph, got) XCTAssertEqual(got, [.product(target: staticLibrary.name, productName: staticLibrary.productNameWithExtension)]) } @@ -110,17 +173,30 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { b.name: b, c.name: c, ]] - let graph = ValueGraph.test(path: project.path, - projects: [project.path: project], - targets: targets, - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + // Given: Value Graph + let valueGraph = ValueGraph.test(path: project.path, + projects: [project.path: project], + targets: targets, + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let cNode = TargetNode.test(project: project, target: b) + let bNode = TargetNode.test(project: project, target: b, dependencies: [cNode]) + let aNode = TargetNode.test(project: project, target: a, dependencies: [bNode]) + + let graph = Graph.test(entryPath: project.path, + entryNodes: [aNode, bNode, cNode], + targets: [project.path: [aNode, bNode, cNode]]) + let graphTraverser = GraphTraverser(graph: graph) // When - let got = subject.directTargetDependencies(path: project.path, name: a.name) + let got = subject.directTargetDependencies(path: project.path, name: a.name).sorted() + let gotGraph = graphTraverser.directTargetDependencies(path: project.path, name: a.name).sorted() // Then - XCTAssertEqual(got, [b]) + XCTAssertEqual(gotGraph, got) + XCTAssertEqual(got.map(\.target), [b]) } func test_resourceBundleDependencies_returns_an_empty_list_when_a_dependency_can_host_resources() { @@ -137,18 +213,31 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: bundle.name, path: project.path): Set([]), ] - let graph = ValueGraph.test(path: project.path, - projects: [project.path: project], - targets: [project.path: [app.name: app, - watchApp.name: watchApp, - bundle.name: bundle]], - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + // Given: Value Graph + let valueGraph = ValueGraph.test(path: project.path, + projects: [project.path: project], + targets: [project.path: [app.name: app, + watchApp.name: watchApp, + bundle.name: bundle]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let bundleNode = TargetNode.test(project: project, target: bundle) + let watchAppNode = TargetNode.test(project: project, target: watchApp, dependencies: [bundleNode]) + let appNode = TargetNode.test(project: project, target: app, dependencies: [watchAppNode]) + + let graph = Graph.test(entryPath: project.path, + entryNodes: [appNode, bundleNode, watchAppNode], + targets: [project.path: [appNode, bundleNode, watchAppNode]]) + let graphTraverser = GraphTraverser(graph: graph) // When - let got = subject.resourceBundleDependencies(path: project.path, name: app.name) + let got = subject.resourceBundleDependencies(path: project.path, name: app.name).sorted() + let gotGraph = graphTraverser.resourceBundleDependencies(path: project.path, name: app.name).sorted() // Then + XCTAssertEqual(gotGraph, got) XCTAssertEqual(got, []) } @@ -166,19 +255,31 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: bundle.name, path: project.path): Set([]), ] - let graph = ValueGraph.test(path: project.path, - projects: [project.path: project], - targets: [project.path: [app.name: app, - staticLibrary.name: staticLibrary, - bundle.name: bundle]], - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + // Given: Value Graph + let valueGraph = ValueGraph.test(path: project.path, + projects: [project.path: project], + targets: [project.path: [app.name: app, + staticLibrary.name: staticLibrary, + bundle.name: bundle]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let bundleNode = TargetNode.test(project: project, target: bundle) + let staticLibraryNode = TargetNode.test(project: project, target: staticLibrary, dependencies: [bundleNode]) + let appNode = TargetNode.test(project: project, target: app, dependencies: [staticLibraryNode]) + let graph = Graph.test(entryPath: project.path, + entryNodes: [appNode, bundleNode, staticLibraryNode], + targets: [project.path: [appNode, bundleNode, staticLibraryNode]]) + let graphTraverser = GraphTraverser(graph: graph) // When - let got = subject.resourceBundleDependencies(path: project.path, name: app.name) + let got = subject.resourceBundleDependencies(path: project.path, name: app.name).sorted() + let gotGraph = graphTraverser.resourceBundleDependencies(path: project.path, name: app.name).sorted() // Then - XCTAssertEqual(got, [bundle]) + XCTAssertEqual(gotGraph, got) + XCTAssertEqual(got.map(\.target), [bundle]) } func test_resourceBundleDependencies_when_the_target_doesnt_support_resources() { @@ -193,17 +294,28 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: bundle.name, path: project.path): Set([]), ] - let graph = ValueGraph.test(path: project.path, - projects: [project.path: project], - targets: [project.path: [staticLibrary.name: staticLibrary, - bundle.name: bundle]], - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + // Given: Value Graph + let valueGraph = ValueGraph.test(path: project.path, + projects: [project.path: project], + targets: [project.path: [staticLibrary.name: staticLibrary, + bundle.name: bundle]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let bundleNode = TargetNode.test(project: project, target: bundle) + let staticLibraryNode = TargetNode.test(project: project, target: staticLibrary, dependencies: [bundleNode]) + let graph = Graph.test(entryPath: project.path, + entryNodes: [bundleNode, staticLibraryNode], + targets: [project.path: [bundleNode, staticLibraryNode]]) + let graphTraverser = GraphTraverser(graph: graph) // When - let got = subject.resourceBundleDependencies(path: project.path, name: staticLibrary.name) + let got = subject.resourceBundleDependencies(path: project.path, name: staticLibrary.name).sorted() + let gotGraph = graphTraverser.resourceBundleDependencies(path: project.path, name: staticLibrary.name).sorted() // Then + XCTAssertEqual(gotGraph, got) XCTAssertEqual(got, []) } @@ -217,18 +329,30 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: app.name, path: project.path): Set([.target(name: bundle.name, path: project.path)]), .target(name: bundle.name, path: project.path): Set([]), ] - let graph = ValueGraph.test(path: project.path, - projects: [project.path: project], - targets: [project.path: [app.name: app, - bundle.name: bundle]], - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + + // Given: Value graph + let valueGraph = ValueGraph.test(path: project.path, + projects: [project.path: project], + targets: [project.path: [app.name: app, + bundle.name: bundle]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: bundle, dependencies: []), + (target: app, dependencies: [bundle]), + ]) + let graphTraverser = GraphTraverser(graph: graph) // When - let result = subject.resourceBundleDependencies(path: project.path, name: app.name) + let gotGraph = graphTraverser.resourceBundleDependencies(path: project.path, name: app.name).sorted() + let got = subject.resourceBundleDependencies(path: project.path, name: app.name).sorted() // Then - XCTAssertEqual(result.map(\.name), [ + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.map(\.target.name), [ "Bundle1", ]) } @@ -245,19 +369,31 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: app.name, path: projectB.path): Set([.target(name: bundle.name, path: projectA.path)]), .target(name: bundle.name, path: projectA.path): Set([]), ] - let graph = ValueGraph.test(path: .root, - projects: [projectA.path: projectA, - projectB.path: projectB], - targets: [projectA.path: [bundle.name: bundle], - projectB.path: [app.name: app]], - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + + // Given: Value Graph + let valueGraph = ValueGraph.test(path: .root, + projects: [projectA.path: projectA, + projectB.path: projectB], + targets: [projectA.path: [bundle.name: bundle], + projectB.path: [app.name: app]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let graph = Graph.create(projects: [projectA, projectB], + dependencies: [ + (project: projectA, target: bundle, dependencies: []), + (project: projectB, target: app, dependencies: [bundle]), + ]) + let graphTraverser = GraphTraverser(graph: graph) // When - let result = subject.resourceBundleDependencies(path: projectB.path, name: app.name) + let graphGot = graphTraverser.resourceBundleDependencies(path: projectB.path, name: app.name).sorted() + let got = subject.resourceBundleDependencies(path: projectB.path, name: app.name).sorted() // Then - XCTAssertEqual(result.map(\.name), [ + XCTAssertEqual(graphGot, got) + XCTAssertEqual(got.map(\.target.name), [ "Bundle1", ]) } @@ -276,20 +412,33 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: bundle.name, path: projectA.path): Set([]), .target(name: app.name, path: projectB.path): Set([.target(name: staticFramework.name, path: projectA.path)]), ] - let graph = ValueGraph.test(path: .root, - projects: [projectA.path: projectA, - projectB.path: projectB], - targets: [projectA.path: [bundle.name: bundle, - staticFramework.name: staticFramework], - projectB.path: [app.name: app]], - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + + // Given: Value Graph + let valueGraph = ValueGraph.test(path: .root, + projects: [projectA.path: projectA, + projectB.path: projectB], + targets: [projectA.path: [bundle.name: bundle, + staticFramework.name: staticFramework], + projectB.path: [app.name: app]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let graph = Graph.create(projects: [projectA, projectB], + dependencies: [ + (project: projectB, target: app, dependencies: [staticFramework]), + (project: projectA, target: staticFramework, dependencies: [bundle]), + (project: projectA, target: bundle, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) // When - let result = subject.resourceBundleDependencies(path: projectB.path, name: app.name) + let got = subject.resourceBundleDependencies(path: projectB.path, name: app.name).sorted() + let gotGraph = graphTraverser.resourceBundleDependencies(path: projectB.path, name: app.name).sorted() // Then - XCTAssertEqual(result.map(\.name), [ + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.map(\.target.name), [ "ResourceBundle", ]) } @@ -313,22 +462,37 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: staticFramework2.name, path: projectA.path): Set([.target(name: bundle2.name, path: projectA.path)]), .target(name: app.name, path: projectB.path): Set([.target(name: staticFramework1.name, path: projectA.path)]), ] - let graph = ValueGraph.test(path: .root, - projects: [projectA.path: projectA, - projectB.path: projectB], - targets: [projectA.path: [bundle1.name: bundle1, - bundle2.name: bundle2, - staticFramework1.name: staticFramework1, - staticFramework2.name: staticFramework2], - projectB.path: [app.name: app]], - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + + // Given: Value Graph + let valueGraph = ValueGraph.test(path: .root, + projects: [projectA.path: projectA, + projectB.path: projectB], + targets: [projectA.path: [bundle1.name: bundle1, + bundle2.name: bundle2, + staticFramework1.name: staticFramework1, + staticFramework2.name: staticFramework2], + projectB.path: [app.name: app]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let graph = Graph.create(projects: [projectA, projectB], + dependencies: [ + (project: projectB, target: app, dependencies: [staticFramework1]), + (project: projectA, target: staticFramework1, dependencies: [staticFramework2, bundle1]), + (project: projectA, target: staticFramework2, dependencies: [bundle2]), + (project: projectA, target: bundle1, dependencies: []), + (project: projectA, target: bundle2, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) // When - let result = subject.resourceBundleDependencies(path: projectB.path, name: app.name) + let got = subject.resourceBundleDependencies(path: projectB.path, name: app.name).sorted() + let gotGraph = graphTraverser.resourceBundleDependencies(path: projectB.path, name: app.name).sorted() // Then - XCTAssertEqual(result.map(\.name), [ + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.map(\.target.name), [ "ResourceBundle1", "ResourceBundle2", ]) @@ -355,47 +519,81 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: dynamicFramework.name, path: projectA.path): Set([.target(name: staticFramework2.name, path: projectA.path)]), .target(name: app.name, path: projectB.path): Set([.target(name: dynamicFramework.name, path: projectA.path)]), ] - let graph = ValueGraph.test(path: .root, - projects: [projectA.path: projectA, - projectB.path: projectB], - targets: [projectA.path: [bundle.name: bundle, - staticFramework1.name: staticFramework1, - staticFramework2.name: staticFramework2, - dynamicFramework.name: dynamicFramework], - projectB.path: [app.name: app]], - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + + // Given: Value Graph + let valueGraph = ValueGraph.test(path: .root, + projects: [projectA.path: projectA, + projectB.path: projectB], + targets: [projectA.path: [bundle.name: bundle, + staticFramework1.name: staticFramework1, + staticFramework2.name: staticFramework2, + dynamicFramework.name: dynamicFramework], + projectB.path: [app.name: app]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let graph = Graph.create(projects: [projectA, projectB], + dependencies: [ + (project: projectA, target: bundle, dependencies: []), + (project: projectA, target: staticFramework1, dependencies: [staticFramework2]), + (project: projectA, target: staticFramework2, dependencies: [bundle]), + (project: projectA, target: dynamicFramework, dependencies: [staticFramework2]), + (project: projectB, target: app, dependencies: [dynamicFramework]), + ]) + let graphTraverser = GraphTraverser(graph: graph) // When - let appResults = subject.resourceBundleDependencies(path: projectB.path, name: app.name) - let dynamicFrameworkResults = subject.resourceBundleDependencies(path: projectA.path, name: dynamicFramework.name) - let staticFramework1Results = subject.resourceBundleDependencies(path: projectA.path, name: staticFramework1.name) - let staticFramework2Results = subject.resourceBundleDependencies(path: projectA.path, name: staticFramework2.name) + let appResults = subject.resourceBundleDependencies(path: projectB.path, name: app.name).sorted() + let dynamicFrameworkResults = subject.resourceBundleDependencies(path: projectA.path, name: dynamicFramework.name).sorted() + let staticFramework1Results = subject.resourceBundleDependencies(path: projectA.path, name: staticFramework1.name).sorted() + let staticFramework2Results = subject.resourceBundleDependencies(path: projectA.path, name: staticFramework2.name).sorted() + + let appResultsGraph = graphTraverser.resourceBundleDependencies(path: projectB.path, name: app.name).sorted() + let dynamicFrameworkResultsGraph = graphTraverser.resourceBundleDependencies(path: projectA.path, name: dynamicFramework.name).sorted() + let staticFramework1ResultsGraph = graphTraverser.resourceBundleDependencies(path: projectA.path, name: staticFramework1.name).sorted() + let staticFramework2ResultsGraph = graphTraverser.resourceBundleDependencies(path: projectA.path, name: staticFramework2.name).sorted() // Then - XCTAssertEqual(appResults.map(\.name), []) - XCTAssertEqual(dynamicFrameworkResults.map(\.name), [ + XCTAssertEqual(appResults, appResultsGraph) + XCTAssertEqual(dynamicFrameworkResults, dynamicFrameworkResultsGraph) + XCTAssertEqual(staticFramework1Results, staticFramework1ResultsGraph) + XCTAssertEqual(staticFramework2Results, staticFramework2ResultsGraph) + + XCTAssertEqual(appResults.map(\.target.name), []) + XCTAssertEqual(dynamicFrameworkResults.map(\.target.name), [ "ResourceBundle", ]) - XCTAssertEqual(staticFramework1Results.map(\.name), []) - XCTAssertEqual(staticFramework2Results.map(\.name), []) + XCTAssertEqual(staticFramework1Results.map(\.target.name), []) + XCTAssertEqual(staticFramework2Results.map(\.target.name), []) } func test_target_from_dependency() { // Given let project = Project.test() let app = Target.test(name: "App", product: .app) - let graph = ValueGraph.test(path: project.path, - projects: [project.path: project], - targets: [project.path: [app.name: app]], - dependencies: [.target(name: app.name, path: project.path): Set()]) - let subject = ValueGraphTraverser(graph: graph) + + // Given: Value Graph + let valueGraph = ValueGraph.test(path: project.path, + projects: [project.path: project], + targets: [project.path: [app.name: app]], + dependencies: [.target(name: app.name, path: project.path): Set()]) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let graph = Graph.create(projects: [project], + dependencies: [ + (project: project, target: app, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) // When - let got = subject.target(from: .target(name: app.name, path: project.path)) + let got = subject.target(path: project.path, name: app.name) + let gotGraph = graphTraverser.target(path: project.path, name: app.name) // Then - XCTAssertEqual(got, app) + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got?.target, app) } func test_allDependencies() { @@ -412,13 +610,14 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: bundle.name, path: project.path): Set([]), ] - let graph = ValueGraph.test(path: project.path, - projects: [project.path: project], - targets: [project.path: [app.name: app, - staticLibrary.name: staticLibrary, - bundle.name: bundle]], - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + // Given: Value Graph + let valueGraph = ValueGraph.test(path: project.path, + projects: [project.path: project], + targets: [project.path: [app.name: app, + staticLibrary.name: staticLibrary, + bundle.name: bundle]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) // When let got = subject.allDependencies(path: project.path) @@ -489,18 +688,34 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: target.name, path: project.path): Set([.target(name: dependency.name, path: project.path)]), .target(name: dependency.name, path: project.path): Set([]), ] - let graph = ValueGraph.test(path: project.path, - projects: [project.path: project], - targets: [project.path: [target.name: target, - dependency.name: dependency]], - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + + // Given: Value Graph + let valueGraph = ValueGraph.test(path: project.path, + projects: [project.path: project], + targets: [project.path: [target.name: target, + dependency.name: dependency]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let dependencyNode = TargetNode(project: project, + target: dependency, + dependencies: []) + let targetNode = TargetNode(project: project, + target: target, + dependencies: [dependencyNode]) + let graph = Graph.test(projects: [project], targets: [ + project.path: [targetNode, dependencyNode], + ]) + let graphTraverser = GraphTraverser(graph: graph) // When - let got = subject.appExtensionDependencies(path: project.path, name: target.name) + let got = subject.appExtensionDependencies(path: project.path, name: target.name).sorted() + let gotGraph = graphTraverser.appExtensionDependencies(path: project.path, name: target.name).sorted() // Then - XCTAssertEqual(got.first?.name, "AppExtension") + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.first?.target.name, "AppExtension") } func test_appExtensionDependencies_when_dependencyIsStickerPackExtension() throws { @@ -512,18 +727,34 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: target.name, path: project.path): Set([.target(name: dependency.name, path: project.path)]), .target(name: dependency.name, path: project.path): Set([]), ] - let graph = ValueGraph.test(path: project.path, - projects: [project.path: project], - targets: [project.path: [target.name: target, - dependency.name: dependency]], - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + + // Given: Value graph + let valueGraph = ValueGraph.test(path: project.path, + projects: [project.path: project], + targets: [project.path: [target.name: target, + dependency.name: dependency]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let dependencyNode = TargetNode(project: project, + target: dependency, + dependencies: []) + let targetNode = TargetNode(project: project, + target: target, + dependencies: [dependencyNode]) + let graph = Graph.test(projects: [project], targets: [ + project.path: [targetNode, dependencyNode], + ]) + let graphTraverser = GraphTraverser(graph: graph) // Given - let got = subject.appExtensionDependencies(path: project.path, name: target.name) + let got = subject.appExtensionDependencies(path: project.path, name: target.name).sorted() + let gotGraph = graphTraverser.appExtensionDependencies(path: project.path, name: target.name).sorted() // Then - XCTAssertEqual(got.first?.name, "StickerPackExtension") + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.first?.target.name, "StickerPackExtension") } func test_appExtensionDependencies_when_dependencyIsMessageExtension() throws { @@ -535,19 +766,1676 @@ final class ValueGraphTraverserTests: TuistUnitTestCase { .target(name: app.name, path: project.path): Set([.target(name: messageExtension.name, path: project.path)]), .target(name: messageExtension.name, path: project.path): Set([]), ] - let graph = ValueGraph.test(path: project.path, - projects: [project.path: project], - targets: [project.path: [app.name: app, - messageExtension.name: messageExtension]], - dependencies: dependencies) - let subject = ValueGraphTraverser(graph: graph) + + // Given: Value Graph + let valueGraph = ValueGraph.test(path: project.path, + projects: [project.path: project], + targets: [project.path: [app.name: app, + messageExtension.name: messageExtension]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [messageExtension]), + (target: messageExtension, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) // When - let result = subject.appExtensionDependencies(path: project.path, name: app.name) + let got = subject.appExtensionDependencies(path: project.path, name: app.name).sorted() + let gotGraph = graphTraverser.appExtensionDependencies(path: project.path, name: app.name).sorted() // Then - XCTAssertEqual(result.map(\.name), [ + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.map(\.target.name), [ "MessageExtension", ]) } + + func test_appClipDependencies() throws { + // Given + let project = Project.test() + let app = Target.test(name: "app", product: .app) + let appClip = Target.test(name: "clip", product: .appClip) + + // Given: Value graph + let appClipNode = TargetNode.test(project: project, + target: appClip) + let appNode = TargetNode.test(project: project, + target: app, + dependencies: [appClipNode]) + let graph = Graph.test(entryNodes: [appNode, appClipNode], + projects: [project], + targets: [project.path: [appNode, appClipNode]]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Graph + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, appClip.name: appClip]], + dependencies: [.target(name: app.name, path: project.path): Set(arrayLiteral: .target(name: appClip.name, path: project.path))]) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let graphGot = graphTraverser.appClipDependencies(path: project.path, name: app.name) + let got = subject.appClipDependencies(path: project.path, name: app.name) + + // Then + XCTAssertEqual(graphGot, got) + XCTAssertEqual(got, .init(path: project.path, target: appClip, project: project)) + } + + func test_embeddableFrameworks_when_targetIsNotApp() throws { + // Given + let target = Target.test(name: "Main", product: .framework) + let dependency = Target.test(name: "Dependency", product: .framework) + let project = Project.test(targets: [target]) + + // Given: Graph + let dependencyNode = TargetNode(project: project, + target: dependency, + dependencies: []) + let targetNode = TargetNode(project: project, + target: target, + dependencies: [dependencyNode]) + let graph = Graph.test(projects: [project], targets: [ + project.path: [targetNode, dependencyNode], + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [dependency.name: dependency, target.name: target]], + dependencies: [ + .target(name: target.name, path: project.path): Set(arrayLiteral: .target(name: dependency.name, path: project.path)), + ]) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = subject.embeddableFrameworks(path: project.path, name: target.name).sorted() + let gotGraph = graphTraverser.embeddableFrameworks(path: project.path, name: target.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertNil(got.first) + } + + func test_embeddableFrameworks_when_dependencyIsATarget() throws { + // Given + let target = Target.test(name: "Main") + let dependency = Target.test(name: "Dependency", product: .framework) + let project = Project.test(targets: [target]) + + // Given: Graph + let dependencyNode = TargetNode(project: project, + target: dependency, + dependencies: []) + let targetNode = TargetNode(project: project, + target: target, + dependencies: [dependencyNode]) + let graph = Graph.test(projects: [project], + targets: [project.path: [targetNode, dependencyNode]]) + + // Given: Value Graph + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [dependency.name: dependency, target.name: target]], + dependencies: [ + .target(name: target.name, path: project.path): Set(arrayLiteral: .target(name: dependency.name, path: project.path)), + ]) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = subject.embeddableFrameworks(path: project.path, name: target.name).sorted() + let gotGraph = graph.embeddableFrameworks(path: project.path, name: target.name).sorted() + + // Then + XCTAssertEqual(gotGraph, got) + XCTAssertEqual(got.first, GraphDependencyReference.product(target: "Dependency", productName: "Dependency.framework")) + } + + func test_embeddableFrameworks_when_dependencyIsAFramework() throws { + // Given + let frameworkPath = AbsolutePath("/test/test.framework") + let target = Target.test(name: "Main", platform: .iOS) + let frameworkNode = FrameworkNode.test(path: frameworkPath, + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64], + dependencies: []) + let project = Project.test(targets: [target]) + + // Given: Graph + let targetNode = TargetNode(project: project, + target: target, + dependencies: [frameworkNode]) + let graph = Graph.test(projects: [project], + precompiled: [frameworkNode], + targets: [project.path: [targetNode]]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let frameworkDependency = ValueGraphDependency.testFramework(path: frameworkPath, + binaryPath: frameworkPath.appending(component: "test"), + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64], + isCarthage: false) + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [target.name: target]], + dependencies: [ + .target(name: target.name, path: project.path): Set(arrayLiteral: frameworkDependency), + ]) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let gotGraph = graphTraverser.embeddableFrameworks(path: project.path, name: target.name).sorted() + let got = subject.embeddableFrameworks(path: project.path, name: target.name).sorted() + + // Then + XCTAssertEqual(gotGraph, got) + XCTAssertEqual(got.first, GraphDependencyReference(precompiledNode: frameworkNode)) + } + + func test_embeddableFrameworks_when_transitiveXCFrameworks() throws { + // Given + let app = Target.test(name: "App", platform: .iOS, product: .app) + let project = Project.test(targets: [app]) + + // Given: Graph + let dNode = XCFrameworkNode.test(path: "/xcframeworks/d.xcframework", + infoPlist: .test(libraries: [.test(identifier: "id", path: RelativePath("path"), architectures: [.arm64])]), + primaryBinaryPath: "/xcframeworks/d.xcframework/d", + linking: .dynamic) + let cNode = XCFrameworkNode.test(path: "/xcframeworks/c.xcframework", + infoPlist: .test(libraries: [.test(identifier: "id", path: RelativePath("path"), architectures: [.arm64])]), + primaryBinaryPath: "/xcframeworks/c.xcframework/c", + linking: .dynamic, + dependencies: [.xcframework(dNode)]) + let appNode = TargetNode.test(target: app, dependencies: [cNode]) + + let cache = GraphLoaderCache() + cache.add(targetNode: appNode) + cache.add(precompiledNode: dNode) + cache.add(precompiledNode: cNode) + let graph = Graph.test(entryNodes: [appNode], + projects: [project], + precompiled: [cNode, dNode], + targets: [project.path: [appNode]]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let cDependency = ValueGraphDependency.xcframework(path: "/xcframeworks/c.xcframework", + infoPlist: .test(libraries: [.test(identifier: "id", path: RelativePath("path"), architectures: [.arm64])]), + primaryBinaryPath: "/xcframeworks/c.xcframework/c", + linking: .dynamic) + let dDependency = ValueGraphDependency.xcframework(path: "/xcframeworks/d.xcframework", + infoPlist: .test(libraries: [.test(identifier: "id", path: RelativePath("path"), architectures: [.arm64])]), + primaryBinaryPath: "/xcframeworks/d.xcframework/d", + linking: .dynamic) + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: cDependency), + cDependency: Set(arrayLiteral: dDependency), + dDependency: Set(), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let gotGraph = graphTraverser.embeddableFrameworks(path: project.path, name: app.name).sorted() + let got = subject.embeddableFrameworks(path: project.path, name: app.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got, [ + GraphDependencyReference(precompiledNode: cNode), + GraphDependencyReference(precompiledNode: dNode), + ]) + } + + func test_embeddableFrameworks_when_dependencyIsATransitiveFramework() throws { + // Given + let target = Target.test(name: "Main") + let dependency = Target.test(name: "Dependency", product: .framework) + let project = Project.test(targets: [target]) + + // Given: Graph + let frameworkNode = FrameworkNode.test(path: "/framework.framework", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64], + dependencies: []) + let dependencyNode = TargetNode( + project: project, + target: dependency, + dependencies: [frameworkNode] + ) + let targetNode = TargetNode( + project: project, + target: target, + dependencies: [dependencyNode] + ) + let graph = Graph.test(projects: [project], + precompiled: [frameworkNode], + targets: [project.path: [targetNode, dependencyNode]]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: target.name, path: project.path): Set(arrayLiteral: .target(name: dependency.name, path: project.path)), + .target(name: dependency.name, path: project.path): Set(arrayLiteral: .testFramework(path: "/framework.framework", + binaryPath: "/framework.framework/framework", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64], + isCarthage: false)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [target.name: target, dependency.name: dependency]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let gotGraph = graphTraverser.embeddableFrameworks(path: project.path, name: target.name).sorted() + let got = subject.embeddableFrameworks(path: project.path, name: target.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got, [ + GraphDependencyReference.product(target: "Dependency", productName: "Dependency.framework"), + GraphDependencyReference(precompiledNode: frameworkNode), + ]) + } + + func test_embeddableFrameworks_when_precompiledStaticFramework() throws { + // Given + let target = Target.test(name: "Main") + let project = Project.test(targets: [target]) + + let frameworkNode = FrameworkNode.test(path: "/test/StaticFramework.framework", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .static, + architectures: [.arm64], + dependencies: []) + let targetNode = TargetNode( + project: project, + target: target, + dependencies: [frameworkNode] + ) + + // Given: Graph + let graph = Graph.test(projects: [project], + precompiled: [frameworkNode], + targets: [project.path: [targetNode]]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: target.name, path: project.path): Set(arrayLiteral: .testFramework(path: "/test/StaticFramework.framework", + binaryPath: "/test/StaticFramework.framework/StaticFramework", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .static, + architectures: [.arm64], + isCarthage: false)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [target.name: target]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let gotGraph = graphTraverser.embeddableFrameworks(path: project.path, name: target.name).sorted() + let got = subject.embeddableFrameworks(path: project.path, name: target.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertTrue(gotGraph.isEmpty) + } + + func test_embeddableFrameworks_when_watchExtension() throws { + // Given + let frameworkA = Target.test(name: "FrameworkA", product: .framework) + let frameworkB = Target.test(name: "FrameworkB", product: .framework) + let watchExtension = Target.test(name: "WatchExtension", product: .watch2Extension) + let project = Project.test(targets: [watchExtension, frameworkA, frameworkB]) + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: watchExtension, dependencies: [frameworkA]), + (target: frameworkA, dependencies: [frameworkB]), + (target: frameworkB, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: watchExtension.name, path: project.path): Set(arrayLiteral: .target(name: frameworkA.name, path: project.path)), + .target(name: frameworkB.name, path: project.path): Set(), + .target(name: frameworkA.name, path: project.path): Set(arrayLiteral: .target(name: frameworkB.name, path: project.path)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [frameworkA.name: frameworkA, + frameworkB.name: frameworkB, + watchExtension.name: watchExtension]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let gotGraph = graphTraverser.embeddableFrameworks(path: project.path, name: watchExtension.name).sorted() + let got = subject.embeddableFrameworks(path: project.path, name: watchExtension.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got, [ + .product(target: "FrameworkA", productName: "FrameworkA.framework"), + .product(target: "FrameworkB", productName: "FrameworkB.framework"), + ]) + } + + func test_embeddableDependencies_whenHostedTestTarget() throws { + // Given + let framework = Target.test(name: "Framework", + product: .framework) + + let app = Target.test(name: "App", product: .app) + let tests = Target.test(name: "AppTests", product: .unitTests) + let project = Project.test(path: "/path/") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [framework]), + (target: framework, dependencies: []), + (target: tests, dependencies: [app]), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: .target(name: framework.name, path: project.path)), + .target(name: framework.name, path: project.path): Set(), + .target(name: tests.name, path: project.path): Set(arrayLiteral: .target(name: app.name, path: project.path)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, + tests.name: tests, + framework.name: framework]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = subject.embeddableFrameworks(path: project.path, name: tests.name).sorted() + let gotGraph = graphTraverser.embeddableFrameworks(path: project.path, name: tests.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertTrue(got.isEmpty) + } + + func test_embeddableDependencies_when_nonHostedTestTarget_dynamic_dependencies() throws { + // Given + let unitTests = Target.test(name: "AppUnitTests", product: .unitTests) + let project = Project.test(path: "/path/a") + let target = Target.test(name: "LocallyBuiltFramework", product: .framework) + + // Given: Graph + let precompiledNode = FrameworkNode.test(path: "/test/test.framework", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64], + dependencies: []) + + let targetNode = TargetNode(project: project, + target: target, + dependencies: []) + + let unitTestsNode = TargetNode(project: project, target: unitTests, dependencies: [precompiledNode, targetNode]) + + let cache = GraphLoaderCache() + cache.add(project: project) + cache.add(precompiledNode: precompiledNode) + cache.add(targetNode: unitTestsNode) + + let graph = Graph( + name: "Graph", + entryPath: project.path, + cache: cache, + entryNodes: [unitTestsNode], + workspace: Workspace.test() + ) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let precompiledDependency = ValueGraphDependency.testFramework(path: "/test/test.framework", + binaryPath: "/test/test.framework/test", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64], + isCarthage: false) + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: target.name, path: project.path): Set(), + .target(name: unitTests.name, path: project.path): Set(arrayLiteral: .target(name: target.name, path: project.path), precompiledDependency), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [unitTests.name: unitTests, + target.name: target]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let gotGraph = graphTraverser.embeddableFrameworks(path: project.path, name: unitTests.name).sorted() + let got = subject.embeddableFrameworks(path: project.path, name: unitTests.name).sorted() + + // Then + XCTAssertEqual(gotGraph, got) + XCTAssertTrue(got.isEmpty) + } + + func test_embeddableDependencies_whenHostedTestTarget_transitiveDepndencies() throws { + // Given + let framework = Target.test(name: "Framework", + product: .framework) + + let staticFramework = Target.test(name: "StaticFramework", + product: .framework) + + let app = Target.test(name: "App", product: .app) + let tests = Target.test(name: "AppTests", product: .unitTests) + let project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [staticFramework]), + (target: framework, dependencies: []), + (target: staticFramework, dependencies: [framework]), + (target: tests, dependencies: [app, staticFramework]), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: .target(name: staticFramework.name, path: project.path)), + .target(name: framework.name, path: project.path): Set(), + .target(name: staticFramework.name, path: project.path): Set(arrayLiteral: .target(name: framework.name, path: project.path)), + .target(name: tests.name, path: project.path): Set(arrayLiteral: .target(name: app.name, path: project.path), .target(name: staticFramework.name, path: project.path)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [framework.name: framework, + staticFramework.name: staticFramework, + app.name: app, + tests.name: tests]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let gotGraph = graphTraverser.embeddableFrameworks(path: project.path, name: tests.name).sorted() + let got = subject.embeddableFrameworks(path: project.path, name: tests.name).sorted() + + // Then + XCTAssertEqual(gotGraph, got) + XCTAssertTrue(got.isEmpty) + } + + func test_embeddableDependencies_whenUITest_andAppPrecompiledDepndencies() throws { + // Given + + let app = Target.test(name: "App", product: .app) + let uiTests = Target.test(name: "AppUITests", product: .uiTests) + let project = Project.test(path: "/path/a") + + // Given: Graph + let precompiledNode = FrameworkNode.test(path: "/test/test.framework", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64], + dependencies: []) + let appNode = TargetNode(project: project, target: app, dependencies: [precompiledNode]) + let uiTestsNode = TargetNode(project: project, target: uiTests, dependencies: [appNode]) + + let cache = GraphLoaderCache() + cache.add(project: project) + cache.add(precompiledNode: precompiledNode) + cache.add(targetNode: appNode) + cache.add(targetNode: uiTestsNode) + + let graph = Graph( + name: "Graph", + entryPath: project.path, + cache: cache, + entryNodes: [appNode, uiTestsNode], + workspace: Workspace.test() + ) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let precompiledDependency = ValueGraphDependency.testFramework(path: "/test/test.framework", + binaryPath: "/test/test.framework/test", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64], + isCarthage: false) + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: precompiledDependency), + .target(name: uiTests.name, path: project.path): Set(arrayLiteral: .target(name: app.name, path: project.path)), + precompiledDependency: Set(), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, + uiTests.name: uiTests]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = subject.embeddableFrameworks(path: project.path, name: uiTests.name).sorted() + let gotGraph = graphTraverser.embeddableFrameworks(path: project.path, name: uiTests.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertTrue(gotGraph.isEmpty) + } + + func test_librariesPublicHeadersFolders() throws { + // Given + let target = Target.test(name: "Main") + let publicHeadersPath = AbsolutePath("/test/public/") + let project = Project.test(targets: [target]) + + // Given: Graph + let precompiledNode = LibraryNode.test(path: AbsolutePath("/test/test.a"), + publicHeaders: publicHeadersPath, + linking: .static) + let targetNode = TargetNode(project: project, + target: target, + dependencies: [precompiledNode]) + let graph = Graph.test(projects: [project], + precompiled: [precompiledNode], + targets: [project.path: [targetNode]]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let precompiledDependency = ValueGraphDependency.testLibrary(path: AbsolutePath("/test/test.a"), + publicHeaders: publicHeadersPath, + linking: .static, + architectures: []) + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: target.name, path: project.path): Set(arrayLiteral: precompiledDependency), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [target.name: target]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = subject.librariesPublicHeadersFolders(path: project.path, + name: target.name).sorted() + let gotGraph = graphTraverser.librariesPublicHeadersFolders(path: project.path, + name: target.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.first, publicHeadersPath) + } + + func test_librariesSearchPaths() throws { + // Given + let target = Target.test(name: "Main") + let project = Project.test(targets: [target]) + + // Given: Graph + let precompiledNode = LibraryNode.test(path: "/test/test.a", + publicHeaders: "/test/public/", + architectures: [], + linking: .static) + let targetNode = TargetNode(project: project, + target: target, + dependencies: [precompiledNode]) + let graph = Graph.test(projects: [project], + precompiled: [precompiledNode], + targets: [project.path: [targetNode]]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let precompiledDependency = ValueGraphDependency.testLibrary(path: "/test/test.a", + publicHeaders: "/test/public/", + linking: .static, + architectures: []) + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: target.name, path: project.path): Set(arrayLiteral: precompiledDependency), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [target.name: target]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = subject.librariesSearchPaths(path: project.path, name: target.name).sorted() + let gotGraph = graphTraverser.librariesSearchPaths(path: project.path, name: target.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got, [AbsolutePath("/test")]) + } + + func test_linkableDependencies_whenPrecompiled() throws { + // Given + let target = Target.test(name: "Main") + let project = Project.test(targets: [target]) + + // Given: Graph + let precompiledNode = FrameworkNode.test(path: "/test/test.framework", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64]) + let targetNode = TargetNode(project: project, + target: target, + dependencies: [precompiledNode]) + let graph = Graph.test(targets: [targetNode.path: [targetNode]]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let precompiledDependency = ValueGraphDependency.testFramework(path: "/test/test.framework", + binaryPath: "/test/test.framework/test", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64], + isCarthage: false) + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: target.name, path: project.path): Set(arrayLiteral: precompiledDependency), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [target.name: target]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: target.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: target.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(gotGraph.first, GraphDependencyReference(precompiledNode: precompiledNode)) + } + + func test_linkableDependencies_whenALibraryTarget() throws { + // Given + let target = Target.test(name: "Main") + let dependency = Target.test(name: "Dependency", product: .staticLibrary) + let project = Project.test(targets: [target]) + + // Given: Graph + let dependencyNode = TargetNode(project: project, + target: dependency, + dependencies: []) + let targetNode = TargetNode(project: project, + target: target, + dependencies: [dependencyNode]) + let graph = Graph.test(projects: [project], targets: [ + project.path: [dependencyNode, targetNode], + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: target.name, path: project.path): Set(arrayLiteral: .target(name: dependency.name, path: project.path)), + .target(name: dependency.name, path: project.path): Set(), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [target.name: target, dependency.name: dependency]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: target.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: target.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.first, .product(target: "Dependency", productName: "libDependency.a")) + } + + func test_linkableDependencies_whenAFrameworkTarget() throws { + // Given + let target = Target.test(name: "Main") + let dependency = Target.test(name: "Dependency", product: .framework) + let staticDependency = Target.test(name: "StaticDependency", product: .staticLibrary) + let project = Project.test(targets: [target]) + + // Given: Graph + let staticDependencyNode = TargetNode(project: project, + target: staticDependency, + dependencies: []) + let dependencyNode = TargetNode(project: project, + target: dependency, + dependencies: [staticDependencyNode]) + let targetNode = TargetNode(project: project, + target: target, + dependencies: [dependencyNode]) + let graph = Graph.test(projects: [project], targets: [project.path: [targetNode, dependencyNode, staticDependencyNode]]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: target.name, path: project.path): Set(arrayLiteral: .target(name: dependency.name, path: project.path)), + .target(name: dependency.name, path: project.path): Set(arrayLiteral: .target(name: staticDependency.name, path: project.path)), + .target(name: staticDependency.name, path: project.path): Set(), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [target.name: target, + dependency.name: dependency, + staticDependency.name: staticDependency]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, + name: target.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, + name: target.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + + XCTAssertEqual(got.count, 1) + XCTAssertEqual(got.first, .product(target: "Dependency", productName: "Dependency.framework")) + + let frameworkGot = try graph.linkableDependencies(path: project.path, name: dependency.name) + + XCTAssertEqual(frameworkGot.count, 1) + XCTAssertTrue(frameworkGot.contains(.product(target: "StaticDependency", productName: "libStaticDependency.a"))) + } + + 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 project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [staticFramework]), + (target: staticFramework, dependencies: [dynamicFramework]), + (target: dynamicFramework, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: .target(name: staticFramework.name, path: project.path)), + .target(name: staticFramework.name, path: project.path): Set(arrayLiteral: .target(name: dynamicFramework.name, path: project.path)), + .target(name: dynamicFramework.name, path: project.path): Set(), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, + staticFramework.name: staticFramework, + dynamicFramework.name: dynamicFramework]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: app.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: app.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got, [GraphDependencyReference.product(target: "DynamicFramework", productName: "DynamicFramework.framework"), + GraphDependencyReference.product(target: "StaticFramework", productName: "StaticFramework.framework")]) + } + + 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 project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [dynamicFramework1]), + (target: dynamicFramework1, dependencies: [staticFramework1]), + (target: staticFramework1, dependencies: [staticFramework2]), + (target: staticFramework2, dependencies: [dynamicFramework2]), + (target: dynamicFramework2, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: .target(name: dynamicFramework1.name, path: project.path)), + .target(name: dynamicFramework1.name, path: project.path): Set(arrayLiteral: .target(name: staticFramework1.name, path: project.path)), + .target(name: staticFramework1.name, path: project.path): Set(arrayLiteral: .target(name: staticFramework2.name, path: project.path)), + .target(name: staticFramework2.name, path: project.path): Set(arrayLiteral: .target(name: dynamicFramework2.name, path: project.path)), + .target(name: dynamicFramework2.name, path: project.path): Set(), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, + dynamicFramework1.name: dynamicFramework1, + dynamicFramework2.name: dynamicFramework2, + staticFramework1.name: staticFramework1, + staticFramework2.name: staticFramework2]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let appGot = try subject.linkableDependencies(path: project.path, name: app.name).sorted() + let dynamicFramework1Got = try subject.linkableDependencies(path: project.path, name: dynamicFramework1.name).sorted() + let appGotGraph = try graphTraverser.linkableDependencies(path: project.path, name: app.name).sorted() + let dynamicFramework1GotGraph = try graphTraverser.linkableDependencies(path: project.path, name: dynamicFramework1.name).sorted() + + // Then + XCTAssertEqual(appGot, appGotGraph) + XCTAssertEqual(dynamicFramework1Got, dynamicFramework1GotGraph) + + XCTAssertEqual(appGot, [ + GraphDependencyReference.product(target: "DynamicFramework1", productName: "DynamicFramework1.framework"), + ]) + XCTAssertEqual(dynamicFramework1Got, [ + GraphDependencyReference.product(target: "DynamicFramework2", productName: "DynamicFramework2.framework"), + GraphDependencyReference.product(target: "StaticFramework1", productName: "libStaticFramework1.a"), + GraphDependencyReference.product(target: "StaticFramework2", productName: "libStaticFramework2.a"), + ]) + } + + 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 project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [dynamicFramework1]), + (target: dynamicFramework1, dependencies: [dynamicFramework2]), + (target: dynamicFramework2, dependencies: [staticFramework1]), + (target: staticFramework1, dependencies: [staticFramework2]), + (target: staticFramework2, dependencies: [dynamicFramework3]), + (target: dynamicFramework3, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: .target(name: dynamicFramework1.name, path: project.path)), + .target(name: dynamicFramework1.name, path: project.path): Set(arrayLiteral: .target(name: dynamicFramework2.name, path: project.path)), + .target(name: dynamicFramework2.name, path: project.path): Set(arrayLiteral: .target(name: staticFramework1.name, path: project.path)), + .target(name: staticFramework1.name, path: project.path): Set(arrayLiteral: .target(name: staticFramework2.name, path: project.path)), + .target(name: staticFramework2.name, path: project.path): Set(arrayLiteral: .target(name: dynamicFramework3.name, path: project.path)), + .target(name: dynamicFramework3.name, path: project.path): Set(), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, + dynamicFramework1.name: dynamicFramework1, + dynamicFramework2.name: dynamicFramework2, + staticFramework1.name: staticFramework1, + staticFramework2.name: staticFramework2, + dynamicFramework3.name: dynamicFramework3]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let dynamicFramework1Got = try subject.linkableDependencies(path: project.path, name: dynamicFramework1.name).sorted() + let dynamicFramework1GotGraph = try graphTraverser.linkableDependencies(path: project.path, name: dynamicFramework1.name).sorted() + + // Then + XCTAssertEqual(dynamicFramework1Got, dynamicFramework1GotGraph) + XCTAssertEqual(dynamicFramework1Got, [GraphDependencyReference.product(target: "DynamicFramework2", productName: "DynamicFramework2.framework")]) + } + + func test_linkableDependencies_transitiveSDKDependenciesStatic() throws { + // Given + let staticFrameworkA = Target.test(name: "StaticFrameworkA", + product: .staticFramework, + dependencies: [.sdk(name: "some.framework", status: .optional)]) + let staticFrameworkB = Target.test(name: "StaticFrameworkB", + product: .staticFramework, + dependencies: []) + let app = Target.test(name: "App", product: .app) + let project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [staticFrameworkB]), + (target: staticFrameworkB, dependencies: [staticFrameworkA]), + (target: staticFrameworkA, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: .target(name: staticFrameworkB.name, path: project.path)), + .target(name: staticFrameworkB.name, path: project.path): Set(arrayLiteral: .target(name: staticFrameworkA.name, path: project.path)), + .target(name: staticFrameworkA.name, path: project.path): Set(arrayLiteral: .sdk(name: "some.framework", + path: "/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/some.framework", + status: .optional, + source: .developer)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, + staticFrameworkB.name: staticFrameworkB, + staticFrameworkA.name: staticFrameworkA]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: app.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: app.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.compactMap(sdkDependency), [ + SDKPathAndStatus(name: "some.framework", status: .optional), + ]) + } + + func test_linkableDependencies_transitiveSDKDependenciesDynamic() throws { + // Given + let staticFramework = Target.test(name: "StaticFramework", + product: .staticFramework, + dependencies: [.sdk(name: "some.framework", status: .optional)]) + let dynamicFramework = Target.test(name: "DynamicFramework", + product: .framework, + dependencies: []) + let app = Target.test(name: "App", product: .app) + let project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [dynamicFramework]), + (target: dynamicFramework, dependencies: [staticFramework]), + (target: staticFramework, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: .target(name: dynamicFramework.name, path: project.path)), + .target(name: dynamicFramework.name, path: project.path): Set(arrayLiteral: .target(name: staticFramework.name, path: project.path)), + .target(name: staticFramework.name, path: project.path): Set(arrayLiteral: .sdk(name: "some.framework", + path: "/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/some.framework", + status: .optional, + source: .developer)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, + staticFramework.name: staticFramework, + dynamicFramework.name: dynamicFramework]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let appGot = try subject.linkableDependencies(path: project.path, name: app.name).sorted() + let dynamicGot = try subject.linkableDependencies(path: project.path, name: dynamicFramework.name).sorted() + let appGotGraph = try graphTraverser.linkableDependencies(path: project.path, name: app.name).sorted() + let dynamicGotGraph = try graphTraverser.linkableDependencies(path: project.path, name: dynamicFramework.name).sorted() + + // Then + XCTAssertEqual(appGot, appGotGraph) + XCTAssertEqual(dynamicGot, dynamicGotGraph) + + XCTAssertEqual(appGotGraph.compactMap(sdkDependency), []) + XCTAssertEqual(dynamicGotGraph.compactMap(sdkDependency), + [SDKPathAndStatus(name: "some.framework", status: .optional)]) + } + + func test_linkableDependencies_transitiveSDKDependenciesNotDuplicated() throws { + // Given + let staticFramework = Target.test(name: "StaticFramework", + product: .staticFramework, + dependencies: [.sdk(name: "some.framework", status: .optional)]) + let app = Target.test(name: "App", + product: .app, + dependencies: [.sdk(name: "some.framework", status: .optional)]) + + let project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [staticFramework]), + (target: staticFramework, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: .target(name: staticFramework.name, path: project.path)), + .target(name: staticFramework.name, path: project.path): Set(arrayLiteral: .sdk(name: "some.framework", + path: "/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/some.framework", + status: .optional, + source: .developer)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, + staticFramework.name: staticFramework]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: app.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: app.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.compactMap(sdkDependency), [SDKPathAndStatus(name: "some.framework", status: .optional)]) + } + + func test_linkableDependencies_transitiveSDKDependenciesImmediateDependencies() throws { + // Given + let staticFramework = Target.test(name: "StaticFrameworkA", + product: .staticFramework, + dependencies: [.sdk(name: "thingone.framework", status: .optional), + .sdk(name: "thingtwo.framework", status: .required)]) + + let project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: staticFramework, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: staticFramework.name, path: project.path): Set(arrayLiteral: .sdk(name: "thingone.framework", + path: "/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/thingone.framework", + status: .optional, + source: .developer), + .sdk(name: "thingtwo.framework", + path: "/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/thingtwo.framework", + status: .required, + source: .developer)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [staticFramework.name: staticFramework]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: staticFramework.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: staticFramework.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.compactMap(sdkDependency), + [SDKPathAndStatus(name: "thingone.framework", status: .optional), + SDKPathAndStatus(name: "thingtwo.framework", status: .required)]) + } + + func test_linkableDependencies_NoTransitiveSDKDependenciesForStaticFrameworks() throws { + // Given + let staticFrameworkA = Target.test(name: "StaticFrameworkA", + product: .staticFramework, + dependencies: [.sdk(name: "ThingOne.framework", status: .optional)]) + let staticFrameworkB = Target.test(name: "StaticFrameworkB", + product: .staticFramework, + dependencies: [.sdk(name: "ThingTwo.framework", status: .optional)]) + + let project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: staticFrameworkA, dependencies: [staticFrameworkB]), + (target: staticFrameworkB, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: staticFrameworkA.name, path: project.path): Set(arrayLiteral: .target(name: staticFrameworkB.name, path: project.path), + .sdk(name: "ThingOne.framework", + path: "/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/ThingOne.framework", + status: .optional, + source: .developer)), + .target(name: staticFrameworkB.name, path: project.path): Set(arrayLiteral: .sdk(name: "ThingTwo.framework", + path: "/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/ThingTwo.framework", + status: .optional, + source: .developer)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [staticFrameworkA.name: staticFrameworkA, + staticFrameworkB.name: staticFrameworkB]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: staticFrameworkA.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: staticFrameworkA.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got.compactMap(sdkDependency), + [SDKPathAndStatus(name: "ThingOne.framework", status: .optional)]) + } + + func test_linkableDependencies_when_watchExtension() throws { + // Given + let frameworkA = Target.test(name: "FrameworkA", product: .framework) + let frameworkB = Target.test(name: "FrameworkB", product: .framework) + let watchExtension = Target.test(name: "WatchExtension", product: .watch2Extension) + let project = Project.test(targets: [watchExtension, frameworkA, frameworkB]) + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: watchExtension, dependencies: [frameworkA]), + (target: frameworkA, dependencies: [frameworkB]), + (target: frameworkB, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: watchExtension.name, path: project.path): Set(arrayLiteral: .target(name: frameworkA.name, path: project.path)), + .target(name: frameworkA.name, path: project.path): Set(arrayLiteral: .target(name: frameworkB.name, path: project.path)), + .target(name: frameworkB.name, path: project.path): Set(), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [watchExtension.name: watchExtension, + frameworkA.name: frameworkA, + frameworkB.name: frameworkB]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: watchExtension.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: watchExtension.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got, [ + .product(target: "FrameworkA", productName: "FrameworkA.framework"), + ]) + } + + func test_linkableDependencies_when_watchExtension_staticDependency() throws { + // Given + let frameworkA = Target.test(name: "FrameworkA", product: .staticFramework) + let frameworkB = Target.test(name: "FrameworkB", product: .framework) + let watchExtension = Target.test(name: "WatchExtension", product: .watch2Extension) + let project = Project.test(targets: [watchExtension, frameworkA, frameworkB]) + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: watchExtension, dependencies: [frameworkA]), + (target: frameworkA, dependencies: [frameworkB]), + (target: frameworkB, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: watchExtension.name, path: project.path): Set(arrayLiteral: .target(name: frameworkA.name, path: project.path)), + .target(name: frameworkA.name, path: project.path): Set(arrayLiteral: .target(name: frameworkB.name, path: project.path)), + .target(name: frameworkB.name, path: project.path): Set(), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [watchExtension.name: watchExtension, + frameworkA.name: frameworkA, + frameworkB.name: frameworkB]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: watchExtension.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: watchExtension.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got, [ + .product(target: "FrameworkA", productName: "FrameworkA.framework"), + .product(target: "FrameworkB", productName: "FrameworkB.framework"), + ]) + } + + func test_linkableDependencies_whenHostedTestTarget_withCommonStaticProducts() throws { + // Given + let staticFramework = Target.test(name: "StaticFramework", + product: .staticFramework) + + let app = Target.test(name: "App", product: .app) + let tests = Target.test(name: "AppTests", product: .unitTests) + let project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [staticFramework]), + (target: staticFramework, dependencies: []), + (target: tests, dependencies: [app, staticFramework]), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: .target(name: staticFramework.name, path: project.path)), + .target(name: staticFramework.name, path: project.path): Set(), + .target(name: tests.name, path: project.path): Set(arrayLiteral: .target(name: staticFramework.name, path: project.path), + .target(name: app.name, path: project.path)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, + staticFramework.name: staticFramework, + tests.name: tests]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: tests.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: tests.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertTrue(got.isEmpty) + } + + func test_linkableDependencies_whenHostedTestTarget_withCommonDynamicProducts() throws { + // Given + let framework = Target.test(name: "Framework", + product: .framework) + + let app = Target.test(name: "App", product: .app) + let tests = Target.test(name: "AppTests", product: .unitTests) + let project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [framework]), + (target: framework, dependencies: []), + (target: tests, dependencies: [app, framework]), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: .target(name: framework.name, path: project.path)), + .target(name: framework.name, path: project.path): Set(), + .target(name: tests.name, path: project.path): Set(arrayLiteral: .target(name: framework.name, path: project.path), + .target(name: app.name, path: project.path)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, + framework.name: framework, + tests.name: tests]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: tests.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: tests.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got, [ + .product(target: "Framework", productName: "Framework.framework"), + ]) + } + + func test_linkableDependencies_whenHostedTestTarget_doNotIncludeRedundantDependencies() throws { + // Given + let framework = Target.test(name: "Framework", + product: .framework) + + let app = Target.test(name: "App", product: .app) + let tests = Target.test(name: "AppTests", product: .unitTests) + let project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [framework]), + (target: framework, dependencies: []), + (target: tests, dependencies: [app]), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: app.name, path: project.path): Set(arrayLiteral: .target(name: framework.name, path: project.path)), + .target(name: framework.name, path: project.path): Set(), + .target(name: tests.name, path: project.path): Set(arrayLiteral: .target(name: app.name, path: project.path)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, + framework.name: framework, + tests.name: tests]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: tests.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: tests.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertTrue(got.isEmpty) + } + + func test_linkableDependencies_when_appClipSDKNode() throws { + // Given + let target = Target.test(name: "AppClip", product: .appClip) + let project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [(target: target, dependencies: [])]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let dependencies: [ValueGraphDependency: Set] = [ + .target(name: target.name, path: project.path): Set(arrayLiteral: .sdk(name: "AppClip.framework", path: "/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/AppClip.framework", status: .required, source: .system)), + ] + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [target.name: target]], + dependencies: dependencies) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = try subject.linkableDependencies(path: project.path, name: target.name).sorted() + let gotGraph = try graphTraverser.linkableDependencies(path: project.path, name: target.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got, [.sdk(path: try SDKNode.appClip(status: .required).path, status: .required, source: .system)]) + } + + func test_librariesSwiftIncludePaths() throws { + // Given + let target = Target.test(name: "Main") + let precompiledNodeA = LibraryNode.test(path: "/test/test.a", swiftModuleMap: "/test/modules/test.swiftmodulemap") + let precompiledNodeB = LibraryNode.test(path: "/test/another.a", swiftModuleMap: nil) + let project = Project.test(targets: [target]) + + // Given: Value Graph + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [target.name: target]], + dependencies: [ + .target(name: target.name, path: project.path): Set([ + .testLibrary(path: "/test/test.a", swiftModuleMap: "/test/modules/test.swiftmodulemap"), + .testLibrary(path: "/test/another.b"), + ]), + .testLibrary(path: "/test/test.a", swiftModuleMap: "/test/modules/test.swiftmodulemap"): Set([]), + .testLibrary(path: "/test/another.b"): Set([]), + ]) + let subject = ValueGraphTraverser(graph: valueGraph) + + // Given: Graph + let targetNode = TargetNode(project: project, + target: target, + dependencies: [precompiledNodeA, precompiledNodeB]) + let graph = Graph.test(projects: [project], + precompiled: [precompiledNodeA, precompiledNodeB], + targets: [project.path: [targetNode]]) + let graphTraverser = GraphTraverser(graph: graph) + + // When + let got = subject.librariesSwiftIncludePaths(path: project.path, name: target.name).sorted() + let gotGraph = graphTraverser.librariesSwiftIncludePaths(path: project.path, name: target.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got, [AbsolutePath("/test/modules")]) + } + + func test_runPathSearchPaths() throws { + // Given + let unitTests = Target.test(name: "AppUnitTests", product: .unitTests) + let project = Project.test(path: "/path/a") + + // Given: Graph + let precompiledNode = FrameworkNode.test(path: "/test/test.framework", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64]) + let precompiledNodeB = FrameworkNode.test(path: "/test/testb.framework", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64]) + let unitTestsNode = TargetNode(project: project, target: unitTests, dependencies: [precompiledNode, precompiledNodeB]) + + let cache = GraphLoaderCache() + cache.add(project: project) + cache.add(precompiledNode: precompiledNode) + cache.add(precompiledNode: precompiledNodeB) + cache.add(targetNode: unitTestsNode) + + let graph = Graph( + name: "Graph", + entryPath: project.path, + cache: cache, + entryNodes: [unitTestsNode], + workspace: Workspace.test(path: project.path) + ) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let precompiledDependency = ValueGraphDependency.testFramework(path: "/test/test.famework", + binaryPath: "/test/test.framework/test", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64], + isCarthage: false) + let precompiledBDependency = ValueGraphDependency.testFramework(path: "/test/testb.famework", + binaryPath: "/test/testb.framework/testb", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64], + isCarthage: false) + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [unitTests.name: unitTests]], + dependencies: [ + .target(name: unitTests.name, path: project.path): Set([precompiledDependency, precompiledBDependency]), + precompiledDependency: Set(), + precompiledBDependency: Set(), + ]) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = subject.runPathSearchPaths(path: project.path, name: unitTests.name).sorted() + let gotGraph = graphTraverser.runPathSearchPaths(path: project.path, name: unitTests.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual( + got, + [AbsolutePath("/test")] + ) + } + + func test_runPathSearchPaths_when_unit_tests_with_hosted_target() throws { + // Given + let precompiledNode = FrameworkNode.test(path: "/test/test.framework", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64]) + let app = Target.test(name: "App", product: .app) + let unitTests = Target.test(name: "AppUnitTests", product: .unitTests) + let project = Project.test(path: "/path/a") + + let appNode = TargetNode(project: project, target: app, dependencies: [precompiledNode]) + let unitTestsNode = TargetNode(project: project, target: unitTests, dependencies: [appNode, precompiledNode]) + + let cache = GraphLoaderCache() + cache.add(project: project) + cache.add(targetNode: appNode) + cache.add(precompiledNode: precompiledNode) + cache.add(targetNode: unitTestsNode) + + // Given: Graph + let graph = Graph( + name: "Graph", + entryPath: project.path, + cache: cache, + entryNodes: [unitTestsNode], + workspace: Workspace.test() + ) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let precompiledDependency = ValueGraphDependency.testFramework(path: "/test/test.famework", + binaryPath: "/test/test.framework/test", + dsymPath: nil, + bcsymbolmapPaths: [], + linking: .dynamic, + architectures: [.arm64], + isCarthage: false) + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [unitTests.name: unitTests, + app.name: app]], + dependencies: [ + .target(name: unitTests.name, path: project.path): Set([precompiledDependency, .target(name: app.name, path: project.path)]), + .target(name: app.name, path: project.path): Set([]), + precompiledDependency: Set(), + ]) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = subject.runPathSearchPaths(path: project.path, name: unitTests.name).sorted() + let gotGraph = graphTraverser.runPathSearchPaths(path: project.path, name: unitTests.name).sorted() + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEmpty(got) + } + + func test_hostTargetNode_watchApp() { + // Given + let app = Target.test(name: "App", platform: .iOS, product: .app) + let watchApp = Target.test(name: "WatchApp", platform: .watchOS, product: .watch2App) + let project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: app, dependencies: [watchApp]), + (target: watchApp, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [app.name: app, + watchApp.name: watchApp]], + dependencies: [ + .target(name: app.name, path: project.path): Set([.target(name: watchApp.name, path: project.path)]), + .target(name: watchApp.name, path: project.path): Set([]), + ]) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = subject.hostTargetFor(path: project.path, name: "WatchApp") + let gotGraph = graphTraverser.hostTargetFor(path: project.path, name: "WatchApp") + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got?.target, app) + } + + func test_hostTargetNode_watchAppExtension() { + // Given + let watchApp = Target.test(name: "WatchApp", platform: .watchOS, product: .watch2App) + let watchAppExtension = Target.test(name: "WatchAppExtension", platform: .watchOS, product: .watch2Extension) + let project = Project.test(path: "/path/a") + + // Given: Graph + let graph = Graph.create(project: project, + dependencies: [ + (target: watchApp, dependencies: [watchAppExtension]), + (target: watchAppExtension, dependencies: []), + ]) + let graphTraverser = GraphTraverser(graph: graph) + + // Given: Value Graph + let valueGraph = ValueGraph.test(projects: [project.path: project], + targets: [project.path: [watchAppExtension.name: watchAppExtension, + watchApp.name: watchApp]], + dependencies: [ + .target(name: watchApp.name, path: project.path): Set([.target(name: watchAppExtension.name, path: project.path)]), + .target(name: watchAppExtension.name, path: project.path): Set([]), + ]) + let subject = ValueGraphTraverser(graph: valueGraph) + + // When + let got = subject.hostTargetFor(path: project.path, name: "WatchAppExtension") + let gotGraph = graphTraverser.hostTargetFor(path: project.path, name: "WatchAppExtension") + + // Then + XCTAssertEqual(got, gotGraph) + XCTAssertEqual(got?.target, watchApp) + } + + // MARK: - Helpers + + private func sdkDependency(from dependency: GraphDependencyReference) -> SDKPathAndStatus? { + switch dependency { + case let .sdk(path, status, _): + return SDKPathAndStatus(name: path.basename, status: status) + default: + return nil + } + } +} + +private struct SDKPathAndStatus: Equatable { + var name: String + var status: SDKStatus }