From 1d6c598ab3893b0c4f39572945b5b96a004d8294 Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Sat, 13 Sep 2025 14:26:35 -0700 Subject: [PATCH] Fix linker filelist parsing when discovering tests Take the format into account when parsing response files, and consistently track it when we read and write them in various places throughout the build system --- Sources/SWBCore/PlannedTaskAction.swift | 2 +- Sources/SWBCore/Settings/BuiltinMacros.swift | 12 ++- .../SpecImplementations/LinkerSpec.swift | 15 +--- .../PropertyDomainSpec.swift | 4 +- .../SpecImplementations/Tools/CCompiler.swift | 4 +- .../Tools/LinkerTools.swift | 6 +- .../Tools/SwiftCompiler.swift | 12 ++- .../Specs/UnixLd.xcspec | 12 +++ .../Specs/UnixLibtool.xcspec | 11 +++ .../BuildDescriptionManager.swift | 4 +- Sources/SWBTaskExecution/CMakeLists.txt | 1 + ...onseFileFormat+ExpressibleByArgument.swift | 16 ++++ .../TaskActions/ClangScanTaskAction.swift | 2 +- .../TaskActions/LinkerTaskAction.swift | 15 ++-- .../ObjectLibraryAssemblerTaskAction.swift | 3 +- .../TaskActions/SwiftDriverTaskAction.swift | 2 +- .../CapturingTaskGenerationDelegate.swift | 4 +- .../TaskPlanningTestSupport.swift | 4 +- Sources/SWBUniversalPlatform/Specs/Ld.xcspec | 22 ++++++ .../Specs/ObjectLibraryAssembler.xcspec | 6 +- .../TestEntryPointGenerationTaskAction.swift | 4 +- .../TestEntryPointGenerationTool.swift | 4 + Sources/SWBUtil/ResponseFiles.swift | 78 +++++++++++++++---- .../SWBWindowsPlatform/Specs/WindowsLd.xcspec | 12 +++ .../Specs/WindowsLibtool.xcspec | 12 +++ .../BuildOperationTests.swift | 24 ------ .../CommandLineSpecPerfTests.swift | 4 +- .../ObjectLibraryTaskConstructionTests.swift | 2 + .../UnitTestTaskConstructionTests.swift | 2 +- Tests/SWBUtilTests/ResponseFileTests.swift | 64 ++++++++++----- 30 files changed, 256 insertions(+), 107 deletions(-) create mode 100644 Sources/SWBTaskExecution/ResponseFileFormat+ExpressibleByArgument.swift diff --git a/Sources/SWBCore/PlannedTaskAction.swift b/Sources/SWBCore/PlannedTaskAction.swift index 4ffc6484..ab6e2419 100644 --- a/Sources/SWBCore/PlannedTaskAction.swift +++ b/Sources/SWBCore/PlannedTaskAction.swift @@ -349,7 +349,7 @@ public protocol TaskActionCreationDelegate func createProcessSDKImportsTaskAction() -> any PlannedTaskAction func createValidateDependenciesTaskAction() -> any PlannedTaskAction func createObjectLibraryAssemblerTaskAction() -> any PlannedTaskAction - func createLinkerTaskAction(expandResponseFiles: Bool) -> any PlannedTaskAction + func createLinkerTaskAction(expandResponseFiles: Bool, responseFileFormat: ResponseFileFormat) -> any PlannedTaskAction } extension TaskActionCreationDelegate { diff --git a/Sources/SWBCore/Settings/BuiltinMacros.swift b/Sources/SWBCore/Settings/BuiltinMacros.swift index 7570b70b..50cc3505 100644 --- a/Sources/SWBCore/Settings/BuiltinMacros.swift +++ b/Sources/SWBCore/Settings/BuiltinMacros.swift @@ -1188,7 +1188,8 @@ public final class BuiltinMacros { public static let _WRAPPER_PARENT_PATH = BuiltinMacros.declareStringMacro("_WRAPPER_PARENT_PATH") public static let _WRAPPER_RESOURCES_DIR = BuiltinMacros.declareStringMacro("_WRAPPER_RESOURCES_DIR") public static let __INPUT_FILE_LIST_PATH__ = BuiltinMacros.declarePathMacro("__INPUT_FILE_LIST_PATH__") - public static let LINKER_FILE_LIST_FORMAT = BuiltinMacros.declareEnumMacro("LINKER_FILE_LIST_FORMAT") as EnumMacroDeclaration + public static let LINKER_FILE_LIST_FORMAT = BuiltinMacros.declareEnumMacro("LINKER_FILE_LIST_FORMAT") as EnumMacroDeclaration + public static let LINKER_RESPONSE_FILE_FORMAT = BuiltinMacros.declareEnumMacro("LINKER_RESPONSE_FILE_FORMAT") as EnumMacroDeclaration public static let SWIFT_RESPONSE_FILE_PATH = BuiltinMacros.declarePathMacro("SWIFT_RESPONSE_FILE_PATH") public static let __ARCHS__ = BuiltinMacros.declareStringListMacro("__ARCHS__") @@ -2441,6 +2442,7 @@ public final class BuiltinMacros { _WRAPPER_RESOURCES_DIR, __INPUT_FILE_LIST_PATH__, LINKER_FILE_LIST_FORMAT, + LINKER_RESPONSE_FILE_FORMAT, __ARCHS__, __SWIFT_MODULE_ONLY_ARCHS__, arch, @@ -2898,12 +2900,8 @@ public enum StripStyle: String, Equatable, Hashable, EnumerationMacroType { case debugging } -public enum LinkerFileListFormat: String, Equatable, Hashable, EnumerationMacroType { - public static let defaultValue = Self.unescapedNewlineSeparated - - case unescapedNewlineSeparated - case unixShellQuotedNewlineSeparated - case windowsShellQuotedNewlineSeparated +extension ResponseFileFormat: EnumerationMacroType { + public static let defaultValue = ResponseFileFormat.unixShellQuotedNewlineSeparated } public enum MergedBinaryType: String, Equatable, Hashable, EnumerationMacroType { diff --git a/Sources/SWBCore/SpecImplementations/LinkerSpec.swift b/Sources/SWBCore/SpecImplementations/LinkerSpec.swift index 6160a31e..5d728619 100644 --- a/Sources/SWBCore/SpecImplementations/LinkerSpec.swift +++ b/Sources/SWBCore/SpecImplementations/LinkerSpec.swift @@ -139,20 +139,7 @@ open class LinkerSpec : CommandLineToolSpec, @unchecked Sendable { } public func inputFileListContents(_ cbc: CommandBuildContext) -> ByteString { - let contents = OutputByteStream() - for input in cbc.inputs { - switch cbc.scope.evaluate(BuiltinMacros.LINKER_FILE_LIST_FORMAT) { - case .unescapedNewlineSeparated: - contents <<< input.absolutePath.strWithPosixSlashes <<< "\n" - case .unixShellQuotedNewlineSeparated: - let escaper = UNIXShellCommandCodec(encodingStrategy: .singleQuotes, encodingBehavior: .argumentsOnly) - contents <<< escaper.encode([input.absolutePath.strWithPosixSlashes]) <<< "\n" - case .windowsShellQuotedNewlineSeparated: - let escaper = WindowsProcessArgumentsCodec() - contents <<< escaper.encode([input.absolutePath.strWithPosixSlashes]) <<< "\n" - } - } - return contents.bytes + return ByteString(encodingAsUTF8: ResponseFiles.responseFileContents(args: cbc.inputs.map { $0.absolutePath.strWithPosixSlashes }, format: cbc.scope.evaluate(BuiltinMacros.LINKER_FILE_LIST_FORMAT))) } open override func constructTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async { diff --git a/Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift b/Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift index bfbff393..b8fa1092 100644 --- a/Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift +++ b/Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift @@ -117,7 +117,9 @@ private final class EnumBuildOptionType : BuildOptionType { case "SWIFT_API_DIGESTER_MODE": return try namespace.declareEnumMacro(name) as EnumMacroDeclaration case "LINKER_FILE_LIST_FORMAT": - return try namespace.declareEnumMacro(name) as EnumMacroDeclaration + return try namespace.declareEnumMacro(name) as EnumMacroDeclaration + case "LINKER_RESPONSE_FILE_FORMAT": + return try namespace.declareEnumMacro(name) as EnumMacroDeclaration case "DOCC_MINIMUM_ACCESS_LEVEL": return try namespace.declareEnumMacro(name) as EnumMacroDeclaration default: diff --git a/Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift b/Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift index f5856959..537358d5 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift @@ -235,7 +235,7 @@ public struct ClangSourceFileIndexingInfo: SourceFileIndexingInfo { // Skip } else if arg.starts(with: ByteString(unicodeScalarLiteral: "@")), let attachmentPath = responseFileMapping[Path(arg.asString.dropFirst())], - let responseFileArgs = try? ResponseFiles.expandResponseFiles(["@\(attachmentPath.str)"], fileSystem: localFS, relativeTo: workingDir) { + let responseFileArgs = try? ResponseFiles.expandResponseFiles(["@\(attachmentPath.str)"], fileSystem: localFS, relativeTo: workingDir, format: .unixShellQuotedSpaceSeparated) { result.append(contentsOf: responseFileArgs.map { ByteString(encodingAsUTF8: $0) }) } else { result.append(arg) @@ -719,7 +719,7 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible ctx.add(string: self.identifier) let responseFilePath = scope.evaluate(BuiltinMacros.PER_ARCH_OBJECT_FILE_DIR).join("\(ctx.signature.asString)-common-args.resp") - let attachmentPath = producer.writeFileSpec.constructFileTasks(CommandBuildContext(producer: producer, scope: scope, inputs: [], output: responseFilePath), delegate, contents: ByteString(encodingAsUTF8: ResponseFiles.responseFileContents(args: responseFileCommandLine)), permissions: nil, logContents: true, preparesForIndexing: true, additionalTaskOrderingOptions: [.immediate, .ignorePhaseOrdering]) + let attachmentPath = producer.writeFileSpec.constructFileTasks(CommandBuildContext(producer: producer, scope: scope, inputs: [], output: responseFilePath), delegate, contents: ByteString(encodingAsUTF8: ResponseFiles.responseFileContents(args: responseFileCommandLine, format: .unixShellQuotedSpaceSeparated)), permissions: nil, logContents: true, preparesForIndexing: true, additionalTaskOrderingOptions: [.immediate, .ignorePhaseOrdering]) return ConstantFlags(flags: regularCommandLine + ["@\(responseFilePath.str)"], headerSearchPaths: headerSearchPaths, inputs: [responseFilePath], responseFileMapping: [responseFilePath: attachmentPath]) } else { diff --git a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift index 002478c9..bd2acc90 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift @@ -1428,7 +1428,8 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec override public func createTaskAction(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) -> (any PlannedTaskAction)? { let useResponseFile = cbc.scope.evaluate(BuiltinMacros.CLANG_USE_RESPONSE_FILE) - return delegate.taskActionCreationDelegate.createLinkerTaskAction(expandResponseFiles: !useResponseFile) + let responseFileFormat = cbc.scope.evaluate(BuiltinMacros.LINKER_RESPONSE_FILE_FORMAT) + return delegate.taskActionCreationDelegate.createLinkerTaskAction(expandResponseFiles: !useResponseFile, responseFileFormat: responseFileFormat) } override public func discoveredCommandLineToolSpecInfo(_ producer: any CommandProducer, _ scope: MacroEvaluationScope, _ delegate: any CoreClientTargetDiagnosticProducingDelegate) async -> (any DiscoveredCommandLineToolSpecInfo)? { @@ -1645,7 +1646,8 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @u override public func createTaskAction(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) -> (any PlannedTaskAction)? { let useResponseFile = cbc.scope.evaluate(BuiltinMacros.LIBTOOL_USE_RESPONSE_FILE) - return delegate.taskActionCreationDelegate.createLinkerTaskAction(expandResponseFiles: !useResponseFile) + let responseFileFormat = cbc.scope.evaluate(BuiltinMacros.LINKER_RESPONSE_FILE_FORMAT) + return delegate.taskActionCreationDelegate.createLinkerTaskAction(expandResponseFiles: !useResponseFile, responseFileFormat: responseFileFormat) } override public func constructLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set]) async { diff --git a/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift b/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift index 14d1030c..07215597 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift @@ -373,8 +373,9 @@ public struct SwiftDriverPayload: Serializable, TaskPayload, Encodable { public let linkerResponseFilePath: Path? public let dependencyFilteringRootPath: Path? public let verifyScannerDependencies: Bool + public let linkerResponseFileFormat: ResponseFileFormat - internal init(uniqueID: String, compilerLocation: LibSwiftDriver.CompilerLocation, moduleName: String, outputPrefix: String, tempDirPath: Path, explicitModulesTempDirPath: Path, variant: String, architecture: String, eagerCompilationEnabled: Bool, explicitModulesEnabled: Bool, commandLine: [String], ruleInfo: [String], isUsingWholeModuleOptimization: Bool, casOptions: CASOptions?, reportRequiredTargetDependencies: BooleanWarningLevel, linkerResponseFilePath: Path?, dependencyFilteringRootPath: Path?, verifyScannerDependencies: Bool) { + internal init(uniqueID: String, compilerLocation: LibSwiftDriver.CompilerLocation, moduleName: String, outputPrefix: String, tempDirPath: Path, explicitModulesTempDirPath: Path, variant: String, architecture: String, eagerCompilationEnabled: Bool, explicitModulesEnabled: Bool, commandLine: [String], ruleInfo: [String], isUsingWholeModuleOptimization: Bool, casOptions: CASOptions?, reportRequiredTargetDependencies: BooleanWarningLevel, linkerResponseFilePath: Path?, linkerResponseFileFormat: ResponseFileFormat, dependencyFilteringRootPath: Path?, verifyScannerDependencies: Bool) { self.uniqueID = uniqueID self.compilerLocation = compilerLocation self.moduleName = moduleName @@ -391,12 +392,13 @@ public struct SwiftDriverPayload: Serializable, TaskPayload, Encodable { self.casOptions = casOptions self.reportRequiredTargetDependencies = reportRequiredTargetDependencies self.linkerResponseFilePath = linkerResponseFilePath + self.linkerResponseFileFormat = linkerResponseFileFormat self.dependencyFilteringRootPath = dependencyFilteringRootPath self.verifyScannerDependencies = verifyScannerDependencies } public init(from deserializer: any Deserializer) throws { - try deserializer.beginAggregate(18) + try deserializer.beginAggregate(19) self.uniqueID = try deserializer.deserialize() self.compilerLocation = try deserializer.deserialize() self.moduleName = try deserializer.deserialize() @@ -413,12 +415,13 @@ public struct SwiftDriverPayload: Serializable, TaskPayload, Encodable { self.casOptions = try deserializer.deserialize() self.reportRequiredTargetDependencies = try deserializer.deserialize() self.linkerResponseFilePath = try deserializer.deserialize() + self.linkerResponseFileFormat = try deserializer.deserialize() self.dependencyFilteringRootPath = try deserializer.deserialize() self.verifyScannerDependencies = try deserializer.deserialize() } public func serialize(to serializer: T) where T : Serializer { - serializer.serializeAggregate(18) { + serializer.serializeAggregate(19) { serializer.serialize(self.uniqueID) serializer.serialize(self.compilerLocation) serializer.serialize(self.moduleName) @@ -435,6 +438,7 @@ public struct SwiftDriverPayload: Serializable, TaskPayload, Encodable { serializer.serialize(self.casOptions) serializer.serialize(self.reportRequiredTargetDependencies) serializer.serialize(self.linkerResponseFilePath) + serializer.serialize(self.linkerResponseFileFormat) serializer.serialize(self.dependencyFilteringRootPath) serializer.serialize(self.verifyScannerDependencies) } @@ -2560,7 +2564,7 @@ public final class SwiftCompilerSpec : CompilerSpec, SpecIdentifierType, SwiftDi let explicitModuleBuildEnabled = await swiftExplicitModuleBuildEnabled(cbc.producer, cbc.scope, delegate) let verifyScannerDependencies = explicitModuleBuildEnabled && cbc.scope.evaluate(BuiltinMacros.SWIFT_DEPENDENCY_REGISTRATION_MODE) == .verifySwiftDependencyScanner - return SwiftDriverPayload(uniqueID: uniqueID, compilerLocation: compilerLocation, moduleName: scope.evaluate(BuiltinMacros.SWIFT_MODULE_NAME), outputPrefix: scope.evaluate(BuiltinMacros.TARGET_NAME) + compilationMode.moduleBaseNameSuffix, tempDirPath: tempDirPath, explicitModulesTempDirPath: explicitModulesTempDirPath, variant: variant, architecture: arch, eagerCompilationEnabled: eagerCompilationEnabled(args: args, scope: scope, compilationMode: compilationMode, isUsingWholeModuleOptimization: isUsingWholeModuleOptimization), explicitModulesEnabled: explicitModuleBuildEnabled, commandLine: commandLine, ruleInfo: ruleInfo, isUsingWholeModuleOptimization: isUsingWholeModuleOptimization, casOptions: casOptions, reportRequiredTargetDependencies: scope.evaluate(BuiltinMacros.DIAGNOSE_MISSING_TARGET_DEPENDENCIES), linkerResponseFilePath: linkerResponseFilePath, dependencyFilteringRootPath: cbc.producer.sdk?.path, verifyScannerDependencies: verifyScannerDependencies) + return SwiftDriverPayload(uniqueID: uniqueID, compilerLocation: compilerLocation, moduleName: scope.evaluate(BuiltinMacros.SWIFT_MODULE_NAME), outputPrefix: scope.evaluate(BuiltinMacros.TARGET_NAME) + compilationMode.moduleBaseNameSuffix, tempDirPath: tempDirPath, explicitModulesTempDirPath: explicitModulesTempDirPath, variant: variant, architecture: arch, eagerCompilationEnabled: eagerCompilationEnabled(args: args, scope: scope, compilationMode: compilationMode, isUsingWholeModuleOptimization: isUsingWholeModuleOptimization), explicitModulesEnabled: explicitModuleBuildEnabled, commandLine: commandLine, ruleInfo: ruleInfo, isUsingWholeModuleOptimization: isUsingWholeModuleOptimization, casOptions: casOptions, reportRequiredTargetDependencies: scope.evaluate(BuiltinMacros.DIAGNOSE_MISSING_TARGET_DEPENDENCIES), linkerResponseFilePath: linkerResponseFilePath, linkerResponseFileFormat: cbc.scope.evaluate(BuiltinMacros.LINKER_RESPONSE_FILE_FORMAT), dependencyFilteringRootPath: cbc.producer.sdk?.path, verifyScannerDependencies: verifyScannerDependencies) } func constructSwiftResponseFileTask(path: Path) { diff --git a/Sources/SWBGenericUnixPlatform/Specs/UnixLd.xcspec b/Sources/SWBGenericUnixPlatform/Specs/UnixLd.xcspec index 5efcd8f5..b408bd89 100644 --- a/Sources/SWBGenericUnixPlatform/Specs/UnixLd.xcspec +++ b/Sources/SWBGenericUnixPlatform/Specs/UnixLd.xcspec @@ -186,10 +186,22 @@ Type = Enumeration; Values = ( unescapedNewlineSeparated, + unixShellQuotedSpaceSeparated, unixShellQuotedNewlineSeparated, windowsShellQuotedNewlineSeparated, ); DefaultValue = unixShellQuotedNewlineSeparated; + }, + { + Name = "LINKER_RESPONSE_FILE_FORMAT"; + Type = Enumeration; + Values = ( + unescapedNewlineSeparated, + unixShellQuotedSpaceSeparated, + unixShellQuotedNewlineSeparated, + windowsShellQuotedNewlineSeparated, + ); + DefaultValue = unixShellQuotedSpaceSeparated; } ); }, diff --git a/Sources/SWBGenericUnixPlatform/Specs/UnixLibtool.xcspec b/Sources/SWBGenericUnixPlatform/Specs/UnixLibtool.xcspec index b46d97f0..7d08dcb2 100644 --- a/Sources/SWBGenericUnixPlatform/Specs/UnixLibtool.xcspec +++ b/Sources/SWBGenericUnixPlatform/Specs/UnixLibtool.xcspec @@ -77,6 +77,17 @@ windowsShellQuotedNewlineSeparated, ); DefaultValue = unixShellQuotedNewlineSeparated; + }, + { + Name = "LINKER_RESPONSE_FILE_FORMAT"; + Type = Enumeration; + Values = ( + unescapedNewlineSeparated, + unixShellQuotedSpaceSeparated, + unixShellQuotedNewlineSeparated, + windowsShellQuotedNewlineSeparated, + ); + DefaultValue = unixShellQuotedSpaceSeparated; } ); }, diff --git a/Sources/SWBTaskExecution/BuildDescriptionManager.swift b/Sources/SWBTaskExecution/BuildDescriptionManager.swift index ecdfbbbd..6bd51a61 100644 --- a/Sources/SWBTaskExecution/BuildDescriptionManager.swift +++ b/Sources/SWBTaskExecution/BuildDescriptionManager.swift @@ -904,8 +904,8 @@ extension BuildSystemTaskPlanningDelegate: TaskActionCreationDelegate { return ObjectLibraryAssemblerTaskAction() } - func createLinkerTaskAction(expandResponseFiles: Bool) -> any PlannedTaskAction { - return LinkerTaskAction(expandResponseFiles: expandResponseFiles) + func createLinkerTaskAction(expandResponseFiles: Bool, responseFileFormat: ResponseFileFormat) -> any PlannedTaskAction { + return LinkerTaskAction(expandResponseFiles: expandResponseFiles, responseFileFormat: responseFileFormat) } } diff --git a/Sources/SWBTaskExecution/CMakeLists.txt b/Sources/SWBTaskExecution/CMakeLists.txt index 6ae2d684..b8c7f116 100644 --- a/Sources/SWBTaskExecution/CMakeLists.txt +++ b/Sources/SWBTaskExecution/CMakeLists.txt @@ -28,6 +28,7 @@ add_library(SWBTaskExecution DynamicTaskSpecs/SwiftDriverJobDynamicTaskSpec.swift DynamicTaskSpecs/SwiftDriverPlanningDynamicTaskSpec.swift ProjectPlanner.swift + ResponseFileFormat+ExpressibleByArgument.swift Task.swift TaskActionExtensionPoint.swift TaskActions/AuxiliaryFileTaskAction.swift diff --git a/Sources/SWBTaskExecution/ResponseFileFormat+ExpressibleByArgument.swift b/Sources/SWBTaskExecution/ResponseFileFormat+ExpressibleByArgument.swift new file mode 100644 index 00000000..4b9abae0 --- /dev/null +++ b/Sources/SWBTaskExecution/ResponseFileFormat+ExpressibleByArgument.swift @@ -0,0 +1,16 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +public import ArgumentParser +import SWBUtil + +extension ResponseFileFormat: ExpressibleByArgument {} diff --git a/Sources/SWBTaskExecution/TaskActions/ClangScanTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/ClangScanTaskAction.swift index d9d7e053..0712b6ff 100644 --- a/Sources/SWBTaskExecution/TaskActions/ClangScanTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/ClangScanTaskAction.swift @@ -74,7 +74,7 @@ public final class ClangScanTaskAction: TaskAction, BuildValueValidatingTaskActi self.scanningOutput = parsedOutput if expandResponseFiles { do { - self.commandLine = try ResponseFiles.expandResponseFiles(cliArguments, fileSystem: executionDelegate.fs, relativeTo: workingDirectory) + self.commandLine = try ResponseFiles.expandResponseFiles(cliArguments, fileSystem: executionDelegate.fs, relativeTo: workingDirectory, format: .unixShellQuotedSpaceSeparated) } catch { outputDelegate.error(error.localizedDescription) return nil diff --git a/Sources/SWBTaskExecution/TaskActions/LinkerTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/LinkerTaskAction.swift index fedbdede..be739b13 100644 --- a/Sources/SWBTaskExecution/TaskActions/LinkerTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/LinkerTaskAction.swift @@ -22,22 +22,26 @@ public final class LinkerTaskAction: TaskAction { /// Whether response files should be expanded before invoking the linker. private let expandResponseFiles: Bool - - public init(expandResponseFiles: Bool) { + private let responseFileFormat: ResponseFileFormat + + public init(expandResponseFiles: Bool, responseFileFormat: ResponseFileFormat) { self.expandResponseFiles = expandResponseFiles + self.responseFileFormat = responseFileFormat super.init() } public override func serialize(to serializer: T) { - serializer.beginAggregate(2) + serializer.beginAggregate(3) serializer.serialize(expandResponseFiles) + serializer.serialize(responseFileFormat) super.serialize(to: serializer) serializer.endAggregate() } public required init(from deserializer: any Deserializer) throws { - try deserializer.beginAggregate(2) + try deserializer.beginAggregate(3) self.expandResponseFiles = try deserializer.deserialize() + self.responseFileFormat = try deserializer.deserialize() try super.init(from: deserializer) } @@ -55,7 +59,8 @@ public final class LinkerTaskAction: TaskAction { commandLine = try ResponseFiles.expandResponseFiles( commandLine, fileSystem: executionDelegate.fs, - relativeTo: task.workingDirectory + relativeTo: task.workingDirectory, + format: responseFileFormat ) } catch { outputDelegate.emitError("Failed to expand response files: \(error.localizedDescription)") diff --git a/Sources/SWBTaskExecution/TaskActions/ObjectLibraryAssemblerTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/ObjectLibraryAssemblerTaskAction.swift index 82176362..31b5487f 100644 --- a/Sources/SWBTaskExecution/TaskActions/ObjectLibraryAssemblerTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/ObjectLibraryAssemblerTaskAction.swift @@ -22,6 +22,7 @@ public final class ObjectLibraryAssemblerTaskAction: TaskAction { private struct Options: ParsableArguments { @Argument var inputs: [Path] @Option var output: Path + @Option var linkerResponseFileFormat: ResponseFileFormat } override public func performTaskAction( @@ -39,7 +40,7 @@ public final class ObjectLibraryAssemblerTaskAction: TaskAction { try executionDelegate.fs.copy(input, to: options.output.join(input.basename)) } let args = options.inputs.map { $0.strWithPosixSlashes } - try executionDelegate.fs.write(options.output.join("args.resp"), contents: ByteString(encodingAsUTF8: ResponseFiles.responseFileContents(args: args))) + try executionDelegate.fs.write(options.output.join("args.resp"), contents: ByteString(encodingAsUTF8: ResponseFiles.responseFileContents(args: args, format: options.linkerResponseFileFormat))) return .succeeded } catch { outputDelegate.emitError("\(error)") diff --git a/Sources/SWBTaskExecution/TaskActions/SwiftDriverTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/SwiftDriverTaskAction.swift index 85f7a182..8b4e72ed 100644 --- a/Sources/SWBTaskExecution/TaskActions/SwiftDriverTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/SwiftDriverTaskAction.swift @@ -141,7 +141,7 @@ final public class SwiftDriverTaskAction: TaskAction, BuildValueValidatingTaskAc responseFileCommandLine.append(contentsOf: ["-Xlinker", "-add_ast_path", "-Xlinker", "\(swiftmodulePath)"]) } } - let contents = ByteString(encodingAsUTF8: ResponseFiles.responseFileContents(args: responseFileCommandLine)) + let contents = ByteString(encodingAsUTF8: ResponseFiles.responseFileContents(args: responseFileCommandLine, format: driverPayload.linkerResponseFileFormat)) try executionDelegate.fs.write(linkerResponseFilePath, contents: contents, atomically: true) } diff --git a/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift b/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift index 91f6937a..2779938d 100644 --- a/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift +++ b/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift @@ -252,7 +252,7 @@ extension CapturingTaskGenerationDelegate: TaskActionCreationDelegate { return ObjectLibraryAssemblerTaskAction() } - package func createLinkerTaskAction(expandResponseFiles: Bool) -> any PlannedTaskAction { - return LinkerTaskAction(expandResponseFiles: expandResponseFiles) + package func createLinkerTaskAction(expandResponseFiles: Bool, responseFileFormat: ResponseFileFormat) -> any PlannedTaskAction { + return LinkerTaskAction(expandResponseFiles: expandResponseFiles, responseFileFormat: responseFileFormat) } } diff --git a/Sources/SWBTestSupport/TaskPlanningTestSupport.swift b/Sources/SWBTestSupport/TaskPlanningTestSupport.swift index 9ba7c3d9..2b0f251a 100644 --- a/Sources/SWBTestSupport/TaskPlanningTestSupport.swift +++ b/Sources/SWBTestSupport/TaskPlanningTestSupport.swift @@ -480,8 +480,8 @@ extension TestTaskPlanningDelegate: TaskActionCreationDelegate { return ObjectLibraryAssemblerTaskAction() } - package func createLinkerTaskAction(expandResponseFiles: Bool) -> any PlannedTaskAction { - return LinkerTaskAction(expandResponseFiles: expandResponseFiles) + package func createLinkerTaskAction(expandResponseFiles: Bool, responseFileFormat: ResponseFileFormat) -> any PlannedTaskAction { + return LinkerTaskAction(expandResponseFiles: expandResponseFiles, responseFileFormat: responseFileFormat) } } diff --git a/Sources/SWBUniversalPlatform/Specs/Ld.xcspec b/Sources/SWBUniversalPlatform/Specs/Ld.xcspec index 2f07a375..47ffa803 100644 --- a/Sources/SWBUniversalPlatform/Specs/Ld.xcspec +++ b/Sources/SWBUniversalPlatform/Specs/Ld.xcspec @@ -807,6 +807,28 @@ ); }; }, + { + Name = "LINKER_FILE_LIST_FORMAT"; + Type = Enumeration; + Values = ( + unescapedNewlineSeparated, + unixShellQuotedSpaceSeparated, + unixShellQuotedNewlineSeparated, + windowsShellQuotedNewlineSeparated, + ); + DefaultValue = unescapedNewlineSeparated; + }, + { + Name = "LINKER_RESPONSE_FILE_FORMAT"; + Type = Enumeration; + Values = ( + unescapedNewlineSeparated, + unixShellQuotedSpaceSeparated, + unixShellQuotedNewlineSeparated, + windowsShellQuotedNewlineSeparated, + ); + DefaultValue = unixShellQuotedSpaceSeparated; + } ); } ) diff --git a/Sources/SWBUniversalPlatform/Specs/ObjectLibraryAssembler.xcspec b/Sources/SWBUniversalPlatform/Specs/ObjectLibraryAssembler.xcspec index 0a3d4758..651ca29f 100644 --- a/Sources/SWBUniversalPlatform/Specs/ObjectLibraryAssembler.xcspec +++ b/Sources/SWBUniversalPlatform/Specs/ObjectLibraryAssembler.xcspec @@ -32,7 +32,11 @@ ); CommandOutputParser = "XCGenericCommandOutputParser"; Options = ( - + { Name = OBJECT_FILE_ASSEMBLER_LINKER_RESPONSE_FILE_FORMAT; + Type = String; + DefaultValue = "$(LINKER_RESPONSE_FILE_FORMAT)"; + CommandLineFlag = "--linker-response-file-format"; + }, ); } ) diff --git a/Sources/SWBUniversalPlatform/TestEntryPointGenerationTaskAction.swift b/Sources/SWBUniversalPlatform/TestEntryPointGenerationTaskAction.swift index 17075967..d5f8ba5f 100644 --- a/Sources/SWBUniversalPlatform/TestEntryPointGenerationTaskAction.swift +++ b/Sources/SWBUniversalPlatform/TestEntryPointGenerationTaskAction.swift @@ -28,8 +28,7 @@ class TestEntryPointGenerationTaskAction: TaskAction { if options.discoverTests { var objects: [Path] = [] for linkerFilelist in options.linkerFilelist { - let filelistContents = String(String(decoding: try executionDelegate.fs.read(linkerFilelist), as: UTF8.self)) - let entries = filelistContents.split(separator: "\n", omittingEmptySubsequences: true).map { Path($0) }.map { + let entries = try ResponseFiles.expandResponseFiles(["@\(linkerFilelist.str)"], fileSystem: executionDelegate.fs, relativeTo: linkerFilelist.dirname, format: options.linkerFileListFormat).map { Path($0) }.map { for indexUnitBasePath in options.indexUnitBasePath { if let remappedPath = generateIndexOutputPath(from: $0, basePath: indexUnitBasePath) { return remappedPath @@ -129,6 +128,7 @@ class TestEntryPointGenerationTaskAction: TaskAction { @Option() var linkerFilelist: [Path] = [] @Option var indexStore: [Path] = [] @Option var indexUnitBasePath: [Path] = [] + @Option var linkerFileListFormat: ResponseFileFormat = ResponseFileFormat.defaultValue @Flag var enableExperimentalTestOutput: Bool = false @Flag var discoverTests: Bool = false } diff --git a/Sources/SWBUniversalPlatform/TestEntryPointGenerationTool.swift b/Sources/SWBUniversalPlatform/TestEntryPointGenerationTool.swift index ec9dbfb7..dbe42335 100644 --- a/Sources/SWBUniversalPlatform/TestEntryPointGenerationTool.swift +++ b/Sources/SWBUniversalPlatform/TestEntryPointGenerationTool.swift @@ -21,6 +21,10 @@ final class TestEntryPointGenerationToolSpec: GenericCommandLineToolSpec, SpecId var args = await super.commandLineFromTemplate(cbc, delegate, optionContext: optionContext, specialArgs: specialArgs, lookup: lookup) if cbc.scope.evaluate(BuiltinMacros.GENERATED_TEST_ENTRY_POINT_INCLUDE_DISCOVERED_TESTS) { args.append("--discover-tests") + + let format = cbc.scope.evaluate(BuiltinMacros.LINKER_FILE_LIST_FORMAT) + args.append(contentsOf: ["--linker-file-list-format", .literal(.init(encodingAsUTF8: format.rawValue))]) + for toolchainLibrarySearchPath in cbc.producer.toolchains.map({ $0.librarySearchPaths }) { if let path = toolchainLibrarySearchPath.findLibrary(operatingSystem: cbc.producer.hostOperatingSystem, basename: "IndexStore") { args.append(contentsOf: ["--index-store-library-path", .path(path)]) diff --git a/Sources/SWBUtil/ResponseFiles.swift b/Sources/SWBUtil/ResponseFiles.swift index 1115fd81..9b4bc741 100644 --- a/Sources/SWBUtil/ResponseFiles.swift +++ b/Sources/SWBUtil/ResponseFiles.swift @@ -10,18 +10,36 @@ // //===----------------------------------------------------------------------===// +public enum ResponseFileFormat: String, Equatable, Hashable, CaseIterable, Sendable, Codable, Serializable { + case unescapedNewlineSeparated + case unixShellQuotedNewlineSeparated + case unixShellQuotedSpaceSeparated + case windowsShellQuotedNewlineSeparated +} + public enum ResponseFiles: Sendable { - public static func responseFileContents(args: [String]) -> String { - UNIXShellCommandCodec(encodingStrategy: .singleQuotes, encodingBehavior: .argumentsOnly).encode(args) + public static func responseFileContents(args: [String], format: ResponseFileFormat) -> String { + switch format { + case .unescapedNewlineSeparated: + return args.map { $0 + "\n" }.joined() + case .unixShellQuotedNewlineSeparated: + let escaper = UNIXShellCommandCodec(encodingStrategy: .singleQuotes, encodingBehavior: .argumentsOnly) + return args.map { escaper.encode([$0]) + "\n" }.joined() + case .unixShellQuotedSpaceSeparated: + return UNIXShellCommandCodec(encodingStrategy: .singleQuotes, encodingBehavior: .argumentsOnly).encode(args) + case .windowsShellQuotedNewlineSeparated: + let escaper = WindowsProcessArgumentsCodec() + return args.map { escaper.encode([$0]) + "\r\n" }.joined() + } } // Adapted from SwiftDriver's response file support. - public static func expandResponseFiles(_ args: [String], fileSystem: any FSProxy, relativeTo basePath: Path) throws -> [String] { + public static func expandResponseFiles(_ args: [String], fileSystem: any FSProxy, relativeTo basePath: Path, format: ResponseFileFormat) throws -> [String] { var visited: Set = [] - return try expandResponseFiles(args, fileSystem: fileSystem, relativeTo: basePath, visitedResponseFiles: &visited) + return try expandResponseFiles(args, fileSystem: fileSystem, relativeTo: basePath, format: format, visitedResponseFiles: &visited) } - private static func expandResponseFiles(_ args: [String], fileSystem: any FSProxy, relativeTo basePath: Path, visitedResponseFiles: inout Set) throws -> [String] { + private static func expandResponseFiles(_ args: [String], fileSystem: any FSProxy, relativeTo basePath: Path, format: ResponseFileFormat, visitedResponseFiles: inout Set) throws -> [String] { var result: [String] = [] for arg in args { if arg.first == "@" { @@ -35,8 +53,8 @@ public enum ResponseFiles: Sendable { } let contents = try fileSystem.read(responseFile).asString - let lines = tokenizeResponseFile(contents) - result.append(contentsOf: try expandResponseFiles(lines, fileSystem: fileSystem, relativeTo: basePath, visitedResponseFiles: &visitedResponseFiles)) + let tokens = tokenizeResponseFile(contents, format: format) + result.append(contentsOf: try expandResponseFiles(tokens, fileSystem: fileSystem, relativeTo: basePath, format: format, visitedResponseFiles: &visitedResponseFiles)) } else { result.append(arg) } @@ -45,17 +63,25 @@ public enum ResponseFiles: Sendable { return result } - private static func tokenizeResponseFile(_ content: String) -> [String] { - return content.split { $0 == "\n" || $0 == "\r\n" } - .flatMap { tokenizeResponseFileLine($0) } + private static func tokenizeResponseFile(_ content: String, format: ResponseFileFormat) -> [String] { + switch format { + case .unescapedNewlineSeparated: + return content.split { $0 == "\n" || $0 == "\r\n" }.map { String($0) } + case .unixShellQuotedNewlineSeparated, .unixShellQuotedSpaceSeparated: + return content.split { $0 == "\n" || $0 == "\r\n" } + .flatMap { tokenizeUnixShellQuotedResponseFileLine($0) } + case .windowsShellQuotedNewlineSeparated: + return content.split { $0 == "\n" || $0 == "\r\n" } + .map { tokenizeWindowsShellQuotedResponseFileArg($0) } + } } - private enum TokenState { + private enum UnixTokenState { case normal, escaping, quoted } /// Tokenizes a response file line generated by `UNIXShellCommandCodec` using the `.singleQuotes` strategy. - private static func tokenizeResponseFileLine(_ line: S) -> [String] { + private static func tokenizeUnixShellQuotedResponseFileLine(_ line: S) -> [String] { // Support double dash comments only if they start at the beginning of a line. if line.hasPrefix("//") { return [] } @@ -64,7 +90,7 @@ public enum ResponseFiles: Sendable { // Conservatively assume ~1 token per line. token.reserveCapacity(line.count) - var state: TokenState = .normal + var state: UnixTokenState = .normal for char in line { if char == #"\"# && state == .normal { @@ -109,4 +135,30 @@ public enum ResponseFiles: Sendable { return tokens } + + private enum WindowsTokenState { + case normal, escaping + } + + private static func tokenizeWindowsShellQuotedResponseFileArg(_ arg: S) -> String { + guard arg.first == "\"" && arg.last == "\"" else { + return String(arg) + } + var result = "" + var state = WindowsTokenState.normal + for char in arg.dropFirst().dropLast() { + switch state { + case .normal: + if char == "\\" { + state = .escaping + } else { + result.append(char) + } + case .escaping: + result.append(char) + state = .normal + } + } + return result + } } diff --git a/Sources/SWBWindowsPlatform/Specs/WindowsLd.xcspec b/Sources/SWBWindowsPlatform/Specs/WindowsLd.xcspec index 4c1fbcaf..b4ef6fbc 100644 --- a/Sources/SWBWindowsPlatform/Specs/WindowsLd.xcspec +++ b/Sources/SWBWindowsPlatform/Specs/WindowsLd.xcspec @@ -201,6 +201,18 @@ Type = Enumeration; Values = ( unescapedNewlineSeparated, + unixShellQuotedSpaceSeparated, + unixShellQuotedNewlineSeparated, + windowsShellQuotedNewlineSeparated, + ); + DefaultValue = windowsShellQuotedNewlineSeparated; + }, + { + Name = "LINKER_RESPONSE_FILE_FORMAT"; + Type = Enumeration; + Values = ( + unescapedNewlineSeparated, + unixShellQuotedSpaceSeparated, unixShellQuotedNewlineSeparated, windowsShellQuotedNewlineSeparated, ); diff --git a/Sources/SWBWindowsPlatform/Specs/WindowsLibtool.xcspec b/Sources/SWBWindowsPlatform/Specs/WindowsLibtool.xcspec index 7885caf7..c5521e0d 100644 --- a/Sources/SWBWindowsPlatform/Specs/WindowsLibtool.xcspec +++ b/Sources/SWBWindowsPlatform/Specs/WindowsLibtool.xcspec @@ -68,11 +68,23 @@ Type = Enumeration; Values = ( unescapedNewlineSeparated, + unixShellQuotedSpaceSeparated, unixShellQuotedNewlineSeparated, windowsShellQuotedNewlineSeparated, ); DefaultValue = windowsShellQuotedNewlineSeparated; }, + { + Name = "LINKER_RESPONSE_FILE_FORMAT"; + Type = Enumeration; + Values = ( + unescapedNewlineSeparated, + unixShellQuotedSpaceSeparated, + unixShellQuotedNewlineSeparated, + windowsShellQuotedNewlineSeparated, + ); + DefaultValue = windowsShellQuotedNewlineSeparated; + } ); }, ) diff --git a/Tests/SWBBuildSystemTests/BuildOperationTests.swift b/Tests/SWBBuildSystemTests/BuildOperationTests.swift index aa178223..150e503a 100644 --- a/Tests/SWBBuildSystemTests/BuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/BuildOperationTests.swift @@ -330,40 +330,16 @@ fileprivate struct BuildOperationTests: CoreBasedTests { try await tester.checkBuild(runDestination: destination, persistent: true, signableTargets: Set(provisioningInputs.keys), signableTargetInputs: provisioningInputs) { results in results.checkNoErrors() if core.hostOperatingSystem.imageFormat.requiresSwiftModulewrap { - try results.checkTask(.matchTargetName("tool"), .matchRulePattern(["WriteAuxiliaryFile", .suffix("LinkFileList")])) { task in - let auxFileAction = try #require(task.action as? AuxiliaryFileTaskAction) - let contents = try tester.fs.read(auxFileAction.context.input).asString - let files = contents.components(separatedBy: "\n").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty } - #expect(files.count == 2) - #expect(files[0].hasSuffix("tool.o")) - #expect(files[1].hasSuffix("main.o")) - } let toolWrap = try #require(results.getTask(.matchTargetName("tool"), .matchRuleType("SwiftModuleWrap"))) try results.checkTask(.matchTargetName("tool"), .matchRuleType("Ld")) { task in try results.checkTaskFollows(task, toolWrap) } - try results.checkTask(.matchTargetName("dynamiclib"), .matchRulePattern(["WriteAuxiliaryFile", .suffix("LinkFileList")])) { task in - let auxFileAction = try #require(task.action as? AuxiliaryFileTaskAction) - let contents = try tester.fs.read(auxFileAction.context.input).asString - let files = contents.components(separatedBy: "\n").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty } - #expect(files.count == 2) - #expect(files[0].hasSuffix("dynamiclib.o")) - #expect(files[1].hasSuffix("dynamic.o")) - } let dylibWrap = try #require(results.getTask(.matchTargetName("dynamiclib"), .matchRuleType("SwiftModuleWrap"))) try results.checkTask(.matchTargetName("dynamiclib"), .matchRuleType("Ld")) { task in try results.checkTaskFollows(task, dylibWrap) } - try results.checkTask(.matchTargetName("staticlib"), .matchRulePattern(["WriteAuxiliaryFile", .suffix("LinkFileList")])) { task in - let auxFileAction = try #require(task.action as? AuxiliaryFileTaskAction) - let contents = try tester.fs.read(auxFileAction.context.input).asString - let files = contents.components(separatedBy: "\n").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty } - #expect(files.count == 2) - #expect(files[0].hasSuffix("staticlib.o")) - #expect(files[1].hasSuffix("static.o")) - } let staticWrap = try #require(results.getTask(.matchTargetName("staticlib"), .matchRuleType("SwiftModuleWrap"))) try results.checkTask(.matchTargetName("staticlib"), .matchRuleType("Libtool")) { task in try results.checkTaskFollows(task, staticWrap) diff --git a/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift b/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift index a9e2004d..a1753962 100644 --- a/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift +++ b/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift @@ -246,8 +246,8 @@ extension CapturingTaskGenerationDelegate: TaskActionCreationDelegate { return ObjectLibraryAssemblerTaskAction() } - func createLinkerTaskAction(expandResponseFiles: Bool) -> any PlannedTaskAction { - return LinkerTaskAction(expandResponseFiles: expandResponseFiles) + func createLinkerTaskAction(expandResponseFiles: Bool, responseFileFormat: ResponseFileFormat) -> any PlannedTaskAction { + return LinkerTaskAction(expandResponseFiles: expandResponseFiles, responseFileFormat: responseFileFormat) } } diff --git a/Tests/SWBTaskConstructionTests/ObjectLibraryTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/ObjectLibraryTaskConstructionTests.swift index e6d0d958..4eff840a 100644 --- a/Tests/SWBTaskConstructionTests/ObjectLibraryTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/ObjectLibraryTaskConstructionTests.swift @@ -56,6 +56,8 @@ fileprivate struct ObjectLibraryTaskConstructionTests: CoreBasedTests { results.checkTask(.matchRuleType("AssembleObjectLibrary")) { task in task.checkCommandLineMatches([ "builtin-ObjectLibraryAssembler", + "--linker-response-file-format", + .any, .suffix("a.o"), .suffix("b.o"), "--output", diff --git a/Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift index cd27640e..20cf4808 100644 --- a/Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift @@ -366,7 +366,7 @@ fileprivate struct UnitTestTaskConstructionTests: CoreBasedTests { await tester.checkBuild(runDestination: .linux, fs: fs) { results in results.checkTarget("UnitTestRunner") { target in results.checkTask(.matchTarget(target), .matchRuleType("GenerateTestEntryPoint")) { task in - task.checkCommandLineMatches([.suffix("builtin-generateTestEntryPoint"), "--output", .suffix("test_entry_point.swift"), "--discover-tests", "--index-store-library-path", .suffix("libIndexStore.so"), "--linker-filelist", .suffix("UnitTestTarget.LinkFileList"), "--index-store", "/index", "--index-unit-base-path", "/tmp/Test/aProject/build"]) + task.checkCommandLineMatches([.suffix("builtin-generateTestEntryPoint"), "--output", .suffix("test_entry_point.swift"), "--discover-tests", "--linker-file-list-format", .any, "--index-store-library-path", .suffix("libIndexStore.so"), "--linker-filelist", .suffix("UnitTestTarget.LinkFileList"), "--index-store", "/index", "--index-unit-base-path", "/tmp/Test/aProject/build"]) task.checkInputs([ .pathPattern(.suffix("UnitTestTarget.LinkFileList")), .pathPattern(.suffix("UnitTestTarget.so")), diff --git a/Tests/SWBUtilTests/ResponseFileTests.swift b/Tests/SWBUtilTests/ResponseFileTests.swift index 64b14064..9dfe569b 100644 --- a/Tests/SWBUtilTests/ResponseFileTests.swift +++ b/Tests/SWBUtilTests/ResponseFileTests.swift @@ -16,18 +16,40 @@ import SWBTestSupport import SWBUtil @Suite fileprivate struct ResponseFileTests { + @Test(arguments: ResponseFileFormat.allCases) + func responseFileRoundTripping(format: ResponseFileFormat) throws { + try assertRoundTrip(args: ["foo", "bar"], format: format) + try assertRoundTrip(args: ["foo bar"], format: format) + try assertRoundTrip(args: ["foo bar", "baz"], format: format) + try assertRoundTrip(args: ["/this/is/a/path", "-bar"], format: format) + + try assertRoundTrip(args: [#"'"#], format: format) + try assertRoundTrip(args: [#"""#], format: format) + try assertRoundTrip(args: [#" a b '"#], format: format) + try assertRoundTrip(args: [#"' a b "#], format: format) + try assertRoundTrip(args: [#"start foo "bar baz's slash\" end"#], format: format) + } + @Test - func responseFileContent() throws { - try assertRoundTrip(args: ["foo", "bar"], "foo bar") - try assertRoundTrip(args: ["foo bar"], "'foo bar'") - try assertRoundTrip(args: ["foo bar", "baz"], "'foo bar' baz") - try assertRoundTrip(args: ["/this/is/a/path", "-bar"], "/this/is/a/path -bar") - - try assertRoundTrip(args: [#"'"#], #"\'"#) - try assertRoundTrip(args: [#"""#], #"'"'"#) - try assertRoundTrip(args: [#" a b '"#], #"' a b '\'"#) - try assertRoundTrip(args: [#"' a b "#], #"\'' a b '"#) - try assertRoundTrip(args: [#"start foo "bar baz's slash\" end"#], #"'start foo "bar baz'\''s slash\" end'"#) + func responseFileContentUnix() throws { + try assertContents(args: ["foo", "bar"], "foo bar", format: .unixShellQuotedSpaceSeparated) + try assertContents(args: ["foo bar"], "'foo bar'", format: .unixShellQuotedSpaceSeparated) + try assertContents(args: ["foo bar", "baz"], "'foo bar' baz", format: .unixShellQuotedSpaceSeparated) + try assertContents(args: ["/this/is/a/path", "-bar"], "/this/is/a/path -bar", format: .unixShellQuotedSpaceSeparated) + + try assertContents(args: [#"'"#], #"\'"#, format: .unixShellQuotedSpaceSeparated) + try assertContents(args: [#"""#], #"'"'"#, format: .unixShellQuotedSpaceSeparated) + try assertContents(args: [#" a b '"#], #"' a b '\'"#, format: .unixShellQuotedSpaceSeparated) + try assertContents(args: [#"' a b "#], #"\'' a b '"#, format: .unixShellQuotedSpaceSeparated) + try assertContents(args: [#"start foo "bar baz's slash\" end"#], #"'start foo "bar baz'\''s slash\" end'"#, format: .unixShellQuotedSpaceSeparated) + } + + @Test + func responseFileContentWindows() throws { + try assertContents(args: ["foo", "bar"], "foo\r\nbar\r\n", format: .windowsShellQuotedNewlineSeparated) + try assertContents(args: ["foo bar"], "\"foo bar\"\r\n", format: .windowsShellQuotedNewlineSeparated) + try assertContents(args: ["foo bar", "baz"], "\"foo bar\"\r\nbaz\r\n", format: .windowsShellQuotedNewlineSeparated) + try assertContents(args: ["/this/is/a/path", "-bar"], "/this/is/a/path\r\n-bar\r\n", format: .windowsShellQuotedNewlineSeparated) } @Test @@ -42,21 +64,25 @@ import SWBUtil // comment two -path '/path/with space' @b.resp """) - try #expect(ResponseFiles.expandResponseFiles(["-first", "@a.resp", "-last"], fileSystem: localFS, relativeTo: tmpDir) == ["-first", "-foo", "-bar", "-baz", "-last"]) - try #expect(ResponseFiles.expandResponseFiles(["-first", "@b.resp", "-last"], fileSystem: localFS, relativeTo: tmpDir) == ["-first", "-b", "-foo", "-bar", "-baz", "-last"]) + try #expect(ResponseFiles.expandResponseFiles(["-first", "@a.resp", "-last"], fileSystem: localFS, relativeTo: tmpDir, format: .unixShellQuotedSpaceSeparated) == ["-first", "-foo", "-bar", "-baz", "-last"]) + try #expect(ResponseFiles.expandResponseFiles(["-first", "@b.resp", "-last"], fileSystem: localFS, relativeTo: tmpDir, format: .unixShellQuotedSpaceSeparated) == ["-first", "-b", "-foo", "-bar", "-baz", "-last"]) #expect(throws: (any Error).self) { - try ResponseFiles.expandResponseFiles(["-first", "@c.resp", "-last"], fileSystem: localFS, relativeTo: tmpDir) + try ResponseFiles.expandResponseFiles(["-first", "@c.resp", "-last"], fileSystem: localFS, relativeTo: tmpDir, format: .unixShellQuotedSpaceSeparated) } - try #expect(ResponseFiles.expandResponseFiles(["-first", "@d.resp", "-last"], fileSystem: localFS, relativeTo: tmpDir) == ["-first", "-foo", "-bar", "-baz", "-path", "/path/with space", "-b", "-foo", "-bar", "-baz", "-last"]) + try #expect(ResponseFiles.expandResponseFiles(["-first", "@d.resp", "-last"], fileSystem: localFS, relativeTo: tmpDir, format: .unixShellQuotedSpaceSeparated) == ["-first", "-foo", "-bar", "-baz", "-path", "/path/with space", "-b", "-foo", "-bar", "-baz", "-last"]) } } - private func assertRoundTrip(args: [String], _ contents: String, sourceLocation: SourceLocation = #_sourceLocation) throws { - let actualContents = ResponseFiles.responseFileContents(args: args) - #expect(actualContents == contents, sourceLocation: sourceLocation) + private func assertRoundTrip(args: [String], format: ResponseFileFormat, sourceLocation: SourceLocation = #_sourceLocation) throws { + let actualContents = ResponseFiles.responseFileContents(args: args, format: format) let fs = PseudoFS() try fs.write(Path.root.join("a.resp"), contents: ByteString(encodingAsUTF8: actualContents)) - try #expect(ResponseFiles.expandResponseFiles(["@a.resp"], fileSystem: fs, relativeTo: .root) == args, sourceLocation: sourceLocation) + try #expect(ResponseFiles.expandResponseFiles(["@a.resp"], fileSystem: fs, relativeTo: .root, format: format) == args, sourceLocation: sourceLocation) + } + + private func assertContents(args: [String], _ contents: String, format: ResponseFileFormat, sourceLocation: SourceLocation = #_sourceLocation) throws { + let actualContents = ResponseFiles.responseFileContents(args: args, format: format) + #expect(actualContents == contents, sourceLocation: sourceLocation) } }