From 36ac682c281c203efd458b10c4e6ed227b35dbb8 Mon Sep 17 00:00:00 2001 From: Logan Gatlin Date: Mon, 29 Jun 2026 11:20:55 -0500 Subject: [PATCH 1/3] link with local --- README.md | 25 +++++++++++++++++++++++++ build.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/README.md b/README.md index 2b5e2ba..bd99e4b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,31 @@ Enabling this feature and `std` would provide you both `Serialize` and `Deserial **libcpp:** Build `ada-url` with `libc++`. This feature is disabled by default. Enabling this feature without `libc++` installed would cause compile error. +### Linking against an external ada + +By default the build script compiles the bundled C++ `ada` sources and links +them statically. If ada is already provided by your host build system (e.g. +Bazel, CMake, or a distribution package), compiling the bundled copy would +produce a second, duplicate copy of ada in the final binary. The following +build-time environment variables let you skip the bundled build and link +against an external ada instead: + +| Variable | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------- | +| `ADA_USE_SYSTEM_LIB` | If set (non-empty), do not compile the bundled `deps/ada.cpp`. | +| `ADA_LIB_DIR` | Optional directory added to the linker search path (`cargo:rustc-link-search=native`). | +| `ADA_LIB_NAME` | Optional library to link, passed verbatim to `cargo:rustc-link-lib` (e.g. `ada` or `static=ada`). | + +When `ADA_USE_SYSTEM_LIB` is set but neither `ADA_LIB_NAME` nor `ADA_LIB_DIR` +is provided, no link directive is emitted, leaving it to the host build system +to provide the ada symbols at final link time. The external ada must expose the +same C ABI (`ada_*`) and be ABI-compatible with this crate's version. + +```sh +# Link against a system-installed libada in /usr/local/lib +ADA_USE_SYSTEM_LIB=1 ADA_LIB_DIR=/usr/local/lib ADA_LIB_NAME=ada cargo build +``` + ### Performance Ada is fast. The benchmark below shows **3.49 times** faster URL parsing compared to `url` diff --git a/build.rs b/build.rs index 746695e..9f66c69 100644 --- a/build.rs +++ b/build.rs @@ -88,6 +88,38 @@ fn ndk_major_version(ndk_dir: &Path) -> u32 { } fn main() { + // Allow downstream builds to skip compiling the bundled C++ `ada` sources + // and instead link against an externally provided ada library. This is + // useful when ada is already provided by the host build system (e.g. Bazel, + // CMake, or a distro package), where compiling the bundled copy would + // otherwise produce a second, duplicate copy of ada in the final binary. + // + // ADA_USE_SYSTEM_LIB if set (non-empty), do not build the bundled ada. + // ADA_LIB_DIR optional directory added to the link search path. + // ADA_LIB_NAME optional library to link, passed verbatim to + // `cargo:rustc-link-lib` (e.g. `ada` or `static=ada`). + // + // When `ADA_USE_SYSTEM_LIB` is set but neither `ADA_LIB_NAME` nor + // `ADA_LIB_DIR` is provided, no link directive is emitted, leaving it to the + // host build system to provide the ada symbols at final link time. + println!("cargo:rerun-if-env-changed=ADA_USE_SYSTEM_LIB"); + println!("cargo:rerun-if-env-changed=ADA_LIB_DIR"); + println!("cargo:rerun-if-env-changed=ADA_LIB_NAME"); + if env::var_os("ADA_USE_SYSTEM_LIB").is_some_and(|v| !v.is_empty()) { + if let Some(dir) = env::var_os("ADA_LIB_DIR") { + println!( + "cargo:rustc-link-search=native={}", + Path::new(&dir).display() + ); + } + if let Ok(name) = env::var("ADA_LIB_NAME") + && !name.is_empty() + { + println!("cargo:rustc-link-lib={name}"); + } + return; + } + let target_str = env::var("TARGET").unwrap(); let target: Vec = target_str.split('-').map(|s| s.into()).collect(); assert!(target.len() >= 2, "Failed to parse TARGET {}", target_str); From 72b80177b37f2cda7fdee10decd897a76161d91b Mon Sep 17 00:00:00 2001 From: Logan Gatlin Date: Mon, 29 Jun 2026 11:42:41 -0500 Subject: [PATCH 2/3] feature and env --- Cargo.toml | 6 +++++- README.md | 45 +++++++++++++++++++++++++++++---------------- build.rs | 32 ++++++++++++++++++-------------- 3 files changed, 52 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 78d85d3..dbb517a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,11 @@ path = "bench/wpt.rs" harness = false [features] -default = ["std"] +default = ["bundled", "std"] +# compile and statically link the bundled C++ `ada` sources. Enabled by +# default; disable (`default-features = false`) to link an externally provided +# ada instead (see `ADA_LIB_DIR` / `ADA_LIB_NAME` in build.rs). +bundled = [] # pass `cpp_set_stdlib("c++")` to `cc` libcpp = [] # enables serde serialization/deserialization support diff --git a/README.md b/README.md index bd99e4b..eaa21ff 100644 --- a/README.md +++ b/README.md @@ -23,29 +23,42 @@ Enabling this feature and `std` would provide you both `Serialize` and `Deserial **libcpp:** Build `ada-url` with `libc++`. This feature is disabled by default. Enabling this feature without `libc++` installed would cause compile error. +**bundled:** Compile and statically link the bundled C++ `ada` sources. This +feature is **enabled by default**. See "Linking against an external ada" below +for how to disable it. + ### Linking against an external ada -By default the build script compiles the bundled C++ `ada` sources and links -them statically. If ada is already provided by your host build system (e.g. -Bazel, CMake, or a distribution package), compiling the bundled copy would -produce a second, duplicate copy of ada in the final binary. The following -build-time environment variables let you skip the bundled build and link -against an external ada instead: +By default (the `bundled` feature) the build script compiles the bundled C++ +`ada` sources and links them statically. If ada is already provided by your +host build system (e.g. Bazel, CMake, or a distribution package), compiling the +bundled copy would produce a second, duplicate copy of ada in the final binary. + +Disable the `bundled` feature to skip the bundled build and link against an +external ada instead: + +```toml +# Cargo.toml — re-enable `std` since disabling default features also drops it +ada-url = { version = "3", default-features = false, features = ["std"] } +``` + +When `bundled` is disabled, these optional environment variables configure how +the external ada is linked: -| Variable | Description | -| -------------------- | ------------------------------------------------------------------------------------------------------------- | -| `ADA_USE_SYSTEM_LIB` | If set (non-empty), do not compile the bundled `deps/ada.cpp`. | -| `ADA_LIB_DIR` | Optional directory added to the linker search path (`cargo:rustc-link-search=native`). | -| `ADA_LIB_NAME` | Optional library to link, passed verbatim to `cargo:rustc-link-lib` (e.g. `ada` or `static=ada`). | +| Variable | Description | +| ------------- | -------------------------------------------------------------------------------------------------- | +| `ADA_LIB_DIR` | Optional directory added to the linker search path (`cargo:rustc-link-search=native`). | +| `ADA_LIB_NAME`| Optional library to link, passed verbatim to `cargo:rustc-link-lib` (e.g. `ada` or `static=ada`). | -When `ADA_USE_SYSTEM_LIB` is set but neither `ADA_LIB_NAME` nor `ADA_LIB_DIR` -is provided, no link directive is emitted, leaving it to the host build system -to provide the ada symbols at final link time. The external ada must expose the -same C ABI (`ada_*`) and be ABI-compatible with this crate's version. +If neither is provided, no link directive is emitted, leaving it to the host +build system to provide the ada symbols at final link time. The external ada +must expose the same C ABI (`ada_*`) and be ABI-compatible with this crate's +version. ```sh # Link against a system-installed libada in /usr/local/lib -ADA_USE_SYSTEM_LIB=1 ADA_LIB_DIR=/usr/local/lib ADA_LIB_NAME=ada cargo build +ADA_LIB_DIR=/usr/local/lib ADA_LIB_NAME=ada \ + cargo build --no-default-features --features std ``` ### Performance diff --git a/build.rs b/build.rs index 9f66c69..88309ac 100644 --- a/build.rs +++ b/build.rs @@ -88,24 +88,28 @@ fn ndk_major_version(ndk_dir: &Path) -> u32 { } fn main() { - // Allow downstream builds to skip compiling the bundled C++ `ada` sources - // and instead link against an externally provided ada library. This is - // useful when ada is already provided by the host build system (e.g. Bazel, - // CMake, or a distro package), where compiling the bundled copy would - // otherwise produce a second, duplicate copy of ada in the final binary. + // By default the bundled C++ `ada` sources are compiled and linked + // statically (the `bundled` feature, enabled by default). Disabling the + // `bundled` feature (e.g. `default-features = false`) skips that compile and + // instead links against an externally provided ada library. This is useful + // when ada is already provided by the host build system (e.g. Bazel, CMake, + // or a distro package), where compiling the bundled copy would otherwise + // produce a second, duplicate copy of ada in the final binary. // - // ADA_USE_SYSTEM_LIB if set (non-empty), do not build the bundled ada. - // ADA_LIB_DIR optional directory added to the link search path. - // ADA_LIB_NAME optional library to link, passed verbatim to - // `cargo:rustc-link-lib` (e.g. `ada` or `static=ada`). + // When the `bundled` feature is disabled, these optional environment + // variables configure how the external ada is linked: // - // When `ADA_USE_SYSTEM_LIB` is set but neither `ADA_LIB_NAME` nor - // `ADA_LIB_DIR` is provided, no link directive is emitted, leaving it to the - // host build system to provide the ada symbols at final link time. - println!("cargo:rerun-if-env-changed=ADA_USE_SYSTEM_LIB"); + // ADA_LIB_DIR directory added to the link search path. + // ADA_LIB_NAME library to link, passed verbatim to `cargo:rustc-link-lib` + // (e.g. `ada` or `static=ada`). + // + // If neither is provided, no link directive is emitted, leaving it to the + // host build system to provide the ada symbols at final link time. The + // external ada must expose the same C ABI (`ada_*`) and be ABI-compatible + // with this crate's version. println!("cargo:rerun-if-env-changed=ADA_LIB_DIR"); println!("cargo:rerun-if-env-changed=ADA_LIB_NAME"); - if env::var_os("ADA_USE_SYSTEM_LIB").is_some_and(|v| !v.is_empty()) { + if !cfg!(feature = "bundled") { if let Some(dir) = env::var_os("ADA_LIB_DIR") { println!( "cargo:rustc-link-search=native={}", From cca7eb56e07b6c3af42578f8edcaf60fe9e3967f Mon Sep 17 00:00:00 2001 From: Logan Gatlin Date: Wed, 1 Jul 2026 10:20:50 -0500 Subject: [PATCH 3/3] tests --- .github/workflows/ci.yml | 43 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d64877..099f647 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,13 +58,54 @@ jobs: run: cargo hack clippy --feature-powerset -- -D warnings - name: Test - run: cargo hack test --feature-powerset + # `bundled` must stay enabled for every combination: it is what compiles + # and statically links the C++ ada sources. Without it (and with no + # external ada provided via ADA_LIB_DIR/ADA_LIB_NAME) the test binaries + # fail to link with `undefined symbol: ada_*`. `--features bundled` forces + # it into each powerset entry while still exercising the other features. + run: cargo hack test --feature-powerset --features bundled - name: Check Documentation env: RUSTDOCFLAGS: '-D warnings' run: cargo hack doc --feature-powerset + external-ada: + name: External ada (non-bundled) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: Swatinem/rust-cache@v2 + with: + shared-key: external-ada + save-if: ${{ github.ref_name == 'main' }} + + - run: rustup show + + # Exercise the non-bundled linking path: instead of compiling the bundled + # C++ sources, build ada once as a standalone shared library and link the + # crate against it via ADA_LIB_DIR / ADA_LIB_NAME. This mirrors how a host + # build system (Bazel, CMake, a distro package) would provide ada. The + # shared library records its own libstdc++ dependency (DT_NEEDED), so the + # final Rust binary needs no extra C++ runtime link flags. + - name: Build standalone libada.so + run: | + c++ -std=c++20 -O2 -DADA_INCLUDE_URL_PATTERN=0 -Ideps -fPIC -shared \ + deps/ada.cpp -o "$RUNNER_TEMP/libada.so" + + # `bundled` is intentionally disabled (default-features off). `std` is + # re-enabled because dropping default features also drops it. The serde + # combination is run separately to cover the extra feature on this path. + - name: Test against external ada + env: + ADA_LIB_DIR: ${{ runner.temp }} + ADA_LIB_NAME: ada + LD_LIBRARY_PATH: ${{ runner.temp }} + run: | + cargo test --no-default-features --features std + cargo test --no-default-features --features serde + sanitizers: name: AddressSanitizer + LeakSanitizer runs-on: ubuntu-latest