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".
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.
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
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.
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();