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            if helm_chart_version != &self.version {
41                return format!(
42                    "v{} ({}, helm chart: {})",
43                    self.version,
44                    &self.sha[..9],
45                    helm_chart_version
46                );
47            }
48        }
49        format!("v{} ({})", self.version, &self.sha[..9])
50    }
51
52    /// Returns the version as a rich [semantic version][semver].
53    ///
54    /// This method is only available when the `semver` feature is active.
55    ///
56    /// # Panics
57    ///
58    /// Panics if the `version` field is not a valid semantic version.
59    ///
60    /// [semver]: https://semver.org
61    #[cfg(feature = "semver")]
62    pub fn semver_version(&self) -> semver::Version {
63        self.version
64            .parse()
65            .expect("build version is not valid semver")
66    }
67
68    /// The same as [`Self::semver_version`], but includes build metadata in the returned version,
69    /// if build metadata is available on the compiled platform.
70    #[cfg(feature = "semver")]
71    pub fn semver_version_build(&self) -> Option<semver::Version> {
72        let build_id = buildid::build_id()?;
73        let build_id = hex::encode(build_id);
74        let version = format!("{}+{}", self.version, build_id)
75            .parse()
76            .expect("build version is not valid semver");
77        Some(version)
78    }
79
80    /// Returns the version as an integer along the lines of Pg's server_version_num
81    #[cfg(feature = "semver")]
82    pub fn version_num(&self) -> i32 {
83        let semver: semver::Version = self
84            .version
85            .parse()
86            .expect("build version is not a valid semver");
87        let ver_string = format!(
88            "{:0>2}{:0>3}{:0>2}",
89            semver.major, semver.minor, semver.patch
90        );
91        ver_string.parse::<i32>().unwrap()
92    }
93
94    /// Returns whether the version is a development version
95    pub fn is_dev(&self) -> bool {
96        self.version.contains("dev")
97    }
98}
99
100/// Generates an appropriate [`BuildInfo`] instance.
101///
102/// This macro should be invoked at the leaf of the crate graph, usually in the
103/// final binary, and the resulting `BuildInfo` struct plumbed into whatever
104/// libraries require it. Invoking the macro in intermediate crates may result
105/// in a build info with stale, cached values for the build SHA and time.
106#[macro_export]
107macro_rules! build_info {
108    () => {
109        $crate::BuildInfo {
110            version: env!("CARGO_PKG_VERSION"),
111            sha: $crate::__git_sha_internal!(),
112        }
113    };
114}
115
116#[macro_export]
117macro_rules! __git_sha_internal {
118    () => {
119        $crate::private::run_command_str!(
120            "sh",
121            "-c",
122            r#"if [ -n "$MZ_DEV_BUILD_SHA" ]; then
123                    echo "$MZ_DEV_BUILD_SHA"
124                else
125                    # Unfortunately we need to suppress error messages from `git`, as
126                    # run_command_str will display no error message at all if we print
127                    # more than one line of output to stderr.
128                    git rev-parse --verify HEAD 2>/dev/null || {
129                        printf "error: unable to determine Git SHA; " >&2
130                        printf "either build from working Git clone " >&2
131                        printf "(see https://materialize.com/docs/install/#build-from-source), " >&2
132                        printf "or specify SHA manually by setting MZ_DEV_BUILD_SHA environment variable" >&2
133                        printf "If you are using git worktrees, you must be in the primary worktree" >&2
134                        printf "for automatic detection to work." >&2
135                        exit 1
136                    }
137                fi"#
138        )
139    }
140}
141
142#[doc(hidden)]
143pub mod private {
144    pub use compile_time_run::run_command_str;
145}
146
147#[cfg(test)]
148mod test {
149    #[test] // allow(test-attribute)
150    fn smoketest_build_info() {
151        let build_info = crate::build_info!();
152
153        assert_eq!(build_info.sha.len(), 40);
154    }
155}