bun_uws_sys 0.1.0

A Rust-native programmable browser runtime built on Servo and SpiderMonkey
// 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");

    // ── 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.
    c_build.compiler("clang");

    // 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("-DLIBUS_USE_EPOLL=1")
        .flag("-DLIBUS_MAX_READY_POLLS=1024")
        .flag("-DLIBUS_SOCKET_DESCRIPTOR=int")
        .flag("-DLIBUS_SOCKET_ERROR=-1")
        .flag("-DLIBUS_EXT_ALIGNMENT=16");

    // 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)

    // 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
    #[cfg(target_os = "linux")]
    {
        c_build.file(usockets_src.join("eventing/epoll_kqueue.c"));
    }

    #[cfg(target_os = "macos")]
    {
        c_build.file(usockets_src.join("eventing/epoll_kqueue.c"));
    }

    // 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)
    // Skip libuv eventing backend (we use epoll/kqueue)

    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();
        tls_cpp.compiler("clang++");
        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
        #[cfg(target_os = "linux")]
        {
            tls_cpp.file(usockets_src.join("crypto/root_certs_linux.cpp"));
        }
        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();
    cpp_build.compiler("clang++");
    cpp_build.cpp(true);
    cpp_build.opt_level(1);
    cpp_build
        .flag("-std=c++20")
        .flag("-DBUN_DEBUG=1")
        .flag("-DLIBUS_USE_EPOLL=1")
        .flag("-DLIBUS_MAX_READY_POLLS=1024")
        .flag("-DLIBUS_SOCKET_DESCRIPTOR=int")
        .flag("-DLIBUS_SOCKET_ERROR=-1")
        .flag("-DLIBUS_EXT_ALIGNMENT=16")
        .flag("-fno-exceptions")          // uWS is compiled without exceptions
        .flag("-Wno-deprecated-declarations");

    // 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>

    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={}", usockets_src.join("eventing/epoll_kqueue.c").display());
    println!("cargo:rerun-if-changed={}", usockets_src.join("internal/internal.h").display());
    println!("cargo:rerun-if-changed={}", usockets_src.join("libusockets.h").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());
            }
        }
    }
}