From 5275448b4e480536c7e705c6d32f07d5a6c40b70 Mon Sep 17 00:00:00 2001 From: Vanshaj Poonia Date: Tue, 30 Jun 2026 21:33:25 +0530 Subject: [PATCH 1/2] fix(tauri): rename misleading `ci` feature and fail loudly without it (#1347) The `prod()` function that spawns the bundled backend/sync sidecars was gated behind a feature named `ci`, which reads as "only needed for continuous integration." In reality it's required for every working release build (pr-check-build.yml and build-and-release.yml already pass --features ci to all of them). A release build without that flag compiled fine and launched normally, but silently never started its backend. - Rename the feature to `bundled-sidecars` everywhere it's referenced (Cargo.toml, main.rs cfg attributes, both workflow files) so its purpose is clear from the name. - Make the no-feature `prod()` variant fail loudly (return an Err that aborts startup) when built in release mode, while still no-op'ing in debug builds so `cargo tauri dev` is unaffected. --- .github/workflows/build-and-release.yml | 8 ++++---- .github/workflows/pr-check-build.yml | 6 +++--- frontend/src-tauri/Cargo.toml | 5 ++++- frontend/src-tauri/src/main.rs | 23 ++++++++++++++++++----- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 7bdae5362..43ef9967f 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -280,16 +280,16 @@ jobs: matrix: include: - platform: "macos-latest" - # Enable CI feature so prod() spawns backend + sync in release builds - args: "--target aarch64-apple-darwin --features ci" + # Enable bundled-sidecars feature so prod() spawns backend + sync in release builds + args: "--target aarch64-apple-darwin --features bundled-sidecars" server-artifact: "PictoPy-MacOS" sync-artifact: "PictoPy-Sync-MacOS" - platform: "ubuntu-22.04" - args: "--features ci" + args: "--features bundled-sidecars" server-artifact: "PictoPy-Ubuntu" sync-artifact: "PictoPy-Sync-Ubuntu" - platform: "windows-latest" - args: "--features ci" + args: "--features bundled-sidecars" server-artifact: "PictoPy-Windows" sync-artifact: "PictoPy-Sync-Windows" runs-on: ${{ matrix.platform }} diff --git a/.github/workflows/pr-check-build.yml b/.github/workflows/pr-check-build.yml index 8e6614df6..565cbdd7b 100644 --- a/.github/workflows/pr-check-build.yml +++ b/.github/workflows/pr-check-build.yml @@ -14,11 +14,11 @@ jobs: matrix: include: - platform: "macos-latest" - args: "--target aarch64-apple-darwin --features ci" + args: "--target aarch64-apple-darwin --features bundled-sidecars" - platform: "ubuntu-22.04" - args: "--features ci" + args: "--features bundled-sidecars" - platform: "windows-latest" - args: "--features ci" + args: "--features bundled-sidecars" runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v4 diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index c16753a36..6689f20db 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -41,6 +41,9 @@ tauri-plugin-opener = "2.5.2" [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! custom-protocol = ["tauri/custom-protocol"] -ci = [] +# Enables spawning the bundled Python backend/sync sidecars. Required for +# release builds (see frontend/src-tauri/src/main.rs); without it `prod()` +# fails loudly in release builds instead of silently launching a backend-less app. +bundled-sidecars = [] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index 5b7aa6acc..95ca1fc60 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -21,7 +21,7 @@ const ENDPOINTS: [(&str, &str, &str); 2] = [ ), ]; -#[cfg(feature = "ci")] +#[cfg(feature = "bundled-sidecars")] fn is_process_alive() -> bool { use reqwest::blocking::Client; @@ -102,9 +102,9 @@ fn kill_process_tree() -> Result<(), String> { Ok(()) } -#[cfg(feature = "ci")] +#[cfg(feature = "bundled-sidecars")] fn prod(app: &tauri::AppHandle, resource_path: &std::path::Path) -> Result<(), String> { - println!("`ci` feature enabled"); + println!("`bundled-sidecars` feature enabled"); let backend_path = resource_path.join("backend"); let backend_executable = backend_path.join("PictoPy_Server"); @@ -183,9 +183,22 @@ fn prod(app: &tauri::AppHandle, resource_path: &std::path::Path) -> Result<(), S Ok(()) } -#[cfg(not(feature = "ci"))] +#[cfg(not(feature = "bundled-sidecars"))] fn prod(_app: &tauri::AppHandle, _resource_path: &std::path::Path) -> Result<(), String> { - Ok(()) + // `cargo tauri dev` intentionally omits this feature; the backend/sync + // services are run separately during local development. + if cfg!(debug_assertions) { + return Ok(()); + } + + // A release build without this feature would otherwise launch with no + // backend and no indication anything is wrong (see issue #1347). + Err( + "Release build is missing the `bundled-sidecars` feature: the bundled \ + backend/sync services will never be started. Rebuild with \ + `--features bundled-sidecars`." + .to_string(), + ) } fn main() { From 0562b2a417450a1c8cbe9fc1c5c4435be0dfcffd Mon Sep 17 00:00:00 2001 From: Vanshaj Poonia Date: Tue, 30 Jun 2026 21:53:09 +0530 Subject: [PATCH 2/2] test(tauri): add unit test for no-feature prod() behavior Extract the no-op/fail-loudly decision into prod_without_bundled_sidecars() so it's testable without mocking tauri::AppHandle (the no-feature prod() variant never touches its app/resource_path args). Add a test asserting it no-ops under debug_assertions and errors otherwise, verified to pass under both `cargo test` and `cargo test --release`. --- frontend/src-tauri/src/main.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index 95ca1fc60..32a99138e 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -185,6 +185,11 @@ fn prod(app: &tauri::AppHandle, resource_path: &std::path::Path) -> Result<(), S #[cfg(not(feature = "bundled-sidecars"))] fn prod(_app: &tauri::AppHandle, _resource_path: &std::path::Path) -> Result<(), String> { + prod_without_bundled_sidecars() +} + +#[cfg(not(feature = "bundled-sidecars"))] +fn prod_without_bundled_sidecars() -> Result<(), String> { // `cargo tauri dev` intentionally omits this feature; the backend/sync // services are run separately during local development. if cfg!(debug_assertions) { @@ -201,6 +206,22 @@ fn prod(_app: &tauri::AppHandle, _resource_path: &std::path::Path) -> Result<(), ) } +#[cfg(all(test, not(feature = "bundled-sidecars")))] +mod tests { + use super::*; + + #[test] + fn no_op_in_debug_builds_fails_loudly_in_release_builds() { + let result = prod_without_bundled_sidecars(); + + if cfg!(debug_assertions) { + assert_eq!(result, Ok(())); + } else { + assert!(result.is_err()); + } + } +} + fn main() { tauri::Builder::default() .plugin(tauri_plugin_opener::init())