Testing *-sys crates

Hi, I am trying to run some tests in a crate that uses a custom *-sys. Here's how my folder tree is organized:

tcl_tk-rs/
    - src/
    - Cargo.toml
    - tests/
    - tcl_tk-sys/
        - src/
        - build.rs
        - Cargo.toml

My tcl_tk-rs/Cargo.toml is

[package]
name = "tk-rs"
version = "0.1.0"
authors = ["Darley Barreto"]
edition = "2018"

[lib]
name = "tcl_tk"
path = "src/lib.rs"

[dependencies]
num-bigint = "0.3"

[dependencies.tcl_tk-sys]
path = "tcl_tk-sys"
version = "0.1.0"

My tcl_tk-rs/tcl_tk-sys/Cargo.toml is

[package]
name = "tcl_tk-sys"
version = "0.1.0"
authors = ["Darley Barreto"]
edition = "2018"
links = "tcl8.6"
build = "build.rs"

[dependencies]
libc = "*"

[build-dependencies]
pkg-config = "0.3.19"

[lib]
name = "tcl_tk_sys"
path = "src/lib.rs"

And my build.rs is

use pkg_config;

fn main() {
    if !build_pkgconfig() {
        println!("cargo:rustc-flags=-l tcl8.6");
        println!("cargo:rustc-flags=-l tk8.6");
    }
}

fn build_pkgconfig() -> bool {
    let conf = pkg_config::Config::new();

    if (&conf.probe("tcl8.6")).is_err() || (&conf.probe("tk8.6")).is_err() {
        print!("Could not find Tcl or Tk via pkgconfig");
        false
    } else {
        true
    }
}

Building is fine, but when I do cargo test lots of cc errors (undefined reference to related to symbols) show up. What am I doing wrong?

Thanks in advance!

Your build script could be failing. You can’t rely on print! or println! macros. It’s better to panic with a message. Also regarding rustc-flags, I’m not sure these are passed to the linker. Why not use rustc-link-lib?
I also noticed a lib name discrepancy "tcl_tk_sys", it could just be a typo. It’s also unusual to see "so numbers" passed directly outside the context of dl-loading.

I've mainly seen rustc-link-lib used for linking instead of rustc-flags=-l. I'm not sure if there's a material difference between these two.

When developing build scripts, build with cargo build -vvv. Otherwise Cargo "eats" script's output and you won't see what is happening.

You can also use:

println!("cargo:warning=Could not find Tcl or Tk via pkgconfig")

to make the text visible to users on failure. However, failure happens only if your build script returns failing exit code (e.g. panics). If you just set the flags and hope for the best, it will fail at link time with a horrible unreadable huge dump of hundreds of command-line flags. It's better to complain loudly early.

Hi, I'm really new to Rust, so there are some things I don't know, for instance:

Why not use rustc-link-lib

What would this be?

I also noticed a lib name discrepancy "tcl_tk_sys", it could just be a typo

If you mean this in the tcl_tk-rs/tcl_tk-sys/Cargo.toml:

[lib]
name = "tcl_tk_sys"
path = "src/lib.rs"

It wasnt, I wrote that so I can use

use tcl_tk_sys::*;

It’s also unusual to see "so numbers" passed directly outside the context of dl-loading

You mean links = "tcl8.6" in the Cargo.toml?

println!("cargo:warning=Could not find Tcl or Tk via pkgconfig")

It does say warning: Could not find Tcl or Tk via pkgconfig, but nothing else. And I've changed to

fn main() {
    if !build_pkgconfig() {
        println!("cargo:rustc-link-lib=dylib=tcl8.6");
        println!("cargo:rustc-link-lib=dylib=tk8.6");
    }
}

Cargo prints warning lines even without -vvv if your script panics. Your script doesn't panic, and falls back to flags that will fail at link time, which is too late to correlate that failure with your script, so user will not get the warning.

I suggest: don't print the link directives unless you've proven that they will work. Check if the files exist, or require an explicit opt-in, e.g. env var for TCL_LIB_DIR that you set as the search path first.

Don't set up linking for failure, because that's an error you can't handle in your script, and that error is handled very very poorly by Rust (it basically does nothing about it, and lets system linker just tell "f*** you" to the user).

Hmm, I see. Another option that failed was:

use pkg_config;

fn main() {
    let mut config = pkg_config::Config::new();
    let epec = config.exactly_version("8.6");

    if epec.probe("tcl").is_ok(){
        panic!("Could not find tcl8.6 using pkg-config");
    }

    if epec.probe("tk").is_ok(){
        panic!("Could not find tk8.6 using pkg-config");
    }
}

But then this worked for me

fn main() {
    println!("cargo:rustc-link-lib=dylib=tcl8.6");
    println!("cargo:rustc-link-lib=dylib=tk8.6");
}

What would you recommend to do?

In your code is_ok() makes the condition backwards. Also epec.probe("tk").expect("Could not find tcl8.6 using pkg-config") does the same thing, but shorter.

It is possible that you have the library, but don't have .pc files that describe where it's installed. That's tough, because just setting the linker flag doesn't give you any chance to handle the case when the library is missing.

I suggest:

if pkg_config_didnt_work {
   if let Ok(dir) = std::env::var("TCL_LIB_DIR") {
      println!("cargo:rustc-link-search={}", dir);
      println!("cargo:rustc-link-lib=dylib=tcl8.6");
   } else {
      panic!("Can't find tcl via pkg-config. Set TCL_LIB_DIR env var for manual dir");
   }
}
1 Like

Thank you so much for this :smiley: