From 2d69a831f3ada67d98f4c43ac6ff31fe0f4262aa Mon Sep 17 00:00:00 2001 From: dank-openai Date: Sat, 28 Feb 2026 12:29:26 -0500 Subject: [PATCH 1/5] xlsynth-sys: add XLSYNTH_ARTIFACT_CONFIG support Resolve relative artifact-config paths against the config file directory, cover that contract in the nested cargo smoke test, and document that XLS_DSO_PATH may be either a file path or a directory depending on how the DSO was materialized. --- README.md | 8 + xlsynth-sys/Cargo.toml | 1 + xlsynth-sys/build.rs | 298 ++++++++++++++---- xlsynth-sys/src/lib.rs | 14 +- xlsynth-sys/tests/artifact_config_test.rs | 366 ++++++++++++++++++++++ 5 files changed, 619 insertions(+), 68 deletions(-) create mode 100644 xlsynth-sys/tests/artifact_config_test.rs diff --git a/README.md b/README.md index 10793f61..c9caf6b4 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,14 @@ cargo test --workspace Note: `XLS_DSO_PATH` and `DSLX_STDLIB_PATH` are a paired build-time override for supplying pre-fetched XLS artifacts (DSO + DSLX stdlib). Setting only one will not enable the override. +Build systems that prefer a single declared input can instead set `XLSYNTH_ARTIFACT_CONFIG` to a +TOML file containing `dso_path` and `dslx_stdlib_path`. + +- If `XLSYNTH_ARTIFACT_CONFIG` is set, it takes precedence over `XLS_DSO_PATH` and + `DSLX_STDLIB_PATH`, and the paired env override is ignored. +- `XLSYNTH_ARTIFACT_CONFIG` itself must be an absolute path. +- `dso_path` and `dslx_stdlib_path` inside that TOML may be absolute paths, or relative paths + resolved from the TOML file's directory. ## Development Notes diff --git a/xlsynth-sys/Cargo.toml b/xlsynth-sys/Cargo.toml index aca8f1c9..be6ee8aa 100644 --- a/xlsynth-sys/Cargo.toml +++ b/xlsynth-sys/Cargo.toml @@ -21,6 +21,7 @@ ureq = { version = "3", default-features = false, features = ["rustls"] } flate2 = "1.0" tar = "0.4" sha2 = "0.10" +toml = "0.8" [dev-dependencies] syn = { version = "2", features = ["full"] } diff --git a/xlsynth-sys/build.rs b/xlsynth-sys/build.rs index 1c610e33..b7e34763 100644 --- a/xlsynth-sys/build.rs +++ b/xlsynth-sys/build.rs @@ -9,6 +9,11 @@ use std::process::Command; const RELEASE_LIB_VERSION_TAG: &str = "v0.39.0"; const MAX_DOWNLOAD_ATTEMPTS: u32 = 6; +struct ArtifactPaths { + dso_path: String, + dslx_stdlib_path: String, +} + fn xlsynth_release_tuple_from_tag(tag: &str) -> (u32, u32, u32, u32) { let s = tag.strip_prefix('v').unwrap_or(tag); let mut dash_split = s.splitn(2, '-'); @@ -460,12 +465,176 @@ fn get_dso_info() -> DsoInfo { } } +fn write_artifact_paths_rs(out_dir: &Path, artifact_paths: &ArtifactPaths) { + let artifact_paths_rs = out_dir.join("artifact_paths.rs"); + let file_contents = format!( + concat!( + "// This file is generated by xlsynth-sys/build.rs.\n", + "pub const DSLX_STDLIB_PATH: &str = {dslx_stdlib_path};\n", + "pub const XLS_DSO_PATH: &str = {dso_path};\n" + ), + dslx_stdlib_path = format!("{:?}", artifact_paths.dslx_stdlib_path), + dso_path = format!("{:?}", artifact_paths.dso_path), + ); + std::fs::write(&artifact_paths_rs, file_contents).unwrap_or_else(|err| { + panic!( + "Failed to write generated artifact paths file {}: {}", + artifact_paths_rs.display(), + err + ) + }); +} + +fn parse_artifact_config_string( + config_table: &toml::Table, + config_path: &Path, + key: &str, +) -> String { + config_table + .get(key) + .and_then(toml::Value::as_str) + .unwrap_or_else(|| { + panic!( + "XLSYNTH_ARTIFACT_CONFIG {} must define string key {:?}", + config_path.display(), + key + ) + }) + .to_string() +} + +fn resolve_artifact_config_dir(config_path: &Path) -> &Path { + config_path.parent().unwrap_or_else(|| { + panic!( + "XLSYNTH_ARTIFACT_CONFIG should have a parent directory: {}", + config_path.display() + ) + }) +} + +fn parse_artifact_config_env_path() -> Option { + let config_path = PathBuf::from(std::env::var_os("XLSYNTH_ARTIFACT_CONFIG")?); + if config_path.is_absolute() { + Some(config_path) + } else { + panic!( + concat!( + "XLSYNTH_ARTIFACT_CONFIG must be an absolute path; got relative path: {}.\n", + "The dso_path and dslx_stdlib_path values inside that TOML may still be relative ", + "to the TOML file's directory." + ), + config_path.display() + ); + } +} + +fn parse_artifact_config_path(config_table: &toml::Table, config_path: &Path, key: &str) -> String { + let raw_path = PathBuf::from(parse_artifact_config_string(config_table, config_path, key)); + let resolved_path = if raw_path.is_absolute() { + raw_path + } else { + resolve_artifact_config_dir(config_path).join(raw_path) + }; + resolved_path.display().to_string() +} + +fn load_artifact_paths_from_config() -> Option { + let config_path = parse_artifact_config_env_path()?; + println!("cargo:rerun-if-changed={}", config_path.display()); + + let config_contents = std::fs::read_to_string(&config_path).unwrap_or_else(|err| { + panic!( + "Failed to read XLSYNTH_ARTIFACT_CONFIG {}: {}", + config_path.display(), + err + ) + }); + let config_value: toml::Value = toml::from_str(&config_contents).unwrap_or_else(|err| { + panic!( + "Failed to parse XLSYNTH_ARTIFACT_CONFIG {} as TOML: {}", + config_path.display(), + err + ) + }); + let config_table = config_value.as_table().unwrap_or_else(|| { + panic!( + "XLSYNTH_ARTIFACT_CONFIG {} must contain a top-level TOML table", + config_path.display() + ) + }); + + Some(ArtifactPaths { + dso_path: parse_artifact_config_path(config_table, &config_path, "dso_path"), + dslx_stdlib_path: parse_artifact_config_path( + config_table, + &config_path, + "dslx_stdlib_path", + ), + }) +} + +fn emit_link_directives_for_explicit_dso(dso_path: &Path) { + let dso_dir = dso_path.parent().unwrap_or_else(|| { + panic!( + "Explicit XLS DSO path must have a parent directory: {}", + dso_path.display() + ) + }); + let dso_name = dso_path.file_name().unwrap_or_else(|| { + panic!( + "Explicit XLS DSO path must name a shared library file: {}", + dso_path.display() + ) + }); + let dso_name = dso_name.to_str().unwrap_or_else(|| { + panic!( + "Explicit XLS DSO path must be valid UTF-8: {}", + dso_path.display() + ) + }); + let (dso_name, ext) = dso_name.rsplit_once('.').unwrap_or_else(|| { + panic!( + "Explicit XLS DSO path must end with a shared library extension: {}", + dso_path.display() + ) + }); + match ext { + "dylib" | "so" => {} + _ => { + panic!("Expected shared library extension: {:?}", ext); + } + } + assert!( + dso_name.starts_with("lib"), + "DSO name should start with 'lib'; dso_name: {:?}", + dso_name + ); + let dso_name = &dso_name[3..]; + println!("cargo:rustc-link-search=native={}", dso_dir.display()); + println!("cargo:rustc-link-lib=dylib={dso_name}"); + println!("cargo:rustc-link-arg=-Wl,-rpath,{}", dso_dir.display()); + println!("cargo:DSO_PATH={}", dso_path.display()); +} + +fn emit_explicit_artifact_override( + out_dir: &Path, + artifact_paths: &ArtifactPaths, + source_name: &str, +) { + println!( + "cargo:info=Using {} with DSO {:?} and DSLX stdlib {:?}", + source_name, artifact_paths.dso_path, artifact_paths.dslx_stdlib_path + ); + write_artifact_paths_rs(out_dir, artifact_paths); + emit_link_directives_for_explicit_dso(Path::new(&artifact_paths.dso_path)); +} + /// Downloads the dynamic shared object for XLS from the release page if it does /// not already exist. -fn download_dso_if_dne(url_base: &str, out_dir: &str) -> DsoInfo { +fn download_dso_if_dne(url_base: &str, out_dir: &Path) -> DsoInfo { let dso_info: DsoInfo = get_dso_info(); let dso_url = dso_info.get_dso_url(url_base); - let dso_path = PathBuf::from(&out_dir).join(dso_info.get_dso_filename()); + let dso_path = out_dir.join(dso_info.get_dso_filename()); // Check if the DSO has already been downloaded if dso_path.exists() { @@ -497,9 +666,8 @@ fn download_dso_if_dne(url_base: &str, out_dir: &str) -> DsoInfo { dso_info } -fn download_stdlib_if_dne(url_base: &str, out_dir: &str) -> PathBuf { - let stdlib_path = - PathBuf::from(&out_dir).join(format!("dslx_stdlib_{RELEASE_LIB_VERSION_TAG}")); +fn download_stdlib_if_dne(url_base: &str, out_dir: &Path) -> PathBuf { + let stdlib_path = out_dir.join(format!("dslx_stdlib_{RELEASE_LIB_VERSION_TAG}")); if stdlib_path.exists() { println!( "cargo:info=DSLX stdlib path already downloaded to: {}", @@ -507,7 +675,7 @@ fn download_stdlib_if_dne(url_base: &str, out_dir: &str) -> PathBuf { ); return stdlib_path; } - let tarball_path = PathBuf::from(&out_dir).join("dslx_stdlib.tar.gz"); + let tarball_path = out_dir.join("dslx_stdlib.tar.gz"); let tarball_url = format!("{url_base}/dslx_stdlib.tar.gz"); high_integrity_download_with_retries(&tarball_url, &tarball_path, MAX_DOWNLOAD_ATTEMPTS) .expect("download of stdlib tarball should succeed"); @@ -522,16 +690,26 @@ fn download_stdlib_if_dne(url_base: &str, out_dir: &str) -> PathBuf { fn main() { // Ensure Cargo rebuilds this sys crate if caller-provided artifact paths // change, since they affect link args and rpath settings. + println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-env-changed=DEV_XLS_DSO_WORKSPACE"); + println!("cargo:rerun-if-env-changed=XLSYNTH_ARTIFACT_CONFIG"); println!("cargo:rerun-if-env-changed=XLS_DSO_PATH"); println!("cargo:rerun-if-env-changed=DSLX_STDLIB_PATH"); println!("cargo:rerun-if-env-changed=CARGO_NET_OFFLINE"); + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + std::fs::create_dir_all(&out_dir).expect("OUT_DIR should be creatable"); + // Detect if building on docs.rs if std::env::var("DOCS_RS").is_ok() { println!("cargo:warning=Skipping dynamic library download on docs.rs"); - println!("cargo:rustc-env=XLS_DSO_PATH=/does/not/exist/libxls.so"); - println!("cargo:rustc-env=DSLX_STDLIB_PATH=/does/not/exist/stdlib/"); + write_artifact_paths_rs( + &out_dir, + &ArtifactPaths { + dso_path: "/does/not/exist/libxls.so".to_string(), + dslx_stdlib_path: "/does/not/exist/stdlib/".to_string(), + }, + ); return; } @@ -549,6 +727,15 @@ fn main() { ); } + // Bazel and similar orchestrators can provide both artifact paths as one + // declared input file instead of managing two separate environment + // variables. The config path itself must be absolute, while TOML entries + // may still be relative to that file's directory. + if let Some(artifact_paths) = load_artifact_paths_from_config() { + emit_explicit_artifact_override(&out_dir, &artifact_paths, "XLSYNTH_ARTIFACT_CONFIG"); + return; + } + // If the user is trying to provide pre-fetched artifacts, they need to provide // both the DSO and stdlib paths together. // @@ -577,64 +764,32 @@ fn main() { } if std::env::var("XLS_DSO_PATH").is_ok() && std::env::var("DSLX_STDLIB_PATH").is_ok() { - println!( - "cargo:info=Using XLS_DSO_PATH {:?} and DSLX_STDLIB_PATH {:?}", - std::env::var("XLS_DSO_PATH"), - std::env::var("DSLX_STDLIB_PATH") - ); - let dso_path_string = std::env::var("XLS_DSO_PATH").unwrap(); - let stdlib_path_string = std::env::var("DSLX_STDLIB_PATH").unwrap(); - - // Extract information from the DSO path -- we need: - // * the directory for linker search path and rpath for the binary - // * the name for the library-to-link-to flag - let dso_path = PathBuf::from(&dso_path_string); - let dso_dir = dso_path.parent().unwrap(); - let dso_name = dso_path.file_name().unwrap(); - // Strip the extension from the dso_name. We make sure we right-split one dot - // and the extension looks like a shared library. - let (dso_name, ext) = dso_name.to_str().unwrap().rsplit_once('.').unwrap(); - match ext { - "dylib" | "so" => {} - _ => { - panic!("Expected shared library extension: {:?}", ext); - } - } - assert!( - dso_name.starts_with("lib"), - "DSO name should start with 'lib'; dso_name: {:?}", - dso_name + let artifact_paths = ArtifactPaths { + dso_path: std::env::var("XLS_DSO_PATH").unwrap(), + dslx_stdlib_path: std::env::var("DSLX_STDLIB_PATH").unwrap(), + }; + emit_explicit_artifact_override( + &out_dir, + &artifact_paths, + "paired XLS_DSO_PATH / DSLX_STDLIB_PATH override", ); - let dso_name = &dso_name[3..]; - println!("cargo:rustc-env=XLS_DSO_PATH={}", dso_path.display()); - println!("cargo:DSO_PATH={}", dso_path.display()); - println!("cargo:rustc-env=DSLX_STDLIB_PATH={stdlib_path_string}"); - println!("cargo:rustc-link-search=native={}", dso_dir.display()); - println!("cargo:rustc-link-lib=dylib={dso_name}"); - println!("cargo:rustc-link-arg=-Wl,-rpath,{}", dso_dir.display()); return; } let url_base = format!("https://github.com/xlsynth/xlsynth/releases/download/{RELEASE_LIB_VERSION_TAG}/"); - let out_dir = std::env::var("OUT_DIR").unwrap(); - - // Ensure the out directory exists. - if !std::fs::metadata(&out_dir).unwrap().is_dir() { - std::fs::create_dir_all(&out_dir).unwrap(); - } // If we're about to fetch artifacts but OFFLINE is set, fail early with a // clear message. We only panic when the artifacts are not already present // in OUT_DIR and no override env is provided. let offline = std::env::var("CARGO_NET_OFFLINE").is_ok(); if offline { - let stdlib_dir = - PathBuf::from(&out_dir).join(format!("dslx_stdlib_{RELEASE_LIB_VERSION_TAG}")); + let stdlib_dir = out_dir.join(format!("dslx_stdlib_{RELEASE_LIB_VERSION_TAG}")); let have_stdlib = stdlib_dir.exists(); let dso_filename = get_dso_info().get_dso_filename(); - let dso_path = PathBuf::from(&out_dir).join(dso_filename); + let dso_path = out_dir.join(dso_filename); let have_dso = dso_path.exists(); + let artifact_config = std::env::var("XLSYNTH_ARTIFACT_CONFIG").ok(); let xls_dso_path = std::env::var("XLS_DSO_PATH").ok(); let dslx_stdlib_path = std::env::var("DSLX_STDLIB_PATH").ok(); let dev_workspace = std::env::var("DEV_XLS_DSO_WORKSPACE").ok(); @@ -645,6 +800,7 @@ fn main() { concat!( "CARGO_NET_OFFLINE is set but build requires downloading XLS artifacts for {}.\n", "Specify one of the following to build offline:\n", + " - XLSYNTH_ARTIFACT_CONFIG (TOML file with dso_path and dslx_stdlib_path)\n", " - XLS_DSO_PATH and DSLX_STDLIB_PATH (pre-fetched artifacts)\n", " - DEV_XLS_DSO_WORKSPACE (path to XLS workspace providing the DSO)\n", " - Or unset CARGO_NET_OFFLINE to allow downloads.\n\n", @@ -652,16 +808,18 @@ fn main() { " OUT_DIR: {}\n", " Expected stdlib dir exists: {} ({})\n", " Expected DSO file exists: {} ({})\n", + " XLSYNTH_ARTIFACT_CONFIG: {}\n", " XLS_DSO_PATH: {}\n", " DSLX_STDLIB_PATH: {}\n", " DEV_XLS_DSO_WORKSPACE: {}\n" ), RELEASE_LIB_VERSION_TAG, - out_dir, + out_dir.display(), have_stdlib, stdlib_dir.display(), have_dso, dso_path.display(), + artifact_config.as_deref().unwrap_or(""), xls_dso_path.as_deref().unwrap_or(""), dslx_stdlib_path.as_deref().unwrap_or(""), dev_workspace.as_deref().unwrap_or("") @@ -672,7 +830,6 @@ fn main() { let stdlib_path: PathBuf = download_stdlib_if_dne(&url_base, &out_dir); let stdlib_path_full = format!("{}/xls/dslx/stdlib/", stdlib_path.display()); - println!("cargo:rustc-env=DSLX_STDLIB_PATH={stdlib_path_full}"); if std::env::var("DEV_XLS_DSO_WORKSPACE").is_ok() { // This points at a XLS workspace root. @@ -729,32 +886,43 @@ fn main() { dso_dest.display() ); - println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rustc-link-arg=-Wl,-rpath,{out_dir}"); - println!("cargo:rustc-link-search=native={out_dir}"); + println!("cargo:rustc-link-arg=-Wl,-rpath,{}", out_dir.display()); + println!("cargo:rustc-link-search=native={}", out_dir.display()); let dso_name_str = dso_info.get_dso_name(); println!("cargo:rustc-link-lib=dylib={dso_name_str}"); - println!("cargo:rustc-env=XLS_DSO_PATH={out_dir}"); - println!("cargo:DSO_PATH={out_dir}"); + write_artifact_paths_rs( + &out_dir, + &ArtifactPaths { + dso_path: out_dir.display().to_string(), + dslx_stdlib_path: stdlib_path_full, + }, + ); + println!("cargo:DSO_PATH={}", out_dir.display()); return; } let dso_info = download_dso_if_dne(&url_base, &out_dir); // Ensure the DSO is copied to the correct location - println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rustc-link-search=native={out_dir}"); + println!("cargo:rustc-link-search=native={}", out_dir.display()); let dso_name_str = dso_info.get_dso_name(); println!("cargo:rustc-link-lib=dylib={dso_name_str}"); - // This is exposed as `xlsynth_sys::XLS_DSO_PATH` from rust via - // xlsynth-sys/src/lib.rs. DO NOT USE THIS FROM WITHIN YOUR build.rs. See - // the definition in lib.rs for an explanation. - println!("cargo:rustc-env=XLS_DSO_PATH={out_dir}"); + // This is exposed as `xlsynth_sys::XLS_DSO_PATH` from Rust via the + // generated `artifact_paths.rs` include in `xlsynth-sys/src/lib.rs`. + // DO NOT USE THIS FROM WITHIN YOUR build.rs. See the definition in lib.rs + // for an explanation. + write_artifact_paths_rs( + &out_dir, + &ArtifactPaths { + dso_path: out_dir.display().to_string(), + dslx_stdlib_path: stdlib_path_full, + }, + ); // Path to the directory containing the DSO. This is the one you should use // from build.rs. The build.rs of a dependent crate can read this value via // the DEP_XLSYNTH_DSO_PATH envvar. See // https://doc.rust-lang.org/cargo/reference/build-script-examples.html#using-another-sys-crate. - println!("cargo:DSO_PATH={out_dir}"); + println!("cargo:DSO_PATH={}", out_dir.display()); } diff --git a/xlsynth-sys/src/lib.rs b/xlsynth-sys/src/lib.rs index 6490974d..e8f637dc 100644 --- a/xlsynth-sys/src/lib.rs +++ b/xlsynth-sys/src/lib.rs @@ -2507,9 +2507,17 @@ unsafe extern "C" { pub fn xls_dslx_quickcheck_get_count(qc: *const CDslxQuickcheck, result_out: *mut i64) -> bool; } -pub const DSLX_STDLIB_PATH: &str = env!("DSLX_STDLIB_PATH"); +mod generated_artifact_paths { + include!(concat!(env!("OUT_DIR"), "/artifact_paths.rs")); +} + +pub const DSLX_STDLIB_PATH: &str = generated_artifact_paths::DSLX_STDLIB_PATH; -/// Directory containing the libxls DSO. +/// Path describing where the libxls shared library was materialized. +/// +/// This value is a shared-library file path when the build uses explicit +/// artifact overrides, and a directory path when `xlsynth-sys` downloaded or +/// symlinked the DSO into its own output directory. /// /// *** DO NOT USE THIS VARIABLE FROM WITHIN YOUR build.rs *** /// @@ -2538,7 +2546,7 @@ pub const DSLX_STDLIB_PATH: &str = env!("DSLX_STDLIB_PATH"); /// /// (If you use this envvar from your crate proper -- i.e. not from build.rs -- /// then it's perfectly fine.) -pub const XLS_DSO_PATH: &str = env!("XLS_DSO_PATH"); +pub const XLS_DSO_PATH: &str = generated_artifact_paths::XLS_DSO_PATH; // Add opaque types for module port and def. #[repr(C)] diff --git a/xlsynth-sys/tests/artifact_config_test.rs b/xlsynth-sys/tests/artifact_config_test.rs new file mode 100644 index 00000000..65022abe --- /dev/null +++ b/xlsynth-sys/tests/artifact_config_test.rs @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: Apache-2.0 + +use std::ffi::OsStr; +use std::fs; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; + +fn resolve_config_dso_path() -> PathBuf { + let configured_path = PathBuf::from(xlsynth_sys::XLS_DSO_PATH); + if configured_path.is_file() { + return configured_path; + } + + if configured_path.is_dir() { + let expected_suffix = if cfg!(target_os = "macos") { + ".dylib" + } else { + ".so" + }; + let mut candidates: Vec = std::fs::read_dir(&configured_path) + .unwrap_or_else(|err| { + panic!( + "Failed to read XLS_DSO_PATH directory {}: {}", + configured_path.display(), + err + ) + }) + .filter_map(|entry| entry.ok()) + .map(|entry| entry.path()) + .filter(|path| path.is_file()) + .filter(|path| { + path.file_name() + .and_then(OsStr::to_str) + .map(|name| name.starts_with("libxls-") && name.ends_with(expected_suffix)) + .unwrap_or(false) + }) + .collect(); + candidates.sort(); + return candidates.pop().unwrap_or_else(|| { + panic!( + "No XLS DSO candidates found in {}", + configured_path.display() + ) + }); + } + + panic!( + "XLS_DSO_PATH must be a directory or file; got: {}", + configured_path.display() + ); +} + +fn make_temp_dir() -> PathBuf { + let unique_id = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time should be after unix epoch") + .as_nanos(); + let temp_dir = std::env::temp_dir().join(format!( + "xlsynth-sys-artifact-config-test-{}-{unique_id}", + std::process::id() + )); + fs::create_dir_all(&temp_dir).unwrap_or_else(|err| { + panic!( + "Failed to create temporary test directory {}: {}", + temp_dir.display(), + err + ) + }); + temp_dir +} + +fn write_file(path: &Path, contents: &str) { + fs::write(path, contents) + .unwrap_or_else(|err| panic!("Failed to write {}: {}", path.display(), err)); +} + +fn write_smoke_crate(temp_crate_dir: &Path, manifest_path: &Path) { + let temp_src_dir = temp_crate_dir.join("src"); + fs::create_dir_all(&temp_src_dir).unwrap_or_else(|err| { + panic!( + "Failed to create temporary crate directory {}: {}", + temp_src_dir.display(), + err + ) + }); + write_file( + &temp_crate_dir.join("Cargo.toml"), + &format!( + concat!( + "[package]\n", + "name = \"artifact-config-smoke\"\n", + "version = \"0.1.0\"\n", + "edition = \"2021\"\n\n", + "[dependencies]\n", + "xlsynth-sys = {{ path = {:?} }}\n" + ), + manifest_path.display().to_string() + ), + ); + write_file( + &temp_src_dir.join("main.rs"), + concat!( + "fn main() {\n", + " println!(\"XLS_DSO_PATH={}\", xlsynth_sys::XLS_DSO_PATH);\n", + " println!(\"DSLX_STDLIB_PATH={}\", xlsynth_sys::DSLX_STDLIB_PATH);\n", + "}\n" + ), + ); +} + +fn copy_dir_recursive(source_dir: &Path, target_dir: &Path) { + fs::create_dir_all(target_dir).unwrap_or_else(|err| { + panic!( + "Failed to create directory {} while copying {}: {}", + target_dir.display(), + source_dir.display(), + err + ) + }); + for entry in fs::read_dir(source_dir).unwrap_or_else(|err| { + panic!( + "Failed to read directory {} while copying to {}: {}", + source_dir.display(), + target_dir.display(), + err + ) + }) { + let entry = entry.unwrap_or_else(|err| { + panic!( + "Failed to read entry in {} while copying to {}: {}", + source_dir.display(), + target_dir.display(), + err + ) + }); + let source_path = entry.path(); + let target_path = target_dir.join(entry.file_name()); + if source_path.is_dir() { + copy_dir_recursive(&source_path, &target_path); + } else if source_path.is_file() { + fs::copy(&source_path, &target_path).unwrap_or_else(|err| { + panic!( + "Failed to copy file {} to {}: {}", + source_path.display(), + target_path.display(), + err + ) + }); + } else { + panic!( + "Expected directory tree entry to be a file or directory: {}", + source_path.display() + ); + } + } +} + +fn copy_config_artifacts(config_artifacts_dir: &Path) -> (PathBuf, PathBuf) { + fs::create_dir_all(config_artifacts_dir).unwrap_or_else(|err| { + panic!( + "Failed to create config artifacts directory {}: {}", + config_artifacts_dir.display(), + err + ) + }); + + let source_dso_path = resolve_config_dso_path(); + let source_stdlib_path = PathBuf::from(xlsynth_sys::DSLX_STDLIB_PATH); + let expected_dso_path = config_artifacts_dir.join( + source_dso_path + .file_name() + .unwrap_or_else(|| panic!("DSO path missing filename: {}", source_dso_path.display())), + ); + fs::copy(&source_dso_path, &expected_dso_path).unwrap_or_else(|err| { + panic!( + "Failed to copy DSO {} to {}: {}", + source_dso_path.display(), + expected_dso_path.display(), + err + ) + }); + + let expected_stdlib_path = config_artifacts_dir.join("stdlib"); + copy_dir_recursive(&source_stdlib_path, &expected_stdlib_path); + (expected_dso_path, expected_stdlib_path) +} + +fn write_artifact_config(config_path: &Path, dso_path: &str, dslx_stdlib_path: &str) { + let config_dir = config_path.parent().unwrap_or_else(|| { + panic!( + "Artifact config path should have a parent directory: {}", + config_path.display() + ) + }); + fs::create_dir_all(config_dir).unwrap_or_else(|err| { + panic!( + "Failed to create artifact config directory {}: {}", + config_dir.display(), + err + ) + }); + write_file( + config_path, + &format!( + "dso_path = {:?}\ndslx_stdlib_path = {:?}\n", + dso_path, dslx_stdlib_path + ), + ); +} + +fn run_nested_cargo_with_artifact_config( + temp_crate_dir: &Path, + target_dir: &Path, + artifact_config_path: &OsStr, +) -> std::process::Output { + let cargo_binary = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); + Command::new(&cargo_binary) + .arg("run") + .arg("--quiet") + .arg("--offline") + .env("CARGO_NET_OFFLINE", "true") + .env("CARGO_TARGET_DIR", target_dir) + .env("XLSYNTH_ARTIFACT_CONFIG", artifact_config_path) + .env("XLS_DSO_PATH", "/definitely/not/the/configured/libxls.so") + .env("DSLX_STDLIB_PATH", "/definitely/not/the/configured/stdlib/") + .current_dir(temp_crate_dir) + .output() + .unwrap_or_else(|err| { + panic!( + "Failed to run nested cargo for artifact-config test: {}", + err + ) + }) +} + +#[test] +fn artifact_config_resolves_relative_toml_paths_from_absolute_config_path() { + let temp_dir = make_temp_dir(); + let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let temp_crate_dir = temp_dir.join("artifact-config-smoke"); + let config_root_dir = temp_dir.join("config-root"); + let config_artifacts_dir = config_root_dir.join("artifacts"); + let (expected_dso_path, expected_stdlib_path) = copy_config_artifacts(&config_artifacts_dir); + let config_path = config_root_dir.join("artifact-config.toml"); + write_artifact_config( + &config_path, + &format!( + "artifacts/{}", + expected_dso_path + .file_name() + .unwrap_or_else(|| panic!( + "Expected copied DSO path to have filename: {}", + expected_dso_path.display() + )) + .to_string_lossy() + ), + "artifacts/stdlib", + ); + write_smoke_crate(&temp_crate_dir, &manifest_path); + + let target_dir = temp_dir.join("target"); + let output = run_nested_cargo_with_artifact_config( + &temp_crate_dir, + &target_dir, + config_path.as_os_str(), + ); + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "Nested cargo run failed with status {:?}.\nstdout:\n{}\nstderr:\n{}", + output.status, + stdout, + stderr + ); + let expected_dso_line = format!("XLS_DSO_PATH={}", expected_dso_path.display()); + let expected_stdlib_line = format!("DSLX_STDLIB_PATH={}", expected_stdlib_path.display()); + assert!( + stdout.contains(&expected_dso_line), + "Nested cargo output did not include expected DSO path.\nExpected line: {}\nstdout:\n{}\nstderr:\n{}", + expected_dso_line, + stdout, + stderr + ); + assert!( + stdout.contains(&expected_stdlib_line), + "Nested cargo output did not include expected DSLX stdlib path.\nExpected line: {}\nstdout:\n{}\nstderr:\n{}", + expected_stdlib_line, + stdout, + stderr + ); + assert!( + !stdout.contains("/definitely/not/the/configured/"), + "Nested cargo output should come from XLSYNTH_ARTIFACT_CONFIG and its relative-path resolution, not the paired env override.\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + + fs::remove_dir_all(&temp_dir).ok(); +} + +#[test] +fn artifact_config_requires_absolute_config_path() { + let temp_dir = make_temp_dir(); + let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let temp_crate_dir = temp_dir.join("artifact-config-relative-smoke"); + let config_root_dir = temp_crate_dir.join("config"); + let config_artifacts_dir = temp_crate_dir.join("artifacts"); + let (expected_dso_path, _expected_stdlib_path) = copy_config_artifacts(&config_artifacts_dir); + let config_path = config_root_dir.join("artifact-config.toml"); + write_artifact_config( + &config_path, + &format!( + "../artifacts/{}", + expected_dso_path + .file_name() + .unwrap_or_else(|| panic!( + "Expected copied DSO path to have filename: {}", + expected_dso_path.display() + )) + .to_string_lossy() + ), + "../artifacts/stdlib", + ); + write_smoke_crate(&temp_crate_dir, &manifest_path); + + let target_dir = temp_dir.join("relative-target"); + let output = run_nested_cargo_with_artifact_config( + &temp_crate_dir, + &target_dir, + OsStr::new("config/artifact-config.toml"), + ); + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + !output.status.success(), + "Nested cargo run unexpectedly succeeded with relative XLSYNTH_ARTIFACT_CONFIG.\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stderr.contains("XLSYNTH_ARTIFACT_CONFIG must be an absolute path"), + "Nested cargo stderr did not explain the absolute-path requirement.\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stderr.contains("dso_path and dslx_stdlib_path values inside that TOML may still be relative"), + "Nested cargo stderr did not preserve the relative-path guidance for TOML entries.\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + !stderr.contains("No such file or directory"), + "Nested cargo stderr should reject relative XLSYNTH_ARTIFACT_CONFIG directly instead of failing with a file lookup error.\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + + fs::remove_dir_all(&temp_dir).ok(); +} From f8c89b5b8e26268e083cac8baed2141a729382f2 Mon Sep 17 00:00:00 2001 From: dank-openai Date: Sat, 28 Feb 2026 22:51:19 -0500 Subject: [PATCH 2/5] xlsynth-sys: document DEP_XLSYNTH_DSO_PATH shape --- xlsynth-sys/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xlsynth-sys/src/lib.rs b/xlsynth-sys/src/lib.rs index e8f637dc..07617b3b 100644 --- a/xlsynth-sys/src/lib.rs +++ b/xlsynth-sys/src/lib.rs @@ -2541,6 +2541,13 @@ pub const DSLX_STDLIB_PATH: &str = generated_artifact_paths::DSLX_STDLIB_PATH; /// let dylib_path = std::env::var("DEP_XLSYNTH_DSO_PATH").unwrap(); /// ``` /// +/// `DEP_XLSYNTH_DSO_PATH` follows the same contract as `XLS_DSO_PATH`: it is a +/// shared-library file path when `xlsynth-sys` uses explicit artifact +/// overrides, and a directory path when `xlsynth-sys` downloaded or symlinked +/// the DSO into its own output directory. Downstream `build.rs` code that needs +/// a directory for link-search or rpath setup should normalize a file path to +/// its parent directory before using it. +/// /// More details are available at /// . /// From 1bf7758395114594ca7d2ae7fe60c5e93e1b942a Mon Sep 17 00:00:00 2001 From: dank-openai Date: Wed, 4 Mar 2026 15:32:31 -0500 Subject: [PATCH 3/5] xlsynth-sys: support Bazel-declared linking --- README.md | 3 +++ xlsynth-sys/build.rs | 45 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c9caf6b4..89a14bc6 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,9 @@ TOML file containing `dso_path` and `dslx_stdlib_path`. - `XLSYNTH_ARTIFACT_CONFIG` itself must be an absolute path. - `dso_path` and `dslx_stdlib_path` inside that TOML may be absolute paths, or relative paths resolved from the TOML file's directory. +- Build systems that declare the shared library separately can set + `XLSYNTH_SYS_LINK_MODE=declared` so `build.rs` records the artifact paths without emitting its + own native `-l...` directives. ## Development Notes diff --git a/xlsynth-sys/build.rs b/xlsynth-sys/build.rs index b7e34763..1a1a899f 100644 --- a/xlsynth-sys/build.rs +++ b/xlsynth-sys/build.rs @@ -14,6 +14,29 @@ struct ArtifactPaths { dslx_stdlib_path: String, } +#[derive(Clone, Copy)] +enum LinkDirectiveMode { + Native, + Declared, +} + +fn parse_link_directive_mode() -> LinkDirectiveMode { + match std::env::var("XLSYNTH_SYS_LINK_MODE") { + Ok(value) => match value.as_str() { + "native" => LinkDirectiveMode::Native, + "declared" => LinkDirectiveMode::Declared, + _ => panic!( + "XLSYNTH_SYS_LINK_MODE must be one of 'native' or 'declared'; got {:?}", + value + ), + }, + Err(std::env::VarError::NotPresent) => LinkDirectiveMode::Native, + Err(std::env::VarError::NotUnicode(value)) => { + panic!("XLSYNTH_SYS_LINK_MODE must be valid UTF-8; got {:?}", value) + } + } +} + fn xlsynth_release_tuple_from_tag(tag: &str) -> (u32, u32, u32, u32) { let s = tag.strip_prefix('v').unwrap_or(tag); let mut dash_split = s.splitn(2, '-'); @@ -620,13 +643,23 @@ fn emit_explicit_artifact_override( out_dir: &Path, artifact_paths: &ArtifactPaths, source_name: &str, + link_directive_mode: LinkDirectiveMode, ) { println!( "cargo:info=Using {} with DSO {:?} and DSLX stdlib {:?}", source_name, artifact_paths.dso_path, artifact_paths.dslx_stdlib_path ); write_artifact_paths_rs(out_dir, artifact_paths); - emit_link_directives_for_explicit_dso(Path::new(&artifact_paths.dso_path)); + let dso_path = Path::new(&artifact_paths.dso_path); + match link_directive_mode { + LinkDirectiveMode::Native => emit_link_directives_for_explicit_dso(dso_path), + LinkDirectiveMode::Declared => { + println!( + "cargo:info=Skipping native link directives because XLSYNTH_SYS_LINK_MODE=declared" + ); + println!("cargo:DSO_PATH={}", dso_path.display()); + } + } } /// Downloads the dynamic shared object for XLS from the release page if it does @@ -695,10 +728,12 @@ fn main() { println!("cargo:rerun-if-env-changed=XLSYNTH_ARTIFACT_CONFIG"); println!("cargo:rerun-if-env-changed=XLS_DSO_PATH"); println!("cargo:rerun-if-env-changed=DSLX_STDLIB_PATH"); + println!("cargo:rerun-if-env-changed=XLSYNTH_SYS_LINK_MODE"); println!("cargo:rerun-if-env-changed=CARGO_NET_OFFLINE"); let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); std::fs::create_dir_all(&out_dir).expect("OUT_DIR should be creatable"); + let link_directive_mode = parse_link_directive_mode(); // Detect if building on docs.rs if std::env::var("DOCS_RS").is_ok() { @@ -732,7 +767,12 @@ fn main() { // variables. The config path itself must be absolute, while TOML entries // may still be relative to that file's directory. if let Some(artifact_paths) = load_artifact_paths_from_config() { - emit_explicit_artifact_override(&out_dir, &artifact_paths, "XLSYNTH_ARTIFACT_CONFIG"); + emit_explicit_artifact_override( + &out_dir, + &artifact_paths, + "XLSYNTH_ARTIFACT_CONFIG", + link_directive_mode, + ); return; } @@ -772,6 +812,7 @@ fn main() { &out_dir, &artifact_paths, "paired XLS_DSO_PATH / DSLX_STDLIB_PATH override", + link_directive_mode, ); return; } From 973cdadcf1274b90038a562adfab0121a435cdea Mon Sep 17 00:00:00 2001 From: dank-openai Date: Wed, 4 Mar 2026 23:53:44 -0500 Subject: [PATCH 4/5] Validate declared-mode explicit DSO paths --- xlsynth-sys/build.rs | 37 ++++++-- xlsynth-sys/tests/artifact_config_test.rs | 102 ++++++++++++++++++++-- 2 files changed, 121 insertions(+), 18 deletions(-) diff --git a/xlsynth-sys/build.rs b/xlsynth-sys/build.rs index 1a1a899f..8ee078aa 100644 --- a/xlsynth-sys/build.rs +++ b/xlsynth-sys/build.rs @@ -14,6 +14,12 @@ struct ArtifactPaths { dslx_stdlib_path: String, } +struct ExplicitDsoPath { + link_name: String, + path: PathBuf, + parent_dir: PathBuf, +} + #[derive(Clone, Copy)] enum LinkDirectiveMode { Native, @@ -596,7 +602,7 @@ fn load_artifact_paths_from_config() -> Option { }) } -fn emit_link_directives_for_explicit_dso(dso_path: &Path) { +fn validate_explicit_dso_path(dso_path: &Path) -> ExplicitDsoPath { let dso_dir = dso_path.parent().unwrap_or_else(|| { panic!( "Explicit XLS DSO path must have a parent directory: {}", @@ -632,11 +638,24 @@ fn emit_link_directives_for_explicit_dso(dso_path: &Path) { "DSO name should start with 'lib'; dso_name: {:?}", dso_name ); - let dso_name = &dso_name[3..]; - println!("cargo:rustc-link-search=native={}", dso_dir.display()); - println!("cargo:rustc-link-lib=dylib={dso_name}"); - println!("cargo:rustc-link-arg=-Wl,-rpath,{}", dso_dir.display()); - println!("cargo:DSO_PATH={}", dso_path.display()); + ExplicitDsoPath { + link_name: dso_name[3..].to_string(), + path: dso_path.to_path_buf(), + parent_dir: dso_dir.to_path_buf(), + } +} + +fn emit_link_directives_for_explicit_dso(dso_path: &ExplicitDsoPath) { + println!( + "cargo:rustc-link-search=native={}", + dso_path.parent_dir.display() + ); + println!("cargo:rustc-link-lib=dylib={}", dso_path.link_name); + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{}", + dso_path.parent_dir.display() + ); + println!("cargo:DSO_PATH={}", dso_path.path.display()); } fn emit_explicit_artifact_override( @@ -650,14 +669,14 @@ fn emit_explicit_artifact_override( source_name, artifact_paths.dso_path, artifact_paths.dslx_stdlib_path ); write_artifact_paths_rs(out_dir, artifact_paths); - let dso_path = Path::new(&artifact_paths.dso_path); + let dso_path = validate_explicit_dso_path(Path::new(&artifact_paths.dso_path)); match link_directive_mode { - LinkDirectiveMode::Native => emit_link_directives_for_explicit_dso(dso_path), + LinkDirectiveMode::Native => emit_link_directives_for_explicit_dso(&dso_path), LinkDirectiveMode::Declared => { println!( "cargo:info=Skipping native link directives because XLSYNTH_SYS_LINK_MODE=declared" ); - println!("cargo:DSO_PATH={}", dso_path.display()); + println!("cargo:DSO_PATH={}", dso_path.path.display()); } } } diff --git a/xlsynth-sys/tests/artifact_config_test.rs b/xlsynth-sys/tests/artifact_config_test.rs index 65022abe..e072c794 100644 --- a/xlsynth-sys/tests/artifact_config_test.rs +++ b/xlsynth-sys/tests/artifact_config_test.rs @@ -215,9 +215,11 @@ fn run_nested_cargo_with_artifact_config( temp_crate_dir: &Path, target_dir: &Path, artifact_config_path: &OsStr, + link_mode: Option<&str>, ) -> std::process::Output { let cargo_binary = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); - Command::new(&cargo_binary) + let mut command = Command::new(&cargo_binary); + command .arg("run") .arg("--quiet") .arg("--offline") @@ -226,14 +228,16 @@ fn run_nested_cargo_with_artifact_config( .env("XLSYNTH_ARTIFACT_CONFIG", artifact_config_path) .env("XLS_DSO_PATH", "/definitely/not/the/configured/libxls.so") .env("DSLX_STDLIB_PATH", "/definitely/not/the/configured/stdlib/") - .current_dir(temp_crate_dir) - .output() - .unwrap_or_else(|err| { - panic!( - "Failed to run nested cargo for artifact-config test: {}", - err - ) - }) + .current_dir(temp_crate_dir); + if let Some(link_mode) = link_mode { + command.env("XLSYNTH_SYS_LINK_MODE", link_mode); + } + command.output().unwrap_or_else(|err| { + panic!( + "Failed to run nested cargo for artifact-config test: {}", + err + ) + }) } #[test] @@ -266,6 +270,7 @@ fn artifact_config_resolves_relative_toml_paths_from_absolute_config_path() { &temp_crate_dir, &target_dir, config_path.as_os_str(), + None, ); let stdout = String::from_utf8_lossy(&output.stdout); @@ -333,6 +338,7 @@ fn artifact_config_requires_absolute_config_path() { &temp_crate_dir, &target_dir, OsStr::new("config/artifact-config.toml"), + None, ); let stdout = String::from_utf8_lossy(&output.stdout); @@ -364,3 +370,81 @@ fn artifact_config_requires_absolute_config_path() { fs::remove_dir_all(&temp_dir).ok(); } + +#[test] +fn artifact_config_declared_mode_rejects_non_library_dso_paths() { + let temp_dir = make_temp_dir(); + let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let temp_crate_dir = temp_dir.join("artifact-config-declared-invalid-dso"); + let config_root_dir = temp_dir.join("config-root"); + let config_artifacts_dir = config_root_dir.join("artifacts"); + let (_expected_dso_path, expected_stdlib_path) = copy_config_artifacts(&config_artifacts_dir); + let invalid_dso_dir = config_artifacts_dir.join("not-a-lib-dir"); + fs::create_dir_all(&invalid_dso_dir).unwrap_or_else(|err| { + panic!( + "Failed to create invalid DSO directory {}: {}", + invalid_dso_dir.display(), + err + ) + }); + let config_path = config_root_dir.join("artifact-config.toml"); + write_artifact_config( + &config_path, + invalid_dso_dir + .strip_prefix(&config_root_dir) + .unwrap_or_else(|err| { + panic!( + "Failed to relativize invalid DSO directory {} against {}: {}", + invalid_dso_dir.display(), + config_root_dir.display(), + err + ) + }) + .to_string_lossy() + .as_ref(), + expected_stdlib_path + .strip_prefix(&config_root_dir) + .unwrap_or_else(|err| { + panic!( + "Failed to relativize stdlib path {} against {}: {}", + expected_stdlib_path.display(), + config_root_dir.display(), + err + ) + }) + .to_string_lossy() + .as_ref(), + ); + write_smoke_crate(&temp_crate_dir, &manifest_path); + + let target_dir = temp_dir.join("declared-invalid-dso-target"); + let output = run_nested_cargo_with_artifact_config( + &temp_crate_dir, + &target_dir, + config_path.as_os_str(), + Some("declared"), + ); + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + !output.status.success(), + "Nested cargo run unexpectedly succeeded with a directory DSO path in declared mode.\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + stderr.contains("Explicit XLS DSO path must end with a shared library extension"), + "Nested cargo stderr did not explain the invalid explicit DSO path.\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + assert!( + !stderr.contains("Skipping native link directives"), + "Declared-mode validation should fail before the build script reports skipped link directives.\nstdout:\n{}\nstderr:\n{}", + stdout, + stderr + ); + + fs::remove_dir_all(&temp_dir).ok(); +} From fae5850379c6868b8e228d703a636209db56f2b4 Mon Sep 17 00:00:00 2001 From: dank-openai Date: Thu, 5 Mar 2026 22:51:34 -0500 Subject: [PATCH 5/5] xlsynth-sys: honor declared link mode for managed artifacts --- xlsynth-sys/build.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/xlsynth-sys/build.rs b/xlsynth-sys/build.rs index 8ee078aa..954e4a38 100644 --- a/xlsynth-sys/build.rs +++ b/xlsynth-sys/build.rs @@ -681,6 +681,28 @@ fn emit_explicit_artifact_override( } } +fn emit_link_directives_for_managed_dso( + out_dir: &Path, + link_name: &str, + include_rpath: bool, + link_directive_mode: LinkDirectiveMode, +) { + match link_directive_mode { + LinkDirectiveMode::Native => { + if include_rpath { + println!("cargo:rustc-link-arg=-Wl,-rpath,{}", out_dir.display()); + } + println!("cargo:rustc-link-search=native={}", out_dir.display()); + println!("cargo:rustc-link-lib=dylib={link_name}"); + } + LinkDirectiveMode::Declared => { + println!( + "cargo:info=Skipping native link directives because XLSYNTH_SYS_LINK_MODE=declared" + ); + } + } +} + /// Downloads the dynamic shared object for XLS from the release page if it does /// not already exist. fn download_dso_if_dne(url_base: &str, out_dir: &Path) -> DsoInfo { @@ -946,10 +968,8 @@ fn main() { dso_dest.display() ); - println!("cargo:rustc-link-arg=-Wl,-rpath,{}", out_dir.display()); - println!("cargo:rustc-link-search=native={}", out_dir.display()); let dso_name_str = dso_info.get_dso_name(); - println!("cargo:rustc-link-lib=dylib={dso_name_str}"); + emit_link_directives_for_managed_dso(&out_dir, &dso_name_str, true, link_directive_mode); write_artifact_paths_rs( &out_dir, &ArtifactPaths { @@ -964,9 +984,8 @@ fn main() { let dso_info = download_dso_if_dne(&url_base, &out_dir); // Ensure the DSO is copied to the correct location - println!("cargo:rustc-link-search=native={}", out_dir.display()); let dso_name_str = dso_info.get_dso_name(); - println!("cargo:rustc-link-lib=dylib={dso_name_str}"); + emit_link_directives_for_managed_dso(&out_dir, &dso_name_str, false, link_directive_mode); // This is exposed as `xlsynth_sys::XLS_DSO_PATH` from Rust via the // generated `artifact_paths.rs` include in `xlsynth-sys/src/lib.rs`.