crates/kiln/ents-kiln/src/lib.rs
| 1 | //! The toolchain domain: the [`Toolchain`] entity and its |
| 2 | //! resolution/materialization machinery (`model.toolchain`, |
| 3 | //! `effect.toolchains`), plus the `toolchain` porcelain command — |
| 4 | //! kernel-independent, unlike `ents-model`'s remaining entities, because |
| 5 | //! resolving a toolchain needs `ents-effect` (to share its tree-checkout |
| 6 | //! primitive and its `Error`/`Result` type) and `ents-receive` (to propose |
| 7 | //! the import mutation), neither of which a purely declarative vocabulary |
| 8 | //! crate like `ents-model` may depend on. |
| 9 | //! |
| 10 | //! This crate sits *above* the kernel in the dependency graph, not inside |
| 11 | //! it: `ents-model`, `ents-anchor`, `ents-gate`, `ents-query`, |
| 12 | //! `ents-receive`, `ents-effect`, `ents-sync`, and `ents-testutil` must |
| 13 | //! never depend on `ents-kiln` (verified by `grep -rn ents-kiln |
| 14 | //! crates/kernel crates/substrate` finding nothing) — `ents-kiln` depends |
| 15 | //! on them, never the reverse. `git-ents` (the CLI) depends on this crate |
| 16 | //! and mounts its toolchain command through a thin wrapper that only adds |
| 17 | //! signer/actor construction and CLI-facing error rendering, and resolves |
| 18 | //! an effect's declared toolchains through this crate before calling |
| 19 | //! `ents_effect::run::run_effect` (`crate::commands::effect::run` on the |
| 20 | //! CLI side). |
| 21 | //! |
| 22 | //! # Spec coverage |
| 23 | //! |
| 24 | //! From `docs/spec/model.sdoc` and `docs/spec/effect.adoc`: |
| 25 | //! |
| 26 | //! - `model.toolchain` — [`Toolchain`]. |
| 27 | //! - `effect.toolchains` — [`Recipe`], [`Component`], [`toolchain::resolve`], |
| 28 | //! [`toolchain::cache_key`], [`toolchain::materialize`]: reading a |
| 29 | //! toolchain manifest, parsing its `recipe`, and extracting it to a host |
| 30 | //! directory. `ents_effect::run::run_one`/`run_effect` no longer resolve |
| 31 | //! toolchain names themselves — they accept an already-materialized |
| 32 | //! `&[(String, PathBuf)]` slice; resolving an effect's declared names to |
| 33 | //! that slice is this crate's job, done by a composition root before it |
| 34 | //! calls into `ents-effect`'s run loop. |
| 35 | //! - `meta-ref.typed-tree` — `toolchain::entity`'s round-trip test (see |
| 36 | //! the module itself for the concrete test, folded into `toolchain`'s |
| 37 | //! private `entity` submodule). |
| 38 | //! |
| 39 | //! # Examples |
| 40 | //! |
| 41 | //! Import a toolchain (embedding a stand-in tree — `ents-kiln`'s own |
| 42 | //! `command::import` walks a real host directory; this example embeds an |
| 43 | //! already-written tree directly to demonstrate just the entity/recipe |
| 44 | //! round trip) and resolve it back. |
| 45 | //! |
| 46 | //! ``` |
| 47 | //! use ents_kiln::{Recipe, Toolchain, toolchain}; |
| 48 | //! use ents_testutil::{MemRefStore, ObjectStore, write_meta_entity}; |
| 49 | //! use gix_object::{Kind, Write as _}; |
| 50 | //! |
| 51 | //! let refs = MemRefStore::default(); |
| 52 | //! let objects = ObjectStore::default(); |
| 53 | //! let bin_tree = objects.write_buf(Kind::Tree, b"").expect("write"); |
| 54 | //! |
| 55 | //! let toolchain = Toolchain { |
| 56 | //! name: "rust-stable".into(), |
| 57 | //! recipe: Recipe::Embedded { tree: bin_tree }.render(), |
| 58 | //! }; |
| 59 | //! let name: gix::refs::FullName = "refs/meta/toolchains/rust-stable".try_into().expect("valid"); |
| 60 | //! write_meta_entity(&refs, &objects, name, &toolchain, None, 100); |
| 61 | //! |
| 62 | //! let (entity, recipe) = toolchain::resolve(&refs, &objects, "rust-stable").expect("resolves"); |
| 63 | //! assert_eq!(entity.name, "rust-stable"); |
| 64 | //! |
| 65 | //! let cache = tempfile::tempdir().expect("tempdir"); |
| 66 | //! let bin = toolchain::materialize(&recipe, &objects, cache.path()).expect("materializes"); |
| 67 | //! assert!(bin.is_dir()); |
| 68 | //! ``` |
| 69 | |
| 70 | pub mod toolchain; |
| 71 | |
| 72 | pub use toolchain::{Component, Recipe, Toolchain, cache_key, materialize, resolve}; |
| 73 | |
| 74 | #[cfg(test)] |
| 75 | mod tests { |
| 76 | use facet::Facet as _; |
| 77 | use rstest::rstest; |
| 78 | |
| 79 | use super::*; |
| 80 | |
| 81 | /// The entity that moved from `ents-model` to this crate keeps the |
| 82 | /// same `model.extensibility` guarantee `ents_model`'s own shape test |
| 83 | /// pins for its remaining entities (and `ents-forge`'s own copy pins |
| 84 | /// for `Comment`/`Issue`): its reflected |
| 85 | /// [`facet::Shape::type_identifier`] is exactly its Rust struct name. |
| 86 | #[rstest] |
| 87 | #[case::toolchain(Toolchain::SHAPE.type_identifier, "Toolchain")] |
| 88 | // @relation(model.extensibility, scope=function, role=Verifies) |
| 89 | fn every_entity_shape_name_tracks_its_struct_declaration( |
| 90 | #[case] reflected: &str, |
| 91 | #[case] expected: &str, |
| 92 | ) { |
| 93 | assert_eq!(reflected, expected); |
| 94 | } |
| 95 | } |