Source code

Revision control

Other Tools

1
// Writes build information to ${OUT_DIR}/build-info.rs which is included in
2
// the program during compilation:
3
//
4
// ```no_run
5
// const COMMIT_HASH: Option<&'static str> = Some("c31a366");
6
// const COMMIT_DATE: Option<&'static str> = Some("1988-05-10");
7
// ```
8
//
9
// The values are `None` if running hg failed, e.g. if it is not installed or
10
// if we are not in an hg repo.
11
12
use std::env;
13
use std::ffi::OsStr;
14
use std::fs::File;
15
use std::io;
16
use std::io::Write;
17
use std::path::{Path, PathBuf};
18
use std::process::Command;
19
20
fn main() -> io::Result<()> {
21
let cur_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
22
let build_info = get_build_info(&cur_dir);
23
24
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
25
let mut fh = File::create(out_dir.join("build-info.rs"))?;
26
writeln!(
27
fh,
28
"const COMMIT_HASH: Option<&'static str> = {:?};",
29
build_info.hash()
30
)?;
31
writeln!(
32
fh,
33
"const COMMIT_DATE: Option<&'static str> = {:?};",
34
build_info.date()
35
)?;
36
37
Ok(())
38
}
39
40
fn get_build_info(dir: &Path) -> Box<dyn BuildInfo> {
41
if Path::exists(&dir.join(".hg")) {
42
Box::new(Hg {})
43
} else if Path::exists(&dir.join(".git")) {
44
Box::new(Git {})
45
} else {
46
if let Some(parent) = dir.parent() {
47
get_build_info(parent)
48
} else {
49
eprintln!("unable to detect vcs");
50
Box::new(Noop {})
51
}
52
}
53
}
54
55
trait BuildInfo {
56
fn hash(&self) -> Option<String>;
57
fn date(&self) -> Option<String>;
58
}
59
60
struct Hg;
61
62
impl Hg {
63
fn exec<I, S>(&self, args: I) -> Option<String>
64
where
65
I: IntoIterator<Item = S>,
66
S: AsRef<OsStr>,
67
{
68
Command::new("hg")
69
.env("HGPLAIN", "1")
70
.args(args)
71
.output()
72
.ok()
73
.and_then(|r| String::from_utf8(r.stdout).ok())
74
.map(|s| s.trim_end().into())
75
}
76
}
77
78
impl BuildInfo for Hg {
79
fn hash(&self) -> Option<String> {
80
self.exec(&["log", "-r.", "-T{node|short}"])
81
}
82
83
fn date(&self) -> Option<String> {
84
self.exec(&["log", "-r.", "-T{date|isodate}"])
85
}
86
}
87
88
struct Git;
89
90
impl Git {
91
fn exec<I, S>(&self, args: I) -> Option<String>
92
where
93
I: IntoIterator<Item = S>,
94
S: AsRef<OsStr>,
95
{
96
Command::new("git")
97
.env("GIT_CONFIG_NOSYSTEM", "1")
98
.args(args)
99
.output()
100
.ok()
101
.and_then(|r| String::from_utf8(r.stdout).ok())
102
.map(|s| s.trim_end().into())
103
}
104
105
fn to_hg_sha(&self, git_sha: String) -> Option<String> {
106
self.exec(&["cinnabar", "git2hg", &git_sha])
107
}
108
}
109
110
impl BuildInfo for Git {
111
fn hash(&self) -> Option<String> {
112
self.exec(&["rev-parse", "HEAD"])
113
.and_then(|sha| self.to_hg_sha(sha))
114
.map(|mut s| {
115
s.truncate(12);
116
s
117
})
118
}
119
120
fn date(&self) -> Option<String> {
121
self.exec(&["log", "-1", "--date=short", "--pretty=format:%cd"])
122
}
123
}
124
125
struct Noop;
126
127
impl BuildInfo for Noop {
128
fn hash(&self) -> Option<String> {
129
None
130
}
131
fn date(&self) -> Option<String> {
132
None
133
}
134
}