dotnet_gitversion_build/
lib.rs

1mod gitversion;
2
3use anyhow::Result;
4use gitversion::GitVersion;
5use quote::quote;
6use std::env;
7use std::fmt::Debug;
8use std::fs::File;
9use std::io::{BufWriter, Read, Write};
10use std::path::Path;
11use std::process::Command;
12use thiserror::Error;
13
14#[derive(Error, Debug)]
15pub enum Error {
16    #[error("I/O error")]
17    Io(#[from] std::io::Error),
18
19    #[error("environment variable is missing")]
20    MissingEnvVar,
21}
22
23fn same_content_as(path: &Path, content: &str) -> Result<bool> {
24    let mut f = File::open(path)?;
25    let mut current = String::new();
26    f.read_to_string(&mut current)?;
27
28    Ok(current == content)
29}
30
31#[allow(unused)]
32macro_rules! include_gitversion_from_path {
33    ($path: tt) => {
34        include!($path);
35    };
36}
37
38/// Calls `dotnet-gitversion`, converts the JSON output and generates a `gitversion.rs`
39/// file in the `OUT_DIR` directory.
40pub fn build() -> Result<GitVersion> {
41    let path = env::var_os("OUT_DIR").ok_or(Error::MissingEnvVar)?;
42    let path: &Path = path.as_ref();
43    let path = path.join("gitversion.rs");
44    write_version_file(path.as_path())
45}
46
47fn dotnet_gitversion() -> Option<String> {
48    Command::new("dotnet-gitversion")
49        .args(&["/nofetch"])
50        .output()
51        .ok()
52        .and_then(|out| {
53            std::str::from_utf8(&out.stdout[..])
54                .map(str::trim)
55                .map(str::to_owned)
56                .ok()
57        })
58}
59
60/// Write version.rs file to OUT_DIR
61#[allow(deprecated)]
62fn write_version_file(path: &Path) -> Result<GitVersion> {
63    let content = if let Some(json) = dotnet_gitversion() {
64        json.to_owned()
65    } else {
66        "{}".to_owned()
67    };
68
69    let gv: GitVersion = serde_json::from_str(content.as_str())?;
70
71    let major = gv.major;
72    let minor = gv.minor;
73    let patch = gv.patch;
74    println!("cargo:rustc-env=GITVERSION_MAJOR={}", major.to_string());
75    println!("cargo:rustc-env=GITVERSION_MINOR={}", minor.to_string());
76    println!("cargo:rustc-env=GITVERSION_PATCH={}", patch.to_string());
77
78    let pre_release_tag = gv.pre_release_tag.clone();
79    let pre_release_tag_with_dash = gv.pre_release_tag_with_dash.clone();
80    println!(
81        "cargo:rustc-env=GITVERSION_PRE_RELEASE_TAG={}",
82        pre_release_tag.clone()
83    );
84    println!(
85        "cargo:rustc-env=GITVERSION_PRE_RELEASE_TAG_WITH_DASH={}",
86        pre_release_tag_with_dash.clone()
87    );
88
89    let pre_release_label = gv.pre_release_label.clone();
90    let pre_release_label_with_dash = gv.pre_release_label_with_dash.clone();
91    println!(
92        "cargo:rustc-env=GITVERSION_PRE_RELEASE_LABEL={}",
93        pre_release_label.clone()
94    );
95    println!(
96        "cargo:rustc-env=GITVERSION_PRE_RELEASE_LABEL_WITH_DASH={}",
97        pre_release_label_with_dash.clone()
98    );
99
100    let has_pre_release_number = gv.pre_release_number != None;
101    let pre_release_number = gv.pre_release_number.unwrap_or(0);
102    if let Some(number) = gv.pre_release_number {
103        println!(
104            "cargo:rustc-env=GITVERSION_PRE_RELEASE_NUMBER={}",
105            number.to_string()
106        );
107    }
108
109    let weighted_pre_release_number = gv.weighted_pre_release_number;
110    println!(
111        "cargo:rustc-env=GITVERSION_WEIGHTED_PRE_RELEASE_NUMBER={}",
112        weighted_pre_release_number.to_string()
113    );
114
115    let has_build_meta_data = gv.build_meta_data != None;
116    let build_meta_data = gv.build_meta_data.unwrap_or(0);
117    if let Some(number) = gv.build_meta_data {
118        println!(
119            "cargo:rustc-env=GITVERSION_BUILD_META_DATA={}",
120            number.to_string()
121        );
122    }
123
124    let build_meta_data_padded = gv.build_meta_data_padded.clone();
125    println!(
126        "cargo:rustc-env=GITVERSION_BUILD_META_DATA_PADDED={}",
127        build_meta_data_padded.clone()
128    );
129
130    let full_build_meta_data = gv.full_build_meta_data.clone();
131    println!(
132        "cargo:rustc-env=GITVERSION_FULL_BUILD_META_DATA={}",
133        full_build_meta_data.clone()
134    );
135
136    let major_minor_patch = gv.major_minor_patch.clone();
137    println!(
138        "cargo:rustc-env=GITVERSION_MAJOR_MINOR_PATCH={}",
139        major_minor_patch.clone()
140    );
141
142    let semver = gv.semver.clone();
143    println!("cargo:rustc-env=GITVERSION_SEMVER={}", semver.clone());
144
145    let legacy_semver = gv.legacy_semver.clone();
146    let legacy_semver_padded = gv.legacy_semver_padded.clone();
147    println!(
148        "cargo:rustc-env=GITVERSION_LEGACY_SEMVER={}",
149        legacy_semver.clone()
150    );
151    println!(
152        "cargo:rustc-env=GITVERSION_LEGACY_SEMVER_PADDED={}",
153        legacy_semver_padded.clone()
154    );
155
156    let assembly_semver = gv.assembly_semver.clone();
157    println!(
158        "cargo:rustc-env=GITVERSION_ASSEMBLY_SEMVER={}",
159        assembly_semver.clone()
160    );
161
162    let assembly_sem_file_version = gv.assembly_sem_file_version.clone();
163    println!(
164        "cargo:rustc-env=GITVERSION_ASSEMBLY_SEM_FILE_VERSION={}",
165        assembly_sem_file_version.clone()
166    );
167
168    let informational_version = gv.informational_version.clone();
169    println!(
170        "cargo:rustc-env=GITVERSION_INFORMATIONAL_VERSION={}",
171        informational_version.clone()
172    );
173
174    let full_semver = gv.full_semver.clone();
175    println!(
176        "cargo:rustc-env=GITVERSION_FULL_SEMVER={}",
177        full_semver.clone()
178    );
179
180    let branch_name = gv.branch_name.clone();
181    println!(
182        "cargo:rustc-env=GITVERSION_BRANCH_NAME={}",
183        branch_name.clone()
184    );
185
186    let escaped_branch_name = gv.escaped_branch_name.clone();
187    println!(
188        "cargo:rustc-env=GITVERSION_ESCAPED_BRANCH_NAME={}",
189        escaped_branch_name.clone()
190    );
191
192    let sha = gv.sha.clone();
193    println!("cargo:rustc-env=GITVERSION_SHA={}", sha.clone());
194
195    let short_sha = gv.short_sha.clone();
196    println!("cargo:rustc-env=GITVERSION_SHORT_SHA={}", short_sha.clone());
197
198    let nuget_version_v2 = gv.nuget_version_v2.clone();
199    println!(
200        "cargo:rustc-env=GITVERSION_NUGET_VERSION_V2={}",
201        nuget_version_v2.clone()
202    );
203
204    let nuget_version = gv.nuget_version.clone();
205    println!(
206        "cargo:rustc-env=GITVERSION_NUGET_VERSION={}",
207        nuget_version.clone()
208    );
209
210    let nuget_prerelease_tag_v2 = gv.nuget_prerelease_tag_v2.clone();
211    println!(
212        "cargo:rustc-env=GITVERSION_NUGET_PRERELEASE_TAG_V2={}",
213        nuget_prerelease_tag_v2.clone()
214    );
215
216    let nuget_prerelease_tag = gv.nuget_prerelease_tag.clone();
217    println!(
218        "cargo:rustc-env=GITVERSION_NUGET_PRERELEASE_TAG={}",
219        nuget_prerelease_tag.clone()
220    );
221
222    let version_source_sha = gv.version_source_sha.clone();
223    println!(
224        "cargo:rustc-env=GITVERSION_VERSION_SOURCE_SHA={}",
225        version_source_sha.clone()
226    );
227
228    let commits_since_version_source = gv.commits_since_version_source;
229    println!(
230        "cargo:rustc-env=GITVERSION_COMMITS_SINCE_VERSION_SOURCE={}",
231        commits_since_version_source.clone()
232    );
233
234    let commits_since_version_source_padded = gv.commits_since_version_source_padded.clone();
235    println!(
236        "cargo:rustc-env=GITVERSION_COMMITS_SINCE_VERSION_SOURCE_PADDED={}",
237        commits_since_version_source_padded.clone()
238    );
239
240    let uncommitted_changes = gv.uncommitted_changes;
241    println!(
242        "cargo:rustc-env=GITVERSION_UNCOMMITTED_CHANGES={}",
243        uncommitted_changes.to_string()
244    );
245
246    let commit_date = gv.commit_date.clone();
247    println!(
248        "cargo:rustc-env=GITVERSION_COMMIT_DATE={}",
249        commit_date.clone()
250    );
251
252    let tokens = quote! {
253        #[allow(dead_code)]
254        pub struct GitVersion {
255            /// The major version. Should be incremented on breaking changes.
256            pub major: u32,
257            /// The minor version. Should be incremented on new features.
258            pub minor: u32,
259            /// The patch version. Should be incremented on bug fixes.
260            pub patch: u32,
261            /// The pre-release tag is the pre-release label suffixed by the `pre_release_number`.
262            pub pre_release_tag: &'static str,
263            /// The pre-release tag prefixed with a dash.
264            pub pre_release_tag_with_dash: &'static str,
265            /// The pre-release label.
266            pub pre_release_label: &'static str,
267            /// The pre-release label prefixed with a dash.
268            pub pre_release_label_with_dash: &'static str,
269            /// The pre-release number.
270            pub pre_release_number: Option<u32>,
271            /// A summation of branch specific `pre-release-weight` and the `pre_release_number`.
272            /// Can be used to obtain a monotonically increasing version number across the branches.
273            pub weighted_pre_release_number: u32,
274            /// The build metadata, usually representing number of commits since the `version_source_sha`.
275            pub build_meta_data: Option<u32>,
276            /// The `build_meta_data` padded with `0` up to `4` digits.
277            pub build_meta_data_padded: &'static str,
278            /// The `build_meta_data` suffixed with `branch_name` and `sha`.
279            pub full_build_meta_data: &'static str,
280            /// `major`, `minor` and `patch` joined together, separated by `.`.
281            pub major_minor_patch: &'static str,
282            /// The semantical version number, including `pre_release_tag_with_dash` for pre-release version numbers.
283            pub semver: &'static str,
284            /// Equal to `semver`, but without a `.` separating `pre_release_label` and `pre_release_number`.
285            #[deprecated]
286            pub legacy_semver: &'static str,
287            /// Equal to `legacy_semver`, but with `pre_release_number` padded with `0` up to `4` digits.
288            #[deprecated]
289            pub legacy_semver_padded: &'static str,
290            /// Defaults to `major.minor.0.0` to allow the assembly to be hotfixed without breaking
291            /// existing applications that may be referencing it.
292            /// (Suitable for .NET `AssemblyVersion`.)
293            #[deprecated]
294            pub assembly_semver: &'static str,
295            /// Defaults to `major.minor.patch.0`.
296            /// (Suitable for .NET `AssemblyFileVersion`.)
297            #[deprecated]
298            pub assembly_sem_file_version: &'static str,
299            /// Defaults to `full_semver` suffixed by `full_build_meta_data`.
300            /// (Suitable for .NET `AssemblyInformationalVersion`. )
301            pub informational_version: &'static str,
302            /// The full, SemVer 2.0 compliant version number.
303            pub full_semver: &'static str,
304            /// The name of the checked out Git branch.
305            pub branch_name: &'static str,
306            /// Equal to `branch_name`, but with `/` replaced with `-`.
307            pub escaped_branch_name: &'static str,
308            /// The SHA of the Git commit.
309            pub sha: &'static str,
310            /// The `sha` limited to `7` characters.
311            pub short_sha: &'static str,
312            /// A NuGet 2.0 compatible version number.
313            #[deprecated]
314            pub nuget_version_v2: &'static str,
315            /// A NuGet 1.0 compatible version number.
316            #[deprecated]
317            pub nuget_version: &'static str,
318            /// A NuGet 2.0 compatible `pre_release_tag`.
319            #[deprecated]
320            pub nuget_prerelease_tag_v2: &'static str,
321            /// A NuGet 1.0 compatible `pre_release_tag`.
322            #[deprecated]
323            pub nuget_prerelease_tag: &'static str,
324            /// The SHA of the commit used as version source.
325            pub version_source_sha: &'static str,
326            /// The number of commits since the version source.
327            pub commits_since_version_source: u32,
328            /// The `commits_since_version_source` padded with `0` up to `4` digits.
329            pub commits_since_version_source_padded: &'static str,
330            /// The ISO-8601 formatted date of the commit identified by `sha`.
331            pub uncommitted_changes: u32,
332            /// The number of uncommitted changes present in the repository.
333            pub commit_date: &'static str,
334        }
335
336        #[allow(dead_code)]
337        impl GitVersion {
338            /// Builds a `GitVersion` instance.
339            #[allow(deprecated)]
340            pub const fn new() -> GitVersion {
341                GitVersion {
342                    major: #major,
343                    minor: #minor,
344                    patch: #patch,
345                    pre_release_tag: #pre_release_tag,
346                    pre_release_tag_with_dash: #pre_release_tag_with_dash,
347                    pre_release_label: #pre_release_label,
348                    pre_release_label_with_dash: #pre_release_label_with_dash,
349                    pre_release_number: if #has_pre_release_number { Some( #pre_release_number ) } else { None },
350                    weighted_pre_release_number: #weighted_pre_release_number,
351                    build_meta_data: if #has_build_meta_data { Some( #build_meta_data ) } else { None },
352                    build_meta_data_padded: #build_meta_data_padded,
353                    full_build_meta_data: #full_build_meta_data,
354                    major_minor_patch: #major_minor_patch,
355                    semver: #semver,
356                    legacy_semver: #legacy_semver,
357                    legacy_semver_padded: #legacy_semver_padded,
358                    assembly_semver: #assembly_semver,
359                    assembly_sem_file_version: #assembly_sem_file_version,
360                    informational_version: #informational_version,
361                    full_semver: #full_semver,
362                    branch_name: #branch_name,
363                    escaped_branch_name: #escaped_branch_name,
364                    sha: #sha,
365                    short_sha: #short_sha,
366                    nuget_version_v2: #nuget_version_v2,
367                    nuget_version: #nuget_version,
368                    nuget_prerelease_tag_v2: #nuget_prerelease_tag_v2,
369                    nuget_prerelease_tag: #nuget_prerelease_tag,
370                    version_source_sha: #version_source_sha,
371                    commits_since_version_source: #commits_since_version_source,
372                    commits_since_version_source_padded: #commits_since_version_source_padded,
373                    uncommitted_changes: #uncommitted_changes,
374                    commit_date: #commit_date
375                }
376            }
377        }
378
379        #[allow(dead_code)]
380        impl Default for GitVersion {
381            fn default() -> Self {
382                GitVersion::new()
383            }
384        }
385
386        #[allow(dead_code)]
387        pub const GIT_VERSION: GitVersion = GitVersion::new();
388
389        impl std::fmt::Display for GitVersion {
390            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391                write!(f, "{}", self.semver)
392            }
393        }
394
395        impl std::fmt::Debug for GitVersion {
396            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397                write!(f, "{}", self.informational_version)
398            }
399        }
400    };
401
402    let code = tokens.to_string();
403    let is_fresh = if path.exists() {
404        same_content_as(&path, &code)?
405    } else {
406        false
407    };
408
409    if !is_fresh {
410        let mut file = BufWriter::new(File::create(&path)?);
411        write!(file, "{}", code)?;
412    }
413    Ok(gv)
414}
415
416#[cfg(test)]
417mod test {
418    use super::*;
419    use tempfile::NamedTempFile;
420
421    #[test]
422    pub fn json_is_created() {
423        let result = dotnet_gitversion().expect("dotnet_gitversion");
424        assert!(result.len() > 8);
425    }
426
427    #[test]
428    pub fn write_file() -> Result<()> {
429        let mut file = NamedTempFile::new()?;
430        write_version_file(file.path());
431        Ok(())
432    }
433}