From e539bf2d8b803e863bf8ec99bf0d536284687f73 Mon Sep 17 00:00:00 2001 From: Joseph Goodrick <19720019+jgoodrick@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:32:15 -0600 Subject: [PATCH 1/5] Created a new cache directory for the project generated by the EditService and utilized it in the EditService instead of generating a temporary directory. --- Sources/TuistCore/Cache/CacheCategory.swift | 5 +++ Sources/TuistKit/Services/EditService.swift | 43 ++++++++++----------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Sources/TuistCore/Cache/CacheCategory.swift b/Sources/TuistCore/Cache/CacheCategory.swift index a0ae5334404..d5f9df8da44 100644 --- a/Sources/TuistCore/Cache/CacheCategory.swift +++ b/Sources/TuistCore/Cache/CacheCategory.swift @@ -11,6 +11,9 @@ public enum CacheCategory: String, CaseIterable, RawRepresentable { /// The manifests cache case manifests + + /// The edit projects cache + case generatedEditProjects public var directoryName: String { switch self { @@ -22,6 +25,8 @@ public enum CacheCategory: String, CaseIterable, RawRepresentable { return "ProjectDescriptionHelpers" case .manifests: return "Manifests" + case .generatedEditProjects: + return "EditProjects" } } } diff --git a/Sources/TuistKit/Services/EditService.swift b/Sources/TuistKit/Services/EditService.swift index b94493e9d5b..6b7a5245598 100644 --- a/Sources/TuistKit/Services/EditService.swift +++ b/Sources/TuistKit/Services/EditService.swift @@ -1,5 +1,6 @@ import Foundation import TSCBasic +import TuistCore import TuistGenerator import TuistGraph import TuistLoader @@ -30,21 +31,22 @@ final class EditService { private let configLoader: ConfigLoading private let pluginService: PluginServicing private let signalHandler: SignalHandling - - private static var temporaryDirectory: AbsolutePath? + private let cacheDirectoryProviderFactory: CacheDirectoriesProviderFactoring init( projectEditor: ProjectEditing = ProjectEditor(), opener: Opening = Opener(), configLoader: ConfigLoading = ConfigLoader(manifestLoader: ManifestLoader()), pluginService: PluginServicing = PluginService(), - signalHandler: SignalHandling = SignalHandler() + signalHandler: SignalHandling = SignalHandler(), + cacheDirectoryProviderFactory: CacheDirectoriesProviderFactoring = CacheDirectoriesProviderFactory() ) { self.projectEditor = projectEditor self.opener = opener self.configLoader = configLoader self.pluginService = pluginService self.signalHandler = signalHandler + self.cacheDirectoryProviderFactory = cacheDirectoryProviderFactory } func run( @@ -56,27 +58,22 @@ final class EditService { let plugins = await loadPlugins(at: path) if !permanent { - try withTemporaryDirectory(removeTreeOnDeinit: true) { generationDirectory in - EditService.temporaryDirectory = generationDirectory - - signalHandler.trap { _ in - try? EditService.temporaryDirectory.map(FileHandler.shared.delete) - exit(0) - } - - guard let selectedXcode = try XcodeController.shared.selected() else { - throw EditServiceError.xcodeNotSelected - } - - let workspacePath = try projectEditor.edit( - at: path, - in: generationDirectory, - onlyCurrentDirectory: onlyCurrentDirectory, - plugins: plugins - ) - logger.pretty("Opening Xcode to edit the project. Press \(.keystroke("CTRL + C")) once you are done editing") - try opener.open(path: workspacePath, application: selectedXcode.path, wait: true) + let pathHash = path.hashValue + let cacheDirectory = try cacheDirectoryProviderFactory.cacheDirectories() + let generationDirectory = try cacheDirectory.tuistCacheDirectory(for: .generatedEditProjects).appending(component: "\(pathHash)") + + guard let selectedXcode = try XcodeController.shared.selected() else { + throw EditServiceError.xcodeNotSelected } + + let workspacePath = try projectEditor.edit( + at: path, + in: generationDirectory, + onlyCurrentDirectory: onlyCurrentDirectory, + plugins: plugins + ) + logger.pretty("Opening Xcode to edit the project. Press \(.keystroke("CTRL + C")) once you are done editing") + try opener.open(path: workspacePath, application: selectedXcode.path, wait: true) } else { let workspacePath = try projectEditor.edit( at: path, From 7be963e07ce22d5a230b45acc43dfd1288f794de Mon Sep 17 00:00:00 2001 From: Joseph Goodrick <19720019+jgoodrick@users.noreply.github.com> Date: Mon, 18 Mar 2024 11:10:19 -0500 Subject: [PATCH 2/5] aligned name of new CacheCategory case with the name of the directory. --- Sources/TuistCore/Cache/CacheCategory.swift | 4 ++-- Sources/TuistKit/Services/EditService.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/TuistCore/Cache/CacheCategory.swift b/Sources/TuistCore/Cache/CacheCategory.swift index d5f9df8da44..f06b46fa276 100644 --- a/Sources/TuistCore/Cache/CacheCategory.swift +++ b/Sources/TuistCore/Cache/CacheCategory.swift @@ -13,7 +13,7 @@ public enum CacheCategory: String, CaseIterable, RawRepresentable { case manifests /// The edit projects cache - case generatedEditProjects + case editProjects public var directoryName: String { switch self { @@ -25,7 +25,7 @@ public enum CacheCategory: String, CaseIterable, RawRepresentable { return "ProjectDescriptionHelpers" case .manifests: return "Manifests" - case .generatedEditProjects: + case .editProjects: return "EditProjects" } } diff --git a/Sources/TuistKit/Services/EditService.swift b/Sources/TuistKit/Services/EditService.swift index 6b7a5245598..2a3b4d2a1c3 100644 --- a/Sources/TuistKit/Services/EditService.swift +++ b/Sources/TuistKit/Services/EditService.swift @@ -60,7 +60,7 @@ final class EditService { if !permanent { let pathHash = path.hashValue let cacheDirectory = try cacheDirectoryProviderFactory.cacheDirectories() - let generationDirectory = try cacheDirectory.tuistCacheDirectory(for: .generatedEditProjects).appending(component: "\(pathHash)") + let generationDirectory = try cacheDirectory.tuistCacheDirectory(for: .editProjects).appending(component: "\(pathHash)") guard let selectedXcode = try XcodeController.shared.selected() else { throw EditServiceError.xcodeNotSelected From b0d34cdc4b00b23163074fe88597c566cf57a044 Mon Sep 17 00:00:00 2001 From: Joseph Goodrick <19720019+jgoodrick@users.noreply.github.com> Date: Mon, 18 Mar 2024 11:11:25 -0500 Subject: [PATCH 3/5] reused the XcodeProjectPathHasher to create the path hash for the EditService --- Sources/TuistKit/Services/EditService.swift | 2 +- Sources/TuistSupport/Utils/DerivedDataLocator.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/TuistKit/Services/EditService.swift b/Sources/TuistKit/Services/EditService.swift index 2a3b4d2a1c3..7f41a7400fb 100644 --- a/Sources/TuistKit/Services/EditService.swift +++ b/Sources/TuistKit/Services/EditService.swift @@ -58,7 +58,7 @@ final class EditService { let plugins = await loadPlugins(at: path) if !permanent { - let pathHash = path.hashValue + let pathHash = try XcodeProjectPathHasher.hashString(for: path.pathString) let cacheDirectory = try cacheDirectoryProviderFactory.cacheDirectories() let generationDirectory = try cacheDirectory.tuistCacheDirectory(for: .editProjects).appending(component: "\(pathHash)") diff --git a/Sources/TuistSupport/Utils/DerivedDataLocator.swift b/Sources/TuistSupport/Utils/DerivedDataLocator.swift index edf0cd7b1c4..3556f2141d2 100644 --- a/Sources/TuistSupport/Utils/DerivedDataLocator.swift +++ b/Sources/TuistSupport/Utils/DerivedDataLocator.swift @@ -25,12 +25,12 @@ public final class DerivedDataLocator: DerivedDataLocating { // This is taken from XCLogParser, from Spotify, at: // https://github.com/spotify/XCLogParser/blob/master/Sources/XcodeHasher/XcodeHasher.swift -enum XcodeProjectPathHasher { +public enum XcodeProjectPathHasher { enum HashingError: Error { case invalidPartitioning } - static func hashString(for path: String) throws -> String { + public static func hashString(for path: String) throws -> String { // Initialize a 28 `String` array since we can't initialize empty `Character`s. var result = Array(repeating: "", count: 28) From 337141786f9a118b129e5acbfcf0c9a782ac68c4 Mon Sep 17 00:00:00 2001 From: Joseph Goodrick <19720019+jgoodrick@users.noreply.github.com> Date: Mon, 18 Mar 2024 11:12:25 -0500 Subject: [PATCH 4/5] added a new variant of 'selected()' to the XcodeController that returns a non-optional Xcode, and throws if none is found --- Sources/TuistKit/Services/EditService.swift | 4 +-- .../TuistSupport/Xcode/XcodeController.swift | 33 +++++++++++++++++++ .../Xcode/Mocks/MockXcodeController.swift | 9 +++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/Sources/TuistKit/Services/EditService.swift b/Sources/TuistKit/Services/EditService.swift index 7f41a7400fb..c7661a36020 100644 --- a/Sources/TuistKit/Services/EditService.swift +++ b/Sources/TuistKit/Services/EditService.swift @@ -62,9 +62,7 @@ final class EditService { let cacheDirectory = try cacheDirectoryProviderFactory.cacheDirectories() let generationDirectory = try cacheDirectory.tuistCacheDirectory(for: .editProjects).appending(component: "\(pathHash)") - guard let selectedXcode = try XcodeController.shared.selected() else { - throw EditServiceError.xcodeNotSelected - } + let selectedXcode = try XcodeController.shared.guaranteed() let workspacePath = try projectEditor.edit( at: path, diff --git a/Sources/TuistSupport/Xcode/XcodeController.swift b/Sources/TuistSupport/Xcode/XcodeController.swift index d739d1f141a..25250d8e62a 100644 --- a/Sources/TuistSupport/Xcode/XcodeController.swift +++ b/Sources/TuistSupport/Xcode/XcodeController.swift @@ -10,6 +10,13 @@ public protocol XcodeControlling { /// - Throws: An error if it can't be obtained. func selected() throws -> Xcode? + /// Returns the non-optional selected Xcode. It uses xcode-select to determine + /// the Xcode that is selected in the environment, and throws if none is. + /// + /// - Returns: Selected Xcode. + /// - Throws: An error if it can't be obtained. + func guaranteed() throws -> Xcode + /// Returns version of the selected Xcode. Uses `selected()` from `XcodeControlling` /// /// - Returns: `Version` of selected Xcode @@ -48,6 +55,32 @@ public class XcodeController: XcodeControlling { return xcode } + /// Returns the selected Xcode as a non-optional value. It uses xcode-select to determine + /// the Xcode that is selected in the environment. + /// + /// - Returns: Selected Xcode. + /// - Throws: An error if it can't be obtained. + public func guaranteed() throws -> Xcode { + if let selected = try selected() { + return selected + } else { + throw XcodeSelectedError.noXcodeSelected + } + } + + public enum XcodeSelectedError: FatalError { + case noXcodeSelected + + public var type: ErrorType { .abort } + + public var description: String { + switch self { + case .noXcodeSelected: + return "No Xcode selected" + } + } + } + enum XcodeVersionError: FatalError { case noXcode case noVersion diff --git a/Sources/TuistSupportTesting/Xcode/Mocks/MockXcodeController.swift b/Sources/TuistSupportTesting/Xcode/Mocks/MockXcodeController.swift index 259dcc702c5..714c838585c 100644 --- a/Sources/TuistSupportTesting/Xcode/Mocks/MockXcodeController.swift +++ b/Sources/TuistSupportTesting/Xcode/Mocks/MockXcodeController.swift @@ -16,6 +16,15 @@ public final class MockXcodeController: XcodeControlling { } } + public func guaranteed() throws -> Xcode { + guard let selectedStub else { throw XcodeController.XcodeSelectedError.noXcodeSelected } + + switch selectedStub { + case let .failure(error): throw error + case let .success(xcode): return xcode + } + } + public func selectedVersion() throws -> Version { switch selectedVersionStub { case let .failure(error): throw error From 1548f7c9e6ce08e54b4e52445d835fafc4b1edc6 Mon Sep 17 00:00:00 2001 From: Joseph Goodrick <19720019+jgoodrick@users.noreply.github.com> Date: Mon, 18 Mar 2024 11:28:22 -0500 Subject: [PATCH 5/5] removed optionality of the selected() method on XcodeController. Chose to surface the XcodeVersionError.noXcode where it was previously unwrapped --- .../Linter/EnvironmentLinter.swift | 4 +- .../ProjectEditor/ProjectEditorMapper.swift | 5 +- Sources/TuistKit/Services/EditService.swift | 2 +- .../TuistSupport/Xcode/XcodeController.swift | 49 +++---------------- .../Xcode/Mocks/MockXcodeController.swift | 13 +---- 5 files changed, 13 insertions(+), 60 deletions(-) diff --git a/Sources/TuistGenerator/Linter/EnvironmentLinter.swift b/Sources/TuistGenerator/Linter/EnvironmentLinter.swift index c8ed1fca05a..519161bd864 100644 --- a/Sources/TuistGenerator/Linter/EnvironmentLinter.swift +++ b/Sources/TuistGenerator/Linter/EnvironmentLinter.swift @@ -36,9 +36,7 @@ public class EnvironmentLinter: EnvironmentLinting { /// - Returns: An array with a linting issue if the selected version is not compatible. /// - Throws: An error if there's an error obtaining the selected Xcode version. func lintXcodeVersion(config: Config) throws -> [LintingIssue] { - guard let xcode = try XcodeController.shared.selected() else { - return [] - } + let xcode = try XcodeController.shared.selected() let version = xcode.infoPlist.version diff --git a/Sources/TuistKit/ProjectEditor/ProjectEditorMapper.swift b/Sources/TuistKit/ProjectEditor/ProjectEditorMapper.swift index ef1a3913cc6..79f05737573 100644 --- a/Sources/TuistKit/ProjectEditor/ProjectEditorMapper.swift +++ b/Sources/TuistKit/ProjectEditor/ProjectEditorMapper.swift @@ -245,9 +245,8 @@ final class ProjectEditorMapper: ProjectEditorMapping { let helperAndPluginDependencies = helperTargetDependencies + editablePluginTargetDependencies let packagesTarget: Target? = try { - guard let packageManifestPath, - let xcode = try XcodeController.shared.selected() - else { return nil } + let xcode = try XcodeController.shared.selected() + guard let packageManifestPath else { return nil } let packageVersion = try swiftPackageManagerController.getToolsVersion(at: packageManifestPath.parentDirectory) var packagesSettings = targetBaseSettings( diff --git a/Sources/TuistKit/Services/EditService.swift b/Sources/TuistKit/Services/EditService.swift index c7661a36020..baca3270467 100644 --- a/Sources/TuistKit/Services/EditService.swift +++ b/Sources/TuistKit/Services/EditService.swift @@ -62,7 +62,7 @@ final class EditService { let cacheDirectory = try cacheDirectoryProviderFactory.cacheDirectories() let generationDirectory = try cacheDirectory.tuistCacheDirectory(for: .editProjects).appending(component: "\(pathHash)") - let selectedXcode = try XcodeController.shared.guaranteed() + let selectedXcode = try XcodeController.shared.selected() let workspacePath = try projectEditor.edit( at: path, diff --git a/Sources/TuistSupport/Xcode/XcodeController.swift b/Sources/TuistSupport/Xcode/XcodeController.swift index 25250d8e62a..a1f37a1a265 100644 --- a/Sources/TuistSupport/Xcode/XcodeController.swift +++ b/Sources/TuistSupport/Xcode/XcodeController.swift @@ -8,14 +8,7 @@ public protocol XcodeControlling { /// /// - Returns: Selected Xcode. /// - Throws: An error if it can't be obtained. - func selected() throws -> Xcode? - - /// Returns the non-optional selected Xcode. It uses xcode-select to determine - /// the Xcode that is selected in the environment, and throws if none is. - /// - /// - Returns: Selected Xcode. - /// - Throws: An error if it can't be obtained. - func guaranteed() throws -> Xcode + func selected() throws -> Xcode /// Returns version of the selected Xcode. Uses `selected()` from `XcodeControlling` /// @@ -39,7 +32,7 @@ public class XcodeController: XcodeControlling { /// /// - Returns: Selected Xcode. /// - Throws: An error if it can't be obtained. - public func selected() throws -> Xcode? { + public func selected() throws -> Xcode { // Return cached value if available if let cached = selectedXcode { return cached @@ -47,7 +40,7 @@ public class XcodeController: XcodeControlling { // e.g. /Applications/Xcode.app/Contents/Developer guard let path = try? System.shared.capture(["xcode-select", "-p"]).spm_chomp() else { - return nil + throw XcodeVersionError.noXcode } let xcode = try Xcode.read(path: try AbsolutePath(validating: path).parentDirectory.parentDirectory) @@ -55,39 +48,13 @@ public class XcodeController: XcodeControlling { return xcode } - /// Returns the selected Xcode as a non-optional value. It uses xcode-select to determine - /// the Xcode that is selected in the environment. - /// - /// - Returns: Selected Xcode. - /// - Throws: An error if it can't be obtained. - public func guaranteed() throws -> Xcode { - if let selected = try selected() { - return selected - } else { - throw XcodeSelectedError.noXcodeSelected - } - } - - public enum XcodeSelectedError: FatalError { - case noXcodeSelected - - public var type: ErrorType { .abort } - - public var description: String { - switch self { - case .noXcodeSelected: - return "No Xcode selected" - } - } - } - - enum XcodeVersionError: FatalError { + public enum XcodeVersionError: FatalError { case noXcode case noVersion - var type: ErrorType { .abort } + public var type: ErrorType { .abort } - var description: String { + public var description: String { switch self { case .noXcode: return "Could not find Xcode" @@ -98,9 +65,7 @@ public class XcodeController: XcodeControlling { } public func selectedVersion() throws -> Version { - guard let xcode = try selected() else { - throw XcodeVersionError.noXcode - } + let xcode = try selected() guard let version = Version(unformattedString: xcode.infoPlist.version) else { throw XcodeVersionError.noXcode diff --git a/Sources/TuistSupportTesting/Xcode/Mocks/MockXcodeController.swift b/Sources/TuistSupportTesting/Xcode/Mocks/MockXcodeController.swift index 714c838585c..555f2f4f504 100644 --- a/Sources/TuistSupportTesting/Xcode/Mocks/MockXcodeController.swift +++ b/Sources/TuistSupportTesting/Xcode/Mocks/MockXcodeController.swift @@ -7,17 +7,8 @@ public final class MockXcodeController: XcodeControlling { public var selectedStub: Result? public var selectedVersionStub: Result = .success(Version(0, 0, 0)) - public func selected() throws -> Xcode? { - guard let selectedStub else { return nil } - - switch selectedStub { - case let .failure(error): throw error - case let .success(xcode): return xcode - } - } - - public func guaranteed() throws -> Xcode { - guard let selectedStub else { throw XcodeController.XcodeSelectedError.noXcodeSelected } + public func selected() throws -> Xcode { + guard let selectedStub else { throw XcodeController.XcodeVersionError.noXcode } switch selectedStub { case let .failure(error): throw error