microsoft/openvmm

Public

mirrored from https://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/review-commit-3347

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

openhcl/build_info/src/lib.rs

279lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Provides build metadata
5
6#![expect(missing_docs)]
7
8use inspect::Inspect;
9
10#[derive(Debug, Inspect)]
11pub struct BuildInfo {
12 #[inspect(safe)]
13 crate_name: &'static str,
14 #[inspect(safe, rename = "scm_revision")]
15 revision: &'static str,
16 #[inspect(safe, rename = "scm_branch")]
17 branch: &'static str,
18 #[inspect(safe)]
19 internal_scm_revision: &'static str,
20 #[inspect(safe)]
21 internal_scm_branch: &'static str,
22 #[inspect(safe)]
23 openhcl_version: &'static str,
24}
25
26impl BuildInfo {
27 pub const fn new() -> Self {
28 // TODO: Once Option::unwrap_or() is stable in the const context
29 // can replace the if statements with it.
30 // Deliberately not storing `Option` to the build information
31 // structure to be closer to PODs.
32 Self {
33 crate_name: env!("CARGO_PKG_NAME"),
34 revision: if let Some(r) = option_env!("BUILD_GIT_SHA") {
35 r
36 } else {
37 ""
38 },
39 branch: if let Some(b) = option_env!("BUILD_GIT_BRANCH") {
40 b
41 } else {
42 ""
43 },
44 internal_scm_revision: if let Some(r) = option_env!("INTERNAL_GIT_SHA") {
45 r
46 } else {
47 ""
48 },
49 internal_scm_branch: if let Some(r) = option_env!("INTERNAL_GIT_BRANCH") {
50 r
51 } else {
52 ""
53 },
54 openhcl_version: if let Some(r) = option_env!("OPENHCL_VERSION") {
55 r
56 } else {
57 ""
58 },
59 }
60 }
61
62 pub fn crate_name(&self) -> &'static str {
63 self.crate_name
64 }
65
66 pub fn scm_revision(&self) -> &'static str {
67 self.revision
68 }
69
70 pub fn scm_branch(&self) -> &'static str {
71 self.branch
72 }
73
74 pub fn openhcl_version(&self) -> &'static str {
75 self.openhcl_version
76 }
77}
78
79// Parse a `&str` segment as a u32. Panics if the segment is empty or not a valid u32.
80const fn const_parse_u32_segment(s: &str) -> u32 {
81 assert!(!s.is_empty(), "version segment must not be empty");
82 match u32::from_str_radix(s, 10) {
83 Ok(v) => v,
84 Err(_) => panic!("version segment is not a valid u32"),
85 }
86}
87
88// Const-compatible equivalent of `s.split_once('.')`.
89const fn const_split_once_dot(s: &str) -> Option<(&str, &str)> {
90 let bytes = s.as_bytes();
91 let mut i = 0;
92 while i < bytes.len() {
93 if bytes[i] == b'.' {
94 let (left, dot_right) = s.split_at(i);
95 let (_, right) = dot_right.split_at(1);
96 return Some((left, right));
97 }
98 i += 1;
99 }
100 None
101}
102
103// Parse the `OPENHCL_VERSION` environment variable into four u32 components.
104// Strictly enforces the format to be "major.minor.build.platform",
105// where each is a valid u32.
106// Empty string is permitted to not panic on builds which omit the
107// `OPENHCL_VERSION` environment variable like `cargo test`.
108const fn const_parse_version(s: &str) -> [u32; 4] {
109 if s.is_empty() {
110 return [0, 0, 0, 0];
111 }
112 let (major, rest) = match const_split_once_dot(s) {
113 Some(pair) => pair,
114 None => panic!("expected 4 dot-separated components in version string"),
115 };
116 let (minor, rest) = match const_split_once_dot(rest) {
117 Some(pair) => pair,
118 None => panic!("expected 4 dot-separated components in version string"),
119 };
120 let (build, platform) = match const_split_once_dot(rest) {
121 Some(pair) => pair,
122 None => panic!("expected 4 dot-separated components in version string"),
123 };
124 // The fourth segment must not contain additional dots.
125 assert!(
126 const_split_once_dot(platform).is_none(),
127 "expected exactly 4 dot-separated components in version string"
128 );
129 let major = const_parse_u32_segment(major);
130 let minor = const_parse_u32_segment(minor);
131 let build = const_parse_u32_segment(build);
132 let platform = const_parse_u32_segment(platform);
133 [major, minor, build, platform]
134}
135
136/// Parsed components of the OPENHCL_VERSION env var (major.minor.build.platform).
137/// All parsing happens at compile time — components are stored as u32.
138#[derive(Debug)]
139pub struct OpenHclVersion {
140 product_name: &'static str,
141 major: u32,
142 minor: u32,
143 build: u32,
144 platform: u32,
145}
146
147impl OpenHclVersion {
148 pub const fn new() -> Self {
149 let [major, minor, build, platform] = const_parse_version(BuildInfo::new().openhcl_version);
150 Self {
151 product_name: "OpenHCL",
152 major,
153 minor,
154 build,
155 platform,
156 }
157 }
158
159 pub const fn product_name(&self) -> &'static str {
160 self.product_name
161 }
162
163 pub const fn major(&self) -> u32 {
164 self.major
165 }
166
167 pub const fn minor(&self) -> u32 {
168 self.minor
169 }
170
171 pub const fn build(&self) -> u32 {
172 self.build
173 }
174
175 pub const fn platform(&self) -> u32 {
176 self.platform
177 }
178}
179
180pub static OPENHCL_VERSION: OpenHclVersion = OpenHclVersion::new();
181
182// Placing into a separate section to make easier to discover
183// the build information even without a debugger.
184//
185// The #[used] attribute is not used as the static is reachable
186// via a public function.
187//
188// The #[external_name] attribute is used to give the static
189// an unmangled name and again be easily discoverable even without
190// a debugger. With a debugger, the non-mangled name is easier
191// to use.
192
193// UNSAFETY: link_section and export_name are unsafe.
194#[expect(unsafe_code)]
195// SAFETY: The build_info section is custom and carries no safety requirements.
196#[unsafe(link_section = ".build_info")]
197// SAFETY: The name "BUILD_INFO" is only declared here in OpenHCL and shouldn't
198// collide with any other symbols. It is a special symbol intended for
199// post-mortem debugging, and no runtime functionality should depend on it.
200#[unsafe(export_name = "BUILD_INFO")]
201static BUILD_INFO: BuildInfo = BuildInfo::new();
202
203pub fn get() -> &'static BuildInfo {
204 // Without `black_box`, BUILD_INFO is optimized away
205 // in the release builds with `fat` LTO.
206 std::hint::black_box(&BUILD_INFO)
207}
208
209#[cfg(test)]
210mod tests {
211 use super::const_parse_version;
212
213 #[test]
214 fn empty_string() {
215 // Ensure running `cargo test` doesn't panic.
216 assert_eq!(const_parse_version(""), [0, 0, 0, 0]);
217 }
218
219 #[test]
220 fn full_version() {
221 assert_eq!(const_parse_version("1.6.499.2"), [1, 6, 499, 2]);
222 }
223
224 #[test]
225 fn zero_platform() {
226 assert_eq!(const_parse_version("1.1.1.0"), [1, 1, 1, 0]);
227 }
228
229 #[test]
230 fn zero_major_allowed() {
231 assert_eq!(const_parse_version("0.1.1.0"), [0, 1, 1, 0]);
232 }
233
234 #[test]
235 fn zero_minor_allowed() {
236 assert_eq!(const_parse_version("1.0.1.0"), [1, 0, 1, 0]);
237 }
238
239 #[test]
240 fn zero_build_allowed() {
241 assert_eq!(const_parse_version("1.1.0.0"), [1, 1, 0, 0]);
242 }
243
244 #[test]
245 #[should_panic(expected = "expected 4 dot-separated components")]
246 fn partial_version_panics() {
247 const_parse_version("1.2");
248 }
249
250 #[test]
251 #[should_panic(expected = "version segment is not a valid u32")]
252 fn non_digit_segment_panics() {
253 const_parse_version("1.2.3A.4");
254 }
255
256 #[test]
257 #[should_panic(expected = "expected exactly 4 dot-separated components")]
258 fn extra_segments_panics() {
259 const_parse_version("1.2.3.4.5");
260 }
261
262 #[test]
263 #[should_panic(expected = "expected 4 dot-separated components")]
264 fn single_component_panics() {
265 const_parse_version("42");
266 }
267
268 #[test]
269 #[should_panic(expected = "version segment is not a valid u32")]
270 fn overflow_panics() {
271 const_parse_version("9999999999.0.0.0");
272 }
273
274 #[test]
275 #[should_panic(expected = "version segment must not be empty")]
276 fn empty_segment_panics() {
277 const_parse_version("1..3.4");
278 }
279}
280