diff --git a/Sources/SWBApplePlatform/AssetCatalogCompiler.swift b/Sources/SWBApplePlatform/AssetCatalogCompiler.swift index 5c589030..45bb476a 100644 --- a/Sources/SWBApplePlatform/AssetCatalogCompiler.swift +++ b/Sources/SWBApplePlatform/AssetCatalogCompiler.swift @@ -55,7 +55,7 @@ public final class ActoolCompilerSpec : GenericCompilerSpec, SpecIdentifierType, } private func assetTagCombinations(catalogInputs inputs: [FileToBuild], _ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async throws -> Set> { - return try await executeExternalTool(cbc, delegate, commandLine: [resolveExecutablePath(cbc, cbc.scope.actoolExecutablePath()).str, "--print-asset-tag-combinations", "--output-format", "xml1"] + inputs.map { $0.absolutePath.str }, workingDirectory: cbc.producer.defaultWorkingDirectory.str, environment: environmentFromSpec(cbc, delegate).bindingsDictionary, executionDescription: "Compute asset tag combinations") { output in + return try await executeExternalTool(cbc, delegate, commandLine: [resolveExecutablePath(cbc, cbc.scope.actoolExecutablePath()).str, "--print-asset-tag-combinations", "--output-format", "xml1"] + inputs.map { $0.absolutePath.str }, workingDirectory: cbc.producer.defaultWorkingDirectory, environment: environmentFromSpec(cbc, delegate).bindingsDictionary, executionDescription: "Compute asset tag combinations") { output in struct AssetCatalogToolOutput: Decodable { struct Diagnostic: Decodable { let description: String diff --git a/Sources/SWBApplePlatform/CoreDataCompiler.swift b/Sources/SWBApplePlatform/CoreDataCompiler.swift index 1a324cf8..9e3bc3ea 100644 --- a/Sources/SWBApplePlatform/CoreDataCompiler.swift +++ b/Sources/SWBApplePlatform/CoreDataCompiler.swift @@ -67,7 +67,7 @@ public final class CoreDataModelCompilerSpec : GenericCompilerSpec, SpecIdentifi // Mark the entire directory structure as being watched by the build system. delegate.access(path: input.absolutePath) - generatedFiles = try await generatedFilePaths(cbc, delegate, commandLine: [commandLine[0]] + ["--dry-run"] + commandLine[1...], workingDirectory: cbc.producer.defaultWorkingDirectory.str, environment: self.environmentFromSpec(cbc, delegate).bindingsDictionary, executionDescription: "Compute data model \(input.absolutePath.basename) code generation output paths") { output in + generatedFiles = try await generatedFilePaths(cbc, delegate, commandLine: [commandLine[0]] + ["--dry-run"] + commandLine[1...], workingDirectory: cbc.producer.defaultWorkingDirectory, environment: self.environmentFromSpec(cbc, delegate).bindingsDictionary, executionDescription: "Compute data model \(input.absolutePath.basename) code generation output paths") { output in return output.unsafeStringValue.split(separator: "\n").map(Path.init).map { $0.prependingPrivatePrefixIfNeeded(otherPath: outputDir) } } guard !generatedFiles.isEmpty else { diff --git a/Sources/SWBApplePlatform/CoreMLCompiler.swift b/Sources/SWBApplePlatform/CoreMLCompiler.swift index dfa14648..17225c0a 100644 --- a/Sources/SWBApplePlatform/CoreMLCompiler.swift +++ b/Sources/SWBApplePlatform/CoreMLCompiler.swift @@ -241,7 +241,7 @@ public final class CoreMLCompilerSpec : GenericCompilerSpec, SpecIdentifierType, // Mark the file as being watched by the build system to invalidate the build description. delegate.access(path: input.absolutePath) - generatedFiles = try await generatedFilePaths(cbc, delegate, commandLine: commandLine[0...3] + ["--dry-run", "yes"] + commandLine[4...], workingDirectory: cbc.producer.defaultWorkingDirectory.str, environment: self.environmentFromSpec(cbc, delegate).bindingsDictionary, executionDescription: "Compute CoreML model \(input.absolutePath.basename) code generation output paths") { output in + generatedFiles = try await generatedFilePaths(cbc, delegate, commandLine: commandLine[0...3] + ["--dry-run", "yes"] + commandLine[4...], workingDirectory: cbc.producer.defaultWorkingDirectory, environment: self.environmentFromSpec(cbc, delegate).bindingsDictionary, executionDescription: "Compute CoreML model \(input.absolutePath.basename) code generation output paths") { output in return output.unsafeStringValue.split(separator: "\n").map(Path.init) } guard !generatedFiles.isEmpty else { diff --git a/Sources/SWBApplePlatform/IntentsCompiler.swift b/Sources/SWBApplePlatform/IntentsCompiler.swift index e31cf5a4..6d879dd1 100644 --- a/Sources/SWBApplePlatform/IntentsCompiler.swift +++ b/Sources/SWBApplePlatform/IntentsCompiler.swift @@ -194,7 +194,7 @@ public final class IntentsCompilerSpec : GenericCompilerSpec, SpecIdentifierType // Mark the file as being watched by the build system to invalidate the build description. delegate.access(path: input.absolutePath) - generatedFiles = try await generatedFilePaths(cbc, delegate, commandLine: commandLine[0...1] + ["-dryRun"] + commandLine[2...], workingDirectory: cbc.producer.defaultWorkingDirectory.str, environment: self.environmentFromSpec(cbc, delegate).bindingsDictionary, executionDescription: "Compute Intent Definition \(input.absolutePath.basename) code generation output paths") { output in + generatedFiles = try await generatedFilePaths(cbc, delegate, commandLine: commandLine[0...1] + ["-dryRun"] + commandLine[2...], workingDirectory: cbc.producer.defaultWorkingDirectory, environment: self.environmentFromSpec(cbc, delegate).bindingsDictionary, executionDescription: "Compute Intent Definition \(input.absolutePath.basename) code generation output paths") { output in return output.unsafeStringValue.split(separator: "\n").map(Path.init).map { $0.prependingPrivatePrefixIfNeeded(otherPath: outputDir) } } guard !generatedFiles.isEmpty else { diff --git a/Sources/SWBApplePlatform/XCStringsCompiler.swift b/Sources/SWBApplePlatform/XCStringsCompiler.swift index 07f6dd8b..2a6eccd7 100644 --- a/Sources/SWBApplePlatform/XCStringsCompiler.swift +++ b/Sources/SWBApplePlatform/XCStringsCompiler.swift @@ -62,7 +62,7 @@ public final class XCStringsCompilerSpec: GenericCompilerSpec, SpecIdentifierTyp // xcstringstool compile --dry-run dryRunCommandLine.insert("--dry-run", at: 2) - outputs = try await generatedFilePaths(cbc, delegate, commandLine: dryRunCommandLine, workingDirectory: cbc.producer.defaultWorkingDirectory.str, environment: environmentFromSpec(cbc, delegate).bindingsDictionary, executionDescription: "Compute XCStrings \(cbc.input.absolutePath.basename) output paths") { output in + outputs = try await generatedFilePaths(cbc, delegate, commandLine: dryRunCommandLine, workingDirectory: cbc.producer.defaultWorkingDirectory, environment: environmentFromSpec(cbc, delegate).bindingsDictionary, executionDescription: "Compute XCStrings \(cbc.input.absolutePath.basename) output paths") { output in return output.unsafeStringValue.split(separator: "\n").map(Path.init) } } catch { diff --git a/Sources/SWBBuildService/BuildDependencyInfo.swift b/Sources/SWBBuildService/BuildDependencyInfo.swift index 223fa75f..09386ef3 100644 --- a/Sources/SWBBuildService/BuildDependencyInfo.swift +++ b/Sources/SWBBuildService/BuildDependencyInfo.swift @@ -469,7 +469,7 @@ extension BuildDependencyInfo { /// Special `CoreClientDelegate`-conforming struct because our use of `GlobalProductPlan` here should never be running external tools. fileprivate struct UnsupportedCoreClientDelegate: CoreClientDelegate { - func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { throw StubError.error("Running external tools is not supported when computing build dependency target info.") } } diff --git a/Sources/SWBBuildService/ClientExchangeDelegate.swift b/Sources/SWBBuildService/ClientExchangeDelegate.swift index 57ffc94e..b391870e 100644 --- a/Sources/SWBBuildService/ClientExchangeDelegate.swift +++ b/Sources/SWBBuildService/ClientExchangeDelegate.swift @@ -33,7 +33,7 @@ final class ClientExchangeDelegate: ClientDelegate { self.session = session } - func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { // Create a synchronous client exchange which the session uses to handle the response from the client, to make the communication synchronous from the point of view of our caller. let exchange = SynchronousClientExchange(session) diff --git a/Sources/SWBBuildSystem/BuildOperation.swift b/Sources/SWBBuildSystem/BuildOperation.swift index 9efc58ee..a74cd34d 100644 --- a/Sources/SWBBuildSystem/BuildOperation.swift +++ b/Sources/SWBBuildSystem/BuildOperation.swift @@ -1122,14 +1122,14 @@ private struct OperatorSystemAdaptorDynamicContext: DynamicTaskExecutionDelegate } @discardableResult - func spawn(commandLine: [String], environment: [String: String], workingDirectory: String, processDelegate: any ProcessDelegate) async throws -> Bool { + func spawn(commandLine: [String], environment: [String: String], workingDirectory: Path, processDelegate: any ProcessDelegate) async throws -> Bool { guard let jobContext else { throw StubError.error("API misuse. Spawning processes is only allowed from `performTaskAction`.") } // This calls into llb_buildsystem_command_interface_spawn, which can block, so ensure it's shunted to a new thread so as not to block the Swift Concurrency thread pool. This shouldn't risk thread explosion because this function is only allowed to be called from performTaskAction, which in turn should be bounded to ncores based on the number of active llbuild lane threads. return await _Concurrency.Task.detachNewThread(name: "llb_buildsystem_command_interface_spawn") { [commandInterface, jobContext, processDelegate] in - commandInterface.spawn(jobContext, commandLine: commandLine, environment: environment, workingDirectory: workingDirectory, processDelegate: processDelegate) + commandInterface.spawn(jobContext, commandLine: commandLine, environment: environment, workingDirectory: workingDirectory.str, processDelegate: processDelegate) } } diff --git a/Sources/SWBCore/ProcessExecutionCache.swift b/Sources/SWBCore/ProcessExecutionCache.swift index ef49b02a..1030561c 100644 --- a/Sources/SWBCore/ProcessExecutionCache.swift +++ b/Sources/SWBCore/ProcessExecutionCache.swift @@ -14,12 +14,20 @@ public import SWBUtil public final class ProcessExecutionCache: Sendable { private let cache = AsyncCache<[String], Processes.ExecutionResult>() + private let workingDirectory: Path? - public init() { } + public init(workingDirectory: Path? = .root) { + // FIXME: Work around lack of thread-safe working directory support in Foundation (Amazon Linux 2, OpenBSD). Executing processes in the current working directory is less deterministic, but all of the clients which use this class are generally not expected to be sensitive to the working directory anyways. This workaround can be removed once we drop support for Amazon Linux 2 and/or adopt swift-subprocess and/or Foundation.Process's working directory support is made thread safe. + if try! Process.hasUnsafeWorkingDirectorySupport { + self.workingDirectory = nil + return + } + self.workingDirectory = workingDirectory + } public func run(_ delegate: any CoreClientTargetDiagnosticProducingDelegate, _ commandLine: [String], executionDescription: String?) async throws -> Processes.ExecutionResult { try await cache.value(forKey: commandLine) { - try await delegate.executeExternalTool(commandLine: commandLine, workingDirectory: "/", environment: [:], executionDescription: executionDescription) + try await delegate.executeExternalTool(commandLine: commandLine, workingDirectory: workingDirectory, environment: [:], executionDescription: executionDescription) } } } diff --git a/Sources/SWBCore/SpecImplementations/CommandLineToolSpec.swift b/Sources/SWBCore/SpecImplementations/CommandLineToolSpec.swift index 866dd2f5..65072bc4 100644 --- a/Sources/SWBCore/SpecImplementations/CommandLineToolSpec.swift +++ b/Sources/SWBCore/SpecImplementations/CommandLineToolSpec.swift @@ -1393,7 +1393,7 @@ open class CommandLineToolSpec : PropertyDomainSpec, SpecType, TaskTypeDescripti return Base.instance.shouldStart(task, buildCommand: buildCommand) } - public func executeExternalTool(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, commandLine: [String], workingDirectory: String?, environment: [String: String], executionDescription: String?, _ parse: @escaping (ByteString) throws -> T) async throws -> T { + public func executeExternalTool(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, commandLine: [String], workingDirectory: Path?, environment: [String: String], executionDescription: String?, _ parse: @escaping (ByteString) throws -> T) async throws -> T { let executionResult = try await delegate.executeExternalTool(commandLine: commandLine, workingDirectory: workingDirectory, environment: environment, executionDescription: executionDescription) guard executionResult.exitStatus.isSuccess else { throw RunProcessNonZeroExitError(args: commandLine, workingDirectory: workingDirectory, environment: .init(environment), status: executionResult.exitStatus, stdout: ByteString(executionResult.stdout), stderr: ByteString(executionResult.stderr)) @@ -1401,7 +1401,7 @@ open class CommandLineToolSpec : PropertyDomainSpec, SpecType, TaskTypeDescripti return try parse(ByteString(executionResult.stdout)) } - public func generatedFilePaths(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, commandLine: [String], workingDirectory: String?, environment: [String: String], executionDescription: String?, _ parse: @escaping (ByteString) throws -> [Path]) async throws -> [Path] { + public func generatedFilePaths(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, commandLine: [String], workingDirectory: Path?, environment: [String: String], executionDescription: String?, _ parse: @escaping (ByteString) throws -> [Path]) async throws -> [Path] { return try await executeExternalTool(cbc, delegate, commandLine: commandLine, workingDirectory: workingDirectory, environment: environment, executionDescription: executionDescription, parse) } } diff --git a/Sources/SWBCore/TaskGeneration.swift b/Sources/SWBCore/TaskGeneration.swift index 44a2ff15..511db51e 100644 --- a/Sources/SWBCore/TaskGeneration.swift +++ b/Sources/SWBCore/TaskGeneration.swift @@ -647,7 +647,7 @@ public struct PlannedTaskBuilder { /// Interface by which core classes can request information from the client. public protocol CoreClientDelegate { - func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String: String]) async throws -> ExternalToolResult + func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String: String]) async throws -> ExternalToolResult } public protocol CoreClientTargetDiagnosticProducingDelegate: AnyObject, TargetDiagnosticProducingDelegate, ActivityReporter { @@ -757,7 +757,7 @@ extension TaskGenerationDelegate { } extension CoreClientTargetDiagnosticProducingDelegate { - public func executeExternalTool(commandLine: [String], workingDirectory: String? = nil, environment: [String: String] = [:], executionDescription: String?) async throws -> Processes.ExecutionResult { + public func executeExternalTool(commandLine: [String], workingDirectory: Path? = nil, environment: [String: String] = [:], executionDescription: String?) async throws -> Processes.ExecutionResult { try await withActivity(ruleInfo: "ExecuteExternalTool " + commandLine.joined(separator: " "), executionDescription: executionDescription ?? CommandLineToolSpec.fallbackExecutionDescription, signature: ByteString(encodingAsUTF8: "\(commandLine) \(String(describing: workingDirectory)) \(environment)"), target: nil, parentActivity: ActivityID.buildDescriptionActivity) { activity in try await coreClientDelegate.executeExternalTool(commandLine: commandLine, workingDirectory: workingDirectory, environment: environment) } @@ -768,7 +768,7 @@ extension CoreClientTargetDiagnosticProducingDelegate { private let externalToolExecutionQueue = AsyncOperationQueue(concurrentTasks: ProcessInfo.processInfo.activeProcessorCount) extension CoreClientDelegate { - package func executeExternalTool(commandLine: [String], workingDirectory: String? = nil, environment: [String: String] = [:]) async throws -> Processes.ExecutionResult { + package func executeExternalTool(commandLine: [String], workingDirectory: Path? = nil, environment: [String: String] = [:]) async throws -> Processes.ExecutionResult { switch try await executeExternalTool(commandLine: commandLine, workingDirectory: workingDirectory, environment: environment) { case .deferred: guard let url = commandLine.first.map(URL.init(fileURLWithPath:)) else { @@ -776,7 +776,7 @@ extension CoreClientDelegate { } return try await externalToolExecutionQueue.withOperation { - try await Process.getOutput(url: url, arguments: Array(commandLine.dropFirst()), currentDirectoryURL: workingDirectory.map(URL.init(fileURLWithPath:)), environment: Environment.current.addingContents(of: .init(environment))) + try await Process.getOutput(url: url, arguments: Array(commandLine.dropFirst()), currentDirectoryURL: workingDirectory.map { URL(fileURLWithPath: $0.str) }, environment: Environment.current.addingContents(of: .init(environment))) } case let .result(status, stdout, stderr): return Processes.ExecutionResult(exitStatus: status, stdout: stdout, stderr: stderr) diff --git a/Sources/SWBProtocol/ClientExchangeMessages.swift b/Sources/SWBProtocol/ClientExchangeMessages.swift index 12922e07..8ee73a58 100644 --- a/Sources/SWBProtocol/ClientExchangeMessages.swift +++ b/Sources/SWBProtocol/ClientExchangeMessages.swift @@ -21,10 +21,10 @@ public struct ExternalToolExecutionRequest: ClientExchangeMessage, Equatable { public let exchangeHandle: String public let commandLine: [String] - public let workingDirectory: String? + public let workingDirectory: Path? public let environment: [String: String] - public init(sessionHandle: String, exchangeHandle: String, commandLine: [String], workingDirectory: String?, environment: [String: String]) { + public init(sessionHandle: String, exchangeHandle: String, commandLine: [String], workingDirectory: Path?, environment: [String: String]) { self.sessionHandle = sessionHandle self.exchangeHandle = exchangeHandle self.commandLine = commandLine diff --git a/Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift index 5f26bde7..18caead1 100644 --- a/Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/ClangCompileTaskAction.swift @@ -266,7 +266,7 @@ public final class ClangCompileTaskAction: TaskAction, BuildValueValidatingTaskA let commandLine = command.arguments let delegate = TaskProcessDelegate(outputDelegate: outputDelegate) // The frontend invocations should be unaffected by the environment, pass an empty one. - try await spawn(commandLine: commandLine, environment: [:], workingDirectory: task.workingDirectory.str, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: delegate) + try await spawn(commandLine: commandLine, environment: [:], workingDirectory: task.workingDirectory, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: delegate) lastResult = delegate.commandResult if lastResult == .succeeded { diff --git a/Sources/SWBTaskExecution/TaskActions/CodeSignTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/CodeSignTaskAction.swift index aeb49157..c059f6a8 100644 --- a/Sources/SWBTaskExecution/TaskActions/CodeSignTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/CodeSignTaskAction.swift @@ -37,7 +37,7 @@ public final class CodeSignTaskAction: TaskAction { commandLine.insert(preEncryptHashesFlag, at: 1) } - try await spawn(commandLine: commandLine, environment: task.environment.bindingsDictionary, workingDirectory: task.workingDirectory.str, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) + try await spawn(commandLine: commandLine, environment: task.environment.bindingsDictionary, workingDirectory: task.workingDirectory, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) } catch { outputDelegate.error(error.localizedDescription) return .failed diff --git a/Sources/SWBTaskExecution/TaskActions/CopyTiffTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/CopyTiffTaskAction.swift index cac4975e..df1675af 100644 --- a/Sources/SWBTaskExecution/TaskActions/CopyTiffTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/CopyTiffTaskAction.swift @@ -145,7 +145,7 @@ public final class CopyTiffTaskAction: TaskAction { let processDelegate = TaskProcessDelegate(outputDelegate: outputDelegate) do { - try await spawn(commandLine: tiffutilCommand, environment: task.environment.bindingsDictionary, workingDirectory: task.workingDirectory.str, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) + try await spawn(commandLine: tiffutilCommand, environment: task.environment.bindingsDictionary, workingDirectory: task.workingDirectory, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) } catch { outputDelegate.error(error.localizedDescription) return .failed diff --git a/Sources/SWBTaskExecution/TaskActions/DeferredExecutionTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/DeferredExecutionTaskAction.swift index a0307227..3af329c9 100644 --- a/Sources/SWBTaskExecution/TaskActions/DeferredExecutionTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/DeferredExecutionTaskAction.swift @@ -24,7 +24,7 @@ public final class DeferredExecutionTaskAction: TaskAction { public override func performTaskAction(_ task: any ExecutableTask, dynamicExecutionDelegate: any DynamicTaskExecutionDelegate, executionDelegate: any TaskExecutionDelegate, clientDelegate: any TaskExecutionClientDelegate, outputDelegate: any TaskOutputDelegate) async -> CommandResult { let processDelegate = TaskProcessDelegate(outputDelegate: outputDelegate) do { - try await spawn(commandLine: Array(task.commandLineAsStrings), environment: task.environment.bindingsDictionary, workingDirectory: task.workingDirectory.str, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) + try await spawn(commandLine: Array(task.commandLineAsStrings), environment: task.environment.bindingsDictionary, workingDirectory: task.workingDirectory, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) } catch { outputDelegate.error(error.localizedDescription) return .failed @@ -50,7 +50,7 @@ fileprivate extension CommandResult { } extension TaskAction { - func spawn(commandLine: [String], environment: [String: String], workingDirectory: String, dynamicExecutionDelegate: any DynamicTaskExecutionDelegate, clientDelegate: any TaskExecutionClientDelegate, processDelegate: any ProcessDelegate) async throws { + func spawn(commandLine: [String], environment: [String: String], workingDirectory: Path, dynamicExecutionDelegate: any DynamicTaskExecutionDelegate, clientDelegate: any TaskExecutionClientDelegate, processDelegate: any ProcessDelegate) async throws { guard dynamicExecutionDelegate.allowsExternalToolExecution else { try await dynamicExecutionDelegate.spawn(commandLine: commandLine, environment: environment, workingDirectory: workingDirectory, processDelegate: processDelegate) return diff --git a/Sources/SWBTaskExecution/TaskActions/EmbedSwiftStdLibTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/EmbedSwiftStdLibTaskAction.swift index f2ded2fd..4c995f5d 100644 --- a/Sources/SWBTaskExecution/TaskActions/EmbedSwiftStdLibTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/EmbedSwiftStdLibTaskAction.swift @@ -515,7 +515,7 @@ public final class EmbedSwiftStdLibTaskAction: TaskAction { let capturingDelegate = CapturingOutputDelegate(outputDelegate: outputDelegate) let processDelegate = TaskProcessDelegate(outputDelegate: capturingDelegate) - try await taskAction.spawn(commandLine: args, environment: effectiveEnvironment, workingDirectory: task.workingDirectory.str, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) + try await taskAction.spawn(commandLine: args, environment: effectiveEnvironment, workingDirectory: task.workingDirectory, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) if let error = processDelegate.executionError { throw StubError.error(error) } @@ -526,7 +526,7 @@ public final class EmbedSwiftStdLibTaskAction: TaskAction { } guard !failed else { - throw RunProcessNonZeroExitError(args: args, workingDirectory: task.workingDirectory.str, environment: .init(effectiveEnvironment), status: { + throw RunProcessNonZeroExitError(args: args, workingDirectory: task.workingDirectory, environment: .init(effectiveEnvironment), status: { if case let .exit(exitStatus, _) = processDelegate.outputDelegate.result { return exitStatus } diff --git a/Sources/SWBTaskExecution/TaskActions/FileCopyTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/FileCopyTaskAction.swift index 94b2899d..f3bbb3f6 100644 --- a/Sources/SWBTaskExecution/TaskActions/FileCopyTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/FileCopyTaskAction.swift @@ -132,7 +132,7 @@ public final class FileCopyTaskAction: TaskAction } for commandLine in commandLine.compileAndLink.flatMap({ [$0.compile, $0.link] }) + [commandLine.lipo] { - try await spawn(commandLine: commandLine, environment: task.environment.bindingsDictionary, workingDirectory: task.workingDirectory.str, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) + try await spawn(commandLine: commandLine, environment: task.environment.bindingsDictionary, workingDirectory: task.workingDirectory, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) } } } diff --git a/Sources/SWBTaskExecution/TaskActions/GenericCachingTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/GenericCachingTaskAction.swift index bb6291c2..466859d9 100644 --- a/Sources/SWBTaskExecution/TaskActions/GenericCachingTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/GenericCachingTaskAction.swift @@ -197,7 +197,7 @@ public final class GenericCachingTaskAction: TaskAction { } emitCacheDebuggingRemark("running sandboxed command") - try await spawn(commandLine: sandboxArgs + remappedCommandLine, environment: remappedEnvironment.bindingsDictionary, workingDirectory: cacheKey.workingDirectory.str, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) + try await spawn(commandLine: sandboxArgs + remappedCommandLine, environment: remappedEnvironment.bindingsDictionary, workingDirectory: cacheKey.workingDirectory, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) if processDelegate.commandResult == .succeeded { try await withThrowingTaskGroup(of: Void.self) { group in diff --git a/Sources/SWBTaskExecution/TaskActions/LSRegisterURLTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/LSRegisterURLTaskAction.swift index 93f1d0c4..0a924145 100644 --- a/Sources/SWBTaskExecution/TaskActions/LSRegisterURLTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/LSRegisterURLTaskAction.swift @@ -23,7 +23,7 @@ public final class LSRegisterURLTaskAction: TaskAction { override public func performTaskAction(_ task: any ExecutableTask, dynamicExecutionDelegate: any DynamicTaskExecutionDelegate, executionDelegate: any TaskExecutionDelegate, clientDelegate: any TaskExecutionClientDelegate, outputDelegate: any TaskOutputDelegate) async -> CommandResult { let processDelegate = TaskProcessDelegate(outputDelegate: outputDelegate) do { - try await spawn(commandLine: Array(task.commandLineAsStrings), environment: task.environment.bindingsDictionary, workingDirectory: task.workingDirectory.str, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) + try await spawn(commandLine: Array(task.commandLineAsStrings), environment: task.environment.bindingsDictionary, workingDirectory: task.workingDirectory, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: processDelegate) } catch { outputDelegate.error(error.localizedDescription) return .failed diff --git a/Sources/SWBTaskExecution/TaskActions/PrecompileClangModuleTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/PrecompileClangModuleTaskAction.swift index 3c308b9e..ebab37b8 100644 --- a/Sources/SWBTaskExecution/TaskActions/PrecompileClangModuleTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/PrecompileClangModuleTaskAction.swift @@ -198,7 +198,7 @@ final public class PrecompileClangModuleTaskAction: TaskAction, BuildValueValida let delegate = TaskProcessDelegate(outputDelegate: outputDelegate) // The frontend invocations should be unaffected by the environment, pass an empty one. - try await spawn(commandLine: commandLine, environment: [:], workingDirectory: dependencyInfo.workingDirectory.str, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: delegate) + try await spawn(commandLine: commandLine, environment: [:], workingDirectory: dependencyInfo.workingDirectory, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: delegate) let result = delegate.commandResult ?? .failed if result == .succeeded { diff --git a/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobTaskAction.swift index 08d57daa..84eee161 100644 --- a/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobTaskAction.swift @@ -520,7 +520,7 @@ public final class SwiftDriverJobTaskAction: TaskAction, BuildValueValidatingTas return .succeeded } - try await spawn(commandLine: options.commandLine, environment: environment, workingDirectory: task.workingDirectory.str, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: delegate) + try await spawn(commandLine: options.commandLine, environment: environment, workingDirectory: task.workingDirectory, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: delegate) if let error = delegate.executionError { outputDelegate.error(error) diff --git a/Sources/SWBTaskExecution/TaskActions/TaskAction.swift b/Sources/SWBTaskExecution/TaskActions/TaskAction.swift index ba5e3f1d..6b7fd761 100644 --- a/Sources/SWBTaskExecution/TaskActions/TaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/TaskAction.swift @@ -229,7 +229,7 @@ public protocol DynamicTaskExecutionDelegate: ActivityReporter { func spawn( commandLine: [String], environment: [String: String], - workingDirectory: String, + workingDirectory: Path, processDelegate: any ProcessDelegate ) async throws -> Bool diff --git a/Sources/SWBTestSupport/BuildOperationTester.swift b/Sources/SWBTestSupport/BuildOperationTester.swift index df0f3878..1635ad5b 100644 --- a/Sources/SWBTestSupport/BuildOperationTester.swift +++ b/Sources/SWBTestSupport/BuildOperationTester.swift @@ -1777,7 +1777,7 @@ extension BuildOperationTester.BuildDescriptionResults: Sendable { } package final class MockTestClientDelegate: ClientDelegate, Sendable { package init() {} - package func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String: String]) async throws -> ExternalToolResult { + package func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String: String]) async throws -> ExternalToolResult { return .deferred } } @@ -1973,7 +1973,7 @@ private final class BuildOperationTesterDelegate: BuildOperationDelegate { if !self.hadErrors { switch result { case let .exit(exitStatus, _) where !exitStatus.isSuccess && !exitStatus.wasCanceled: - self.delegate.events.append(.buildHadDiagnostic(Diagnostic(behavior: .error, location: .unknown, data: DiagnosticData("Command \(task.ruleInfo[0]) failed. \(RunProcessNonZeroExitError(args: Array(task.commandLineAsStrings), workingDirectory: task.workingDirectory.str, environment: .init(task.environment.bindingsDictionary), status: exitStatus, mergedOutput: output).description)")))) + self.delegate.events.append(.buildHadDiagnostic(Diagnostic(behavior: .error, location: .unknown, data: DiagnosticData("Command \(task.ruleInfo[0]) failed. \(RunProcessNonZeroExitError(args: Array(task.commandLineAsStrings), workingDirectory: task.workingDirectory, environment: .init(task.environment.bindingsDictionary), status: exitStatus, mergedOutput: output).description)")))) case .failedSetup: self.delegate.events.append(.buildHadDiagnostic(Diagnostic(behavior: .error, location: .unknown, data: DiagnosticData("Command \(task.ruleInfo[0]) failed setup.")))) case .exit, .skipped: diff --git a/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift b/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift index 516931d9..b4689254 100644 --- a/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift +++ b/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift @@ -106,7 +106,7 @@ package class CapturingTaskGenerationDelegate: TaskGenerationDelegate, CoreClien package func fileExists(at path: Path) -> Bool { return true } package var taskActionCreationDelegate: any TaskActionCreationDelegate { return self } package var clientDelegate: any CoreClientDelegate { return self } - package func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String: String]) async throws -> ExternalToolResult { + package func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String: String]) async throws -> ExternalToolResult { return .deferred } } diff --git a/Sources/SWBTestSupport/CommandLineToolSpecDiscoveredInfo.swift b/Sources/SWBTestSupport/CommandLineToolSpecDiscoveredInfo.swift index 3676dd9b..b35fb1d3 100644 --- a/Sources/SWBTestSupport/CommandLineToolSpecDiscoveredInfo.swift +++ b/Sources/SWBTestSupport/CommandLineToolSpecDiscoveredInfo.swift @@ -61,7 +61,7 @@ private class ToolSpecCapturingTaskGenerationDelegate: CapturingTaskGenerationDe try super.init(producer: producer, userPreferences: userPreferences) } - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String: String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String: String]) async throws -> ExternalToolResult { result } } diff --git a/Sources/SWBTestSupport/CoreBasedTests.swift b/Sources/SWBTestSupport/CoreBasedTests.swift index 2cfbeb6f..7c3e6f42 100644 --- a/Sources/SWBTestSupport/CoreBasedTests.swift +++ b/Sources/SWBTestSupport/CoreBasedTests.swift @@ -365,7 +365,7 @@ private final class AlwaysDeferredCoreClientDelegate: CoreClientDelegate, CoreCl _diagnosticsEngine.hasErrors } - func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { .deferred } } diff --git a/Sources/SWBTestSupport/LibraryGeneration.swift b/Sources/SWBTestSupport/LibraryGeneration.swift index 02cc269d..1a238077 100644 --- a/Sources/SWBTestSupport/LibraryGeneration.swift +++ b/Sources/SWBTestSupport/LibraryGeneration.swift @@ -64,28 +64,28 @@ extension InstalledXcode { let linkerArgs = linkerOptions.map({ $0.args }).reduce([], +) if buildLibraryForDistribution { distributionArgs = ["-enable-library-evolution"] - _ = try await xcrun(["-sdk", platform.sdkName, "swiftc", "-target", target] + targetVariantArgs + distributionArgs as [String] + ["-emit-module-interface", "-emit-module-interface-path", swiftModuleDir.join("\(name).swiftinterface").str, "-c", sourcePath.str], workingDirectory: workingDirectory.str) + _ = try await xcrun(["-sdk", platform.sdkName, "swiftc", "-target", target] + targetVariantArgs + distributionArgs as [String] + ["-emit-module-interface", "-emit-module-interface-path", swiftModuleDir.join("\(name).swiftinterface").str, "-c", sourcePath.str], workingDirectory: workingDirectory) } else { distributionArgs = [] } // Generate the macho file. let machoArgs = [["-sdk", platform.sdkName, "swiftc", "-target", target], targetVariantArgs, distributionArgs, ["-emit-module", "-emit-module-path", swiftModuleDir.join("\(arch).swiftmodule").str, "-module-name", name, "-c", sourcePath.str]].reduce([], +) - _ = try await xcrun(machoArgs, workingDirectory: workingDirectory.str) + _ = try await xcrun(machoArgs, workingDirectory: workingDirectory) // Generate the swiftmodule file. let swiftmoduleArgs = [["-sdk", platform.sdkName, "swiftc", "-target", target], targetVariantArgs, distributionArgs, ["-module-name", name, "-o", objectPath.str, "-c", sourcePath.str]].reduce([], +) - _ = try await xcrun(swiftmoduleArgs, workingDirectory: workingDirectory.str) + _ = try await xcrun(swiftmoduleArgs, workingDirectory: workingDirectory) if `static` { return objectPath } else if object { let linkArgs = [["-sdk", platform.sdkName, "clang", "-r", "-target", target], targetVariantArgs, linkerArgs, ["-L" + swiftRuntimeLibraryDirectoryPath(name: platform.sdkName), "-L/usr/lib/swift", "-o", machoPath.str, objectPath.str]].reduce([], +) - _ = try await xcrun(linkArgs, workingDirectory: workingDirectory.str) + _ = try await xcrun(linkArgs, workingDirectory: workingDirectory) return machoPath } else { let linkArgs = [["-sdk", platform.sdkName, "clang", "-dynamiclib", "-target", target], targetVariantArgs, linkerArgs, ["-L" + swiftRuntimeLibraryDirectoryPath(name: platform.sdkName), "-L/usr/lib/swift", "-o", machoPath.str, objectPath.str]].reduce([], +) - _ = try await xcrun(linkArgs, workingDirectory: workingDirectory.str) + _ = try await xcrun(linkArgs, workingDirectory: workingDirectory) if needSigned { _ = try await runProcessWithDeveloperDirectory(["/usr/bin/codesign", "-s", "-", machoPath.str]) } diff --git a/Sources/SWBTestSupport/Misc.swift b/Sources/SWBTestSupport/Misc.swift index 85394475..9e77ad92 100644 --- a/Sources/SWBTestSupport/Misc.swift +++ b/Sources/SWBTestSupport/Misc.swift @@ -39,7 +39,7 @@ package extension Sequence where Element: Equatable { /// - throws: ``StubError`` if the arguments list is an empty array. /// - throws: ``RunProcessNonZeroExitError`` if the process exited with a nonzero status code or uncaught signal. @discardableResult -package func runProcess(_ args: [String], workingDirectory: String? = nil, environment: Environment = .init(), interruptible: Bool = true, redirectStderr: Bool = false) async throws -> String { +package func runProcess(_ args: [String], workingDirectory: Path? = nil, environment: Environment = .init(), interruptible: Bool = true, redirectStderr: Bool = false) async throws -> String { guard let first = args.first else { throw StubError.error("Invalid number of arguments") } @@ -48,13 +48,13 @@ package func runProcess(_ args: [String], workingDirectory: String? = nil, envir } let arguments = Array(args.dropFirst()) if redirectStderr { - let (exitStatus, output) = try await Process.getMergedOutput(url: url, arguments: arguments, currentDirectoryURL: workingDirectory.map(URL.init(fileURLWithPath:)), environment: environment, interruptible: interruptible) + let (exitStatus, output) = try await Process.getMergedOutput(url: url, arguments: arguments, currentDirectoryURL: workingDirectory.map { URL(fileURLWithPath: $0.str) }, environment: environment, interruptible: interruptible) guard exitStatus.isSuccess else { throw RunProcessNonZeroExitError(args: args, workingDirectory: workingDirectory, environment: environment, status: exitStatus, mergedOutput: ByteString(output)) } return String(decoding: output, as: UTF8.self) } else { - let executionResult = try await Process.getOutput(url: url, arguments: arguments, currentDirectoryURL: workingDirectory.map(URL.init(fileURLWithPath:)), environment: environment, interruptible: interruptible) + let executionResult = try await Process.getOutput(url: url, arguments: arguments, currentDirectoryURL: workingDirectory.map { URL(fileURLWithPath: $0.str) }, environment: environment, interruptible: interruptible) guard executionResult.exitStatus.isSuccess else { throw RunProcessNonZeroExitError(args: args, workingDirectory: workingDirectory, environment: environment, status: executionResult.exitStatus, stdout: ByteString(executionResult.stdout), stderr: ByteString(executionResult.stderr)) } @@ -65,14 +65,14 @@ package func runProcess(_ args: [String], workingDirectory: String? = nil, envir /// Runs the command specified by `args` with the `DEVELOPER_DIR` environment variable set. /// /// This method will use the current value of `DEVELOPER_DIR` in the environment by default, or the value of `overrideDeveloperDirectory` if specified. -package func runProcessWithDeveloperDirectory(_ args: [String], workingDirectory: String? = nil, overrideDeveloperDirectory: String? = nil, interruptible: Bool = true, redirectStderr: Bool = true) async throws -> String { +package func runProcessWithDeveloperDirectory(_ args: [String], workingDirectory: Path? = nil, overrideDeveloperDirectory: String? = nil, interruptible: Bool = true, redirectStderr: Bool = true) async throws -> String { let environment = Environment.current .filter(keys: ["DEVELOPER_DIR", "LLVM_PROFILE_FILE"]) .addingContents(of: overrideDeveloperDirectory.map { Environment(["DEVELOPER_DIR": $0]) } ?? .init()) return try await runProcess(args, workingDirectory: workingDirectory, environment: environment, interruptible: interruptible, redirectStderr: redirectStderr) } -package func runHostProcess(_ args: [String], workingDirectory: String? = nil, interruptible: Bool = true, redirectStderr: Bool = true) async throws -> String { +package func runHostProcess(_ args: [String], workingDirectory: Path? = nil, interruptible: Bool = true, redirectStderr: Bool = true) async throws -> String { switch try ProcessInfo.processInfo.hostOperatingSystem() { case .macOS: return try await InstalledXcode.currentlySelected().xcrun(args, workingDirectory: workingDirectory, redirectStderr: redirectStderr) diff --git a/Sources/SWBTestSupport/PerfTestSupport.swift b/Sources/SWBTestSupport/PerfTestSupport.swift index 0cfaa6ea..f05532ad 100644 --- a/Sources/SWBTestSupport/PerfTestSupport.swift +++ b/Sources/SWBTestSupport/PerfTestSupport.swift @@ -39,11 +39,11 @@ extension PerfTests { extension Trait where Self == Testing.ConditionTrait { package static var performance: Self { - disabled("Skipping performance test") { + enabled("Skipping performance test") { #if DEBUG - return true + return getEnvironmentVariable("SWB_PERF_TESTS_ENABLE")?.boolValue ?? false #else - return false + return true #endif } } diff --git a/Sources/SWBTestSupport/SkippedTestSupport.swift b/Sources/SWBTestSupport/SkippedTestSupport.swift index 08ef7190..27040e16 100644 --- a/Sources/SWBTestSupport/SkippedTestSupport.swift +++ b/Sources/SWBTestSupport/SkippedTestSupport.swift @@ -146,17 +146,9 @@ extension Trait where Self == Testing.ConditionTrait { }) } - /// Constructs a condition trait that causes a test to be disabled if the Foundation process spawning implementation is not using `posix_spawn_file_actions_addchdir`. + /// Constructs a condition trait that causes a test to be disabled if the Foundation process spawning implementation is not thread-safe. package static var requireThreadSafeWorkingDirectory: Self { - disabled("Thread-safe process working directory support is unavailable.", { - switch try ProcessInfo.processInfo.hostOperatingSystem() { - case .linux: - // Amazon Linux 2 has glibc 2.26, and glibc 2.29 is needed for posix_spawn_file_actions_addchdir_np support - FileManager.default.contents(atPath: "/etc/system-release").map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false - default: - false - } - }) + disabled(if: try Process.hasUnsafeWorkingDirectorySupport, "Foundation.Process working directory support is not thread-safe.") } /// Constructs a condition trait that causes a test to be disabled if the specified llbuild API version requirement is not met. diff --git a/Sources/SWBTestSupport/TaskPlanningTestSupport.swift b/Sources/SWBTestSupport/TaskPlanningTestSupport.swift index ae67ca1c..f615bdc7 100644 --- a/Sources/SWBTestSupport/TaskPlanningTestSupport.swift +++ b/Sources/SWBTestSupport/TaskPlanningTestSupport.swift @@ -15,7 +15,7 @@ public import enum SWBProtocol.ExternalToolResult public import struct SWBProtocol.BuildOperationTaskEnded public import SWBTaskConstruction import SWBTaskExecution -package import SWBUtil +public import SWBUtil import Testing package import SWBMacro import Foundation @@ -192,7 +192,7 @@ package extension Array where Element == TaskCondition { open class MockTestTaskPlanningClientDelegate: TaskPlanningClientDelegate, @unchecked Sendable { package init() {} - open func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String: String]) async throws -> ExternalToolResult { + open func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String: String]) async throws -> ExternalToolResult { let args = commandLine.dropFirst() switch commandLine.first.map(Path.init)?.basenameWithoutSuffix { case "actool" where args == ["--version", "--output-format", "xml1"]: diff --git a/Sources/SWBTestSupport/Xcode.swift b/Sources/SWBTestSupport/Xcode.swift index 00448733..969fc923 100644 --- a/Sources/SWBTestSupport/Xcode.swift +++ b/Sources/SWBTestSupport/Xcode.swift @@ -31,7 +31,7 @@ package struct InstalledXcode: Sendable { return try await Path(xcrun(["-f", tool] + toolchainArgs).trimmingCharacters(in: .whitespacesAndNewlines)) } - package func xcrun(_ args: [String], workingDirectory: String? = nil, redirectStderr: Bool = true) async throws -> String { + package func xcrun(_ args: [String], workingDirectory: Path? = nil, redirectStderr: Bool = true) async throws -> String { return try await runProcessWithDeveloperDirectory(["/usr/bin/xcrun"] + args, workingDirectory: workingDirectory, overrideDeveloperDirectory: self.developerDirPath.str, redirectStderr: redirectStderr) } diff --git a/Sources/SWBUtil/PbxCp.swift b/Sources/SWBUtil/PbxCp.swift index c917f411..f5be8a02 100644 --- a/Sources/SWBUtil/PbxCp.swift +++ b/Sources/SWBUtil/PbxCp.swift @@ -80,7 +80,7 @@ fileprivate func xSecCodePathIsSigned(_ path: Path) throws -> Bool { // FIXME: Move this fully to Swift Concurrency and execute the process via llbuild after PbxCp is fully converted to Swift /// Spawns a process and waits for it to finish, closing stdin and redirecting stdout and stderr to fdout. Failure to launch, non-zero exit code, or exit with a signal will throw an error. -fileprivate func spawnTaskAndWait(_ launchPath: Path, _ arguments: [String]?, _ environment: Environment?, _ workingDirPath: String?, _ dryRun: Bool, _ stream: OutputByteStream) async throws { +fileprivate func spawnTaskAndWait(_ launchPath: Path, _ arguments: [String]?, _ environment: Environment?, _ workingDirPath: Path?, _ dryRun: Bool, _ stream: OutputByteStream) async throws { stream <<< launchPath.str for arg in arguments ?? [] { stream <<< " \(arg)" @@ -91,7 +91,7 @@ fileprivate func spawnTaskAndWait(_ launchPath: Path, _ arguments: [String]?, _ return } - let (exitStatus, output) = try await Process.getMergedOutput(url: URL(fileURLWithPath: launchPath.str), arguments: arguments ?? [], currentDirectoryURL: workingDirPath.map { URL(fileURLWithPath: $0, isDirectory: true) }, environment: environment) + let (exitStatus, output) = try await Process.getMergedOutput(url: URL(fileURLWithPath: launchPath.str), arguments: arguments ?? [], currentDirectoryURL: workingDirPath.map { URL(fileURLWithPath: $0.str, isDirectory: true) }, environment: environment) // Copy the process output to the output stream. stream <<< "\(String(decoding: output, as: UTF8.self))" diff --git a/Sources/SWBUtil/Process.swift b/Sources/SWBUtil/Process.swift index 634c3f0d..d07f8e9f 100644 --- a/Sources/SWBUtil/Process.swift +++ b/Sources/SWBUtil/Process.swift @@ -63,6 +63,20 @@ public final class Process { public typealias Process = Foundation.Process #endif +extension Process { + public static var hasUnsafeWorkingDirectorySupport: Bool { + get throws { + switch try ProcessInfo.processInfo.hostOperatingSystem() { + case .linux: + // Amazon Linux 2 has glibc 2.26, and glibc 2.29 is needed for posix_spawn_file_actions_addchdir_np support + FileManager.default.contents(atPath: "/etc/system-release").map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false + default: + false + } + } + } +} + extension Process { public static func getOutput(url: URL, arguments: [String], currentDirectoryURL: URL? = nil, environment: Environment? = nil, interruptible: Bool = true) async throws -> Processes.ExecutionResult { if #available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *) { @@ -125,13 +139,8 @@ extension Process { } private static func _getOutput(url: URL, arguments: [String], currentDirectoryURL: URL?, environment: Environment?, interruptible: Bool, setup: (Process) -> T, collect: (T) async throws -> U) async throws -> (exitStatus: Processes.ExitStatus, output: U) { - if !url.isFileURL { - throw StubError.error("\(url) is not an absolute file URL") - } let executableFilePath = try url.standardizedFileURL.filePath - if try !localFS.isExecutable(executableFilePath) { - throw StubError.error("\(executableFilePath.str) is not an executable file") - } + let process = Process() process.executableURL = url process.arguments = arguments @@ -140,6 +149,14 @@ extension Process { } process.environment = environment.map { .init($0) } ?? nil + if try currentDirectoryURL != nil && hasUnsafeWorkingDirectorySupport { + throw try RunProcessLaunchError(process, context: "Foundation.Process working directory support is not thread-safe") + } + + if try !localFS.isExecutable(executableFilePath) { + throw try RunProcessLaunchError(process, context: "\(executableFilePath.str) is not an executable file") + } + let streams = setup(process) try await process.run(interruptible: interruptible) @@ -300,9 +317,68 @@ extension Processes.ExitStatus: CustomStringConvertible { } } -public struct RunProcessNonZeroExitError: Error { +public protocol RunProcessError: Sendable { + var args: [String] { get } + var workingDirectory: Path? { get } + var environment: Environment { get } +} + +extension RunProcessError { + fileprivate var commandIdentityPrefixString: String { + let fullArgs: [String] + if !environment.isEmpty { + fullArgs = ["env"] + [String: String](environment).sorted(byKey: <).map { key, value in "\(key)=\(value)" } + args + } else { + fullArgs = args + } + + let commandString = UNIXShellCommandCodec(encodingStrategy: .singleQuotes, encodingBehavior: .fullCommandLine).encode(fullArgs) + let fullCommandString: String + if let workingDirectory { + let directoryCommandString = UNIXShellCommandCodec(encodingStrategy: .singleQuotes, encodingBehavior: .fullCommandLine).encode(["cd", workingDirectory.str]) + fullCommandString = "(\([directoryCommandString, commandString].joined(separator: " && ")))" + } else { + fullCommandString = commandString + } + + return "The command `\(fullCommandString)`" + } +} + +public struct RunProcessLaunchError: Error, RunProcessError { public let args: [String] - public let workingDirectory: String? + public let workingDirectory: Path? + public let environment: Environment + public let context: String + + public init(args: [String], workingDirectory: Path?, environment: Environment, context: String) { + self.args = args + self.workingDirectory = workingDirectory + self.environment = environment + self.context = context + } + + public init(_ process: Process, context: String) throws { + self.args = ((process.executableURL?.path).map { [$0] } ?? []) + (process.arguments ?? []) + self.workingDirectory = try process.currentDirectoryURL?.filePath + self.environment = process.environment.map { .init($0) } ?? .init() + self.context = context + } +} + +extension RunProcessLaunchError: CustomStringConvertible, LocalizedError { + public var description: String { + return "\(commandIdentityPrefixString) failed to launch. \(context)." + } + + public var errorDescription: String? { + return description + } +} + +public struct RunProcessNonZeroExitError: Error, RunProcessError { + public let args: [String] + public let workingDirectory: Path? public let environment: Environment public let status: Processes.ExitStatus @@ -313,15 +389,15 @@ public struct RunProcessNonZeroExitError: Error { public let output: Output? - public init(args: [String], workingDirectory: String?, environment: Environment, status: Processes.ExitStatus, mergedOutput: ByteString) { + public init(args: [String], workingDirectory: Path?, environment: Environment, status: Processes.ExitStatus, mergedOutput: ByteString) { self.init(args: args, workingDirectory: workingDirectory, environment: environment, status: status, output: .merged(mergedOutput)) } - public init(args: [String], workingDirectory: String?, environment: Environment, status: Processes.ExitStatus, stdout: ByteString, stderr: ByteString) { + public init(args: [String], workingDirectory: Path?, environment: Environment, status: Processes.ExitStatus, stdout: ByteString, stderr: ByteString) { self.init(args: args, workingDirectory: workingDirectory, environment: environment, status: status, output: .separate(stdout: stdout, stderr: stderr)) } - public init(args: [String], workingDirectory: String?, environment: Environment, status: Processes.ExitStatus, output: Output) { + public init(args: [String], workingDirectory: Path?, environment: Environment, status: Processes.ExitStatus, output: Output) { self.args = args self.workingDirectory = workingDirectory self.environment = environment @@ -331,7 +407,7 @@ public struct RunProcessNonZeroExitError: Error { public init?(_ process: Process) throws { self.args = ((process.executableURL?.path).map { [$0] } ?? []) + (process.arguments ?? []) - self.workingDirectory = process.currentDirectoryURL?.path + self.workingDirectory = try process.currentDirectoryURL?.filePath self.environment = process.environment.map { .init($0) } ?? .init() self.status = try .init(process) self.output = nil @@ -343,23 +419,7 @@ public struct RunProcessNonZeroExitError: Error { extension RunProcessNonZeroExitError: CustomStringConvertible, LocalizedError { public var description: String { - let fullArgs: [String] - if !environment.isEmpty { - fullArgs = ["env"] + [String: String](environment).sorted(byKey: <).map { key, value in "\(key)=\(value)" } + args - } else { - fullArgs = args - } - - let commandString = UNIXShellCommandCodec(encodingStrategy: .singleQuotes, encodingBehavior: .fullCommandLine).encode(fullArgs) - let fullCommandString: String - if let workingDirectory { - let directoryCommandString = UNIXShellCommandCodec(encodingStrategy: .singleQuotes, encodingBehavior: .fullCommandLine).encode(["cd", workingDirectory]) - fullCommandString = "(\([directoryCommandString, commandString].joined(separator: " && ")))" - } else { - fullCommandString = commandString - } - - let message = "The command `\(fullCommandString)` \(status)." + let message = "\(commandIdentityPrefixString) \(status)." switch output { case let .separate(stdout, stderr) where !stdout.isEmpty || !stderr.isEmpty: return message + [ diff --git a/Sources/SWBUtil/URL.swift b/Sources/SWBUtil/URL.swift index 264a792d..725f7ac5 100644 --- a/Sources/SWBUtil/URL.swift +++ b/Sources/SWBUtil/URL.swift @@ -35,6 +35,13 @@ extension URL { } } -fileprivate enum FileURLError: Error { +fileprivate enum FileURLError: Error, CustomStringConvertible { case notRepresentable(URL) + + var description: String { + switch self { + case .notRepresentable(let url): + return "URL \(url) cannot be represented as an absolute file path" + } + } } diff --git a/Sources/SwiftBuild/SWBClientExchangeSupport.swift b/Sources/SwiftBuild/SWBClientExchangeSupport.swift index f93c7af7..730caa20 100644 --- a/Sources/SwiftBuild/SWBClientExchangeSupport.swift +++ b/Sources/SwiftBuild/SWBClientExchangeSupport.swift @@ -42,7 +42,7 @@ fileprivate extension Processes.ExitStatus { return await session.service.send(ErrorResponse("No delegate for response.")) } - let result = await Result.catching { try await delegate.executeExternalTool(commandLine: message.commandLine, workingDirectory: message.workingDirectory, environment: message.environment) } + let result = await Result.catching { try await delegate.executeExternalTool(commandLine: message.commandLine, workingDirectory: message.workingDirectory?.str, environment: message.environment) } let reply = ExternalToolExecutionResponse(sessionHandle: message.sessionHandle, exchangeHandle: message.exchangeHandle, value: result.map(ExternalToolResult.init).mapError { .error("\($0)") }) return await session.service.send(reply) } diff --git a/Tests/SWBAndroidPlatformTests/SWBAndroidPlatformTests.swift b/Tests/SWBAndroidPlatformTests/SWBAndroidPlatformTests.swift index 2299add3..74ef48e4 100644 --- a/Tests/SWBAndroidPlatformTests/SWBAndroidPlatformTests.swift +++ b/Tests/SWBAndroidPlatformTests/SWBAndroidPlatformTests.swift @@ -19,7 +19,7 @@ import SWBCore @Suite fileprivate struct AndroidBuildOperationTests: CoreBasedTests { - @Test(.requireSDKs(.android), .requireThreadSafeWorkingDirectory, arguments: ["armv7", "aarch64", "riscv64", "i686", "x86_64"]) + @Test(.requireSDKs(.android), arguments: ["armv7", "aarch64", "riscv64", "i686", "x86_64"]) func androidCommandLineTool(arch: String) async throws { try await withTemporaryDirectory { (tmpDir: Path) in let testProject = TestProject( diff --git a/Tests/SWBBuildSystemTests/BuildCommandTests.swift b/Tests/SWBBuildSystemTests/BuildCommandTests.swift index 995e8a43..2cd6a109 100644 --- a/Tests/SWBBuildSystemTests/BuildCommandTests.swift +++ b/Tests/SWBBuildSystemTests/BuildCommandTests.swift @@ -22,7 +22,7 @@ import Foundation @Suite fileprivate struct BuildCommandTests: CoreBasedTests { /// Check compilation of a single file in C, ObjC and Swift, including the `uniquingSuffix` behavior. - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host)) func singleFileCompile() async throws { try await withTemporaryDirectory { tmpDirPath async throws -> Void in let testWorkspace = try await TestWorkspace( @@ -84,8 +84,13 @@ fileprivate struct BuildCommandTests: CoreBasedTests { try await tester.checkBuild(parameters: parameters, runDestination: runDestination, persistent: true, buildOutputMap: [cOutputPath: cFile.str]) { results in results.consumeTasksMatchingRuleTypes(excludedTypes) results.checkTaskExists(.matchRule(["CompileC", tmpDirPath.join("Test/aProject/build/aProject.build/Debug\(runDestination.builtProductsDirSuffix)/aLibrary.build/Objects-normal/\(results.runDestinationTargetArchitecture)/CFile.o").str, cFile.str, "normal", results.runDestinationTargetArchitecture, "c", "com.apple.compilers.llvm.clang.1_0.compiler"])) - if runDestination == .linux { // FIXME: This needs to be investigated... iIs not clear why this task is added when building a C file, and only on Linux. - results.checkTaskExists(.matchRule(["SwiftEmitModule", "normal", results.runDestinationTargetArchitecture, "Emitting module for aLibrary"])) + if runDestination == .linux { + // FIXME: This needs to be investigated... iIs not clear why this task is added when building a C file, and only on Linux. It's also nondeterministic. + let tasks = results.findMatchingTasks([.matchRule(["SwiftEmitModule", "normal", results.runDestinationTargetArchitecture, "Emitting module for aLibrary"])]) + for task in tasks { + results.removeMatchedTask(task) + } + #expect(tasks.count == 0 || tasks.count == 1) } results.checkNoTask() } @@ -164,7 +169,7 @@ fileprivate struct BuildCommandTests: CoreBasedTests { } /// Check analyze of a single file. - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host)) func singleFileAnalyze() async throws { try await runSingleFileTask(BuildParameters(configuration: "Debug", activeRunDestination: .host, overrides: ["RUN_CLANG_STATIC_ANALYZER": "YES"]), buildCommand: .singleFileBuild(buildOnlyTheseFiles: [Path("")]), fileName: "File.m") { results, excludedTypes, _, _ in results.consumeTasksMatchingRuleTypes(excludedTypes) @@ -174,7 +179,7 @@ fileprivate struct BuildCommandTests: CoreBasedTests { } /// Check preprocessing of a single file. - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host)) func preprocessSingleFile() async throws { try await runSingleFileTask(BuildParameters(configuration: "Debug", activeRunDestination: .host), buildCommand: .generatePreprocessedFile(buildOnlyTheseFiles: [Path("")]), fileName: "File.m") { results, excludedTypes, inputs, outputs in results.consumeTasksMatchingRuleTypes(excludedTypes) @@ -255,7 +260,7 @@ fileprivate struct BuildCommandTests: CoreBasedTests { } /// Check behavior of the skip dependencies flag. - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host)) func skipDependenciesFlag() async throws { func runTest(skipDependencies: Bool, checkAuxiliaryTarget: (_ results: BuildOperationTester.BuildResults) throws -> Void) async throws { try await withTemporaryDirectory { tmpDirPath async throws -> Void in diff --git a/Tests/SWBBuildSystemTests/BuildOperationTests.swift b/Tests/SWBBuildSystemTests/BuildOperationTests.swift index 70778e4a..e6c0caaf 100644 --- a/Tests/SWBBuildSystemTests/BuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/BuildOperationTests.swift @@ -30,7 +30,7 @@ import SWBTestSupport @Suite(.requireXcode16()) fileprivate struct BuildOperationTests: CoreBasedTests { - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory, arguments: ["clang", "swiftc"]) + @Test(.requireSDKs(.host), arguments: ["clang", "swiftc"]) func commandLineTool(linkerDriver: String) async throws { try await withTemporaryDirectory { (tmpDir: Path) in let testProject = try await TestProject( @@ -164,7 +164,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { } } - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host)) func commandLineToolAutolinkingFoundation() async throws { try await withTemporaryDirectory { (tmpDir: Path) in let testProject = try await TestProject( @@ -223,7 +223,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { } } - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host)) func debuggableCommandLineTool() async throws { try await withTemporaryDirectory { (tmpDir: Path) in let testProject = try await TestProject( @@ -397,7 +397,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { } } - @Test(.requireSDKs(.host), .skipHostOS(.macOS), .skipHostOS(.windows, "cannot find testing library"), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.macOS), .skipHostOS(.windows, "cannot find testing library")) func unitTestWithGeneratedEntryPoint() async throws { try await withTemporaryDirectory { (tmpDir: Path) in let testProject = try await TestProject( @@ -842,7 +842,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script } } - @Test(.requireSDKs(.host), .skipHostOS(.windows), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows)) func missingShellScriptInputs() async throws { // Test that shell scripts run, even if their inputs are missing. try await withTemporaryDirectory { tmpDirPath async throws -> Void in @@ -884,7 +884,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script } /// Check that target dependencies are honored. - @Test(.requireSDKs(.host), .skipHostOS(.windows), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows)) func simulatedTargetDependencies() async throws { try await withTemporaryDirectory { tmpDirPath async throws -> Void in let testWorkspace = TestWorkspace( @@ -948,7 +948,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script } /// Check that "build targets not in parallel" is honored. - @Test(.requireSDKs(.host), .skipHostOS(.windows), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows)) func simulatedNonParallelTargetBuild() async throws { try await withTemporaryDirectory { tmpDirPath async throws -> Void in let testWorkspace = TestWorkspace( @@ -990,7 +990,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script } /// Check that build phase order is honored. - @Test(.requireSDKs(.host), .skipHostOS(.windows), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows)) func simulatedBuildPhaseOrder() async throws { try await withTemporaryDirectory { tmpDirPath async throws -> Void in let testWorkspace = TestWorkspace( @@ -2392,7 +2392,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script } /// Check non-UTF8 encoded shell scripts don't cause any unexpected issues. - @Test(.requireSDKs(.host), .skipHostOS(.windows), .requireSystemPackages(apt: "xxd", yum: "vim-common"), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows), .requireSystemPackages(apt: "xxd", yum: "vim-common")) func nonUTF8ShellScript() async throws { try await withTemporaryDirectory { tmpDir in let testWorkspace = TestWorkspace( @@ -2461,7 +2461,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script } /// Check special shell script dependency handling - @Test(.requireSDKs(.host), .skipHostOS(.windows), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows)) func shellScriptIncrementalBehaviors() async throws { try await withTemporaryDirectory { tmpDirPath async throws -> Void in // Test that an incremental rebuild of an empty project does nothing. @@ -2532,7 +2532,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script } /// Check chown/chmod dependency handling. - @Test(.requireSDKs(.host), .skipHostOS(.windows), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows)) func setFileAttributesIncrementalBehaviors() async throws { try await withTemporaryDirectory { tmpDirPath async throws -> Void in // Test that an incremental rebuild of an empty project does nothing. @@ -2736,7 +2736,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script } /// Check the handling of a minimal copied bundle. - @Test(.requireSDKs(.host), .skipHostOS(.windows), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows)) func minimalCopiedBundle() async throws { try await withTemporaryDirectory { tmpDirPath async throws -> Void in let testWorkspace = TestWorkspace( @@ -4201,7 +4201,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script } /// Check that PCH file dependencies are respected. - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host)) func prefixHeaderDependencies() async throws { try await withTemporaryDirectory { tmpDirPath async throws -> Void in let testWorkspace = TestWorkspace( diff --git a/Tests/SWBBuildSystemTests/BuildTaskBehaviorTests.swift b/Tests/SWBBuildSystemTests/BuildTaskBehaviorTests.swift index c7ae170f..edc2d359 100644 --- a/Tests/SWBBuildSystemTests/BuildTaskBehaviorTests.swift +++ b/Tests/SWBBuildSystemTests/BuildTaskBehaviorTests.swift @@ -81,7 +81,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests { // FIXME: We should migrate these tests to primarily only use internal execution nodes, and not end up running tools (except for tests which are explicitly trying to test that behavior). - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory, .skipHostOS(.windows, "no /bin/echo")) + @Test(.requireSDKs(.host), .skipHostOS(.windows, "no /bin/echo")) func simulatedSingleInputlessOutputlessCommand() async throws { let echoTask = createTask(ruleInfo: ["echo", "hi"], commandLine: ["/bin/echo", "hi"], inputs: [], outputs: [MakePlannedVirtualNode("")], action: nil) @@ -207,7 +207,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests { /// Stress concurrent access to the build system cache during rapid cancel /// then build scenarios. - @Test(.requireSDKs(.host), .skipHostOS(.windows, "no /usr/bin/true"), .requireThreadSafeWorkingDirectory, + @Test(.requireSDKs(.host), .skipHostOS(.windows, "no /usr/bin/true"), // To aid in establishing the subtle concurrent // timing required to trigger chaos, we disable early build operation // cancellation. @@ -307,7 +307,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests { } /// Check that we honor specs which are unsafe to interrupt. - @Test(.requireSDKs(.host), .skipHostOS(.windows, "no bash shell"), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows, "no bash shell")) func unsafeToInterrupt() async throws { let fs = localFS let output = MakePlannedVirtualNode("") @@ -382,7 +382,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests { } /// Check the behavior of gate tasks. - @Test(.requireSDKs(.host), .skipHostOS(.windows, "no /usr/bin/true"), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows, "no /usr/bin/true")) func simulatedTasksWithGate() async throws { let aNode = MakePlannedVirtualNode("A") let bNode = MakePlannedVirtualNode("B") @@ -406,7 +406,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests { } } - @Test(.requireSDKs(.host), .skipHostOS(.windows, "no /bin/echo"), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows, "no /bin/echo")) func simulatedDiamondGraph() async throws { let tasksToMake = [ ("START", inputs: ["/INPUT"]), @@ -442,7 +442,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests { } } - @Test(.requireSDKs(.host), .skipHostOS(.windows, "no /usr/bin/true"), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows, "no /usr/bin/true")) func simulatedMustPrecede() async throws { let tasksToMake = ["A", "B", "C", "D"] var tasks: [any PlannedTask] = [] @@ -902,7 +902,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests { } /// Check the handling of directory tree nodes. - @Test(.skipHostOS(.windows, "no /usr/bin/find"), .requireSDKs(.host), .requireThreadSafeWorkingDirectory) + @Test(.skipHostOS(.windows, "no /usr/bin/find"), .requireSDKs(.host)) func directoryTreeInputs() async throws { try await withTemporaryDirectory { tmpDir in let fs = localFS @@ -953,7 +953,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests { } } - @Test(.requireSDKs(.host), .skipHostOS(.windows, "no /bin/echo"), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host), .skipHostOS(.windows, "no /bin/echo")) func additionalInfoOutput() async throws { let echoTask = createTask(ruleInfo: ["echo", "additional-output"], commandLine: ["/bin/echo", "additional-output"], additionalOutput: ["just some extra output"], inputs: [], outputs: [MakePlannedVirtualNode("")], action: nil) diff --git a/Tests/SWBBuildSystemTests/CustomTaskBuildOperationTests.swift b/Tests/SWBBuildSystemTests/CustomTaskBuildOperationTests.swift index c8dfe2f0..f94d48ac 100644 --- a/Tests/SWBBuildSystemTests/CustomTaskBuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/CustomTaskBuildOperationTests.swift @@ -24,7 +24,7 @@ import class Foundation.ProcessInfo @Suite fileprivate struct CustomTaskBuildOperationTests: CoreBasedTests { - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host)) func outputParsing() async throws { try await withTemporaryDirectory { tmpDir in let destination: RunDestinationInfo = .host diff --git a/Tests/SWBBuildSystemTests/LinkerTests.swift b/Tests/SWBBuildSystemTests/LinkerTests.swift index 184f51d2..36833664 100644 --- a/Tests/SWBBuildSystemTests/LinkerTests.swift +++ b/Tests/SWBBuildSystemTests/LinkerTests.swift @@ -181,7 +181,7 @@ fileprivate struct LinkerTests: CoreBasedTests { /// * The clang output on Windows has paths that have double slashes, not /// quite valid command quoted. i.e. "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC" /// This needs to be taken into account. - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host)) func alternateLinkerSelection() async throws { let runDestination: RunDestinationInfo = .host let swiftVersion = try await self.swiftVersion diff --git a/Tests/SWBBuildSystemTests/SwiftBuildTraceTests.swift b/Tests/SWBBuildSystemTests/SwiftBuildTraceTests.swift index 461cf787..563c0575 100644 --- a/Tests/SWBBuildSystemTests/SwiftBuildTraceTests.swift +++ b/Tests/SWBBuildSystemTests/SwiftBuildTraceTests.swift @@ -20,7 +20,7 @@ import SWBTaskExecution @Suite fileprivate struct SwiftBuildTraceTests: CoreBasedTests { - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory) + @Test(.requireSDKs(.host)) func swiftBuildTraceEmission() async throws { try await withTemporaryDirectory { tmpDirPath async throws -> Void in try await withEnvironment(["SWIFTBUILD_TRACE_FILE": tmpDirPath.join(".SWIFTBUILD_TRACE").str]) { @@ -71,7 +71,6 @@ fileprivate struct SwiftBuildTraceTests: CoreBasedTests { } let trace = try tester.fs.read(tmpDirPath.join(".SWIFTBUILD_TRACE")).asString - print(trace) #expect(try #/\{\"buildDescriptionSignature\":\".*\",\"isTargetParallelizationEnabled\":true,\"name\":\"Test\",\"path\":\".*\"\}\n\{\"buildDescriptionSignature\":\".*\",\"isTargetParallelizationEnabled\":true,\"name\":\"Test\",\"path\":\".*\"\}\n/#.wholeMatch(in: trace) != nil) } } diff --git a/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift b/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift index 6a2eae3b..d85ccb2c 100644 --- a/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift +++ b/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift @@ -236,7 +236,7 @@ extension CapturingTaskGenerationDelegate: TaskActionCreationDelegate { } extension CapturingTaskGenerationDelegate: CoreClientDelegate { - func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String: String]) async throws -> ExternalToolResult { + func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String: String]) async throws -> ExternalToolResult { return .emptyResult } } diff --git a/Tests/SWBCoreTests/ClangSerializedDiagnosticsTests.swift b/Tests/SWBCoreTests/ClangSerializedDiagnosticsTests.swift index 81070281..3f37e39e 100644 --- a/Tests/SWBCoreTests/ClangSerializedDiagnosticsTests.swift +++ b/Tests/SWBCoreTests/ClangSerializedDiagnosticsTests.swift @@ -28,11 +28,11 @@ fileprivate struct ClangSerializedDiagnosticsTests: CoreBasedTests { } /// Test that Clang serialized diagnostics are supported. - @Test + @Test(.requireThreadSafeWorkingDirectory) func clangSerializedDiagnosticSupported() async throws { try await withTemporaryDirectory { tmpDir in let diagnosticsPath = tmpDir.join("foo.diag") - _ = try? await runHostProcess(["clang", "-serialize-diagnostics", diagnosticsPath.str, "foo.c"], workingDirectory: tmpDir.str) + _ = try? await runHostProcess(["clang", "-serialize-diagnostics", diagnosticsPath.str, "foo.c"], workingDirectory: tmpDir) let libclang = try #require(await Libclang(path: libclangPath)) libclang.leak() @@ -53,7 +53,7 @@ fileprivate struct ClangSerializedDiagnosticsTests: CoreBasedTests { let cFilePath = tmpDir.join("dir/foo.c") try localFS.write(cFilePath, contents: "#include \"other/foo.h\"\nint main() { return 0; }") let taskWorkingDirectory = cFilePath.dirname - _ = try? await runHostProcess(["clang", "-I../", "-Wall", "-serialize-diagnostics", diagnosticsPath.str, "foo.c"], workingDirectory: taskWorkingDirectory.str) + _ = try? await runHostProcess(["clang", "-I../", "-Wall", "-serialize-diagnostics", diagnosticsPath.str, "foo.c"], workingDirectory: taskWorkingDirectory) let libclang = try #require(await Libclang(path: libclangPath)) libclang.leak() @@ -82,7 +82,7 @@ fileprivate struct ClangSerializedDiagnosticsTests: CoreBasedTests { let cFilePath = tmpDir.join("dir/foo.c") try localFS.write(cFilePath, contents: "#include \"other/foo.h\"\nint main() { return 0; }") let taskWorkingDirectory = cFilePath.dirname - _ = try? await runHostProcess(["clang", "-I../", "-Wall", "-serialize-diagnostics", diagnosticsPath.str, "foo.c"], workingDirectory: taskWorkingDirectory.str) + _ = try? await runHostProcess(["clang", "-I../", "-Wall", "-serialize-diagnostics", diagnosticsPath.str, "foo.c"], workingDirectory: taskWorkingDirectory) let libclang = try #require(await Libclang(path: libclangPath)) libclang.leak() @@ -106,7 +106,7 @@ fileprivate struct ClangSerializedDiagnosticsTests: CoreBasedTests { try localFS.write(swiftFilePath, contents: "#warning(\"custom warning\")") let taskWorkingDirectory = swiftFilePath.dirname let diagnosticsPath = taskWorkingDirectory.join("foo.dia") - _ = try? await runHostProcess(["swiftc", "-c", "-serialize-diagnostics", "foo.swift"], workingDirectory: taskWorkingDirectory.str) + _ = try? await runHostProcess(["swiftc", "-c", "-serialize-diagnostics", "foo.swift"], workingDirectory: taskWorkingDirectory) let libclang = try #require(await Libclang(path: libclangPath)) libclang.leak() diff --git a/Tests/SWBProtocolTests/MessageSerializationTests.swift b/Tests/SWBProtocolTests/MessageSerializationTests.swift index 53dcc2c4..c749db18 100644 --- a/Tests/SWBProtocolTests/MessageSerializationTests.swift +++ b/Tests/SWBProtocolTests/MessageSerializationTests.swift @@ -105,7 +105,7 @@ import Testing } @Test func clientExchangeMessagesRoundTrip() { - assertMsgPackMessageRoundTrip(ExternalToolExecutionRequest(sessionHandle: "theSession", exchangeHandle: "handle", commandLine: ["echo", "foo"], workingDirectory: "/foo", environment: ["FOO": "BAR"])) + assertMsgPackMessageRoundTrip(ExternalToolExecutionRequest(sessionHandle: "theSession", exchangeHandle: "handle", commandLine: ["echo", "foo"], workingDirectory: .root.join("foo"), environment: ["FOO": "BAR"])) assertMsgPackMessageRoundTrip(ExternalToolExecutionResponse(sessionHandle: "theSession", exchangeHandle: "handle", value: .success(.result(status: .exit(1), stdout: Data("foo".utf8), stderr: Data("bar".utf8))))) assertMsgPackMessageRoundTrip(ExternalToolExecutionResponse(sessionHandle: "theSession", exchangeHandle: "handle", value: .failure(StubError.error("broken!")))) diff --git a/Tests/SWBQNXPlatformTests/SWBQNXPlatformTests.swift b/Tests/SWBQNXPlatformTests/SWBQNXPlatformTests.swift index 3d9b9b92..bdce751c 100644 --- a/Tests/SWBQNXPlatformTests/SWBQNXPlatformTests.swift +++ b/Tests/SWBQNXPlatformTests/SWBQNXPlatformTests.swift @@ -19,7 +19,7 @@ import SWBCore @Suite fileprivate struct QNXBuildOperationTests: CoreBasedTests { - @Test(.requireSDKs(.qnx), .skipHostOS(.macOS), .requireThreadSafeWorkingDirectory, arguments: ["aarch64", "x86_64"]) + @Test(.requireSDKs(.qnx), .skipHostOS(.macOS), arguments: ["aarch64", "x86_64"]) func qnxCommandLineTool(arch: String) async throws { try await withTemporaryDirectory { (tmpDir: Path) in let testProject = TestProject( diff --git a/Tests/SWBTaskConstructionTests/BuildToolTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/BuildToolTaskConstructionTests.swift index 4f8f8ca7..5bcdaf39 100644 --- a/Tests/SWBTaskConstructionTests/BuildToolTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/BuildToolTaskConstructionTests.swift @@ -245,7 +245,7 @@ fileprivate struct BuildToolTaskConstructionTests: CoreBasedTests { /// Client to generate files from the core data model. final class TestCoreDataCompilerTaskPlanningClientDelegate: MockTestTaskPlanningClientDelegate, @unchecked Sendable { - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { if commandLine.first.map(Path.init)?.basename != "momc" { return try await super.executeExternalTool(commandLine: commandLine, workingDirectory: workingDirectory, environment: environment) } @@ -469,7 +469,7 @@ fileprivate struct BuildToolTaskConstructionTests: CoreBasedTests { /// Client to generate files from the CoreML model. final class TestCoreMLCompilerTaskPlanningClientDelegate: MockTestTaskPlanningClientDelegate, @unchecked Sendable { - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { if commandLine.first.map(Path.init)?.basename == "coremlc", let outputDir = commandLine[safe: 3].map(Path.init), let input = commandLine.firstIndex(where: { $0.hasSuffix(".mlmodel") || $0.hasSuffix(".mlpackage") }).map({ Path(commandLine[$0]) }), @@ -1172,7 +1172,7 @@ fileprivate struct BuildToolTaskConstructionTests: CoreBasedTests { self.moduleName = moduleName } - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { if commandLine.first.map(Path.init)?.basename == "intentbuilderc", let outputDir = commandLine.elementAfterElements(["-output"]).map(Path.init), let classPrefix = commandLine.elementAfterElements(["-classPrefix"]), diff --git a/Tests/SWBTaskConstructionTests/ClangTests.swift b/Tests/SWBTaskConstructionTests/ClangTests.swift index 39cb40df..20010d33 100644 --- a/Tests/SWBTaskConstructionTests/ClangTests.swift +++ b/Tests/SWBTaskConstructionTests/ClangTests.swift @@ -196,7 +196,7 @@ fileprivate struct ClangTests: CoreBasedTests { super.init() } - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { if commandLine.first == mockClangPath { return .result(status: .exit(0), stdout: Data(), stderr: Data()) } diff --git a/Tests/SWBTaskConstructionTests/EagerCompilationTests.swift b/Tests/SWBTaskConstructionTests/EagerCompilationTests.swift index af9a6cc6..fe8f01bb 100644 --- a/Tests/SWBTaskConstructionTests/EagerCompilationTests.swift +++ b/Tests/SWBTaskConstructionTests/EagerCompilationTests.swift @@ -1256,7 +1256,7 @@ fileprivate struct EagerCompilationTests: CoreBasedTests { let buildRequest = BuildRequest(parameters: parameters, buildTargets: tester.workspace.projects[0].targets.map({ BuildRequest.BuildTargetInfo(parameters: parameters, target: $0) }), continueBuildingAfterErrors: true, useParallelTargets: true, useImplicitDependencies: false, useDryRun: false) final class Delegate: MockTestTaskPlanningClientDelegate, @unchecked Sendable { - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { switch commandLine.first.map(Path.init)?.basename { case "intentbuilderc"?: do { diff --git a/Tests/SWBTaskConstructionTests/InstallAPITaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/InstallAPITaskConstructionTests.swift index 8390c67d..10338052 100644 --- a/Tests/SWBTaskConstructionTests/InstallAPITaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/InstallAPITaskConstructionTests.swift @@ -1797,7 +1797,7 @@ fileprivate struct InstallAPITaskConstructionTests: CoreBasedTests { let tester = try await TaskConstructionTester(getCore(), testProject) final class Delegate: MockTestTaskPlanningClientDelegate, @unchecked Sendable { - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { if commandLine.first.map(Path.init)?.basename == "intentbuilderc", let outputDir = commandLine.elementAfterElements(["-output"]).map(Path.init), let classPrefix = commandLine.elementAfterElements(["-classPrefix"]), diff --git a/Tests/SWBTaskConstructionTests/ModuleVerifierTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/ModuleVerifierTaskConstructionTests.swift index 8d83632e..e01298a5 100644 --- a/Tests/SWBTaskConstructionTests/ModuleVerifierTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/ModuleVerifierTaskConstructionTests.swift @@ -901,7 +901,7 @@ fileprivate struct ModuleVerifierTaskConstructionTests: CoreBasedTests { } fileprivate final class TestIntentsCompilerTaskPlanningClientDelegate: MockTestTaskPlanningClientDelegate, @unchecked Sendable { - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { let commandName = commandLine.first.map(Path.init)?.basename switch commandName { case "intentbuilderc": diff --git a/Tests/SWBTaskConstructionTests/OnDemandResourcesTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/OnDemandResourcesTaskConstructionTests.swift index 5695b3e5..feda1daf 100644 --- a/Tests/SWBTaskConstructionTests/OnDemandResourcesTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/OnDemandResourcesTaskConstructionTests.swift @@ -67,7 +67,7 @@ fileprivate struct OnDemandResourcesTaskConstructionTests: CoreBasedTests { let SRCROOT = tester.workspace.projects[0].sourceRoot.str final class ClientDelegate: MockTestTaskPlanningClientDelegate, @unchecked Sendable { - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { if commandLine.first.map(Path.init)?.basename == "actool", commandLine.dropFirst().first != "--version" { return .result(status: .exit(0), stdout: Data("{}".utf8), stderr: Data()) } diff --git a/Tests/SWBTaskConstructionTests/PackageProductConstructionTests.swift b/Tests/SWBTaskConstructionTests/PackageProductConstructionTests.swift index e829ad94..ee33a453 100644 --- a/Tests/SWBTaskConstructionTests/PackageProductConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/PackageProductConstructionTests.swift @@ -1245,7 +1245,7 @@ fileprivate struct PackageProductConstructionTests: CoreBasedTests { /// Client to generate files from the core data model. final class TestCoreDataCompilerTaskPlanningClientDelegate: MockTestTaskPlanningClientDelegate, @unchecked Sendable { - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { if commandLine.first.map(Path.init)?.basename == "momc", let outputDir = commandLine.last.map(Path.init) { return .result(status: .exit(0), stdout: Data([ outputDir.join("EntityOne+CoreDataClass.swift"), diff --git a/Tests/SWBTaskConstructionTests/PreviewsTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/PreviewsTaskConstructionTests.swift index d829de9e..4e7e6eee 100644 --- a/Tests/SWBTaskConstructionTests/PreviewsTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/PreviewsTaskConstructionTests.swift @@ -807,7 +807,7 @@ fileprivate struct PreviewsTaskConstructionTests: CoreBasedTests { ]) final class ClientDelegate: MockTestTaskPlanningClientDelegate, @unchecked Sendable { - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { if commandLine.first.map(Path.init)?.basename == "actool", commandLine.dropFirst().first != "--version" { return .result(status: .exit(0), stdout: Data("{}".utf8), stderr: Data()) } @@ -984,7 +984,7 @@ fileprivate struct PreviewsTaskConstructionTests: CoreBasedTests { ]) final class ClientDelegate: MockTestTaskPlanningClientDelegate, @unchecked Sendable { - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { if commandLine.first.map(Path.init)?.basename == "actool", commandLine.dropFirst().first != "--version" { return .result(status: .exit(0), stdout: Data("{}".utf8), stderr: Data()) } @@ -1074,7 +1074,7 @@ fileprivate struct PreviewsTaskConstructionTests: CoreBasedTests { ]) final class ClientDelegate: MockTestTaskPlanningClientDelegate, @unchecked Sendable { - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { if commandLine.first.map(Path.init)?.basename == "actool", commandLine.dropFirst().first != "--version" { return .result(status: .exit(0), stdout: Data("{}".utf8), stderr: Data()) } @@ -1175,7 +1175,7 @@ fileprivate struct PreviewsTaskConstructionTests: CoreBasedTests { ]) final class ClientDelegate: MockTestTaskPlanningClientDelegate, @unchecked Sendable { - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { if commandLine.first.map(Path.init)?.basename == "actool", commandLine.dropFirst().first != "--version" { return .result(status: .exit(0), stdout: Data("{}".utf8), stderr: Data()) } diff --git a/Tests/SWBTaskConstructionTests/TaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/TaskConstructionTests.swift index a40e9382..a3931342 100644 --- a/Tests/SWBTaskConstructionTests/TaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/TaskConstructionTests.swift @@ -7287,7 +7287,7 @@ fileprivate struct TaskConstructionTests: CoreBasedTests { /// Client to generate files from the core data model. class TestCoreDataCompilerTaskPlanningClientDelegate: TaskPlanningClientDelegate { - func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { return .emptyResult } } @@ -7393,7 +7393,7 @@ fileprivate struct TaskConstructionTests: CoreBasedTests { let tester = try await TaskConstructionTester(getCore(), testWorkspace) final class Delegate: MockTestTaskPlanningClientDelegate, @unchecked Sendable { - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { if commandLine.first.map(Path.init)?.basename == "intentbuilderc", let outputDir = commandLine.elementAfterElements(["-output"]).map(Path.init), let classPrefix = commandLine.elementAfterElements(["-classPrefix"]), diff --git a/Tests/SWBTaskConstructionTests/XCStringsTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/XCStringsTaskConstructionTests.swift index 9e5272fb..79ec9b21 100644 --- a/Tests/SWBTaskConstructionTests/XCStringsTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/XCStringsTaskConstructionTests.swift @@ -32,7 +32,7 @@ private final class MockXCStringsTool: MockTestTaskPlanningClientDelegate, @unch self.requiredCommandLine = requiredCommandLine } - override func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + override func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { switch commandLine.first.map(Path.init)?.basename { case "xcstringstool": break diff --git a/Tests/SWBTaskExecutionTests/InProcessTaskTestSupport.swift b/Tests/SWBTaskExecutionTests/InProcessTaskTestSupport.swift index ac43422e..82e321c8 100644 --- a/Tests/SWBTaskExecutionTests/InProcessTaskTestSupport.swift +++ b/Tests/SWBTaskExecutionTests/InProcessTaskTestSupport.swift @@ -59,7 +59,7 @@ struct MockExecutionDelegate: TaskExecutionDelegate { } struct MockTaskExecutionClientDelegate: TaskExecutionClientDelegate { - func executeExternalTool(commandLine: [String], workingDirectory: String?, environment: [String : String]) async throws -> ExternalToolResult { + func executeExternalTool(commandLine: [String], workingDirectory: Path?, environment: [String : String]) async throws -> ExternalToolResult { .deferred } } diff --git a/Tests/SWBTaskExecutionTests/TaskTestSupport.swift b/Tests/SWBTaskExecutionTests/TaskTestSupport.swift index 18c0fd93..ba202ff0 100644 --- a/Tests/SWBTaskExecutionTests/TaskTestSupport.swift +++ b/Tests/SWBTaskExecutionTests/TaskTestSupport.swift @@ -56,14 +56,14 @@ class MockDynamicTaskExecutionDelegate: DynamicTaskExecutionDelegate { func spawn( commandLine: [String], environment: [String: String], - workingDirectory: String, + workingDirectory: Path, processDelegate: any ProcessDelegate ) async throws -> Bool { if commandLine.isEmpty { throw StubError.error("Invalid number of arguments") } - let executionResult = try await Process.getOutput(url: URL(fileURLWithPath: commandLine[0]), arguments: Array(commandLine.dropFirst()), currentDirectoryURL: URL(fileURLWithPath: workingDirectory), environment: .init(environment)) + let executionResult = try await Process.getOutput(url: URL(fileURLWithPath: commandLine[0]), arguments: Array(commandLine.dropFirst()), currentDirectoryURL: URL(fileURLWithPath: workingDirectory.str), environment: .init(environment)) // FIXME: Pass the real PID let pid = llbuild_pid_t.invalid diff --git a/Tests/SWBUtilTests/MachOTests.swift b/Tests/SWBUtilTests/MachOTests.swift index c7709e1b..06f9554f 100644 --- a/Tests/SWBUtilTests/MachOTests.swift +++ b/Tests/SWBUtilTests/MachOTests.swift @@ -448,7 +448,7 @@ fileprivate struct MachOTests { try await withTemporaryDirectory { path in let expectedVersion = Version(11, 2, 3) try localFS.write(path.join("main.c"), contents: "int main() { return 0; }") - _ = try await InstalledXcode.currentlySelected().xcrun(["-sdk", "macosx", "clang", "-target", "\(#require(Architecture.host.stringValue))-apple-macos\(expectedVersion)", "main.c"], workingDirectory: path.str) + _ = try await InstalledXcode.currentlySelected().xcrun(["-sdk", "macosx", "clang", "-target", "\(#require(Architecture.host.stringValue))-apple-macos\(expectedVersion)", "main.c"], workingDirectory: path) let machOPath = path.join("a.out") let files: [BinaryReader] = try allReaders(machOPath) @@ -475,7 +475,7 @@ fileprivate struct MachOTests { func rPaths() async throws { try await withTemporaryDirectory { path in try localFS.write(path.join("main.c"), contents: "int main() { return 0; }") - _ = try await InstalledXcode.currentlySelected().xcrun(["-sdk", "macosx", "clang", "-target", "\(#require(Architecture.host.stringValue))-apple-macos11.0", "-rpath", "@loader_path/Frameworks", "-rpath", "@loader_path/../Frameworks", "main.c"], workingDirectory: path.str) + _ = try await InstalledXcode.currentlySelected().xcrun(["-sdk", "macosx", "clang", "-target", "\(#require(Architecture.host.stringValue))-apple-macos11.0", "-rpath", "@loader_path/Frameworks", "-rpath", "@loader_path/../Frameworks", "main.c"], workingDirectory: path) let machOPath = path.join("a.out") let files: [BinaryReader] = try allReaders(machOPath) @@ -501,7 +501,7 @@ fileprivate struct MachOTests { func atomInfo() async throws { try await withTemporaryDirectory { path in try localFS.write(path.join("file.c"), contents: "const int foo = 0;") - _ = try await InstalledXcode.currentlySelected().xcrun(["-sdk", "macosx", "clang", "-target", "\(#require(Architecture.host.stringValue))-apple-macos11.0", "-dynamiclib", "-Xlinker", "-make_mergeable", "file.c"], workingDirectory: path.str) + _ = try await InstalledXcode.currentlySelected().xcrun(["-sdk", "macosx", "clang", "-target", "\(#require(Architecture.host.stringValue))-apple-macos11.0", "-dynamiclib", "-Xlinker", "-make_mergeable", "file.c"], workingDirectory: path) let machOPath = path.join("a.out") let files: [BinaryReader] = try allReaders(machOPath) diff --git a/Tests/SWBWebAssemblyPlatformTests/SWBWebAssemblyPlatformTests.swift b/Tests/SWBWebAssemblyPlatformTests/SWBWebAssemblyPlatformTests.swift index fcef5cb9..3f1eddc1 100644 --- a/Tests/SWBWebAssemblyPlatformTests/SWBWebAssemblyPlatformTests.swift +++ b/Tests/SWBWebAssemblyPlatformTests/SWBWebAssemblyPlatformTests.swift @@ -21,7 +21,6 @@ import SWBUtil fileprivate struct SWBWebAssemblyPlatformTests: CoreBasedTests { @Test( .requireSDKs(.wasi), - .requireThreadSafeWorkingDirectory, .skipXcodeToolchain, arguments: ["wasm32"], [true, false] ) @@ -132,7 +131,7 @@ fileprivate struct SWBWebAssemblyPlatformTests: CoreBasedTests { } } - @Test(.requireSDKs(.wasi), .requireThreadSafeWorkingDirectory, .skipXcodeToolchain, arguments: ["wasm32"]) + @Test(.requireSDKs(.wasi), .skipXcodeToolchain, arguments: ["wasm32"]) func wasiCommandWithCAndCxx(arch: String) async throws { let sdkroot = try await #require(getCore().loadSDK(llvmTargetTripleSys: "wasi")).path.str diff --git a/Tests/SwiftBuildPerfTests/BuildOperationPerfTests.swift b/Tests/SwiftBuildPerfTests/BuildOperationPerfTests.swift index 327b2777..48654582 100644 --- a/Tests/SwiftBuildPerfTests/BuildOperationPerfTests.swift +++ b/Tests/SwiftBuildPerfTests/BuildOperationPerfTests.swift @@ -21,7 +21,7 @@ import SWBTestSupport import Testing -@Suite(.performance, .requireThreadSafeWorkingDirectory) +@Suite(.performance) fileprivate struct BuildOperationPerfTests: PerfTests { /// Run a test of a synthetic project with a given number of targets and files. /// diff --git a/Tests/SwiftBuildTests/BuildOperationTests.swift b/Tests/SwiftBuildTests/BuildOperationTests.swift index c6d217a0..e9d94dbf 100644 --- a/Tests/SwiftBuildTests/BuildOperationTests.swift +++ b/Tests/SwiftBuildTests/BuildOperationTests.swift @@ -288,7 +288,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { } } - @Test(.requireSDKs(.macOS), .skipHostOS(.windows), .requireThreadSafeWorkingDirectory) // version info discovery isn't working on Windows + @Test(.requireSDKs(.macOS), .skipHostOS(.windows)) // version info discovery isn't working on Windows func onlyCreateBuildDescription() async throws { try await withTemporaryDirectory { temporaryDirectory in try await withAsyncDeferrable { deferrable in @@ -445,8 +445,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { @Test( .requireSDKs(.host), - .skipHostOS(.windows), - .requireThreadSafeWorkingDirectory /* version info discovery isn't working on Windows */, + .skipHostOS(.windows), /* version info discovery isn't working on Windows */ .flaky("Test occasionally crashes in linux CI"), .bug("https://github.com/swiftlang/swift-build/issues/528") ) @@ -1777,7 +1776,7 @@ fileprivate struct BuildOperationTests: CoreBasedTests { } /// Check scraped build issues. - @Test(.requireSDKs(.macOS), .skipHostOS(.windows), .requireThreadSafeWorkingDirectory) // relies on UNIX shell, consider adding Windows command shell support for script phases? + @Test(.requireSDKs(.macOS), .skipHostOS(.windows)) // relies on UNIX shell, consider adding Windows command shell support for script phases? func buildScriptIssues() async throws { try await withTemporaryDirectory { temporaryDirectory in try await withAsyncDeferrable { deferrable in diff --git a/Tests/SwiftBuildTests/DocumentationTests.swift b/Tests/SwiftBuildTests/DocumentationTests.swift index c0534523..49409775 100644 --- a/Tests/SwiftBuildTests/DocumentationTests.swift +++ b/Tests/SwiftBuildTests/DocumentationTests.swift @@ -20,7 +20,7 @@ import Testing @Suite fileprivate struct DocumentationBuildTests: CoreBasedTests { // docc fails on Windows for some reason - @Test(.requireSDKs(.host), .requireThreadSafeWorkingDirectory, .skipHostOS(.windows)) + @Test(.requireSDKs(.host), .skipHostOS(.windows)) func documentationBuild() async throws { try await withTemporaryDirectory { tmpDir in let fs: any FSProxy = localFS diff --git a/Tests/SwiftBuildTests/ServiceTests.swift b/Tests/SwiftBuildTests/ServiceTests.swift index dcd8eef7..aa928a09 100644 --- a/Tests/SwiftBuildTests/ServiceTests.swift +++ b/Tests/SwiftBuildTests/ServiceTests.swift @@ -406,7 +406,7 @@ fileprivate struct ServiceTests { let osv = ProcessInfo.processInfo.operatingSystemVersion let osVersion = try Version(osv) let deploymentTarget = Version(Version.Component(osv.majorVersion), Version.Component(osv.minorVersion), UInt(osv.patchVersion) + 1) - _ = try await InstalledXcode.currentlySelected().xcrun(["-sdk", "macosx", "clang", "-target", "\(#require(Architecture.host.stringValue))-apple-macos\(deploymentTarget.canonicalDeploymentTargetForm)", "main.c"], workingDirectory: path.str) + _ = try await InstalledXcode.currentlySelected().xcrun(["-sdk", "macosx", "clang", "-target", "\(#require(Architecture.host.stringValue))-apple-macos\(deploymentTarget.canonicalDeploymentTargetForm)", "main.c"], workingDirectory: path) _ = await withEnvironment(["SWBBUILDSERVICE_PATH": path.join("a.out").str]) { await #expect(performing: { diff --git a/Tests/SwiftBuildTests/ValidationTests.swift b/Tests/SwiftBuildTests/ValidationTests.swift index 161ccacb..b58590e2 100644 --- a/Tests/SwiftBuildTests/ValidationTests.swift +++ b/Tests/SwiftBuildTests/ValidationTests.swift @@ -40,7 +40,7 @@ fileprivate struct ValidationTests: CoreBasedTests { // Run the subprocess, check the result, and return the output if we succeeded. let executionResult = try await Process.getOutput(url: url, arguments: args, currentDirectoryURL: URL(fileURLWithPath: workingDirectory.str), environment: environment) if !executionResult.exitStatus.isSuccess { - throw RunProcessNonZeroExitError(args: [url.path] + args, workingDirectory: workingDirectory.str, environment: environment, status: executionResult.exitStatus, stdout: ByteString(executionResult.stdout), stderr: ByteString(executionResult.stderr)) + throw RunProcessNonZeroExitError(args: [url.path] + args, workingDirectory: workingDirectory, environment: environment, status: executionResult.exitStatus, stdout: ByteString(executionResult.stdout), stderr: ByteString(executionResult.stderr)) } }