diff --git a/.github/workflows/proof.yml b/.github/workflows/proof.yml index db171ee1..8b119ac2 100644 --- a/.github/workflows/proof.yml +++ b/.github/workflows/proof.yml @@ -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 diff --git a/crates/proof/build.rs b/crates/proof/build.rs index ccceb0b3..a0440624 100644 --- a/crates/proof/build.rs +++ b/crates/proof/build.rs @@ -4,12 +4,16 @@ //! one constant per file in [`FILES`], each an `include_str!`/`include_bytes!` of //! `$CRAFT_I18N_DIR/language-data/`. 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 { 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()); diff --git a/crates/proof/src/lib.rs b/crates/proof/src/lib.rs index 85835797..578f5df4 100644 --- a/crates/proof/src/lib.rs +++ b/crates/proof/src/lib.rs @@ -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)]