Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get list of active dependencies and their versions during "cargo build"

Some crates offer a pub const &str-version string, some do not. To have a general solution, I need a list of all dependencies and their versions as known to and used by cargo build during compilation so I can build my own const &str of This is my own version and all the versions I was compiled with-Debug output.

Is it possible to get a list of all dependencies and their version in build.rs ?

Cargo.lock seems to be a good source. Is it actually sound to parse Cargo.lock in build.rs? Is it guaranteed to have been updated to what Cargo actually uses and written to disk?

like image 722
user2722968 Avatar asked Jan 12 '17 16:01

user2722968


1 Answers

My own answer, solved by parsing Cargo.lock. The following gives us a list of dependencies and dependencies of dependencies - every crate that ended up getting linked somehow (lto besides).

In Cargo.toml:

[package]
name = "mycrate"
version = "0.1.0"
authors = ["John Doe"]
build = "build.rs"

[build-dependencies]
toml = "0.2"

In build.rs:

extern crate toml;

use std::env;
use std::fs;
use std::path;
use std::io::{Read, Write};

fn main() {
    // Read Cargo.lock and de-toml it
    let mut lock_buf = String::new();
    fs::File::open("Cargo.lock").unwrap().read_to_string(&mut lock_buf).unwrap();
    let lock_toml = toml::Parser::new(&lock_buf).parse().unwrap();

    // Get the table of [[package]]s. This is the deep list of dependencies and dependencies of
    // dependencies.
    let mut packages = Vec::new();
    for package in lock_toml.get("package").unwrap().as_slice().unwrap() {
        let package = package.as_table().unwrap();
        packages.push((package.get("name").unwrap().as_str().unwrap(),
                       package.get("version").unwrap().as_str().unwrap()));
    }
    packages.sort();

    // Write out the file to be included in the module stub
    let out_dir = env::var("OUT_DIR").unwrap();
    let mut versions_file = fs::File::create(&path::Path::new(&out_dir).join("versions.include")).unwrap();
    versions_file.write(format!("pub const BUILD_DEPS: [(&'static str, &'static str); {}] = [", packages.len()).as_ref()).unwrap();
    for package in packages {
        versions_file.write(format!("(\"{}\", \"{}\"),\n", package.0, package.1).as_ref()).unwrap();
    }
    versions_file.write("];".as_ref()).unwrap();
}

In src/versions.rs:

//! Information about the build-environment

// More info from env!() and friends go here

include!(concat!(env!("OUT_DIR"), "/versions.include"));

In src/main.rs or wherever needed:

mod versions;

fn main() {
    println!("I was built using {}", versions::BUILD_DEPS.iter().map(|&(ref pkg, ref ver)| format!("{} {}", pkg, ver)).collect::<Vec<_>>().join(", "));
}

Output is then like

I was built using android_glue 0.2.1, bitflags 0.3.3, bitflags 0.4.0, bitflags 0.6.0, bitflags 0.7.0, block 0.1.6, byteorder 0.5.3, bytes 0.3.0, cfg-if 0.1.0, cgl 0.1.5, cgmath 0.7.0, clippy 0.0.104 ...

Of course, we could build a string-like representation at compile time, having it neatly separated gives the opportunity to parse this information at runtime, though. As far as I can see this works if Cargo.lock is not present: Cargo always generates it before build.rs is run.

like image 190
user2722968 Avatar answered Sep 25 '22 03:09

user2722968