• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

baoyachi / shadow-rs / 16680866786

01 Aug 2025 05:04PM UTC coverage: 76.723% (+0.3%) from 76.417%
16680866786

push

github

web-flow
Update Cargo.toml

501 of 653 relevant lines covered (76.72%)

1.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.44
/src/shadow.rs
1
use crate::build::{ConstType, ConstVal};
2
use crate::ci::CiType;
3
use crate::date_time::now_date_time;
4
use crate::env::{new_project, new_system_env};
5
use crate::gen_const::{
6
    clap_long_version_branch_const, clap_long_version_tag_const, version_branch_const,
7
    version_tag_const, BUILD_CONST_CLAP_LONG_VERSION, BUILD_CONST_VERSION,
8
};
9
use crate::git::new_git;
10
use crate::{
11
    get_std_env, BuildPattern, SdResult, ShadowBuilder, ShadowConst, CARGO_CLIPPY_ALLOW_ALL, TAG,
12
};
13
use std::collections::{BTreeMap, BTreeSet};
14
use std::fs::File;
15
use std::io::Write;
16
use std::path::Path;
17

18
pub(crate) const DEFINE_SHADOW_RS: &str = "shadow.rs";
19

20
/// `shadow-rs` configuration.
21
///
22
/// This struct encapsulates the configuration for the `shadow-rs` build process. It allows for fine-grained control over
23
/// various aspects of the build, including file output, build constants, environment variables, deny lists, and build patterns.
24
///
25
/// While it is possible to construct a [`Shadow`] instance manually, it is highly recommended to use the [`ShadowBuilder`] builder pattern structure
26
/// provided by `shadow-rs`. The builder pattern simplifies the setup process and ensures that all necessary configurations are properly set up,
27
/// allowing you to customize multiple aspects simultaneously, such as using a denylist and a hook function at the same time.
28
///
29
/// # Fields
30
///
31
/// * `f`: The file that `shadow-rs` writes build information to. This file will contain serialized build constants and other metadata.
32
/// * `map`: A map of build constant identifiers to their corresponding `ConstVal`. These are the values that will be written into the file.
33
/// * `std_env`: A map of environment variables obtained through [`std::env::vars`]. These variables can influence the build process.
34
/// * `deny_const`: A set of build constant identifiers that should be excluded from the build process. This can be populated via [`ShadowBuilder::deny_const`].
35
/// * `out_path`: The path where the generated files will be placed. This is usually derived from the `OUT_DIR` environment variable but can be customized via [`ShadowBuilder::out_path`].
36
/// * `build_pattern`: Determines the strategy for triggering package rebuilds (`Lazy`, `RealTime`, or `Custom`). This affects when Cargo will rerun the build script and can be configured via [`ShadowBuilder::build_pattern`].
37
///
38
/// # Example
39
///
40
/// ```no_run
41
/// use std::collections::BTreeSet;
42
/// use shadow_rs::{ShadowBuilder, BuildPattern, CARGO_TREE, CARGO_METADATA};
43
///
44
/// ShadowBuilder::builder()
45
///    .build_pattern(BuildPattern::RealTime)
46
///    .deny_const(BTreeSet::from([CARGO_TREE, CARGO_METADATA]))
47
///    .build().unwrap();
48
/// ```
49
///
50
#[derive(Debug)]
51
pub struct Shadow {
52
    /// The file that `shadow-rs` writes build information to.
53
    ///
54
    /// This file will contain all the necessary information about the build, including serialized build constants and other metadata.
55
    pub f: File,
56

57
    /// The values of build constants to be written.
58
    ///
59
    /// This is a mapping from `ShadowConst` identifiers to their corresponding `ConstVal` objects. Each entry in this map represents a build constant that will be included in the final build.
60
    pub map: BTreeMap<ShadowConst, ConstVal>,
61

62
    /// Build environment variables, obtained through [`std::env::vars`].
63
    ///
64
    /// These environment variables can affect the build process and are captured here for consistency and reproducibility.
65
    pub std_env: BTreeMap<String, String>,
66

67
    /// Constants in the deny list, passed through [`ShadowBuilder::deny_const`].
68
    ///
69
    /// This set contains build constant identifiers that should be excluded from the build process. By specifying these, you can prevent certain constants from being written into the build file.
70
    pub deny_const: BTreeSet<ShadowConst>,
71

72
    /// The output path where generated files will be placed.
73
    ///
74
    /// This specifies the directory where the build script will write its output. It's typically set using the `OUT_DIR` environment variable but can be customized using [`ShadowBuilder::out_path`].
75
    pub out_path: String,
76

77
    /// Determines the strategy for triggering package rebuilds.
78
    ///
79
    /// This field sets the pattern for how often the package should be rebuilt. Options include `Lazy`, `RealTime`, and `Custom`, each with its own implications on the build frequency and conditions under which a rebuild is triggered.
80
    /// It can be configured using [`ShadowBuilder::build_pattern`].
81
    pub build_pattern: BuildPattern,
82
}
83

84
impl Shadow {
85
    /// Write the build configuration specified by this [`Shadow`] instance.
86
    /// The hook function is run as well, allowing it to append to `shadow-rs`'s output.
87
    pub fn hook<F>(&self, f: F) -> SdResult<()>
×
88
    where
89
        F: Fn(&File) -> SdResult<()>,
90
    {
91
        let desc = r#"// Below code generated by project custom from by build.rs"#;
×
92
        writeln!(&self.f, "\n{desc}\n")?;
×
93
        f(&self.f)?;
×
94
        Ok(())
×
95
    }
96

97
    /// Try to infer the CI system that we're currently running under.
98
    ///
99
    /// TODO: Recognize other CI types, especially Travis and Jenkins.
100
    fn try_ci(&self) -> CiType {
1✔
101
        if let Some(c) = self.std_env.get("GITLAB_CI") {
1✔
102
            if c == "true" {
×
103
                return CiType::Gitlab;
×
104
            }
105
        }
106

107
        if let Some(c) = self.std_env.get("GITHUB_ACTIONS") {
2✔
108
            if c == "true" {
1✔
109
                return CiType::Github;
1✔
110
            }
111
        }
112

113
        CiType::None
×
114
    }
115

116
    /// Checks if the specified build constant is in the deny list.
117
    ///
118
    /// # Arguments
119
    /// * `deny_const` - A value of type `ShadowConst` representing the build constant to check.
120
    ///
121
    /// # Returns
122
    /// * `true` if the build constant is present in the deny list; otherwise, `false`.
123
    pub fn deny_contains(&self, deny_const: ShadowConst) -> bool {
1✔
124
        self.deny_const.contains(&deny_const)
1✔
125
    }
126

127
    pub(crate) fn build_inner(builder: ShadowBuilder) -> SdResult<Shadow> {
1✔
128
        let out_path = builder.get_out_path()?;
2✔
129
        let src_path = builder.get_src_path()?;
1✔
130
        let build_pattern = builder.get_build_pattern().clone();
1✔
131
        let deny_const = builder.get_deny_const().clone();
2✔
132

133
        let out = {
134
            let path = Path::new(out_path);
2✔
135
            if !out_path.ends_with('/') {
1✔
136
                path.join(format!("{out_path}/{DEFINE_SHADOW_RS}"))
×
137
            } else {
138
                path.join(DEFINE_SHADOW_RS)
2✔
139
            }
140
        };
141

142
        let mut shadow = Shadow {
143
            f: File::create(out)?,
2✔
144
            map: Default::default(),
1✔
145
            std_env: Default::default(),
1✔
146
            deny_const,
147
            out_path: out_path.to_string(),
1✔
148
            build_pattern,
149
        };
150
        shadow.std_env = get_std_env();
2✔
151

152
        let ci_type = shadow.try_ci();
1✔
153
        let src_path = Path::new(src_path.as_str());
1✔
154

155
        let mut map = new_git(src_path, ci_type, &shadow.std_env);
1✔
156
        for (k, v) in new_project(&shadow.std_env) {
3✔
157
            map.insert(k, v);
2✔
158
        }
159
        for (k, v) in new_system_env(&shadow) {
2✔
160
            map.insert(k, v);
2✔
161
        }
162
        shadow.map = map;
1✔
163

164
        // deny const
165
        shadow.filter_deny();
1✔
166

167
        shadow.write_all()?;
1✔
168

169
        // handle hook
170
        if let Some(h) = builder.get_hook() {
1✔
171
            shadow.hook(h.hook_inner())?
×
172
        }
173

174
        Ok(shadow)
1✔
175
    }
176

177
    fn filter_deny(&mut self) {
1✔
178
        self.deny_const.iter().for_each(|x| {
2✔
179
            self.map.remove(x);
1✔
180
        })
181
    }
182

183
    fn write_all(&mut self) -> SdResult<()> {
1✔
184
        self.gen_header()?;
1✔
185

186
        self.gen_const()?;
1✔
187

188
        //write version function
189
        let gen_version = self.gen_version()?;
1✔
190

191
        self.gen_build_in(gen_version)?;
2✔
192

193
        Ok(())
1✔
194
    }
195

196
    fn gen_const(&mut self) -> SdResult<()> {
1✔
197
        let out_dir = &self.out_path;
1✔
198
        self.build_pattern.rerun_if(self.map.keys(), out_dir);
1✔
199

200
        for (k, v) in &self.map {
2✔
201
            self.write_const(k, v)?;
1✔
202
        }
203
        Ok(())
1✔
204
    }
205

206
    fn gen_header(&self) -> SdResult<()> {
1✔
207
        let desc = format!(
1✔
208
            r#"// Code automatically generated by `shadow-rs` (https://github.com/baoyachi/shadow-rs), do not edit.
209
// Author: https://www.github.com/baoyachi
210
// Generation time: {}
211
"#,
212
            now_date_time().to_rfc2822()
1✔
213
        );
214
        writeln!(&self.f, "{desc}\n\n")?;
2✔
215
        Ok(())
1✔
216
    }
217

218
    fn write_const(&self, shadow_const: ShadowConst, val: &ConstVal) -> SdResult<()> {
1✔
219
        let desc = format!("#[doc=r#\"{}\"#]", val.desc);
1✔
220
        let define = match val.t {
1✔
221
            ConstType::Str => format!(
2✔
222
                "#[allow(dead_code)]\n\
223
                {}\n\
224
            pub const {} :{} = r#\"{}\"#;",
225
                CARGO_CLIPPY_ALLOW_ALL,
226
                shadow_const.to_ascii_uppercase(),
1✔
227
                ConstType::Str,
228
                val.v
229
            ),
230
            ConstType::Bool => format!(
3✔
231
                "#[allow(dead_code)]\n\
232
                    {}\n\
233
            pub const {} :{} = {};",
234
                CARGO_CLIPPY_ALLOW_ALL,
235
                shadow_const.to_ascii_uppercase(),
1✔
236
                ConstType::Bool,
237
                val.v.parse::<bool>().unwrap()
1✔
238
            ),
239
            ConstType::Slice => format!(
3✔
240
                "#[allow(dead_code)]\n\
241
                    {}\n\
242
            pub const {} :{} = &{:?};",
243
                CARGO_CLIPPY_ALLOW_ALL,
244
                shadow_const.to_ascii_uppercase(),
1✔
245
                ConstType::Slice,
246
                val.v.as_bytes()
1✔
247
            ),
248
            ConstType::Usize => format!(
×
249
                "#[allow(dead_code)]\n\
250
                {}\n\
251
            pub const {} :{} = {};",
252
                CARGO_CLIPPY_ALLOW_ALL,
253
                shadow_const.to_ascii_uppercase(),
×
254
                ConstType::Usize,
255
                val.v.parse::<usize>().unwrap_or_default()
×
256
            ),
257
        };
258

259
        writeln!(&self.f, "{desc}")?;
2✔
260
        writeln!(&self.f, "{define}\n")?;
1✔
261
        Ok(())
1✔
262
    }
263

264
    fn gen_version(&mut self) -> SdResult<Vec<&'static str>> {
1✔
265
        let (ver_fn, clap_long_ver_fn) = match self.map.get(TAG) {
2✔
266
            None => (version_branch_const(), clap_long_version_branch_const()),
×
267
            Some(tag) => {
1✔
268
                if !tag.v.is_empty() {
2✔
269
                    (version_tag_const(), clap_long_version_tag_const())
1✔
270
                } else {
271
                    (version_branch_const(), clap_long_version_branch_const())
×
272
                }
273
            }
274
        };
275
        writeln!(&self.f, "{ver_fn}\n")?;
2✔
276
        writeln!(&self.f, "{clap_long_ver_fn}\n")?;
1✔
277

278
        Ok(vec![BUILD_CONST_VERSION, BUILD_CONST_CLAP_LONG_VERSION])
1✔
279
    }
280

281
    fn gen_build_in(&self, gen_const: Vec<&'static str>) -> SdResult<()> {
1✔
282
        let mut print_val = String::from("\n");
1✔
283

284
        // append gen const
285
        for (k, v) in &self.map {
2✔
286
            let tmp = match v.t {
1✔
287
                ConstType::Str | ConstType::Bool | ConstType::Usize => {
288
                    format!(r#"{}println!("{k}:{{{k}}}\n");{}"#, "\t", "\n")
2✔
289
                }
290
                ConstType::Slice => {
291
                    format!(r#"{}println!("{k}:{{:?}}\n",{});{}"#, "\t", k, "\n",)
1✔
292
                }
293
            };
294
            print_val.push_str(tmp.as_str());
2✔
295
        }
296

297
        // append gen fn
298
        for k in gen_const {
2✔
299
            let tmp = format!(r#"{}println!("{k}:{{{k}}}\n");{}"#, "\t", "\n");
2✔
300
            print_val.push_str(tmp.as_str());
2✔
301
        }
302

303
        #[cfg(not(feature = "no_std"))]
304
        {
305
            let everything_define = format!(
1✔
306
                "/// Prints all built-in `shadow-rs` build constants to standard output.\n\
307
            #[allow(dead_code)]\n\
308
            {CARGO_CLIPPY_ALLOW_ALL}\n\
309
            pub fn print_build_in() {\
310
            {{print_val}}\
311
            }\n",
312
            );
313

314
            writeln!(&self.f, "{everything_define}")?;
2✔
315

316
            use crate::gen_const::cargo_metadata_fn;
317
            writeln!(&self.f, "{}", cargo_metadata_fn(self))?;
1✔
318
        }
319

320
        Ok(())
1✔
321
    }
322
}
323

324
#[cfg(test)]
325
mod tests {
326
    use super::*;
327
    use crate::CARGO_TREE;
328
    use std::fs;
329

330
    #[test]
331
    fn test_build() -> SdResult<()> {
332
        ShadowBuilder::builder()
333
            .src_path("./")
334
            .out_path("./")
335
            .build()?;
336
        let shadow = fs::read_to_string(DEFINE_SHADOW_RS)?;
337
        assert!(!shadow.is_empty());
338
        assert!(shadow.lines().count() > 0);
339

340
        fs::remove_file(DEFINE_SHADOW_RS)?;
341

342
        ShadowBuilder::builder()
343
            .src_path("./")
344
            .out_path("./")
345
            .deny_const(BTreeSet::from([CARGO_TREE]))
346
            .build()?;
347

348
        let content = fs::read_to_string(DEFINE_SHADOW_RS)?;
349
        assert!(!content.is_empty());
350
        assert!(content.lines().count() > 0);
351
        let expect = "pub const CARGO_TREE :&str";
352
        assert!(!content.contains(expect));
353

354
        Ok(())
355
    }
356
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc