-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathbuild.rs
More file actions
232 lines (211 loc) · 9.5 KB
/
Copy pathbuild.rs
File metadata and controls
232 lines (211 loc) · 9.5 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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
//! Compile-time enumeration of legacy seeded API-response `.lino` files.
//!
//! Issue #398 removed the committed legacy bundle in favor of explicit
//! source snapshots under `data/cache/wikidata/`. The reader remains so older
//! or locally generated `data/seed/api-cache/` bundles can still be replayed.
//! When that directory is absent this script emits an empty registry.
//!
//! `include_str!` cannot iterate a directory, so this build script writes
//! a generated `seed_bundle_files.rs` into `OUT_DIR` that lists every
//! legacy seed file in deterministic order. `cache.rs` includes that file via
//! `include!(concat!(env!("OUT_DIR"), "/seed_bundle_files.rs"))` so all
//! parts ship in the binary without per-file edits when the legacy bundle is
//! present.
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
let seed_dir = manifest_dir.join("data/seed/api-cache");
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"));
let out_path = out_dir.join("seed_bundle_files.rs");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed={}", seed_dir.display());
install_git_hooks(&manifest_dir);
let mut files = enumerate_lino_files(&seed_dir);
files.sort_by_key(|a| seed_sort_key(a));
let mut generated = String::new();
generated.push_str("// Generated by build.rs — do not edit.\n");
generated.push_str("pub const SEED_BUNDLE_FILES: &[(&str, &str)] = &[\n");
for path in &files {
let rel = path
.strip_prefix(&manifest_dir)
.unwrap_or(path)
.to_string_lossy()
.replace('\\', "/");
let abs = path.to_string_lossy().replace('\\', "/");
println!("cargo:rerun-if-changed={abs}");
generated.push_str(" (\"");
generated.push_str(&rel);
generated.push_str("\", include_str!(\"");
generated.push_str(&abs);
generated.push_str("\")),\n");
}
generated.push_str("];\n");
fs::write(&out_path, generated).expect("write seed_bundle_files.rs");
emit_owned_source_manifest(&manifest_dir, &out_dir);
emit_crate_edition(&manifest_dir);
}
/// Export the crate's own Rust edition as `FORMAL_AI_CRATE_EDITION`.
///
/// [`crate::memory_revision::rustc_verdict`] compiles the next version of this
/// crate with a bare `rustc`, which has no manifest to read the edition from. A
/// constant typed into that module would be a second place to remember, and the
/// day it fell behind the manifest the ledger would start rolling back versions
/// that `cargo build` accepts. So the value is read from `Cargo.toml` here,
/// where there is only one of it.
fn emit_crate_edition(manifest_dir: &Path) {
let manifest_path = manifest_dir.join("Cargo.toml");
println!("cargo:rerun-if-changed={}", manifest_path.display());
let manifest = fs::read_to_string(&manifest_path).expect("read Cargo.toml");
let edition = package_edition(&manifest)
.unwrap_or_else(|| panic!("[package] edition missing from {}", manifest_path.display()));
println!("cargo:rustc-env=FORMAL_AI_CRATE_EDITION={edition}");
}
/// The `edition` of the manifest's `[package]` table, ignoring every other
/// table -- a `[dependencies]` entry may carry an `edition` key of its own.
fn package_edition(manifest: &str) -> Option<String> {
let mut in_package = false;
for line in manifest.lines() {
let line = line.trim();
if line.starts_with('[') {
in_package = line == "[package]";
} else if in_package && let Some(value) = line.strip_prefix("edition") {
let value = value.trim_start().strip_prefix('=')?.trim();
return Some(value.trim_matches('"').to_owned());
}
}
None
}
/// Emit `owned_source_files.rs` — the compile-time manifest of every owned Rust
/// source file under `src/`, embedded via `include_str!`.
///
/// Issue #558 asks for *"the entire source code of our system"* to be translatable
/// to the links/meta language and *"present in the seed data"*. `include_str!`
/// cannot iterate a directory, so this script writes a generated slice of
/// `(repo_relative_path, source_text)` pairs into `OUT_DIR`; the whole-repository
/// projection ([`crate::self_source_links`]) includes it via
/// `include!(concat!(env!("OUT_DIR"), "/owned_source_files.rs"))` so every module's
/// source ships in the binary and the projection works with no filesystem access —
/// exactly as it must when the loop runs from an agent CLI's sandbox workdir.
fn emit_owned_source_manifest(manifest_dir: &Path, out_dir: &Path) {
let src_dir = manifest_dir.join("src");
let out_path = out_dir.join("owned_source_files.rs");
println!("cargo:rerun-if-changed={}", src_dir.display());
let mut files = Vec::new();
enumerate_rust_files(&src_dir, &mut files);
// Deterministic order: sort by the path *string* (not component-wise like
// `PathBuf`'s `Ord`, which would order `memory/bundle.rs` before `memory.rs`)
// so the manifest — and every projection derived from it — is stable across
// machines and rebuilds and matches conventional lexicographic path order.
files.sort_by(|a, b| a.to_string_lossy().cmp(&b.to_string_lossy()));
let mut generated = String::new();
generated.push_str("// Generated by build.rs — do not edit.\n");
generated.push_str("pub const OWNED_SOURCE_FILES: &[(&str, &str)] = &[\n");
for path in &files {
let rel = path
.strip_prefix(manifest_dir)
.unwrap_or(path)
.to_string_lossy()
.replace('\\', "/");
let abs = path.to_string_lossy().replace('\\', "/");
println!("cargo:rerun-if-changed={abs}");
generated.push_str(" (\"");
generated.push_str(&rel);
generated.push_str("\", include_str!(\"");
generated.push_str(&abs);
generated.push_str("\")),\n");
}
generated.push_str("];\n");
fs::write(&out_path, generated).expect("write owned_source_files.rs");
}
/// Recursively collect every `.rs` file under `dir` in filesystem order.
fn enumerate_rust_files(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
enumerate_rust_files(&path, out);
} else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
out.push(path);
}
}
}
/// Point `core.hooksPath` at the tracked `.githooks/` directory.
///
/// Issue #1041: `.pre-commit-config.yaml` describes a hook that sweeps the
/// build cache on every commit, but it runs only for someone who has installed
/// the `pre-commit` framework *and* run `pre-commit install`. On a fresh clone
/// neither is true, so the config sat committed and inert while `target/` grew
/// until the disk filled.
///
/// A build script is the one step every contributor takes without being told,
/// which makes it the reliable place to do this. It is also the only place that
/// runs before the first commit of a new clone.
///
/// Every failure here is ignored. This is a convenience, never a build
/// requirement: a source tarball with no `.git`, a sandbox with no `git` on
/// PATH, or a read-only checkout must all still build.
fn install_git_hooks(manifest_dir: &Path) {
// CI checks out fresh for every job and commits nothing, so installing
// hooks there is pure overhead on hundreds of jobs.
if env::var_os("CI").is_some() {
return;
}
let hooks_dir = manifest_dir.join(".githooks");
if !hooks_dir.is_dir() || !manifest_dir.join(".git").exists() {
return;
}
// Respect a deliberate choice. Someone who already points hooksPath
// somewhere -- their own directory, or a tool that manages hooks -- must
// not have it silently taken over by a dependency build.
let configured = Command::new("git")
.args(["-C"])
.arg(manifest_dir)
.args(["config", "--local", "--get", "core.hooksPath"])
.output();
if let Ok(output) = &configured
&& output.status.success()
&& !String::from_utf8_lossy(&output.stdout).trim().is_empty()
{
return;
}
let _ = Command::new("git")
.args(["-C"])
.arg(manifest_dir)
.args(["config", "--local", "core.hooksPath", ".githooks"])
.status();
}
fn enumerate_lino_files(seed_dir: &Path) -> Vec<PathBuf> {
let mut out = Vec::new();
let Ok(entries) = fs::read_dir(seed_dir) else {
return out;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() && path.extension().and_then(|e| e.to_str()) == Some("lino") {
out.push(path);
}
}
out
}
// Keeps split records in body order: `<bucket>.lino` is chunk 0 and must
// load before `<bucket>-part1.lino`, `<bucket>-part2.lino`, ... in
// numeric order. Lexicographic sort puts `<bucket>-part10.lino` before
// `<bucket>-part2.lino` and `<bucket>.lino` after every part, both of
// which reorder the base64 chunks and corrupt the assembled body.
fn seed_sort_key(path: &Path) -> (String, u32, String) {
let stem = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or_default();
if let Some((bucket, suffix)) = stem.rsplit_once("-part")
&& let Ok(n) = suffix.parse::<u32>()
{
return (bucket.to_string(), n, stem.to_string());
}
(stem.to_string(), 0, stem.to_string())
}