diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a8db606c..e2f6f83ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/ - Product name linting failing when it contains variables https://github.com/tuist/tuist/pull/494 by @dcvz - Build phases not generated in the right position https://github.com/tuist/tuist/pull/506 by @pepibumur - Remove \$(SRCROOT) from being included in `Info.plist` path https://github.com/tuist/tuist/pull/511 by @dcvz +- Prevent generation of redundant file elements https://github.com/tuist/tuist/pull/515 by @kwridan ## 0.17.0 diff --git a/Sources/TuistGenerator/Generator/LinkGenerator.swift b/Sources/TuistGenerator/Generator/LinkGenerator.swift index d28b6a3a3..4ad54b5ed 100644 --- a/Sources/TuistGenerator/Generator/LinkGenerator.swift +++ b/Sources/TuistGenerator/Generator/LinkGenerator.swift @@ -182,7 +182,7 @@ final class LinkGenerator: LinkGenerating { precompiledEmbedPhase.inputPaths.append(relativePath) precompiledEmbedPhase.outputPaths.append("$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/\(path.components.last!)") - } else if case let DependencyReference.product(target) = dependency { + } else if case let DependencyReference.product(target, _) = dependency { guard let fileRef = fileElements.product(target: target) else { throw LinkGeneratorError.missingProduct(name: target) } @@ -283,7 +283,7 @@ final class LinkGenerator: LinkGenerating { let buildFile = PBXBuildFile(file: fileRef) pbxproj.add(object: buildFile) buildPhase.files?.append(buildFile) - case let .product(target): + case let .product(target, _): guard let fileRef = fileElements.product(target: target) else { throw LinkGeneratorError.missingProduct(name: target) } @@ -322,14 +322,7 @@ final class LinkGenerator: LinkGenerating { // This technique also allows resource bundles that reside in different projects to get built ahead of the // "Copy Bundle Resources" phase. - var dependencies = [DependencyReference]() - if target.product.isStatic { - dependencies.append(contentsOf: graph.staticDependencies(path: path, name: target.name)) - } - - dependencies.append(contentsOf: - graph.resourceBundleDependencies(path: path, name: target.name) - .map { .product(target: $0.target.name) }) + let dependencies = graph.copyProductDependencies(path: path, target: target) if !dependencies.isEmpty { try generateDependenciesBuildPhase( @@ -347,7 +340,7 @@ final class LinkGenerator: LinkGenerating { fileElements: ProjectFileElements) throws { var files: [PBXBuildFile] = [] - for case let .product(target) in dependencies.sorted() { + for case let .product(target, _) in dependencies.sorted() { guard let fileRef = fileElements.product(target: target) else { throw LinkGeneratorError.missingProduct(name: target) } diff --git a/Sources/TuistGenerator/Generator/ProjectFileElements.swift b/Sources/TuistGenerator/Generator/ProjectFileElements.swift index 98329666a..9ae07e669 100644 --- a/Sources/TuistGenerator/Generator/ProjectFileElements.swift +++ b/Sources/TuistGenerator/Generator/ProjectFileElements.swift @@ -33,15 +33,18 @@ class ProjectFileElements { var sdks: [AbsolutePath: PBXFileReference] = [:] let playgrounds: Playgrounding let filesSortener: ProjectFilesSortening + private let system: Systeming // MARK: - Init init(_ elements: [AbsolutePath: PBXFileElement] = [:], playgrounds: Playgrounding = Playgrounds(), - filesSortener: ProjectFilesSortening = ProjectFilesSortener()) { + filesSortener: ProjectFilesSortening = ProjectFilesSortener(), + system: Systeming = System()) { self.elements = elements self.playgrounds = playgrounds self.filesSortener = filesSortener + self.system = system } func generateProjectFiles(project: Project, @@ -50,10 +53,9 @@ class ProjectFileElements { pbxproj: PBXProj, sourceRootPath: AbsolutePath) throws { var files = Set() - var products = Set() - project.targets.forEach { target in - files.formUnion(targetFiles(target: target, sourceRootPath: sourceRootPath)) - products.formUnion(targetProducts(target: target)) + + try project.targets.forEach { target in + try files.formUnion(targetFiles(target: target, projectPath: project.path, graph: graph)) } let projectFileElements = projectFiles(project: project) files.formUnion(projectFileElements) @@ -75,17 +77,15 @@ class ProjectFileElements { pbxproj: pbxproj, sourceRootPath: sourceRootPath) - let dependencies = graph.findAll(path: project.path) + // Products + let directProducts = project.targets.map { + DependencyReference.product(target: $0.name, productName: $0.productNameWithExtension) + } - /// Products - try generateProducts(project: project, - dependencies: dependencies, - groups: groups, - pbxproj: pbxproj) + // Dependencies + let dependencies = try graph.allDependencyReferences(for: project, system: system) - /// Dependencies - try generate(dependencies: dependencies, - path: project.path, + try generate(dependencyReferences: Set(directProducts + dependencies), groups: groups, pbxproj: pbxproj, sourceRootPath: sourceRootPath, @@ -112,13 +112,7 @@ class ProjectFileElements { return fileElements } - func targetProducts(target: Target) -> Set { - var products: Set = Set() - products.insert(target.productNameWithExtension) - return products - } - - func targetFiles(target: Target, sourceRootPath _: AbsolutePath) -> Set { + func targetFiles(target: Target, projectPath: AbsolutePath, graph: Graphing) throws -> Set { var files = Set() files.formUnion(target.sources.map { $0.path }) files.formUnion(target.coreDataModels.map { $0.path }) @@ -153,6 +147,20 @@ class ProjectFileElements { isReference: $0.isReference) }) + // Local Packages + elements.formUnion( + try graph.packages(path: projectPath, name: target.name).compactMap { node -> GroupFileElement? in + switch node.packageType { + case let .local(path: packagePath, productName: _): + return GroupFileElement(path: projectPath.appending(packagePath), + group: target.filesGroup, + isReference: true) + default: + return nil + } + } + ) + return elements } @@ -184,86 +192,50 @@ class ProjectFileElements { } } - func generateProducts(project: Project, - dependencies: Set, - groups: ProjectGroups, - pbxproj: PBXProj) throws { - try prepareProductsFileReferences(project: project, dependencies: dependencies).forEach { pair in - guard self.products[pair.targetName] == nil else { return } - pbxproj.add(object: pair.fileReference) - groups.products.children.append(pair.fileReference) - self.products[pair.targetName] = pair.fileReference - } - } - - func prepareProductsFileReferences(project: Project, dependencies: Set) - throws -> [(targetName: String, fileReference: PBXFileReference)] { - let targetsProducts = project.targets - .map { ($0, $0.product) } - let dependenciesProducts = dependencies - .compactMap { $0 as? TargetNode } - .map { $0.target } - .map { ($0, $0.product) } - let mergeStrategy: (Product, Product) -> Product = { first, _ in first } - let sortByName: ((Target, Product), (Target, Product)) -> Bool = { first, second in - first.0.productNameWithExtension < second.0.productNameWithExtension - } - - let targetsProductsDictionary = Dictionary(targetsProducts, uniquingKeysWith: mergeStrategy) - let dependenciesProductsDictionary = Dictionary(dependenciesProducts, uniquingKeysWith: mergeStrategy) - let productsDictionary = targetsProductsDictionary.merging(dependenciesProductsDictionary, - uniquingKeysWith: mergeStrategy) - return productsDictionary - .sorted(by: sortByName) - .map { target, product in - let fileType = Xcode.filetype(extension: product.xcodeValue.fileExtension!) - return (targetName: target.name, - fileReference: PBXFileReference(sourceTree: .buildProductsDir, - explicitFileType: fileType, - path: target.productNameWithExtension, - includeInIndex: false)) - } - } - - func generate(dependencies: Set, - path _: AbsolutePath, + func generate(dependencyReferences: Set, groups: ProjectGroups, pbxproj: PBXProj, sourceRootPath: AbsolutePath, filesGroup: ProjectGroup) throws { - let sortedDependencies = dependencies.sorted(by: { $0.path < $1.path }) - try sortedDependencies.forEach { node in - switch node { - case let precompiledNode as PrecompiledNode: - let fileElement = GroupFileElement(path: precompiledNode.path, + let sortedDependencies = dependencyReferences.sorted() + try sortedDependencies.forEach { dependency in + switch dependency { + case let .absolute(dependencyPath): + let fileElement = GroupFileElement(path: dependencyPath, group: filesGroup) try generate(fileElement: fileElement, groups: groups, pbxproj: pbxproj, sourceRootPath: sourceRootPath) - case let sdkNode as SDKNode: - generateSDKFileElement(node: sdkNode, + case let .sdk(sdkNodePath, _): + generateSDKFileElement(sdkNodePath: sdkNodePath, toGroup: groups.frameworks, pbxproj: pbxproj) - case let packageNode as PackageNode: - switch packageNode.packageType { - case let .local(path: packagePath, productName: _): - let fileElement = GroupFileElement(path: sourceRootPath.appending(packagePath), - group: filesGroup) - try generate(fileElement: fileElement, - groups: groups, - pbxproj: pbxproj, - sourceRootPath: sourceRootPath) - case .remote: - // Only local packages need group, remote are handled by Xcode itself - break - } - default: - return + case let .product(target: target, productName: productName): + generateProduct(targetName: target, + productName: productName, + groups: groups, + pbxproj: pbxproj) } } } + private func generateProduct(targetName: String, + productName: String, + groups: ProjectGroups, + pbxproj: PBXProj) { + guard products[targetName] == nil else { return } + let fileType = RelativePath(productName).extension.flatMap { Xcode.filetype(extension: $0) } + let fileReference = PBXFileReference(sourceTree: .buildProductsDir, + explicitFileType: fileType, + path: productName, + includeInIndex: false) + + pbxproj.add(object: fileReference) + groups.products.children.append(fileReference) + products[targetName] = fileReference + } + func generate(fileElement: GroupFileElement, groups: ProjectGroups, pbxproj: PBXProj, @@ -469,20 +441,20 @@ class ProjectFileElements { elements[fileAbsolutePath] = file } - private func generateSDKFileElement(node: SDKNode, + private func generateSDKFileElement(sdkNodePath: AbsolutePath, toGroup: PBXGroup, pbxproj: PBXProj) { - guard sdks[node.path] == nil else { + guard sdks[sdkNodePath] == nil else { return } - addSDKElement(node: node, toGroup: toGroup, pbxproj: pbxproj) + addSDKElement(sdkNodePath: sdkNodePath, toGroup: toGroup, pbxproj: pbxproj) } - private func addSDKElement(node: SDKNode, + private func addSDKElement(sdkNodePath: AbsolutePath, toGroup: PBXGroup, pbxproj: PBXProj) { - let sdkPath = node.path.relative(to: AbsolutePath("/")) // SDK paths are relative + let sdkPath = sdkNodePath.relative(to: AbsolutePath("/")) // SDK paths are relative let lastKnownFileType = sdkPath.extension.flatMap { Xcode.filetype(extension: $0) } let file = PBXFileReference(sourceTree: .developerDir, @@ -491,7 +463,7 @@ class ProjectFileElements { path: sdkPath.pathString) pbxproj.add(object: file) toGroup.children.append(file) - sdks[node.path] = file + sdks[sdkNodePath] = file } func group(path: AbsolutePath) -> PBXGroup? { diff --git a/Sources/TuistGenerator/Graph/Graph.swift b/Sources/TuistGenerator/Graph/Graph.swift index e5327ec23..eb45c62ef 100644 --- a/Sources/TuistGenerator/Graph/Graph.swift +++ b/Sources/TuistGenerator/Graph/Graph.swift @@ -22,15 +22,16 @@ enum GraphError: FatalError { enum DependencyReference: Equatable, Comparable, Hashable { case absolute(AbsolutePath) - case product(target: String) + case product(target: String, productName: String) case sdk(AbsolutePath, SDKStatus) func hash(into hasher: inout Hasher) { switch self { case let .absolute(path): hasher.combine(path) - case let .product(target): + case let .product(target, productName): hasher.combine(target) + hasher.combine(productName) case let .sdk(path, status): hasher.combine(path) hasher.combine(status) @@ -41,8 +42,8 @@ enum DependencyReference: Equatable, Comparable, Hashable { switch (lhs, rhs) { case let (.absolute(lhsPath), .absolute(rhsPath)): return lhsPath == rhsPath - case let (.product(lhsName), .product(rhsName)): - return lhsName == rhsName + case let (.product(lhsTarget, lhsProductName), .product(rhsTarget, rhsProductName)): + return lhsTarget == rhsTarget && lhsProductName == rhsProductName case let (.sdk(lhsPath, lhsStatus), .sdk(rhsPath, rhsStatus)): return lhsPath == rhsPath && lhsStatus == rhsStatus default: @@ -54,8 +55,11 @@ enum DependencyReference: Equatable, Comparable, Hashable { switch (lhs, rhs) { case let (.absolute(lhsPath), .absolute(rhsPath)): return lhsPath < rhsPath - case let (.product(lhsName), .product(rhsName)): - return lhsName < rhsName + case let (.product(lhsTarget, lhsProductName), .product(rhsTarget, rhsProductName)): + if lhsTarget == rhsTarget { + return lhsProductName < rhsProductName + } + return lhsTarget < rhsTarget case let (.sdk(lhsPath, _), .sdk(rhsPath, _)): return lhsPath < rhsPath case (.sdk, .absolute): @@ -101,6 +105,12 @@ protocol Graphing: AnyObject, Encodable { func staticDependencies(path: AbsolutePath, name: String) -> [DependencyReference] func resourceBundleDependencies(path: AbsolutePath, name: String) -> [TargetNode] + /// Products that are added to a dummy copy files phase to enforce build order between dependencies that Xcode doesn't usually respect (e.g. Resouce Bundles) + func copyProductDependencies(path: AbsolutePath, target: Target) -> [DependencyReference] + + /// All dependency referrences expected to present within a Project + func allDependencyReferences(for project: Project, system: Systeming) throws -> [DependencyReference] + // MARK: - Depth First Search /// Depth-first search (DFS) is an algorithm for traversing graph data structures. It starts at a source node @@ -182,7 +192,7 @@ class Graph: Graphing { return targetNode.targetDependencies .filter(isStaticLibrary) - .map { DependencyReference.product(target: $0.target.name) } + .map { DependencyReference.product(target: $0.target.name, productName: $0.target.productNameWithExtension) } } func resourceBundleDependencies(path: AbsolutePath, name: String) -> [TargetNode] { @@ -241,13 +251,13 @@ class Graph: Graphing { if targetNode.target.canLinkStaticProducts() { let staticLibraryTargetNodes = findAll(targetNode: targetNode, test: isStaticLibrary, skip: isFramework) let staticLibraries = staticLibraryTargetNodes.map { - DependencyReference.product(target: $0.target.name) + DependencyReference.product(target: $0.target.name, productName: $0.target.productNameWithExtension) } let staticDependenciesDynamicLibraries = staticLibraryTargetNodes.flatMap { $0.targetDependencies .filter(or(isFramework, isDynamicLibrary)) - .map { DependencyReference.product(target: $0.target.name) } + .map { DependencyReference.product(target: $0.target.name, productName: $0.target.productNameWithExtension) } } references = references.union(staticLibraries + staticDependenciesDynamicLibraries) @@ -257,7 +267,7 @@ class Graph: Graphing { let dynamicLibrariesAndFrameworks = targetNode.targetDependencies .filter(or(isFramework, isDynamicLibrary)) - .map { DependencyReference.product(target: $0.target.name) } + .map { DependencyReference.product(target: $0.target.name, productName: $0.target.productNameWithExtension) } references = references.union(dynamicLibrariesAndFrameworks) return Array(references).sorted() @@ -321,13 +331,44 @@ class Graph: Graphing { /// Other targets' frameworks. let otherTargetFrameworks = findAll(targetNode: targetNode, test: isFramework) - .map { DependencyReference.product(target: $0.target.name) } + .map { DependencyReference.product(target: $0.target.name, productName: $0.target.productNameWithExtension) } references.append(contentsOf: otherTargetFrameworks) return Set(references).sorted() } + func copyProductDependencies(path: AbsolutePath, target: Target) -> [DependencyReference] { + var dependencies = [DependencyReference]() + + if target.product.isStatic { + dependencies.append(contentsOf: staticDependencies(path: path, name: target.name)) + } + + dependencies.append(contentsOf: + resourceBundleDependencies(path: path, name: target.name) + .map { .product(target: $0.target.name, productName: $0.target.productNameWithExtension) }) + + return Set(dependencies).sorted() + } + + func allDependencyReferences(for project: Project, system: Systeming) throws -> [DependencyReference] { + let linkableDependencies = try project.targets.flatMap { + try self.linkableDependencies(path: project.path, name: $0.name, system: system) + } + + let embeddableDependencies = try project.targets.flatMap { + try self.embeddableFrameworks(path: project.path, name: $0.name, system: system) + } + + let copyProductDependencies = project.targets.flatMap { + self.copyProductDependencies(path: project.path, target: $0) + } + + let allDepdendencies = linkableDependencies + embeddableDependencies + copyProductDependencies + return Set(allDepdendencies).sorted() + } + // MARK: - Fileprivate private func findTargetNode(path: AbsolutePath, name: String) -> TargetNode? { diff --git a/Tests/TuistGeneratorTests/Generator/LinkGeneratorTests.swift b/Tests/TuistGeneratorTests/Generator/LinkGeneratorTests.swift index 8a4d3393e..82803b0f8 100644 --- a/Tests/TuistGeneratorTests/Generator/LinkGeneratorTests.swift +++ b/Tests/TuistGeneratorTests/Generator/LinkGeneratorTests.swift @@ -27,7 +27,7 @@ final class LinkGeneratorErrorTests: XCTestCase { func test_generateEmbedPhase() throws { var dependencies: [DependencyReference] = [] dependencies.append(DependencyReference.absolute(AbsolutePath("/test.framework"))) - dependencies.append(DependencyReference.product(target: "Test")) + dependencies.append(DependencyReference.product(target: "Test", productName: "Test.framework")) let pbxproj = PBXProj() let pbxTarget = PBXNativeTarget(name: "Test") let fileElements = ProjectFileElements() @@ -58,7 +58,7 @@ final class LinkGeneratorErrorTests: XCTestCase { func test_generateEmbedPhase_throws_when_aProductIsMissing() throws { var dependencies: [DependencyReference] = [] - dependencies.append(DependencyReference.product(target: "Test")) + dependencies.append(DependencyReference.product(target: "Test", productName: "Test.framework")) let pbxproj = PBXProj() let pbxTarget = PBXNativeTarget(name: "Test") let fileElements = ProjectFileElements() @@ -218,7 +218,7 @@ final class LinkGeneratorErrorTests: XCTestCase { func test_generateLinkingPhase() throws { var dependencies: [DependencyReference] = [] dependencies.append(DependencyReference.absolute(AbsolutePath("/test.framework"))) - dependencies.append(DependencyReference.product(target: "Test")) + dependencies.append(DependencyReference.product(target: "Test", productName: "Test.framework")) let pbxproj = PBXProj() let pbxTarget = PBXNativeTarget(name: "Test") let fileElements = ProjectFileElements() @@ -260,7 +260,7 @@ final class LinkGeneratorErrorTests: XCTestCase { func test_generateLinkingPhase_throws_whenProductIsMissing() throws { var dependencies: [DependencyReference] = [] - dependencies.append(DependencyReference.product(target: "Test")) + dependencies.append(DependencyReference.product(target: "Test", productName: "Test.framework")) let pbxproj = PBXProj() let pbxTarget = PBXNativeTarget(name: "Test") let fileElements = ProjectFileElements() diff --git a/Tests/TuistGeneratorTests/Generator/ProjectFileElementsTests.swift b/Tests/TuistGeneratorTests/Generator/ProjectFileElementsTests.swift index c24134080..7df392ec8 100644 --- a/Tests/TuistGeneratorTests/Generator/ProjectFileElementsTests.swift +++ b/Tests/TuistGeneratorTests/Generator/ProjectFileElementsTests.swift @@ -215,13 +215,8 @@ final class ProjectFileElementsTests: XCTestCase { ]) } - func test_targetProducts() { - let target = Target.test() - let products = subject.targetProducts(target: target).sorted() - XCTAssertEqual(products.first, "Target.app") - } - - func test_targetFiles() { + func test_targetFiles() throws { + // Given let sourceRootPath = AbsolutePath("/a/project/") let settings = Settings.test( @@ -250,7 +245,10 @@ final class ProjectFileElementsTests: XCTestCase { project: [AbsolutePath("/project/project.h")]), dependencies: []) - let files = subject.targetFiles(target: target, sourceRootPath: sourceRootPath) + // When + let files = try subject.targetFiles(target: target, projectPath: sourceRootPath, graph: Graph.test()) + + // Then XCTAssertTrue(files.isSuperset(of: [ GroupFileElement(path: "/project/debug.xcconfig", group: target.filesGroup), GroupFileElement(path: "/project/release.xcconfig", group: target.filesGroup), @@ -266,130 +264,92 @@ final class ProjectFileElementsTests: XCTestCase { } func test_generateProduct() throws { - let pbxproj = PBXProj() - let project = Project.test() - let sourceRootPath = AbsolutePath("/a/project/") - let groups = ProjectGroups.generate(project: project, - pbxproj: pbxproj, - sourceRootPath: sourceRootPath) - try subject.generateProducts(project: project, - dependencies: [], - groups: groups, - pbxproj: pbxproj) - XCTAssertEqual(groups.products.children.count, 1) - let fileReference = subject.product(target: "Target") - XCTAssertNotNil(fileReference) - XCTAssertEqual(fileReference?.sourceTree, .buildProductsDir) - XCTAssertEqual(fileReference?.path, "Target.app") - XCTAssertNil(fileReference?.name) - XCTAssertEqual(fileReference?.includeInIndex, false) - } - - func test_generateProducts_secondTime() throws { // Given - let project = Project.test() - let sourceRootPath = AbsolutePath("/a/project/") + let pbxproj = PBXProj() + let project = Project.test(targets: [ + .test(name: "App", product: .app), + .test(name: "Framework", product: .framework), + .test(name: "Library", product: .staticLibrary), + ]) + let graph = Graph.test() let groups = ProjectGroups.generate(project: project, pbxproj: pbxproj, - sourceRootPath: sourceRootPath) - try subject.generateProducts(project: project, - dependencies: [], - groups: groups, - pbxproj: pbxproj) - let products = groups.products.children + sourceRootPath: project.path) // When - try subject.generateProducts(project: project, - dependencies: [], - groups: groups, - pbxproj: pbxproj) + try subject.generateProjectFiles(project: project, + graph: graph, + groups: groups, + pbxproj: pbxproj, + sourceRootPath: project.path) // Then - XCTAssertEqual(groups.products.children, products) // we don't get duplicates + XCTAssertEqual(groups.products.flattenedChildren, [ + "App.app", + "Framework.framework", + "libLibrary.a", + ]) } func test_generateProducts_stableOrder() throws { for _ in 0 ..< 5 { - // Given - let subject = ProjectFileElements(playgrounds: playgrounds) let pbxproj = PBXProj() - let project = Project.test() - let sourceRootPath = AbsolutePath("/a/project/") + let subject = ProjectFileElements() + let targets: [Target] = [ + .test(name: "App1", product: .app), + .test(name: "App2", product: .app), + .test(name: "Framework1", product: .framework), + .test(name: "Framework2", product: .framework), + .test(name: "Library1", product: .staticLibrary), + .test(name: "Library2", product: .staticLibrary), + ].shuffled() + + let project = Project.test(targets: targets) + let graph = Graph.test() let groups = ProjectGroups.generate(project: project, pbxproj: pbxproj, - sourceRootPath: sourceRootPath) - let dependencies: Set = Set((1 ... 5).map { - let target = Target.test(name: "Target\($0)", product: .framework) - return TargetNode(project: project, - target: target, - dependencies: []) - }) + sourceRootPath: project.path) // When - try subject.generateProducts(project: project, - dependencies: dependencies, - groups: groups, - pbxproj: pbxproj) + try subject.generateProjectFiles(project: project, + graph: graph, + groups: groups, + pbxproj: pbxproj, + sourceRootPath: project.path) // Then - let expected = ["Target.app", - "Target1.framework", - "Target2.framework", - "Target3.framework", - "Target4.framework", - "Target5.framework"] - XCTAssertEqual(groups.products.children.map { $0.path }, expected) + XCTAssertEqual(groups.products.flattenedChildren, [ + "App1.app", + "App2.app", + "Framework1.framework", + "Framework2.framework", + "libLibrary1.a", + "libLibrary2.a", + ]) } } - func test_generateDependencies_whenTargetNode_thatHasAlreadyBeenAdded() throws { + func test_generateProduct_fileReferencesProperties() throws { + // Given let pbxproj = PBXProj() - let sourceRootPath = AbsolutePath("/a/project/") - let path = AbsolutePath("/test") - let target = Target.test() - let project = Project.test(path: path, targets: [target]) + let project = Project.test(targets: [ + .test(name: "App", product: .app), + ]) + let graph = Graph.test() let groups = ProjectGroups.generate(project: project, pbxproj: pbxproj, - sourceRootPath: sourceRootPath) - var dependencies: Set = Set() - let targetNode = TargetNode(project: project, - target: target, - dependencies: []) - dependencies.insert(targetNode) + sourceRootPath: project.path) - try subject.generate(dependencies: dependencies, - path: path, - groups: groups, - pbxproj: pbxproj, - sourceRootPath: sourceRootPath, - filesGroup: .group(name: "Project")) + // When + try subject.generateProjectFiles(project: project, + graph: graph, + groups: groups, + pbxproj: pbxproj, + sourceRootPath: project.path) - XCTAssertEqual(groups.products.children.count, 0) - } - - func test_generateDependencies_whenTargetNode() throws { - let pbxproj = PBXProj() - let sourceRootPath = AbsolutePath("/a/project/") - let path = AbsolutePath("/test") - let target = Target.test() - let project = Project.test(path: AbsolutePath("/waka"), targets: [target]) - let groups = ProjectGroups.generate(project: project, - pbxproj: pbxproj, - sourceRootPath: sourceRootPath) - var dependencies: Set = Set() - let targetNode = TargetNode(project: project, - target: target, - dependencies: []) - dependencies.insert(targetNode) - - try subject.generate(dependencies: dependencies, - path: path, - groups: groups, - pbxproj: pbxproj, - sourceRootPath: sourceRootPath, - filesGroup: .group(name: "Project")) - - XCTAssertTrue(groups.products.children.isEmpty) + // Then + let fileReference = subject.product(target: "App") + XCTAssertEqual(fileReference?.sourceTree, .buildProductsDir) } func test_generateDependencies_whenPrecompiledNode() throws { @@ -402,16 +362,15 @@ final class ProjectFileElementsTests: XCTestCase { let groups = ProjectGroups.generate(project: project, pbxproj: pbxproj, sourceRootPath: sourceRootPath) - var dependencies: Set = Set() - let precompiledNode = FrameworkNode(path: project.path.appending(component: "waka.framework")) + var dependencies: Set = Set() + let precompiledNode = DependencyReference.absolute(project.path.appending(component: "waka.framework")) dependencies.insert(precompiledNode) - try subject.generate(dependencies: dependencies, - path: project.path, + try subject.generate(dependencyReferences: dependencies, groups: groups, pbxproj: pbxproj, sourceRootPath: sourceRootPath, - filesGroup: .group(name: "Project")) + filesGroup: project.filesGroup) let fileReference = groups.main.group(named: projectGroupName)?.children.first as? PBXFileReference XCTAssertEqual(fileReference?.path, "waka.framework") @@ -605,10 +564,10 @@ final class ProjectFileElementsTests: XCTestCase { let sdk = try SDKNode(name: "ARKit.framework", platform: .iOS, status: .required) + let sdkDependency = DependencyReference.sdk(sdk.path, sdk.status) // When - try subject.generate(dependencies: [sdk], - path: sourceRootPath, + try subject.generate(dependencyReferences: [sdkDependency], groups: groups, pbxproj: pbxproj, sourceRootPath: sourceRootPath, filesGroup: .group(name: "Project")) @@ -628,21 +587,24 @@ final class ProjectFileElementsTests: XCTestCase { func test_generateDependencies_localSwiftPackage() throws { // Given let pbxproj = PBXProj() - let sourceRootPath = AbsolutePath("/a/project/") + let target = Target.empty(name: "TargetA") + let project = Project.empty(path: "/a/project", + targets: [target]) let groups = ProjectGroups.generate(project: .test(), pbxproj: pbxproj, - sourceRootPath: sourceRootPath) + sourceRootPath: project.path) let package = PackageNode(packageType: .local(path: RelativePath("packages/A"), productName: "A"), path: "/a/project/packages/A") + let graph = createGraph(project: project, target: target, dependencies: [package]) // When - try subject.generate(dependencies: [package], - path: sourceRootPath, - groups: groups, pbxproj: pbxproj, - sourceRootPath: sourceRootPath, - filesGroup: .group(name: "Project")) + try subject.generateProjectFiles(project: project, + graph: graph, + groups: groups, + pbxproj: pbxproj, + sourceRootPath: project.path) // Then let projectGroup = groups.main.group(named: "Project") @@ -654,27 +616,40 @@ final class ProjectFileElementsTests: XCTestCase { func test_generateDependencies_remoteSwiftPackage_doNotGenerateElements() throws { // Given let pbxproj = PBXProj() - let sourceRootPath = AbsolutePath("/a/project/") + let target = Target.empty(name: "TargetA") + let project = Project.empty(path: "/a/project", + targets: [target]) let groups = ProjectGroups.generate(project: .test(), pbxproj: pbxproj, - sourceRootPath: sourceRootPath) + sourceRootPath: project.path) let package = PackageNode(packageType: .remote(url: "url", productName: "A", versionRequirement: .branch("master")), path: "/packages/url") + let graph = createGraph(project: project, target: target, dependencies: [package]) + // When - try subject.generate(dependencies: [package], - path: sourceRootPath, - groups: groups, pbxproj: pbxproj, - sourceRootPath: sourceRootPath, - filesGroup: .group(name: "Project")) + try subject.generateProjectFiles(project: project, + graph: graph, + groups: groups, + pbxproj: pbxproj, + sourceRootPath: project.path) // Then let projectGroup = groups.main.group(named: "Project") XCTAssertEqual(projectGroup?.flattenedChildren, []) } + + // MARK: - + + private func createGraph(project: Project, target: Target, dependencies: [GraphNode]) -> Graph { + let targetNode = TargetNode(project: project, target: target, dependencies: dependencies) + let cache = GraphLoaderCache() + cache.add(targetNode: targetNode) + return Graph.test(cache: cache) + } } private extension PBXGroup { diff --git a/Tests/TuistGeneratorTests/Graph/GraphTests.swift b/Tests/TuistGeneratorTests/Graph/GraphTests.swift index 8057a84e1..d4feded01 100644 --- a/Tests/TuistGeneratorTests/Graph/GraphTests.swift +++ b/Tests/TuistGeneratorTests/Graph/GraphTests.swift @@ -88,7 +88,7 @@ final class GraphTests: XCTestCase { let got = try graph.linkableDependencies(path: project.path, name: target.name, system: system) - XCTAssertEqual(got.first, .product(target: "Dependency")) + XCTAssertEqual(got.first, .product(target: "Dependency", productName: "libDependency.a")) } func test_linkableDependencies_whenAFrameworkTarget() throws { @@ -117,14 +117,14 @@ final class GraphTests: XCTestCase { name: target.name, system: system) XCTAssertEqual(got.count, 1) - XCTAssertEqual(got.first, .product(target: "Dependency")) + XCTAssertEqual(got.first, .product(target: "Dependency", productName: "Dependency.framework")) let frameworkGot = try graph.linkableDependencies(path: project.path, name: dependency.name, system: system) XCTAssertEqual(frameworkGot.count, 1) - XCTAssertTrue(frameworkGot.contains(.product(target: "StaticDependency"))) + XCTAssertTrue(frameworkGot.contains(.product(target: "StaticDependency", productName: "libStaticDependency.a"))) } func test_linkableDependencies_transitiveDynamicLibrariesOneStaticHop() throws { @@ -151,8 +151,8 @@ final class GraphTests: XCTestCase { let result = try graph.linkableDependencies(path: projectA.path, name: app.name, system: system) // Then - XCTAssertEqual(result, [DependencyReference.product(target: "DynamicFramework"), - DependencyReference.product(target: "StaticFramework")]) + XCTAssertEqual(result, [DependencyReference.product(target: "DynamicFramework", productName: "DynamicFramework.framework"), + DependencyReference.product(target: "StaticFramework", productName: "StaticFramework.framework")]) } func test_linkableDependencies_transitiveDynamicLibrariesThreeHops() throws { @@ -188,10 +188,14 @@ final class GraphTests: XCTestCase { let dynamicFramework1Result = try graph.linkableDependencies(path: projectA.path, name: dynamicFramework1.name, system: system) // Then - XCTAssertEqual(appResult, [DependencyReference.product(target: "DynamicFramework1")]) - XCTAssertEqual(dynamicFramework1Result, [DependencyReference.product(target: "DynamicFramework2"), - DependencyReference.product(target: "StaticFramework1"), - DependencyReference.product(target: "StaticFramework2")]) + XCTAssertEqual(appResult, [ + DependencyReference.product(target: "DynamicFramework1", productName: "DynamicFramework1.framework"), + ]) + XCTAssertEqual(dynamicFramework1Result, [ + DependencyReference.product(target: "DynamicFramework2", productName: "DynamicFramework2.framework"), + DependencyReference.product(target: "StaticFramework1", productName: "libStaticFramework1.a"), + DependencyReference.product(target: "StaticFramework2", productName: "libStaticFramework2.a"), + ]) } func test_linkableDependencies_transitiveDynamicLibrariesCheckNoDuplicatesInParentDynamic() throws { @@ -230,7 +234,7 @@ final class GraphTests: XCTestCase { let dynamicFramework1Result = try graph.linkableDependencies(path: projectA.path, name: dynamicFramework1.name, system: system) // Then - XCTAssertEqual(dynamicFramework1Result, [DependencyReference.product(target: "DynamicFramework2")]) + XCTAssertEqual(dynamicFramework1Result, [DependencyReference.product(target: "DynamicFramework2", productName: "DynamicFramework2.framework")]) } func test_linkableDependencies_transitiveSDKDependenciesStatic() throws { @@ -421,7 +425,7 @@ final class GraphTests: XCTestCase { let got = try graph.embeddableFrameworks(path: project.path, name: target.name, system: system) - XCTAssertEqual(got.first, DependencyReference.product(target: "Dependency")) + XCTAssertEqual(got.first, DependencyReference.product(target: "Dependency", productName: "Dependency.framework")) } func test_embeddableFrameworks_when_dependencyIsAFramework() throws { @@ -478,7 +482,7 @@ final class GraphTests: XCTestCase { ) XCTAssertEqual(got, [ - DependencyReference.product(target: "Dependency"), + DependencyReference.product(target: "Dependency", productName: "Dependency.framework"), DependencyReference.absolute(frameworkPath), ]) } @@ -535,7 +539,7 @@ final class GraphTests: XCTestCase { system: system) // Then - let expected = dependencyNames.sorted().map { DependencyReference.product(target: $0) } + let expected = dependencyNames.sorted().map { DependencyReference.product(target: $0, productName: "\($0).framework") } XCTAssertEqual(got, expected) } @@ -719,18 +723,18 @@ final class DependencyReferenceTests: XCTestCase { let subjects: [(DependencyReference, DependencyReference, Bool)] = [ // Absolute (.absolute(.init("/a.framework")), .absolute(.init("/a.framework")), true), - (.absolute(.init("/a.framework")), .product(target: "Main"), false), + (.absolute(.init("/a.framework")), .product(target: "Main", productName: "Main.app"), false), (.absolute(.init("/a.framework")), .sdk(.init("/CoreData.framework"), .required), false), // Product - (.product(target: "Main"), .product(target: "Main"), true), - (.product(target: "Main"), .absolute(.init("/a.framework")), false), - (.product(target: "Main"), .sdk(.init("/CoreData.framework"), .required), false), - (.product(target: "Main-iOS"), .product(target: "Main-macOS"), false), + (.product(target: "Main", productName: "Main.app"), .product(target: "Main", productName: "Main.app"), true), + (.product(target: "Main", productName: "Main.app"), .absolute(.init("/a.framework")), false), + (.product(target: "Main", productName: "Main.app"), .sdk(.init("/CoreData.framework"), .required), false), + (.product(target: "Main-iOS", productName: "Main.app"), .product(target: "Main-macOS", productName: "Main.app"), false), // SDK (.sdk(.init("/CoreData.framework"), .required), .sdk(.init("/CoreData.framework"), .required), true), - (.sdk(.init("/CoreData.framework"), .required), .product(target: "Main"), false), + (.sdk(.init("/CoreData.framework"), .required), .product(target: "Main", productName: "Main.app"), false), (.sdk(.init("/CoreData.framework"), .required), .absolute(.init("/a.framework")), false), ] @@ -742,17 +746,37 @@ final class DependencyReferenceTests: XCTestCase { XCTAssertTrue(DependencyReference.absolute("/A") < .absolute("/B")) XCTAssertFalse(DependencyReference.absolute("/B") < .absolute("/A")) - XCTAssertFalse(DependencyReference.product(target: "A") < .product(target: "A")) - XCTAssertTrue(DependencyReference.product(target: "A") < .product(target: "B")) - XCTAssertFalse(DependencyReference.product(target: "B") < .product(target: "A")) + XCTAssertFalse(DependencyReference.product(target: "A", productName: "A.framework") < .product(target: "A", productName: "A.framework")) + XCTAssertTrue(DependencyReference.product(target: "A", productName: "A.framework") < .product(target: "B", productName: "B.framework")) + XCTAssertFalse(DependencyReference.product(target: "B", productName: "B.framework") < .product(target: "A", productName: "A.framework")) + XCTAssertTrue(DependencyReference.product(target: "A", productName: "A.app") < .product(target: "A", productName: "A.framework")) - XCTAssertTrue(DependencyReference.product(target: "/A") < .absolute("/A")) - XCTAssertTrue(DependencyReference.product(target: "/A") < .absolute("/B")) - XCTAssertTrue(DependencyReference.product(target: "/B") < .absolute("/A")) + XCTAssertTrue(DependencyReference.product(target: "/A", productName: "A.framework") < .absolute("/A")) + XCTAssertTrue(DependencyReference.product(target: "/A", productName: "A.framework") < .absolute("/B")) + XCTAssertTrue(DependencyReference.product(target: "/B", productName: "B.framework") < .absolute("/A")) - XCTAssertFalse(DependencyReference.absolute("/A") < .product(target: "/A")) - XCTAssertFalse(DependencyReference.absolute("/A") < .product(target: "/B")) - XCTAssertFalse(DependencyReference.absolute("/B") < .product(target: "/A")) + XCTAssertFalse(DependencyReference.absolute("/A") < .product(target: "/A", productName: "A.framework")) + XCTAssertFalse(DependencyReference.absolute("/A") < .product(target: "/B", productName: "B.framework")) + XCTAssertFalse(DependencyReference.absolute("/B") < .product(target: "/A", productName: "A.framework")) + } + + func test_compare_isStable() { + // Given + let subject: [DependencyReference] = [ + .absolute("/A"), + .absolute("/B"), + .product(target: "A", productName: "A.framework"), + .product(target: "B", productName: "B.framework"), + .sdk("/A.framework", .required), + .sdk("/B.framework", .optional), + ] + + // When + let sorted = (0 ..< 10).map { _ in subject.shuffled().sorted() } + + // Then + let unstable = sorted.dropFirst().filter { $0 != sorted.first } + XCTAssertTrue(unstable.isEmpty) } } diff --git a/fixtures/README.md b/fixtures/README.md index 15e6858df..07de69385 100644 --- a/fixtures/README.md +++ b/fixtures/README.md @@ -46,6 +46,12 @@ Workspace: - Framework2: - Framework2 (dynamic iOS framework) - Framework2Tests (iOS unit tests) + - Framework3: + - Framework3 (dynamic iOS framework) + - Framework4: + - Framework4 (dynamic iOS framework) + - Framework5: + - Framework5 (dynamic iOS framework) ``` Dependencies: @@ -53,6 +59,9 @@ Dependencies: - App -> Framework1 - App -> Framework2 - Framework1 -> Framework2 +- Framework2 -> Framework3 +- Framework3 -> Framework4 +- Framework4 -> Framework5 ## ios_app_with_framework_and_resources diff --git a/fixtures/ios_app_with_frameworks/Framework2/Project.swift b/fixtures/ios_app_with_frameworks/Framework2/Project.swift index c87d2ef9a..9777332e0 100644 --- a/fixtures/ios_app_with_frameworks/Framework2/Project.swift +++ b/fixtures/ios_app_with_frameworks/Framework2/Project.swift @@ -12,7 +12,9 @@ let project = Project(name: "Framework2", headers: Headers(public: "Sources/Public/**", private: "Sources/Private/**", project: "Sources/Project/**"), - dependencies: []), + dependencies: [ + .project(target: "Framework3", path: "../Framework3") + ]), Target(name: "Framework2-macOS", platform: .macOS, product: .framework, diff --git a/fixtures/ios_app_with_frameworks/Framework3/Config/Framework3-Info.plist b/fixtures/ios_app_with_frameworks/Framework3/Config/Framework3-Info.plist new file mode 100644 index 000000000..3c29058d4 --- /dev/null +++ b/fixtures/ios_app_with_frameworks/Framework3/Config/Framework3-Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright Tuist©. All rights reserved. + + diff --git a/fixtures/ios_app_with_frameworks/Framework3/Project.swift b/fixtures/ios_app_with_frameworks/Framework3/Project.swift new file mode 100644 index 000000000..88b9c73f7 --- /dev/null +++ b/fixtures/ios_app_with_frameworks/Framework3/Project.swift @@ -0,0 +1,16 @@ +import ProjectDescription + +let project = Project( + name: "Framework3", + targets: [ + Target(name: "Framework3", + platform: .iOS, + product: .framework, + bundleId: "io.tuist.Framework3", + infoPlist: "Config/Framework3-Info.plist", + sources: "Sources/**", + dependencies: [ + .project(target: "Framework4", path: "../Framework4") + ]), + ] +) diff --git a/fixtures/ios_app_with_frameworks/Framework3/Sources/Framework3File.swift b/fixtures/ios_app_with_frameworks/Framework3/Sources/Framework3File.swift new file mode 100644 index 000000000..306dab881 --- /dev/null +++ b/fixtures/ios_app_with_frameworks/Framework3/Sources/Framework3File.swift @@ -0,0 +1,9 @@ +import Foundation + +public class Framework3File { + public init() {} + + public func hello() -> String { + return "Framework3File.hello()" + } +} diff --git a/fixtures/ios_app_with_frameworks/Framework4/Config/Framework4-Info.plist b/fixtures/ios_app_with_frameworks/Framework4/Config/Framework4-Info.plist new file mode 100644 index 000000000..3c29058d4 --- /dev/null +++ b/fixtures/ios_app_with_frameworks/Framework4/Config/Framework4-Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright Tuist©. All rights reserved. + + diff --git a/fixtures/ios_app_with_frameworks/Framework4/Project.swift b/fixtures/ios_app_with_frameworks/Framework4/Project.swift new file mode 100644 index 000000000..cdd7254d7 --- /dev/null +++ b/fixtures/ios_app_with_frameworks/Framework4/Project.swift @@ -0,0 +1,16 @@ +import ProjectDescription + +let project = Project( + name: "Framework4", + targets: [ + Target(name: "Framework4", + platform: .iOS, + product: .framework, + bundleId: "io.tuist.Framework4", + infoPlist: "Config/Framework4-Info.plist", + sources: "Sources/**", + dependencies: [ + .project(target: "Framework5", path: "../Framework5") + ]), + ] +) diff --git a/fixtures/ios_app_with_frameworks/Framework4/Sources/Framework4File.swift b/fixtures/ios_app_with_frameworks/Framework4/Sources/Framework4File.swift new file mode 100644 index 000000000..4bbd52133 --- /dev/null +++ b/fixtures/ios_app_with_frameworks/Framework4/Sources/Framework4File.swift @@ -0,0 +1,9 @@ +import Foundation + +public class Framework4File { + public init() {} + + public func hello() -> String { + return "Framework4File.hello()" + } +} diff --git a/fixtures/ios_app_with_frameworks/Framework5/Config/Framework5-Info.plist b/fixtures/ios_app_with_frameworks/Framework5/Config/Framework5-Info.plist new file mode 100644 index 000000000..3c29058d4 --- /dev/null +++ b/fixtures/ios_app_with_frameworks/Framework5/Config/Framework5-Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright Tuist©. All rights reserved. + + diff --git a/fixtures/ios_app_with_frameworks/Framework5/Project.swift b/fixtures/ios_app_with_frameworks/Framework5/Project.swift new file mode 100644 index 000000000..675d4bbd6 --- /dev/null +++ b/fixtures/ios_app_with_frameworks/Framework5/Project.swift @@ -0,0 +1,16 @@ +import ProjectDescription + +let project = Project( + name: "Framework5", + targets: [ + Target(name: "Framework5", + platform: .iOS, + product: .framework, + bundleId: "io.tuist.Framework5", + infoPlist: "Config/Framework5-Info.plist", + sources: "Sources/**", + dependencies: [ + .sdk(name: "ARKit.framework") + ]), + ] +) diff --git a/fixtures/ios_app_with_frameworks/Framework5/Sources/Framework5File.swift b/fixtures/ios_app_with_frameworks/Framework5/Sources/Framework5File.swift new file mode 100644 index 000000000..a817732f4 --- /dev/null +++ b/fixtures/ios_app_with_frameworks/Framework5/Sources/Framework5File.swift @@ -0,0 +1,9 @@ +import Foundation + +public class Framework5File { + public init() {} + + public func hello() -> String { + return "Framework5File.hello()" + } +} diff --git a/fixtures/ios_app_with_local_swift_package/Frameworks/FrameworkA/Config/FrameworkA-Info.plist b/fixtures/ios_app_with_local_swift_package/Frameworks/FrameworkA/Config/FrameworkA-Info.plist new file mode 100644 index 000000000..3c29058d4 --- /dev/null +++ b/fixtures/ios_app_with_local_swift_package/Frameworks/FrameworkA/Config/FrameworkA-Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright Tuist©. All rights reserved. + + diff --git a/fixtures/ios_app_with_local_swift_package/Frameworks/FrameworkA/Project.swift b/fixtures/ios_app_with_local_swift_package/Frameworks/FrameworkA/Project.swift new file mode 100644 index 000000000..9e9d1e9d1 --- /dev/null +++ b/fixtures/ios_app_with_local_swift_package/Frameworks/FrameworkA/Project.swift @@ -0,0 +1,16 @@ +import ProjectDescription + +let project = Project( + name: "FrameworkA", + targets: [ + Target(name: "FrameworkA", + platform: .iOS, + product: .staticFramework, + bundleId: "io.tuist.FrameworkA", + infoPlist: "Config/FrameworkA-Info.plist", + sources: "Sources/**", + dependencies: [ + .package(path: "../../Packages/PackageA", productName: "LibraryA"), + ]), + ] +) diff --git a/fixtures/ios_app_with_local_swift_package/Frameworks/FrameworkA/Sources/FrameworkAClass.swift b/fixtures/ios_app_with_local_swift_package/Frameworks/FrameworkA/Sources/FrameworkAClass.swift new file mode 100644 index 000000000..311c24e94 --- /dev/null +++ b/fixtures/ios_app_with_local_swift_package/Frameworks/FrameworkA/Sources/FrameworkAClass.swift @@ -0,0 +1,10 @@ +import Foundation +import LibraryA + +public class FrameworkAClass { + public let text: String + public init() { + let libraryAClass = LibraryAClass() + text = "FrameworkAClass::\(libraryAClass.text)" + } +} diff --git a/fixtures/ios_app_with_local_swift_package/Project.swift b/fixtures/ios_app_with_local_swift_package/Project.swift index e89f291dc..15070b14c 100644 --- a/fixtures/ios_app_with_local_swift_package/Project.swift +++ b/fixtures/ios_app_with_local_swift_package/Project.swift @@ -13,6 +13,7 @@ let project = Project(name: "App", // "Resources/**" ], dependencies: [ + .project(target: "FrameworkA", path: "Frameworks/FrameworkA"), .package(path: "Packages/PackageA", productName: "LibraryA"), .package(path: "Packages/PackageA", productName: "LibraryB"), ]), diff --git a/fixtures/ios_app_with_local_swift_package/Sources/AppDelegate.swift b/fixtures/ios_app_with_local_swift_package/Sources/AppDelegate.swift index 140ce0907..5f8975729 100644 --- a/fixtures/ios_app_with_local_swift_package/Sources/AppDelegate.swift +++ b/fixtures/ios_app_with_local_swift_package/Sources/AppDelegate.swift @@ -1,6 +1,7 @@ import UIKit import LibraryA import LibraryB +import FrameworkA @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -12,6 +13,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil ) -> Bool { + useFrameworkCode() usePackageCode() window = UIWindow(frame: UIScreen.main.bounds) @@ -22,8 +24,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } + private func useFrameworkCode() { + print(FrameworkAClass().text) + } + private func usePackageCode() { print(LibraryAClass().text) print(LibraryBClass().text) } -} \ No newline at end of file +}