1use anyhow::Context as _;
38use clap::ArgAction;
39use vergen::{BuildBuilder, CargoBuilder, Emitter, RustcBuilder};
40use vergen_git2::Git2Builder;
41
42#[macro_export]
49macro_rules! build_info {
50 () => {
51 pub(crate) mod build_info {
52 fn profile_human() -> &'static str {
53 let profile = env!("VERGEN_CARGO_OPT_LEVEL");
54
55 match profile {
57 "0" => "0, no optimizations",
58 "1" => "1, basic optimizations",
59 "2" => "2, some optimizations",
60 "3" => "3, all optimizations",
61 "s" => "'s', optimize for binary size",
62 "z" => "'z', optimize for binary size, but also turn off loop vectorization.",
63 profile => profile,
64 }
65 }
66
67 #[derive(Debug, Clone)]
69 pub struct BuildInfo {
70 build_timestamp: &'static str,
71 build_version: &'static str,
72 commit_sha: Option<&'static str>,
73 commit_dirty: Option<&'static str>,
74 commit_date: Option<&'static str>,
75 commit_branch: Option<&'static str>,
76 rustc_version: &'static str,
77 rustc_channel: &'static str,
78 rustc_host_triple: &'static str,
79 cargo_target_triple: &'static str,
80 cargo_profile: &'static str,
81 license_information: Option<String>,
82 }
83
84 impl BuildInfo {
85 pub fn new() -> Self {
87 Self {
88 build_timestamp: env!("VERGEN_BUILD_TIMESTAMP"),
89 build_version: env!("CARGO_PKG_VERSION"),
90 commit_sha: option_env!("VERGEN_GIT_SHA"),
91 commit_dirty: option_env!("VERGEN_GIT_DIRTY"),
92 commit_date: option_env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
93 commit_branch: option_env!("VERGEN_GIT_BRANCH"),
94 rustc_version: env!("VERGEN_RUSTC_SEMVER"),
95 rustc_channel: env!("VERGEN_RUSTC_CHANNEL"),
96 rustc_host_triple: env!("VERGEN_RUSTC_HOST_TRIPLE"),
97 cargo_target_triple: env!("VERGEN_CARGO_TARGET_TRIPLE"),
98 cargo_profile: profile_human(),
99 license_information: None,
100 }
101 }
102
103 pub fn with_license(license_information: String) -> Self {
104 let mut this = Self::new();
105 this.license_information = Some(license_information);
106 this
107 }
108 }
109
110 impl BuildInfo {
111 pub fn format(&self, args: &opentalk_version::InfoArgs) -> Option<String> {
112 if !args.should_print() {
113 return None;
114 }
115 let mut text = String::new();
116 if (args.version) {
117 let _ = self.version(&mut text);
118 }
119 if (args.license) {
120 self.license(&mut text);
121 }
122 Some(text)
123 }
124
125 fn version(&self, text: &mut String) -> Result<(), std::fmt::Error> {
126 use std::fmt::Write as _;
127
128 write!(text, "Build Timestamp: {}\n", self.build_timestamp)?;
129 write!(text, "Build Version: {}\n", self.build_version)?;
130 if let Some(sha) = self.commit_sha {
131 write!(text, "Commit SHA: {}", sha)?;
132 match self.commit_dirty {
133 Some("true") => write!(text, "-dirty\n")?,
134 _ => write!(text, "\n")?,
135 }
136 }
137 if let Some(commit_date) = self.commit_date {
138 write!(text, "Commit Date: {}\n", commit_date)?;
139 }
140
141 if let Some(commit_branch) = self.commit_branch {
142 write!(text, "Commit Branch: {}\n", commit_branch)?;
143 }
144 write!(text, "rustc Version: {}\n", self.rustc_version)?;
145 write!(text, "rustc Channel: {}\n", self.rustc_channel)?;
146 write!(text, "rustc Host Triple: {}\n", self.rustc_host_triple)?;
147 write!(text, "cargo Target Triple: {}\n", self.cargo_target_triple)?;
148 write!(text, "cargo Profile: {}\n", self.cargo_profile)
149 }
150
151 fn license(&self, text: &mut String) {
152 if let Some(license) = self.license_information.as_deref() {
153 text.push_str(license);
154 }
155 }
156 }
157 }
158 };
159}
160
161#[derive(clap::Args, Debug, Clone)]
162pub struct InfoArgs {
163 #[arg(short('V'), long, action=ArgAction::SetTrue, help = "Print version information")]
165 pub version: bool,
166 #[arg(short, long, action=ArgAction::SetTrue, help = "Print license information")]
168 pub license: bool,
169}
170
171impl InfoArgs {
172 pub fn should_print(&self) -> bool {
173 self.version || self.license
174 }
175}
176
177pub fn collect_build_information() -> anyhow::Result<()> {
183 let mut emitter = Emitter::default();
184 let builder = emitter
185 .add_instructions(&CargoBuilder::all_cargo().context("Failed to build cargo variables")?)
186 .context("Failed to add cargo instructions")?
187 .add_instructions(&BuildBuilder::all_build().context("Failed to build builder variables")?)
188 .context("Failed to add builder variables")?
189 .add_instructions(&RustcBuilder::all_rustc().context("Failed to build rustc variables")?)
190 .context("Failed to add rustc variables")?;
191
192 if is_contained_in_git()? {
193 builder
194 .add_instructions(&Git2Builder::all_git().context("Failed to build git variables")?)
195 .context("Failed to add git variables")?;
196 }
197 builder.emit().context("Failed to emit")?;
198
199 Ok(())
200}
201
202fn is_contained_in_git() -> anyhow::Result<bool> {
204 let current_dir = std::env::current_dir().context("Failed to get current dir")?;
205 let mut path = &*current_dir
206 .canonicalize()
207 .context("Failed to canonicalize path")?;
208 loop {
209 if path.join(".git").exists() {
210 return Ok(true);
211 }
212 let Some(parent) = path.parent() else {
213 println!("cargo::warning=No .git directory found");
214 return Ok(false);
215 };
216 path = parent;
217 }
218}