shadow_rs/
build.rs

1use crate::date_time::DEFINE_SOURCE_DATE_EPOCH;
2use crate::hook::HookExt;
3use crate::shadow::DEFINE_SHADOW_RS;
4use crate::{SdResult, Shadow, CARGO_METADATA};
5use is_debug::is_debug;
6use std::collections::BTreeSet;
7use std::fmt::{Display, Formatter};
8
9/// `shadow-rs` build constant identifiers.
10pub type ShadowConst = &'static str;
11
12/// Since [cargo metadata](https://crates.io/crates/cargo_metadata) details about workspace
13/// membership and resolved dependencies for the current package, storing this data can result in
14/// significantly larger crate sizes. As such, the CARGO_METADATA const is disabled by default.
15///
16/// Should you choose to retain this information, you have the option to customize a deny_const
17/// object and override the `new_deny` method parameters accordingly.
18///
19#[allow(clippy::all, clippy::pedantic, clippy::restriction, clippy::nursery)]
20pub fn default_deny() -> BTreeSet<ShadowConst> {
21    BTreeSet::from([CARGO_METADATA])
22}
23
24/// Serialized values for build constants.
25#[derive(Debug, Clone)]
26pub struct ConstVal {
27    /// User-facing documentation for the build constant.
28    pub desc: String,
29    /// Serialized value of the build constant.
30    pub v: String,
31    /// Type of the build constant.
32    pub t: ConstType,
33}
34
35impl ConstVal {
36    pub fn new<S: Into<String>>(desc: S) -> ConstVal {
37        // Creates a new `ConstVal` with an empty string as its value and `Str` as its type.
38        ConstVal {
39            desc: desc.into(),
40            v: "".to_string(),
41            t: ConstType::Str,
42        }
43    }
44
45    pub fn new_bool<S: Into<String>>(desc: S) -> ConstVal {
46        // Creates a new `ConstVal` with "true" as its value and `Bool` as its type.
47        ConstVal {
48            desc: desc.into(),
49            v: "true".to_string(),
50            t: ConstType::Bool,
51        }
52    }
53
54    pub fn new_slice<S: Into<String>>(desc: S) -> ConstVal {
55        // Creates a new `ConstVal` with an empty string as its value and `Slice` as its type.
56        ConstVal {
57            desc: desc.into(),
58            v: "".to_string(),
59            t: ConstType::Slice,
60        }
61    }
62
63    pub fn new_usize<S: Into<String>>(desc: S) -> ConstVal {
64        // Creates a new `ConstVal` with an empty 0 as its value and `Usize` as its type.
65        ConstVal {
66            desc: desc.into(),
67            v: "0".to_string(),
68            t: ConstType::Usize,
69        }
70    }
71}
72
73/// Supported types of build constants.
74#[derive(Debug, Clone)]
75pub enum ConstType {
76    /// [`&str`](`str`).
77    Str,
78    /// [`bool`].
79    Bool,
80    /// [`&[u8]`].
81    Slice,
82    /// [`usize`].
83    Usize,
84}
85
86impl Display for ConstType {
87    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
88        match self {
89            ConstType::Str => write!(f, "&str"),
90            ConstType::Bool => write!(f, "bool"),
91            ConstType::Slice => write!(f, "&[u8]"),
92            ConstType::Usize => write!(f, "usize"),
93        }
94    }
95}
96
97/// The BuildPattern enum defines strategies for triggering package rebuilding.
98///
99/// Default mode is `Lazy`.
100///
101/// * `Lazy`: The lazy mode. In this mode, if the current Rust environment is set to `debug`,
102///   the rebuild package will not run every time the build script is triggered.
103///   If the environment is set to `release`, it behaves the same as the `RealTime` mode.
104/// * `RealTime`: The real-time mode. It will always trigger rebuilding a package upon any change,
105///   regardless of whether the Rust environment is set to `debug` or `release`.
106/// * `Custom`: The custom build mode, an enhanced version of `RealTime` mode, allowing for user-defined conditions
107///   to trigger rebuilding a package.
108///
109#[derive(Debug, Default, Clone)]
110pub enum BuildPattern {
111    #[default]
112    Lazy,
113    RealTime,
114    Custom {
115        /// A list of paths that, if changed, will trigger a rebuild.
116        /// See <https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed>
117        if_path_changed: Vec<String>,
118        /// A list of environment variables that, if changed, will trigger a rebuild.
119        /// See <https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-env-changed>
120        if_env_changed: Vec<String>,
121    },
122}
123
124impl BuildPattern {
125    /// Determines when Cargo should rerun the build script based on the configured pattern.
126    ///
127    /// # Arguments
128    ///
129    /// * `other_keys` - An iterator over additional keys that should trigger a rebuild if they change.
130    /// * `out_dir` - The output directory where generated files are placed.
131    pub(crate) fn rerun_if<'a>(
132        &self,
133        other_keys: impl Iterator<Item = &'a ShadowConst>,
134        out_dir: &str,
135    ) {
136        match self {
137            BuildPattern::Lazy => {
138                if is_debug() {
139                    return;
140                }
141            }
142            BuildPattern::RealTime => {}
143            BuildPattern::Custom {
144                if_path_changed,
145                if_env_changed,
146            } => {
147                if_env_changed
148                    .iter()
149                    .for_each(|key| println!("cargo:rerun-if-env-changed={key}"));
150                if_path_changed
151                    .iter()
152                    .for_each(|p| println!("cargo:rerun-if-changed={p}"));
153            }
154        }
155
156        other_keys.for_each(|key| println!("cargo:rerun-if-env-changed={key}"));
157        println!("cargo:rerun-if-env-changed={DEFINE_SOURCE_DATE_EPOCH}");
158        println!("cargo:rerun-if-changed={out_dir}/{DEFINE_SHADOW_RS}");
159    }
160}
161
162/// A builder pattern structure to construct a `Shadow` instance.
163///
164/// This struct allows for configuring various aspects of how shadow-rs will be built into your Rust project.
165/// It provides methods to set up hooks, specify build patterns, define paths, and deny certain build constants.
166///
167/// # Fields
168///
169/// * `hook`: An optional hook that can be used during the build process. Hooks implement the `HookExt` trait.
170/// * `build_pattern`: Determines the strategy for triggering package rebuilds (`Lazy`, `RealTime`, or `Custom`).
171/// * `deny_const`: A set of build constant identifiers that should not be included in the build.
172/// * `src_path`: The source path from which files are read for building.
173/// * `out_path`: The output path where generated files will be placed.
174///
175pub struct ShadowBuilder<'a> {
176    hook: Option<Box<dyn HookExt + 'a>>,
177    build_pattern: BuildPattern,
178    deny_const: BTreeSet<ShadowConst>,
179    src_path: Option<String>,
180    out_path: Option<String>,
181}
182
183impl<'a> ShadowBuilder<'a> {
184    /// Creates a new `ShadowBuilder` with default settings.
185    ///
186    /// Initializes the builder with the following defaults:
187    /// - `hook`: None
188    /// - `build_pattern`: `BuildPattern::Lazy`
189    /// - `deny_const`: Uses the result from `default_deny()`
190    /// - `src_path`: Attempts to get the manifest directory using `CARGO_MANIFEST_DIR` environment variable.
191    /// - `out_path`: Attempts to get the output directory using `OUT_DIR` environment variable.
192    ///
193    /// # Returns
194    ///
195    /// A new instance of `ShadowBuilder`.
196    pub fn builder() -> Self {
197        let default_src_path = std::env::var("CARGO_MANIFEST_DIR").ok();
198        let default_out_path = std::env::var("OUT_DIR").ok();
199        Self {
200            hook: None,
201            build_pattern: BuildPattern::default(),
202            deny_const: default_deny(),
203            src_path: default_src_path,
204            out_path: default_out_path,
205        }
206    }
207
208    /// Sets the build hook for this builder.
209    ///
210    /// # Arguments
211    ///
212    /// * `hook` - An object implementing the `HookExt` trait that defines custom behavior for the build process.
213    ///
214    /// # Returns
215    ///
216    /// A new `ShadowBuilder` instance with the specified hook applied.
217    pub fn hook(mut self, hook: impl HookExt + 'a) -> Self {
218        self.hook = Some(Box::new(hook));
219        self
220    }
221
222    /// Sets the source path for this builder.
223    ///
224    /// # Arguments
225    ///
226    /// * `src_path` - A string reference that specifies the source directory for the build.
227    ///
228    /// # Returns
229    ///
230    /// A new `ShadowBuilder` instance with the specified source path.
231    pub fn src_path<P: AsRef<str>>(mut self, src_path: P) -> Self {
232        self.src_path = Some(src_path.as_ref().to_owned());
233        self
234    }
235
236    /// Sets the output path for this builder.
237    ///
238    /// # Arguments
239    ///
240    /// * `out_path` - A string reference that specifies the output directory for the build.
241    ///
242    /// # Returns
243    ///
244    /// A new `ShadowBuilder` instance with the specified output path.
245    pub fn out_path<P: AsRef<str>>(mut self, out_path: P) -> Self {
246        self.out_path = Some(out_path.as_ref().to_owned());
247        self
248    }
249
250    /// Sets the build pattern for this builder.
251    ///
252    /// # Arguments
253    ///
254    /// * `pattern` - A `BuildPattern` that determines when the package should be rebuilt.
255    ///
256    /// # Returns
257    ///
258    /// A new `ShadowBuilder` instance with the specified build pattern.
259    pub fn build_pattern(mut self, pattern: BuildPattern) -> Self {
260        self.build_pattern = pattern;
261        self
262    }
263
264    /// Sets the denied constants for this builder.
265    ///
266    /// # Arguments
267    ///
268    /// * `deny_const` - A set of `ShadowConst` that should be excluded from the build.
269    ///
270    /// # Returns
271    ///
272    /// A new `ShadowBuilder` instance with the specified denied constants.
273    pub fn deny_const(mut self, deny_const: BTreeSet<ShadowConst>) -> Self {
274        self.deny_const = deny_const;
275        self
276    }
277
278    /// Builds a `Shadow` instance based on the current configuration.
279    ///
280    /// # Returns
281    ///
282    /// A `SdResult<Shadow>` that represents the outcome of the build operation.
283    pub fn build(self) -> SdResult<Shadow> {
284        Shadow::build_inner(self)
285    }
286
287    /// Gets the source path if it has been set.
288    ///
289    /// # Returns
290    ///
291    /// A `SdResult<&String>` containing the source path or an error if the path is missing.
292    pub fn get_src_path(&self) -> SdResult<&String> {
293        let src_path = self.src_path.as_ref().ok_or("missing `src_path`")?;
294        Ok(src_path)
295    }
296
297    /// Gets the output path if it has been set.
298    ///
299    /// # Returns
300    ///
301    /// A `SdResult<&String>` containing the output path or an error if the path is missing.
302    pub fn get_out_path(&self) -> SdResult<&String> {
303        let out_path = self.out_path.as_ref().ok_or("missing `out_path`")?;
304        Ok(out_path)
305    }
306
307    /// Gets the build pattern.
308    ///
309    /// # Returns
310    ///
311    /// A reference to the `BuildPattern` currently configured for this builder.
312    pub fn get_build_pattern(&self) -> &BuildPattern {
313        &self.build_pattern
314    }
315
316    /// Gets the denied constants.
317    ///
318    /// # Returns
319    ///
320    /// A reference to the set of `ShadowConst` that are denied for this build.
321    pub fn get_deny_const(&self) -> &BTreeSet<ShadowConst> {
322        &self.deny_const
323    }
324
325    /// Gets the build hook if it has been set.
326    ///
327    /// # Returns
328    ///
329    /// An option containing a reference to the hook if one is present.
330    pub fn get_hook(&'a self) -> Option<&'a (dyn HookExt + 'a)> {
331        self.hook.as_deref()
332    }
333}