git_const/
lib.rs

1//!Proc macro to access git repo properties at build time.
2//!
3//!## Usage
4//!
5//!```rust
6//!use git_const::{git_hash, git_short_hash, git_root};
7//!
8//!const ROOT: &str = git_root!();
9//!const SHORT_VERSION: &str = git_short_hash!();
10//!const VERSION: &str = git_hash!();
11//!assert_ne!(VERSION, "");
12//!assert!(!VERSION.contains('\n'));
13//!assert_ne!(VERSION, SHORT_VERSION);
14//!assert!(VERSION.starts_with(SHORT_VERSION));
15//!
16//!const MASTER_VERSION: &str = git_hash!(master);
17//!assert_eq!(MASTER_VERSION, VERSION); //true if current branch is master
18//!let path = std::path::Path::new(ROOT);
19//!assert_eq!(path.file_name().unwrap().to_str().unwrap(), "git-const");
20//!```
21
22#![warn(missing_docs)]
23#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
24
25extern crate proc_macro;
26
27use proc_macro::TokenStream;
28
29use std::process::Command;
30use core::fmt;
31
32#[cold]
33#[inline(never)]
34fn compile_error(args: fmt::Arguments<'_>) -> TokenStream {
35    format!("compile_error!(\"{args}\")").parse().expect("To generate compile error")
36}
37
38#[inline(always)]
39fn run_git(args: &[&str]) -> Result<String, TokenStream> {
40    match Command::new("git").args(args).output() {
41        Ok(output) => match output.status.success() {
42            true => match String::from_utf8(output.stdout) {
43                Ok(output) => Ok(output),
44                Err(error) => Err(compile_error(format_args!("git output is not valid utf-8: {error}"))),
45            },
46            false => {
47                let status = output.status.code().unwrap_or(1);
48                let stderr = core::str::from_utf8(&output.stderr).unwrap_or("<invalid utf-8>");
49                Err(compile_error(format_args!("git failed with status {status}:\n {stderr}")))
50            }
51        },
52        Err(error) => Err(compile_error(format_args!("git execution error: {error}"))),
53    }
54}
55
56#[proc_macro]
57///Retrieves git hash from current project repo
58///
59///Accepts branch/tag name to use as reference.
60///Otherwise defaults to `HEAD`
61pub fn git_hash(input: TokenStream) -> TokenStream {
62    let input = input.to_string();
63    let revision = match input.trim() {
64        "" => "HEAD",
65        input => input,
66    };
67
68    let output = match run_git(&["rev-parse", revision]) {
69        Ok(output) => output,
70        Err(error) => return error,
71    };
72
73    let output = output.trim();
74    format!("\"{output}\"").parse().expect("generate hash string")
75}
76
77#[proc_macro]
78///Retrieves short hash from current project repo
79///
80///Accepts branch/tag name to use as reference.
81///Otherwise defaults to `HEAD`
82pub fn git_short_hash(input: TokenStream) -> TokenStream {
83    let input = input.to_string();
84    let revision = match input.trim() {
85        "" => "HEAD",
86        input => input,
87    };
88
89    let output = match run_git(&["rev-parse", "--short", revision]) {
90        Ok(output) => output,
91        Err(error) => return error,
92    };
93
94    let output = output.trim();
95    format!("\"{output}\"").parse().expect("generate hash string")
96}
97
98#[proc_macro]
99///Retrieves path to root folder of current project repo
100pub fn git_root(_input: TokenStream) -> TokenStream {
101    let output = match run_git(&["rev-parse", "--show-toplevel"]) {
102        Ok(output) => output,
103        Err(error) => return error,
104    };
105
106    let output = output.trim();
107    format!("\"{output}\"").parse().expect("generate hash string")
108}