Rust handles libraries that it links with in a somewhat "magical" way, in that if you want to link to
libpixman-1.so.0
you would just write:
println!("cargo:rustc-link-lib=pixman-1");
in your build.rs
script. This can be nice, but it can also be pretty frustrating if you want to be explicit.
Of course, you can also write:
println!("cargo:rustc-link-arg=/lib64/libpixman-1.so.0");
to link it explicitly by absolute path, or println!("cargo:rustc-link-lib=dylib:+verbatim=libpixman-1.so.0");
to let the linker find it, but tell it what to find more precisely.
For no reason and definitely not because I was reimplementing a hacky form of the (recently hotly controversial)
artifact-dependencies I recently needed to know for each platform
and crate target type, what file name format would be produced for build artifacts. Here's the answer [(d)
=inherits the default]:
Platform Spec | DLL Prefix | DLL Suffix | EXE Suffix | Staticlib Prefix | Staticlib Suffix |
---|---|---|---|---|---|
Default | lib (d) |
.so (d) | lib (d) | .a (d) | |
MSVC | .dll | .exe | .lib | ||
Windows GNU | .dll | .exe | lib (d) | .a (d) | |
WASM | lib (d) |
.wasm | .wasm | lib (d) | .a (d) |
AIX | lib (d) |
.a | lib (d) | .a (d) | |
Apple | lib (d) |
.dylib | lib (d) | .a (d,framework?) | |
NVPTX | .ptx | .ptx | lib (d) | .a (d) | |
Windows GNULLVM | .dll | .exe | lib (d) | .a (d) | |
Windows DLL Import Lib | _imports.dll or |
||||
_imports_redirect.dll |
The only real way to figure this out is to dig into rustc
pretty substantially. First, we can find the
generic answer, which is the innocuous filename_for_input
function in
output.rs. This
tells us how things are formatted:
match crate_type {
CrateType::Rlib => outputs.out_directory.join(&format!("lib{libname}.rlib")),
CrateType::Cdylib | CrateType::ProcMacro | CrateType::Dylib => {
let (prefix, suffix) = (&sess.target.dll_prefix, &sess.target.dll_suffix);
outputs.out_directory.join(&format!("{prefix}{libname}{suffix}"))
}
CrateType::Staticlib => {
let (prefix, suffix) = (&sess.target.staticlib_prefix, &sess.target.staticlib_suffix);
outputs.out_directory.join(&format!("{prefix}{libname}{suffix}"))
}
CrateType::Executable => {
let suffix = &sess.target.exe_suffix;
let out_filename = outputs.path(OutputType::Exe);
if suffix.is_empty() { out_filename } else { out_filename.with_extension(&suffix[1..]) }
}
}
but it doesn't tell us what the dll_prefix
, staticlib_prefix
, etc.
values actually are.
To find that out, we need to look at spec/mod.rs. This is a huge file, but:
impl Default for TargetOptions {
fn default() -> TargetOptions {
TargetOptions {
/* ...a ton of fields... */
dll_prefix: "lib".to_string(),
dll_suffix: ".so".to_string(),
exe_suffix: String::new(),
staticlib_prefix: "lib".to_string(),
staticlib_suffix: ".a".to_string(),
/* ...a ton *more* fields... */
}
}
}
Now we're getting somewhere. If we just trawl through this file from "most-base" to "most-specialized", we can figure out all the places the defaults aren't used and therefore what each platform is using. The only thing we don't know yet is how this is handled for dll import libs. That's elsewhere, in the LLVM codegen code:
let name_suffix = if is_direct_dependency { "_imports" } else { "_imports_indirect" };
let output_path = {
let mut output_path: PathBuf = tmpdir.to_path_buf();
output_path.push(format!("{}{}", lib_name, name_suffix));
output_path.with_extension("lib")
};
Nice.