This document gives a high level overview of Cargo internals. You may find it useful if you want to contribute to Cargo or if you are interested in the inner workings of Cargo.
The purpose of Cargo is to formalize a canonical Rust workflow, by automating the standard tasks associated with distributing software. Cargo simplifies structuring a new project, adding dependencies, writing and running unit tests, and more.
Cargo is a single binary composed of a set of
clap subcommands. All subcommands live in
src/bin/cargo/main.rs is the entry point.
Each subcommand, such as
src/bin/cargo/commands/build.rs, has its own API
interface, similarly to Git's, parsing command line options, reading the
configuration files, discovering the Cargo project in the current directory and
delegating the actual implementation to one
of the functions in
src/cargo/ops/mod.rs. This short file is a good
place to find out about most of the things that Cargo can do.
Subcommands are designed to pipe to one another, and custom subcommands make
Cargo easy to extend and attach tools to.
Important Data Structures
There are some important data structures which are used throughout Cargo.
Config is available almost everywhere and holds "global"
information, such as
CARGO_HOME or configuration from
.cargo/config files. The
shell method of
Config is the entry
point for printing status messages and other info to the console.
Workspace is the description of the workspace for the current
working directory. Each workspace contains at least one
Package. Each package corresponds to a single
Cargo.toml, and may
Targets, such as the library, binaries, integration
test or examples. Targets are crates (each target defines a crate
examples/foo.rs) and are what is actually
A typical package defines the single library target and several
auxiliary ones. Packages are a unit of dependency in Cargo, and when
foo depends on package
bar, that means that each target
foo needs the library target from
PackageId is the unique identifier of a (possibly remote)
package. It consist of three components: name, version and source
id. Source is the place where the source code for package comes
from. Typical sources are crates.io, a git repository or a folder on
the local hard drive.
Resolve is the representation of a directed acyclic graph of package
dependencies, which uses
PackageIds for nodes. This is the data
structure that is saved to the lock file. If there is no lock file,
Cargo constructs a resolve by finding a graph of packages which
matches declared dependency specification according to semver.
Cargo is a non-daemon command line application, which means that all
the information used by Cargo must be persisted on the hard drive. The
main sources of information are
.cargo/config configuration files and the globally shared registry
of packages downloaded from crates.io, usually located at
src/cargo/sources/registry for the specifics of
the registry storage format.
Cargo is mostly single threaded. The only concurrency inside a single
instance of Cargo happens during compilation, when several instances
rustc are invoked in parallel to build independent
targets. However there can be several different instances of Cargo
process running concurrently on the system. Cargo guarantees that this
is always safe by using file locks when accessing potentially shared
data like the registry or the target directory.
Cargo has an impressive test suite located in the
tests folder. Most
of the test are integration: a project structure with
rust source code is created in a temporary directory,
is invoked via
std::process::Command and then stdout and stderr are
verified against the expected output. To simplify testing, several
macros of the form
[MACRO] are used in the expected output. For
[..] matches any string.
To see stdout and stderr streams of the subordinate process, add
call to the built-up
// Before p.cargo("run").run(); // After p.cargo("run").stream().run();
Alternatively to build and run a custom version of cargo simply run
target/debug/cargo. Note that
+stable (and variants),
being rustup features, won't work when executing the locally
built cargo binary directly, you have to instead build with
cargo +nightly build
and run with
rustup run (e.g
rustup run nightly <path-to-cargo>/target/debug/cargo <args>..) (or set the
RUSTC env var to point
to nightly rustc).
env_logger, so you can set
CARGO_LOG environment variable to get the logs. This is useful both for diagnosing
bugs in stable Cargo and for local development. Cargo also has internal hierarchical
profiling infrastructure, which is activated via
# Outputs all logs with levels debug and higher $ CARGO_LOG=debug cargo generate-lockfile # Don't forget that you can filter by module as well $ CARGO_LOG=cargo::core::resolver=trace cargo generate-lockfile # Output first three levels of profiling info $ CARGO_PROFILE=3 cargo generate-lockfile