1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#![recursion_limit = "128"]

extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::Span;
use proc_macro_hack::proc_macro_hack;
use quote::quote;
use std::process::Command;
use syn::{parse_macro_input, LitStr};

fn to_str(s: &[u8]) -> String {
    String::from_utf8_lossy(s).trim_end().to_string()
}

macro_rules! cmd {
    ($c:expr; $d:expr) => {
        LitStr::new(
            &Command::new($c)
                .output()
                .map(|s| to_str(&s.stdout))
                .unwrap_or($d.into()),
            Span::call_site(),
        )
    };
    ($c:expr, $($a:expr),*; $d:expr) => {
        LitStr::new(
            &Command::new($c)
                .args(&[$($a,)*])
                .output()
                .map(|s| to_str(&s.stdout))
                .unwrap_or($d.into()),
            Span::call_site(),
        )
    };
}

#[proc_macro_hack]
pub fn ever(input: TokenStream) -> TokenStream {
    let version_env = if input.is_empty() {
        LitStr::new("VERSION", Span::call_site())
    } else {
        parse_macro_input!(input as LitStr)
    };

    let date = cmd!("date"; "<unknown_date>");
    let commit = cmd!("git", "rev-parse", "HEAD"; "<unknown_commit>" );
    let user = cmd!("whoami"; "<unknown_user>");
    let host = cmd!("hostname"; "<unknown_host>");
    let builddir = cmd!("pwd"; "<unknown_dir>");
    let rustc = cmd!("rustc", "--version"; "<unknown_rustc_version>");

    let s = quote! {
        if std::env::var(#version_env).unwrap_or("0".into()) == "1" {
            let name = option_env!("CARGO_PKG_NAME").unwrap_or("<unknown_package>");
            let version = option_env!("CARGO_PKG_VERSION").unwrap_or("<unknown_version>");
            let about = option_env!("CARGO_PKG_DESCRIPTION").unwrap_or("<no_description>");
            #[cfg(debug_assertions)]
            let mode = "debug";
            #[cfg(not(debug_assertions))]
            let mode = "release";

            println!(
                r#"{name} {version} ({mode}): {about}

    date:     {date}
    commit:   {commit}
    user:     {user}
    host:     {host}
    builddir: {builddir}
    rustc:    {rustc}"#,
                name = name,
                version = version,
                about = about,
                date = #date,
                commit = #commit,
                user = #user,
                host = #host,
                builddir = #builddir,
                rustc = #rustc,
                mode = mode,
            );

            std::process::exit(1);
        }
    };

    s.into()
}