diff --git a/Sources/SWBCore/CMakeLists.txt b/Sources/SWBCore/CMakeLists.txt index 93ceda77..f3cabed7 100644 --- a/Sources/SWBCore/CMakeLists.txt +++ b/Sources/SWBCore/CMakeLists.txt @@ -139,6 +139,7 @@ add_library(SWBCore SpecImplementations/Tools/MergeInfoPlist.swift SpecImplementations/Tools/MkdirTool.swift SpecImplementations/Tools/ModulesVerifierTool.swift + SpecImplementations/Tools/ObjectLibraryAssembler.swift SpecImplementations/Tools/PLUtilTool.swift SpecImplementations/Tools/PrelinkedObjectLink.swift SpecImplementations/Tools/ProcessSDKImports.swift diff --git a/Sources/SWBCore/PlannedTaskAction.swift b/Sources/SWBCore/PlannedTaskAction.swift index c81f93ee..4844d09c 100644 --- a/Sources/SWBCore/PlannedTaskAction.swift +++ b/Sources/SWBCore/PlannedTaskAction.swift @@ -347,6 +347,8 @@ public protocol TaskActionCreationDelegate func createClangModuleVerifierInputGeneratorTaskAction() -> any PlannedTaskAction func createProcessSDKImportsTaskAction() -> any PlannedTaskAction func createValidateDependenciesTaskAction() -> any PlannedTaskAction + func createObjectLibraryAssemblerTaskAction() -> any PlannedTaskAction + func createLinkerTaskAction(expandResponseFiles: Bool) -> any PlannedTaskAction } extension TaskActionCreationDelegate { diff --git a/Sources/SWBCore/SpecImplementations/LinkerSpec.swift b/Sources/SWBCore/SpecImplementations/LinkerSpec.swift index df135901..c4a6782c 100644 --- a/Sources/SWBCore/SpecImplementations/LinkerSpec.swift +++ b/Sources/SWBCore/SpecImplementations/LinkerSpec.swift @@ -24,14 +24,16 @@ open class LinkerSpec : CommandLineToolSpec, @unchecked Sendable { case textBased case framework case object + case objectLibrary public var description: String { switch self { - case .static: return "static library" - case .dynamic: return "dynamic library" - case .textBased: return "text-based stub" - case .framework: return "framework" - case .object: return "object file" + case .static: return "static library" + case .dynamic: return "dynamic library" + case .textBased: return "text-based stub" + case .framework: return "framework" + case .object: return "object file" + case .objectLibrary: return "object library" } } } @@ -144,7 +146,7 @@ open class LinkerSpec : CommandLineToolSpec, @unchecked Sendable { /// Custom entry point for constructing linker tasks. public func constructLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set]) async { /// Delegate to the generic machinery. - await delegate.createTask(type: self, ruleInfo: defaultRuleInfo(cbc, delegate), commandLine: commandLineFromTemplate(cbc, delegate, optionContext: discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate)).map(\.asString), environment: environmentFromSpec(cbc, delegate), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: cbc.inputs.map({ $0.absolutePath }), outputs: [cbc.output], action: nil, execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing) + await delegate.createTask(type: self, ruleInfo: defaultRuleInfo(cbc, delegate), commandLine: commandLineFromTemplate(cbc, delegate, optionContext: discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate)).map(\.asString), environment: environmentFromSpec(cbc, delegate), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: cbc.inputs.map({ $0.absolutePath }), outputs: [cbc.output], action: createTaskAction(cbc, delegate), execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing) } } diff --git a/Sources/SWBCore/SpecImplementations/RegisterSpecs.swift b/Sources/SWBCore/SpecImplementations/RegisterSpecs.swift index 9199c2ec..6d86dac4 100644 --- a/Sources/SWBCore/SpecImplementations/RegisterSpecs.swift +++ b/Sources/SWBCore/SpecImplementations/RegisterSpecs.swift @@ -59,6 +59,7 @@ public struct BuiltinSpecsExtension: SpecificationsExtension { LibtoolLinkerSpec.self, LipoToolSpec.self, MkdirToolSpec.self, + ObjectLibraryAssemblerSpec.self, PLUtilToolSpec.self, ProductPackagingToolSpec.self, ShellScriptToolSpec.self, diff --git a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift index 959232e5..69a87c72 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift @@ -918,7 +918,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec ) // Create the task. - delegate.createTask(type: self, payload: payload, ruleInfo: defaultRuleInfo(cbc, delegate), commandLine: commandLine, environment: environmentFromSpec(cbc, delegate), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: inputs, outputs: outputs, action: nil, execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing) + delegate.createTask(type: self, payload: payload, ruleInfo: defaultRuleInfo(cbc, delegate), commandLine: commandLine, environment: environmentFromSpec(cbc, delegate), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: inputs, outputs: outputs, action: createTaskAction(cbc, delegate), execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing) } public func constructPreviewsBlankInjectionDylibTask( @@ -992,7 +992,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: [], outputs: outputs, - action: nil, + action: createTaskAction(cbc, delegate), execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing ) @@ -1246,6 +1246,9 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec case .object: // Object files are added to linker inputs in the sources task producer. return ([], []) + case .objectLibrary: + let pathFlags = specifier.absolutePathFlagsForLd() + return (pathFlags, [specifier.path]) } }.reduce(([], [])) { (lhs, rhs) in (lhs.args + rhs.args, lhs.inputs + rhs.inputs) } } @@ -1367,6 +1370,11 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec return LdLinkerOutputParser.self } + 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) + } + override public func discoveredCommandLineToolSpecInfo(_ producer: any CommandProducer, _ scope: MacroEvaluationScope, _ delegate: any CoreClientTargetDiagnosticProducingDelegate) async -> (any DiscoveredCommandLineToolSpecInfo)? { // The ALTERNATE_LINKER is the 'name' of the linker not the executable name, clang will find the linker binary based on name passed via -fuse-ld, but we need to discover // its properties by executing the actual binary. There is a common filename when the linker is not "ld" across all platforms using "ld.(.exe)" @@ -1482,6 +1490,9 @@ fileprivate extension LinkerSpec.LibrarySpecifier { case (.object, _): // Object files are added to linker inputs in the sources task producer. return [] + case (.objectLibrary, _): + // Object libraries can't be found via search paths. + return [] } } @@ -1518,6 +1529,8 @@ fileprivate extension LinkerSpec.LibrarySpecifier { case (.object, _): // Object files are added to linker inputs in the sources task producer. return [] + case (.objectLibrary, _): + return ["@\(path.join("args.resp").str)"] } } } @@ -1574,6 +1587,11 @@ 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) + } + override public func constructLinkerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, libraries: [LibrarySpecifier], usedTools: [CommandLineToolSpec: Set]) async { var inputPaths = cbc.inputs.map({ $0.absolutePath }) var specialArgs = [String]() @@ -1619,6 +1637,10 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @u // Object files are added to linker inputs in the sources task producer and so end up in the link-file-list. return [] + case .objectLibrary: + inputPaths.append(specifier.path) + return ["@\(specifier.path.join("args.resp").str)"] + case .framework: // A static library can build against a framework, since the library in the framework could be a static library, which is valid, and we can't tell here whether it is or not. So we leave it to libtool to do the right thing here. // Also, we wouldn't want to emit an error here even if we could determine that it contained a dylib, since the target might be only using the framework to find headers. @@ -1710,7 +1732,7 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @u workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: inputs, outputs: outputs, - action: nil, + action: createTaskAction(cbc, delegate), execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing ) diff --git a/Sources/SWBCore/SpecImplementations/Tools/ObjectLibraryAssembler.swift b/Sources/SWBCore/SpecImplementations/Tools/ObjectLibraryAssembler.swift new file mode 100644 index 00000000..d228388d --- /dev/null +++ b/Sources/SWBCore/SpecImplementations/Tools/ObjectLibraryAssembler.swift @@ -0,0 +1,19 @@ +//===----------------------------------------------------------------------===// +// +// 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 final class ObjectLibraryAssemblerSpec : GenericLinkerSpec, SpecIdentifierType, @unchecked Sendable { + public static let identifier: String = "org.swift.linkers.object-library-assembler" + + public override func createTaskAction(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) -> (any PlannedTaskAction)? { + return delegate.taskActionCreationDelegate.createObjectLibraryAssemblerTaskAction() + } +} diff --git a/Sources/SWBCore/Specs/CoreBuildSystem.xcspec b/Sources/SWBCore/Specs/CoreBuildSystem.xcspec index 1c0f6f57..fe11ab15 100644 --- a/Sources/SWBCore/Specs/CoreBuildSystem.xcspec +++ b/Sources/SWBCore/Specs/CoreBuildSystem.xcspec @@ -304,6 +304,7 @@ "mh_bundle", staticlib, "mh_object", + "objectlib", ); DefaultValue = ""; }, @@ -1351,6 +1352,9 @@ When `GENERATE_INFOPLIST_FILE` is enabled, sets the value of the [CFBundleIdenti { Value = "mh_object"; }, + { + Value = "objectlib"; + }, ); DefaultValue = ""; ConditionFlavors = ( diff --git a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift index 82af5212..154a25e7 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift @@ -356,7 +356,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F // When emitting remarks, for now, a dSYM is required () let dSYMForRemarks = scope.evaluate(BuiltinMacros.CLANG_GENERATE_OPTIMIZATION_REMARKS) let dSYM = dSYMForDebugInfo || dSYMForRemarks - return dSYM && scope.evaluate(BuiltinMacros.MACH_O_TYPE) != "staticlib" && scope.evaluate(BuiltinMacros.MACH_O_TYPE) != "mh_object" + return dSYM && !["staticlib", "mh_object", "objectlib"].contains(scope.evaluate(BuiltinMacros.MACH_O_TYPE)) } /// Computes and returns a list of libraries to include when linking. @@ -687,6 +687,15 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F xcframeworkSourcePath: xcframeworkPath, privacyFile: nil ) + } else if fileType.conformsTo(identifier: "compiled.object-library") { + return LinkerSpec.LibrarySpecifier( + kind: .objectLibrary, + path: absolutePath, + mode: .normal, + useSearchPaths: false, + swiftModulePaths: swiftModulePaths, + swiftModuleAdditionalLinkerArgResponseFilePaths: swiftModuleAdditionalLinkerArgResponseFilePaths, + ) } else { // FIXME: Error handling. return nil @@ -1640,21 +1649,33 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F /// Compute the linker to use in the given scope. private func getLinkerToUse(_ scope: MacroEvaluationScope) -> LinkerSpec { - let isStaticLib = scope.evaluate(BuiltinMacros.MACH_O_TYPE) == "staticlib" - - // Return the custom linker, if specified. - var identifier = scope.evaluate(isStaticLib ? BuiltinMacros.LIBRARIAN : BuiltinMacros.LINKER) - if !identifier.isEmpty { - let spec = context.getSpec(identifier) - if let linker = spec as? LinkerSpec { - return linker + let identifier: String + switch scope.evaluate(BuiltinMacros.MACH_O_TYPE) { + case "objectlib": + identifier = ObjectLibraryAssemblerSpec.identifier + case "staticlib": + let override = scope.evaluate(BuiltinMacros.LIBRARIAN) + if !override.isEmpty { + let spec = context.getSpec(override) + if let linker = spec as? LinkerSpec { + return linker + } + + // FIXME: Emit a warning here. } + identifier = LibtoolLinkerSpec.identifier + default: + let override = scope.evaluate(BuiltinMacros.LINKER) + if !override.isEmpty { + let spec = context.getSpec(override) + if let linker = spec as? LinkerSpec { + return linker + } - // FIXME: Emit a warning here. + // FIXME: Emit a warning here. + } + identifier = LdLinkerSpec.identifier } - - // Return the default linker. - identifier = isStaticLib ? LibtoolLinkerSpec.identifier : LdLinkerSpec.identifier return context.getSpec(identifier) as! LinkerSpec } diff --git a/Sources/SWBTaskExecution/BuildDescriptionManager.swift b/Sources/SWBTaskExecution/BuildDescriptionManager.swift index 667e0e44..68013078 100644 --- a/Sources/SWBTaskExecution/BuildDescriptionManager.swift +++ b/Sources/SWBTaskExecution/BuildDescriptionManager.swift @@ -895,6 +895,14 @@ extension BuildSystemTaskPlanningDelegate: TaskActionCreationDelegate { func createValidateDependenciesTaskAction() -> any PlannedTaskAction { return ValidateDependenciesTaskAction() } + + func createObjectLibraryAssemblerTaskAction() -> any PlannedTaskAction { + return ObjectLibraryAssemblerTaskAction() + } + + func createLinkerTaskAction(expandResponseFiles: Bool) -> any PlannedTaskAction { + return LinkerTaskAction(expandResponseFiles: expandResponseFiles) + } } fileprivate extension BuildDescription { diff --git a/Sources/SWBTaskExecution/BuiltinTaskActionsExtension.swift b/Sources/SWBTaskExecution/BuiltinTaskActionsExtension.swift index fa3b298b..cd19b016 100644 --- a/Sources/SWBTaskExecution/BuiltinTaskActionsExtension.swift +++ b/Sources/SWBTaskExecution/BuiltinTaskActionsExtension.swift @@ -53,6 +53,8 @@ public struct BuiltinTaskActionsExtension: TaskActionExtension { 38: GenericCachingTaskAction.self, 39: ProcessSDKImportsTaskAction.self, 40: ValidateDependenciesTaskAction.self, + 42: ObjectLibraryAssemblerTaskAction.self, + 43: LinkerTaskAction.self, ] } } diff --git a/Sources/SWBTaskExecution/CMakeLists.txt b/Sources/SWBTaskExecution/CMakeLists.txt index 4a766605..6ae2d684 100644 --- a/Sources/SWBTaskExecution/CMakeLists.txt +++ b/Sources/SWBTaskExecution/CMakeLists.txt @@ -50,8 +50,10 @@ add_library(SWBTaskExecution TaskActions/GenericCachingTaskAction.swift TaskActions/InfoPlistProcessorTaskAction.swift TaskActions/LinkAssetCatalogTaskAction.swift + TaskActions/LinkerTaskAction.swift TaskActions/LSRegisterURLTaskAction.swift TaskActions/MergeInfoPlistTaskAction.swift + TaskActions/ObjectLibraryAssemblerTaskAction.swift TaskActions/ODRAssetPackManifestTaskAction.swift TaskActions/PrecompileClangModuleTaskAction.swift TaskActions/ProcessProductEntitlementsTaskAction.swift diff --git a/Sources/SWBTaskExecution/TaskActions/LinkerTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/LinkerTaskAction.swift new file mode 100644 index 00000000..fedbdede --- /dev/null +++ b/Sources/SWBTaskExecution/TaskActions/LinkerTaskAction.swift @@ -0,0 +1,96 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Foundation +public import SWBCore +public import SWBUtil + +public final class LinkerTaskAction: TaskAction { + + public override class var toolIdentifier: String { + return "linker" + } + + /// Whether response files should be expanded before invoking the linker. + private let expandResponseFiles: Bool + + public init(expandResponseFiles: Bool) { + self.expandResponseFiles = expandResponseFiles + super.init() + } + + public override func serialize(to serializer: T) { + serializer.beginAggregate(2) + serializer.serialize(expandResponseFiles) + super.serialize(to: serializer) + serializer.endAggregate() + } + + public required init(from deserializer: any Deserializer) throws { + try deserializer.beginAggregate(2) + self.expandResponseFiles = try deserializer.deserialize() + try super.init(from: deserializer) + } + + public override func performTaskAction( + _ task: any ExecutableTask, + dynamicExecutionDelegate: any DynamicTaskExecutionDelegate, + executionDelegate: any TaskExecutionDelegate, + clientDelegate: any TaskExecutionClientDelegate, + outputDelegate: any TaskOutputDelegate + ) async -> CommandResult { + var commandLine = Array(task.commandLineAsStrings) + + if expandResponseFiles { + do { + commandLine = try ResponseFiles.expandResponseFiles( + commandLine, + fileSystem: executionDelegate.fs, + relativeTo: task.workingDirectory + ) + } catch { + outputDelegate.emitError("Failed to expand response files: \(error.localizedDescription)") + return .failed + } + } + + let processDelegate = TaskProcessDelegate(outputDelegate: outputDelegate) + do { + let success = try await dynamicExecutionDelegate.spawn( + commandLine: commandLine, + environment: task.environment.bindingsDictionary, + workingDirectory: task.workingDirectory, + processDelegate: processDelegate + ) + + if let error = processDelegate.executionError { + outputDelegate.emitError(error) + return .failed + } + + if success { + if let spec = task.type as? CommandLineToolSpec, let files = task.dependencyData { + do { + try spec.adjust(dependencyFiles: files, for: task, fs: executionDelegate.fs) + } catch { + outputDelegate.emitWarning("Unable to perform dependency info modifications: \(error)") + } + } + } + + return success ? .succeeded : .failed + } catch { + outputDelegate.emitError("Process execution failed: \(error.localizedDescription)") + return .failed + } + } +} diff --git a/Sources/SWBTaskExecution/TaskActions/ObjectLibraryAssemblerTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/ObjectLibraryAssemblerTaskAction.swift new file mode 100644 index 00000000..82176362 --- /dev/null +++ b/Sources/SWBTaskExecution/TaskActions/ObjectLibraryAssemblerTaskAction.swift @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// 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 SWBCore +import SWBUtil +import ArgumentParser + +public final class ObjectLibraryAssemblerTaskAction: TaskAction { + public override class var toolIdentifier: String { + return "assemble-object-library" + } + + private struct Options: ParsableArguments { + @Argument var inputs: [Path] + @Option var output: Path + } + + override public func performTaskAction( + _ task: any ExecutableTask, + dynamicExecutionDelegate: any DynamicTaskExecutionDelegate, + executionDelegate: any TaskExecutionDelegate, + clientDelegate: any TaskExecutionClientDelegate, + outputDelegate: any TaskOutputDelegate + ) async -> CommandResult { + do { + let options = try Options.parse(Array(task.commandLineAsStrings.dropFirst())) + try? executionDelegate.fs.remove(options.output) + try executionDelegate.fs.createDirectory(options.output) + _ = try await options.inputs.concurrentMap(maximumParallelism: 10) { input in + 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))) + return .succeeded + } catch { + outputDelegate.emitError("\(error)") + return .failed + } + } +} diff --git a/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift b/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift index cc8c6eee..979e947f 100644 --- a/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift +++ b/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift @@ -243,4 +243,12 @@ extension CapturingTaskGenerationDelegate: TaskActionCreationDelegate { package func createValidateDependenciesTaskAction() -> any PlannedTaskAction { return ValidateDependenciesTaskAction() } + + package func createObjectLibraryAssemblerTaskAction() -> any PlannedTaskAction { + return ObjectLibraryAssemblerTaskAction() + } + + package func createLinkerTaskAction(expandResponseFiles: Bool) -> any PlannedTaskAction { + return LinkerTaskAction(expandResponseFiles: expandResponseFiles) + } } diff --git a/Sources/SWBTestSupport/TaskPlanningTestSupport.swift b/Sources/SWBTestSupport/TaskPlanningTestSupport.swift index 0fdacfea..cc6505df 100644 --- a/Sources/SWBTestSupport/TaskPlanningTestSupport.swift +++ b/Sources/SWBTestSupport/TaskPlanningTestSupport.swift @@ -471,6 +471,14 @@ extension TestTaskPlanningDelegate: TaskActionCreationDelegate { package func createValidateDependenciesTaskAction() -> any PlannedTaskAction { return ValidateProductTaskAction() } + + package func createObjectLibraryAssemblerTaskAction() -> any PlannedTaskAction { + return ObjectLibraryAssemblerTaskAction() + } + + package func createLinkerTaskAction(expandResponseFiles: Bool) -> any PlannedTaskAction { + return LinkerTaskAction(expandResponseFiles: expandResponseFiles) + } } package final class CancellingTaskPlanningDelegate: TestTaskPlanningDelegate, @unchecked Sendable { diff --git a/Sources/SWBTestSupport/TestWorkspaces.swift b/Sources/SWBTestSupport/TestWorkspaces.swift index 9dcc2015..34d55abf 100644 --- a/Sources/SWBTestSupport/TestWorkspaces.swift +++ b/Sources/SWBTestSupport/TestWorkspaces.swift @@ -919,6 +919,7 @@ package final class TestStandardTarget: TestInternalTarget, Sendable { case staticFramework case staticLibrary case objectFile + case objectLibrary case dynamicLibrary case bundle case xpcService @@ -961,6 +962,8 @@ package final class TestStandardTarget: TestInternalTarget, Sendable { return "com.apple.product-type.library.static" case .objectFile: return "com.apple.product-type.objfile" + case .objectLibrary: + return "org.swift.product-type.library.object" case .dynamicLibrary: return "com.apple.product-type.library.dynamic" case .bundle: @@ -1030,6 +1033,8 @@ package final class TestStandardTarget: TestInternalTarget, Sendable { return "lib\(name).a" case .objectFile: return "\(name).o" + case .objectLibrary: + return "\(name).objlib" case .dynamicLibrary: // FIXME: This should be based on the target platform, not the host. See also: Swift Build doesn't support product references with non-constant basenames guard let suffix = try? ProcessInfo.processInfo.hostOperatingSystem().imageFormat.dynamicLibraryExtension else { diff --git a/Sources/SWBUniversalPlatform/CMakeLists.txt b/Sources/SWBUniversalPlatform/CMakeLists.txt index 90c37a6c..9a9d436e 100644 --- a/Sources/SWBUniversalPlatform/CMakeLists.txt +++ b/Sources/SWBUniversalPlatform/CMakeLists.txt @@ -37,6 +37,7 @@ SwiftBuild_Bundle(MODULE SWBUniversalPlatform FILES Specs/Ld.xcspec Specs/Lex.xcspec Specs/Libtool.xcspec + Specs/ObjectLibraryAssembler.xcspec Specs/PackageTypes.xcspec Specs/PBXCp.xcspec Specs/ProductTypes.xcspec diff --git a/Sources/SWBUniversalPlatform/Specs/Ld.xcspec b/Sources/SWBUniversalPlatform/Specs/Ld.xcspec index e1e1a60d..2f07a375 100644 --- a/Sources/SWBUniversalPlatform/Specs/Ld.xcspec +++ b/Sources/SWBUniversalPlatform/Specs/Ld.xcspec @@ -27,6 +27,7 @@ ProgressDescription = Linking; InputFileTypes = ( "compiled.mach-o.objfile", + "compiled.object-library", "compiled.mach-o.dylib", "sourcecode.text-based-dylib-definition", "wrapper.framework", diff --git a/Sources/SWBUniversalPlatform/Specs/Libtool.xcspec b/Sources/SWBUniversalPlatform/Specs/Libtool.xcspec index 28f8b8a3..d146ce6c 100644 --- a/Sources/SWBUniversalPlatform/Specs/Libtool.xcspec +++ b/Sources/SWBUniversalPlatform/Specs/Libtool.xcspec @@ -23,7 +23,8 @@ ExecDescription = "Create static library $(OutputFile:file)"; ProgressDescription = "Creating static library"; InputFileTypes = ( - compiled.mach-o.objfile + compiled.mach-o.objfile, + compiled.object-library ); Outputs = ( // We're a linker-like task, so we expect to be given an output path in 'OutputPath'. diff --git a/Sources/SWBUniversalPlatform/Specs/ObjectLibraryAssembler.xcspec b/Sources/SWBUniversalPlatform/Specs/ObjectLibraryAssembler.xcspec new file mode 100644 index 00000000..0a3d4758 --- /dev/null +++ b/Sources/SWBUniversalPlatform/Specs/ObjectLibraryAssembler.xcspec @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +( + { + Identifier = "org.swift.linkers.object-library-assembler"; + Type = Linker; + Name = Ld; + Description = "Assembles an object library"; + IsAbstract = Yes; + BinaryFormats = ( + "mach-o", + ); + CommandLine = "builtin-ObjectLibraryAssembler [options] [special-args] [inputs] --output $(OutputPath)"; + RuleName = "AssembleObjectLibrary $(OutputPath) $(variant) $(arch)"; + ExecDescription = "Assemble object library $(OutputFile:file)"; + ProgressDescription = Linking; + InputFileTypes = ( + "compiled.mach-o.objfile", + ); + Outputs = ( + "$(OutputPath)", + ); + CommandOutputParser = "XCGenericCommandOutputParser"; + Options = ( + + ); + } +) diff --git a/Sources/SWBUniversalPlatform/Specs/PackageTypes.xcspec b/Sources/SWBUniversalPlatform/Specs/PackageTypes.xcspec index 2a4dd1fe..538da914 100644 --- a/Sources/SWBUniversalPlatform/Specs/PackageTypes.xcspec +++ b/Sources/SWBUniversalPlatform/Specs/PackageTypes.xcspec @@ -87,6 +87,25 @@ }; }, + // Object Library + { + Type = PackageType; + Identifier = org.swift.package-type.object-library; + Name = "Object Library"; + Description = "Object Library"; + DefaultBuildSettings = { + EXECUTABLE_PREFIX = ""; + EXECUTABLE_SUFFIX = ""; + EXECUTABLE_NAME = "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_VARIANT_SUFFIX)$(EXECUTABLE_SUFFIX)"; + EXECUTABLE_PATH = "$(EXECUTABLE_NAME)"; + }; + ProductReference = { + FileType = compiled.object-library; + Name = "$(EXECUTABLE_NAME)"; + IsLaunchable = NO; + }; + }, + // CFBundle wrapper { Type = PackageType; diff --git a/Sources/SWBUniversalPlatform/Specs/ProductTypes.xcspec b/Sources/SWBUniversalPlatform/Specs/ProductTypes.xcspec index bd6cf9c8..e8211ed1 100644 --- a/Sources/SWBUniversalPlatform/Specs/ProductTypes.xcspec +++ b/Sources/SWBUniversalPlatform/Specs/ProductTypes.xcspec @@ -170,6 +170,37 @@ ); }, + // Object library + { + Type = ProductType; + Identifier = org.swift.product-type.library.object; + Class = XCStandaloneExecutableProductType; + Name = "Object Library"; + Description = "Object library"; + IconNamePrefix = "TargetLibrary"; + DefaultTargetName = "Object Library"; + DefaultBuildProperties = { + FULL_PRODUCT_NAME = "$(EXECUTABLE_NAME)"; + MACH_O_TYPE = "objectlib"; + REZ_EXECUTABLE = YES; + EXECUTABLE_SUFFIX = ".$(EXECUTABLE_EXTENSION)"; + EXECUTABLE_EXTENSION = "objlib"; + PUBLIC_HEADERS_FOLDER_PATH = "/usr/local/include"; + PRIVATE_HEADERS_FOLDER_PATH = "/usr/local/include"; + INSTALL_PATH = "/usr/local/lib"; + FRAMEWORK_FLAG_PREFIX = "-framework"; + LIBRARY_FLAG_PREFIX = "-l"; + LIBRARY_FLAG_NOSPACE = YES; + SKIP_INSTALL = YES; + STRIP_STYLE = "debugging"; + GCC_INLINES_ARE_PRIVATE_EXTERN = YES; + CODE_SIGNING_ALLOWED = NO; + }; + PackageTypes = ( + org.swift.package-type.object-library + ); + }, + // // Wrapper product types // diff --git a/Sources/SWBUniversalPlatform/Specs/StandardFileTypes.xcspec b/Sources/SWBUniversalPlatform/Specs/StandardFileTypes.xcspec index 490c60f6..9e400f27 100644 --- a/Sources/SWBUniversalPlatform/Specs/StandardFileTypes.xcspec +++ b/Sources/SWBUniversalPlatform/Specs/StandardFileTypes.xcspec @@ -853,6 +853,16 @@ AppliesToBuildRules = yes; UTI = "com.apple.mach-o-object"; }, + { + Type = FileType; + Identifier = compiled.object-library; + Class = PBXMachOFileType; + BasedOn = compiled.mach-o; + Name = "Object Library"; + Extensions = (o); + AppliesToBuildRules = yes; + UTI = "org.swift.object-library"; + }, { Type = FileType; Identifier = compiled.mach-o.executable; diff --git a/Sources/SWBWindowsPlatform/Specs/WindowsLibtool.xcspec b/Sources/SWBWindowsPlatform/Specs/WindowsLibtool.xcspec index b5257af4..4863b70f 100644 --- a/Sources/SWBWindowsPlatform/Specs/WindowsLibtool.xcspec +++ b/Sources/SWBWindowsPlatform/Specs/WindowsLibtool.xcspec @@ -28,7 +28,8 @@ ProgressDescription = "Creating static library"; InputFileTypes = ( // TODO: elf files instead - compiled.mach-o.objfile + compiled.mach-o.objfile, + compiled.object-library ); Outputs = ( // We're a linker-like task, so we expect to be given an output path in 'OutputPath'. diff --git a/Tests/SWBBuildSystemTests/ObjectLibraryBuildOperationTests.swift b/Tests/SWBBuildSystemTests/ObjectLibraryBuildOperationTests.swift new file mode 100644 index 00000000..37228924 --- /dev/null +++ b/Tests/SWBBuildSystemTests/ObjectLibraryBuildOperationTests.swift @@ -0,0 +1,363 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Testing + +import SWBCore +import SWBTestSupport +import SwiftBuildTestSupport +@_spi(Testing) import SWBUtil +import SWBProtocol +import SWBTaskExecution + +@Suite +fileprivate struct ObjectLibraryBuildOperationTests: CoreBasedTests { + @Test(.requireSDKs(.host)) + func objectLibraryBasics() async throws { + try await withTemporaryDirectory { tmpDirPath async throws -> Void in + let testWorkspace = TestWorkspace( + "Test", + sourceRoot: tmpDirPath.join("Test"), + projects: [ + TestProject( + "aProject", + groupTree: TestGroup( + "Sources", + children: [ + TestFile("a.c"), + TestFile("b.c"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "CODE_SIGNING_ALLOWED": "NO", + "PRODUCT_NAME": "$(TARGET_NAME)", + ]), + ], + targets: [ + TestStandardTarget( + "Library", + type: .objectLibrary, + buildPhases: [ + TestSourcesBuildPhase([ + "a.c", + "b.c", + ]), + ] + ), + ]) + ]) + let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false) + + try await tester.fs.writeFileContents(tmpDirPath.join("Test/aProject/a.c")) { + $0 <<< "void foo(void) {}\n" + } + + try await tester.fs.writeFileContents(tmpDirPath.join("Test/aProject/b.c")) { + $0 <<< "void bar(void) {}\n" + } + + try await tester.checkBuild(runDestination: .host) { results in + results.checkNoDiagnostics() + let libPath = tmpDirPath.join("Test/aProject/build/Debug\(RunDestinationInfo.host.builtProductsDirSuffix)/Library.objlib") + #expect(tester.fs.exists(libPath)) + try #expect(tester.fs.listdir(libPath).sorted() == ["a.o", "args.resp", "b.o"]) + } + } + } + + @Test(.requireSDKs(.host)) + func consumingObjectLibrary_ld() async throws { + try await withTemporaryDirectory { tmpDirPath async throws -> Void in + let testWorkspace = TestWorkspace( + "Test", + sourceRoot: tmpDirPath.join("Test"), + projects: [ + TestProject( + "aProject", + groupTree: TestGroup( + "Sources", + children: [ + TestFile("a.swift"), + TestFile("b.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "CODE_SIGNING_ALLOWED": "NO", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SWIFT_VERSION": try await swiftVersion, + ]), + ], + targets: [ + TestStandardTarget( + "Tool", + type: .commandLineTool, + buildPhases: [ + TestSourcesBuildPhase([ + "b.swift", + ]), + TestFrameworksBuildPhase([ + "Library.objlib" + ]) + ], + dependencies: [ + "Library", + ] + ), + TestStandardTarget( + "Library", + type: .objectLibrary, + buildPhases: [ + TestSourcesBuildPhase([ + "a.swift", + ]), + ] + ), + ]) + ]) + let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false) + + try await tester.fs.writeFileContents(tmpDirPath.join("Test/aProject/a.swift")) { + $0 <<< """ + public struct Foo { + public var x: Int + + public init(x: Int) { + self.x = x + } + } + """ + } + + try await tester.fs.writeFileContents(tmpDirPath.join("Test/aProject/b.swift")) { + $0 <<< """ + import Library + + @main + struct Entry { + static func main() { + let f = Foo(x: 42) + print(f) + } + } + + """ + } + + try await tester.checkBuild(runDestination: .host) { results in + results.checkNoDiagnostics() + results.checkTaskExists(.matchRuleType("Ld")) + } + } + } + + @Test(.requireSDKs(.host)) + func consumingObjectLibrary_libtool() async throws { + try await withTemporaryDirectory { tmpDirPath async throws -> Void in + let testWorkspace = TestWorkspace( + "Test", + sourceRoot: tmpDirPath.join("Test"), + projects: [ + TestProject( + "aProject", + groupTree: TestGroup( + "Sources", + children: [ + TestFile("a.swift"), + TestFile("b.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "CODE_SIGNING_ALLOWED": "NO", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SWIFT_VERSION": try await swiftVersion, + "LIBTOOL_USE_RESPONSE_FILE": "NO", + ]), + ], + targets: [ + TestStandardTarget( + "StaticLibrary", + type: .staticLibrary, + buildPhases: [ + TestSourcesBuildPhase([ + "b.swift", + ]), + TestFrameworksBuildPhase([ + "Library.objlib" + ]) + ], + dependencies: [ + "Library", + ] + ), + TestStandardTarget( + "Library", + type: .objectLibrary, + buildPhases: [ + TestSourcesBuildPhase([ + "a.swift", + ]), + ] + ), + ]) + ]) + let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false) + + try await tester.fs.writeFileContents(tmpDirPath.join("Test/aProject/a.swift")) { + $0 <<< """ + public struct Foo { + public var x: Int + + public init(x: Int) { + self.x = x + } + } + """ + } + + try await tester.fs.writeFileContents(tmpDirPath.join("Test/aProject/b.swift")) { + $0 <<< """ + import Library + + @main + struct Entry { + static func main() { + let f = Foo(x: 42) + print(f) + } + } + + """ + } + + try await tester.checkBuild(runDestination: .host) { results in + results.checkNoDiagnostics() + results.checkTaskExists(.matchRuleType("Libtool")) + } + } + } + + @Test(.requireSDKs(.macOS)) + func consumingObjectLibraryIncrementalBuild() async throws { + try await withTemporaryDirectory { tmpDirPath async throws -> Void in + let testWorkspace = TestWorkspace( + "Test", + sourceRoot: tmpDirPath.join("Test"), + projects: [ + TestProject( + "aProject", + groupTree: TestGroup( + "Sources", + children: [ + TestFile("a.swift"), + TestFile("b.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "CODE_SIGNING_ALLOWED": "NO", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SWIFT_VERSION": try await swiftVersion, + ]), + ], + targets: [ + TestStandardTarget( + "Tool", + type: .commandLineTool, + buildPhases: [ + TestSourcesBuildPhase([ + "b.swift", + ]), + TestFrameworksBuildPhase([ + "Library.objlib" + ]) + ], + dependencies: [ + "Library", + ] + ), + TestStandardTarget( + "Library", + type: .objectLibrary, + buildPhases: [ + TestSourcesBuildPhase([ + "a.swift", + ]), + ] + ), + ]) + ]) + let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false) + + try await tester.fs.writeFileContents(tmpDirPath.join("Test/aProject/a.swift")) { + $0 <<< """ + public struct Foo { + public var x: Int + + public init(x: Int) { + self.x = x + } + } + """ + } + + try await tester.fs.writeFileContents(tmpDirPath.join("Test/aProject/b.swift")) { + $0 <<< """ + import Library + + @main + struct Entry { + static func main() { + let f = Foo(x: 42) + print(f) + } + } + + """ + } + + try await tester.checkBuild(runDestination: .host, persistent: true) { results in + results.checkNoDiagnostics() + } + + try await tester.checkNullBuild(runDestination: .host, persistent: true) + + try await tester.fs.writeFileContents(tmpDirPath.join("Test/aProject/a.swift")) { + $0 <<< """ + public struct Foo { + public var x: Int + + public init(x: Int) { + print("hello, world!") + self.x = x + } + } + """ + } + + try await tester.checkBuild(runDestination: .host, persistent: true) { results in + results.checkNoDiagnostics() + // We should both reassemble the object library and relink the executable after updating an object file. + results.checkTaskExists(.matchRuleType("AssembleObjectLibrary")) + results.checkTaskExists(.matchRuleType("Ld")) + } + + try await tester.checkNullBuild(runDestination: .host, persistent: true) + } + } +} diff --git a/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift b/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift index c3094eb8..75c1d8ea 100644 --- a/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift +++ b/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift @@ -237,6 +237,14 @@ extension CapturingTaskGenerationDelegate: TaskActionCreationDelegate { func createValidateDependenciesTaskAction() -> any PlannedTaskAction { return ValidateProductTaskAction() } + + func createObjectLibraryAssemblerTaskAction() -> any PlannedTaskAction { + return ObjectLibraryAssemblerTaskAction() + } + + func createLinkerTaskAction(expandResponseFiles: Bool) -> any PlannedTaskAction { + return LinkerTaskAction(expandResponseFiles: expandResponseFiles) + } } extension CapturingTaskGenerationDelegate: CoreClientDelegate { diff --git a/Tests/SWBCoreTests/CommandLineSpecTests.swift b/Tests/SWBCoreTests/CommandLineSpecTests.swift index c00dfc25..754f3cfc 100644 --- a/Tests/SWBCoreTests/CommandLineSpecTests.swift +++ b/Tests/SWBCoreTests/CommandLineSpecTests.swift @@ -1234,7 +1234,7 @@ import SWBMacro let mockFileType = try core.specRegistry.getSpec("file") as FileTypeSpec let cbc = CommandBuildContext(producer: producer, scope: mockScope, inputs: [FileToBuild(absolutePath: Path.root.join("tmp/obj/normal/x86_64/file1.o"), fileType: mockFileType)], output: Path.root.join("tmp/obj/normal/x86_64/output")) - // Test all permutations of library kind, linkage mode and search path usage, except for object files. + // Test all permutations of library kind, linkage mode and search path usage, except for object files and object libraries. func generateLibrarySpecifiers(kind: LinkerSpec.LibrarySpecifier.Kind) -> [LinkerSpec.LibrarySpecifier] { var result = [LinkerSpec.LibrarySpecifier]() for useSearchPaths in [true, false] { @@ -1253,6 +1253,8 @@ import SWBMacro filePath = Path.root.join("tmp/Foo\(suffix).framework") case .object: continue + case .objectLibrary: + continue } result.append(LinkerSpec.LibrarySpecifier(kind: kind, path: filePath, mode: mode, useSearchPaths: useSearchPaths, swiftModulePaths: [:], swiftModuleAdditionalLinkerArgResponseFilePaths: [:])) } diff --git a/Tests/SWBTaskConstructionTests/ObjectLibraryTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/ObjectLibraryTaskConstructionTests.swift new file mode 100644 index 00000000..e6d0d958 --- /dev/null +++ b/Tests/SWBTaskConstructionTests/ObjectLibraryTaskConstructionTests.swift @@ -0,0 +1,138 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Testing + +import SWBCore +import SWBTaskConstruction +import SWBTestSupport +import SWBUtil + +@Suite +fileprivate struct ObjectLibraryTaskConstructionTests: CoreBasedTests { + @Test(.requireSDKs(.host)) + func objectLibraryBasics() async throws { + let testProject = TestProject( + "aProject", + groupTree: TestGroup( + "SomeFiles", + children: [ + TestFile("a.c"), + TestFile("b.c"), + ]), + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [ + "PRODUCT_NAME": "$(TARGET_NAME)", + "SWIFT_EXEC": try await swiftCompilerPath.str, + "SWIFT_VERSION": try await swiftVersion + ]), + ], + targets: [ + TestStandardTarget( + "Library", + type: .objectLibrary, + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [:]), + ], + buildPhases: [ + TestSourcesBuildPhase(["a.c", "b.c"]), + ] + ), + ]) + let core = try await getCore() + let tester = try TaskConstructionTester(core, testProject) + + await tester.checkBuild(BuildParameters(configuration: "Debug", overrides: [:]), runDestination: .host) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("AssembleObjectLibrary")) { task in + task.checkCommandLineMatches([ + "builtin-ObjectLibraryAssembler", + .suffix("a.o"), + .suffix("b.o"), + "--output", + .suffix("Library.objlib") + ]) + task.checkInputs([ + .pathPattern(.suffix("a.o")), + .pathPattern(.suffix("b.o")), + .namePattern(.any), + .namePattern(.any), + ]) + task.checkOutputs([ + .pathPattern(.suffix("Library.objlib")) + ]) + } + } + } + + @Test(.requireSDKs(.host)) + func objectLibraryConsumer() async throws { + let testWorkspace = TestWorkspace( + "Test", + projects: [ + TestProject( + "aProject", + groupTree: TestGroup( + "Sources", + children: [ + TestFile("a.swift"), + TestFile("b.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "CODE_SIGNING_ALLOWED": "NO", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SWIFT_VERSION": try await swiftVersion, + "SWIFT_EXEC": try await swiftCompilerPath.str + ]), + ], + targets: [ + TestStandardTarget( + "Tool", + type: .commandLineTool, + buildPhases: [ + TestSourcesBuildPhase([ + "b.swift", + ]), + TestFrameworksBuildPhase([ + "Library.objlib" + ]) + ], + dependencies: [ + "Library", + ] + ), + TestStandardTarget( + "Library", + type: .objectLibrary, + buildPhases: [ + TestSourcesBuildPhase([ + "a.swift", + ]), + ] + ), + ]) + ]) + + let core = try await getCore() + let tester = try TaskConstructionTester(core, testWorkspace) + + await tester.checkBuild(BuildParameters(configuration: "Debug", overrides: [:]), runDestination: .host) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("Ld")) { task in + task.checkCommandLineMatches([.and(.suffix("args.resp"), .prefix("@"))]) + } + } + } +}