This repository was archived by the owner on May 13, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.rs
More file actions
190 lines (174 loc) · 7.69 KB
/
Copy pathbuild.rs
File metadata and controls
190 lines (174 loc) · 7.69 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
//! Stamp `FOLDDB_BUILD_VERSION` into every binary at compile time.
//!
//! Resolution order:
//! 1. `$GITHUB_REF_NAME` when it looks like a release tag (`v<semver...>`).
//! This is how the release workflow pins binaries to the pushed tag —
//! without this, clap's `version` reads `CARGO_PKG_VERSION` and the
//! binary reports the stale manifest version regardless of the tag.
//! 2. `git describe --tags --always --dirty` so local dev builds reflect
//! real git state (e.g. `v0.3.1-5-ge1f2a` or `e1f2a-dirty`).
//! 3. `CARGO_PKG_VERSION` fallback when neither is available (e.g. source
//! tarball builds without git metadata).
//!
//! Keep this small and panic-free — build scripts run on every compile.
use std::process::Command;
fn main() {
// Re-run when GITHUB_REF_NAME changes (release builds are driven by the
// tag) or when the build script itself is edited. We intentionally do
// NOT track `.git/HEAD` / `.git/refs/tags` because this package is a
// git-submodule worktree: `.git` is a *file* pointing at the real
// gitdir, so the usual trick silently no-ops. Dev builds with a stale
// git-describe stamp are a mild annoyance; `cargo clean` refreshes.
println!("cargo:rerun-if-env-changed=GITHUB_REF_NAME");
println!("cargo:rerun-if-env-changed=FOLDDB_BUILD_VERSION_OVERRIDE");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=environments.json");
let version = resolve_version();
println!("cargo:rustc-env=FOLDDB_BUILD_VERSION={version}");
ensure_react_dist_stub();
generate_environments_module();
}
/// `src/server/static_assets.rs` uses `#[derive(RustEmbed)]` pointing at
/// `src/server/static-react/dist`. That directory is a Vite build output
/// (gitignored), so a fresh clone or a new worktree can't even `cargo
/// check` until someone runs `npm install && npm run build` inside
/// `static-react/`. Agents kept re-discovering this from scratch.
///
/// Write a tiny stub index so rustc-level compilation works. A real
/// `npm run build` overwrites the stub. CI always builds the frontend
/// before running Rust jobs, so prod and CI are unaffected.
fn ensure_react_dist_stub() {
let dist = std::path::Path::new("src/server/static-react/dist");
if dist.exists() && dist.join("index.html").exists() {
return;
}
if let Err(e) = std::fs::create_dir_all(dist) {
println!("cargo:warning=failed to create static-react/dist stub dir: {e}");
return;
}
let stub = b"<!-- fold_db_node build.rs stub \
(run `npm --prefix src/server/static-react run build` \
for the real UI). -->\n";
if let Err(e) = std::fs::write(dist.join("index.html"), stub) {
println!("cargo:warning=failed to write static-react/dist stub: {e}");
}
}
fn resolve_version() -> String {
if let Ok(override_val) = std::env::var("FOLDDB_BUILD_VERSION_OVERRIDE") {
let trimmed = override_val.trim();
if !trimmed.is_empty() {
return trimmed.to_string();
}
}
if let Ok(ref_name) = std::env::var("GITHUB_REF_NAME") {
if let Some(stripped) = strip_tag_prefix(&ref_name) {
return stripped;
}
}
if let Some(described) = git_describe() {
return described;
}
env!("CARGO_PKG_VERSION").to_string()
}
/// Strip the leading `v` from `v0.3.1`-style tags. Returns `None` for refs
/// that do not look like semver-ish release tags (e.g. branch names in
/// GitHub Actions branch-push workflows), so we fall through to git describe.
fn strip_tag_prefix(ref_name: &str) -> Option<String> {
let trimmed = ref_name.trim();
let rest = trimmed.strip_prefix('v')?;
let first = rest.chars().next()?;
if first.is_ascii_digit() {
Some(rest.to_string())
} else {
None
}
}
/// Parse `environments.json` (the cross-environment URL registry — single
/// source of truth) and emit `$OUT_DIR/environments_generated.rs` with one
/// `pub const` per (env, key). `src/endpoints.rs` includes the file via
/// `include!()`. A malformed registry breaks the build, which is the point —
/// drift cannot reach a release.
///
/// Keep the generated names in lockstep with `endpoints.rs`: any new key in
/// the JSON automatically gets a constant, but the consumer-side wrapper
/// must opt in by referencing it.
fn generate_environments_module() {
let path = std::path::Path::new("environments.json");
let raw = std::fs::read_to_string(path)
.unwrap_or_else(|e| panic!("environments.json must exist at repo root: {e}"));
let envs = ["dev", "prod"];
// `region` is in environments.json but only consumed by shell helpers
// (scripts/get-env-url.sh) via jq — no Rust caller reads it. Skip it
// here so the generated module has no dead constants.
let keys = ["exemem_api", "schema_service", "discovery"];
let mut out = String::from(
"// @generated by build.rs from environments.json — do not edit.\n\
// Edits to URLs go in environments.json (the single source of truth).\n\n",
);
for env in &envs {
for key in &keys {
let value = extract_string(&raw, env, key)
.unwrap_or_else(|| panic!("environments.json: missing environments.{env}.{key}"));
let const_name = format!("{}_{}", env.to_uppercase(), key.to_uppercase());
out.push_str(&format!("pub const {const_name}: &str = {value:?};\n"));
}
}
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR is set by cargo");
let out_path = std::path::Path::new(&out_dir).join("environments_generated.rs");
std::fs::write(&out_path, out)
.unwrap_or_else(|e| panic!("failed to write {}: {e}", out_path.display()));
}
/// Tiny JSON-string extractor for the fixed shape
/// `{"environments": {"<env>": {"<key>": "<value>", ...}, ...}}`. Avoids a
/// `serde_json` build-dep — at build time we'd rather not compile serde
/// just to read four URLs. Robust to whitespace and field ordering; not
/// robust to escaped quotes inside values (we don't have any).
fn extract_string(raw: &str, env: &str, key: &str) -> Option<String> {
// Find `"<env>":` then within its object find `"<key>":`. Brace-depth
// tracking keeps us inside the right env's object.
let env_marker = format!("\"{env}\"");
let env_start = raw.find(&env_marker)?;
let after_env = &raw[env_start..];
let obj_start = after_env.find('{')?;
let mut depth = 0i32;
let mut obj_end = obj_start;
for (i, c) in after_env[obj_start..].char_indices() {
match c {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
obj_end = obj_start + i;
break;
}
}
_ => {}
}
}
let env_obj = &after_env[obj_start..=obj_end];
let key_marker = format!("\"{key}\"");
let key_pos = env_obj.find(&key_marker)?;
let after_key = &env_obj[key_pos + key_marker.len()..];
let colon = after_key.find(':')?;
let after_colon = &after_key[colon + 1..];
let q1 = after_colon.find('"')?;
let after_q1 = &after_colon[q1 + 1..];
let q2 = after_q1.find('"')?;
Some(after_q1[..q2].to_string())
}
fn git_describe() -> Option<String> {
let output = Command::new("git")
.args(["describe", "--tags", "--always", "--dirty"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let raw = String::from_utf8(output.stdout).ok()?;
let trimmed = raw.trim();
if trimmed.is_empty() {
return None;
}
// Keep `v` prefix stripped for consistency with the tag branch.
Some(strip_tag_prefix(trimmed).unwrap_or_else(|| trimmed.to_string()))
}