From 03201a963edda88311faeda781ab7349e9f1b1b9 Mon Sep 17 00:00:00 2001 From: Gavin John Date: Fri, 26 Jun 2026 01:20:46 -0400 Subject: [PATCH] feat: optionally use system liblsl --- lsl-sys/Cargo.toml | 13 ++++- lsl-sys/build.rs | 119 +++++++++++++++++++++++++++++++++++---------- lsl-sys/src/lib.rs | 12 +++++ 3 files changed, 116 insertions(+), 28 deletions(-) diff --git a/lsl-sys/Cargo.toml b/lsl-sys/Cargo.toml index 65b8062..94c7e6a 100644 --- a/lsl-sys/Cargo.toml +++ b/lsl-sys/Cargo.toml @@ -10,5 +10,16 @@ keywords = ["lsl", "ffi"] categories = ["external-ffi-bindings"] links = "lsl" +[features] +system = ["dep:pkg-config", "dep:vcpkg"] +bundled = ["dep:cmake"] +bindgen = ["dep:bindgen"] +default = ["system", "bundled", "bindgen"] + [build-dependencies] -cmake = "0.1.44" +cmake = { version = "0.1.44", optional = true } +bindgen = { version = "0.72", optional = true } +pkg-config = { version = "0.3", optional = true } + +[target.'cfg(windows)'.build-dependencies] +vcpkg = { version = "0.2", optional = true } diff --git a/lsl-sys/build.rs b/lsl-sys/build.rs index 0747fa8..b337d27 100644 --- a/lsl-sys/build.rs +++ b/lsl-sys/build.rs @@ -1,29 +1,80 @@ use std::env; +use std::path::{Path, PathBuf}; fn main() { - // TODO: find out if liblsl already present on system and usable (if so, link to that instead) - // println!("cargo:warning={}", "rebuilding..."); - build_liblsl(); + let include_dirs = resolve_lsl(); + + #[cfg(feature = "bindgen")] + { + let header = find_header(&include_dirs, "lsl_c.h").unwrap_or_else(|| { + panic!( + "lsl_c.h not found in any resolved include dir: {include_dirs:?}.\n\ + If liblsl is installed in a non-standard location, make sure pkg-config \ + can find its .pc file (set PKG_CONFIG_PATH), or on Windows that vcpkg \ + knows about the 'lsl' port." + ) + }); + run_bindgen(&header, &include_dirs); + } + + let _ = include_dirs; +} + +#[cfg(all(feature = "system", feature = "bundled"))] +fn resolve_lsl() -> Vec { + try_system().unwrap_or_else(build_liblsl) +} + +#[cfg(all(feature = "system", not(feature = "bundled")))] +fn resolve_lsl() -> Vec { + try_system().unwrap_or_else(|| { + panic!( + "liblsl not found via pkg-config (or vcpkg on Windows). Install liblsl's \ + development package, or enable the 'bundled' feature to build it from source." + ) + }) } -// Build the liblsl library from source using cmake -fn build_liblsl() { +#[cfg(all(not(feature = "system"), feature = "bundled"))] +fn resolve_lsl() -> Vec { + build_liblsl() +} + +#[cfg(not(any(feature = "system", feature = "bundled")))] +fn resolve_lsl() -> Vec { + compile_error!("at least one of the 'system' or 'bundled' features must be enabled") +} + +#[cfg(feature = "system")] +fn try_system() -> Option> { + println!("cargo:rerun-if-env-changed=PKG_CONFIG_PATH"); + + if let Ok(lib) = pkg_config::Config::new().probe("lsl") { + return Some(lib.include_paths); + } + + #[cfg(windows)] + if let Ok(lib) = vcpkg::find_package("lsl") { + return Some(lib.include_paths); + } + + None +} + +fn find_header(dirs: &[PathBuf], name: &str) -> Option { + dirs.iter().map(|d| d.join(name)).find(|p| p.exists()) +} + +#[cfg(feature = "bundled")] +fn build_liblsl() -> Vec { let target = env::var("TARGET").unwrap(); - - // build with cmake + let mut cfg = cmake::Config::new("liblsl"); - cfg - .define("LSL_NO_FANCY_LIBNAME", "ON") + cfg.define("LSL_NO_FANCY_LIBNAME", "ON") .define("LSL_BUILD_STATIC", "ON"); if target.contains("msvc") { - // override some C/CXX flags that the cmake crate splices in on Windows - // (these cause the build to fail)... - // * /EHsc sets the correct exception handling mode - // * /GR enables RTTI - // * /MD links in the msvcrt as a DLL instead of statically let cxx_args = " /nologo /EHsc /MD /GR"; - cfg - .define("WIN32", "1") + cfg.define("WIN32", "1") .define("_WINDOWS", "1") .define("CMAKE_C_FLAGS", cxx_args) .define("CMAKE_CXX_FLAGS", cxx_args) @@ -34,23 +85,37 @@ fn build_liblsl() { } let install_dir = cfg.build(); - // emit link directives let libdir = install_dir.join("lib"); - let libname = "lsl"; - println!( - "cargo:rustc-link-search=native={}", - libdir.to_str().unwrap() - ); - println!("cargo:rustc-link-lib=static={}", libname); - - // make sure we also link some additional libs + println!("cargo:rustc-link-search=native={}", libdir.display()); + println!("cargo:rustc-link-lib=static=lsl"); + if target.contains("linux") { println!("cargo:rustc-link-lib=dylib=stdc++"); } else if target.contains("windows") { - // TODO: this is a shortcoming in the current cmake file, which should be - // linking in this library (once this is fixed, we should remove this) println!("cargo:rustc-link-lib=dylib=bcrypt"); } else { println!("cargo:rustc-link-lib=dylib=c++"); } + + vec![install_dir.join("include")] +} + +#[cfg(feature = "bindgen")] +fn run_bindgen(header: &Path, include_dirs: &[PathBuf]) { + let mut builder = bindgen::Builder::default() + .header(header.to_str().expect("header path is not valid UTF-8")) + .allowlist_function("^lsl_.*") + .allowlist_var("^lsl_.*") + .allowlist_type("^lsl_.*"); + + for dir in include_dirs { + builder = builder.clang_arg(format!("-I{}", dir.display())); + } + + let bindings = builder.generate().expect("failed to generate liblsl bindings"); + + let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join("generated.rs"); + bindings + .write_to_file(&out) + .expect("failed to write liblsl bindings"); } diff --git a/lsl-sys/src/lib.rs b/lsl-sys/src/lib.rs index 2c5467b..ef7caf5 100644 --- a/lsl-sys/src/lib.rs +++ b/lsl-sys/src/lib.rs @@ -1,7 +1,19 @@ #[allow(non_camel_case_types)] #[allow(non_upper_case_globals)] +#[cfg(not(feature = "bindgen"))] mod generated; +#[allow(non_camel_case_types)] +#[allow(non_upper_case_globals)] +#[cfg(feature = "bindgen")] +mod generated { + include!(concat!(env!("OUT_DIR"), "/generated.rs")); +} + +#[cfg(feature = "system")] +#[link(name = "lsl")] +extern "C" {} + pub use generated::*; #[cfg(test)]