diff --git a/Sources/SWBCore/CMakeLists.txt b/Sources/SWBCore/CMakeLists.txt index 9dd6a4bb..89c3ccf0 100644 --- a/Sources/SWBCore/CMakeLists.txt +++ b/Sources/SWBCore/CMakeLists.txt @@ -138,6 +138,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 3518ddc3..75bccc1a 100644 --- a/Sources/SWBCore/PlannedTaskAction.swift +++ b/Sources/SWBCore/PlannedTaskAction.swift @@ -346,6 +346,7 @@ public protocol TaskActionCreationDelegate func createSignatureCollectionTaskAction() -> any PlannedTaskAction func createClangModuleVerifierInputGeneratorTaskAction() -> any PlannedTaskAction func createProcessSDKImportsTaskAction() -> any PlannedTaskAction + func createObjectLibraryAssemblerTaskAction() -> 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 4bf7277f..c97d82a0 100644 --- a/Sources/SWBCore/SpecImplementations/RegisterSpecs.swift +++ b/Sources/SWBCore/SpecImplementations/RegisterSpecs.swift @@ -58,6 +58,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 9c36a2c1..7da65541 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift @@ -1228,6 +1228,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) } } @@ -1464,6 +1467,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 [] } } @@ -1500,6 +1506,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)"] } } } @@ -1601,6 +1609,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. 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/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift index 5987aa71..9ee184b7 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift @@ -687,6 +687,15 @@ final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBase 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 @@ -1624,20 +1633,30 @@ final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBase /// Compute the linker to use in the given scope. private func getLinkerToUse(_ scope: MacroEvaluationScope) -> LinkerSpec { let isStaticLib = scope.evaluate(BuiltinMacros.MACH_O_TYPE) == "staticlib" + let isObjectLibrary = context.productType?.conformsTo(identifier: "org.swift.product-type.library.object") == true // 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 - } + if !isObjectLibrary { + let identifier = scope.evaluate(isStaticLib ? BuiltinMacros.LIBRARIAN : BuiltinMacros.LINKER) + if !identifier.isEmpty { + let spec = context.getSpec(identifier) + if let linker = spec as? LinkerSpec { + return linker + } - // FIXME: Emit a warning here. + // FIXME: Emit a warning here. + } } // Return the default linker. - identifier = isStaticLib ? LibtoolLinkerSpec.identifier : LdLinkerSpec.identifier + let identifier: String + if isObjectLibrary { + identifier = ObjectLibraryAssemblerSpec.identifier + } else if isStaticLib { + identifier = LibtoolLinkerSpec.identifier + } else { + identifier = LdLinkerSpec.identifier + } return context.getSpec(identifier) as! LinkerSpec } diff --git a/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift index 01cda675..9c95dc2b 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift @@ -1356,7 +1356,7 @@ extension TaskProducerContext: CommandProducer { let indexEnableBuildArena = scope.evaluate(BuiltinMacros.INDEX_ENABLE_BUILD_ARENA) let isBundleProductType = productType?.conformsTo(identifier: "com.apple.product-type.bundle") ?? false let isStaticLibrary = scope.evaluate(BuiltinMacros.MACH_O_TYPE) == "staticlib" - let isObject = scope.evaluate(BuiltinMacros.MACH_O_TYPE) == "mh_object" + let isObject = scope.evaluate(BuiltinMacros.MACH_O_TYPE) == "mh_object" && productType?.conformsTo(identifier: "org.swift.product-type.library.object") != true let result = (isBuild || isLocExport) && !indexEnableBuildArena && (isBundleProductType || isStaticLibrary || isObject) diff --git a/Sources/SWBTaskExecution/BuildDescriptionManager.swift b/Sources/SWBTaskExecution/BuildDescriptionManager.swift index c6a57ead..0cbdda5f 100644 --- a/Sources/SWBTaskExecution/BuildDescriptionManager.swift +++ b/Sources/SWBTaskExecution/BuildDescriptionManager.swift @@ -891,6 +891,10 @@ extension BuildSystemTaskPlanningDelegate: TaskActionCreationDelegate { func createProcessSDKImportsTaskAction() -> any PlannedTaskAction { return ProcessSDKImportsTaskAction() } + + func createObjectLibraryAssemblerTaskAction() -> any PlannedTaskAction { + return ObjectLibraryAssemblerTaskAction() + } } fileprivate extension BuildDescription { diff --git a/Sources/SWBTaskExecution/BuiltinTaskActionsExtension.swift b/Sources/SWBTaskExecution/BuiltinTaskActionsExtension.swift index 6474dd83..573981cd 100644 --- a/Sources/SWBTaskExecution/BuiltinTaskActionsExtension.swift +++ b/Sources/SWBTaskExecution/BuiltinTaskActionsExtension.swift @@ -51,7 +51,8 @@ public struct BuiltinTaskActionsExtension: TaskActionExtension { 36: ConstructStubExecutorInputFileListTaskAction.self, 37: ConcatenateTaskAction.self, 38: GenericCachingTaskAction.self, - 39: ProcessSDKImportsTaskAction.self + 39: ProcessSDKImportsTaskAction.self, + 42: ObjectLibraryAssemblerTaskAction.self ] } } diff --git a/Sources/SWBTaskExecution/CMakeLists.txt b/Sources/SWBTaskExecution/CMakeLists.txt index 6c7cba03..862651ce 100644 --- a/Sources/SWBTaskExecution/CMakeLists.txt +++ b/Sources/SWBTaskExecution/CMakeLists.txt @@ -52,6 +52,7 @@ add_library(SWBTaskExecution TaskActions/LinkAssetCatalogTaskAction.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/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 b4689254..cc0927cc 100644 --- a/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift +++ b/Sources/SWBTestSupport/CapturingTaskGenerationDelegate.swift @@ -239,4 +239,8 @@ extension CapturingTaskGenerationDelegate: TaskActionCreationDelegate { package func createProcessSDKImportsTaskAction() -> any PlannedTaskAction { return ProcessSDKImportsTaskAction() } + + package func createObjectLibraryAssemblerTaskAction() -> any PlannedTaskAction { + return ObjectLibraryAssemblerTaskAction() + } } diff --git a/Sources/SWBTestSupport/TaskPlanningTestSupport.swift b/Sources/SWBTestSupport/TaskPlanningTestSupport.swift index f615bdc7..2212b50d 100644 --- a/Sources/SWBTestSupport/TaskPlanningTestSupport.swift +++ b/Sources/SWBTestSupport/TaskPlanningTestSupport.swift @@ -467,6 +467,10 @@ extension TestTaskPlanningDelegate: TaskActionCreationDelegate { package func createProcessSDKImportsTaskAction() -> any PlannedTaskAction { return ProcessSDKImportsTaskAction() } + + package func createObjectLibraryAssemblerTaskAction() -> any PlannedTaskAction { + return ObjectLibraryAssemblerTaskAction() + } } package final class CancellingTaskPlanningDelegate: TestTaskPlanningDelegate, @unchecked Sendable { diff --git a/Sources/SWBTestSupport/TestWorkspaces.swift b/Sources/SWBTestSupport/TestWorkspaces.swift index 6837bcf7..765e4c55 100644 --- a/Sources/SWBTestSupport/TestWorkspaces.swift +++ b/Sources/SWBTestSupport/TestWorkspaces.swift @@ -917,6 +917,7 @@ package final class TestStandardTarget: TestInternalTarget, Sendable { case staticFramework case staticLibrary case objectFile + case objectLibrary case dynamicLibrary case bundle case xpcService @@ -959,6 +960,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: @@ -1028,6 +1031,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 ddacd3d2..b0594706 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..a350a9d5 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 = "mh_object"; + 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 5001cacd..35950e97 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..fbd2bce3 --- /dev/null +++ b/Tests/SWBBuildSystemTests/ObjectLibraryBuildOperationTests.swift @@ -0,0 +1,274 @@ +//===----------------------------------------------------------------------===// +// +// 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() 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() + } + } + } + + @Test(.requireSDKs(.host)) + 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 d85ccb2c..51c9f490 100644 --- a/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift +++ b/Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift @@ -233,6 +233,10 @@ extension CapturingTaskGenerationDelegate: TaskActionCreationDelegate { func createProcessSDKImportsTaskAction() -> any PlannedTaskAction { return ProcessSDKImportsTaskAction() } + + func createObjectLibraryAssemblerTaskAction() -> any PlannedTaskAction { + return ObjectLibraryAssemblerTaskAction() + } } extension CapturingTaskGenerationDelegate: CoreClientDelegate { diff --git a/Tests/SWBCoreTests/CommandLineSpecTests.swift b/Tests/SWBCoreTests/CommandLineSpecTests.swift index 826e08ab..3bef6d38 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("@"))]) + } + } + } +}