-
Notifications
You must be signed in to change notification settings - Fork 105
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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)"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
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 |
---|---|---|
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a 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 | ||
} | ||
|
||
|
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
---|---|---|
|
@@ -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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
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 = ( | ||
|
||
); | ||
} | ||
) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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