Rambling
- I just want a good workflow for wokring iwht large cargo packges on worktrees
- Particualrly conerning about are
- cold tests or builds that may take a long time on worktrees when the pre-warm cache exiisnt on the main worktree
- to avoid unnecessary cold builds or tests when working on linked work trees
- I heard that sscache is a good too get around this
- so i want to make sure to document a workflow that prevents unnecessary cold tests/builds/compiles… etc
New Vocab
2. target folder
/target- this is the folder that cargo will output its build and cache artificats too- Its comparable to the DBT
/targetfolder - It is comparable Next.js’
/.nextfodler (not to be confused with the/outfodler which contians the final static export - It has a few subfolders that amtter to use
/target/release- this is final build files so this is comaprable to next.js/outthis is gerneated bycargo build --release(compiles slow but runs fast)/target/debug- output of a plancargo build- compiles fast but runs slow/target/deps- hold compiled dependencies, which is most a cold build and wahtsccachecan share/target/incremental- caches own crates which incrementally. first build cache initialize cache for each crate
- Its comparable to the DBT
2. Files and modules primer
- Within a workspace there are two types of files
tomlandlockfiles whicha re config filesrsrust code base.rscode lives in a few packge folderssrc/,tests/,examples/or/benches
- Rust code take the form of either
- crate roots
- special file names -
src/lib.rs(one library crate) src/main.rsand eachsrc/bin/*.rs→ binary cratessrc/test/*.rsdeclare integration tests tests- others
- special file names -
- any other rs file are modules declared wiht the
modsytnax
- crate roots
- Files are any file within the
srcfolder moduleis an organizational concept to reference specific chunks of atomic code logic- Each module has a
path- there are two types of paths relative and abosulteabsolute pathfrom the crate rootcrate::parser::fast::parse
relative path- relative to which module you arefast::parseform insideparserorsuper::to go up one level
● my-workspace/ ← workspace root
├── Cargo.toml ← [workspace] members = ["mylib", "mycli"]
├── Cargo.lock ← shared by all packages
├── target/ ← shared build output + cache
├── mylib/ ← package "mylib"
│ ├── Cargo.toml ← name = "mylib"
│ ├── src/
│ │ ├── libr.rs ← library crate root: mod parser;
│ │ ├── parser.rs ← module parser: mod fast;
│ │ └── parser/
│ │ └── fast.rs ← module parser::fast → fn parse
│ └── tests/
│ └── api.rs ← integration test crate "api"
└── mycli/ ← package "mycli"
├── Cargo.toml ← name = "mycli", depends on mylib
└── src/
└── main.rs ← binary crate root, calls mylib::parser::fast::parse
- so analyzing the work tree above for paths
- Module
mylib/src/parser/fast.rs- Absolute path (starts at
lib.rs)- Module:
crate::parser::fast - Function:
crate::parser::fast::<function-name> - Test:
crate::parser::fast::tests::<test-name>
- Module:
- Relative path (starts at
parser.rs- its parent module)- Module:
fast - Function:
fast::<function-name> - Test:
fast::test::<test-name>
- Module:
- Crates
mylib/src/libr.rs- Absolute path
crate - From another crate its
mylib- this is for libary crates
- packges can refernece other pacakges library carte (one only iwht the apckag name like
mylib) - packages cannot use other packages binaries (.exe)
- Absolute path
- Absolute path (starts at
- Module
3. Test primer
- A test name is the test functions full path such as
parser::tests::parses_empty - There are several types of tests
unit testssit inline insrc/files inside amod testsintegration testsare files in the packages’test/folder- each of those files is its own crate, which
--test <fileselects
- each of those files is its own crate, which
- Both kinds are
#[test]functions, so both have names cargo test parsesruns every test whos name contains parses
4. What is sccache
sccacheis a rust tool that keeps one shared dependency compilation across worktrees- use it to prevent cold-recompile of dpendneces across worktrees
- It does not effect the crate compilation for your workspace
Understanding
- Triage - guess there are some core concepts i need to know before we cna really put things together Core questions 1 - How Cargo handles cache and efficient builds
- Core Questions 1 - How Cargo handles cache and efficient builds
- Core questions 2 - How Cargo handles cache and efficient tests
- Core questions 3 - How work trees introduce unnecessary cold computes
- Core questions 4 - How can we mitigate unnecessary cold computes for worktree workflows
- Core Questions 1 - How Cargo handles cache and efficient builds
cargo buildonly compiles crates- so a workspace has many crates - lets say 50 crates
- first time - cold build
- when you run cargo build for the first time
- each crate gets compiled the results are stored in targets/incremental and the outputs int o target/debug
- now the development starts
- lets say that one crate code base is changed
- you want to build it so you run cargo build
- cargo will be smart enough to look at a
targetartifact (target/debug/.fingerprint/) in as a reference to see what crates have changed - then only compile for the crates that have changed (and all the crates that depend on it)
- Core questions 2 - How Cargo handles cache and efficient tests
cargo testdoes a few things- first it runs a build process (seperate test version of crate) - so a prior carogo build doesnt cover it
- then it runs all tests (regardless if a crate has changed or not)
- so you have to narrow it yourself with
- package filters :
-p <pacakge> - crate fitlers: –lib, –bin <name> or –test <name> for one crate
- test name
- package filters :
- So the test version build is incremental
- the first test will compile the crates in scope, plus dependencies
- then int he future target willd etect what crates ahve chagneds codebase
- only that crate and its ocnsumers’ tests willbe test-compiled
- but the test is going to run on everything (its not incremental) - test only relies on package, crate and test name filters
- Core questions 3 - How work trees introduce unnecessary cold computes
- For cargo workspaces that have a lot of crates
- Cargo builds incrementally for the main build and the test build, and cahces upstream dpeneiceis
- however when we are working on worktrees. each new linked worktree only gets the files that are under version control
- Since
targetis gitignore; new linekd worktrees will not get that folder or the artifacts within the main worktree’stargetfolder - importaltn not eh fingerprint or incremental fodler or the compiled dpendiceis which allows incrememtal builds to work - as a result un-mitigated, new linked trees and build or test would have to re-compile the crates (and hte huge upstream depenicesi) from scratch for the first build or test - taking a long time
- This introduces uncesary cold computes
- crates need to be re-compiled
- usptrema dpenciesn are compield fromt heir source again
- Core questions 4 - How can we mitigate unnecessary cold computes for worktree workflows
- Firstly implment
sccachebeuase it stops the uncesary ocmpaitlion dependencie source code for worktree build and test calls - In terms of compilations
- A first full recompile is unavaiodable (with defualt cargo icnrmeimetnal set to on); one full compile per worktree is unavaibaldable
- once its done its incrememtnal
- On worktree creation, prebuild int he background with
cargo test --no-run(addcargo buildonly if you runt he binary - e.g. harness testing)
- In terms of tests
- use the proper filters in work trees
- only run your packges and their consumer tests, let CI runt he whol e wworkspace
- Firstly implment