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

baoyachi / shadow-rs / 20376485967

19 Dec 2025 04:23PM UTC coverage: 76.504% (+0.3%) from 76.218%
20376485967

push

github

web-flow
Update Cargo.toml

534 of 698 relevant lines covered (76.5%)

1.16 hits per line

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

90.08
/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!(
1✔
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!(
1✔
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()
2✔
238
            ),
239
            ConstType::Slice => format!(
1✔
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()
2✔
247
            ),
248
            ConstType::Usize => format!(
1✔
249
                "#[allow(dead_code)]\n\
250
                {}\n\
251
            pub const {} :{} = {};",
252
                CARGO_CLIPPY_ALLOW_ALL,
253
                shadow_const.to_ascii_uppercase(),
1✔
254
                ConstType::Usize,
255
                val.v.parse::<usize>().unwrap_or_default()
2✔
256
            ),
257
            ConstType::Int => format!(
1✔
258
                "#[allow(dead_code)]\n\
259
                {}\n\
260
            pub const {} :{} = {};",
261
                CARGO_CLIPPY_ALLOW_ALL,
262
                shadow_const.to_ascii_uppercase(),
1✔
263
                ConstType::Int,
264
                val.v.parse::<i64>().unwrap_or_default()
2✔
265
            ),
266
        };
267

268
        writeln!(&self.f, "{desc}")?;
2✔
269
        writeln!(&self.f, "{define}\n")?;
1✔
270
        Ok(())
1✔
271
    }
272

273
    fn gen_version(&mut self) -> SdResult<Vec<&'static str>> {
1✔
274
        let (ver_fn, clap_long_ver_fn) = match self.map.get(TAG) {
2✔
275
            None => (version_branch_const(), clap_long_version_branch_const()),
×
276
            Some(tag) => {
1✔
277
                if !tag.v.is_empty() {
2✔
278
                    (version_tag_const(), clap_long_version_tag_const())
1✔
279
                } else {
280
                    (version_branch_const(), clap_long_version_branch_const())
×
281
                }
282
            }
283
        };
284
        writeln!(&self.f, "{ver_fn}\n")?;
2✔
285
        writeln!(&self.f, "{clap_long_ver_fn}\n")?;
1✔
286

287
        Ok(vec![BUILD_CONST_VERSION, BUILD_CONST_CLAP_LONG_VERSION])
1✔
288
    }
289

290
    fn gen_build_in(&self, gen_const: Vec<&'static str>) -> SdResult<()> {
1✔
291
        let mut print_val = String::from("\n");
1✔
292
        let mut params = String::from("\n");
1✔
293
        let mut default = String::from("\n");
1✔
294
        let mut all = String::from("\n");
1✔
295

296
        // append gen const
297
        for (k, v) in &self.map {
2✔
298
            let tmp = match v.t {
1✔
299
                ConstType::Str | ConstType::Bool | ConstType::Usize | ConstType::Int => {
300
                    default.push_str(&format!("\t\t\t{k}: true,\n"));
2✔
301
                    all.push_str(&format!("\t\t\t{k}: true,\n"));
1✔
302
                    format!(
1✔
303
                        r#"{}if self.{k} {{ writeln!(f, "{k}:{{{k}}}\n")?; }}{}"#,
304
                        "\t\t", "\n"
305
                    )
306
                }
307
                ConstType::Slice => {
308
                    default.push_str(&format!("\t\t\t{k}: false,\n"));
1✔
309
                    all.push_str(&format!("\t\t\t{k}: true,\n"));
1✔
310
                    format!(
1✔
311
                        r#"{}if self.{k} {{ writeln!(f, "{k}:{{:?}}\n",{})?; }}{}"#,
312
                        "\t\t", k, "\n",
313
                    )
314
                }
315
            };
316
            print_val.push_str(tmp.as_str());
2✔
317
            params.push_str(&format!("\tpub {k}: bool,\n"));
1✔
318
        }
319

320
        // append gen fn
321
        for k in gen_const {
2✔
322
            let tmp = format!(
2✔
323
                r#"{}if self.{k} {{ writeln!(f, "{k}:{{{k}}}\n")?; }}{}"#,
324
                "\t\t", "\n"
325
            );
326
            print_val.push_str(tmp.as_str());
2✔
327
            params.push_str(&format!("\tpub {k}: bool,\n"));
1✔
328
            default.push_str(&format!("\t\t\t{k}: true,\n"));
1✔
329
            all.push_str(&format!("\t\t\t{k}: true,\n"));
1✔
330
        }
331

332
        default.push_str("\t\t");
1✔
333
        all.push_str("\t\t");
1✔
334
        print_val.push_str("\t\tOk(())\n\t");
1✔
335

336
        let build_info_display_define = format!(
1✔
337
            "/// A struct that implements [`core::fmt::Display`] which\n\
338
            /// writes consts generated by `shadow-rs` to it's formatter\n\
339
            #[allow(non_snake_case)]\n\
340
            {CARGO_CLIPPY_ALLOW_ALL}\n\
341
            pub struct BuildInfoDisplay {\
342
                {{params}}\
343
            }\n\n\
344
            impl Default for BuildInfoDisplay {{\n\
345
                \t#[allow(dead_code)]\n\
346
                \t{CARGO_CLIPPY_ALLOW_ALL}\n\
347
                \t/// Every constant that `shadow-rs` tracks will be printed\n\
348
                \t/// except for slices (CARGO_METADATA for example)\n\
349
                \tfn default() -> Self {{\n\
350
                    \t\tSelf {\
351
                        {{default}}\
352
                    }\n\
353
                \t}}\n\
354
            }}\n\n\
355
            impl BuildInfoDisplay {{\n\
356
                \t#[allow(dead_code)]\n\
357
                \t{CARGO_CLIPPY_ALLOW_ALL}\n\
358
                \t/// Every constant that `shadow-rs` tracks will be printed\n\
359
                \tpub fn all() -> Self {{\n\
360
                    \t\tSelf {\
361
                        {{all}}\
362
                    }\n\
363
                \t}}\n\
364
            }}\n\n\
365
            impl core::fmt::Display for BuildInfoDisplay {{\n\
366
                \t#[allow(dead_code)]\n\
367
                \t{CARGO_CLIPPY_ALLOW_ALL}\n\
368
                \tfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\
369
                    {{print_val}}\
370
                }\n\
371
            }}\n",
372
        );
373

374
        writeln!(&self.f, "{build_info_display_define}")?;
2✔
375

376
        #[cfg(not(feature = "no_std"))]
377
        {
378
            let print_build_in_define = format!(
1✔
379
                "/// Prints all built-in `shadow-rs` build constants\n\
380
                /// (except for slices) to standard output.\n\
381
            #[allow(dead_code)]\n\
382
            {CARGO_CLIPPY_ALLOW_ALL}\n\
383
            pub fn print_build_in() {{\n\
384
                \tprintln!(\"{{}}\", BuildInfoDisplay::default());\n\
385
            }}\n"
386
            );
387

388
            writeln!(&self.f, "{print_build_in_define}")?;
2✔
389

390
            #[cfg(feature = "metadata")]
391
            {
392
                use crate::gen_const::cargo_metadata_fn;
393
                writeln!(&self.f, "{}", cargo_metadata_fn(self))?;
394
            }
395
        }
396

397
        Ok(())
1✔
398
    }
399
}
400

401
#[cfg(test)]
402
mod tests {
403
    use super::*;
404
    use crate::CARGO_TREE;
405
    use std::fs;
406

407
    #[test]
408
    fn test_build() -> SdResult<()> {
409
        ShadowBuilder::builder()
410
            .src_path("./")
411
            .out_path("./")
412
            .build()?;
413
        let shadow = fs::read_to_string(DEFINE_SHADOW_RS)?;
414
        assert!(!shadow.is_empty());
415
        assert!(shadow.lines().count() > 0);
416

417
        fs::remove_file(DEFINE_SHADOW_RS)?;
418

419
        ShadowBuilder::builder()
420
            .src_path("./")
421
            .out_path("./")
422
            .deny_const(BTreeSet::from([CARGO_TREE]))
423
            .build()?;
424

425
        let content = fs::read_to_string(DEFINE_SHADOW_RS)?;
426
        assert!(!content.is_empty());
427
        assert!(content.lines().count() > 0);
428
        let expect = "pub const CARGO_TREE :&str";
429
        assert!(!content.contains(expect));
430

431
        Ok(())
432
    }
433
}
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

© 2026 Coveralls, Inc