Is there any way to figure out the path where libraries and header files are installed (ideally cross-platform)?
I'm asking because even if I require the user of the crate to specify the Lua version, I would like to check that the particular version is installed and where it is installed. Currently, the user of the crate must specify LUA_INCLUDE
, LUA_LIB
, and LUA_LIBNAME
environment variables.
On FreeBSD, for example, I must set:
LUA_INCLUDE = /usr/local/include/lua53/
LUA_LIB = /usr/local/lib
LUA_LIBNAME = lua5.3
But for Ubuntu 20.4 LTS (which is used by the docs.rs build system), it should be:
LUA_INCLUDE = /usr/include/lua5.3
LUA_LIB = /usr/lib/x86_64-linux-gnu
LUA_LIBNAME = lua5.3
But how do I know I need to search for liblua5.3.a
or liblua5.3.so
(or liblua5.4.a or liblua5.4.so
, respectively) in /usr/lib/x64_64-linux-gnu
?
That's part of my build script as of now:
fn get_env(key: &str) -> Option<String> {
/* … */
}
fn get_env_default<'a>(key: &'_ str, default: &'a str) -> std::borrow::Cow<'a, str> {
/* … */
}
#[cfg(not(any(feature = "Lua-5.3", feature = "Lua-5.4")))]
compile_error!("must specify feature \"Lua-5.3\" or \"Lua-5.4\"");
fn main() {
#[cfg(feature = "Lua-5.3")]
const MINOR_VERSION: &'static str = "3";
#[cfg(feature = "Lua-5.4")]
const MINOR_VERSION: &'static str = "4";
// get configuration from environment
let lua_include = get_env("LUA_INCLUDE");
let lua_lib = get_env("LUA_LIB");
let lua_libname = get_env_default("LUA_LIBNAME", "lua");
// how to search these automatically?
/* … */
}
P.S.: Apparently the shell command pkg-config --cflags --libs lua-5.4
gives promising results both on Linux and FreeBSD at least. Not sure yet how to integrate that into my build script (or whether the cc
crate offers a more "Rusty" interface, but I don't think it does).
P.P.S.: Maybe the pkg-config
crate is my friend.
This got a bit off-topic, but in case someone is interested in the answer to my last question, I finally ended up doing this in my build.rs
script:
use std::mem::take;
use std::process::{Command, Stdio};
#[cfg(not(any(feature = "Lua5_3", feature = "Lua5_4")))]
compile_error!("must specify feature \"Lua5_3\" or \"Lua5_4\"");
fn main() {
let mut include_dirs: Vec<String> = Vec::new();
let mut lib_dirs: Vec<String> = Vec::new();
let mut lib_names: Vec<String> = Vec::new();
// use `pkg-config` binary to determine Lua library name and location
{
#[cfg(feature = "Lua5_3")]
const PKG_NAME: &'static str = "lua-5.3";
#[cfg(feature = "Lua5_4")]
const PKG_NAME: &'static str = "lua-5.4";
#[cfg(not(any(feature = "Lua5_3", feature = "Lua5_4")))]
const PKG_NAME: &'static str = unreachable!();
let mut pkgcnf_cmd = Command::new("pkg-config");
pkgcnf_cmd
.args(["--cflags", "--libs", PKG_NAME])
.stderr(Stdio::inherit());
eprintln!("using pkg-config command: {pkgcnf_cmd:?}");
let pkgcnf = pkgcnf_cmd.output().expect("could not execute pkg-config");
eprintln!("pkg-config status: {:?}", pkgcnf.status);
eprintln!(
"pkg-config stdout: {:?}",
String::from_utf8_lossy(&pkgcnf.stdout)
);
if !pkgcnf.status.success() {
panic!("pkg-config returned with failure");
}
let mut parse_element = |s: String| {
if !s.is_empty() {
if s.len() >= 2 {
let prefix = &s[0..2];
let value = &s[2..];
match prefix {
"-I" => include_dirs.push(value.to_string()),
"-L" => lib_dirs.push(value.to_string()),
"-l" => lib_names.push(value.to_string()),
_ => (),
}
}
}
};
let mut element: String = Default::default();
let mut escape: bool = false;
for ch in String::from_utf8(pkgcnf.stdout)
.expect("invalid UTF-8 from pkg-config")
.chars()
{
if escape {
element.push(ch);
escape = false;
} else if ch == '\\' {
escape = true;
} else if ch.is_ascii_whitespace() {
parse_element(take(&mut element));
} else {
element.push(ch);
}
}
if escape {
panic!("unexpected EOF from pkg-config (escape character at end)");
}
parse_element(element);
if lib_names.is_empty() {
panic!("pkg-config did not return any library name");
}
}
// create automatic bindings
{
let mut builder = bindgen::Builder::default();
for dir in &include_dirs {
builder = builder.clang_arg(format!("-I{dir}"));
}
builder = builder.header("src/cmach.c");
builder = builder.parse_callbacks(Box::new(bindgen::CargoCallbacks));
let bindings = builder.generate().expect("unable to generate bindings");
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("ffi_cmach.rs"))
.expect("unable to write bindings");
}
// build own C lib
{
println!("cargo:rerun-if-changed=src/cmach.c");
let mut config = cc::Build::new();
for dir in &include_dirs {
config.include(dir);
}
config.file("src/cmach.c");
config.compile("libffi_cmach.a");
}
// link with Lua
for dir in &lib_dirs {
println!("cargo:rustc-link-search=native={}", dir);
}
for name in &lib_names {
println!("cargo:rustc-link-lib={}", name);
}
}
Maybe it's not very elegant, but it will throw a nice error when no Lua version is selected and disallow both versions to be selected. Moreover, the correct include and library directories and library name is determined automatically.
After some struggles with (and headaches about) the crates.io/docs.rs/cargo workflow, I was also able to publish everything as a crate: sandkiste_lua
.
Regarding linking with different versions of the same C library at the same time, I opened a new thread here: