163 lines
5.7 KiB
Rust
163 lines
5.7 KiB
Rust
use std::{
|
|
env, fs,
|
|
path::{Path, PathBuf},
|
|
process::Command,
|
|
};
|
|
|
|
fn main() {
|
|
println!("cargo:rerun-if-changed=../extensions/windows-thumbnail/src");
|
|
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
|
if target_os == "windows" {
|
|
build_windows_thumbnail();
|
|
}
|
|
|
|
propagate_sentry_dsn();
|
|
propagate_app_version();
|
|
|
|
tauri_build::build()
|
|
}
|
|
|
|
/// Bake the app version from `package.json` into the crate as `READEST_APP_VERSION`
|
|
/// (read back via `option_env!`). Sentry keys its release/environment off this
|
|
/// rather than `CARGO_PKG_VERSION`, because the crate version in `Cargo.toml` is
|
|
/// not kept in sync with the app version (and only `package.json` carries the
|
|
/// nightly `-YYYYMMDDHH` stamp). Absent/unparseable => unset, so the Rust code
|
|
/// falls back to the crate version.
|
|
fn propagate_app_version() {
|
|
let package_json = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
|
|
.join("..")
|
|
.join("package.json");
|
|
println!("cargo:rerun-if-changed={}", package_json.display());
|
|
|
|
if let Some(version) = read_json_string_field(&package_json, "version") {
|
|
println!("cargo:rustc-env=READEST_APP_VERSION={version}");
|
|
}
|
|
}
|
|
|
|
/// Read a top-level `"key": "value"` string from a JSON file without pulling in a
|
|
/// JSON parser. Returns the first match; `None` if the file/key is absent or the
|
|
/// value is empty. `package.json`'s own `"version"` is the first `"version"` key.
|
|
fn read_json_string_field(path: &Path, key: &str) -> Option<String> {
|
|
let contents = fs::read_to_string(path).ok()?;
|
|
let needle = format!("\"{key}\"");
|
|
for line in contents.lines() {
|
|
let Some(rest) = line.trim_start().strip_prefix(&needle) else {
|
|
continue;
|
|
};
|
|
let value = rest
|
|
.trim_start()
|
|
.strip_prefix(':')?
|
|
.trim()
|
|
.trim_end_matches(',')
|
|
.trim()
|
|
.trim_matches('"');
|
|
if !value.is_empty() {
|
|
return Some(value.to_string());
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Bake the Sentry DSN into the crate at build time via `cargo:rustc-env`, so
|
|
/// `option_env!("SENTRY_DSN")` (and, on iOS, the `readest_sentry_dsn` FFI) sees
|
|
/// it. Precedence: an existing `SENTRY_DSN` in the environment (CI secret / shell
|
|
/// export) wins; otherwise fall back to the gitignored `.env.local`, then `.env`,
|
|
/// at the app root. Absent everywhere => unset, so reporting stays disabled for
|
|
/// local and fork builds. `rerun-if-*` makes cargo recompile when the value or
|
|
/// the dotenv files change (avoiding a stale baked-in value).
|
|
fn propagate_sentry_dsn() {
|
|
println!("cargo:rerun-if-env-changed=SENTRY_DSN");
|
|
let app_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("..");
|
|
let env_local = app_dir.join(".env.local");
|
|
let env_file = app_dir.join(".env");
|
|
println!("cargo:rerun-if-changed={}", env_local.display());
|
|
println!("cargo:rerun-if-changed={}", env_file.display());
|
|
|
|
let dsn = env::var("SENTRY_DSN")
|
|
.ok()
|
|
.filter(|v| !v.is_empty())
|
|
.or_else(|| read_env_value(&env_local, "SENTRY_DSN"))
|
|
.or_else(|| read_env_value(&env_file, "SENTRY_DSN"));
|
|
|
|
if let Some(dsn) = dsn {
|
|
println!("cargo:rustc-env=SENTRY_DSN={dsn}");
|
|
}
|
|
}
|
|
|
|
/// Read a single `KEY=value` from a dotenv-style file, skipping blank lines and
|
|
/// `#` comments and stripping surrounding quotes. `None` if the file/key is
|
|
/// absent or the value is empty.
|
|
fn read_env_value(path: &Path, key: &str) -> Option<String> {
|
|
let contents = fs::read_to_string(path).ok()?;
|
|
for line in contents.lines() {
|
|
let line = line.trim();
|
|
if line.is_empty() || line.starts_with('#') {
|
|
continue;
|
|
}
|
|
if let Some(value) = line
|
|
.strip_prefix(key)
|
|
.and_then(|rest| rest.trim_start().strip_prefix('='))
|
|
{
|
|
let value = value.trim().trim_matches(|c| c == '"' || c == '\'');
|
|
if !value.is_empty() {
|
|
return Some(value.to_string());
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn build_windows_thumbnail() {
|
|
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
|
let dll_crate_dir = manifest_dir
|
|
.join("..")
|
|
.join("extensions")
|
|
.join("windows-thumbnail");
|
|
let dll_crate_manifest = dll_crate_dir.join("Cargo.toml");
|
|
let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".into());
|
|
|
|
let mut cmd = Command::new(env::var("CARGO").unwrap_or("cargo".into()));
|
|
cmd.arg("build")
|
|
.arg("--package")
|
|
.arg("windows_thumbnail")
|
|
.arg("--manifest-path")
|
|
.arg(&dll_crate_manifest);
|
|
|
|
if profile == "release" {
|
|
cmd.arg("--release");
|
|
}
|
|
|
|
let target_triple = env::var("TARGET").unwrap_or_default();
|
|
let host_triple = env::var("HOST").unwrap_or_default();
|
|
if !target_triple.is_empty() && target_triple != host_triple {
|
|
cmd.arg("--target").arg(&target_triple);
|
|
}
|
|
|
|
let status = cmd
|
|
.status()
|
|
.expect("Failed to run cargo build for windows_thumbnail");
|
|
if !status.success() {
|
|
panic!("Failed to build windows_thumbnail DLL");
|
|
}
|
|
|
|
let dll_name = "windows_thumbnail.dll";
|
|
let candidate_paths = [
|
|
dll_crate_dir.join("target").join(&profile).join(dll_name),
|
|
dll_crate_dir
|
|
.join("target")
|
|
.join(&target_triple)
|
|
.join(&profile)
|
|
.join(dll_name),
|
|
];
|
|
|
|
let dll_src = candidate_paths
|
|
.iter()
|
|
.find(|p| p.exists())
|
|
.expect("Failed to find built windows_thumbnail DLL");
|
|
|
|
let dll_dest = &dll_crate_dir.join("target").join(dll_name);
|
|
|
|
fs::copy(dll_src, dll_dest).expect("Failed to copy windows_thumbnail DLL");
|
|
println!("cargo:rerun-if-changed={}", dll_dest.display());
|
|
}
|