Skip to content

Instantly share code, notes, and snippets.

@not-fl3

not-fl3/cargo.md Secret

Last active August 14, 2021 04:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save not-fl3/4eefc5ffc4adc6b9c1ded88ea8bfeae6 to your computer and use it in GitHub Desktop.
Save not-fl3/4eefc5ffc4adc6b9c1ded88ea8bfeae6 to your computer and use it in GitHub Desktop.

Cargo, the rust build system. Usually it is used from CLI, something like

> cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.61s
  Hello World!

But cargo is also a rust library may be used as a dependency, like this:

...

[dependencies]
cargo = "0.55"

The purpose of cargo as dependency - is writing "subcommands".

Cargo Subcommands.

If there is a cargo-somename binary in your PATH - it may be invoked as cargo somename.

Those binaries should not be neceserily built in rust or whatever, there are no checks or limitations. Just a quick test:

> cat <<EOF > ~/.bin/cargo-hello
#!/bin/sh
echo "Hi from cargo!"
EOF

> chmod +x ~/.bin/cargo-hello

> cargo hello
Hi from cargo!

Haha, it actually works! There are plenty of "subcommands": https://github.com/rust-lang/cargo/wiki/Third-party-cargo-subcommands

All of them are made in rust, using cargo as a dependency. not it bash :( Being rust crates allows them to be installed with cargo install:

> cargo install cargo-expand
    Updating crates.io index
  Downloaded cargo-expand v1.0.8
  Downloaded 1 crate (22.5 KB) in 2.49s
  Installing cargo-expand v1.0.8
  ..
> cargo expand
  ..
  mod ioctl {
    #![allow(dead_code)]
  ..

All the installed subcommands may be reviewed with cargo --list:

> cargo --list
Installed Commands:
    b                    alias: build
    bench                Execute all benchmarks of a local package
    build                Compile a local package and all of its dependencies
    ..
    hello
    ..

Even my little bash cargo-hello made it to the list! :)

So it looks and feels like some sort of cargo extensions. To keep this consistent feel - most subcommands follows a similar to cargo itself CLI arguments schemes and, usually, are somehow extending the build process.

Introduction to cargo, the crate

Cargo, the crate, is a giant, scary looking monster. Just look at the docs: https://docs.rs/cargo/0.54.0/cargo/. It allows to do anything cargo do, but from rust code, customizing and overriding each step.

It can do really lots of things, but I needed very few of them. My goal was:

  • build a dependency graph and go through some dependencies source code and collect certain artifacts from those repos, before the compilation process
  • build the project for certain platforms with certain arguments and get the build artifacts
  • grab those build artifacts, post process them for a little and put to special folder

Workspace intialisation

Most API calls work with a "Workspace", so the first step - get an instance of a Workspace.

https://docs.rs/cargo/0.54.0/cargo/core/struct.Workspace.html#method.new

root_manifest is a path to project's Cargo.toml (it may be Cargo workspaces definition). config is a Cargo.toml's runtime counterpart - all the environment variables and pathes required for cargo to work.

let mut config = util::Config::default().unwrap();

config
    .configure(0, false, None, false, false, false, &None, &[], &[])
    .unwrap();

let root_manifest = find_root_manifest_for_wd(config.cwd().unwrap());

let ws = core::Workspace::new(&root_manifest, &config).unwrap();

Intresting here: CargoConfig::default() looks like a pure function. While it is not - it will go an grab tons of environment variables, like current pwd, home directory, cargo's(the binary) executible path etc.

So calling .cwd() on something just built with ::default() is totally fine.

Some snippets

From now on nothing really intresting to tell, just a code I spent quite some time looking for.

This snippet - how to run a compilation process.

    let jobs = None;
    let compile_mode = CompileMode::Build;
    let build_config = BuildConfig::new(
        &config,
        jobs,
        &["wasm32-unknown-unknown".to_string()],
        compile_mode,
    )
    .unwrap();

    let bins = vec![];
    let examples = vec![];
    let compile_opts = ops::CompileOptions {
        build_config,
        cli_features: CliFeatures::new_all(false),
        spec: ops::Packages::Default,
        filter: ops::CompileFilter::from_raw_arguments(
            false,
            bins,
            false,
            vec![],
            false,
            examples,
            false,
            vec![],
            false,
            false,
        ),
        target_rustdoc_args: None,
        target_rustc_args: None,
        local_rustdoc_args: None,
        rustdoc_document_private_items: false,
        honor_rust_version: false,
    };

    let compiled = ops::compile(&ws, &compile_opts).unwrap();

How to ask cargo to build in release mode:

    let build_config = BuildConfig::new(
        &config,
        jobs,
        &["wasm32-unknown-unknown".to_string()],
        compile_mode,
    )
    .unwrap();
build_config.requested_profile = "release".to_string();   

This - how to get all the dependencies from dependencies graph:

    let targets = &[CompileKind::Target(
        CompileTarget::new("wasm32-unknown-unknown").unwrap(),
    )];
    let target_data = RustcTargetData::new(&ws, targets).unwrap();

    let cli_features = CliFeatures::new_all(false);

    let specs = ops::Packages::Default.to_package_id_specs(&ws).unwrap();

    let ws_resolve = ops::resolve_ws_with_opts(
        &ws,
        &target_data,
        targets,
        &cli_features,
        &specs,
        HasDevUnits::No,
        ForceAllTargets::No,
    )
    .unwrap();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment