diff --git a/Sources/Basics/CMakeLists.txt b/Sources/Basics/CMakeLists.txt index 343bbedbc56..5a9c35891ea 100644 --- a/Sources/Basics/CMakeLists.txt +++ b/Sources/Basics/CMakeLists.txt @@ -29,6 +29,7 @@ add_library(Basics Errors.swift FileSystem/AbsolutePath.swift FileSystem/FileSystem+Extensions.swift + FileSystem/InMemoryFileSystem.swift FileSystem/NativePathExtensions.swift FileSystem/RelativePath.swift FileSystem/TemporaryFile.swift diff --git a/Sources/Basics/FileSystem/FileSystem+Extensions.swift b/Sources/Basics/FileSystem/FileSystem+Extensions.swift index d16637d5872..4a61f56f006 100644 --- a/Sources/Basics/FileSystem/FileSystem+Extensions.swift +++ b/Sources/Basics/FileSystem/FileSystem+Extensions.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift project authors +// Copyright (c) 2020-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -21,7 +21,6 @@ import class TSCBasic.FileLock import enum TSCBasic.FileMode import protocol TSCBasic.FileSystem import enum TSCBasic.FileSystemAttribute -import class TSCBasic.InMemoryFileSystem import var TSCBasic.localFileSystem import protocol TSCBasic.WritableByteStream diff --git a/Sources/Basics/FileSystem/InMemoryFileSystem.swift b/Sources/Basics/FileSystem/InMemoryFileSystem.swift new file mode 100644 index 00000000000..56e6450aa55 --- /dev/null +++ b/Sources/Basics/FileSystem/InMemoryFileSystem.swift @@ -0,0 +1,500 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors + */ + +import class Foundation.NSLock +import class Dispatch.DispatchQueue +import struct TSCBasic.AbsolutePath +import struct TSCBasic.ByteString +import class TSCBasic.FileLock +import enum TSCBasic.FileMode +import struct TSCBasic.FileSystemError + +/// Concrete FileSystem implementation which simulates an empty disk. +public final class InMemoryFileSystem: FileSystem { + /// Private internal representation of a file system node. + /// Not thread-safe. + private class Node { + /// The actual node data. + let contents: NodeContents + + /// Whether the node has executable bit enabled. + var isExecutable: Bool + + init(_ contents: NodeContents, isExecutable: Bool = false) { + self.contents = contents + self.isExecutable = isExecutable + } + + /// Creates deep copy of the object. + func copy() -> Node { + return Node(contents.copy()) + } + } + + /// Private internal representation the contents of a file system node. + /// Not thread-safe. + private enum NodeContents { + case file(ByteString) + case directory(DirectoryContents) + case symlink(String) + + /// Creates deep copy of the object. + func copy() -> NodeContents { + switch self { + case .file(let bytes): + return .file(bytes) + case .directory(let contents): + return .directory(contents.copy()) + case .symlink(let path): + return .symlink(path) + } + } + } + + /// Private internal representation the contents of a directory. + /// Not thread-safe. + private final class DirectoryContents { + var entries: [String: Node] + + init(entries: [String: Node] = [:]) { + self.entries = entries + } + + /// Creates deep copy of the object. + func copy() -> DirectoryContents { + let contents = DirectoryContents() + for (key, node) in entries { + contents.entries[key] = node.copy() + } + return contents + } + } + + /// The root node of the filesystem. + private var root: Node + + /// Protects `root` and everything underneath it. + /// FIXME: Using a single lock for this is a performance problem, but in + /// reality, the only practical use for InMemoryFileSystem is for unit + /// tests. + private let lock = NSLock() + /// A map that keeps weak references to all locked files. + private var lockFiles = Dictionary>() + /// Used to access lockFiles in a thread safe manner. + private let lockFilesLock = NSLock() + + /// Exclusive file system lock vended to clients through `withLock()`. + /// Used to ensure that DispatchQueues are released when they are no longer in use. + private struct WeakReference { + weak var reference: Value? + + init(_ value: Value?) { + self.reference = value + } + } + + public init() { + root = Node(.directory(DirectoryContents())) + } + + /// Creates deep copy of the object. + public func copy() -> InMemoryFileSystem { + return lock.withLock { + let fs = InMemoryFileSystem() + fs.root = root.copy() + return fs + } + } + + /// Private function to look up the node corresponding to a path. + /// Not thread-safe. + private func getNode(_ path: TSCBasic.AbsolutePath, followSymlink: Bool = true) throws -> Node? { + func getNodeInternal(_ path: TSCBasic.AbsolutePath) throws -> Node? { + // If this is the root node, return it. + if path.isRoot { + return root + } + + // Otherwise, get the parent node. + guard let parent = try getNodeInternal(path.parentDirectory) else { + return nil + } + + // If we didn't find a directory, this is an error. + guard case .directory(let contents) = parent.contents else { + throw FileSystemError(.notDirectory, path.parentDirectory) + } + + // Return the directory entry. + let node = contents.entries[path.basename] + + switch node?.contents { + case .directory, .file: + return node + case .symlink(let destination): + let destination = try TSCBasic.AbsolutePath(validating: destination, relativeTo: path.parentDirectory) + return followSymlink ? try getNodeInternal(destination) : node + case .none: + return nil + } + } + + // Get the node that corresponds to the path. + return try getNodeInternal(path) + } + + // MARK: FileSystem Implementation + + public func exists(_ path: TSCBasic.AbsolutePath, followSymlink: Bool) -> Bool { + return lock.withLock { + do { + switch try getNode(path, followSymlink: followSymlink)?.contents { + case .file, .directory, .symlink: return true + case .none: return false + } + } catch { + return false + } + } + } + + public func isDirectory(_ path: TSCBasic.AbsolutePath) -> Bool { + return lock.withLock { + do { + if case .directory? = try getNode(path)?.contents { + return true + } + return false + } catch { + return false + } + } + } + + public func isFile(_ path: TSCBasic.AbsolutePath) -> Bool { + return lock.withLock { + do { + if case .file? = try getNode(path)?.contents { + return true + } + return false + } catch { + return false + } + } + } + + public func isSymlink(_ path: TSCBasic.AbsolutePath) -> Bool { + return lock.withLock { + do { + if case .symlink? = try getNode(path, followSymlink: false)?.contents { + return true + } + return false + } catch { + return false + } + } + } + + public func isReadable(_ path: TSCBasic.AbsolutePath) -> Bool { + self.exists(path) + } + + public func isWritable(_ path: TSCBasic.AbsolutePath) -> Bool { + self.exists(path) + } + + public func isExecutableFile(_ path: TSCBasic.AbsolutePath) -> Bool { + (try? self.getNode(path)?.isExecutable) ?? false + } + + public func updatePermissions(_ path: AbsolutePath, isExecutable: Bool) throws { + try lock.withLock { + guard let node = try self.getNode(path.underlying, followSymlink: true) else { + throw FileSystemError(.noEntry, path) + } + node.isExecutable = isExecutable + } + } + + /// Virtualized current working directory. + public var currentWorkingDirectory: TSCBasic.AbsolutePath? { + return try? .init(validating: "/") + } + + public func changeCurrentWorkingDirectory(to path: TSCBasic.AbsolutePath) throws { + throw FileSystemError(.unsupported, path) + } + + public var homeDirectory: TSCBasic.AbsolutePath { + get throws { + // FIXME: Maybe we should allow setting this when creating the fs. + return try .init(validating: "/home/user") + } + } + + public var cachesDirectory: TSCBasic.AbsolutePath? { + return try? self.homeDirectory.appending(component: "caches") + } + + public var tempDirectory: TSCBasic.AbsolutePath { + get throws { + return try .init(validating: "/tmp") + } + } + + public func getDirectoryContents(_ path: TSCBasic.AbsolutePath) throws -> [String] { + return try lock.withLock { + guard let node = try getNode(path) else { + throw FileSystemError(.noEntry, path) + } + guard case .directory(let contents) = node.contents else { + throw FileSystemError(.notDirectory, path) + } + + // FIXME: Perhaps we should change the protocol to allow lazy behavior. + return [String](contents.entries.keys) + } + } + + /// Not thread-safe. + private func _createDirectory(_ path: TSCBasic.AbsolutePath, recursive: Bool) throws { + // Ignore if client passes root. + guard !path.isRoot else { + return + } + // Get the parent directory node. + let parentPath = path.parentDirectory + guard let parent = try getNode(parentPath) else { + // If the parent doesn't exist, and we are recursive, then attempt + // to create the parent and retry. + if recursive && path != parentPath { + // Attempt to create the parent. + try _createDirectory(parentPath, recursive: true) + + // Re-attempt creation, non-recursively. + return try _createDirectory(path, recursive: false) + } else { + // Otherwise, we failed. + throw FileSystemError(.noEntry, parentPath) + } + } + + // Check that the parent is a directory. + guard case .directory(let contents) = parent.contents else { + // The parent isn't a directory, this is an error. + throw FileSystemError(.notDirectory, parentPath) + } + + // Check if the node already exists. + if let node = contents.entries[path.basename] { + // Verify it is a directory. + guard case .directory = node.contents else { + // The path itself isn't a directory, this is an error. + throw FileSystemError(.notDirectory, path) + } + + // We are done. + return + } + + // Otherwise, the node does not exist, create it. + contents.entries[path.basename] = Node(.directory(DirectoryContents())) + } + + public func createDirectory(_ path: TSCBasic.AbsolutePath, recursive: Bool) throws { + return try lock.withLock { + try _createDirectory(path, recursive: recursive) + } + } + + public func createSymbolicLink( + _ path: TSCBasic.AbsolutePath, + pointingAt destination: TSCBasic.AbsolutePath, + relative: Bool + ) throws { + return try lock.withLock { + // Create directory to destination parent. + guard let destinationParent = try getNode(path.parentDirectory) else { + throw FileSystemError(.noEntry, path.parentDirectory) + } + + // Check that the parent is a directory. + guard case .directory(let contents) = destinationParent.contents else { + throw FileSystemError(.notDirectory, path.parentDirectory) + } + + guard contents.entries[path.basename] == nil else { + throw FileSystemError(.alreadyExistsAtDestination, path) + } + + let destination = relative ? destination.relative(to: path.parentDirectory).pathString : destination.pathString + + contents.entries[path.basename] = Node(.symlink(destination)) + } + } + + public func readFileContents(_ path: TSCBasic.AbsolutePath) throws -> ByteString { + return try lock.withLock { + // Get the node. + guard let node = try getNode(path) else { + throw FileSystemError(.noEntry, path) + } + + // Check that the node is a file. + guard case .file(let contents) = node.contents else { + // The path is a directory, this is an error. + throw FileSystemError(.isDirectory, path) + } + + // Return the file contents. + return contents + } + } + + public func writeFileContents(_ path: TSCBasic.AbsolutePath, bytes: ByteString) throws { + return try lock.withLock { + // It is an error if this is the root node. + let parentPath = path.parentDirectory + guard path != parentPath else { + throw FileSystemError(.isDirectory, path) + } + + // Get the parent node. + guard let parent = try getNode(parentPath) else { + throw FileSystemError(.noEntry, parentPath) + } + + // Check that the parent is a directory. + guard case .directory(let contents) = parent.contents else { + // The parent isn't a directory, this is an error. + throw FileSystemError(.notDirectory, parentPath) + } + + // Check if the node exists. + if let node = contents.entries[path.basename] { + // Verify it is a file. + guard case .file = node.contents else { + // The path is a directory, this is an error. + throw FileSystemError(.isDirectory, path) + } + } + + // Write the file. + contents.entries[path.basename] = Node(.file(bytes)) + } + } + + public func writeFileContents(_ path: TSCBasic.AbsolutePath, bytes: ByteString, atomically: Bool) throws { + // In memory file system's writeFileContents is already atomic, so ignore the parameter here + // and just call the base implementation. + try writeFileContents(path, bytes: bytes) + } + + public func removeFileTree(_ path: TSCBasic.AbsolutePath) throws { + return lock.withLock { + // Ignore root and get the parent node's content if its a directory. + guard !path.isRoot, + let parent = try? getNode(path.parentDirectory), + case .directory(let contents) = parent.contents else { + return + } + // Set it to nil to release the contents. + contents.entries[path.basename] = nil + } + } + + public func chmod(_ mode: FileMode, path: TSCBasic.AbsolutePath, options: Set) throws { + // FIXME: We don't have these semantics in InMemoryFileSystem. + } + + /// Private implementation of core copying function. + /// Not thread-safe. + private func _copy(from sourcePath: TSCBasic.AbsolutePath, to destinationPath: TSCBasic.AbsolutePath) throws { + // Get the source node. + guard let source = try getNode(sourcePath) else { + throw FileSystemError(.noEntry, sourcePath) + } + + // Create directory to destination parent. + guard let destinationParent = try getNode(destinationPath.parentDirectory) else { + throw FileSystemError(.noEntry, destinationPath.parentDirectory) + } + + // Check that the parent is a directory. + guard case .directory(let contents) = destinationParent.contents else { + throw FileSystemError(.notDirectory, destinationPath.parentDirectory) + } + + guard contents.entries[destinationPath.basename] == nil else { + throw FileSystemError(.alreadyExistsAtDestination, destinationPath) + } + + contents.entries[destinationPath.basename] = source + } + + public func copy(from sourcePath: TSCBasic.AbsolutePath, to destinationPath: TSCBasic.AbsolutePath) throws { + return try lock.withLock { + try _copy(from: sourcePath, to: destinationPath) + } + } + + public func move(from sourcePath: TSCBasic.AbsolutePath, to destinationPath: TSCBasic.AbsolutePath) throws { + return try lock.withLock { + // Get the source parent node. + guard let sourceParent = try getNode(sourcePath.parentDirectory) else { + throw FileSystemError(.noEntry, sourcePath.parentDirectory) + } + + // Check that the parent is a directory. + guard case .directory(let contents) = sourceParent.contents else { + throw FileSystemError(.notDirectory, sourcePath.parentDirectory) + } + + try _copy(from: sourcePath, to: destinationPath) + + contents.entries[sourcePath.basename] = nil + } + } + + public func withLock( + on path: TSCBasic.AbsolutePath, + type: FileLock.LockType = .exclusive, + _ body: () throws -> T + ) throws -> T { + let resolvedPath: TSCBasic.AbsolutePath = try lock.withLock { + if case let .symlink(destination) = try getNode(path)?.contents { + return try .init(validating: destination, relativeTo: path.parentDirectory) + } else { + return path + } + } + + let fileQueue: DispatchQueue = lockFilesLock.withLock { + if let queueReference = lockFiles[resolvedPath], let queue = queueReference.reference { + return queue + } else { + let queue = DispatchQueue(label: "org.swift.swiftpm.in-memory-file-system.file-queue", attributes: .concurrent) + lockFiles[resolvedPath] = WeakReference(queue) + return queue + } + } + + return try fileQueue.sync(flags: type == .exclusive ? .barrier : .init() , execute: body) + } + + public func withLock(on path: TSCBasic.AbsolutePath, type: FileLock.LockType, blocking: Bool, _ body: () throws -> T) throws -> T { + try self.withLock(on: path, type: type, body) + } +} + +// Internal state of `InMemoryFileSystem` is protected with a lock in all of its `public` methods. +extension InMemoryFileSystem: @unchecked Sendable {} diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 40481b45373..7d8e406a575 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -265,6 +265,10 @@ public final class SwiftCommandState { fileprivate var buildSystemProvider: BuildSystemProvider? + private let environment: EnvironmentVariables + + private let hostTriple: Basics.Triple? + /// Create an instance of this tool. /// /// - parameter options: The command line options to be passed to this tool. @@ -294,14 +298,20 @@ public final class SwiftCommandState { options: GlobalOptions, toolWorkspaceConfiguration: ToolWorkspaceConfiguration, workspaceDelegateProvider: @escaping WorkspaceDelegateProvider, - workspaceLoaderProvider: @escaping WorkspaceLoaderProvider + workspaceLoaderProvider: @escaping WorkspaceLoaderProvider, + hostTriple: Basics.Triple? = nil, + fileSystem: any FileSystem = localFileSystem, + environment: EnvironmentVariables = ProcessEnv.vars ) throws { - self.fileSystem = localFileSystem + self.hostTriple = hostTriple + self.fileSystem = fileSystem + self.environment = environment // first, bootstrap the observability system self.logLevel = options.logging.logLevel self.observabilityHandler = SwiftCommandObservabilityHandler(outputStream: outputStream, logLevel: self.logLevel) let observabilitySystem = ObservabilitySystem(self.observabilityHandler) - self.observabilityScope = observabilitySystem.topScope + let observabilityScope = observabilitySystem.topScope + self.observabilityScope = observabilityScope self.shouldDisableSandbox = options.security.shouldDisableSandbox self.toolWorkspaceConfiguration = toolWorkspaceConfiguration self.workspaceDelegateProvider = workspaceDelegateProvider @@ -858,16 +868,25 @@ public final class SwiftCommandState { return self._hostToolchain } - return Result(catching: { try UserToolchain(swiftSDK: swiftSDK) }) + return Result(catching: { + try UserToolchain(swiftSDK: swiftSDK, environment: self.environment, fileSystem: self.fileSystem) + }) }() /// Lazily compute the host toolchain used to compile the package description. private lazy var _hostToolchain: Result = { return Result(catching: { - try UserToolchain(swiftSDK: SwiftSDK.hostSwiftSDK( - originalWorkingDirectory: self.originalWorkingDirectory, + var hostSwiftSDK = try SwiftSDK.hostSwiftSDK( + environment: self.environment, observabilityScope: self.observabilityScope - )) + ) + hostSwiftSDK.targetTriple = self.hostTriple + + return try UserToolchain( + swiftSDK: hostSwiftSDK, + environment: self.environment, + fileSystem: self.fileSystem + ) }) }() diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift index 5d14b86d2e4..ec3b0e47a81 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift @@ -483,14 +483,10 @@ public struct SwiftSDK: Equatable { } /// Returns the bin directory for the host. - /// - /// - Parameter originalWorkingDirectory: The working directory when the program was launched. private static func hostBinDir( - fileSystem: FileSystem, - originalWorkingDirectory: AbsolutePath? = nil + fileSystem: FileSystem ) throws -> AbsolutePath { - let originalWorkingDirectory = originalWorkingDirectory ?? fileSystem.currentWorkingDirectory - guard let cwd = originalWorkingDirectory else { + guard let cwd = fileSystem.currentWorkingDirectory else { return try AbsolutePath(validating: CommandLine.arguments[0]).parentDirectory } return try AbsolutePath(validating: CommandLine.arguments[0], relativeTo: cwd).parentDirectory @@ -501,34 +497,30 @@ public struct SwiftSDK: Equatable { public static func hostDestination( _ binDir: AbsolutePath? = nil, originalWorkingDirectory: AbsolutePath? = nil, - environment: [String: String] = ProcessEnv.vars + environment: [String: String] ) throws -> SwiftSDK { - try self.hostSwiftSDK(binDir, originalWorkingDirectory: originalWorkingDirectory, environment: environment) + try self.hostSwiftSDK(binDir, environment: environment) } /// The Swift SDK for the host platform. public static func hostSwiftSDK( _ binDir: AbsolutePath? = nil, - originalWorkingDirectory: AbsolutePath? = nil, - environment: [String: String] = ProcessEnv.vars, - observabilityScope: ObservabilityScope? = nil + environment: EnvironmentVariables = .process(), + observabilityScope: ObservabilityScope? = nil, + fileSystem: any FileSystem = localFileSystem ) throws -> SwiftSDK { - let originalWorkingDirectory = originalWorkingDirectory ?? localFileSystem.currentWorkingDirectory // Select the correct binDir. - if ProcessEnv.block["SWIFTPM_CUSTOM_BINDIR"] != nil { + if environment["SWIFTPM_CUSTOM_BINDIR"] != nil { print("SWIFTPM_CUSTOM_BINDIR was deprecated in favor of SWIFTPM_CUSTOM_BIN_DIR") } - let customBinDir = (ProcessEnv.block["SWIFTPM_CUSTOM_BIN_DIR"] ?? ProcessEnv.block["SWIFTPM_CUSTOM_BINDIR"]) + let customBinDir = (environment["SWIFTPM_CUSTOM_BIN_DIR"] ?? environment["SWIFTPM_CUSTOM_BINDIR"]) .flatMap { try? AbsolutePath(validating: $0) } - let binDir = try customBinDir ?? binDir ?? SwiftSDK.hostBinDir( - fileSystem: localFileSystem, - originalWorkingDirectory: originalWorkingDirectory - ) + let binDir = try customBinDir ?? binDir ?? SwiftSDK.hostBinDir(fileSystem: fileSystem) let sdkPath: AbsolutePath? #if os(macOS) // Get the SDK. - if let value = ProcessEnv.block["SDKROOT"] { + if let value = environment["SDKROOT"] { sdkPath = try AbsolutePath(validating: value) } else { // No value in env, so search for it. @@ -707,7 +699,8 @@ public struct SwiftSDK: Equatable { ) } - swiftSDK.add(toolsetRootPath: binDir.appending(components: "usr", "bin")) + // `--tooolchain` should override existing anything in the SDK and search paths. + swiftSDK.prepend(toolsetRootPath: binDir.appending(components: "usr", "bin")) } if let sdk = customCompileSDK { swiftSDK.pathsConfiguration.sdkRootPath = sdk @@ -718,7 +711,7 @@ public struct SwiftSDK: Equatable { // Append the host toolchain's toolset paths at the end for the case the target Swift SDK // doesn't have some of the tools (e.g. swift-frontend might be shared between the host and // target Swift SDKs). - hostSwiftSDK.toolset.rootPaths.forEach { swiftSDK.add(toolsetRootPath: $0) } + hostSwiftSDK.toolset.rootPaths.forEach { swiftSDK.append(toolsetRootPath: $0) } } return swiftSDK @@ -736,9 +729,22 @@ public struct SwiftSDK: Equatable { self.toolset.knownTools[.swiftCompiler] = properties } + /// Prepends a path to the array of toolset root paths. + /// + /// Note: Use this operation if you want new root path to take priority over existing paths. + /// + /// - Parameter toolsetRootPath: new path to add to Swift SDK's toolset. + public mutating func prepend(toolsetRootPath path: AbsolutePath) { + self.toolset.rootPaths.insert(path, at: 0) + } + /// Appends a path to the array of toolset root paths. + /// + /// Note: The paths are evaluated in insertion order which means that newly added path would + /// have a lower priority vs. existing paths. + /// /// - Parameter toolsetRootPath: new path to add to Swift SDK's toolset. - public mutating func add(toolsetRootPath: AbsolutePath) { + public mutating func append(toolsetRootPath: AbsolutePath) { self.toolset.rootPaths.append(toolsetRootPath) } } diff --git a/Sources/PackageModel/UserToolchain.swift b/Sources/PackageModel/UserToolchain.swift index eedce88bd60..64b663b6a6d 100644 --- a/Sources/PackageModel/UserToolchain.swift +++ b/Sources/PackageModel/UserToolchain.swift @@ -58,6 +58,8 @@ public final class UserToolchain: Toolchain { self.swiftCompilerPath.parentDirectory.appending("swift" + hostExecutableSuffix) } + private let fileSystem: any FileSystem + /// The compilation destination object. @available(*, deprecated, renamed: "swiftSDK") public var destination: SwiftSDK { swiftSDK } @@ -101,7 +103,7 @@ public final class UserToolchain: Toolchain { ) // Ensure that the runtime is present. - guard localFileSystem.exists(runtime) else { + guard fileSystem.exists(runtime) else { throw InvalidToolchainDiagnostic("Missing runtime for \(sanitizer) sanitizer") } @@ -118,13 +120,17 @@ public final class UserToolchain: Toolchain { lookupExecutablePath(filename: environment[variable], searchPaths: searchPaths) } - private static func getTool(_ name: String, binDirectories: [AbsolutePath]) throws -> AbsolutePath { + private static func getTool( + _ name: String, + binDirectories: [AbsolutePath], + fileSystem: any FileSystem + ) throws -> AbsolutePath { let executableName = "\(name)\(hostExecutableSuffix)" var toolPath: AbsolutePath? for dir in binDirectories { let path = dir.appending(component: executableName) - guard localFileSystem.isExecutableFile(path) else { + guard fileSystem.isExecutableFile(path) else { continue } toolPath = path @@ -140,7 +146,8 @@ public final class UserToolchain: Toolchain { private static func findTool( _ name: String, envSearchPaths: [AbsolutePath], - useXcrun: Bool + useXcrun: Bool, + fileSystem: any FileSystem ) throws -> AbsolutePath { if useXcrun { #if os(macOS) @@ -150,7 +157,7 @@ public final class UserToolchain: Toolchain { #endif } - return try getTool(name, binDirectories: envSearchPaths) + return try getTool(name, binDirectories: envSearchPaths, fileSystem: fileSystem) } // MARK: - public API @@ -161,10 +168,9 @@ public final class UserToolchain: Toolchain { useXcrun: Bool, environment: EnvironmentVariables, searchPaths: [AbsolutePath], - extraSwiftFlags: [String] - ) throws - -> AbsolutePath - { + extraSwiftFlags: [String], + fileSystem: any FileSystem + ) throws -> AbsolutePath { let variable: String = triple.isApple() ? "LIBTOOL" : "AR" let tool: String = { if triple.isApple() { return "libtool" } @@ -194,25 +200,25 @@ public final class UserToolchain: Toolchain { searchPaths: searchPaths, environment: environment ) { - if localFileSystem.isExecutableFile(librarian) { + if fileSystem.isExecutableFile(librarian) { return librarian } } - if let librarian = try? UserToolchain.getTool(tool, binDirectories: binDirectories) { + if let librarian = try? UserToolchain.getTool(tool, binDirectories: binDirectories, fileSystem: fileSystem) { return librarian } if triple.isApple() || triple.isWindows() { - return try UserToolchain.findTool(tool, envSearchPaths: searchPaths, useXcrun: useXcrun) + return try UserToolchain.findTool(tool, envSearchPaths: searchPaths, useXcrun: useXcrun, fileSystem: fileSystem) } else { - if let librarian = try? UserToolchain.findTool(tool, envSearchPaths: searchPaths, useXcrun: false) { + if let librarian = try? UserToolchain.findTool(tool, envSearchPaths: searchPaths, useXcrun: false, fileSystem: fileSystem) { return librarian } // Fall back to looking for binutils `ar` if `llvm-ar` can't be found. - if let librarian = try? UserToolchain.getTool("ar", binDirectories: binDirectories) { + if let librarian = try? UserToolchain.getTool("ar", binDirectories: binDirectories, fileSystem: fileSystem) { return librarian } - return try UserToolchain.findTool("ar", envSearchPaths: searchPaths, useXcrun: false) + return try UserToolchain.findTool("ar", envSearchPaths: searchPaths, useXcrun: false, fileSystem: fileSystem) } } @@ -221,11 +227,12 @@ public final class UserToolchain: Toolchain { binDirectories: [AbsolutePath], useXcrun: Bool, environment: EnvironmentVariables, - searchPaths: [AbsolutePath] + searchPaths: [AbsolutePath], + fileSystem: any FileSystem ) throws -> SwiftCompilers { func validateCompiler(at path: AbsolutePath?) throws { guard let path else { return } - guard localFileSystem.isExecutableFile(path) else { + guard fileSystem.isExecutableFile(path) else { throw InvalidToolchainDiagnostic( "could not find the `swiftc\(hostExecutableSuffix)` at expected path \(path)" ) @@ -246,7 +253,7 @@ public final class UserToolchain: Toolchain { let resolvedBinDirCompiler: AbsolutePath if let SWIFT_EXEC { resolvedBinDirCompiler = SWIFT_EXEC - } else if let binDirCompiler = try? UserToolchain.getTool("swiftc", binDirectories: binDirectories) { + } else if let binDirCompiler = try? UserToolchain.getTool("swiftc", binDirectories: binDirectories, fileSystem: fileSystem) { resolvedBinDirCompiler = binDirCompiler } else { // Try to lookup swift compiler on the system which is possible when @@ -254,13 +261,14 @@ public final class UserToolchain: Toolchain { resolvedBinDirCompiler = try UserToolchain.findTool( "swiftc", envSearchPaths: searchPaths, - useXcrun: useXcrun + useXcrun: useXcrun, + fileSystem: fileSystem ) } // The compiler for compilation tasks is SWIFT_EXEC or the bin dir compiler. // The compiler for manifest is either SWIFT_EXEC_MANIFEST or the bin dir compiler. - return (SWIFT_EXEC ?? resolvedBinDirCompiler, SWIFT_EXEC_MANIFEST ?? resolvedBinDirCompiler) + return (compile: SWIFT_EXEC ?? resolvedBinDirCompiler, manifest: SWIFT_EXEC_MANIFEST ?? resolvedBinDirCompiler) } /// Returns the path to clang compiler tool. @@ -281,15 +289,22 @@ public final class UserToolchain: Toolchain { } // Then, check the toolchain. - do { - if let toolPath = try? UserToolchain.getTool("clang", binDirectories: self.swiftSDK.toolset.rootPaths) { - self._clangCompiler = toolPath - return toolPath - } + if let toolPath = try? UserToolchain.getTool( + "clang", + binDirectories: self.swiftSDK.toolset.rootPaths, + fileSystem: self.fileSystem + ) { + self._clangCompiler = toolPath + return toolPath } // Otherwise, lookup it up on the system. - let toolPath = try UserToolchain.findTool("clang", envSearchPaths: self.envSearchPaths, useXcrun: useXcrun) + let toolPath = try UserToolchain.findTool( + "clang", + envSearchPaths: self.envSearchPaths, + useXcrun: useXcrun, + fileSystem: self.fileSystem + ) self._clangCompiler = toolPath return toolPath } @@ -307,21 +322,38 @@ public final class UserToolchain: Toolchain { /// Returns the path to lldb. public func getLLDB() throws -> AbsolutePath { // Look for LLDB next to the compiler first. - if let lldbPath = try? UserToolchain.getTool("lldb", binDirectories: [self.swiftCompilerPath.parentDirectory]) { + if let lldbPath = try? UserToolchain.getTool( + "lldb", + binDirectories: [self.swiftCompilerPath.parentDirectory], + fileSystem: self.fileSystem + ) { return lldbPath } // If that fails, fall back to xcrun, PATH, etc. - return try UserToolchain.findTool("lldb", envSearchPaths: self.envSearchPaths, useXcrun: useXcrun) + return try UserToolchain.findTool( + "lldb", + envSearchPaths: self.envSearchPaths, + useXcrun: useXcrun, + fileSystem: self.fileSystem + ) } /// Returns the path to llvm-cov tool. public func getLLVMCov() throws -> AbsolutePath { - try UserToolchain.getTool("llvm-cov", binDirectories: [self.swiftCompilerPath.parentDirectory]) + try UserToolchain.getTool( + "llvm-cov", + binDirectories: [self.swiftCompilerPath.parentDirectory], + fileSystem: self.fileSystem + ) } /// Returns the path to llvm-prof tool. public func getLLVMProf() throws -> AbsolutePath { - try UserToolchain.getTool("llvm-profdata", binDirectories: [self.swiftCompilerPath.parentDirectory]) + try UserToolchain.getTool( + "llvm-profdata", + binDirectories: [self.swiftCompilerPath.parentDirectory], + fileSystem: self.fileSystem + ) } public func getSwiftAPIDigester() throws -> AbsolutePath { @@ -332,7 +364,12 @@ public final class UserToolchain: Toolchain { ) { return envValue } - return try UserToolchain.getTool("swift-api-digester", binDirectories: [self.swiftCompilerPath.parentDirectory]) + return try UserToolchain.getTool( + "swift-api-digester", + binDirectories: [self.swiftCompilerPath.parentDirectory], + fileSystem: self.fileSystem + + ) } public func getSymbolGraphExtract() throws -> AbsolutePath { @@ -343,13 +380,18 @@ public final class UserToolchain: Toolchain { ) { return envValue } - return try UserToolchain.getTool("swift-symbolgraph-extract", binDirectories: [self.swiftCompilerPath.parentDirectory]) + return try UserToolchain.getTool( + "swift-symbolgraph-extract", + binDirectories: [self.swiftCompilerPath.parentDirectory], + fileSystem: self.fileSystem + ) } internal static func deriveSwiftCFlags( triple: Triple, swiftSDK: SwiftSDK, - environment: EnvironmentVariables + environment: EnvironmentVariables, + fileSystem: any FileSystem ) throws -> [String] { var swiftCompilerFlags = swiftSDK.toolset.knownTools[.swiftCompiler]?.extraCLIOptions ?? [] @@ -370,7 +412,7 @@ public final class UserToolchain: Toolchain { if let settings = WindowsSDKSettings( reading: sdkroot.appending("SDKSettings.plist"), observabilityScope: nil, - filesystem: localFileSystem + filesystem: fileSystem ) { switch settings.defaults.runtime { case .multithreadedDebugDLL: @@ -395,7 +437,7 @@ public final class UserToolchain: Toolchain { if let info = WindowsPlatformInfo( reading: platform.appending("Info.plist"), observabilityScope: nil, - filesystem: localFileSystem + filesystem: fileSystem ) { let installation: AbsolutePath = platform.appending("Developer") @@ -436,7 +478,7 @@ public final class UserToolchain: Toolchain { validating: "usr/lib/swift/windows/XCTest.lib", relativeTo: installation ) - if localFileSystem.exists(implib) { + if fileSystem.exists(implib) { xctest.append(contentsOf: ["-L", implib.parentDirectory.pathString]) } @@ -475,7 +517,8 @@ public final class UserToolchain: Toolchain { swiftSDK: destination, environment: environment, searchStrategy: searchStrategy, - customLibrariesLocation: customLibrariesLocation + customLibrariesLocation: customLibrariesLocation, + fileSystem: localFileSystem ) } @@ -485,7 +528,8 @@ public final class UserToolchain: Toolchain { searchStrategy: SearchStrategy = .default, customLibrariesLocation: ToolchainConfiguration.SwiftPMLibrariesLocation? = nil, customInstalledSwiftPMConfiguration: InstalledSwiftPMConfiguration? = nil, - customProvidedLibraries: [ProvidedLibrary]? = nil + customProvidedLibraries: [ProvidedLibrary]? = nil, + fileSystem: any FileSystem = localFileSystem ) throws { self.swiftSDK = swiftSDK self.environment = environment @@ -495,7 +539,7 @@ public final class UserToolchain: Toolchain { // Get the search paths from PATH. self.envSearchPaths = getEnvSearchPaths( pathString: environment.path, - currentWorkingDirectory: localFileSystem.currentWorkingDirectory + currentWorkingDirectory: fileSystem.currentWorkingDirectory ) self.useXcrun = true case .custom(let searchPaths, let useXcrun): @@ -505,9 +549,10 @@ public final class UserToolchain: Toolchain { let swiftCompilers = try UserToolchain.determineSwiftCompilers( binDirectories: swiftSDK.toolset.rootPaths, - useXcrun: useXcrun, + useXcrun: self.useXcrun, environment: environment, - searchPaths: envSearchPaths + searchPaths: self.envSearchPaths, + fileSystem: fileSystem ) self.swiftCompilerPath = swiftCompilers.compile self.architectures = swiftSDK.architectures @@ -562,7 +607,9 @@ public final class UserToolchain: Toolchain { swiftCompilerFlags: try Self.deriveSwiftCFlags( triple: triple, swiftSDK: swiftSDK, - environment: environment), + environment: environment, + fileSystem: fileSystem + ), linkerFlags: swiftSDK.toolset.knownTools[.linker]?.extraCLIOptions ?? [], xcbuildFlags: swiftSDK.toolset.knownTools[.xcbuild]?.extraCLIOptions ?? []) @@ -575,7 +622,8 @@ public final class UserToolchain: Toolchain { useXcrun: useXcrun, environment: environment, searchPaths: envSearchPaths, - extraSwiftFlags: self.extraFlags.swiftCompilerFlags + extraSwiftFlags: self.extraFlags.swiftCompilerFlags, + fileSystem: fileSystem ) if let sdkDir = swiftSDK.pathsConfiguration.sdkRootPath { @@ -588,7 +636,7 @@ public final class UserToolchain: Toolchain { if let settings = WindowsSDKSettings( reading: root.appending("SDKSettings.plist"), observabilityScope: nil, - filesystem: localFileSystem + filesystem: fileSystem ) { switch settings.defaults.runtime { case .multithreadedDebugDLL: @@ -624,7 +672,8 @@ public final class UserToolchain: Toolchain { let swiftPMLibrariesLocation = try customLibrariesLocation ?? Self.deriveSwiftPMLibrariesLocation( swiftCompilerPath: swiftCompilerPath, swiftSDK: swiftSDK, - environment: environment + environment: environment, + fileSystem: fileSystem ) let xctestPath: AbsolutePath? @@ -634,7 +683,8 @@ public final class UserToolchain: Toolchain { xctestPath = try Self.deriveXCTestPath( swiftSDK: self.swiftSDK, triple: triple, - environment: environment + environment: environment, + fileSystem: fileSystem ) } @@ -647,12 +697,15 @@ public final class UserToolchain: Toolchain { sdkRootPath: self.swiftSDK.pathsConfiguration.sdkRootPath, xctestPath: xctestPath ) + + self.fileSystem = fileSystem } private static func deriveSwiftPMLibrariesLocation( swiftCompilerPath: AbsolutePath, swiftSDK: SwiftSDK, - environment: EnvironmentVariables + environment: EnvironmentVariables, + fileSystem: any FileSystem ) throws -> ToolchainConfiguration.SwiftPMLibrariesLocation? { // Look for an override in the env. if let pathEnvVariable = environment["SWIFTPM_CUSTOM_LIBS_DIR"] ?? environment["SWIFTPM_PD_LIBS"] { @@ -668,7 +721,7 @@ public final class UserToolchain: Toolchain { #endif let paths = pathEnvVariable.split(separator: separator).map(String.init) for pathString in paths { - if let path = try? AbsolutePath(validating: pathString), localFileSystem.exists(path) { + if let path = try? AbsolutePath(validating: pathString), fileSystem.exists(path) { // we found the custom one return .init(root: path) } @@ -687,7 +740,7 @@ public final class UserToolchain: Toolchain { for applicationPath in swiftSDK.toolset.rootPaths { // this is the normal case when using the toolchain let librariesPath = applicationPath.parentDirectory.appending(components: "lib", "swift", "pm") - if localFileSystem.exists(librariesPath) { + if fileSystem.exists(librariesPath) { return .init(root: librariesPath) } @@ -697,7 +750,7 @@ public final class UserToolchain: Toolchain { "PackageDescription.framework" ) let pluginFrameworksPath = applicationPath.appending(components: "PackageFrameworks", "PackagePlugin.framework") - if localFileSystem.exists(manifestFrameworksPath), localFileSystem.exists(pluginFrameworksPath) { + if fileSystem.exists(manifestFrameworksPath), fileSystem.exists(pluginFrameworksPath) { return .init( manifestLibraryPath: manifestFrameworksPath, pluginLibraryPath: pluginFrameworksPath @@ -742,7 +795,8 @@ public final class UserToolchain: Toolchain { private static func deriveXCTestPath( swiftSDK: SwiftSDK, triple: Triple, - environment: EnvironmentVariables + environment: EnvironmentVariables, + fileSystem: any FileSystem ) throws -> AbsolutePath? { if triple.isDarwin() { // XCTest is optional on macOS, for example when Xcode is not installed @@ -774,7 +828,7 @@ public final class UserToolchain: Toolchain { if let info = WindowsPlatformInfo( reading: platform.appending("Info.plist"), observabilityScope: nil, - filesystem: localFileSystem + filesystem: fileSystem ) { let xctest: AbsolutePath = platform.appending("Developer") @@ -794,7 +848,7 @@ public final class UserToolchain: Toolchain { let path: AbsolutePath = xctest.appending("usr") .appending("bin64") - if localFileSystem.exists(path) { + if fileSystem.exists(path) { return path } @@ -802,7 +856,7 @@ public final class UserToolchain: Toolchain { let path: AbsolutePath = xctest.appending("usr") .appending("bin32") - if localFileSystem.exists(path) { + if fileSystem.exists(path) { return path } @@ -810,7 +864,7 @@ public final class UserToolchain: Toolchain { let path: AbsolutePath = xctest.appending("usr") .appending("bin32a") - if localFileSystem.exists(path) { + if fileSystem.exists(path) { return path } @@ -818,7 +872,7 @@ public final class UserToolchain: Toolchain { let path: AbsolutePath = xctest.appending("usr") .appending("bin64a") - if localFileSystem.exists(path) { + if fileSystem.exists(path) { return path } diff --git a/Sources/SPMTestSupport/InMemoryGitRepository.swift b/Sources/SPMTestSupport/InMemoryGitRepository.swift index 5d8ea5bc622..c7fc4c4abba 100644 --- a/Sources/SPMTestSupport/InMemoryGitRepository.swift +++ b/Sources/SPMTestSupport/InMemoryGitRepository.swift @@ -18,7 +18,6 @@ import SourceControl import struct TSCBasic.ByteString import enum TSCBasic.FileMode import struct TSCBasic.FileSystemError -import class TSCBasic.InMemoryFileSystem /// The error encountered during in memory git repository operations. public enum InMemoryGitRepositoryError: Swift.Error { diff --git a/Sources/SPMTestSupport/MockWorkspace.swift b/Sources/SPMTestSupport/MockWorkspace.swift index acd5a7f508d..ff8bc051461 100644 --- a/Sources/SPMTestSupport/MockWorkspace.swift +++ b/Sources/SPMTestSupport/MockWorkspace.swift @@ -19,16 +19,41 @@ import SourceControl import Workspace import XCTest -import class TSCBasic.InMemoryFileSystem - import struct TSCUtility.Version +extension UserToolchain { + package static func mockHostToolchain(_ fileSystem: InMemoryFileSystem) throws -> UserToolchain { + var hostSwiftSDK = try SwiftSDK.hostSwiftSDK(environment: .mockEnvironment, fileSystem: fileSystem) + hostSwiftSDK.targetTriple = hostTriple + return try UserToolchain( + swiftSDK: hostSwiftSDK, + environment: .mockEnvironment, + fileSystem: fileSystem + ) + } +} + +extension EnvironmentVariables { + package static var mockEnvironment: Self { ["PATH": "/fake/path/to"] } +} + +extension InMemoryFileSystem { + package func createMockToolchain() throws { + let files = ["/fake/path/to/swiftc", "/fake/path/to/ar"] + self.createEmptyFiles(at: AbsolutePath.root, files: files) + for toolPath in files { + try self.updatePermissions(.init(toolPath), isExecutable: true) + } + } +} + public final class MockWorkspace { let sandbox: AbsolutePath let fileSystem: InMemoryFileSystem let roots: [MockPackage] let packages: [MockPackage] let customToolsVersion: ToolsVersion? + private let customHostToolchain: UserToolchain let fingerprints: MockPackageFingerprintStorage let signingEntities: MockPackageSigningEntityStorage let mirrors: DependencyMirrors @@ -62,6 +87,8 @@ public final class MockWorkspace { sourceControlToRegistryDependencyTransformation: WorkspaceConfiguration.SourceControlToRegistryDependencyTransformation = .disabled, defaultRegistry: Registry? = .none ) throws { + try fileSystem.createMockToolchain() + self.sandbox = sandbox self.fileSystem = fileSystem self.roots = roots @@ -93,6 +120,7 @@ public final class MockWorkspace { httpClient: LegacyHTTPClient.mock(fileSystem: fileSystem), archiver: MockArchiver() ) + self.customHostToolchain = try UserToolchain.mockHostToolchain(fileSystem) try self.create() } @@ -274,6 +302,7 @@ public final class MockWorkspace { let workspace = try Workspace._init( fileSystem: self.fileSystem, + environment: .mockEnvironment, location: .init( scratchDirectory: self.sandbox.appending(".build"), editsDirectory: self.sandbox.appending("edits"), @@ -300,6 +329,7 @@ public final class MockWorkspace { customFingerprints: self.fingerprints, customMirrors: self.mirrors, customToolsVersion: self.customToolsVersion, + customHostToolchain: self.customHostToolchain, customManifestLoader: self.manifestLoader, customPackageContainerProvider: self.customPackageContainerProvider, customRepositoryProvider: self.repositoryProvider, diff --git a/Sources/SPMTestSupport/Toolchain.swift b/Sources/SPMTestSupport/Toolchain.swift index ff7e403c6b9..02518b2c0b0 100644 --- a/Sources/SPMTestSupport/Toolchain.swift +++ b/Sources/SPMTestSupport/Toolchain.swift @@ -41,7 +41,7 @@ extension SwiftSDK { public static var `default`: Self { get throws { let binDir = try resolveBinDir() - return try! SwiftSDK.hostSwiftSDK(binDir) + return try! SwiftSDK.hostSwiftSDK(binDir, environment: .process()) } } } @@ -49,7 +49,7 @@ extension SwiftSDK { extension UserToolchain { public static var `default`: Self { get throws { - return try .init(swiftSDK: SwiftSDK.default) + return try .init(swiftSDK: SwiftSDK.default, environment: .process(), fileSystem: localFileSystem) } } } diff --git a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift index 93a01d0dad3..a739afc28f6 100644 --- a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift +++ b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +private import Basics + import struct Foundation.URL import struct PackageGraph.ResolvedModule @@ -19,6 +21,8 @@ internal import struct PackageModel.ToolsVersion private import class PackageModel.UserToolchain import enum PackageGraph.BuildTriple +private import enum TSCBasic.ProcessEnv + struct PluginTargetBuildDescription: BuildTarget { private let target: ResolvedModule private let toolsVersion: ToolsVersion @@ -45,7 +49,15 @@ struct PluginTargetBuildDescription: BuildTarget { func compileArguments(for fileURL: URL) throws -> [String] { // FIXME: This is very odd and we should clean this up by merging `ManifestLoader` and `DefaultPluginScriptRunner` again. - let loader = ManifestLoader(toolchain: try UserToolchain(swiftSDK: .hostSwiftSDK())) + let environment = EnvironmentVariables.process() + let loader = ManifestLoader( + toolchain: try UserToolchain( + swiftSDK: .hostSwiftSDK( + environment: environment + ), + environment: environment + ) + ) var args = loader.interpreterFlags(for: self.toolsVersion) // Note: we ignore the `fileURL` here as the expectation is that we get a commandline for the entire target in case of Swift. Plugins are always assumed to only consist of Swift files. args += sources.map { $0.path } diff --git a/Sources/SwiftSDKCommand/SwiftSDKSubcommand.swift b/Sources/SwiftSDKCommand/SwiftSDKSubcommand.swift index ce56b9bb553..25f60575efc 100644 --- a/Sources/SwiftSDKCommand/SwiftSDKSubcommand.swift +++ b/Sources/SwiftSDKCommand/SwiftSDKSubcommand.swift @@ -60,7 +60,13 @@ extension SwiftSDKSubcommand { let observabilityScope = observabilitySystem.topScope let swiftSDKsDirectory = try self.getOrCreateSwiftSDKsDirectory() - let hostToolchain = try UserToolchain(swiftSDK: SwiftSDK.hostSwiftSDK()) + let environment = EnvironmentVariables.process() + let hostToolchain = try UserToolchain( + swiftSDK: SwiftSDK.hostSwiftSDK( + environment: environment + ), + environment: environment + ) let triple = try Triple.getHostTriple(usingSwiftCompiler: hostToolchain.swiftCompilerPath) var commandError: Error? = nil diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index 816fb2dcbe7..135fc1f05c9 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -173,6 +173,7 @@ public class Workspace { /// - delegate: Delegate for workspace events public convenience init( fileSystem: any FileSystem, + environment: EnvironmentVariables = .process(), location: Location, authorizationProvider: (any AuthorizationProvider)? = .none, registryAuthorizationProvider: (any AuthorizationProvider)? = .none, @@ -189,6 +190,7 @@ public class Workspace { ) throws { try self.init( fileSystem: fileSystem, + environment: environment, location: location, authorizationProvider: authorizationProvider, registryAuthorizationProvider: registryAuthorizationProvider, @@ -236,6 +238,7 @@ public class Workspace { /// - delegate: Delegate for workspace events public convenience init( fileSystem: FileSystem? = .none, + environment: EnvironmentVariables = .process(), forRootPackage packagePath: AbsolutePath, authorizationProvider: AuthorizationProvider? = .none, registryAuthorizationProvider: AuthorizationProvider? = .none, @@ -243,6 +246,7 @@ public class Workspace { cancellator: Cancellator? = .none, initializationWarningHandler: ((String) -> Void)? = .none, // optional customization used for advanced integration situations + customHostToolchain: UserToolchain? = .none, customManifestLoader: ManifestLoaderProtocol? = .none, customPackageContainerProvider: PackageContainerProvider? = .none, customRepositoryProvider: RepositoryProvider? = .none, @@ -253,12 +257,14 @@ public class Workspace { let location = try Location(forRootPackage: packagePath, fileSystem: fileSystem) try self.init( fileSystem: fileSystem, + environment: environment, location: location, authorizationProvider: authorizationProvider, registryAuthorizationProvider: registryAuthorizationProvider, configuration: configuration, cancellator: cancellator, initializationWarningHandler: initializationWarningHandler, + customHostToolchain: customHostToolchain, customManifestLoader: customManifestLoader, customPackageContainerProvider: customPackageContainerProvider, customRepositoryProvider: customRepositoryProvider, @@ -331,6 +337,7 @@ public class Workspace { public static func _init( // core fileSystem: FileSystem, + environment: EnvironmentVariables, location: Location, authorizationProvider: AuthorizationProvider? = .none, registryAuthorizationProvider: AuthorizationProvider? = .none, @@ -359,6 +366,7 @@ public class Workspace { ) throws -> Workspace { try .init( fileSystem: fileSystem, + environment: environment, location: location, authorizationProvider: authorizationProvider, registryAuthorizationProvider: registryAuthorizationProvider, @@ -388,6 +396,7 @@ public class Workspace { private init( // core fileSystem: FileSystem, + environment: EnvironmentVariables, location: Location, authorizationProvider: AuthorizationProvider?, registryAuthorizationProvider: AuthorizationProvider?, @@ -426,7 +435,13 @@ public class Workspace { ) let currentToolsVersion = customToolsVersion ?? ToolsVersion.current - let hostToolchain = try customHostToolchain ?? UserToolchain(swiftSDK: .hostSwiftSDK()) + let hostToolchain = try customHostToolchain ?? UserToolchain( + swiftSDK: .hostSwiftSDK( + environment: environment + ), + environment: environment, + fileSystem: fileSystem + ) var manifestLoader = customManifestLoader ?? ManifestLoader( toolchain: hostToolchain, cacheDir: location.sharedManifestsCacheDirectory, diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index a31aba3a982..60debafba76 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -219,13 +219,16 @@ struct SwiftBootstrapBuildTool: ParsableCommand { ] init(fileSystem: FileSystem, observabilityScope: ObservabilityScope, logLevel: Basics.Diagnostic.Severity) throws { - guard let cwd: AbsolutePath = fileSystem.currentWorkingDirectory else { - throw ExitCode.failure - } - self.identityResolver = DefaultIdentityResolver() self.dependencyMapper = DefaultDependencyMapper(identityResolver: self.identityResolver) - self.hostToolchain = try UserToolchain(swiftSDK: SwiftSDK.hostSwiftSDK(originalWorkingDirectory: cwd)) + let environment = EnvironmentVariables.process() + self.hostToolchain = try UserToolchain( + swiftSDK: SwiftSDK.hostSwiftSDK( + environment: environment, + fileSystem: fileSystem + ), + environment: environment + ) self.targetToolchain = hostToolchain // TODO: support cross-compilation? self.fileSystem = fileSystem self.observabilityScope = observabilityScope diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 50fda5b1f66..0a811c59dce 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -32,7 +32,6 @@ import Workspace import XCTest import struct TSCBasic.ByteString -import class TSCBasic.InMemoryFileSystem import enum TSCUtility.Diagnostics @@ -4520,6 +4519,7 @@ final class BuildPlanTests: XCTestCase { "/Pkg/Sources/lib/lib.c", "/Pkg/Sources/lib/include/lib.h" ) + try fs.createMockToolchain() let observability = ObservabilitySystem.makeForTesting() let graph = try loadModulesGraph( @@ -4542,12 +4542,14 @@ final class BuildPlanTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let userSwiftSDK = try SwiftSDK( + hostTriple: .arm64Linux, + targetTriple: .wasi, toolset: .init( knownTools: [ .cCompiler: .init(extraCLIOptions: ["-I/fake/sdk/sysroot", "-clang-flag-from-json"]), .swiftCompiler: .init(extraCLIOptions: ["-use-ld=lld", "-swift-flag-from-json"]), ], - rootPaths: UserToolchain.default.swiftSDK.toolset.rootPaths + rootPaths: UserToolchain.mockHostToolchain(fs).swiftSDK.toolset.rootPaths ), pathsConfiguration: .init( sdkRootPath: "/fake/sdk", @@ -4555,7 +4557,7 @@ final class BuildPlanTests: XCTestCase { swiftStaticResourcesPath: "/fake/lib/swift_static" ) ) - let mockToolchain = try UserToolchain(swiftSDK: userSwiftSDK) + let mockToolchain = try UserToolchain(swiftSDK: userSwiftSDK, environment: .mockEnvironment, fileSystem: fs) let commonFlags = BuildFlags( cCompilerFlags: ["-clang-command-line-flag"], swiftCompilerFlags: ["-swift-command-line-flag"] @@ -4575,11 +4577,7 @@ final class BuildPlanTests: XCTestCase { let lib = try result.target(for: "lib").clangTarget() var args: [StringPattern] = [.anySequence] - #if os(macOS) - args += ["-isysroot"] - #else args += ["--sysroot"] - #endif args += [ "\(userSwiftSDK.pathsConfiguration.sdkRootPath!)", "-I/fake/sdk/sysroot", @@ -4651,6 +4649,7 @@ final class BuildPlanTests: XCTestCase { "/Pkg/Sources/cxxLib/cxxLib.c", "/Pkg/Sources/cxxLib/include/cxxLib.h" ) + try fileSystem.createMockToolchain() let observability = ObservabilitySystem.makeForTesting() let graph = try loadModulesGraph( @@ -4683,18 +4682,19 @@ final class BuildPlanTests: XCTestCase { .librarian: .init(path: "/fake/toolchain/usr/bin/librarian"), .linker: .init(path: "/fake/toolchain/usr/bin/linker", extraCLIOptions: [jsonFlag(tool: .linker)]), ], - rootPaths: UserToolchain.default.swiftSDK.toolset.rootPaths + rootPaths: UserToolchain.mockHostToolchain(fileSystem).swiftSDK.toolset.rootPaths ) let targetTriple = try Triple("armv7em-unknown-none-macho") - let swiftSDK = try SwiftSDK( + let swiftSDK = SwiftSDK( + hostTriple: .arm64Linux, targetTriple: targetTriple, - properties: .init( + toolset: toolset, + pathsConfiguration: .init( sdkRootPath: "/fake/sdk", swiftStaticResourcesPath: "/usr/lib/swift_static/none" - ), - toolset: toolset + ) ) - let toolchain = try UserToolchain(swiftSDK: swiftSDK) + let toolchain = try UserToolchain(swiftSDK: swiftSDK, environment: .mockEnvironment, fileSystem: fileSystem) let result = try BuildPlanResult(plan: mockBuildPlan( triple: targetTriple, toolchain: toolchain, @@ -4815,6 +4815,7 @@ final class BuildPlanTests: XCTestCase { "/Pkg/Sources/cLib/cLib.c", "/Pkg/Sources/cLib/include/cLib.h" ) + try fileSystem.createMockToolchain() let observability = ObservabilitySystem.makeForTesting() let graph = try loadModulesGraph( @@ -4847,7 +4848,7 @@ final class BuildPlanTests: XCTestCase { .swiftCompiler: .init(extraCLIOptions: ["-use-ld=lld"]), ]) ) - let toolchain = try UserToolchain(swiftSDK: swiftSDK) + let toolchain = try UserToolchain(swiftSDK: swiftSDK, environment: .mockEnvironment, fileSystem: fileSystem) let result = try BuildPlanResult(plan: mockBuildPlan( toolchain: toolchain, graph: graph, diff --git a/Tests/BuildTests/PluginsBuildPlanTests.swift b/Tests/BuildTests/PluginsBuildPlanTests.swift index 7b2a1a78378..62ea679cf06 100644 --- a/Tests/BuildTests/PluginsBuildPlanTests.swift +++ b/Tests/BuildTests/PluginsBuildPlanTests.swift @@ -36,7 +36,7 @@ final class PluginsBuildPlanTests: XCTestCase { try XCTSkipIf(true, "test is only supported on macOS") #endif - let hostToolchain = try UserToolchain(swiftSDK: .hostSwiftSDK()) + let hostToolchain = try UserToolchain(swiftSDK: .hostSwiftSDK(environment: .empty()), environment: .empty()) let hostTriple = try! hostToolchain.targetTriple.withoutVersion().tripleString let x86Triple = "x86_64-apple-macosx" diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index 162eb50e4b7..0535c88fccc 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2022 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -1842,7 +1842,11 @@ final class PackageCommandTests: CommandsTestCase { """ ) - let hostTriple = try UserToolchain(swiftSDK: .hostSwiftSDK()).targetTriple + let environment = EnvironmentVariables.process() + let hostTriple = try UserToolchain( + swiftSDK: .hostSwiftSDK(environment: environment), + environment: environment + ).targetTriple let hostTripleString = if hostTriple.isDarwin() { hostTriple.tripleString(forPlatformVersion: "") } else { diff --git a/Tests/CommandsTests/SwiftCommandStateTests.swift b/Tests/CommandsTests/SwiftCommandStateTests.swift index 1e3f4afe6ce..05cbfe26bc5 100644 --- a/Tests/CommandsTests/SwiftCommandStateTests.swift +++ b/Tests/CommandsTests/SwiftCommandStateTests.swift @@ -22,8 +22,8 @@ import func PackageGraph.loadModulesGraph import SPMTestSupport import XCTest +import enum TSCBasic.ProcessEnv import class TSCBasic.BufferedOutputByteStream -import class TSCBasic.InMemoryFileSystem import protocol TSCBasic.OutputByteStream import var TSCBasic.stderrStream @@ -246,7 +246,7 @@ final class SwiftCommandStateTests: CommandsTestCase { ]) let observer = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph(fileSystem: fs, manifests: [ + let graph = try loadModulesGraph(fileSystem: fs, manifests: [ Manifest.createRootManifest(displayName: "Pkg", path: "/Pkg", targets: [TargetDescription(name: "exe")]) @@ -319,12 +319,79 @@ final class SwiftCommandStateTests: CommandsTestCase { try XCTAssertMatch(plan.buildProducts.compactMap { $0 as? Build.ProductBuildDescription }.first?.linkArguments() ?? [], [.anySequence, "-gnone", .anySequence]) } + + func testToolchainArgument() throws { + let customTargetToolchain = AbsolutePath("/path/to/toolchain") + let hostSwiftcPath = AbsolutePath("/usr/bin/swiftc") + let hostArPath = AbsolutePath("/usr/bin/ar") + let targetSwiftcPath = customTargetToolchain.appending(components: ["usr", "bin" , "swiftc"]) + let targetArPath = customTargetToolchain.appending(components: ["usr", "bin", "llvm-ar"]) + + let fs = InMemoryFileSystem(emptyFiles: [ + "/Pkg/Sources/exe/main.swift", + hostSwiftcPath.pathString, + hostArPath.pathString, + targetSwiftcPath.pathString, + targetArPath.pathString + ]) + + for path in [hostSwiftcPath, hostArPath, targetSwiftcPath, targetArPath,] { + try fs.updatePermissions(path, isExecutable: true) + } + + let observer = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Pkg", + path: "/Pkg", + targets: [TargetDescription(name: "exe")] + ) + ], + observabilityScope: observer.topScope + ) + + let options = try GlobalOptions.parse( + [ + "--toolchain", customTargetToolchain.pathString, + "--triple", "x86_64-unknown-linux-gnu", + ] + ) + let swiftCommandState = try SwiftCommandState.makeMockState( + options: options, + fileSystem: fs, + environment: ["PATH": "/usr/bin"] + ) + XCTAssertEqual(swiftCommandState.originalWorkingDirectory, fs.currentWorkingDirectory) + XCTAssertEqual( + try swiftCommandState.getTargetToolchain().swiftCompilerPath, + targetSwiftcPath + ) + XCTAssertEqual( + try swiftCommandState.getTargetToolchain().swiftSDK.toolset.knownTools[.swiftCompiler]?.path, + nil + ) + let plan = try BuildPlan( + destinationBuildParameters: swiftCommandState.productsBuildParameters, + toolsBuildParameters: swiftCommandState.toolsBuildParameters, + graph: graph, + fileSystem: fs, + observabilityScope: observer.topScope + ) + + let arguments = try plan.buildProducts.compactMap { $0 as? Build.ProductBuildDescription }.first?.linkArguments() ?? [] + + XCTAssertMatch(arguments, [.contains("/path/to/toolchain")]) + } } extension SwiftCommandState { static func makeMockState( outputStream: OutputByteStream = stderrStream, - options: GlobalOptions + options: GlobalOptions, + fileSystem: any FileSystem = localFileSystem, + environment: EnvironmentVariables = .process() ) throws -> SwiftCommandState { return try SwiftCommandState( outputStream: outputStream, @@ -343,6 +410,10 @@ extension SwiftCommandState { fileSystem: $0, observabilityScope: $1 ) - }) + }, + hostTriple: .arm64Linux, + fileSystem: fileSystem, + environment: environment + ) } } diff --git a/Tests/PackageModelTests/PackageModelTests.swift b/Tests/PackageModelTests/PackageModelTests.swift index 7ba89f6927d..0f5b61616c3 100644 --- a/Tests/PackageModelTests/PackageModelTests.swift +++ b/Tests/PackageModelTests/PackageModelTests.swift @@ -20,7 +20,7 @@ import XCTest import struct TSCBasic.ByteString -class PackageModelTests: XCTestCase { +final class PackageModelTests: XCTestCase { func testProductTypeCodable() throws { struct Foo: Codable, Equatable { var type: ProductType @@ -61,8 +61,11 @@ class PackageModelTests: XCTestCase { func testAndroidCompilerFlags() throws { let triple = try Triple("x86_64-unknown-linux-android") + let fileSystem = InMemoryFileSystem() let sdkDir = AbsolutePath("/some/path/to/an/SDK.sdk") + try fileSystem.createDirectory(sdkDir, recursive: true) let toolchainPath = AbsolutePath("/some/path/to/a/toolchain.xctoolchain") + try fileSystem.createDirectory(toolchainPath, recursive: true) let swiftSDK = SwiftSDK( targetTriple: triple, @@ -71,10 +74,16 @@ class PackageModelTests: XCTestCase { ) XCTAssertEqual( - try UserToolchain.deriveSwiftCFlags(triple: triple, swiftSDK: swiftSDK, environment: .process()), + try UserToolchain.deriveSwiftCFlags( + triple: triple, + swiftSDK: swiftSDK, + environment: .process(), + fileSystem: fileSystem + ), [ // Needed when cross‐compiling for Android. 2020‐03‐01 - "-sdk", sdkDir.pathString, + "-sdk", + sdkDir.pathString, ] ) } @@ -123,7 +132,8 @@ class PackageModelTests: XCTestCase { try XCTAssertEqual( UserToolchain.determineLibrarian( triple: triple, binDirectories: [bin], useXcrun: false, environment: [:], searchPaths: [], - extraSwiftFlags: ["-Xswiftc", "-use-ld=lld"] + extraSwiftFlags: ["-Xswiftc", "-use-ld=lld"], + fileSystem: fs ), lld ) @@ -131,7 +141,8 @@ class PackageModelTests: XCTestCase { try XCTAssertEqual( UserToolchain.determineLibrarian( triple: triple, binDirectories: [bin], useXcrun: false, environment: [:], searchPaths: [], - extraSwiftFlags: ["-Xswiftc", "-use-ld=not-link"] + extraSwiftFlags: ["-Xswiftc", "-use-ld=not-link"], + fileSystem: fs ), not ) @@ -139,7 +150,8 @@ class PackageModelTests: XCTestCase { try XCTAssertThrowsError( UserToolchain.determineLibrarian( triple: triple, binDirectories: [bin], useXcrun: false, environment: [:], searchPaths: [], - extraSwiftFlags: [] + extraSwiftFlags: [], + fileSystem: fs ) ) } @@ -173,7 +185,8 @@ class PackageModelTests: XCTestCase { binDirectories: [toolchainBinDir], useXcrun: false, environment: [:], - searchPaths: binDirs + searchPaths: binDirs, + fileSystem: fs ) // The first swiftc in the search paths should be chosen. diff --git a/Tests/PackageModelTests/SwiftSDKBundleTests.swift b/Tests/PackageModelTests/SwiftSDKBundleTests.swift index 5650d09ec33..58b9e96e462 100644 --- a/Tests/PackageModelTests/SwiftSDKBundleTests.swift +++ b/Tests/PackageModelTests/SwiftSDKBundleTests.swift @@ -366,7 +366,7 @@ final class SwiftSDKBundleTests: XCTestCase { ]) } - func testTargetSDKDeriviation() async throws { + func testTargetSDKDerivation() async throws { let toolsetRootPath = AbsolutePath("/path/to/toolpath") let (fileSystem, bundles, swiftSDKsDirectory) = try generateTestFileSystem( bundleArtifacts: [ @@ -375,7 +375,7 @@ final class SwiftSDKBundleTests: XCTestCase { ] ) let system = ObservabilitySystem.makeForTesting() - let hostSwiftSDK = try SwiftSDK.hostSwiftSDK() + let hostSwiftSDK = try SwiftSDK.hostSwiftSDK(environment: .empty()) let hostTriple = try! Triple("arm64-apple-macosx14.0") let archiver = MockArchiver() let store = SwiftSDKBundleStore( @@ -447,6 +447,10 @@ final class SwiftSDKBundleTests: XCTestCase { ) XCTAssertEqual(targetSwiftSDK.architectures, archs) XCTAssertEqual(targetSwiftSDK.pathsConfiguration.sdkRootPath, customCompileSDK) + XCTAssertEqual( + targetSwiftSDK.toolset.rootPaths, + [customCompileToolchain.appending(components: ["usr", "bin"])] + hostSwiftSDK.toolset.rootPaths + ) } } } diff --git a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift index 60a8e2a886b..c558f536eb8 100644 --- a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift +++ b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift @@ -30,7 +30,6 @@ import class TSCBasic.InMemoryFileSystem import struct TSCUtility.SerializedDiagnostics final class PluginInvocationTests: XCTestCase { - func testBasics() throws { // Construct a canned file system and package graph with a single package and a library that uses a build tool plugin that invokes a tool. let fileSystem = InMemoryFileSystem(emptyFiles: @@ -1053,10 +1052,17 @@ final class PluginInvocationTests: XCTestCase { ///////// // Load a workspace from the package. let observability = ObservabilitySystem.makeForTesting() + let environment = EnvironmentVariables.process() let workspace = try Workspace( fileSystem: localFileSystem, location: try Workspace.Location(forRootPackage: packageDir, fileSystem: localFileSystem), - customHostToolchain: UserToolchain(swiftSDK: .hostSwiftSDK(), customLibrariesLocation: .init(manifestLibraryPath: fakeExtraModulesDir, pluginLibraryPath: fakeExtraModulesDir)), + customHostToolchain: UserToolchain( + swiftSDK: .hostSwiftSDK( + environment: environment + ), + environment: environment, + customLibrariesLocation: .init(manifestLibraryPath: fakeExtraModulesDir, pluginLibraryPath: fakeExtraModulesDir) + ), customManifestLoader: ManifestLoader(toolchain: UserToolchain.default), delegate: MockWorkspaceDelegate() ) @@ -1238,7 +1244,8 @@ final class PluginInvocationTests: XCTestCase { targetTriple: hostTriple, toolset: swiftSDK.toolset, pathsConfiguration: swiftSDK.pathsConfiguration - ) + ), + environment: .process() ) // Create a plugin script runner for the duration of the test. diff --git a/Tests/SourceControlTests/InMemoryGitRepositoryTests.swift b/Tests/SourceControlTests/InMemoryGitRepositoryTests.swift index 8b643ec8a4f..cef996045e2 100644 --- a/Tests/SourceControlTests/InMemoryGitRepositoryTests.swift +++ b/Tests/SourceControlTests/InMemoryGitRepositoryTests.swift @@ -15,8 +15,6 @@ import SourceControl import SPMTestSupport import XCTest -import class TSCBasic.InMemoryFileSystem - class InMemoryGitRepositoryTests: XCTestCase { func testBasics() throws { let fs = InMemoryFileSystem() diff --git a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift index 2520b3a2731..1f27ef4aceb 100644 --- a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift +++ b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Copyright (c) 2023-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -19,10 +19,9 @@ import PackageGraph import PackageModel import SourceKitLSPAPI import SPMTestSupport -import TSCBasic import XCTest -class SourceKitLSPAPITests: XCTestCase { +final class SourceKitLSPAPITests: XCTestCase { func testBasicSwiftPackage() throws { let fs = InMemoryFileSystem(emptyFiles: "/Pkg/Sources/exe/main.swift", diff --git a/Tests/WorkspaceTests/RegistryPackageContainerTests.swift b/Tests/WorkspaceTests/RegistryPackageContainerTests.swift index ea669b6cff5..4ff02d352b3 100644 --- a/Tests/WorkspaceTests/RegistryPackageContainerTests.swift +++ b/Tests/WorkspaceTests/RegistryPackageContainerTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -20,14 +20,12 @@ import SPMTestSupport @testable import Workspace import XCTest -import class TSCBasic.InMemoryFileSystem - import struct TSCUtility.Version -class RegistryPackageContainerTests: XCTestCase { - +final class RegistryPackageContainerTests: XCTestCase { func testToolsVersionCompatibleVersions() async throws { let fs = InMemoryFileSystem() + try fs.createMockToolchain() let packageIdentity = PackageIdentity.plain("org.foo") let packageVersion = Version("1.0.0") @@ -88,8 +86,10 @@ class RegistryPackageContainerTests: XCTestCase { return try Workspace._init( fileSystem: fs, + environment: .mockEnvironment, location: .init(forRootPackage: packagePath, fileSystem: fs), customToolsVersion: toolsVersion, + customHostToolchain: .mockHostToolchain(fs), customManifestLoader: MockManifestLoader(manifests: [:]), customRegistryClient: registryClient ) @@ -122,6 +122,7 @@ class RegistryPackageContainerTests: XCTestCase { func testAlternateManifests() async throws { let fs = InMemoryFileSystem() + try fs.createMockToolchain() let packageIdentity = PackageIdentity.plain("org.foo") let packageVersion = Version("1.0.0") @@ -153,8 +154,10 @@ class RegistryPackageContainerTests: XCTestCase { return try Workspace._init( fileSystem: fs, + environment: .mockEnvironment, location: .init(forRootPackage: packagePath, fileSystem: fs), customToolsVersion: toolsVersion, + customHostToolchain: .mockHostToolchain(fs), customManifestLoader: MockManifestLoader(manifests: [:]), customRegistryClient: registryClient ) @@ -208,6 +211,7 @@ class RegistryPackageContainerTests: XCTestCase { func testLoadManifest() async throws { let fs = InMemoryFileSystem() + try fs.createMockToolchain() let packageIdentity = PackageIdentity.plain("org.foo") let packageVersion = Version("1.0.0") @@ -246,8 +250,10 @@ class RegistryPackageContainerTests: XCTestCase { return try Workspace._init( fileSystem: fs, + environment: .mockEnvironment, location: .init(forRootPackage: packagePath, fileSystem: fs), customToolsVersion: toolsVersion, + customHostToolchain: .mockHostToolchain(fs), customManifestLoader: MockManifestLoader(), customRegistryClient: registryClient ) diff --git a/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift b/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift index 199b1041bb9..de87b5c1a9e 100644 --- a/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift +++ b/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2020 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -20,8 +20,6 @@ import SPMTestSupport @testable import Workspace import XCTest -import class TSCBasic.InMemoryFileSystem - import enum TSCUtility.Git import struct TSCUtility.Version @@ -192,9 +190,10 @@ private let v1: Version = "1.0.0" private let v2: Version = "2.0.0" private let v1Range: VersionSetSpecifier = .range("1.0.0" ..< "2.0.0") -class SourceControlPackageContainerTests: XCTestCase { +final class SourceControlPackageContainerTests: XCTestCase { func testVprefixVersions() async throws { let fs = InMemoryFileSystem() + try fs.createMockToolchain() let repoPath = AbsolutePath.root let filePath = repoPath.appending("Package.swift") @@ -224,7 +223,9 @@ class SourceControlPackageContainerTests: XCTestCase { let provider = try Workspace._init( fileSystem: fs, + environment: .mockEnvironment, location: .init(forRootPackage: repoPath, fileSystem: fs), + customHostToolchain: .mockHostToolchain(fs), customManifestLoader: MockManifestLoader(manifests: [:]), customRepositoryManager: repositoryManager ) @@ -237,6 +238,7 @@ class SourceControlPackageContainerTests: XCTestCase { func testVersions() async throws { let fs = InMemoryFileSystem() + try fs.createMockToolchain() let repoPath = AbsolutePath.root let filePath = repoPath.appending("Package.swift") @@ -277,8 +279,10 @@ class SourceControlPackageContainerTests: XCTestCase { func createProvider(_ currentToolsVersion: ToolsVersion) throws -> PackageContainerProvider { return try Workspace._init( fileSystem: fs, + environment: .mockEnvironment, location: .init(forRootPackage: repoPath, fileSystem: fs), customToolsVersion: currentToolsVersion, + customHostToolchain: .mockHostToolchain(fs), customManifestLoader: MockManifestLoader(manifests: [:]), customRepositoryManager: repositoryManager ) @@ -330,6 +334,7 @@ class SourceControlPackageContainerTests: XCTestCase { func testPreReleaseVersions() async throws { let fs = InMemoryFileSystem() + try fs.createMockToolchain() let repoPath = AbsolutePath.root let filePath = repoPath.appending("Package.swift") @@ -361,7 +366,9 @@ class SourceControlPackageContainerTests: XCTestCase { let provider = try Workspace._init( fileSystem: fs, + environment: .mockEnvironment, location: .init(forRootPackage: repoPath, fileSystem: fs), + customHostToolchain: .mockHostToolchain(fs), customManifestLoader: MockManifestLoader(manifests: [:]), customRepositoryManager: repositoryManager ) @@ -374,6 +381,7 @@ class SourceControlPackageContainerTests: XCTestCase { func testSimultaneousVersions() async throws { let fs = InMemoryFileSystem() + try fs.createMockToolchain() let repoPath = AbsolutePath.root let filePath = repoPath.appending("Package.swift") @@ -410,7 +418,9 @@ class SourceControlPackageContainerTests: XCTestCase { let provider = try Workspace._init( fileSystem: fs, + environment: .mockEnvironment, location: .init(forRootPackage: repoPath, fileSystem: fs), + customHostToolchain: .mockHostToolchain(fs), customManifestLoader: MockManifestLoader(manifests: [:]), customRepositoryManager: repositoryManager ) @@ -594,6 +604,7 @@ class SourceControlPackageContainerTests: XCTestCase { ) let containerProvider = try Workspace._init( fileSystem: localFileSystem, + environment: .process(), location: .init(forRootPackage: packageDir, fileSystem: localFileSystem), customManifestLoader: MockManifestLoader(manifests: [.init(url: packageDir.pathString, version: nil): manifest]), customRepositoryManager: repositoryManager @@ -646,6 +657,7 @@ class SourceControlPackageContainerTests: XCTestCase { let containerProvider = try Workspace._init( fileSystem: localFileSystem, + environment: .process(), location: .init(forRootPackage: packageDirectory, fileSystem: localFileSystem), customManifestLoader: MockManifestLoader( manifests: [ @@ -757,6 +769,7 @@ class SourceControlPackageContainerTests: XCTestCase { ) let containerProvider = try Workspace._init( fileSystem: localFileSystem, + environment: .process(), location: .init(forRootPackage: packageDirectory, fileSystem: localFileSystem), customManifestLoader: MockManifestLoader( manifests: [.init(url: packageDirectory.pathString, version: Version(1, 0, 0)): manifest] diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index bbbcd8943af..ae996deb4cc 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -24,7 +24,6 @@ import SPMTestSupport import XCTest import struct TSCBasic.ByteString -import class TSCBasic.InMemoryFileSystem import struct TSCUtility.Version @@ -7833,6 +7832,7 @@ final class WorkspaceTests: XCTestCase { func testArtifactChecksum() throws { let fs = InMemoryFileSystem() + try fs.createMockToolchain() let sandbox = AbsolutePath("/tmp/ws/") try fs.createDirectory(sandbox, recursive: true) @@ -7840,7 +7840,7 @@ final class WorkspaceTests: XCTestCase { let binaryArtifactsManager = try Workspace.BinaryArtifactsManager( fileSystem: fs, authorizationProvider: .none, - hostToolchain: UserToolchain(swiftSDK: .hostSwiftSDK()), + hostToolchain: UserToolchain.mockHostToolchain(fs), checksumAlgorithm: checksumAlgorithm, cachePath: .none, customHTTPClient: .none, @@ -9079,8 +9079,9 @@ final class WorkspaceTests: XCTestCase { func testDownloadArchiveIndexFilesHappyPath() throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() + try fs.createMockToolchain() let downloads = ThreadSafeKeyValueStore() - let hostToolchain = try UserToolchain(swiftSDK: .hostSwiftSDK()) + let hostToolchain = try UserToolchain.mockHostToolchain(fs) let ariFiles = [ """ @@ -9369,7 +9370,8 @@ final class WorkspaceTests: XCTestCase { func testDownloadArchiveIndexFileBadChecksum() throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() - let hostToolchain = try UserToolchain(swiftSDK: .hostSwiftSDK()) + try fs.createMockToolchain() + let hostToolchain = try UserToolchain.mockHostToolchain(fs) let ari = """ { @@ -9488,7 +9490,8 @@ final class WorkspaceTests: XCTestCase { func testDownloadArchiveIndexFileBadArchivesChecksum() throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() - let hostToolchain = try UserToolchain(swiftSDK: .hostSwiftSDK()) + try fs.createMockToolchain() + let hostToolchain = try UserToolchain.mockHostToolchain(fs) let ari = """ { @@ -9598,7 +9601,8 @@ final class WorkspaceTests: XCTestCase { func testDownloadArchiveIndexFileArchiveNotFound() throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() - let hostToolchain = try UserToolchain(swiftSDK: .hostSwiftSDK()) + try fs.createMockToolchain() + let hostToolchain = try UserToolchain.mockHostToolchain(fs) let ari = """ { @@ -9673,8 +9677,9 @@ final class WorkspaceTests: XCTestCase { func testDownloadArchiveIndexTripleNotFound() throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() + try fs.createMockToolchain() - let hostToolchain = try UserToolchain(swiftSDK: .hostSwiftSDK()) + let hostToolchain = try UserToolchain.mockHostToolchain(fs) let androidTriple = try Triple("x86_64-unknown-linux-android") let macTriple = try Triple("arm64-apple-macosx") let notHostTriple = hostToolchain.targetTriple == androidTriple ? macTriple : androidTriple @@ -12278,6 +12283,7 @@ final class WorkspaceTests: XCTestCase { } let fs = InMemoryFileSystem() + try fs.createMockToolchain() let observability = ObservabilitySystem.makeForTesting() // write a manifest @@ -12288,12 +12294,16 @@ final class WorkspaceTests: XCTestCase { fileSystem: fs ) + let customHostToolchain = try UserToolchain.mockHostToolchain(fs) + do { // no error let delegate = MockWorkspaceDelegate() let workspace = try Workspace( fileSystem: fs, + environment: .mockEnvironment, forRootPackage: .root, + customHostToolchain: customHostToolchain, customManifestLoader: TestLoader(error: .none), delegate: delegate ) @@ -12309,7 +12319,9 @@ final class WorkspaceTests: XCTestCase { let delegate = MockWorkspaceDelegate() let workspace = try Workspace( fileSystem: fs, + environment: .mockEnvironment, forRootPackage: .root, + customHostToolchain: customHostToolchain, customManifestLoader: TestLoader(error: StringError("boom")), delegate: delegate )