use std::{ env, fs, path::{Path, PathBuf}, }; use build_support::catalog_gate::{ CatalogBuildMode, CatalogGateInput, select_catalog_build_mode, validate_production_catalog, }; mod build_support { #[path = "catalog_gate.rs"] pub(crate) mod catalog_gate; #[path = "catalog_resources.rs"] pub(crate) mod catalog_resources; } const FIXTURE_DEVELOPMENT_ENV: &str = "LANSPREAD_USE_FIXTURE_CATALOG"; const LOCAL_DEVELOPMENT_ENV: &str = "LANSPREAD_USE_LOCAL_CATALOG"; const LOCAL_GAMES_DIR_ENV: &str = "LANSPREAD_LOCAL_GAMES_DIR"; const CATALOG_CONTENT_INDEX_NAME: &str = "catalog-content-index-v1.jsonl"; const EMBEDDED_CATALOG_INDEX_NAME: &str = "embedded-catalog-content-index-v1.jsonl"; const FIXTURE_CATALOG_ROOT: &str = "../../lanspread-peer-cli/catalogs/default"; const PRODUCTION_CATALOG_ROOT: &str = "production-catalog"; fn main() { println!("cargo:rerun-if-env-changed=TAURI_CONFIG"); println!("cargo:rerun-if-env-changed={FIXTURE_DEVELOPMENT_ENV}"); println!("cargo:rerun-if-env-changed={LOCAL_DEVELOPMENT_ENV}"); println!("cargo:rerun-if-env-changed={LOCAL_GAMES_DIR_ENV}"); println!("cargo:rerun-if-changed=tauri.conf.json"); println!("cargo:rerun-if-changed={PRODUCTION_CATALOG_ROOT}"); let mode = catalog_build_mode().unwrap_or_else(|error| { panic!("catalog packaging policy failed: {error}"); }); assert!( mode != CatalogBuildMode::LocalDevelopment || tauri_build::is_dev(), "local catalog testing is only available with tauri dev" ); if mode != CatalogBuildMode::FixtureDevelopment && let Err(error) = validate_production_catalog( catalog_root(mode).join("game.db"), catalog_root(mode).join("manifests"), ) { panic!("production catalog authority gate failed: {error}"); } embed_catalog_content_index(mode).unwrap_or_else(|error| { panic!("failed to embed catalog launch authority: {error}"); }); embed_startup_game_directory(mode).unwrap_or_else(|error| { panic!("failed to embed local test game directory: {error}"); }); let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR is not set"); let resources = build_support::catalog_resources::CatalogResources::prepare( Path::new(&out_dir), catalog_root(mode), ) .unwrap_or_else(|error| panic!("failed to reset generated catalog resources: {error}")); tauri_build::build(); // Tauri watches source resources only. Watching the generated database and // manifest tree also repairs deleted copies and notices another catalog // mode overwriting this profile's shared resource destination. for resource in resources .finish() .unwrap_or_else(|error| panic!("failed to track generated catalog resources: {error}")) { println!("cargo:rerun-if-changed={}", resource.display()); } } fn catalog_root(mode: CatalogBuildMode) -> &'static Path { Path::new(match mode { CatalogBuildMode::FixtureDevelopment => FIXTURE_CATALOG_ROOT, CatalogBuildMode::LocalDevelopment => "local-catalog", CatalogBuildMode::Production => PRODUCTION_CATALOG_ROOT, }) } fn embed_startup_game_directory(mode: CatalogBuildMode) -> Result<(), Box> { let directory = if mode == CatalogBuildMode::LocalDevelopment { let path = env::var_os(LOCAL_GAMES_DIR_ENV) .ok_or_else(|| format!("{LOCAL_GAMES_DIR_ENV} is required for local test builds"))?; let path = PathBuf::from(path).canonicalize()?; if !path.is_dir() { return Err("local test games path must be a directory".into()); } Some( path.into_os_string() .into_string() .map_err(|_| "local test games path must be UTF-8")?, ) } else { None }; let out_dir = env::var_os("OUT_DIR").ok_or("OUT_DIR is not set")?; // Rust string escaping keeps arbitrary path characters out of Cargo's // line-based build-script protocol. Production compiles a literal None. fs::write( PathBuf::from(out_dir).join("startup-game-directory.rs"), format!("{directory:?}"), )?; Ok(()) } fn embed_catalog_content_index(mode: CatalogBuildMode) -> Result<(), Box> { let manifest_root = catalog_root(mode).join("manifests"); let source = manifest_root.join(CATALOG_CONTENT_INDEX_NAME); println!("cargo:rerun-if-changed={}", source.display()); let bytes = fs::read(&source)?; let out_dir = env::var_os("OUT_DIR").ok_or_else(|| std::io::Error::other("OUT_DIR is not set"))?; let output = PathBuf::from(out_dir).join(EMBEDDED_CATALOG_INDEX_NAME); fs::write(output, bytes)?; Ok(()) } fn catalog_build_mode() -> Result> { let base_config = fs::read_to_string("tauri.conf.json")?; let config_override = env::var_os("TAURI_CONFIG") .map(|value| { value .into_string() .map_err(|_| "TAURI_CONFIG is not valid UTF-8".to_owned()) }) .transpose()?; let fixture_development_opt_in = match env::var_os(FIXTURE_DEVELOPMENT_ENV) { None => false, Some(value) if value == "1" => true, Some(_) => { return Err(format!("{FIXTURE_DEVELOPMENT_ENV} must be exactly 1 when set").into()); } }; let cargo_profile = env::var("PROFILE").ok(); let local_development_opt_in = match env::var_os(LOCAL_DEVELOPMENT_ENV) { None => false, Some(value) if value == "1" => true, Some(_) => { return Err(format!("{LOCAL_DEVELOPMENT_ENV} must be exactly 1 when set").into()); } }; let out_dir = env::var_os("OUT_DIR").map(std::path::PathBuf::from); select_catalog_build_mode(CatalogGateInput { base_config: &base_config, config_override: config_override.as_deref(), fixture_development_opt_in, local_development_opt_in, cargo_profile: cargo_profile.as_deref(), out_dir: out_dir.as_deref(), }) .map_err(Into::into) }