From d2124fd07c206efe901dac67953d9da7d0f8bca9 Mon Sep 17 00:00:00 2001 From: kaichao Date: Fri, 24 Jul 2026 09:59:58 +0800 Subject: [PATCH] feat: added an opt-in LOGOS_DELIVERY_RELOCATABLE mode (#185) * added an opt-in LOGOS_DELIVERY_RELOCATABLE mode. * chore: remove duplicat build.rs --- extensions/embedded-logos-delivery/build.rs | 160 -------------------- extensions/logos-delivery-rust/build.rs | 51 +++++-- 2 files changed, 40 insertions(+), 171 deletions(-) delete mode 100644 extensions/embedded-logos-delivery/build.rs diff --git a/extensions/embedded-logos-delivery/build.rs b/extensions/embedded-logos-delivery/build.rs deleted file mode 100644 index 2773451..0000000 --- a/extensions/embedded-logos-delivery/build.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::fs; -use std::path::{Path, PathBuf}; -use std::process::Command; - -fn main() { - println!("cargo:rerun-if-env-changed=LOGOS_DELIVERY_LIB_DIR"); - - let Some(lib_dir) = locate_lib_dir() else { - println!( - "cargo:warning=liblogosdelivery could not be located; `cargo check`/\ - `clippy` will pass, but building or testing will fail at link. Enter \ - the dev shell with `nix develop` or set LOGOS_DELIVERY_LIB_DIR to \ - the directory containing the library." - ); - return; - }; - - let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set"); - let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); - - // The shipped library carries a relocatable install name (@rpath on macOS, - // $ORIGIN soname on Linux), which would force every downstream BINARY to - // inject its own RPATH. Cargo propagates `rustc-link-search` and - // `rustc-link-lib` across crates, but NOT `rustc-link-arg` (the rpath) — so - // that relocatable name is exactly what makes consumers need their own - // build.rs. Instead, stamp a private copy with an ABSOLUTE install name; - // the propagating search + lib directives are then sufficient and consumers - // need zero build-script glue. - match target_os.as_str() { - "macos" => stamp_absolute_macos(&lib_dir, &out_dir), - "linux" => stamp_absolute_linux(&lib_dir, &out_dir), - other => panic!("unsupported OS for logos-delivery transport: {other}"), - } - - println!("cargo:rustc-link-search=native={out_dir}"); - println!("cargo:rustc-link-lib=dylib=logosdelivery"); -} - -/// Locate the native library directory as an ABSOLUTE, canonical path. Prefers -/// `LOGOS_DELIVERY_LIB_DIR`, then falls back to building it via nix. Returns -/// `None` when neither is available (e.g. `cargo check` without nix). -fn locate_lib_dir() -> Option { - if let Ok(dir) = std::env::var("LOGOS_DELIVERY_LIB_DIR") { - if let Some(resolved) = resolve_lib_dir(&dir) { - return Some(resolved); - } - println!( - "cargo:warning=LOGOS_DELIVERY_LIB_DIR='{dir}' could not be resolved; \ - falling back to `nix build`" - ); - } - resolve_lib_dir(&nix_build_logos_delivery()?) -} - -/// Resolve a lib dir to an absolute, canonical path. Cargo runs build scripts -/// with the cwd set to the crate dir, but a relative value (e.g. CI's -/// `./result/lib`) is anchored at the flake/workspace root where `nix build` -/// drops `result`. Canonicalizing also follows the `result` symlink to the -/// immutable store path, so the stamped install name / soname stays stable. -fn resolve_lib_dir(dir: &str) -> Option { - let path = Path::new(dir); - let anchored = if path.is_absolute() { - path.to_path_buf() - } else { - let manifest = std::env::var("CARGO_MANIFEST_DIR").ok()?; - Path::new(&find_flake_root(&manifest)?).join(path) - }; - anchored.canonicalize().ok() -} - -/// Copy `liblogosdelivery.dylib` into `OUT_DIR` and rewrite its install name to -/// the absolute store path. The consumer records that absolute path, so dyld -/// loads the original file directly — whose own `@loader_path` RPATH resolves -/// `librln.dylib` beside it — with no RPATH needed on the consumer. -fn stamp_absolute_macos(lib_dir: &Path, out_dir: &str) { - let src = lib_dir.join("liblogosdelivery.dylib"); - let dst = format!("{out_dir}/liblogosdelivery.dylib"); - copy_writable(&src, Path::new(&dst)); - run("install_name_tool", &["-id", path_str(&src), &dst]); - println!("cargo:rerun-if-changed={}", src.display()); -} - -/// Linux equivalent: an absolute `DT_SONAME` is recorded verbatim in the -/// consumer's `DT_NEEDED`, so `ld.so` loads it by path with no RPATH. Requires -/// `patchelf` at build time (provided by the nix devshell). -fn stamp_absolute_linux(lib_dir: &Path, out_dir: &str) { - let src = lib_dir.join("liblogosdelivery.so"); - let dst = format!("{out_dir}/liblogosdelivery.so"); - copy_writable(&src, Path::new(&dst)); - run("patchelf", &["--set-soname", path_str(&src), &dst]); - println!("cargo:rerun-if-changed={}", src.display()); -} - -fn path_str(p: &Path) -> &str { - p.to_str() - .unwrap_or_else(|| panic!("non-UTF-8 path: {}", p.display())) -} - -fn copy_writable(src: &Path, dst: &Path) { - use std::os::unix::fs::PermissionsExt; - - fs::copy(src, dst) - .unwrap_or_else(|e| panic!("copy {} -> {}: {e}", src.display(), dst.display())); - // Store-sourced files are read-only; restore owner write so the install - // name / soname can be rewritten. - fs::set_permissions(dst, fs::Permissions::from_mode(0o644)).unwrap(); -} - -fn run(cmd: &str, args: &[&str]) { - let status = Command::new(cmd) - .args(args) - .status() - .unwrap_or_else(|e| panic!("failed to run `{cmd}`: {e}")); - assert!(status.success(), "`{cmd} {args:?}` failed with {status}"); -} - -fn nix_build_logos_delivery() -> Option { - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").ok()?; - let flake_root = find_flake_root(&manifest_dir)?; - - println!("cargo:rerun-if-changed={flake_root}/flake.lock"); - - let output = Command::new("nix") - .args([ - "build", - ".#logos-delivery", - "--no-link", - "--print-out-paths", - ]) - .current_dir(&flake_root) - .output() - .ok()?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - println!("cargo:warning=nix build .#logos-delivery failed: {stderr}"); - return None; - } - - let store_path = String::from_utf8(output.stdout).ok()?; - let lib_dir = format!("{}/lib", store_path.trim()); - - if std::path::Path::new(&lib_dir).exists() { - Some(lib_dir) - } else { - None - } -} - -fn find_flake_root(start: &str) -> Option { - let mut path = std::path::PathBuf::from(start); - loop { - if path.join("flake.nix").exists() { - return Some(path.to_string_lossy().into_owned()); - } - if !path.pop() { - return None; - } - } -} diff --git a/extensions/logos-delivery-rust/build.rs b/extensions/logos-delivery-rust/build.rs index 2773451..e015061 100644 --- a/extensions/logos-delivery-rust/build.rs +++ b/extensions/logos-delivery-rust/build.rs @@ -4,6 +4,7 @@ use std::process::Command; fn main() { println!("cargo:rerun-if-env-changed=LOGOS_DELIVERY_LIB_DIR"); + println!("cargo:rerun-if-env-changed=LOGOS_DELIVERY_RELOCATABLE"); let Some(lib_dir) = locate_lib_dir() else { println!( @@ -18,22 +19,50 @@ fn main() { let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set"); let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); - // The shipped library carries a relocatable install name (@rpath on macOS, - // $ORIGIN soname on Linux), which would force every downstream BINARY to - // inject its own RPATH. Cargo propagates `rustc-link-search` and - // `rustc-link-lib` across crates, but NOT `rustc-link-arg` (the rpath) — so - // that relocatable name is exactly what makes consumers need their own - // build.rs. Instead, stamp a private copy with an ABSOLUTE install name; - // the propagating search + lib directives are then sufficient and consumers - // need zero build-script glue. match target_os.as_str() { - "macos" => stamp_absolute_macos(&lib_dir, &out_dir), - "linux" => stamp_absolute_linux(&lib_dir, &out_dir), + "macos" | "linux" => {} other => panic!("unsupported OS for logos-delivery transport: {other}"), } - println!("cargo:rustc-link-search=native={out_dir}"); + // Two linking modes, because dev builds and *distributable* builds want + // opposite things out of the library's install name / soname. + if relocatable() { + // Distribution: link the shipped library in place and leave its + // relocatable name (@rpath on macOS, $ORIGIN soname on Linux) intact, + // so the consumer can copy it into its own bundle and resolve it from + // there. The library's own @loader_path/$ORIGIN rpath then finds + // librln beside it. This costs the consumer some build-script glue -- + // on macOS it MUST add an rpath, since cargo does not propagate + // `rustc-link-arg` across crates -- which is exactly what the default + // mode below exists to avoid. `lib_dir` is published as + // DEP_LOGOSDELIVERY_LIB_DIR so direct dependents can locate the + // libraries to bundle. + println!("cargo:rustc-link-search=native={}", lib_dir.display()); + } else { + // Default (dev): stamp a private copy with an ABSOLUTE install name. + // The propagating search + lib directives are then sufficient and + // consumers need zero build-script glue -- but the resulting binary + // hardcodes a nix store path and only runs on this machine. + match target_os.as_str() { + "macos" => stamp_absolute_macos(&lib_dir, &out_dir), + "linux" => stamp_absolute_linux(&lib_dir, &out_dir), + _ => unreachable!("target OS validated above"), + } + println!("cargo:rustc-link-search=native={out_dir}"); + } + println!("cargo:rustc-link-lib=dylib=logosdelivery"); + println!("cargo:lib_dir={}", lib_dir.display()); +} + +/// Opt-in relocatable linking for builds that get shipped to other machines. +/// Off by default so existing consumers (and this repo's own tests) keep the +/// zero-glue absolute-path behaviour. +fn relocatable() -> bool { + matches!( + std::env::var("LOGOS_DELIVERY_RELOCATABLE").as_deref(), + Ok("1") | Ok("true") + ) } /// Locate the native library directory as an ABSOLUTE, canonical path. Prefers