mz_build_info/
lib.rs

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.
9
10//! 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.
14
15/// Build information.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct BuildInfo {
18    /// The version number of the build.
19    pub version: &'static str,
20    /// The 40-character SHA-1 hash identifying the Git commit of the build.
21    pub sha: &'static str,
22}
23
24/// 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};
32
33/// The target triple of the platform.
34pub const TARGET_TRIPLE: &str = env!("TARGET_TRIPLE");
35
36impl BuildInfo {
37    /// Constructs a human-readable version string.
38    pub fn human_version(&self, helm_chart_version: Option<String>) -> String {
39        if let Some(ref helm_chart_version) = helm_chart_version {
40            format!(
41                "v{} ({}, helm chart: {})",
42                self.version,
43                &self.sha[..9],
44                helm_chart_version
45            )
46        } else {
47            format!("v{} ({})", self.version, &self.sha[..9])
48        }
49    }
50
51    /// 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")]
61    pub fn semver_version(&self) -> semver::Version {
62        self.version
63            .parse()
64            .expect("build version is not valid semver")
65    }
66
67    /// 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")]
70    pub fn semver_version_build(&self) -> Option<semver::Version> {
71        let build_id = buildid::build_id()?;
72        let build_id = hex::encode(build_id);
73        let version = format!("{}+{}", self.version, build_id)
74            .parse()
75            .expect("build version is not valid semver");
76        Some(version)
77    }
78
79    /// Returns the version as an integer along the lines of Pg's server_version_num
80    #[cfg(feature = "semver")]
81    pub fn version_num(&self) -> i32 {
82        let semver: semver::Version = self
83            .version
84            .parse()
85            .expect("build version is not a valid semver");
86        let 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    }
92
93    /// Returns whether the version is a development version
94    pub fn is_dev(&self) -> bool {
95        self.version.contains("dev")
96    }
97}
98
99/// 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}
114
115#[cfg(all(bazel, stamped))]
116#[macro_export]
117macro_rules! __git_sha_internal {
118    () => {
119        $crate::private::bazel_variables::GIT_COMMIT_HASH
120    };
121}
122
123// 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",
134            r#"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}
142
143#[cfg(not(bazel))]
144#[macro_export]
145macro_rules! __git_sha_internal {
146    () => {
147        $crate::private::run_command_str!(
148            "sh",
149            "-c",
150            r#"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}
169
170#[doc(hidden)]
171pub mod private {
172    pub use compile_time_run::run_command_str;
173
174    // 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)]
179    pub mod bazel_variables {
180        include!(std::env!("BAZEL_GEN_BUILD_INFO"));
181    }
182}
183
184#[cfg(test)]
185mod test {
186    #[test] // allow(test-attribute)
187    fn smoketest_build_info() {
188        let build_info = crate::build_info!();
189
190        assert_eq!(build_info.sha.len(), 40);
191    }
192}