-
Notifications
You must be signed in to change notification settings - Fork 2
/
build.rs
148 lines (135 loc) · 5.22 KB
/
build.rs
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use std::io::Write;
use std::path::{absolute, Path, PathBuf};
/// Fetch the contents of the given file, and also tell cargo that we looked
/// in there.
fn file_contents<P: AsRef<Path>>(path: P) -> String {
let path =
absolute(path.as_ref()).expect("Unable to make the path absolute");
let mut stdout = std::io::stdout();
stdout
.write_all(b"cargo::rerun-if-changed=")
.expect("Unable to write stdout");
stdout
.write_all(path.as_os_str().as_encoded_bytes())
.expect("Unable to write path to stdout");
stdout
.write_all(b"\n")
.expect("Unable to write newline to stdout");
std::fs::read_to_string(path).expect("Unable to read file")
}
fn git_rel<P: AsRef<Path>>(path: P) -> PathBuf {
let output = std::process::Command::new("git")
.arg("rev-parse")
.arg("--show-toplevel")
.output()
.expect("Error launching git rev-parse");
if !output.status.success() {
let stderr = std::str::from_utf8(&output.stderr)
.expect("git failed and stderr was not utf8");
eprintln!("`git rev-parse --show-toplevel` failed, stderr: {stderr}");
panic!("`git rev-parse --show-toplevel` failed");
}
let mut root = PathBuf::from(
std::str::from_utf8(&output.stdout)
.expect("Output was not utf-8")
.trim(),
);
root.push(path);
root
}
/// Emit the current git commit.
fn emit_git_commit() {
// Fetch the current commit from the head. We do it this way instead of
// asking `git rev-parse` to do it for us because we want to reliably
// tell cargo which files it should monitor for changes.
let head = file_contents(git_rel(".git/HEAD"));
let rev = if let Some(r) = head.strip_prefix("ref: ") {
let mut ref_path = git_rel(".git/");
ref_path.push(r.trim());
file_contents(ref_path)
} else {
head
};
// But *now* we ask git rev-parse to make this into a short hash (a) to
// make sure we got it right and (b) because git knows how to quickly
// determine how much of a commit is required to be unique. We don't need
// to tell cargo anything here, no file that git consults will be
// mutable.
let output = std::process::Command::new("git")
.arg("rev-parse")
.arg("--short")
.arg(rev.trim())
.output()
.expect("could not spawn `git` to get the hash");
if !output.status.success() {
let stderr = std::str::from_utf8(&output.stderr)
.expect("git failed and stderr was not utf8");
eprintln!("`git rev-parse --short HEAD` failed, stderr: {stderr}");
panic!("`git rev-parse --short HEAD` failed");
}
let rev = std::str::from_utf8(&output.stdout)
.expect("git did not output utf8")
.trim();
println!("cargo::rustc-env=REPO_REV={rev}");
}
fn emit_git_dirty() {
// Here is the way to see if anything is up with the repository: run `git
// status --porcelain=v1`. The status output in the v1 porcelain format
// has one line for every file that's modified in some way: staged,
// changed but unstaged, untracked, you name it. Files in the working
// tree that are up to date with the repository are not emitted. This is
// exactly what we want.
//
// (Yes, I want to track untracked files, because they can mess with the
// build too. The only good build is a clean build!)
let output = std::process::Command::new("git")
.arg("status")
.arg("-z")
.arg("--porcelain=v1")
.output()
.expect("could not spawn `git` to get repository status");
if !output.status.success() {
let stderr = std::str::from_utf8(&output.stderr)
.expect("git failed and stderr was not utf8");
eprintln!("`git status` failed, stderr: {stderr}");
panic!("`git status` failed");
}
let output =
std::str::from_utf8(&output.stdout).expect("git did not output utf8");
// Emit the repository status.
let dirty = if output.trim().is_empty() {
""
} else {
" *dirty*"
};
println!("cargo::rustc-env=REPO_DIRTY={dirty}");
// NOW: The output here has to do with *all* of the files in the git
// respository. (Because if nothing was modified, but then *becomes*
// modified, we need to rerun the script to notice the dirty bit.)
// `git-ls-files` is the way to do that.
let output = std::process::Command::new("git")
.arg("ls-files")
.arg("-z")
.arg("--cached")
.arg("--deleted")
.arg("--modified")
.arg("--others")
.arg("--exclude-standard")
.output()
.expect("could not spawn `git` to get repository status");
if !output.status.success() {
let stderr = std::str::from_utf8(&output.stderr)
.expect("git failed and stderr was not utf-8");
eprintln!("`git ls-files` failed, stderr: {stderr}");
panic!("`git ls-files` failed");
}
let output =
std::str::from_utf8(&output.stdout).expect("git did not output utf8");
for fname in output.split_terminator("\0") {
println!("cargo::rerun-if-changed={fname}");
}
}
fn main() {
emit_git_commit();
emit_git_dirty();
}