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}