tauri-apps / tauri

Build smaller, faster, and more secure desktop and mobile applications with a web frontend.
https://tauri.app
Apache License 2.0
83.94k stars 2.52k forks source link

[bug] External command spawns Tauri app clone and stalls, until clone app is closed #8551

Open regexident opened 9 months ago

regexident commented 9 months ago

Describe the bug

Running a third-party function from src-tauri/srcmain.rs that spawns a call to an external command-line tool through an env var causes the Tauri app to …

  1. spawn a (fully functional) clone of itself
  2. stalls the execution of the external command until the spawned Tauri app is closed

Both of these are undesirable for obvious reasons.

The third-party function in this case is ProjectWorkspace.run_build_scripts(…) (docs) from rust-analyzer's ra_ap_project_model crate (docs) and the external command-line tool is cargo check with the env var being RUSTC_WRAPPER (cargo book).

The ProjectWorkspace.run_build_scripts(…) function works just fine outside of Tauri.

Reproduction

  1. Create a barebones project:

    yarn create tauri-app:

    yarn create v1.22.21
    
    ...
    
    success Installed "create-tauri-app@3.11.7" with binaries:
         - create-tauri-app
    ✔ Project name · tauri-repro
    ✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, bun)
    ✔ Choose your package manager · yarn
    ✔ Choose your UI template · Vanilla
    ✔ Choose your UI flavor · TypeScript

    Then add the following dependencies:

    [dependencies]
    ...
    fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs" }
    anyhow = "1.0.78"
    log = "0.4.20"
    env_logger = "0.10.1"
    ra_ap_load-cargo = "=0.0.190"
    ra_ap_paths = "=0.0.190"
    ra_ap_project_model = "=0.0.190"
    ra_ap_cfg = "=0.0.190"
    ra_ap_ide_db = "=0.0.190"
  2. And modify the src-tauri/src/main.rs file to look like this:

    // Prevents additional console window on Windows in release, DO NOT REMOVE!!
    #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
    
    use std::path::{Path, PathBuf};
    
    use ra_ap_load_cargo::{LoadCargoConfig, ProcMacroServerChoice};
    use ra_ap_paths::AbsPathBuf;
    use ra_ap_project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
    
    fn load_workspace(manifest_path: &Path) -> anyhow::Result<()> {
       let manifest_path = if !manifest_path.is_absolute() {
           std::env::current_dir()?.join(manifest_path)
       } else {
           manifest_path.to_owned()
       }
       .canonicalize()?;
    
       // Changing this to `true` causes the app to
       // 1. spawn a clone of itself
       // 2. stall itself, until the clone is closed
       const WRAP_RUSTC_IN_BUILD_SCRIPTS: bool = false;
    
       let cargo_config = CargoConfig {
           wrap_rustc_in_build_scripts: WRAP_RUSTC_IN_BUILD_SCRIPTS,
           ..Default::default()
       };
    
       let load_config = LoadCargoConfig {
           load_out_dirs_from_check: true,
           prefill_caches: false,
           with_proc_macro_server: ProcMacroServerChoice::Sysroot,
       };
    
       let mut project_workspace = {
           ProjectWorkspace::load(
               ProjectManifest::discover_single(AbsPathBuf::assert(manifest_path).as_path())?,
               &cargo_config,
               &|_| {},
           )
       }?;
    
       if load_config.load_out_dirs_from_check {
           log::debug!("🟡 Running build scripts ...");
           if WRAP_RUSTC_IN_BUILD_SCRIPTS {
               log::debug!("🔴 Execution stalled, waiting for clone app to be closed.");
           }
           let build_scripts = project_workspace.run_build_scripts(&cargo_config, &|_| {})?;
           project_workspace.set_build_scripts(build_scripts);
       }
    
       let _ = ra_ap_load_cargo::load_workspace(
           project_workspace.clone(),
           &cargo_config.extra_env,
           &load_config,
       )?;
    
       log::debug!("🟢 Done.");
    
       Ok(())
    }
    
    // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
    #[tauri::command]
    fn greet(name: &str) -> String {
       let manifest_path = PathBuf::from(name);
    
       match load_workspace(&manifest_path) {
           Ok(()) => "It worked!".to_string(),
           Err(err) => format!("Error: {err:?}"),
       }
    }
    
    fn main() {
       let _ = fix_path_env::fix();
    
       env_logger::init_from_env({
           let env = env_logger::Env::default();
           let key = env_logger::DEFAULT_FILTER_ENV;
           let filters = ["debug", "tao=trace", "salsa=warn"];
           env.filter_or(key, filters.join(","))
       });
    
       tauri::Builder::default()
           .invoke_handler(tauri::generate_handler![greet])
           .run(tauri::generate_context!())
           .expect("error while running tauri application");
    }
  3. Create a dummy Rust project (e.g. via cargo new "tauri-dummy" in a separate shell) somewhere else on your machine.

  4. Run the tauri app via yarn tauri dev/npm run tauri dev.

  5. Pass the dummy project's manifest path (e.g. /path/to/tauri-dummy/Cargo.toml) into the greet input field and click "Greet".

  6. The tauri web UI should show "It worked!" as the greeting and the logs should look something like this:

    [… DEBUG tauri_repro] 🟡 Running build scripts ...
    [… DEBUG tauri_repro] 🟢 Done.
  7. Change the value of const WRAP_RUSTC_IN_BUILD_SCRIPTS: bool in main.rs to true.

  8. Open your IDE's console and re-launch the tauri app.

  9. Notice how the program execution seemingly stalls and how a second app instance has spawned all by itself, right on top of the original one, with the console's tail looking something like this:

    [… DEBUG tauri_repro] 🟡 Running build scripts ...
    [… DEBUG tauri_repro] 🔴 Execution stalled, waiting for clone app to be closed.
  10. Close the clone app instance and notice how the original app's execution continues, and the UI shows "It worked!", with the console finally showing the following:

    [… DEBUG tauri_repro] 🟢 Done.

Expected behavior

The app should neither …

Full tauri info output

yarn run v1.22.21
$ tauri info

[✔] Environment
    - OS: Mac OS 14.3.0 X64
    ✔ Xcode Command Line Tools: installed
    ✔ rustc: 1.75.0 (82e1608df 2023-12-21)
    ✔ cargo: 1.75.0 (1d8b05cdd 2023-11-20)
    ✔ rustup: 1.26.0 (5af9b9484 2023-04-05)
    ✔ Rust toolchain: stable-x86_64-apple-darwin (default)
    - node: 21.5.0
    - yarn: 1.22.21
    - npm: 10.2.5

[-] Packages
    - tauri [RUST]: 1.5.4
    - tauri-build [RUST]: 1.5.1
    - wry [RUST]: 0.24.7
    - tao [RUST]: 0.16.5
    - @tauri-apps/api [NPM]: 1.5.3
    - @tauri-apps/cli [NPM]: 1.5.9

[-] App
    - build-type: bundle
    - CSP: unset
    - distDir: ../dist
    - devPath: http://localhost:1420/
    - bundler: Vite
✨  Done in 8.81s.

Stack trace

n/a

Additional context

Changing WRAP_RUSTC_IN_BUILD_SCRIPTS from false to true causes rust-analyzer (the library, aka the ra_ap_… dependencies we're linking) to wrap its invocation of cargo check through the RUSTC_WRAPPER env var.

The Cargo Book:

RUSTC_WRAPPER — Instead of simply running rustc, Cargo will execute this specified wrapper, passing as its command-line arguments the rustc invocation, with the first argument being the path to the actual rustc. Useful to set up a build cache tool such as sccache. See build.rustc-wrapper to set via config. Setting this to the empty string overwrites the config and resets cargo to not use a wrapper.

My initial suspicion was that I was missing a call to let _ = fix_path_env::fix(); to make this work on macOS, but as evident by the reproduction above the problem persists, even with the env fix.

regexident commented 9 months ago

While #7681 doesn't mention the undesired spawning of an entire app clone (but merely of its window) they might still be connected, I reckon?

kotekpsotek commented 8 months ago

Yep. It looks like the similar prone