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
15152mod 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 ( ) ;
0 commit comments