1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
// Build script for bun_uws_sys: compiles the uSockets C library (libusockets)
// using the `cc` crate. This provides real us_socket_* / us_socket_group_*
// symbols, replacing the no-op stubs in bao_native_stubs.
//
// Two compilation modes:
// 1. Plain TCP (default): compiles C sources without BoringSSL, links
// crypto/ssl_stubs.c for us_internal_ssl_* no-ops.
// 2. With TLS (future, Wave 74-TLS): define BAO_UWS_WITH_TLS, compile
// crypto/openssl.c, link BoringSSL.
use std::env;
use std::path::PathBuf;
fn main() {
let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
// W0b publish incorporation: all C/C++ sources are vendored in-package
// under csrc/ (byte-identical copies of packages/bun-usockets/src,
// packages/bun-uws/src, vendor/lsquic public + internal headers,
// vendor/lsqpack lsxpack_header.h, vendor/lshpack headers, and the
// vendor/boringssl include tree). A crates.io package can only ship
// in-package files, so the published crate carries its own source set and
// local builds compile the exact same bytes from csrc/. Keep csrc/ in
// sync when absorbing upstream changes.
let csrc_dir = crate_dir.join("csrc");
let usockets_dir = csrc_dir.join("bun-usockets");
let usockets_src = usockets_dir.join("src");
let with_tls = env::var("BAO_UWS_WITH_TLS").as_deref() != Ok("0");
// ── Platform matrix (TARGET, never host) ─────────────────────────────
// `#[cfg(target_os = ...)]` in a build script describes the HOST running
// cargo, not the artifact being produced. Cross builds (linux host →
// aarch64-apple-darwin) must select the eventing backend, the system
// root-cert loader TU, and the socket descriptor shape for CARGO_CFG_
// TARGET_OS, or the compiled C and the Rust FFI disagree.
//
// The matrix mirrors the dispatch the vendored C sources already apply
// to themselves, so there is exactly one truth per platform:
// - libusockets.h "default eventing" block: _WIN32 → LIBUS_USE_LIBUV,
// __APPLE__ || __FreeBSD__ → LIBUS_USE_KQUEUE, else → LIBUS_USE_EPOLL.
// epoll_kqueue.c carries both the epoll and kqueue halves behind
// those macros; eventing/libuv.c is the windows-only backend.
// - root_certs.cpp call site: __APPLE__ → us_load_system_certificates_
// macos (root_certs_darwin.cpp, dlopen'd Security framework — no
// link-time framework dependency), _WIN32 → the windows loader
// (root_certs_windows.cpp; root_certs.cpp defines the STACK_OF(X509)
// wrapper itself and calls ..._windows_raw from that TU), else →
// root_certs_linux.cpp.
// - libusockets.h / bsd.h already default LIBUS_SOCKET_DESCRIPTOR /
// LIBUS_SOCKET_ERROR to (SOCKET, INVALID_SOCKET) under _WIN32 and
// (int, -1) elsewhere; the explicit -D flags below are kept only on
// non-windows (identical to the header defaults, preserves the
// historical flag set) and MUST NOT be forced on windows, where
// SOCKET is UINT_PTR and defining int would break the ABI.
//
// windows note: eventing/libuv.c pulls <uv.h> via internal/eventing/
// libuv.h, and so does libuwsockets.cpp (it includes internal/internal.h
// directly). The libuv public include face is vendored under
// bun-usockets/src/deps/libuv/include (uv 1.51.0, the _WIN32 closure of
// uv.h; byte-identical mirror in packages/bun-usockets) and wired into
// both builds below — headers only, no libuv sources: every uv_* symbol
// is supplied at link time by bun_libuv_sys (cfg(windows) dependency of
// this crate), whose #[repr(C)] mirrors target the same 1.51.0.
let target_os = env::var("CARGO_CFG_TARGET_OS")
.expect("CARGO_CFG_TARGET_OS must be set by cargo for build scripts");
let (eventing_macro, eventing_src, root_certs_src): (&str, PathBuf, PathBuf) =
match target_os.as_str() {
"linux" => (
"LIBUS_USE_EPOLL",
usockets_src.join("eventing/epoll_kqueue.c"),
usockets_src.join("crypto/root_certs_linux.cpp"),
),
"macos" | "ios" => (
"LIBUS_USE_KQUEUE",
usockets_src.join("eventing/epoll_kqueue.c"),
usockets_src.join("crypto/root_certs_darwin.cpp"),
),
"freebsd" => (
"LIBUS_USE_KQUEUE",
usockets_src.join("eventing/epoll_kqueue.c"),
usockets_src.join("crypto/root_certs_linux.cpp"),
),
"windows" => {
// Fail-closed (issue #34): the windows eventing backend is
// LIBUS_USE_LIBUV — eventing/libuv.c and libuwsockets.cpp are
// compiled against libuv's uv_* API, but this workspace
// vendors only the <uv.h> include face
// (csrc/bun-usockets/src/deps/libuv/include) and compiles no
// libuv object code anywhere. bun_libuv_sys — the
// cfg(windows) dependency this crate's manifest points at for
// the supply — is a pure FFI declaration crate: it compiles
// no C and supplies no uv_* symbols. Upstream Bun satisfies
// uv_* from its C++/CMake build; that supply path has not
// been ported to Bao, so a windows build would end in
// undefined uv_* symbols at link time. Refuse before any C
// is compiled instead of surfacing an opaque linker error.
// Closing the supply (vendor libuv or port the CMake path) is
// a separate wave — issue #34; platform matrix:
// docs/platform-support.md.
eprintln!(
"error: bun_uws_sys is not buildable for windows targets (fail-closed; issue #34)
The windows eventing backend (LIBUS_USE_LIBUV) compiles eventing/libuv.c and
libuwsockets.cpp against libuv's uv_* API, but only the <uv.h> include face is
vendored (csrc/bun-usockets/src/deps/libuv/include) and no libuv object code is
compiled anywhere in this workspace: bun_libuv_sys (the cfg(windows) dependency
nominally supplying the symbols) is a pure FFI declaration crate. The upstream
Bun uv_* supply path (C++/CMake) has not been ported, so linking would fail with
undefined uv_* symbols.
To close the gap: vendor libuv or port the CMake supply path (issue #34).
Platform support matrix: docs/platform-support.md"
);
std::process::exit(1);
}
other => panic!("unsupported target OS for bun_uws_sys: {other}"),
};
let non_windows_socket_shape = target_os != "windows";
// windows: <uv.h> include face consumed by eventing/libuv.c (C build) and
// by libuwsockets.cpp via internal/internal.h → internal/eventing/libuv.h
// (C++ build). See the platform-matrix note above for provenance.
let libuv_include = usockets_src.join("deps").join("libuv").join("include");
let is_windows = target_os == "windows";
// ── C compilation: uSockets core ──────────────────────────────────────
let mut c_build = cc::Build::new();
// Use clang: the uSockets C sources use __attribute__((always_inline))
// on static functions, which is incompatible with GCC + -fPIC.
// Bun's upstream build uses clang exclusively. A per-target CC env (cross
// toolchain, issue #10 musl wave) must still override it: cc-rs honors
// .compiler() before its own env chain (cc 1.2.x get_base_compiler
// early-return), so probe the env here — clang is only the no-cross-env
// fallback (host builds unchanged).
c_build.compiler(env_cc("CC").unwrap_or_else(|| "clang".into()));
// Compiler flags
c_build
.opt_level(1) // -O1: always_inline requires optimization
.flag("-DBUN_DEBUG=1") // makes nonnull_arg/nonnull_fn_decl expand to empty
.flag(format!("-D{}=1", eventing_macro))
.flag("-DLIBUS_MAX_READY_POLLS=1024")
.flag("-DLIBUS_EXT_ALIGNMENT=16");
if non_windows_socket_shape {
c_build
.flag("-DLIBUS_SOCKET_DESCRIPTOR=int")
.flag("-DLIBUS_SOCKET_ERROR=-1");
}
// GCC compat: __has_feature is Clang-only. Define it as 0 via a wrapper
// flag. We use a separate .h file to define it as a function-like macro.
let wrapper_h = crate_dir.join("src").join("_gcc_compat.h");
if wrapper_h.exists() {
c_build.flag(format!("-include{}", wrapper_h.display()));
}
if with_tls {
c_build
.flag("-DLIBUS_USE_OPENSSL=1")
.flag("-DLIBUS_USE_BORINGSSL=1")
.flag("-DWITH_BORINGSSL=1");
}
// Include paths
let lsquic_dir = csrc_dir.join("deps").join("lsquic");
let lsqpack_dir = csrc_dir.join("deps").join("lsqpack");
let lshpack_dir = csrc_dir.join("deps").join("lshpack");
c_build
.include(&usockets_dir) // for #include "libusockets.h"
.include(&usockets_src) // for #include "internal/internal.h"
.include(usockets_src.join("internal")) // for internal/ sub-includes
.include(usockets_src.join("internal/networking")) // for bsd.h
.include(lsquic_dir.join("include")) // for #include "lsquic.h" (quic.c)
.include(lsquic_dir.join("src").join("liblsquic")) // for lsquic internal headers
.include(&lsqpack_dir) // for #include "lsxpack_header.h" (quic.c)
.include(&lshpack_dir); // for #include "lshpack.h" (quic.c → lsxpack)
if is_windows {
c_build.include(&libuv_include); // for #include <uv.h> (eventing/libuv.c)
}
// C source files (uSockets core — platform-independent)
let core_sources = [
"bsd.c",
"context.c",
"loop.c",
"socket.c",
"udp.c",
"quic.c",
];
for src in &core_sources {
let path = usockets_src.join(src);
if path.exists() {
c_build.file(&path);
} else {
panic!("uSockets source file not found: {:?}", path);
}
}
// Platform-specific eventing backend (epoll / kqueue halves both live in
// epoll_kqueue.c; windows uses the libuv backend — see platform matrix)
c_build.file(&eventing_src);
// SSL: stubs or real OpenSSL
if with_tls {
c_build.file(usockets_src.join("crypto/openssl.c"));
let boringssl_dir = csrc_dir.join("boringssl");
c_build.include(boringssl_dir.join("include"));
} else {
c_build.file(usockets_src.join("crypto/ssl_stubs.c"));
}
// Skip QUIC and UDP for now (not needed for P1-B HTTP server)
// (eventing backend is per-target — see the platform matrix above)
c_build.compile("usockets");
// ── C++ compilation: TLS crypto helpers (sni_tree + root_certs) ────────
// These are C++ files that openssl.c calls into; compiled separately
// because the main uSockets build uses the C compiler.
if with_tls {
let boringssl_dir = csrc_dir.join("boringssl");
let mut tls_cpp = cc::Build::new();
// Env-first per-target probe (see the C build note above); clang++ is
// only the no-cross-env fallback.
tls_cpp.compiler(env_cc("CXX").unwrap_or_else(|| "clang++".into()));
tls_cpp.cpp(true);
tls_cpp.opt_level(1);
tls_cpp
.flag("-std=c++17")
.flag("-fno-exceptions")
.flag("-fno-rtti")
.flag("-DBORINGSSL_IMPLEMENTATION=1")
.include(boringssl_dir.join("include"))
.include(&usockets_dir)
.include(&usockets_src)
.include(usockets_src.join("internal"));
tls_cpp.file(usockets_src.join("crypto/sni_tree.cpp"));
tls_cpp.file(usockets_src.join("crypto/root_certs.cpp"));
// Platform-specific system certificate loading (darwin dlopens the
// Security framework at runtime, so no link-time framework is needed)
tls_cpp.file(&root_certs_src);
tls_cpp.compile("usockets_tls");
}
// ── C++ compilation: uWS C-ABI wrapper (libuwsockets.cpp) ────────────
// Provides uws_app_*, uws_res_*, uws_req_* symbols that Rust FFI calls.
let uws_dir = csrc_dir.join("bun-uws");
let uws_src = uws_dir.join("src");
let mut cpp_build = cc::Build::new();
// Env-first per-target probe (see the C build note above); clang++ is
// only the no-cross-env fallback.
cpp_build.compiler(env_cc("CXX").unwrap_or_else(|| "clang++".into()));
cpp_build.cpp(true);
cpp_build.opt_level(1);
cpp_build
.flag("-std=c++20")
.flag("-DBUN_DEBUG=1")
.flag(format!("-D{}=1", eventing_macro))
.flag("-DLIBUS_MAX_READY_POLLS=1024")
.flag("-DLIBUS_EXT_ALIGNMENT=16")
.flag("-fno-exceptions") // uWS is compiled without exceptions
.flag("-Wno-deprecated-declarations");
// Must mirror the C core build: libusockets.h shapes us_loop_t / poll
// layout by the eventing macro, so a C↔C++ macro mismatch is an ABI
// break, not a warning. Same rule as above for the socket shape flags.
if non_windows_socket_shape {
cpp_build
.flag("-DLIBUS_SOCKET_DESCRIPTOR=int")
.flag("-DLIBUS_SOCKET_ERROR=-1");
}
// GCC compat wrapper
if wrapper_h.exists() {
cpp_build.flag(format!("-include{}", wrapper_h.display()));
}
if with_tls {
cpp_build
.flag("-DLIBUS_USE_OPENSSL=1")
.flag("-DLIBUS_USE_BORINGSSL=1")
.flag("-DWITH_BORINGSSL=1");
let boringssl_dir = csrc_dir.join("boringssl");
cpp_build.include(boringssl_dir.join("include"));
}
// Include paths for uWS C++ headers + uSockets internals
cpp_build
.include(&csrc_dir) // for #include <bun-uws/src/App.h>
.include(&uws_dir) // for #include "App.h" via bun-uws/src/
.include(&uws_src) // for #include "App.h" etc.
.include(&usockets_dir) // for #include "libusockets.h"
.include(&usockets_src) // for #include "internal/internal.h"
.include(usockets_src.join("internal"))
.include(usockets_src.join("internal/networking"))
.include(&crate_dir) // for #include "_libusockets.h"
.include(crate_dir.join("src")); // for #include <wtf/Assertions.h>
if is_windows {
// internal/internal.h → internal/eventing/libuv.h → <uv.h>
cpp_build.include(&libuv_include);
}
cpp_build.file(crate_dir.join("libuwsockets.cpp"));
cpp_build.compile("uwsockets");
// ── Link dependencies ─────────────────────────────────────────────────
// pthread is needed for bsd.c (pthread_atfork in some code paths)
println!("cargo:rustc-link-lib=pthread");
// zlib for HTTP content-encoding (gzip/deflate) in libuwsockets.cpp
println!("cargo:rustc-link-lib=z");
// libdeflate for fast compression/decompression in libuwsockets.cpp
println!("cargo:rustc-link-lib=deflate");
// SPEC (CLAUDE.md L13): libuwsockets.a (C++ wrapper) depends on libusockets.a
// (C core). For static archives, the linker resolves undefined symbols only
// from libraries listed AFTER the reference. cc::compile emits
// `cargo:rustc-link-lib=static=usockets` BEFORE
// `cargo:rustc-link-lib=static=uwsockets`, which puts usockets first in the
// link line — but uwsockets (compiled later) references symbols in usockets,
// so usockets must come AFTER uwsockets. Re-declare usockets LAST to fix the
// order (Cargo dedupes link libs in dep-graph order, so this becomes the
// effective position).
println!("cargo:rustc-link-lib=static=usockets");
// ── Rebuild hints ─────────────────────────────────────────────────────
// Rebuild if any C source changes
println!("cargo:rerun-if-changed={}", usockets_src.join("bsd.c").display());
println!("cargo:rerun-if-changed={}", usockets_src.join("context.c").display());
println!("cargo:rerun-if-changed={}", usockets_src.join("loop.c").display());
println!("cargo:rerun-if-changed={}", usockets_src.join("socket.c").display());
println!("cargo:rerun-if-changed={}", usockets_src.join("udp.c").display());
println!("cargo:rerun-if-changed={}", usockets_src.join("crypto/ssl_stubs.c").display());
println!("cargo:rerun-if-changed={}", eventing_src.display());
if with_tls {
println!("cargo:rerun-if-changed={}", root_certs_src.display());
// openssl.c is the TLS core TU (compiled only under with_tls);
// without this entry, touching it silently keeps the stale object.
println!("cargo:rerun-if-changed={}", usockets_src.join("crypto/openssl.c").display());
// Same rule for the two C++ TUs linked into usockets_tls.
println!("cargo:rerun-if-changed={}", usockets_src.join("crypto/sni_tree.cpp").display());
println!("cargo:rerun-if-changed={}", usockets_src.join("crypto/root_certs.cpp").display());
}
println!("cargo:rerun-if-changed={}", usockets_src.join("internal/internal.h").display());
println!("cargo:rerun-if-changed={}", usockets_src.join("libusockets.h").display());
if is_windows {
// Vendored libuv include face (windows-only consumer: eventing/libuv.c
// and the C++ wrapper TU). Directory path → cargo watches recursively.
println!("cargo:rerun-if-changed={}", libuv_include.display());
}
// ── Rebuild hints: C++ wrapper TU ─────────────────────────────────────
// libuwsockets.cpp is one translation unit that #includes the uWS headers
// (App.h → HttpContext.h → HttpParser.h → HttpContextData.h …), so an edit
// to any of them must rerun this script. `cc` only emits rerun-if-changed
// for the compiled .cpp file itself, never for headers — without these
// lines a header-only change (e.g. an absorbed upstream parser fix)
// silently links the stale archive.
println!("cargo:rerun-if-changed={}", crate_dir.join("libuwsockets.cpp").display());
println!("cargo:rerun-if-changed={}", crate_dir.join("libuwsockets_h3.cpp").display());
println!("cargo:rerun-if-changed={}", crate_dir.join("_libusockets.h").display());
if let Ok(entries) = std::fs::read_dir(&uws_src) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().map_or(false, |ext| ext == "h" || ext == "cpp") {
println!("cargo:rerun-if-changed={}", path.display());
}
}
}
// wtf/ helpers are included as <wtf/*.h> from the crate's src dir.
if let Ok(entries) = std::fs::read_dir(crate_dir.join("src").join("wtf")) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().map_or(false, |ext| ext == "h") {
println!("cargo:rerun-if-changed={}", path.display());
}
}
}
}
// cc-rs-compatible per-target compiler probe, mirroring cc's own resolution
// order: {key}_{target-hyphen} → {key}_{target-underscore} → TARGET_{key} →
// {key}. An empty value counts as unset; no hit at all means the caller's
// hardcoded fallback applies (host builds keep the historical toolchain).
fn env_cc(key: &str) -> Option<std::ffi::OsString> {
let target = env::var("TARGET").ok()?;
let underscored = target.replace('-', "_");
[
format!("{key}_{target}"),
format!("{key}_{underscored}"),
format!("TARGET_{key}"),
key.to_string(),
]
.into_iter()
.find_map(|name| env::var_os(name).filter(|v| !v.is_empty()))
}