Hi everyone, I have a follow on proposal for SE-0272 and SE-0305, I would like to pitch to the community. For those who prefer a plain markdown file it's available here.
Binary Static Library Dependencies
- Proposal: SE-NNNN
- Authors: Daniel Grumberg, Max Desiatov, Franz Busch
- Review Manager: TBD
- Status: Awaiting implementation
- Implementation: apple/swift-package-manager#6967
- Bugs: Swift Package Manger Issue
Introduction
Swift continues to grow as a cross-platform language supporting a wide variety of use cases from programming embedded device to server-side development across a multitude of operating systems.
However, currently SwiftPM supports linking against binary dependencies on Apple platforms only.
This proposal aims to make it possible to provide static library dependencies exposing a C interface on non-Apple platforms.
Swift-evolution thread:
Motivation
The Swift Package Manager’s binaryTarget
type lets packages vend libraries that either cannot be built in Swift Package Manager for technical reasons,
or for which the source code cannot be published for legal or other reasons.
In the current version of SwiftPM, binary targets support the following:
- Libraries in an Xcode-oriented format called XCFramework, and only for Apple platforms, introduced in SE-0272.
- Executables through the use of artifact bundles introduced in SE-0305.
We aim here to bring a subset of the XCFramework capabilities to non-Apple platforms in a safe way.
While this proposal is specifically focused on binary static library dependencies without unexpected unresolved external symbols on non-Apple platforms,
it tries to do so in a way that will not prevent broader future support for static libraries and dynamically linked libraries.
Proposed solution
This proposal extends artifact bundles introduced by SE-0305 to include a new kind of artifact type to represent a binary library dependency: staticLibrary
.
The artifact manifest would encode the following information for each variant:
- The static library to pass to the linker.
On Apple and Linux platforms, this would be.a
files and on Windows it would be a.lib
file. - Enough information to be able to use the library's API in the packages source code,
i.e., headers and module maps for libraries exporting a C-based interface.
Additionnaly, we propose the addition of an auditing tool that can validate the library artifact is safe to use across the Linux-based platforms supported by the Swift project.
Such a tool would ensure that people do not accidentally distribute artifacts that require dependencies that are not met on the various deployment platforms.
Detailed design
This section describes the changes to artifact bundle manifests in detail, the semantic impact of the changes on SwiftPM's build infrastructure, and describes the operation of the auditing tool.
Artifact Manifest Semantics
The artifact manifest JSON format for a static library is described below:
{
"schemaVersion": "1.0",
"artifacts": {
"<identifier>": {
"version": "<version number>",
"type": "staticLibrary",
"variants": [
{
"path": "<relative-path-to-library-file>",
"headerPaths": ["<relative-path-to-header-directory-1>, ...],
"moduleMapPath": "<path-to-module-map>",
"supportedTriples": ["<triple1>", ... ],
},
...
]
},
...
}
}
The additions are:
- The
staticLibrary
artifacttype
that indicates this binary artifact is not an executable but rather a static library to link against. - The
headerPaths
field specifies directory paths relative to the root of the artifact bundle that contain the header interfaces to the static library.
These are forwarded along to the swift compiler (or the C compiler) using the usual search path arguments.
Each of these directories can optionally contain amodule.modulemap
file that will be used for importing the API into Swift code. - The optional
moduleMapPath
field specifies a custom module map to use if the header paths do not contain the module definitions or to provide custom overrides.
As with executable binary artifacts, the path
field represents the relative path to the binary from the root of the artifact bundle,
and the supportedTriples
field provides information about the target triples supported by this variant.
Auditing tool
Without proper auditing it would be very easy to provide binary static library artifacts that call into unresolved external symbols that are not available on the runtime platform, e.g., due to missing linkage to a system dynamic library.
We propose the introduction of a new tool that can validate the "safety" of a binary library artifact across the platforms it supports and the corresponding runtime environment.
In this proposal we restrict ourselves to static libraries that do not have any external dependencies beyond the C standard library and runtime.
To achieve this we need to be able to detect validate this property across the three object file formats used in static libraries on our supported platforms: Mach-O on Apple platforms, ELF on Linux-based platforms, and COFF on Windows.
All three formats express references to external symbols as relocations which reside in a single section of each object file.
We propose adding the llvm-objdump
to the toolchain to provide the capability to inspect relocations across all three supported object file formats. The tool would use llvm-objdump
every object file in the static library and construct a complete list of symbols defined and referenced across the entire library.
Additionally, the tool would construct a simple C compiler invocation to derive to generate a default linker invocation.
This would be used to derive the libraries linked by default in a C program, these libraries would then be scanned to contribute to the list of defined symbols.
The tool would then check that the referenced symbols list is a subset of the set of defined symbols and emit an error otherwise.
This would be sufficient to guarantee that all symbols from the static library would be available at runtime for statically linked executables or for ones running on the build host.
To ensure maximum runtime compatibility we would also provide a Linux-based Docker image that uses the oldest supported glibc
for a given Swift version.
As glibc
is backwards compatible, a container running the audit on a given static library would ensure that the version of glibc
on any runtime platform would be compatible with the binary artifact.
This strategy as been succesfully employed in the Python community with manylinux
.
Security
This proposal brings the security implications outlined in SE-0272 to non-Apple platforms,
namely that a malicious attacker having access to both the server hosting the artifact and the git repository that vends the Package Manifest could provide a malicious library.
Users should exercise caution when onboarding binary dependencies.
Impact on existing packages
No current package should be affected by this change since this is only an additive change in enabling SwiftPM to use binary target library dependencies on non-Apple platforms.
Future directions
Support Swift static libraries
To do this we would extend the static library binary artifact manifest to provide a .swiftinterface
file that can be consumed by the Swift compiler to import the Swift APIs.
Additionally we would extend the auditing tool to validate the usage of Swift standard library and runtime symbols, e.g., from libSwiftCore
.
Extend binary compatibility guarantees
This proposal limits itself to providing facilities for binary compatibility only with the C standard library and runtime.
In the future we could provide a system to allow binary artifact distributors to specify additional linkage dependencies for their binary artifacts.
These would be used to customize the operation of the audit tool and perform automatic linking of them in any client target that depends on the binary artifact, in the same way CMake propagates link dependencies transitively.
Add support for dynamically linked dependencies
On Windows dynamic linking requires an import library which is a small static library that contains stubs for symbols exported by the dynamic library.
These stubs are roughly equivalent to a PLT entry in an ELF executable, but are generated during the build of the dynamic library and must be provided to clients of the library for linking purposes.
Similarly on Linux and Apple platforms binary artifact maintainers may wish to provide a dynamic library stub to improve link performance.
To support these use cases the library binary artifact manifest schema could be extended to provide facilities to provide both a link-time and runtime dependency.