Skip to content

Commit 4f657b1

Browse files
committed
[anneal][v2][exocrate] Crate-level documentation
- Documentation with examples crate `lib.rs` - Simple explanation, diagram, and link to crate docs in `README.md` - Test-data archive referenced in doc-tested example code - Can also be used in future integration tests if desired gherrit-pr-id: Gmum75fawi66ursah6w4azlcro7hvrkf4
1 parent 1e8210d commit 4f657b1

3 files changed

Lines changed: 156 additions & 10 deletions

File tree

anneal/v2/exocrate/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Exocrate: An exoskeleton for your crate
2+
3+
Exocrate is a manager for rust crate dependencies which are not managed by the `cargo` tool itself, such as external toolchains, large binary files, etc.
4+
5+
<img src="diagram2.png" width="100%">
6+
7+
See [crate documentation](src/lib.rs) for details.
8+
9+
<!-- FIXME: Inline crate documentation using `cargo-readme` or similar -->

anneal/v2/exocrate/src/lib.rs

Lines changed: 147 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,144 @@
1010
//! Exocrate: An exoskeleton for your crate.
1111
//!
1212
//! Exocrate is a manager for crate dependencies which are not managed by Cargo
13-
//! itself. This might include external toolchains, large binary files, etc.
13+
//! itself such as external toolchains, large binary files, etc.
14+
//!
15+
//! Exocrate *assumes*:
16+
//!
17+
//! - An external mechanism that packages external dependencies into a single archive.
18+
//!
19+
//! Exocrate *supports*:
20+
//!
21+
//! - Remote archives loaded by URL and verified by checksum;
22+
//! - Local archives designated by file path.
23+
//!
24+
//! Exocrate *provides* mechanisms to:
25+
//!
26+
//! - Install external dependencies:
27+
//! - Download and extract an archive;
28+
//! - Fix up archive artifacts that depend on the installed environment (e.g., rewriting absolute
29+
//! paths in dependency files);
30+
//! - Install archive contents in a versioned filesystem location.
31+
//! - Access extracted external dependencies:
32+
//! - Resolve current installation location.
33+
//!
34+
//! # How exocrate installations are versioned
35+
//!
36+
//! ## Platform + versioned files list
37+
//!
38+
//! The simplest way to auto-version your installations is using macros that tie your installation
39+
//! platform (i.e., `std::env::consts::OS` and `std::env::consts::ARCH` values) and versioning
40+
//! files that will change anytime your external dependencies might. Here is an example of
41+
//! realizing this versioning strategy using the macros exported by `exocrate`:
42+
//!
43+
//! ```rust
44+
//! exocrate::config! {
45+
//! const CONFIG: Config = Config {
46+
//! // Path components that are joined to establish the base directory for all exocrate
47+
//! // installations.
48+
//! //
49+
//! // For `my-tool` developers: `<my-tool-root>/target/.my-tool/exocrate/<version>`
50+
//! // For `my-tool` users: `~/.my-tool/exocrate/<version>`
51+
//! rel_dir_path: [".my-tool", "exocrate"],
52+
//! // Since the definition of platform-specific exocrate releases for `my-tool` will be
53+
//! // specified in its `Cargo.toml` file (see below), we use `Cargo.toml` and `Cargo.lock`
54+
//! // as a "change detector" to change the version.
55+
//! //
56+
//! // This is an over-approximation: unrelated changes to `Cargo.toml` or `Cargo.lock`
57+
//! // (e.g., patch-level version bumps of cargo-managed dependencies, changes in listed
58+
//! // crate dependencies, shipping an exocrate release for some other platform, etc.) will
59+
//! // also change the exocrate version. This is generally not a problem because only
60+
//! // developers of `my-tool` itself need to deal with noisy exocrate version changes;
61+
//! // users will only install versions associated with less frequent releases of
62+
//! // `my-tool`, e.g., installed via `cargo install my-tool`.
63+
//! versioned_files: &["../Cargo.toml", "../Cargo.lock"],
64+
//! };
65+
//! }
66+
//!
67+
//! exocrate::parse_remote_archive! {
68+
//! const REMOTE: RemoteArchive = "Cargo.toml" [
69+
//! // `(std::env::consts::OS, std::env::consts::ARCH)` pairs you support.
70+
//! (linux, x86_64),
71+
//! (macos, x86_64),
72+
//! (linux, aarch64),
73+
//! (macos, aarch64),
74+
//! ];
75+
//! }
76+
//!
77+
//! // FIXME: Detect whether running in tool-installed or tool-development environment. The typical
78+
//! // pattern would be:
79+
//! // - tool-installed => use `exocrate::Location::UserGlobal`,
80+
//! // - tool-development => use `exocrate::Location::LocalDev`.
81+
//! let location: exocrate::Location = exocrate::Location::LocalDev;
82+
//!
83+
//! // FIXME: Use environment detection here too. The typical pattern would be:
84+
//! // - tool-installed => use `exocrate::Source::Remote(REMOTE)`,
85+
//! // - tool-development => use
86+
//! // `exocrate::Source::Local("/path/to/dep-archive-builder-output.tar.zst".into())`.
87+
//! let source: exocrate::Source = exocrate::Source::Local("tests/my-tool-deps.tar.zst".into());
88+
//!
89+
//! // Check whether `source` archive is already installed at `location`, and if not, install it.
90+
//! let installed_exocrate_dir = CONFIG.resolve_installation_dir_or_install(location, source)
91+
//! .expect("failed to resolve or install my-tool's exocrate");
92+
//!
93+
//! // Invoke tool installed from `tests/my-tool-deps.tar.zst` extracted to versioned exocrate
94+
//! // directory.
95+
//! let tool_status = std::process::Command::new(installed_exocrate_dir.join("bin").join("tool"))
96+
//! .status()
97+
//! .expect("failed to start external dependency located at `bin/tool`");
98+
//! assert!(tool_status.success());
99+
//! ```
100+
//!
101+
//! And in your `Cargo.toml`:
102+
//!
103+
//! ```toml
104+
//! #
105+
//! # Prepares `REMOTE = exocrate::RemoteArchive { sha256, url }` according to compile-time machine
106+
//! # `std::env::consts::OS . std::env::consts::ARCH` listed as supported in your invocation of
107+
//! # `exocrate::parse_remote_archive`.
108+
//! #
109+
//! [package.metadata.exocrate.linux.x86_64]
110+
//! # FIXME: Replace `sha256` with actual linux-x86_64.tar.zst checksum.
111+
//! sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
112+
//! url = "https://example.com/linux-x86_64.tar.zst"
113+
//!
114+
//! [package.metadata.exocrate.macos.x86_64]
115+
//! # FIXME: Replace `sha256` with actual macos-x86_64.tar.zst checksum.
116+
//! sha256 = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
117+
//! url = "https://example.com/macos-x86_64.tar.zst"
118+
//!
119+
//! [package.metadata.exocrate.linux.aarch64]
120+
//! # FIXME: Replace `sha256` with actual linux-aarch64.tar.zst checksum.
121+
//! sha256 = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
122+
//! url = "https://example.com/linux-aarch64.tar.zst"
123+
//!
124+
//! [package.metadata.exocrate.macos.aarch64]
125+
//! # FIXME: Replace `sha256` with actual macos-aarch64.tar.zst checksum.
126+
//! sha256 = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
127+
//! url = "https://example.com/macos-aarch64.tar.zst"
128+
//! ```
129+
//!
130+
//! ### Limitations
131+
//!
132+
//! This versioning strategy does not distinguish between changes to `package.metadata.exocrate` and
133+
//! other changes to `Cargo.toml`. This can result in proliferation of distinct exocrate versions
134+
//! that developers need to clear out if your exocrate is particularly large. This cleanup can be
135+
//! achieved using `cargo clean`.
136+
//!
137+
//! ## Custom versioning
138+
//!
139+
//! Manually constructing an [`Config`] offers you full control over the base directory (relative
140+
//! to one or another [`Location`] for installation) and version string that is used to construct
141+
//! this `Config`'s version of the exocrate. By using this pattern, you takeresponsbility for
142+
//! determining what version of your exocrate corresponds with the code consuming the `exocrate`
143+
//! library.
144+
//!
145+
//! # Limitations
146+
//!
147+
//! Exocrate does not currently support any helpers for cleaning up old versions of your exocrate
148+
//! installations. All installed versions will be stored somewhere inside the directories designated
149+
//! by one of the [`Location`] variants, but there are no other guarantees about how these
150+
//! directory trees are managed at this time.
14151
15152
mod sync;
16153

@@ -50,7 +187,7 @@ pub enum Location {
50187
UserGlobal,
51188
/// A location in the Cargo `target` directory if it can be resolved, and
52189
/// otherwise in the current working directory.
53-
Local,
190+
LocalDev,
54191
}
55192

56193
/// The source from which to install dependencies
@@ -125,7 +262,7 @@ impl Config {
125262
fn dir_path(&self, location: Location) -> PathBuf {
126263
let mut parts = match location {
127264
Location::UserGlobal => dirs::home_dir().expect("home dir not found"),
128-
Location::Local => {
265+
Location::LocalDev => {
129266
std::env::var("CARGO_MANIFEST_DIR")
130267
.map(PathBuf::from)
131268
// Fall back to current directory if `CARGO_MANIFEST_DIR` is
@@ -599,7 +736,7 @@ mod tests {
599736
assert!(prod_path.starts_with(dirs::home_dir().unwrap()));
600737
assert!(prod_path.ends_with(PathBuf::from_iter(["test_dir_path", "slug"])));
601738

602-
let dev_path = config.dir_path(Location::Local);
739+
let dev_path = config.dir_path(Location::LocalDev);
603740
assert!(
604741
dev_path.starts_with(
605742
std::env::var("CARGO_MANIFEST_DIR")
@@ -614,12 +751,12 @@ mod tests {
614751
#[test]
615752
fn test_config_resolve_installation_dir() {
616753
let config = dummy_config(&["test_dir_resolve"]);
617-
assert!(config.resolve_installation_dir(Location::Local).is_err());
754+
assert!(config.resolve_installation_dir(Location::LocalDev).is_err());
618755

619-
let dev_path = config.dir_path(Location::Local);
756+
let dev_path = config.dir_path(Location::LocalDev);
620757
fs::create_dir_all(&dev_path).unwrap();
621758

622-
let resolved = config.resolve_installation_dir(Location::Local).unwrap();
759+
let resolved = config.resolve_installation_dir(Location::LocalDev).unwrap();
623760
assert_eq!(resolved, dev_path);
624761

625762
fs::remove_dir_all(&dev_path).unwrap();
@@ -634,18 +771,18 @@ mod tests {
634771

635772
let config = Config { rel_dir_path: &["test_dir_install"], version_slug: "slug" };
636773

637-
let dev_path = config.dir_path(Location::Local);
774+
let dev_path = config.dir_path(Location::LocalDev);
638775
let _ = fs::remove_dir_all(&dev_path);
639776

640777
let resolved = config
641-
.resolve_installation_dir_or_install(Location::Local, Source::Local(archive_path))
778+
.resolve_installation_dir_or_install(Location::LocalDev, Source::Local(archive_path))
642779
.unwrap();
643780
assert_eq!(resolved, dev_path);
644781
assert!(dev_path.join("bin/compiler").exists());
645782

646783
let resolved2 = config
647784
.resolve_installation_dir_or_install(
648-
Location::Local,
785+
Location::LocalDev,
649786
Source::Local(PathBuf::from("/nonexistent/path/should/not/be/accessed")),
650787
)
651788
.unwrap();
184 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)