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#[macro_export]
116macro_rules! __git_sha_internal {
117    () => {
118        $crate::private::run_command_str!(
119            "sh",
120            "-c",
121            r#"if [ -n "$MZ_DEV_BUILD_SHA" ]; then
122                    echo "$MZ_DEV_BUILD_SHA"
123                else
124                    # Unfortunately we need to suppress error messages from `git`, as
125                    # run_command_str will display no error message at all if we print
126                    # more than one line of output to stderr.
127                    git rev-parse --verify HEAD 2>/dev/null || {
128                        printf "error: unable to determine Git SHA; " >&2
129                        printf "either build from working Git clone " >&2
130                        printf "(see https://materialize.com/docs/install/#build-from-source), " >&2
131                        printf "or specify SHA manually by setting MZ_DEV_BUILD_SHA environment variable" >&2
132                        printf "If you are using git worktrees, you must be in the primary worktree" >&2
133                        printf "for automatic detection to work." >&2
134                        exit 1
135                    }
136                fi"#
137        )
138    }
139}
140
141#[doc(hidden)]
142pub mod private {
143    pub use compile_time_run::run_command_str;
144}
145
146#[cfg(test)]
147mod test {
148    #[test] // allow(test-attribute)
149    fn smoketest_build_info() {
150        let build_info = crate::build_info!();
151
152        assert_eq!(build_info.sha.len(), 40);
153    }
154}