build_info_build/build_script_options/
mod.rs1use core::sync::atomic::{AtomicBool, Ordering};
2use std::path::{Path, PathBuf};
3
4use base64::write::EncoderWriter as Base64Encoder;
5use build_info_common::{OptimizationLevel, VersionedString};
6use chrono::{DateTime, Utc};
7
8pub use self::crate_info::DependencyDepth;
9use super::BuildInfo;
10
11mod compiler;
12mod crate_info;
13mod target;
14mod timestamp;
15mod version_control;
16
17pub fn cargo_toml() -> &'static Path {
18 static CARGO_TOML: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
19 CARGO_TOML.get_or_init(|| Path::new(&std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("Cargo.toml"))
20}
21
22pub struct BuildScriptOptions {
24 consumed: bool,
26
27 timestamp: Option<DateTime<Utc>>,
29
30 collect_runtime_dependencies: DependencyDepth,
32
33 collect_build_dependencies: DependencyDepth,
35
36 collect_dev_dependencies: DependencyDepth,
38}
39static BUILD_SCRIPT_RAN: AtomicBool = AtomicBool::new(false);
40
41impl BuildScriptOptions {
42 fn drop_to_build_info(&mut self) -> BuildInfo {
44 assert!(!self.consumed);
45 self.consumed = true;
46
47 let profile = std::env::var("PROFILE").unwrap_or_else(|_| "UNKNOWN".to_string());
48 let optimization_level = match std::env::var("OPT_LEVEL")
49 .expect("Expected environment variable `OPT_LEVEL` to be set by cargo")
50 .as_str()
51 {
52 "0" => OptimizationLevel::O0,
53 "1" => OptimizationLevel::O1,
54 "2" => OptimizationLevel::O2,
55 "3" => OptimizationLevel::O3,
56 "s" => OptimizationLevel::Os,
57 "z" => OptimizationLevel::Oz,
58 level => panic!("Unknown optimization level {level:?}"),
59 };
60
61 let compiler = compiler::get_info();
62 let target = target::get_info();
63 let crate_info::Manifest {
64 crate_info,
65 workspace_root,
66 } = crate_info::read_manifest(
67 &target.triple,
68 self.collect_runtime_dependencies,
69 self.collect_build_dependencies,
70 self.collect_dev_dependencies,
71 );
72 let version_control = version_control::get_info();
73
74 let timestamp = self.timestamp.unwrap_or_else(timestamp::get_timestamp);
75 let build_info = BuildInfo {
76 timestamp,
77 profile,
78 optimization_level,
79 crate_info,
80 compiler,
81 target,
82 version_control,
83 };
84
85 let mut bytes = Vec::new();
86 const BASE64_ENGINE: base64::engine::GeneralPurpose = base64::engine::GeneralPurpose::new(
87 &base64::alphabet::STANDARD,
88 base64::engine::GeneralPurposeConfig::new()
89 .with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
90 );
91 let string_safe = Base64Encoder::new(&mut bytes, &BASE64_ENGINE);
92 let mut compressed = zstd::Encoder::new(string_safe, 22).expect("Could not create ZSTD encoder");
93 bincode::serde::encode_into_std_write(&build_info, &mut compressed, bincode::config::standard()).unwrap();
94 compressed.finish().unwrap().finish().unwrap();
95
96 let string = String::from_utf8(bytes).unwrap();
97 let versioned = VersionedString::build_info_common_versioned(string);
98 let serialized = serde_json::to_string(&versioned).unwrap();
99
100 println!("cargo:rustc-env=BUILD_INFO={serialized}");
101
102 rebuild_if_project_changes(&workspace_root);
106
107 build_info
108 }
109
110 pub fn build(mut self) -> BuildInfo {
113 self.drop_to_build_info()
114 }
115}
116
117impl From<BuildScriptOptions> for BuildInfo {
118 fn from(opts: BuildScriptOptions) -> BuildInfo {
119 opts.build()
120 }
121}
122
123impl Default for BuildScriptOptions {
124 fn default() -> Self {
125 let build_script_ran = BUILD_SCRIPT_RAN.swap(true, Ordering::SeqCst);
126 assert!(!build_script_ran, "The build script may only be run once.");
127
128 Self {
129 consumed: false,
130 timestamp: None,
131 collect_runtime_dependencies: DependencyDepth::None,
132 collect_build_dependencies: DependencyDepth::None,
133 collect_dev_dependencies: DependencyDepth::None,
134 }
135 }
136}
137
138impl Drop for BuildScriptOptions {
139 fn drop(&mut self) {
140 if !self.consumed {
141 let _build_info = self.drop_to_build_info();
142 }
143 }
144}
145
146fn rebuild_if_project_changes(workspace_root: &str) {
152 println!("cargo:rerun-if-changed={}", cargo_toml().to_str().unwrap());
153 println!(
154 "cargo:rerun-if-changed={}",
155 Path::new(workspace_root).join("Cargo.lock").to_str().unwrap()
156 );
157
158 for source in glob::glob_with(
159 "**/*.rs",
160 glob::MatchOptions {
161 case_sensitive: false,
162 require_literal_separator: false,
163 require_literal_leading_dot: false,
164 },
165 )
166 .unwrap()
167 .map(|source| source.unwrap())
168 {
169 println!("cargo:rerun-if-changed={}", source.to_str().unwrap());
170 }
171}