Hi all,
I would like to discuss adding a generated, read-only EmbeddedFileSystem to
SwiftPM. It would let a target access resources declared with SwiftPM's
existing Resource.embedInCode(_:) rule as a path-preserving file system.
The intended use cases are self-contained command-line and server executables,
WASI programs, test fixtures, templates, and static web content: cases where
raw resources should travel with the linked program without requiring a
Foundation bundle or a separately deployed resource directory.
This pitch does not propose a new resource declaration rule. The existing
.embedInCode rule would populate the embedded file system.
Background
SE-0271: Package Manager
Resources
introduced target-scoped resources and Bundle.module. SwiftPM later added
Resource.embedInCode(_:),
available since PackageDescription 5.9.
Today, embedding a resource generates a property similar to:
struct PackageResources {
static let identifier_txt: [UInt8] = [72, 101, 108, 108, 111, /* ... */]
}
This is useful for small, individual files, but it has two important
limitations.
First, the generated API is a collection of properties derived from file
basenames. SwiftPM does not generate an EmbeddedFileSystem with a directory
hierarchy that can be traversed or queried by path. A directory of templates or
static web files therefore cannot be consumed as a file system.
Second, the native SwiftPM implementation currently renders every byte as a
Swift integer literal. The implementation itself contains a FIXME noting that
this does not work well for large files. The open issue
#75288 reports a roughly
200-second debug build for a single 165 KB resource, with larger resources
eventually exceeding the type checker's limits.
As a result, users currently choose between:
.processor.copyplusBundle.module, which uses Foundation and deploys
resources separately from a standalone executable; or.embedInCode, which is Foundation-free and linked into the program, but
exposes individual byte arrays and does not scale to resource trees or large
files.
Proposed embedded file system
The manifest declaration would remain unchanged:
let package = Package(
name: "ExampleServer",
targets: [
.executableTarget(
name: "ExampleServer",
resources: [
.embedInCode("Web")
]
)
]
)
Given this target layout:
Sources/ExampleServer/
βββ main.swift
βββ Web/
βββ index.html
βββ css/site.css
βββ images/logo.svg
SwiftPM would preserve the target-relative logical paths and generate a
module-scoped accessor provisionally spelled:
let resources = PackageResources.fileSystem
guard let index = resources.file(at: "Web/index.html") else {
// Missing resource
}
for entry in resources.entries(in: "Web/images") {
print(entry.name)
}
The exact API needs discussion, but the intended minimum functionality is:
- look up an entry by its logical path;
- distinguish files and directories;
- access a file's bytes without Foundation;
- list the immediate entries of a directory; and
- use the same path syntax and behavior on every supported platform.
An illustrative APIβnot yet a detailed proposalβcould look like this:
struct PackageResources {
static let fileSystem: EmbeddedFileSystem
struct EmbeddedFileSystem: Sendable {
func entry(at path: String) -> Entry?
func file(at path: String) -> File?
func entries(in directory: String = "") -> [Entry]
}
struct Entry: Sendable {
enum Kind: Sendable {
case file
case directory
}
let path: String
let name: String
let kind: Kind
}
struct File: RandomAccessCollection, Sendable {
typealias Element = UInt8
// Collection requirements omitted from this sketch.
}
}
Nesting the generated types under PackageResources is only one possible way
to avoid adding a runtime module or new standard-library API. The spelling and
ownership of these types are important questions for the pitch.
Like Bundle.module, the generated API would initially be internal to the
target. A library author who wants clients to access its embedded resources can
deliberately expose an appropriate public API.
Logical path semantics
Embedded resource paths describe a virtual tree, not locations in the host
filesystem. I propose that they therefore have platform-independent semantics:
- paths are relative and use
/as the separator on every platform; - matching is case-sensitive;
- path components are non-empty UTF-8 strings;
- absolute paths and
.,.., NUL, and empty components are rejected; - the empty string identifies the embedded root; and
- directory listing has deterministic ordering.
In particular, this API should not use the host platform's native FilePath
syntax. An embedded path should not change meaning when the same package is
built on Windows rather than Linux or Darwin.
For .embedInCode("Web"), the example above uses Web/index.html, preserving
the target-relative path that was declared. Whether the declaration root itself
should be retained is one point on which I would particularly welcome feedback.
This model is broadly inspired by Go's
embed.FS, whose
embedded paths are slash-separated and independent of the host platform. This
pitch does not propose adopting Go's source-directive syntax.
Storage and build behavior
The public API should not expose an object-file layout.
One possible implementation is a raw byte blob plus a compact, sorted index
containing logical paths, entry kinds, offsets, and lengths. The bytes would be
placed directly in an object file or read-only object section rather than
written as Swift source. The generated Swift source would then contain only the
small accessor and metadata needed to locate that data.
Conceptually:
linked image
βββββββββββββββββββββββββββββββ
β program code β
βββββββββββββββββββββββββββββββ€
β path β kind/offset/length β
βββββββββββββββββββββββββββββββ€
β raw resource bytes β
βββββββββββββββββββββββββββββββ
SwiftPM would be free to use different mechanisms for Mach-O, ELF, COFF, and
WASM, or a portable fallback where necessary. Section names, generated symbols,
alignment, and the number of blobs would remain implementation details.
Directly embedding bytes is not required for the filesystem semantics, but it
is important to making the existing .embedInCode rule practical for realistic
resource trees. It also avoids making Swift parser and type-checker performance
proportional to the textual decimal representation of every resource byte.
The representation improvement is separable from the proposed filesystem API:
it could be implemented independently as an optimization of today's generated
properties. The evolution proposal only needs to standardize user-visible
semantics, not a particular linker technique.
The byte-access API still needs a deliberate design. A copied [UInt8] is
simple and compatible with today's generated properties, while a collection or
scoped Span/RawSpan view could permit zero-copy access. The final proposal
must specify ownership, lifetime, bounds checking, and Sendable behavior
without exposing unsafe pointers as the primary interface.
Relationship to existing resource APIs
This is intended to complement, not replace, Bundle.module:
.processremains appropriate for localized or platform-processed resources;.copyremains appropriate when resources should be deployed as files;.embedInCoderemains appropriate for immutable raw bytes that should be
linked with the program.
The new EmbeddedFileSystem accessor would be generated when a target contains one or
more .embedInCode resources.
Existing generated PackageResources.<mangledName>: [UInt8] properties could
remain available for source compatibility. Their implementation could load
from the same embedded blob instead of containing a Swift array literal.
Resources with duplicate basenames in different directoriesβwhich the current
property model cannot representβwould still be addressable by full logical
path through the filesystem accessor.
Any new generated API or changed path behavior would be gated by a new Swift
tools version.
Why SwiftPM rather than a package plugin?
A package plugin could generate a resource table, but each implementation would
need to reproduce SwiftPM's target resource discovery, dependency tracking,
linker integration, and platform-specific object generation. It would also add
a plugin and generator dependency to every adopting package.
SwiftPM already owns the .embedInCode rule and knows the complete set of
target resources. Providing the generated accessor there gives packages one
consistent behavior across command-line SwiftPM and build-system integrations.
Security and reproducibility
The build should reject logical paths that could escape the resource root and
should diagnose duplicate logical paths. The implementation must validate
offset and length metadata before exposing a byte view and avoid integer
overflow for large resources.
Generated output should be deterministic. Only logical names, file/directory
kind, offsets, lengths, and file contents need to be represented. Source
timestamps, permissions, extended attributes, and absolute build-machine paths
should not be embedded.
Symlink handling needs an explicit rule. My initial preference is to reject
symlinked embedded resources so that a resource declaration cannot
unintentionally capture content outside its package, but I would welcome input
on compatibility with existing SwiftPM resource behavior.
Alternatives considered
Continue generating individual [UInt8] properties
This is simple for callers embedding one small file, but it does not model a
directory and generates very large Swift expressions. Improving array-literal
type checking may reduce compilation time but would not provide path-preserving
lookup or avoid generating source proportional to the resource size.
Use only Bundle.module
Bundle.module is the right abstraction for many application resources, but it
requires Foundation and generally leaves a resource directory or bundle to be
deployed with the executable. It does not satisfy Foundation-free or
single-linked-artifact use cases.
Introduce a new resource rule
A spelling such as .embedFileSystem("Web") would make the new behavior
explicit, but it would overlap heavily with the existing meaning of
.embedInCode. My preference is to complete the existing rule rather than add
a second way to request that the same bytes be linked into the program.
Introduce a general filesystem protocol now
A shared read-only protocol could eventually make embedded resources,
Bundle, and on-disk directories interchangeable. However, such a protocol
needs an owning runtime module and could expand the proposal into Foundation or
standard-library evolution. This pitch keeps the first step focused on
SwiftPM-generated embedded resources while leaving that abstraction as a
future direction.
Questions for discussion
I would particularly appreciate feedback on these points:
- Is
PackageResources.fileSystema suitable generated entry point, and should
its concrete supporting types be nested underPackageResources? - Should
.embedInCode("Web")exposeWeb/index.html, as proposed here, or
mount the contents ofWebdirectly at the embedded root? - What should the primary byte-access API be: a collection value, a scoped
span, or an owned[UInt8]? - Are exact lookup, file/directory metadata, whole-file byte access, and
immediate directory listing the right minimum filesystem surface? - Should existing generated per-file properties remain indefinitely, or be
considered for later deprecation after the filesystem accessor is available? - Are there build-system or object-format constraints that make a blob plus
sorted path index unsuitable on any SwiftPM-supported platform? - Should symlinks always be rejected for embedded resources?
If the overall direction is sound, I plan to turn the result of this discussion
into a SwiftPM evolution proposal and a focused implementation prototype.