<!--
{
  "documentType" : "article",
  "framework" : "Event",
  "identifier" : "/documentation/Event/EmbeddingCLibrariesInSwiftPM",
  "metadataVersion" : "0.1.0",
  "role" : "article",
  "title" : "Embedding a C Library in Swift Package Manager"
}
-->

# Embedding a C Library in Swift Package Manager

How to make a C library consumable from Swift Package Manager — using libevent and [`Event`](/documentation/Event) as the worked example, with the module-map and product-re-export patterns extracted for your own ports.

## Overview

If you’ve tried to bring a non-trivial C library — libevent, libssh, libuv, OpenSSL, libtor — into a Swift Package Manager target, you’ve hit the same questions every time:

1. Where do the headers go?
1. How do you write a `module.modulemap` so Swift sees the C symbols?
1. How do you let *other* SwiftPM packages link against the C library through yours, without duplicating the build?
1. What about C++ consumers that can’t parse the headers as a Clang module?

This article documents the pattern this package uses for libevent, generalised for your own ports. The pattern is also the proof for [`swift-tor`](https://github.com/21-DOT-DEV/swift-tor), which depends on `libevent` (from this package) alongside `libcrypto` / `libssl` (from [`swift-openssl`](https://github.com/21-DOT-DEV/swift-openssl)) to build a Swift-native Tor daemon — without bundling its own libevent build.

### The shape of the problem

A C library you want to wrap typically has this layout in its upstream source tree:

```
libfoo/
├── include/
│   ├── foo.h
│   └── foo/
│       ├── core.h
│       ├── ext.h
│       └── ...
├── src/
│   ├── core.c
│   ├── ext.c
│   └── ...
└── configure.ac          // or CMakeLists.txt — autotools / cmake build
```

SwiftPM doesn’t run `configure` or `cmake`. To consume the library from Swift, you need to:

- Place the headers somewhere SwiftPM expects (`Sources/<target>/include/`).
- Place the source files somewhere SwiftPM compiles (`Sources/<target>/`).
- Write a `Package.swift` `target` declaration that tells SwiftPM how to compile the sources and what flags / defines to pass.
- (For non-trivial libraries) write a manual `module.modulemap` that tells Clang which headers form the public module surface.

### Step 1 — Vendoring the upstream sources

The cleanest pattern is to copy or `git subtree` the upstream `include/` and `src/` directories into your SwiftPM target:

```
swift-libfoo/
├── Package.swift
└── Sources/
    └── libfoo/                  // SwiftPM target name
        ├── include/
        │   ├── foo.h
        │   ├── foo/
        │   │   ├── core.h
        │   │   └── ext.h
        │   └── module.modulemap   // ← we'll write this
        └── src/
            ├── core.c
            └── ext.c
```

This package uses a `git subtree` extraction (driven by `subtree.yaml`) so that running the extraction script pulls fresh upstream sources from the libevent repo into `Sources/libevent/`. See `subtree.yaml` and the `Vendor/AGENTS.md` notes for the exact configuration.

For a one-off vendor, plain `cp -R` works.

> Important: **Do not edit the vendored files in place.** Any patches you need go in a separate “manually maintained” file list (this package keeps the list in `Vendor/AGENTS.md`) so re-extraction doesn’t silently overwrite your changes. Forgetting this rule is the single most common way to lose patches across upstream-sync cycles.

### Step 2 — `Package.swift` for the C target

```swift
.target(
    name: "libfoo",
    exclude: [
        // Sources you don't want compiled (e.g., platform-specific
        // implementations that conflict with the host's libc):
        "src/arc4random.c",
    ],
    cSettings: [
        // Pass any defines the upstream build system would have set:
        .define("_GNU_SOURCE", .when(platforms: [.linux])),
        .define("HAVE_CONFIG_H"),
    ]
)
```

Two non-obvious points:

- **`exclude:` lets you skip files** that conflict with the host platform — for example, libevent ships its own `arc4random.c` that collides with glibc 2.36+’s `arc4random_buf` definition. This package excludes that file and lets the bundled getrandom() fallback handle randomness on Linux.
- **`cSettings` `.define` declarations** stand in for what `./configure` would have written into `config.h`. For libevent, this means `event-config.h` is shipped as a hand-maintained file (see `Sources/libevent/include/event2/event-config.h`) rather than being generated at build time. The trade-off: you lose autoconf’s per-host detection, and you have to update `event-config.h` when the host environment changes (e.g., the recent glibc 2.36 arc4random handling).

### Step 3 — The module map

**Use a hand-written shim header listed in the modulemap; do NOT use `umbrella "."`.** When SwiftPM sees a `module.modulemap` inside `Sources/<target>/include/`, it uses *your* map and skips its automatic generation. The recommended pattern for a non-trivial C library is:

```
module libfoo {
    requires !cplusplus
    header "swift-shim.h"
    export *
}
```

…paired with `Sources/<target>/include/swift-shim.h`:

```c
#ifndef LIBFOO_SWIFT_SHIM_H
#define LIBFOO_SWIFT_SHIM_H
#include "foo.h"
#include "foo/core.h"
#include "foo/ext.h"
/* …every public header you want Swift consumers to see… */
#endif
```

What each line does:

- **`requires !cplusplus`** — Clang skips this module when the consumer is C++ (i.e. `__cplusplus` is defined). The decisive reason is downstream **Swift C++ interop**: when a consumer compiles C++ with `-cxx-interoperability-mode=default`, Clang is invoked with `-fmodules` forced on, and `requires !cplusplus` keeps libfoo from being loaded as a module in C++ TUs. The module is still available to Swift and plain-C consumers.
- **`header "swift-shim.h"`** — claims a single shim header for the module. The shim textually `#include`s every public C header you want Swift to see; transitive includes propagate symbols into Swift’s view. **Only the shim itself is owned by the module — none of the underlying `foo/*.h` headers are claimed.** That’s the critical property: a downstream C++ consumer doing `#include <foo/core.h>` via the `-I` path resolves to a header Clang considers unclaimed, so it textually includes it instead of attempting a module load that would fail the `requires !cplusplus` clause.
- **`export *`** — re-exports every imported symbol so Swift consumers don’t need to qualify with submodule prefixes.

> Warning: **Do not use `umbrella "."`.** It looks tempting because it auto-includes every header in the directory, but it has a fatal interaction with Swift C++ interop: `umbrella "."` claims every `foo/*.h` as part of the libfoo module, so a C++ TU doing `#include <foo/core.h>` triggers Clang to load the module, evaluate `requires !cplusplus`, fail, and emit `error: module 'libfoo' is incompatible with feature 'cplusplus'` instead of falling through to a textual include. This is exactly the regression that broke swift-bitcoin in swift-event 0.2.0; the shim-header pattern was introduced to fix it. The `Examples/LinuxConsumerProbe/` package in this repo (built by every CI run) is the regression coverage that catches it.

If your library’s public API is reachable through one umbrella header AND no downstream consumer uses Swift C++ interop, you can use the simpler form:

```
module libfoo {
    requires !cplusplus
    umbrella header "foo.h"
    export *
}
```

…but `umbrella header "foo.h"` claims every header transitively reachable from `foo.h`, which is the same trap as `umbrella "."` for C++ consumers. The shim-header form is the safe default for a library you expect anyone to consume from a C++-interop Swift project.

### Step 4 — Exposing the C target as a SwiftPM product

To let other Swift packages depend on your C library directly (not through your Swift wrapper), declare it as a product in `Package.swift`:

```swift
let package = Package(
    name: "swift-libfoo",
    products: [
        .library(name: "libfoo", targets: ["libfoo"]),   // ← raw C bindings
        .library(name: "Foo",    targets: ["Foo"]),       // ← idiomatic Swift API
    ],
    targets: [
        .target(name: "libfoo", /* ... as above ... */),
        .target(name: "Foo", dependencies: ["libfoo"]),
    ]
)
```

A downstream package can then depend on either:

```swift
// Downstream Package.swift
dependencies: [
    .package(url: "https://github.com/you/swift-libfoo.git", branch: "main"),
],
targets: [
    .target(
        name: "MyCLibThatNeedsLibfoo",
        dependencies: [
            .product(name: "libfoo", package: "swift-libfoo"),
        ]
    )
]
```

This is the pattern `swift-tor` uses to depend on this package’s `libevent` product:

```swift
// From swift-tor's Package.swift
.target(
    name: "libtor",
    dependencies: [
        .product(name: "libcrypto", package: "swift-openssl"),
        .product(name: "libssl",    package: "swift-openssl"),
        .product(name: "libevent",  package: "swift-event"),
    ]
)
```

The downstream target gets the `libevent` headers on its `-I` path and links the libevent object files transitively. No duplicate libevent build, no source vendoring on the consumer side.

### Step 5 — Test that downstream consumers actually work

The single most useful test you can write for a C-bindings package is “another SwiftPM target can import this and call into it.” This package’s `libeventTests` target does exactly that — the higher-level Swift API ([`EventLoop`](/documentation/Event/EventLoop), [`Socket`](/documentation/Event/Socket), [`ServerSocket`](/documentation/Event/ServerSocket), [`SocketAddress`](/documentation/Event/SocketAddress), [`SocketError`](/documentation/Event/SocketError)) lives in a *separate* target (`Event`) that depends on `libevent`, proving the pattern from the consumer side too:

```swift
import XCTest
@testable import libevent

final class libeventTests: XCTestCase {
    func testExample() throws {
        _ = event()    // Constructs a libevent `event` struct — proves the C
                       // headers are visible and the linker resolved.
    }
}
```

If this test passes, your downstream consumers will be able to import your C product. If it fails (typically with “cannot find ‘X’ in scope” errors), your module map is wrong — usually the shim header is missing an `#include`.

For higher-confidence coverage — particularly for the C++ interop case Step 3 warns about — also build a *separate* SPM package that depends on yours via `path: "../.."` and consumes it from a Swift target with `interoperabilityMode(.Cxx)` over a C++ shim. This package’s `Examples/LinuxConsumerProbe/` is the worked example: it has four targets (pure C++, plain Swift, Swift-with-Cxx-interop over a C++ shim) that together exercise every modulemap regression in one `swift build`. Wiring the probe into both the Linux Dockerfile and the macOS CI job means a regression fails the build before it can ship to consumers.

### Embedded Swift compatibility (forward-looking)

Apple’s [Embedded Swift](https://github.com/apple/swift/blob/main/docs/EmbeddedSwift/UserManual.md) push (WWDC 2024-25) opens a new audience for C-bindings packages: microcontroller and bare-metal Swift code that needs minimal-runtime C interop. Today, the [`Event`](/documentation/Event) Swift API uses `Foundation`, `CheckedContinuation`, and other features that aren’t yet available in Embedded Swift mode — so [`Socket`](/documentation/Event/Socket), [`ServerSocket`](/documentation/Event/ServerSocket), and [`EventLoop`](/documentation/Event/EventLoop) won’t compile under Embedded Swift today. But the `libevent` C product itself has no such dependency and should be consumable from Embedded Swift targets that can build libevent’s own dependency footprint.

This is forward-looking — we have not yet certified swift-event’s `libevent` product against the Embedded Swift toolchain. If you’re trying this, file an issue with the specific build error and we can iterate.

### Common pitfalls

**“Header X not found” inside the C library’s own .c files.** Your `cSettings` is missing a header search path. Add `.headerSearchPath("include/foo")` for any subdirectory the C sources `#include "foo/bar.h"` from.

**Swift consumer can’t see a symbol you expected to be public.** The header that declares it isn’t reachable from your shim. Add it to `swift-shim.h`. Resist the urge to “simplify” by switching to `umbrella "."` — see Step 3’s warning.

**Downstream C++ consumer fails with `module '<libfoo>' is incompatible with feature 'cplusplus'`.** Your modulemap is claiming the C headers (almost always via `umbrella "."` or `umbrella header "foo.h"`). Switch to the shim-header form in Step 3 so the `<foo/...>` headers stay unclaimed and resolve as textual includes via the `-I` path.

**`ambiguous use of 'X'` errors in Swift consumers.** Your library exports a symbol that collides with one from `Darwin` / `Glibc` (e.g., libevent’s `EV_TIMEOUT` collides with kqueue’s `EV_TIMEOUT` constant on Apple platforms). Disambiguate at the call site with module qualification: `libevent.EV_TIMEOUT`. The [`Event`](/documentation/Event) package’s own [`schedule(after:_:)`](/documentation/Event/EventLoop/schedule(after:_:)) implementation does exactly this.

> Warning: Without module qualification, the Swift compiler picks one of the colliding `EV_TIMEOUT` definitions arbitrarily — usually the wrong one. The compile error is loud (`ambiguous use of 'EV_TIMEOUT'`), but a *successful* build with the wrong constant value can produce silent runtime misbehavior (events that never fire, or fire constantly). Always qualify when both modules are imported in the same file.

**`undefined symbol` at link time but compile succeeds.** A `.c` file is excluded from the build (via `exclude:`) or its source isn’t in `Sources/<target>/`. Check `Package.swift`’s `exclude:` list and your file layout.

**Tests pass on macOS but fail on Linux.** Almost always a `_GNU_SOURCE` / `__GLIBC__` issue. Add platform-conditional defines in `cSettings` and check `event-config.h`-style hand-maintained config files for stale assumptions.

## See Also

[Choosing Between Event and libevent](/documentation/Event/ChoosingLibeventVsEvent)

This package ships two library products — which one should you link? This article covers the decision boundary, the canonical consumer example (`swift-tor`), and the stability guarantees that differ between the idiomatic Swift API and the raw C bindings.

[Getting Started with Event in Swift](/documentation/Event/GettingStarted)

A task-oriented walkthrough of the three shipping capabilities in `Event`: inspecting the I/O backend, writing an async TCP client, and writing an async TCP server.

[Backend and Platforms](/documentation/Event/BackendAndPlatforms)

The I/O multiplexer `Event` uses at runtime, the platform matrix it supports, and the backends deliberately excluded from the build.

[`Event`](/documentation/Event)

Async TCP sockets and event-loop primitives for Swift on top of `kqueue` (Apple platforms) or `epoll` (Linux), backed by a vendored build of libevent.

[`EventLoop`](/documentation/Event/EventLoop)

A libevent-backed event loop owning an `event_base` for async I/O dispatch.

[`Socket`](/documentation/Event/Socket)

An async non-blocking TCP socket backed by libevent.

[`ServerSocket`](/documentation/Event/ServerSocket)

A listening TCP server socket built on a non-blocking file descriptor and an [`EventLoop`](/documentation/Event/EventLoop).

[`schedule(after:_:)`](/documentation/Event/EventLoop/schedule(after:_:))

Schedules `work` to run once after `duration` elapses, then returns immediately.

  [`swift-tor`](https://github.com/21-DOT-DEV/swift-tor)

  [Apple SwiftPM documentation: C language targets](https://developer.apple.com/documentation/packagedescription/target)

  [Clang Modules specification](https://clang.llvm.org/docs/Modules.html)

