diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab781a04..112b0c8cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/ ### Fixed +- Fix `tuist build` building a wrong workspace [#1427](https://github.com/tuist/tuist/pull/1427) by [@fortmarek](https://github.com/fortmarek) - `tuist edit` always creates a project in a new temp dir [#1424](https://github.com/tuist/tuist/pull/1424) by [@fortmarek](https://github.com/fortmarek) - Fix `tuist init` and `tuist scaffold` with new ArgumentParser version [#1425](https://github.com/tuist/tuist/pull/1425) by [@fortmarek](https://github.com/fortmarek) - `--clean` argument ot the build command [#1421](https://github.com/tuist/tuist/pull/1421) by [@pepibumur](https://github.com/pepibumur) diff --git a/Sources/TuistAutomation/Utilities/BuildGraphInspector.swift b/Sources/TuistAutomation/Utilities/BuildGraphInspector.swift index b5825b23e..b289c87e9 100644 --- a/Sources/TuistAutomation/Utilities/BuildGraphInspector.swift +++ b/Sources/TuistAutomation/Utilities/BuildGraphInspector.swift @@ -1,6 +1,7 @@ import Foundation import TSCBasic import TuistCore +import TuistSupport public protocol BuildGraphInspecting { /// Returns the build arguments to be used with the given target. @@ -10,7 +11,7 @@ public protocol BuildGraphInspecting { /// Given a directory, it returns the first .xcworkspace found. /// - Parameter path: Found .xcworkspace. - func workspacePath(directory: AbsolutePath) -> AbsolutePath? + func workspacePath(directory: AbsolutePath) throws -> AbsolutePath? /// From the list of buildable targets of the given scheme, it returns the first one. /// - Parameters: @@ -62,7 +63,13 @@ public class BuildGraphInspector: BuildGraphInspecting { .sorted(by: { $0.name < $1.name }) } - public func workspacePath(directory: AbsolutePath) -> AbsolutePath? { - directory.glob("**/*.xcworkspace").first + public func workspacePath(directory: AbsolutePath) throws -> AbsolutePath? { + try directory.glob("**/*.xcworkspace") + .filter { + try FileHandler.shared.contentsOfDirectory($0) + .map(\.basename) + .contains(Constants.tuistGeneratedFileName) + } + .first } } diff --git a/Sources/TuistGenerator/Generator/WorkspaceGenerator.swift b/Sources/TuistGenerator/Generator/WorkspaceGenerator.swift index 5793dc2ef..eb89dbb5d 100644 --- a/Sources/TuistGenerator/Generator/WorkspaceGenerator.swift +++ b/Sources/TuistGenerator/Generator/WorkspaceGenerator.swift @@ -123,12 +123,14 @@ final class WorkspaceGenerator: WorkspaceGenerating { generatedProjects: generatedProjects, graph: graph) - return WorkspaceDescriptor(path: path, - xcworkspacePath: workspacePath, - xcworkspace: xcWorkspace, - projectDescriptors: projects, - schemeDescriptors: schemes, - sideEffectDescriptors: []) + return WorkspaceDescriptor( + path: path, + xcworkspacePath: workspacePath, + xcworkspace: xcWorkspace, + projectDescriptors: projects, + schemeDescriptors: schemes, + sideEffectDescriptors: [] + ) } /// Create a XCWorkspaceDataElement.file from a path string. diff --git a/Sources/TuistKit/ProjectGenerator/ProjectGenerator.swift b/Sources/TuistKit/ProjectGenerator/ProjectGenerator.swift index cad96abc8..c4962b76c 100644 --- a/Sources/TuistKit/ProjectGenerator/ProjectGenerator.swift +++ b/Sources/TuistKit/ProjectGenerator/ProjectGenerator.swift @@ -124,7 +124,7 @@ class ProjectGenerator: ProjectGenerating { private func generateProjectWorkspace(path: AbsolutePath) throws -> (AbsolutePath, Graph) { // Load - let (project, graph, sideEffects) = try loadProject(path: path) + let (project, graph, sideEffects) = try loadProjectWorkspace(path: path) // Lint try lint(graph: graph) @@ -191,6 +191,23 @@ class ProjectGenerator: ProjectGenerating { return (project, updatedGraph, modelMapperSideEffects + graphMapperSideEffects) } + private func loadProjectWorkspace(path: AbsolutePath) throws -> (Project, Graph, [SideEffectDescriptor]) { + // Load project + let (project, graph, sideEffects) = try loadProject(path: path) + + /// Used to disambiguate which workspaces are generated by tuist and which are not + let tuistGeneratedFileDescriptor = FileDescriptor( + path: project.path.appending(components: "\(project.name).xcworkspace", Constants.tuistGeneratedFileName) + ) + + /// Additional side effects + let additionalSideEffects: [SideEffectDescriptor] = [ + .file(tuistGeneratedFileDescriptor), + ] + + return (project, graph, sideEffects + additionalSideEffects) + } + private func loadWorkspace(path: AbsolutePath) throws -> (Workspace, Graph, [SideEffectDescriptor]) { // Load all manifests let manifests = try recursiveManifestLoader.loadWorkspace(at: path) @@ -220,7 +237,17 @@ class ProjectGenerator: ProjectGenerating { // Apply graph mappers let (updatedGraph, graphMapperSideEffects) = try graphMapperProvider.mapper(config: config).map(graph: graph) - return (workspace, updatedGraph, modelMapperSideEffects + graphMapperSideEffects) + /// Used to disambiguate which workspaces are generated by tuist and which are not + let tuistGeneratedFileDescriptor = FileDescriptor( + path: workspace.path.appending(components: "\(workspace.name).xcworkspace", Constants.tuistGeneratedFileName) + ) + + /// Additional side effects + let additionalSideEffects: [SideEffectDescriptor] = [ + .file(tuistGeneratedFileDescriptor), + ] + + return (workspace, updatedGraph, modelMapperSideEffects + graphMapperSideEffects + additionalSideEffects) } private func convert(manifests: LoadedProjects, diff --git a/Sources/TuistKit/Services/BuildService.swift b/Sources/TuistKit/Services/BuildService.swift index 6a0d7c868..0715097de 100644 --- a/Sources/TuistKit/Services/BuildService.swift +++ b/Sources/TuistKit/Services/BuildService.swift @@ -50,7 +50,7 @@ final class BuildService { func run(schemeName: String?, generate: Bool, clean: Bool, configuration: String?, path: AbsolutePath) throws { let graph: Graph - if generate || buildGraphInspector.workspacePath(directory: path) == nil { + if try (generate || buildGraphInspector.workspacePath(directory: path) == nil) { graph = try projectGenerator.generateWithGraph(path: path, projectOnly: false).1 } else { graph = try projectGenerator.load(path: path) @@ -82,7 +82,7 @@ final class BuildService { guard let buildableTarget = buildGraphInspector.buildableTarget(scheme: scheme, graph: graph) else { throw BuildServiceError.schemeWithoutBuildableTargets(scheme: scheme.name) } - let workspacePath = buildGraphInspector.workspacePath(directory: path)! + let workspacePath = try buildGraphInspector.workspacePath(directory: path)! _ = try xcodebuildController.build(.workspace(workspacePath), scheme: scheme.name, clean: clean, diff --git a/Sources/TuistSupport/Constants.swift b/Sources/TuistSupport/Constants.swift index 7d1c20346..beea78aa1 100644 --- a/Sources/TuistSupport/Constants.swift +++ b/Sources/TuistSupport/Constants.swift @@ -15,6 +15,7 @@ public struct Constants { public static let templatesDirectoryName: String = "Templates" public static let twitterHandle: String = "tuistio" public static let joinSlackURL: String = "https://slack.tuist.io/" + public static let tuistGeneratedFileName = ".tuist-generated" public struct EnvironmentVariables { public static let colouredOutput = "TUIST_COLOURED_OUTPUT" diff --git a/Tests/TuistAutomationTests/Utilities/BuildGraphInspectorTests.swift b/Tests/TuistAutomationTests/Utilities/BuildGraphInspectorTests.swift index eeeae6cb0..ce8693f1b 100644 --- a/Tests/TuistAutomationTests/Utilities/BuildGraphInspectorTests.swift +++ b/Tests/TuistAutomationTests/Utilities/BuildGraphInspectorTests.swift @@ -166,10 +166,40 @@ final class BuildGraphInspectorTests: TuistUnitTestCase { // Given let path = try temporaryPath() let workspacePath = path.appending(component: "App.xcworkspace") - try FileHandler.shared.touch(workspacePath) + try FileHandler.shared.createFolder(workspacePath) + try FileHandler.shared.touch(workspacePath.appending(component: Constants.tuistGeneratedFileName)) // When - let got = subject.workspacePath(directory: path) + let got = try subject.workspacePath(directory: path) + + // Then + XCTAssertEqual(got, workspacePath) + } + + func test_workspacePath_when_no_tuist_workspace_is_present() throws { + // Given + let path = try temporaryPath() + let workspacePath = path.appending(component: "App.xcworkspace") + try FileHandler.shared.createFolder(workspacePath) + + // When + let got = try subject.workspacePath(directory: path) + + // Then + XCTAssertNil(got) + } + + func test_workspacePath_when_multiple_workspaces_are_present() throws { + // Given + let path = try temporaryPath() + let nonTuistWorkspacePath = path.appending(components: "SPM.xcworkspace") + try FileHandler.shared.createFolder(nonTuistWorkspacePath) + let workspacePath = path.appending(component: "TuistApp.xcworkspace") + try FileHandler.shared.createFolder(workspacePath) + try FileHandler.shared.touch(workspacePath.appending(component: Constants.tuistGeneratedFileName)) + + // When + let got = try subject.workspacePath(directory: path) // Then XCTAssertEqual(got, workspacePath)