Skip to content

Instantly share code, notes, and snippets.

@novafacing
Last active August 31, 2023 00:31
Show Gist options
  • Save novafacing/1389cbb2f0a362d7eb103e67b4468e2b to your computer and use it in GitHub Desktop.
Save novafacing/1389cbb2f0a362d7eb103e67b4468e2b to your computer and use it in GitHub Desktop.
Rust's Expected/Produced Library naming

Rust's expected/produced library naming

What rustc/your linker expects

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.

What rustc produces

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment