Repository: tauri
Issue: [bug] External command spawns Tauri app clone and stalls, until clone app is closed
Labels: type: bug,status: needs triage

### 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](https://docs.rs/ra_ap_project_model/0.0.190/ra_ap_project_model/enum.ProjectWorkspace.html#method.run_build_scripts)) from `rust-analyzer`'s `ra_ap_project_model` crate ([docs](https://docs.rs/ra_ap_project_model/0.0.190/ra_ap_project_model/)) and the external command-line tool is `cargo check` with the env var being `RUSTC_WRAPPER` ([cargo book](https://doc.rust-lang.org/cargo/reference/environment-variables.html)).

The `ProjectWorkspace.run_build_scripts(…)` function works just fine outside of Tauri.

### Reproduction

1. Create a barebones project:

   `yarn create tauri-app`:

   ```terminal
   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:

   ```toml
   [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:

   ```rust
   // 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:

    ```terminal
    [… 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:

    ```terminal
    [… 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:

    ```terminal
    [… DEBUG tauri_repro] 🟢 Done.
    ```

### Expected behavior

The app should neither …

- spawn a clone of itself
- stall its execution

### Full `tauri info` output

```text
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

```text
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](https://doc.rust-lang.org/cargo/reference/environment-variables.html):
> 
> `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](https://doc.rust-lang.org/cargo/reference/config.html#buildrustc-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.