Skip to content

Commit

Permalink
Add ShadowBuilder build shadow
Browse files Browse the repository at this point in the history
  • Loading branch information
baoyachi committed Dec 15, 2024
1 parent 062c31c commit 4455964
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 47 deletions.
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,16 @@ link [example_wasm](https://github.com/baoyachi/shadow-rs/tree/master/example_wa

![build_module](./build_module.png)

# Note on Caching
# BuildPattern
The BuildPattern enum defines strategies for triggering package rebuilding. **Default mode is `Lazy`**.

`shadow-rs` build information **is not always rebuilt** when you build a project. `shadow-rs` outputs several hints to
Cargo in order to force rebuilds when required, but this does not always work. You can enforce up-to-date build
information by running `cargo clean` before the build, or use a CI/CD pipeline tool. For more details,
see <https://github.com/baoyachi/shadow-rs/issues/95>.
* `Lazy`: The lazy mode. In this mode, if the current Rust environment is set to `debug`,
the rebuild package will not run every time the build script is triggered.
If the environment is set to `release`, it behaves the same as the `RealTime` mode.
* `RealTime`: The real-time mode. It will always trigger rebuilding a package upon any change,
regardless of whether the Rust environment is set to `debug` or `release`.
* `Custom`: The custom build mode, an enhanced version of `RealTime` mode, allowing for user-defined conditions
to trigger rebuilding a package.

# Examples

Expand Down Expand Up @@ -80,8 +84,8 @@ shadow-rs = "{latest version}"
Now in the root of your project (same directory as `Cargo.toml`) add a file `build.rs`:

```rust
fn main() -> shadow_rs::SdResult<()> {
shadow_rs::new()
fn main() {
ShadowBuilder::builder().build().unwrap();
}
```

Expand Down
9 changes: 6 additions & 3 deletions example_shadow/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
fn main() -> shadow_rs::SdResult<()> {
// shadow_rs::new()
use shadow_rs::ShadowBuilder;

shadow_rs::new_deny(Default::default())
fn main() {
ShadowBuilder::builder()
.deny_const(Default::default())
.build()
.unwrap();
}
6 changes: 3 additions & 3 deletions example_shadow_hook/build.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use shadow_rs::SdResult;
use shadow_rs::{SdResult, ShadowBuilder};
use std::fs::File;
use std::io::Write;

fn main() -> SdResult<()> {
shadow_rs::new_hook(hook)
fn main() {
ShadowBuilder::builder().hook(hook).build().unwrap();
}

fn hook(file: &File) -> SdResult<()> {
Expand Down
189 changes: 171 additions & 18 deletions src/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::hook::HookExt;
use crate::{default_deny, SdResult, Shadow};
use is_debug::is_debug;
use std::collections::BTreeSet;
use std::fmt::{Display, Formatter};

Expand All @@ -19,6 +20,7 @@ pub struct ConstVal {

impl ConstVal {
pub fn new<S: Into<String>>(desc: S) -> ConstVal {
// Creates a new `ConstVal` with an empty string as its value and `Str` as its type.
ConstVal {
desc: desc.into(),
v: "".to_string(),
Expand All @@ -27,6 +29,7 @@ impl ConstVal {
}

pub fn new_bool<S: Into<String>>(desc: S) -> ConstVal {
// Creates a new `ConstVal` with "true" as its value and `Bool` as its type.
ConstVal {
desc: desc.into(),
v: "true".to_string(),
Expand All @@ -35,6 +38,7 @@ impl ConstVal {
}

pub fn new_slice<S: Into<String>>(desc: S) -> ConstVal {
// Creates a new `ConstVal` with an empty string as its value and `Slice` as its type.
ConstVal {
desc: desc.into(),
v: "".to_string(),
Expand Down Expand Up @@ -64,32 +68,105 @@ impl Display for ConstType {
}
}

pub struct ShadowBuilder<'a> {
hook: Option<Box<dyn HookExt + 'a>>,
build_pattern: BuildPattern,
deny_const: BTreeSet<ShadowConst>,
src_path: Option<String>,
out_path: Option<String>,
}

/// The BuildPattern enum defines strategies for triggering package rebuilding.
///
/// Default mode is `Lazy`.
///
/// * `Lazy`: The lazy mode. In this mode, if the current Rust environment is set to `debug`,
/// the rebuild package will not run every time the build script is triggered.
/// If the environment is set to `release`, it behaves the same as the `RealTime` mode.
/// * `RealTime`: The real-time mode. It will always trigger rebuilding a package upon any change,
/// regardless of whether the Rust environment is set to `debug` or `release`.
/// * `Custom`: The custom build mode, an enhanced version of `RealTime` mode, allowing for user-defined conditions
/// to trigger rebuilding a package.
///
#[derive(Debug, Default, Clone)]
pub enum BuildPattern {
//TODO handle pattern
#[default]
Lazy,
RealTime,
Custom(CBuildPattern),
Custom {
/// A list of paths that, if changed, will trigger a rebuild.
/// See <https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed>
if_path_changed: Vec<String>,
/// A list of environment variables that, if changed, will trigger a rebuild.
/// See <https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-env-changed>
if_env_changed: Vec<String>,
},
}

#[derive(Debug, Default, Clone)]
pub struct CBuildPattern {
// https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed
if_path_changed: Vec<String>,
// https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-env-changed
if_env_changed: Vec<String>,
impl BuildPattern {
/// Determines when Cargo should rerun the build script based on the configured pattern.
///
/// # Arguments
///
/// * `other_keys` - An iterator over additional keys that should trigger a rebuild if they change.
/// * `out_dir` - The output directory where generated files are placed.
pub(crate) fn rerun_if<'a>(
&self,
other_keys: impl Iterator<Item = &'a ShadowConst>,
out_dir: &str,
) {
match self {
BuildPattern::Lazy => {
if is_debug() {
return;
}
}
BuildPattern::RealTime => {}
BuildPattern::Custom {
if_path_changed,
if_env_changed,
} => {
if_env_changed
.iter()
.for_each(|key| println!("cargo:rerun-if-env-changed={key}"));
if_path_changed
.iter()
.for_each(|p| println!("cargo:rerun-if-changed={p}"));
}
}

other_keys.for_each(|key| println!("cargo:rerun-if-env-changed={key}"));
println!("cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH");
println!("cargo:rerun-if-changed={}/shadow.rs", out_dir);
}
}

/// A builder pattern structure to construct a `Shadow` instance.
///
/// This struct allows for configuring various aspects of how shadow-rs will be built into your Rust project.
/// It provides methods to set up hooks, specify build patterns, define paths, and deny certain build constants.
///
/// # Fields
///
/// * `hook`: An optional hook that can be used during the build process. Hooks implement the `HookExt` trait.
/// * `build_pattern`: Determines the strategy for triggering package rebuilds (`Lazy`, `RealTime`, or `Custom`).
/// * `deny_const`: A set of build constant identifiers that should not be included in the build.
/// * `src_path`: The source path from which files are read for building.
/// * `out_path`: The output path where generated files will be placed.
///
pub struct ShadowBuilder<'a> {
hook: Option<Box<dyn HookExt + 'a>>,
build_pattern: BuildPattern,
deny_const: BTreeSet<ShadowConst>,
src_path: Option<String>,
out_path: Option<String>,
}

impl<'a> ShadowBuilder<'a> {
/// Creates a new `ShadowBuilder` with default settings.
///
/// Initializes the builder with the following defaults:
/// - `hook`: None
/// - `build_pattern`: `BuildPattern::Lazy`
/// - `deny_const`: Uses the result from `default_deny()`
/// - `src_path`: Attempts to get the manifest directory using `CARGO_MANIFEST_DIR` environment variable.
/// - `out_path`: Attempts to get the output directory using `OUT_DIR` environment variable.
///
/// # Returns
///
/// A new instance of `ShadowBuilder`.
pub fn builder() -> Self {
let default_src_path = std::env::var("CARGO_MANIFEST_DIR").ok();
let default_out_path = std::env::var("OUT_DIR").ok();
Expand All @@ -102,53 +179,129 @@ impl<'a> ShadowBuilder<'a> {
}
}

/// Sets the build hook for this builder.
///
/// # Arguments
///
/// * `hook` - An object implementing the `HookExt` trait that defines custom behavior for the build process.
///
/// # Returns
///
/// A new `ShadowBuilder` instance with the specified hook applied.
pub fn hook(mut self, hook: impl HookExt + 'a) -> Self {
self.hook = Some(Box::new(hook));
self
}

/// Sets the source path for this builder.
///
/// # Arguments
///
/// * `src_path` - A string reference that specifies the source directory for the build.
///
/// # Returns
///
/// A new `ShadowBuilder` instance with the specified source path.
pub fn src_path<P: AsRef<str>>(mut self, src_path: P) -> Self {
self.src_path = Some(src_path.as_ref().to_owned());
self
}

/// Sets the output path for this builder.
///
/// # Arguments
///
/// * `out_path` - A string reference that specifies the output directory for the build.
///
/// # Returns
///
/// A new `ShadowBuilder` instance with the specified output path.
pub fn out_path<P: AsRef<str>>(mut self, out_path: P) -> Self {
self.out_path = Some(out_path.as_ref().to_owned());
self
}

/// Sets the build pattern for this builder.
///
/// # Arguments
///
/// * `pattern` - A `BuildPattern` that determines when the package should be rebuilt.
///
/// # Returns
///
/// A new `ShadowBuilder` instance with the specified build pattern.
pub fn build_pattern(mut self, pattern: BuildPattern) -> Self {
self.build_pattern = pattern;
self
}

/// Sets the denied constants for this builder.
///
/// # Arguments
///
/// * `deny_const` - A set of `ShadowConst` that should be excluded from the build.
///
/// # Returns
///
/// A new `ShadowBuilder` instance with the specified denied constants.
pub fn deny_const(mut self, deny_const: BTreeSet<ShadowConst>) -> Self {
self.deny_const = deny_const;
self
}

/// Builds a `Shadow` instance based on the current configuration.
///
/// # Returns
///
/// A `SdResult<Shadow>` that represents the outcome of the build operation.
pub fn build(self) -> SdResult<Shadow> {
Shadow::build_inner(self)
}

/// Gets the source path if it has been set.
///
/// # Returns
///
/// A `SdResult<&String>` containing the source path or an error if the path is missing.
pub fn get_src_path(&self) -> SdResult<&String> {
let src_path = self.src_path.as_ref().ok_or("missing `src_path`")?;
Ok(src_path)
}

/// Gets the output path if it has been set.
///
/// # Returns
///
/// A `SdResult<&String>` containing the output path or an error if the path is missing.
pub fn get_out_path(&self) -> SdResult<&String> {
let out_path = self.out_path.as_ref().ok_or("missing `out_path`")?;
Ok(out_path)
}

/// Gets the build pattern.
///
/// # Returns
///
/// A reference to the `BuildPattern` currently configured for this builder.
pub fn get_build_pattern(&self) -> &BuildPattern {
&self.build_pattern
}

/// Gets the denied constants.
///
/// # Returns
///
/// A reference to the set of `ShadowConst` that are denied for this build.
pub fn get_deny_const(&self) -> &BTreeSet<ShadowConst> {
&self.deny_const
}

pub fn get_hook(&'a self) -> Option<&Box<dyn HookExt + 'a>> {
self.hook.as_ref()
/// Gets the build hook if it has been set.
///
/// # Returns
///
/// An option containing a reference to the hook if one is present.
pub fn get_hook(&'a self) -> Option<&'a (dyn HookExt + 'a)> {
self.hook.as_deref()
}
}
1 change: 0 additions & 1 deletion src/date_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ pub fn now_date_time() -> DateTime {
// `SOURCE_DATE_EPOCH` env variable.
//
// https://reproducible-builds.org/docs/source-date-epoch/
println!("cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH");
match std::env::var_os("SOURCE_DATE_EPOCH") {
None => DateTime::now(),
Some(timestamp) => {
Expand Down
Loading

0 comments on commit 4455964

Please sign in to comment.