1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
910//! Metadata about a Materialize build.
11//!
12//! These types are located in a dependency-free crate so they can be used
13//! from any layer of the stack.
1415/// Build information.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct BuildInfo {
18/// The version number of the build.
19pub version: &'static str,
20/// The 40-character SHA-1 hash identifying the Git commit of the build.
21pub sha: &'static str,
22}
2324/// Dummy build information.
25///
26/// Intended for use in contexts where getting the correct build information is
27/// impossible or unnecessary, like in tests.
28pub const DUMMY_BUILD_INFO: BuildInfo = BuildInfo {
29 version: "0.0.0+dummy",
30 sha: "0000000000000000000000000000000000000000",
31};
3233/// The target triple of the platform.
34pub const TARGET_TRIPLE: &str = env!("TARGET_TRIPLE");
3536impl BuildInfo {
37/// Constructs a human-readable version string.
38pub fn human_version(&self, helm_chart_version: Option<String>) -> String {
39if let Some(ref helm_chart_version) = helm_chart_version {
40format!(
41"v{} ({}, helm chart: {})",
42self.version,
43&self.sha[..9],
44 helm_chart_version
45 )
46 } else {
47format!("v{} ({})", self.version, &self.sha[..9])
48 }
49 }
5051/// Returns the version as a rich [semantic version][semver].
52 ///
53 /// This method is only available when the `semver` feature is active.
54 ///
55 /// # Panics
56 ///
57 /// Panics if the `version` field is not a valid semantic version.
58 ///
59 /// [semver]: https://semver.org
60#[cfg(feature = "semver")]
61pub fn semver_version(&self) -> semver::Version {
62self.version
63 .parse()
64 .expect("build version is not valid semver")
65 }
6667/// The same as [`Self::semver_version`], but includes build metadata in the returned version,
68 /// if build metadata is available on the compiled platform.
69#[cfg(feature = "semver")]
70pub fn semver_version_build(&self) -> Option<semver::Version> {
71let build_id = buildid::build_id()?;
72let build_id = hex::encode(build_id);
73let version = format!("{}+{}", self.version, build_id)
74 .parse()
75 .expect("build version is not valid semver");
76Some(version)
77 }
7879/// Returns the version as an integer along the lines of Pg's server_version_num
80#[cfg(feature = "semver")]
81pub fn version_num(&self) -> i32 {
82let semver: semver::Version = self
83.version
84 .parse()
85 .expect("build version is not a valid semver");
86let ver_string = format!(
87"{:0>2}{:0>3}{:0>2}",
88 semver.major, semver.minor, semver.patch
89 );
90 ver_string.parse::<i32>().unwrap()
91 }
9293/// Returns whether the version is a development version
94pub fn is_dev(&self) -> bool {
95self.version.contains("dev")
96 }
97}
9899/// Generates an appropriate [`BuildInfo`] instance.
100///
101/// This macro should be invoked at the leaf of the crate graph, usually in the
102/// final binary, and the resulting `BuildInfo` struct plumbed into whatever
103/// libraries require it. Invoking the macro in intermediate crates may result
104/// in a build info with stale, cached values for the build SHA and time.
105#[macro_export]
106macro_rules! build_info {
107 () => {
108$crate::BuildInfo {
109 version: env!("CARGO_PKG_VERSION"),
110 sha: $crate::__git_sha_internal!(),
111 }
112 };
113}
114115#[cfg(all(bazel, stamped))]
116#[macro_export]
117macro_rules! __git_sha_internal {
118 () => {
119$crate::private::bazel_variables::GIT_COMMIT_HASH
120};
121}
122123// To improve the effectiveness of remote caching we only stamp "release" builds
124// and otherwise side-channel git status through a known file.
125//
126// See: <https://github.com/bazelbuild/bazel/issues/10075>
127#[cfg(all(bazel, not(stamped)))]
128#[macro_export]
129macro_rules! __git_sha_internal {
130 () => {
131$crate::private::run_command_str!(
132"sh",
133"-c",
134r#"if [ -f /tmp/mz_git_hash.txt ]; then
135 cat /tmp/mz_git_hash.txt
136 else
137 echo "0000000000000000000000000000000000000000"
138 fi"#
139)
140 };
141}
142143#[cfg(not(bazel))]
144#[macro_export]
145macro_rules! __git_sha_internal {
146 () => {
147$crate::private::run_command_str!(
148"sh",
149"-c",
150r#"if [ -n "$MZ_DEV_BUILD_SHA" ]; then
151 echo "$MZ_DEV_BUILD_SHA"
152 else
153 # Unfortunately we need to suppress error messages from `git`, as
154 # run_command_str will display no error message at all if we print
155 # more than one line of output to stderr.
156 git rev-parse --verify HEAD 2>/dev/null || {
157 printf "error: unable to determine Git SHA; " >&2
158 printf "either build from working Git clone " >&2
159 printf "(see https://materialize.com/docs/install/#build-from-source), " >&2
160 printf "or specify SHA manually by setting MZ_DEV_BUILD_SHA environment variable" >&2
161 printf "If you are using git worktrees, you must be in the primary worktree" >&2
162 printf "for automatic detection to work." >&2
163 exit 1
164 }
165 fi"#
166)
167 }
168}
169170#[doc(hidden)]
171pub mod private {
172pub use compile_time_run::run_command_str;
173174// Bazel has a "workspace status" feature that allows us to collect info from
175 // the workspace (e.g. git hash) at build time. These values get incleded via
176 // a generated file.
177#[cfg(all(bazel, stamped))]
178 #[allow(unused)]
179pub mod bazel_variables {
180include!(std::env!("BAZEL_GEN_BUILD_INFO"));
181 }
182}
183184#[cfg(test)]
185mod test {
186#[test] // allow(test-attribute)
187fn smoketest_build_info() {
188let build_info = crate::build_info!();
189190assert_eq!(build_info.sha.len(), 40);
191 }
192}