CP1.2: build.rs keys the data tests on a whole data folder, handles non-UTF-8 paths; CI pins craft-i18n

Review of craft-libs#482:
- craft_proof_data is set only when every Language data file is embedded, and the new
  craft_proof_no_data only when none is, so a partial folder (en-us but no pl) runs neither
  the pattern tests nor the no-data tests.
- A CRAFT_I18N_DIR without language-data/ is watched (rerun-if-changed), so the build
  notices when the checkout gains it.
- A CRAFT_I18N_DIR that isn't valid UTF-8 is reported and embeds nothing, instead of a lossy
  path in include_str!.
- proof.yml checks out craft-i18n at 062a6375c322c88b96691a17f7b5fed71c51f7a8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2
This commit is contained in:
Martin BallandClaude Opus 5.5 committed 2026-11-01 18:37:02 +00:00
1 parent c80b6225f1
commit 48ed6f8cbd
3 files changed
+26 -16

No files matched your search

+1 -1
View File
@@ -25,10 +25,10 @@ jobs:
with:
persist-credentials: false
# The Language data (craft-i18n `language-data/`), embedded by crates/proof/build.rs.
# TODO(CP1.2): pin `ref` to the craft-i18n commit that adds language-data/ once it merges.
- uses: actions/checkout@v4
with:
repository: storytold/craft-i18n
ref: 062a6375c322c88b96691a17f7b5fed71c51f7a8
path: craft-i18n
persist-credentials: false
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
+23 -12
View File
@@ -4,12 +4,16 @@
//! one constant per file in [`FILES`], each an `include_str!`/`include_bytes!` of
//! `$CRAFT_I18N_DIR/language-data/<path>`. Unset, or not a checkout, or a file missing (a warning):
//! that constant is empty, and the crate has no embedded Language data for it (English spelling
//! falls back to an installed Hunspell dictionary or the crate's own core word list, and
//! falls back to an installed Hunspell dictionary or the crate's own copy of the Moby word list, and
//! hyphenation that needs the data is off). With `CRAFT_I18N_REQUIRED=1` (releases) any of those
//! is an error instead.
//!
//! When the English word list is embedded, the `craft_proof_data` cfg is set (the tests that pin
//! results of the data use it).
//! When every file in [`FILES`] is embedded, the `craft_proof_data` cfg is set (the tests that pin
//! results of the data use it); when none is, `craft_proof_no_data` (the tests of a build without
//! the data). A partial data folder sets neither, so neither set of tests runs against it.
//!
//! `include_str!` takes a string, so a `CRAFT_I18N_DIR` that isn't valid UTF-8 can't be embedded:
//! it is treated like a folder without the data.
use std::fmt::Write as _;
use std::path::{Path, PathBuf};
@@ -32,6 +36,7 @@ fn main() {
println!("cargo::rerun-if-env-changed=CRAFT_I18N_DIR");
println!("cargo::rerun-if-env-changed=CRAFT_I18N_REQUIRED");
println!("cargo::rustc-check-cfg=cfg(craft_proof_data)");
println!("cargo::rustc-check-cfg=cfg(craft_proof_no_data)");
let required = std::env::var("CRAFT_I18N_REQUIRED").is_ok_and(|v| v.trim() == "1");
let level = if required { "error" } else { "warning" };
let dir = match std::env::var_os("CRAFT_I18N_DIR").filter(|d| !d.is_empty()) {
@@ -47,35 +52,36 @@ fn main() {
println!("cargo::error=CRAFT_I18N_REQUIRED=1 but CRAFT_I18N_DIR is unset: a release must embed the Language data");
} else {
println!(
"cargo::warning=CRAFT_I18N_DIR is unset: craft-proof embeds no Language data (English spelling falls back to system Hunspell or the core word list; no hyphenation)"
"cargo::warning=CRAFT_I18N_DIR is unset: craft-proof embeds no Language data (English spelling falls back to system Hunspell or the built-in Moby word list; no hyphenation)"
);
}
None
}
};
let mut o = String::from("// Generated by craft-proof's build.rs from CRAFT_I18N_DIR/language-data. Do not edit.\n");
let mut english = false;
let mut embedded = 0;
for &(name, rel, bytes) in FILES {
let file = dir.as_ref().map(|d| d.join(rel)).filter(|f| f.is_file());
if let (Some(d), None) = (&dir, &file) {
println!("cargo::{level}=CRAFT_I18N_DIR: {} is missing; craft-proof embeds none", d.join(rel).display());
}
let (ty, empty, mac) = if bytes { ("&[u8]", "b\"\"", "include_bytes") } else { ("&str", "\"\"", "include_str") };
let value = match &file {
// `language_data` only hands back a UTF-8 folder, so this is the path as it is.
let value = match file.as_ref().and_then(|f| f.to_str()) {
Some(f) => {
println!("cargo::rerun-if-changed={}", f.display());
if name == "EN_US_DIC" {
english = true;
}
println!("cargo::rerun-if-changed={f}");
embedded += 1;
// `{:?}` escapes the path (Windows backslashes) as a Rust string literal.
format!("{mac}!({:?})", f.display().to_string())
format!("{mac}!({f:?})")
}
None => empty.to_string(),
};
let _ = writeln!(o, "/// `language-data/{rel}` (empty when it isn't embedded).\npub(crate) static {name}: {ty} = {value};");
}
if english {
if embedded == FILES.len() {
println!("cargo::rustc-cfg=craft_proof_data");
} else if embedded == 0 {
println!("cargo::rustc-cfg=craft_proof_no_data");
}
let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or_default()).join("language_data.rs");
if let Err(e) = std::fs::write(&out, o) {
@@ -91,8 +97,13 @@ fn language_data(dir: &Path) -> Result<PathBuf, String> {
if !dir.is_absolute() {
return Err(format!("{} is not an absolute path", dir.display()));
}
if dir.to_str().is_none() {
return Err(format!("{} is not valid UTF-8, which include_str! needs", dir.display()));
}
let data = dir.join("language-data");
if !data.is_dir() {
// Notice when the checkout gains its Language data.
println!("cargo::rerun-if-changed={}", dir.display());
return Err(format!("{} has no language-data/ (a craft-i18n checkout older than its Language data?)", dir.display()));
}
println!("cargo::rerun-if-changed={}", data.display());
+2 -3
View File
@@ -1115,9 +1115,8 @@ fn starts_consonant_sound(w: &str) -> bool {
#[cfg(test)]
pub(crate) static DICTIONARY_TESTS: std::sync::Mutex<()> = std::sync::Mutex::new(());
/// The no-Language-data build (`build.rs` sets `craft_proof_data` when the English word list is
/// embedded).
#[cfg(all(test, not(craft_proof_data)))]
/// The no-Language-data build (`build.rs` sets `craft_proof_no_data` when nothing is embedded).
#[cfg(all(test, craft_proof_no_data))]
mod no_data;
#[cfg(test)]