Repository navigation
Expand file tree
/
Copy pathbuild.rs
More file actions
209 lines (199 loc) · 8.83 KB
/
Copy pathbuild.rs
File metadata and controls
209 lines (199 loc) · 8.83 KB
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//! Stamps the build with the commit it came from, so `roundhouse
//! --version` (and the MCP `serverInfo`) name the exact tree behind a
//! number someone pastes into an issue — the same `ROUNDHOUSE_COMMIT`
//! the wasm build already carries for the /ide/ summary line.
//!
//! Precedence: an explicit `ROUNDHOUSE_COMMIT` in the environment (CI
//! sets `github.sha`), else `git rev-parse --short HEAD` on the source
//! tree, else nothing — a tarball build simply has no commit.
use std::fmt::Write as _;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
// CARGO_MANIFEST_DIR is Cargo-injected for build scripts; Cargo does not
// honor rerun-if-env-changed for it (cargo#8693). Switching checkouts that
// share a target dir is covered by reading the env at execution time below.
embed_runtime_files();
embed_rbs_files();
println!("cargo:rerun-if-env-changed=ROUNDHOUSE_TEST_BUNDLE_ROOT");
println!("cargo:rustc-check-cfg=cfg(roundhouse_test_bundle)");
// Scope this setting to the checkout: tests also build small child crates
// with this build script, which must not inherit the test bundle.
if std::env::var_os("ROUNDHOUSE_TEST_BUNDLE_ROOT").is_some_and(|root| {
Path::new(&root).canonicalize().expect("test bundle checkout root")
== manifest_root().canonicalize().expect("manifest root")
}) {
println!("cargo:rustc-cfg=roundhouse_test_bundle");
integration_test_modules();
}
println!("cargo:rerun-if-env-changed=ROUNDHOUSE_COMMIT");
let commit = std::env::var("ROUNDHOUSE_COMMIT")
.ok()
.filter(|s| !s.is_empty())
.or_else(git_head);
if let Some(commit) = commit {
println!("cargo:rustc-env=ROUNDHOUSE_COMMIT={commit}");
}
}
/// Short HEAD of the checkout containing this manifest, with rerun
/// triggers on HEAD and its loose or packed branch ref. Git resolves
/// these paths because linked worktrees share refs but have their own HEAD.
fn git_head() -> Option<String> {
let root = manifest_root();
let head = git_path(&root, "HEAD")?;
println!("cargo:rerun-if-changed={}", head.display());
if let Ok(contents) = std::fs::read_to_string(&head) {
if let Some(rf) = contents.trim().strip_prefix("ref: ") {
let loose = git_path(&root, rf)?;
if loose.exists() {
println!("cargo:rerun-if-changed={}", loose.display());
} else {
// A nonexistent watch makes Cargo rebuild on every run.
// Watch the nearest existing parent to detect creation of
// a loose ref (including a new nested branch directory).
if let Some(parent) = loose.ancestors().skip(1).find(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", parent.display());
}
if let Some(packed) = git_path(&root, "packed-refs").filter(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", packed.display());
}
}
}
}
let out = Command::new("git")
.args(["rev-parse", "--short=8", "HEAD"])
.current_dir(&root)
.output()
.ok()
.filter(|o| o.status.success())?;
let sha = String::from_utf8_lossy(&out.stdout).trim().to_string();
(!sha.is_empty()).then_some(sha)
}
/// A compiled build script may be reused by checkouts sharing a Cargo
/// target directory. Cargo supplies the current manifest at execution;
/// `env!` would retain the checkout that first compiled this script.
fn manifest_root() -> PathBuf {
PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"))
}
fn git_path(root: &Path, name: &str) -> Option<PathBuf> {
let out = Command::new("git")
.args(["rev-parse", "--git-path", name])
.current_dir(root)
.output()
.ok()
.filter(|o| o.status.success())?;
let path = String::from_utf8_lossy(&out.stdout);
let path = path.trim();
(!path.is_empty()).then(|| root.join(path))
}
/// The `runtime/ruby/` and `runtime/spinel/` trees the ruby and spinel
/// targets compose their output from, as a generated table of
/// `include_str!`s (`$OUT_DIR/runtime_files.rs`, read by
/// `src/runtime_files.rs`). The emit used to read these from disk
/// relative to the working directory, which meant the shipped binary
/// could emit Go from anywhere but ruby/spinel only from inside the
/// repo. `include_str!` keeps the table cheap to compile, and cargo
/// re-runs this script when anything under either directory changes.
///
/// Same admission rule as the disk walkers this replaces: dotfiles
/// skipped, non-UTF-8 files skipped. Directory filtering (`SKIP_DIRS`)
/// stays with the walkers, which apply it per query.
fn embed_runtime_files() {
let root = manifest_root();
let mut entries: Vec<(String, PathBuf)> = Vec::new();
for top in ["runtime/ruby", "runtime/spinel"] {
let dir = root.join(top);
println!("cargo:rerun-if-changed={}", dir.display());
collect(&dir, top, &mut entries);
}
entries.sort();
let dest = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR"));
std::fs::write(dest.join("runtime_files.rs"), runtime_table(entries.iter()))
.expect("write runtime_files.rs");
// The browser analyzer also needs framework and Spinel shim
// declarations. Exclude emit scaffolds and tests from its table.
let ruby = entries.iter().filter(|(path, _)| {
(path.starts_with("runtime/ruby/") || path.starts_with("runtime/spinel/"))
&& path.ends_with(".rb")
&& !path
.split('/')
.any(|part| part == "test" || part == "scaffold")
});
std::fs::write(dest.join("runtime_const_sources.rs"), runtime_table(ruby))
.expect("write runtime_const_sources.rs");
}
/// The Ruby core and stdlib signatures vendored under `rbs/`
/// (`scripts/vendor-rbs`), as `$OUT_DIR/rbs_files.rs`, read by
/// `src/stdlib_rbs.rs`. Every target embeds them, wasm included: the
/// constant resolver indexes them, and the browser analyzer must answer
/// as the CLI does. About 0.5 MB once comments are stripped.
fn embed_rbs_files() {
let dir = manifest_root().join("rbs");
println!("cargo:rerun-if-changed={}", dir.display());
let mut entries: Vec<(String, PathBuf)> = Vec::new();
collect(&dir, "rbs", &mut entries);
entries.retain(|(path, _)| path.ends_with(".rbs") || path.ends_with(".tsv"));
entries.sort();
let dest = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR"));
std::fs::write(dest.join("rbs_files.rs"), runtime_table(entries.iter()))
.expect("write rbs_files.rs");
}
fn runtime_table<'a>(entries: impl Iterator<Item = &'a (String, PathBuf)>) -> String {
let mut out = String::from("&[\n");
for (rel, abs) in entries {
out.push_str(&format!(
" ({rel:?}, include_str!({:?})),\n",
abs.display().to_string()
));
}
out.push_str("]\n");
out
}
/// Compile the same integration sources together for full-suite execution.
/// Directory discovery includes new suites without a second registration list.
fn integration_test_modules() {
let tests = manifest_root().join("tests");
println!("cargo:rerun-if-changed={}", tests.display());
let mut paths = Vec::new();
for entry in std::fs::read_dir(&tests).expect("read integration test directory") {
let path = entry.expect("read integration test entry").path();
if path.is_file()
&& path.extension().is_some_and(|ext| ext == "rs")
&& path.file_name().is_some_and(|name| name != "all_tests.rs")
{
paths.push(path);
}
}
paths.sort();
let mut modules = String::new();
for path in paths {
let stem = path.file_stem().and_then(|stem| stem.to_str()).expect("UTF-8 test name");
writeln!(
modules,
"#[path = {:?}]\nmod r#{stem};",
path.to_str().expect("UTF-8 test path"),
).expect("format integration test module");
}
let dest = PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR"))
.join("integration_tests.rs");
if std::fs::read_to_string(&dest).ok().as_deref() != Some(modules.as_str()) {
std::fs::write(dest, modules).expect("write integration test modules");
}
}
fn collect(dir: &Path, rel: &str, out: &mut Vec<(String, PathBuf)>) {
let Ok(rd) = std::fs::read_dir(dir) else { return };
for entry in rd.flatten() {
let name = entry.file_name();
let name = name.to_string_lossy();
if name.starts_with('.') {
continue;
}
let path = entry.path();
let nested = format!("{rel}/{name}");
if path.is_dir() {
collect(&path, &nested, out);
} else if std::fs::read_to_string(&path).is_ok() {
out.push((nested, path));
}
}
}