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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
//! Build script: spellcheck dictionaries next to the binary + a Windows `.exe` icon
//! + the build stamp.
//!
//! **Dictionaries.** Third-party work, pinned and licensed in `dictionaries/SOURCES.md`.
//! In portable mode the application reads data from a `data/` subdirectory
//! next to the executable (in dev — `target/<profile>/data/`, see shared/paths.rs),
//! so dictionaries are expected at `data/dictionaries/`. Dictionaries live in the project
//! root's `dictionaries/` (checked into the repo; the release workflow also packs them into
//! `data/dictionaries/`); this script copies them into the output directory so
//! `cargo run` sees spellcheck right away, with no manual copying. Missing dictionaries
//! aren't an error (spellcheck simply turns off). The **destination** is an input of this
//! script as well as its output (`copy_dictionaries`) — without that, a `data/dictionaries`
//! deleted by hand never comes back.
//!
//! **Icon and version info.** For the Windows target, an icon resource from
//! `assets/mindfork.ico` is embedded into the `.exe` — otherwise Explorer, the taskbar, and Alt+Tab show the
//! default icon. Shortcuts and the installer's `UninstallDisplayIcon`
//! (`packaging/windows/mindfork.iss`) pick it up from here for free too. See docs/branding.md §4.1.
//! The same resource carries the VERSIONINFO strings — product name, description,
//! company, copyright — which Windows shows in the UAC dialog, Task Manager and
//! Explorer, and which code signing pins (docs/research/code-signing.md §6.2).
//!
//! **Build stamp.** The moment of the build goes in as `MINDFORK_BUILD_EPOCH`
//! (Unix seconds, or `SOURCE_DATE_EPOCH` when set) and surfaces as the build-date
//! row of the "About" tab — in release builds only, because this script does not
//! re-run for a `src/` change. See `embed_build_stamp` and
//! `shared/credits.rs::build_date` (spec §11.7).
//!
//! **Syntax dump.** The vendored grammars in `syntaxes/` (see its `SOURCES.md`)
//! are added to syntect's bundled set and written into `OUT_DIR` as one
//! uncompressed dump, which `shared/markdown/code.rs` embeds. Assembling the
//! set costs ~130 ms; loading the dump costs ~0.55 ms, so it belongs here and
//! not in a `LazyLock` — and a grammar syntect cannot load fails the **build**
//! instead of silently disappearing at runtime (docs/history/vendored-syntaxes.md §2.3).
use ;
use ;
/// The name the product presents under, in the `.exe`'s VERSIONINFO block.
///
/// `AppName` in the installer, the command, the shortcut and every user-facing
/// surface say `mindfork`, while `mindfork-rs` stays the name of files and
/// directories (docs/research/binary-rename.md). Spelled out rather than left to
/// `winresource`, which takes `package.name`: that says `mindfork` too since the
/// crate was renamed for crates.io (§10), but a registry identity and the product
/// name Windows shows are two different things and must not be tied by accident.
///
/// Gated by host, like everything else the resource needs: on a Linux host the
/// crate that would read it is not even a dependency (see [`embed_windows_icon`]).
const PRODUCT_NAME: &str = "mindfork";
/// `CompanyName` — the same string as the installer's `AppPublisher`.
const PUBLISHER: &str = "Vladimir Shylov";
/// Copies the repository's `dictionaries/` into the portable data root next to
/// the binary (`target/<profile>/data/dictionaries/`, see shared/paths.rs).
///
/// **Both `rerun-if-changed` sides are load-bearing.** The source, so that an
/// edited dictionary reaches the build. The destination, because cargo re-runs a
/// build script only for the paths it declares — and a declared path that does
/// not exist counts as changed. Without the destination among them, a
/// `data/dictionaries` that is *deleted* never comes back: wiping `data/` is the
/// ordinary way to put the app back to a fresh install, nothing under `src/`
/// re-runs this script, and so `cargo run` / `cargo run -r` keep launching a
/// build with spellcheck silently off until `dictionaries/`, `assets/` or
/// `syntaxes/` happen to move for their own reasons. Measured on 1.96.0: after
/// `rm -rf target/release/data` a no-op `cargo check --release` did not re-create
/// it, and on a probe crate with the same declaration shape neither did a build
/// after a `src/` edit or a `Cargo.toml` touch (docs/journal/release.md).
///
/// The destination is declared only once there is something to copy: a declared
/// path that never gets created would re-run this script on **every** build.
/// Compiles the moment of the build in as `MINDFORK_BUILD_EPOCH` (Unix seconds),
/// which `shared/credits.rs` turns into the build-date row of the "About" tab.
///
/// **Seconds, not a formatted date**: `chrono` is already a runtime dependency
/// and knows how to render a timestamp, so a build dependency (and a second
/// date implementation) would buy nothing. Formatting happens where the value
/// is displayed.
///
/// `SOURCE_DATE_EPOCH` wins when it is set — the cross-distribution convention
/// for reproducible builds (Debian, Nix, openSUSE): a package rebuilt from the
/// same source must produce the same bytes, and a wall clock in the binary is
/// exactly what breaks that. `rerun-if-env-changed` makes cargo notice when the
/// variable appears or changes.
///
/// **The value is only as fresh as the last run of this script**, and this
/// script declares `rerun-if-changed` paths, so cargo will not re-run it when
/// `src/` changes — a development binary would carry the date of whenever
/// `dictionaries/`, `assets/` or `syntaxes/` last moved. That is why the row
/// is shown for release builds alone (`credits::build_date`); forcing a re-run
/// on every build would rebuild the syntax dump each time and buy a row nobody
/// reads in a debug build.
/// Embeds the icon into the Windows `.exe` (the `IDI_ICON1` resource) — the Windows-host variant.
///
/// A double gate is unavoidable: `winresource` is declared under
/// `[target.'cfg(windows)'.build-dependencies]`, and for **build** dependencies `cfg`
/// is evaluated by the **host** (the build script runs on it) — meaning on a Linux host
/// the crate is absent and referencing it won't compile. Hence `#[cfg(windows)]` by host
/// (is the crate present) plus a `CARGO_CFG_TARGET_OS` check by target (is the icon needed).
///
/// A failure deliberately **doesn't fail the build**, going into `cargo:warning` instead: `winresource` on
/// the MSVC target calls `rc.exe` from the Windows SDK, and on a machine without the SDK the app should
/// still build — the icon is cosmetic.
/// The copyright line, read out of `LICENSE` rather than written down twice.
///
/// The year is the part that drifts, and a stale one in a signed binary is the
/// kind of detail nobody notices until it is in front of a reviewer. The
/// installer's `VersionInfoCopyright` cannot read a file, so it keeps its own
/// copy — pinned to this same line by `credits::tests` instead.
/// The non-Windows-host variant: `winresource` is unavailable (see above).
///
/// The release workflow builds Windows on a windows runner, so the regular path isn't
/// affected. We only warn on a Linux → Windows cross-build, so as not to silently
/// ship a `.exe` with no icon.
/// Assembles syntect's bundled syntaxes plus everything in `syntaxes/` into one
/// uncompressed dump at `$OUT_DIR/syntaxes.packdump`.
///
/// **A grammar that fails to load fails the build**, deliberately: syntect
/// reads only `.sublime-syntax` and does not support `extends:`, so an upstream
/// that migrates to sublime-syntax v2 would otherwise drop a language silently
/// (docs/history/vendored-syntaxes.md §2.1). The `true` in `load_from_str` is
/// `lines_include_newline`, matching `load_defaults_newlines` — the renderer
/// feeds lines with their trailing `\n` (`LinesWithEndings`).
/// The directory with the binary (`target/<profile>/`), derived from `OUT_DIR`:
/// `…/target/<profile>/build/<crate>-<hash>/out` → 3 levels up.
/// Copies regular files from `src` into `dst` (no recursion, no hidden files).
///
/// **The destination's timestamps are kept still**, because `dst` is a
/// `rerun-if-changed` input of this script (see [`copy_dictionaries`]): a file
/// already there is left alone ([`is_up_to_date`]), and one that is copied is
/// given its source's modification time. A copy that stamped "now" on every run
/// would leave the destination looking newer than the fingerprint, so cargo
/// would re-run this script on every build — and each re-run re-stamps
/// `MINDFORK_BUILD_EPOCH` ([`embed_build_stamp`]), which relinks the whole
/// binary (in release: LTO, one codegen unit). Recreating the directory still
/// costs one such extra run, which is the case where the work is wanted anyway.
/// Is `dst` already the copy of `src` this script would make? Same length and
/// not older — the exact pair [`copy_dir`] leaves behind. Enough to notice an
/// edited dictionary: a rewrite that happens to preserve the byte count still
/// moves the modification time.
/// Gives `dst` the modification time of `src` (see [`copy_dir`] for why).
///
/// Best effort: a filesystem that refuses the update costs an extra build-script
/// run, not a broken build, so a failure is deliberately ignored rather than
/// turned into a `cargo:warning` nobody can act on.