Skip to content

Add initial support for object library product type #631

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/SWBCore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Sources/SWBCore/PlannedTaskAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 8 additions & 6 deletions Sources/SWBCore/SpecImplementations/LinkerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
Expand Down Expand Up @@ -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<FileTypeSpec>]) 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)
}
}

Expand Down
1 change: 1 addition & 0 deletions Sources/SWBCore/SpecImplementations/RegisterSpecs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public struct BuiltinSpecsExtension: SpecificationsExtension {
LibtoolLinkerSpec.self,
LipoToolSpec.self,
MkdirToolSpec.self,
ObjectLibraryAssemblerSpec.self,
PLUtilToolSpec.self,
ProductPackagingToolSpec.self,
ShellScriptToolSpec.self,
Expand Down
12 changes: 12 additions & 0 deletions Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}
Expand Down Expand Up @@ -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 []
}
}

Expand Down Expand Up @@ -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)"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all tools on all platforms support response files; we already have CLANG_USE_RESPONSE_FILE / LIBTOOL_USE_RESPONSE_FILE for that reason. I think we need some checks here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, this is going to be annoying because determining the full list of object files a dependency target will produce from the dependent is tricky

}
}
}
Expand Down Expand Up @@ -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)"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should respect LIBTOOL_USE_RESPONSE_FILE (see above)


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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a switch (isObjectLibrary, isStaticLib) to explicitly handle all cases? It's a bit ambiguous what happens if both are true; actually I think that should probably an error because a while ago we switched to MACH_O_TYPE consistently being the source of truth (since people set it, so there are dylib product types with MACH_O_TYPE set to staticlib, and static lib product types with it set to mh_dylib).

That said, should we also consider driving this entirely off a new MACH_O_TYPE value like "objectlib" for consistency and code simplicity? It's not like "staticlib" is an actual Mach-O object type anyways, so it's already overloaded and this doesn't correspond 1:1 with the Mach-O file format header's file type field (and is has way more values anyways).

identifier = ObjectLibraryAssemblerSpec.identifier
} else if isStaticLib {
identifier = LibtoolLinkerSpec.identifier
} else {
identifier = LdLinkerSpec.identifier
}
return context.getSpec(identifier) as! LinkerSpec
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions Sources/SWBTaskExecution/BuildDescriptionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,10 @@ extension BuildSystemTaskPlanningDelegate: TaskActionCreationDelegate {
func createProcessSDKImportsTaskAction() -> any PlannedTaskAction {
return ProcessSDKImportsTaskAction()
}

func createObjectLibraryAssemblerTaskAction() -> any PlannedTaskAction {
return ObjectLibraryAssemblerTaskAction()
}
}

fileprivate extension BuildDescription {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
]
}
}
1 change: 1 addition & 0 deletions Sources/SWBTaskExecution/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Been waiting forever to be able to use this in task action implementations ❤️

@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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,8 @@ extension CapturingTaskGenerationDelegate: TaskActionCreationDelegate {
package func createProcessSDKImportsTaskAction() -> any PlannedTaskAction {
return ProcessSDKImportsTaskAction()
}

package func createObjectLibraryAssemblerTaskAction() -> any PlannedTaskAction {
return ObjectLibraryAssemblerTaskAction()
}
}
4 changes: 4 additions & 0 deletions Sources/SWBTestSupport/TaskPlanningTestSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions Sources/SWBTestSupport/TestWorkspaces.swift
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,7 @@ package final class TestStandardTarget: TestInternalTarget, Sendable {
case staticFramework
case staticLibrary
case objectFile
case objectLibrary
case dynamicLibrary
case bundle
case xpcService
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -1028,6 +1031,8 @@ package final class TestStandardTarget: TestInternalTarget, Sendable {
return "lib\(name).a"
case .objectFile:
return "\(name).o"
case .objectLibrary:
return "\(name).objlib"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is .objlib what CMake uses? Doesn't particularly matter, just wondering if there's any prior art at all

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No precedent, I just made this up

case .dynamicLibrary:
// FIXME: This should be based on the target platform, not the host. See also: <rdar://problem/29410050> Swift Build doesn't support product references with non-constant basenames
guard let suffix = try? ProcessInfo.processInfo.hostOperatingSystem().imageFormat.dynamicLibraryExtension else {
Expand Down
1 change: 1 addition & 0 deletions Sources/SWBUniversalPlatform/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Sources/SWBUniversalPlatform/Specs/Ld.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion Sources/SWBUniversalPlatform/Specs/Libtool.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -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'.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = (

);
}
)
19 changes: 19 additions & 0 deletions Sources/SWBUniversalPlatform/Specs/PackageTypes.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading