Skip to main content

ekege_artifact/
lib.rs

1//! General-purpose build artifact generation crate, used in [Ekege](https://docs.rs/ekege/)'s build script.
2//!
3//! It provides a mechanism to generate and store arbitrary data during the build process
4//! (typically in a [`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html) script)
5//! and then load that data at compile time in your main crate code.
6//!
7//! # Usage
8//!
9//! First, implement the [`Artifact`] trait for your custom types. Then use [`store_artifact`] in your
10//! build script to serialize them, and [`load_artifacts!`] in your main code to make the constants available.
11//!
12//! # Examples
13//!
14//! Given an [artifact](Artifact) `MyVec`:
15//!
16//! ```
17//! # use ekege_artifact::{Artifact ToTokens, quote, TokenStream, TokenStreamExt};
18//! #
19//! struct MyVec(Vec<u16>);
20//!
21//! // Store `MyVec`'s data using tokens, as a build artifact
22//! impl ToTokens for MyVec {
23//!     fn to_tokens(&self, tokens: &mut TokenStream) {
24//!         let items = &self.0;
25//!
26//!         tokens.append_all(quote! { [#(#items),*] });
27//!     }
28//! }
29//!
30//! // Get `MyVec`'s build artifact type, as tokens
31//! impl Artifact for MyVec {
32//!     fn generate_type(&self) -> impl ToTokens {
33//!         let length = self.0.len();
34//!
35//!         quote! { [u16; #length] }
36//!     }
37//! }
38//! ```
39//!
40//! We can store it using [`store_artifact`] like so:
41//!
42//! ```no_run
43//! # use ekege_artifact::{}
44//! #
45//! ekege_artifact::store_artifact(
46//!     format_ident!("MY_VEC"),
47//!     MyVec(vec![1, 2, 3]),
48//! );
49//! ```
50//!
51//! At this point, the stored artifact will look somewhat like:
52//!
53//! ```
54//! const MY_VEC: [u16; 3] = [1, 2, 3];
55//! ```
56//!
57//! Using [`load_artifacts!`] we can then use that artifact in our main crate code:
58//!
59//! ```no_run
60//! # use ekege_artifact::load_artifacts;
61//! #
62//! load_artifacts!();
63//!
64//! println!("{}", MY_VEC[0]);
65//! ```
66use std::{
67    env,
68    fs::OpenOptions,
69    io::Write,
70    path::PathBuf,
71    sync::atomic::{self, AtomicBool},
72};
73
74/// Returns the filename used for storing artifact data.
75///
76/// This macro provides a consistent way to reference the artifact file
77/// across the crate's public API.
78///
79/// Currently, the artifact file name is `ekege-artifact.rs`.
80#[macro_export]
81macro_rules! artifact_file {
82    () => {
83        "ekege-artifact.rs"
84    };
85}
86
87use proc_macro2::Ident;
88pub use proc_macro2::TokenStream;
89pub use quote::{ToTokens, TokenStreamExt, format_ident, quote};
90
91/// A type that can be serialized as a compile-time artifact.
92///
93/// Implementors must be able to generate their own type representation
94/// and convert themselves to a token stream.
95///
96/// # Example
97///
98/// ```
99/// # use ekege_artifact::{Artifact ToTokens, quote, TokenStream, TokenStreamExt};
100/// #
101/// struct MyVec(Vec<u16>);
102///
103/// // Store `MyVec`'s data using tokens, as a build artifact
104/// impl ToTokens for MyVec {
105///     fn to_tokens(&self, tokens: &mut TokenStream) {
106///         let items = &self.0;
107///
108///         tokens.append_all(quote! { [#(#items),*] });
109///     }
110/// }
111///
112/// // Get `MyVec`'s build artifact type, as tokens
113/// impl Artifact for MyVec {
114///     fn generate_type(&self) -> impl ToTokens {
115///         let length = self.0.len();
116///
117///         quote! { [u16; #length] }
118///     }
119/// }
120/// ```
121pub trait Artifact: ToTokens {
122    /// Generates a token representation of this artifact's type.
123    ///
124    /// This is used when storing the artifact as a typed constant.
125    fn generate_type(&self) -> impl ToTokens;
126}
127
128static IS_FIRST_USE: AtomicBool = AtomicBool::new(true);
129
130/// Stores an artifact in the build output directory.
131///
132/// This function writes the given artifact to a file in the build output
133/// directory, making it available at compile time. The artifact is stored
134/// as a Rust constant with the specified name.
135///
136/// # Panics
137///
138/// Beyond panicking upon IO errors, this function will panic when not run in a build script
139/// (or if the `OUT_DIR` environment variable is otherwise unavailable).
140///
141/// # Examples
142///
143/// See [this](crate) for a full usage example.
144pub fn store_artifact<A: Artifact>(name: Ident, value: A) {
145    let value_type = value.generate_type();
146
147    let is_first_use = IS_FIRST_USE.swap(false, atomic::Ordering::Acquire);
148
149    let mut file = OpenOptions::new()
150        .write(true)
151        .create(true)
152        .truncate(is_first_use)
153        .append(!is_first_use)
154        .open(PathBuf::from(env::var("OUT_DIR").expect("get OUT_DIR value")).join(artifact_file!()))
155        .expect("open artifact file");
156    file.write_all(
157        quote! { const #name: #value_type = #value; }
158            .to_token_stream()
159            .to_string()
160            .as_bytes(),
161    )
162    .expect("write to artifact file");
163}
164
165/// Includes all stored artifacts at compile time.
166///
167/// This macro includes the file generated by all [`store_artifact`] invocations,
168/// making all the stored constants available in the current scope.
169///
170/// # Examples
171///
172/// See [this](crate) for a full usage example.
173#[macro_export]
174macro_rules! load_artifacts {
175    () => {
176        include!(concat!(env!("OUT_DIR"), "/", $crate::artifact_file!()));
177    };
178}