scratch/
lib.rs

1//! This crate exposes a compile-time temporary directory shareable by multiple
2//! crates in a build graph and erased by `cargo clean`.
3//!
4//! The intended usage is from a build.rs Cargo build script, or more likely
5//! from a library which is called by other crates' build scripts.
6//!
7//! ```toml
8//! # Cargo.toml
9//!
10//! [build-dependencies]
11//! scratch = "1.0"
12//! ```
13//!
14//! ```edition2021
15//! // build.rs
16//!
17//! fn main() {
18//!     let dir = scratch::path("mycrate");
19//!     // ... write or read inside of that path
20//! }
21//! ```
22//!
23//! <br>
24//!
25//! # Comparisons
26//!
27//! Comparison to **`std::env::var_os("OUT_DIR")`**:
28//!
29//! - This functionality is different from OUT_DIR in that the same directory
30//!   path will be seen by *all* crates whose build passes a matching `suffix`
31//!   argument, and each crate can see content placed into the directory by
32//!   those other crates' build scripts that have already run.
33//!
34//! - This functionality is similar to OUT_DIR in that both are erased when a
35//!   `cargo clean` is executed.
36//!
37//! Comparison to **`std::env::temp_dir()`**:
38//!
39//! - This functionality is similar to temp_dir() in that stuff that goes in is
40//!   visible to subsequently running build scripts.
41//!
42//! - This functionality is different from temp_dir() in that `cargo clean`
43//!   cleans up the contents.
44//!
45//! <br>
46//!
47//! # Tips
48//!
49//! You'll want to consider what happens when Cargo runs multiple build scripts
50//! concurrently that access the same scratch dir. In some use cases you likely
51//! want some synchronization over the contents of the scratch directory, such
52//! as by an advisory [file lock]. On Unix-like and Windows host systems the
53//! simplest way to sequence the build scripts such that each one gets exclusive
54//! access one after the other is something like:
55//!
56//! [file lock]: https://man7.org/linux/man-pages/man2/flock.2.html
57//!
58//! ```edition2021
59//! use std::fs::File;
60//! use std::io;
61//!
62//! fn main() -> io::Result<()> {
63//!     let dir = scratch::path("demo");
64//!     let flock = File::create(dir.join(".lock"))?;
65//!     fs2::FileExt::lock_exclusive(&flock)?;
66//!
67//!     // ... now do work
68//!     # Ok(())
69//! }
70//! ```
71//!
72//! This simplest approach is fine for a cache which is slow to fill (maybe a
73//! large download) but fast/almost immediate to use. On the other hand if the
74//! build scripts using your cache will take a while to complete even if they
75//! only read from the scratch directory, a different approach which allows
76//! readers to make progress in parallel would perform better.
77//!
78//! ```edition2021
79//! use std::fs::{self, File};
80//! use std::io;
81//!
82//! fn main() -> io::Result<()> {
83//!     let dir = scratch::path("demo");
84//!     let flock = File::create(dir.join(".lock"))?;
85//!     let sdk = dir.join("thing.sdk");
86//!
87//!     if !sdk.exists() {
88//!         fs2::FileExt::lock_exclusive(&flock)?;
89//!         if !sdk.exists() {
90//!             let download_location = sdk.with_file_name("thing.sdk.partial");
91//!             download_sdk_to(&download_location)?;
92//!             fs::rename(&download_location, &sdk)?;
93//!         }
94//!         fs2::FileExt::unlock(&flock)?;
95//!     }
96//!
97//!     // ... now use the SDK
98//!     # Ok(())
99//! }
100//! #
101//! # use std::path::Path;
102//! #
103//! # fn download_sdk_to(location: &Path) -> io::Result<()> {
104//! #     fs::write(location, "...")
105//! # }
106//! ```
107//!
108//! For use cases that are not just a matter of the first build script writing
109//! to the directory and the rest reading, more elaborate schemes involving
110//! `lock_shared` might be something to consider.
111
112#![doc(html_root_url = "https://docs.rs/scratch/1.0.8")]
113#![cfg_attr(clippy, allow(renamed_and_removed_lints))]
114#![cfg_attr(clippy, allow(doc_markdown, must_use_candidate, needless_doctest_main))]
115
116use std::fs;
117use std::path::{Path, PathBuf};
118
119pub fn path(suffix: &str) -> PathBuf {
120    let p = Path::new(env!("OUT_DIR")).join(suffix);
121    let _ = fs::create_dir(&p);
122    p
123}