Repository: tauri-apps/tauri (language: rust)

Issue:
[bug] cargo tauri dev command doesn't respect .cargo/config.toml [env] section settings
### Describe the bug

If you set environment variable through cargo `config.toml` file, like below, `cargo tauri dev` doesn't add it to the target app.

**.cargo/config.toml**
```toml
[env]
FOO = "bar"
```

### Reproduction

1. preprare barebone  tauri-app
```shell
cargo install create-tauri-app --locked
cargo create-tauri-app
```

2. edit  **tauri-app/src-tauri/src/main.rs**
```rust
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

fn main() {

    match std::env::var("FOO") {
        Ok(v) => println!("FOO={v}"),
        Err(e) => println!("Error: {:?}",e),
    }

    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

3. edit **.cargo/config.toml**
```toml
[env]
FOO = "bar"
```

4. run `cargo tauri dev`

> /r/tauri-app> cargo tauri dev
        Info Watching tauri-app/src-tauri for changes...
   Compiling tauri-app v0.0.0 (tauri-app/src-tauri)
    Finished dev [unoptimized + debuginfo] target(s) in 2.55s
Error: NotPresent

### Expected behavior

Running command `cargo tauri dev`  should print below.

> /r/tauri-app> cargo tauri dev
        Info Watching tauri-app/src-tauri for changes...
   Compiling tauri-app v0.0.0 (tauri-app/src-tauri)
    Finished dev [unoptimized + debuginfo] target(s) in 2.55s
FOO=bar

### Platform and versions

```text
[✔] Environment
    - OS: Mac OS 10.14.5 X64
    ✔ Xcode Command Line Tools: installed
    ✔ rustc: 1.70.0 (90c541806 2023-05-31)
    ✔ cargo: 1.70.0 (ec8a8a0ca 2023-04-25)
    ✔ rustup: 1.26.0 (5af9b9484 2023-04-05)
    ✔ Rust toolchain: stable-x86_64-apple-darwin (environment override by RUSTUP_TOOLCHAIN)
    - node: 20.5.0
    - yarn: 1.22.11
    - npm: 9.8.0

[-] Packages
    - tauri [RUST]: 1.5.2
    - tauri-build [RUST]: 1.5.0
    - wry [RUST]: 0.24.4
    - tao [RUST]: 0.16.5
    - tauri-cli [RUST]: 1.5.6
    - @tauri-apps/api : not installed!
    - @tauri-apps/cli [NPM]: 1.5.6

[-] App
    - build-type: bundle
    - CSP: unset
    - distDir: ../src
    - devPath: ../src
```


### Stack trace

_No response_

### Additional context

If we run tauri-app by running command `cd src-tauri; cargo run`, the app correctly prints the environment variable set by `.cargo/config.toml`.


Discussion / hints:
Similar-ish to this one https://github.com/tauri-apps/tauri/issues/4724#issuecomment-1548974637 (Only commenting this so we fix both at the same time).

p.s. thanks for the report. and using `cargo run` in the meantime is fine btw, for simple apps the only thing you'll loose is the `beforeDevCommand` which you can just run in a seperate terminal yourself. What i mean is that there shouldn't be any noteworthy runtime consequences (again, for simple apps, once you add sidecars and resources etc it can get tricky).

Fix patch:
diff --git a/crates/tauri-bundler/src/bundle/linux/appimage.rs b/crates/tauri-bundler/src/bundle/linux/appimage.rs
index 02fe710d394a..533f3fbe0a90 100644
--- a/crates/tauri-bundler/src/bundle/linux/appimage.rs
+++ b/crates/tauri-bundler/src/bundle/linux/appimage.rs
@@ -12,7 +12,6 @@ use crate::{
 use anyhow::Context;
 use std::{
   fs,
-  io::Write,
   path::{Path, PathBuf},
   process::Command,
 };
diff --git a/crates/tauri-bundler/src/bundle/macos/dmg/mod.rs b/crates/tauri-bundler/src/bundle/macos/dmg/mod.rs
index 7df50a055276..747b3963c09c 100644
--- a/crates/tauri-bundler/src/bundle/macos/dmg/mod.rs
+++ b/crates/tauri-bundler/src/bundle/macos/dmg/mod.rs
@@ -178,7 +178,6 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
     if let Some(value) = env::var_os("CI") {
       if value == "true" {
         bundle_dmg_cmd.arg("--skip-jenkins");
-      } else {
       }
     }
   }
diff --git a/crates/tauri-cli/src/dev.rs b/crates/tauri-cli/src/dev.rs
index fdeaabe9cea0..09e7a4b67ad5 100644
--- a/crates/tauri-cli/src/dev.rs
+++ b/crates/tauri-cli/src/dev.rs
@@ -10,7 +10,7 @@ use crate::{
       get as get_config, reload as reload_config, BeforeDevCommand, ConfigHandle, FrontendDist,
     },
   },
-  interface::{AppInterface, DevProcess, ExitReason, Interface},
+  interface::{AppInterface, ExitReason, Interface},
   CommandExt, ConfigValue, Result,
 };
 
@@ -338,35 +338,8 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand
   Ok(())
 }
 
-pub fn wait_dev_process<
-  C: DevProcess + Send + 'static,
-  F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static,
->(
-  child: C,
-  on_exit: F,
-) {
-  std::thread::spawn(move || {
-    let code = child
-      .wait()
-      .ok()
-      .and_then(|status| status.code())
-      .or(Some(1));
-    on_exit(
-      code,
-      if child.manually_killed_process() {
-        ExitReason::TriggeredKill
-      } else {
-        ExitReason::NormalExit
-      },
-    );
-  });
-}
-
 pub fn on_app_exit(code: Option<i32>, reason: ExitReason, exit_on_panic: bool, no_watch: bool) {
-  if no_watch
-    || (!matches!(reason, ExitReason::TriggeredKill)
-      && (exit_on_panic || matches!(reason, ExitReason::NormalExit)))
-  {
+  if no_watch || exit_on_panic || matches!(reason, ExitReason::NormalExit) {
     kill_before_dev_process();
     exit(code.unwrap_or(0));
   }
diff --git a/crates/tauri-cli/src/interface/mod.rs b/crates/tauri-cli/src/interface/mod.rs
index 3c3bc7b30291..1b2ad7be8a56 100644
--- a/crates/tauri-cli/src/interface/mod.rs
+++ b/crates/tauri-cli/src/interface/mod.rs
@@ -20,7 +20,10 @@ pub use rust::{MobileOptions, Options, Rust as AppInterface};
 pub trait DevProcess {
   fn kill(&self) -> std::io::Result<()>;
   fn try_wait(&self) -> std::io::Result<Option<ExitStatus>>;
+  // TODO:
+  #[allow(unused)]
   fn wait(&self) -> std::io::Result<ExitStatus>;
+  #[allow(unused)]
   fn manually_killed_process(&self) -> bool;
 }
 
@@ -84,7 +87,7 @@ pub trait AppSettings {
 #[derive(Debug)]
 pub enum ExitReason {
   /// Killed manually.
-  TriggeredKill,
+  // TriggeredKill,
   /// App compilation failed.
   CompilationFailed,
   /// Regular exit.
diff --git a/crates/tauri-cli/src/interface/rust.rs b/crates/tauri-cli/src/interface/rust.rs
index 918729a30862..b6542466da18 100644
--- a/crates/tauri-cli/src/interface/rust.rs
+++ b/crates/tauri-cli/src/interface/rust.rs
@@ -363,6 +363,7 @@ fn lookup<F: FnMut(FileType, PathBuf)>(dir: &Path, mut f: F) {
 }
 
 fn shared_options(
+  desktop_dev: bool,
   mobile: bool,
   args: &mut Vec<String>,
   features: &mut Option<Vec<String>>,
@@ -374,7 +375,9 @@ fn shared_options(
       .get_or_insert(Vec::new())
       .push("tauri/rustls-tls".into());
   } else {
-    args.push("--bins".into());
+    if !desktop_dev {
+      args.push("--bins".into());
+    }
     let all_features = app_settings
       .manifest
       .lock()
@@ -408,7 +411,7 @@ fn dev_options(
   }
   *args = dev_args;
 
-  shared_options(mobile, args, features, app_settings);
+  shared_options(true, mobile, args, features, app_settings);
 
   if !args.contains(&"--no-default-features".into()) {
     let manifest_features = app_settings.manifest.lock().unwrap().features();
@@ -488,7 +491,7 @@ impl Rust {
     features
       .get_or_insert(Vec::new())
       .push("tauri/custom-protocol".into());
-    shared_options(mobile, args, features, &self.app_settings);
+    shared_options(false, mobile, args, features, &self.app_settings);
   }
 
   fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
@@ -502,8 +505,6 @@ impl Rust {
       run_args,
       &mut self.available_targets,
       self.config_features.clone(),
-      &self.app_settings,
-      self.main_binary_name.clone(),
       on_exit,
     )
     .map(|c| Box::new(c) as Box<dyn DevProcess + Send>)
diff --git a/crates/tauri-cli/src/interface/rust/desktop.rs b/crates/tauri-cli/src/interface/rust/desktop.rs
index a1825fff99cd..70fbbea511c6 100644
--- a/crates/tauri-cli/src/interface/rust/desktop.rs
+++ b/crates/tauri-cli/src/interface/rust/desktop.rs
@@ -20,39 +20,22 @@ use std::{
 
 pub struct DevChild {
   manually_killed_app: Arc<AtomicBool>,
-  build_child: Option<Arc<SharedChild>>,
-  app_child: Arc<Mutex<Option<Arc<SharedChild>>>>,
+  dev_child: Arc<SharedChild>,
 }
 
 impl DevProcess for DevChild {
   fn kill(&self) -> std::io::Result<()> {
-    if let Some(child) = &*self.app_child.lock().unwrap() {
-      child.kill()?;
-    } else if let Some(child) = &self.build_child {
-      child.kill()?;
-    }
+    self.dev_child.kill()?;
     self.manually_killed_app.store(true, Ordering::Relaxed);
     Ok(())
   }
 
   fn try_wait(&self) -> std::io::Result<Option<ExitStatus>> {
-    if let Some(child) = &*self.app_child.lock().unwrap() {
-      child.try_wait()
-    } else if let Some(child) = &self.build_child {
-      child.try_wait()
-    } else {
-      unreachable!()
-    }
+    self.dev_child.try_wait()
   }
 
   fn wait(&self) -> std::io::Result<ExitStatus> {
-    if let Some(child) = &*self.app_child.lock().unwrap() {
-      child.wait()
-    } else if let Some(child) = &self.build_child {
-      child.wait()
-    } else {
-      unreachable!()
-    }
+    self.dev_child.wait()
   }
 
   fn manually_killed_process(&self) -> bool {
@@ -65,122 +48,12 @@ pub fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
   run_args: Vec<String>,
   available_targets: &mut Option<Vec<RustupTarget>>,
   config_features: Vec<String>,
-  app_settings: &RustAppSettings,
-  main_binary_name: Option<String>,
   on_exit: F,
 ) -> crate::Result<impl DevProcess> {
-  let bin_path = app_settings.app_binary_path(&options)?;
-
-  let manually_killed_app = Arc::new(AtomicBool::default());
-  let manually_killed_app_ = manually_killed_app.clone();
-  let app_child = Arc::new(Mutex::new(None));
-  let app_child_ = app_child.clone();
-
-  let build_child = build_dev_app(
-    options,
-    available_targets,
-    config_features,
-    move |status, reason| {
-      if status == Some(0) {
-        let main_binary_name = main_binary_name.as_deref();
-        let bin_path = rename_app(bin_path, main_binary_name).expect("failed to rename app");
-
-        let mut app = Command::new(bin_path);
-        app.stdout(os_pipe::dup_stdout().unwrap());
-        app.stderr(os_pipe::dup_stderr().unwrap());
-        app.args(run_args);
-        let app_child = Arc::new(SharedChild::spawn(&mut app).unwrap());
-        crate::dev::wait_dev_process(
-          DevChild {
-            manually_killed_app: manually_killed_app_,
-            build_child: None,
-            app_child: Arc::new(Mutex::new(Some(app_child.clone()))),
-          },
-          on_exit,
-        );
-
-        app_child_.lock().unwrap().replace(app_child);
-      } else {
-        on_exit(
-          status,
-          if manually_killed_app_.load(Ordering::Relaxed) {
-            ExitReason::TriggeredKill
-          } else {
-            reason
-          },
-        );
-      }
-    },
-  )?;
-
-  Ok(DevChild {
-    manually_killed_app,
-    build_child: Some(build_child),
-    app_child,
-  })
-}
-
-pub fn build(
-  options: Options,
-  app_settings: &RustAppSettings,
-  available_targets: &mut Option<Vec<RustupTarget>>,
-  config_features: Vec<String>,
-  main_binary_name: Option<&str>,
-) -> crate::Result<PathBuf> {
-  let out_dir = app_settings.out_dir(&options)?;
-  let bin_path = app_settings.app_binary_path(&options)?;
-
-  if !std::env::var("STATIC_VCRUNTIME").map_or(false, |v| v == "false") {
-    std::env::set_var("STATIC_VCRUNTIME", "true");
-  }
-
-  if options.target == Some("universal-apple-darwin".into()) {
-    std::fs::create_dir_all(&out_dir).with_context(|| "failed to create project out directory")?;
-
-    let bin_name = bin_path.file_stem().unwrap();
-
-    let mut lipo_cmd = Command::new("lipo");
-    lipo_cmd
-      .arg("-create")
-      .arg("-output")
-      .arg(out_dir.join(bin_name));
-    for triple in ["aarch64-apple-darwin", "x86_64-apple-darwin"] {
-      let mut options = options.clone();
-      options.target.replace(triple.into());
-
-      let triple_out_dir = app_settings
-        .out_dir(&options)
-        .with_context(|| format!("failed to get {triple} out dir"))?;
-
-      build_production_app(options, available_targets, config_features.clone())
-        .with_context(|| format!("failed to build {triple} binary"))?;
-
-      lipo_cmd.arg(triple_out_dir.join(bin_name));
-    }
-
-    let lipo_status = lipo_cmd.output_ok()?.status;
-    if !lipo_status.success() {
-      return Err(anyhow::anyhow!(format!(
-        "Result of `lipo` command was unsuccessful: {lipo_status}. (Is `lipo` installed?)"
-      )));
-    }
-  } else {
-    build_production_app(options, available_targets, config_features)
-      .with_context(|| "failed to build app")?;
-  }
-
-  rename_app(bin_path, main_binary_name)
-}
+  let mut dev_cmd = cargo_command(true, options, available_targets, config_features)?;
+  let runner = dev_cmd.get_program().to_string_lossy().into_owned();
 
-fn build_dev_app<F: FnOnce(Option<i32>, ExitReason) + Send + 'static>(
-  options: Options,
-  available_targets: &mut Option<Vec<RustupTarget>>,
-  config_features: Vec<String>,
-  on_exit: F,
-) -> crate::Result<Arc<SharedChild>> {
-  let mut build_cmd = build_command(options, available_targets, config_features)?;
-  let runner = build_cmd.get_program().to_string_lossy().into_owned();
-  build_cmd
+  dev_cmd
     .env(
       "CARGO_TERM_PROGRESS_WIDTH",
       terminal::stderr_width()
@@ -195,13 +68,17 @@ fn build_dev_app<F: FnOnce(Option<i32>, ExitReason) + Send + 'static>(
         .to_string(),
     )
     .env("CARGO_TERM_PROGRESS_WHEN", "always");
-  build_cmd.arg("--color");
-  build_cmd.arg("always");
+  dev_cmd.arg("--color");
+  dev_cmd.arg("always");
 
-  build_cmd.stdout(os_pipe::dup_stdout()?);
-  build_cmd.stderr(Stdio::piped());
+  // TODO: double check this
+  dev_cmd.stdout(os_pipe::dup_stdout()?);
+  dev_cmd.stderr(Stdio::piped());
 
-  let build_child = match SharedChild::spawn(&mut build_cmd) {
+  dev_cmd.arg("--");
+  dev_cmd.args(run_args);
+
+  let dev_child = match SharedChild::spawn(&mut dev_cmd) {
     Ok(c) => Ok(c),
     Err(e) if e.kind() == ErrorKind::NotFound => Err(anyhow::anyhow!(
       "`{}` command not found.{}",
@@ -214,9 +91,9 @@ fn build_dev_app<F: FnOnce(Option<i32>, ExitReason) + Send + 'static>(
     )),
     Err(e) => Err(e.into()),
   }?;
-  let build_child = Arc::new(build_child);
-  let build_child_stderr = build_child.take_stderr().unwrap();
-  let mut stderr = BufReader::new(build_child_stderr);
+  let dev_child = Arc::new(dev_child);
+  let dev_child_stderr = dev_child.take_stderr().unwrap();
+  let mut stderr = BufReader::new(dev_child_stderr);
   let stderr_lines = Arc::new(Mutex::new(Vec::new()));
   let stderr_lines_ = stderr_lines.clone();
   std::thread::spawn(move || {
@@ -232,10 +109,9 @@ fn build_dev_app<F: FnOnce(Option<i32>, ExitReason) + Send + 'static>(
       lines.push(String::from_utf8_lossy(&buf).into_owned());
     }
   });
-
-  let build_child_ = build_child.clone();
+  let dev_child_ = dev_child.clone();
   std::thread::spawn(move || {
-    let status = build_child_.wait().expect("failed to build app");
+    let status = dev_child_.wait().expect("failed to build app");
 
     if status.success() {
       on_exit(status.code(), ExitReason::NormalExit);
@@ -259,7 +135,65 @@ fn build_dev_app<F: FnOnce(Option<i32>, ExitReason) + Send + 'static>(
     }
   });
 
-  Ok(build_child)
+  // TODO: remove this and DevChild (requires refactor for code shared between mobile and desktop)
+  let manually_killed_app = Arc::new(AtomicBool::default());
+
+  Ok(DevChild {
+    manually_killed_app,
+    dev_child,
+  })
+}
+
+pub fn build(
+  options: Options,
+  app_settings: &RustAppSettings,
+  available_targets: &mut Option<Vec<RustupTarget>>,
+  config_features: Vec<String>,
+  main_binary_name: Option<&str>,
+) -> crate::Result<PathBuf> {
+  let out_dir = app_settings.out_dir(&options)?;
+  let bin_path = app_settings.app_binary_path(&options)?;
+
+  if !std::env::var("STATIC_VCRUNTIME").map_or(false, |v| v == "false") {
+    std::env::set_var("STATIC_VCRUNTIME", "true");
+  }
+
+  if options.target == Some("universal-apple-darwin".into()) {
+    std::fs::create_dir_all(&out_dir).with_context(|| "failed to create project out directory")?;
+
+    let bin_name = bin_path.file_stem().unwrap();
+
+    let mut lipo_cmd = Command::new("lipo");
+    lipo_cmd
+      .arg("-create")
+      .arg("-output")
+      .arg(out_dir.join(bin_name));
+    for triple in ["aarch64-apple-darwin", "x86_64-apple-darwin"] {
+      let mut options = options.clone();
+      options.target.replace(triple.into());
+
+      let triple_out_dir = app_settings
+        .out_dir(&options)
+        .with_context(|| format!("failed to get {triple} out dir"))?;
+
+      build_production_app(options, available_targets, config_features.clone())
+        .with_context(|| format!("failed to build {triple} binary"))?;
+
+      lipo_cmd.arg(triple_out_dir.join(bin_name));
+    }
+
+    let lipo_status = lipo_cmd.output_ok()?.status;
+    if !lipo_status.success() {
+      return Err(anyhow::anyhow!(format!(
+        "Result of `lipo` command was unsuccessful: {lipo_status}. (Is `lipo` installed?)"
+      )));
+    }
+  } else {
+    build_production_app(options, available_targets, config_features)
+      .with_context(|| "failed to build app")?;
+  }
+
+  rename_app(bin_path, main_binary_name)
 }
 
 fn build_production_app(
@@ -267,7 +201,7 @@ fn build_production_app(
   available_targets: &mut Option<Vec<RustupTarget>>,
   config_features: Vec<String>,
 ) -> crate::Result<()> {
-  let mut build_cmd = build_command(options, available_targets, config_features)?;
+  let mut build_cmd = cargo_command(false, options, available_targets, config_features)?;
   let runner = build_cmd.get_program().to_string_lossy().into_owned();
   match build_cmd.piped() {
     Ok(status) if status.success() => Ok(()),
@@ -285,13 +219,17 @@ fn build_production_app(
   }
 }
 
-fn build_command(
+fn cargo_command(
+  dev: bool,
   options: Options,
   available_targets: &mut Option<Vec<RustupTarget>>,
   config_features: Vec<String>,
 ) -> crate::Result<Command> {
   let runner = options.runner.unwrap_or_else(|| "cargo".into());
 
+  let mut build_cmd = Command::new(runner);
+  build_cmd.arg(if dev { "run" } else { "build" });
+
   if let Some(target) = &options.target {
     if available_targets.is_none() {
       *available_targets = fetch_available_targets();
@@ -299,33 +237,26 @@ fn build_command(
     validate_target(available_targets, target)?;
   }
 
-  let mut args = Vec::new();
-  if !options.args.is_empty() {
-    args.extend(options.args);
-  }
+  build_cmd.args(&options.args);
 
   let mut features = config_features;
   if let Some(f) = options.features {
     features.extend(f);
   }
   if !features.is_empty() {
-    args.push("--features".into());
-    args.push(features.join(","));
+    build_cmd.arg("--features");
+    build_cmd.arg(features.join(","));
   }
 
-  if !options.debug && !args.contains(&"--profile".to_string()) {
-    args.push("--release".into());
+  if !options.debug && !options.args.contains(&"--profile".to_string()) {
+    build_cmd.arg("--release");
   }
 
   if let Some(target) = options.target {
-    args.push("--target".into());
-    args.push(target);
+    build_cmd.arg("--target");
+    build_cmd.arg(target);
   }
 
-  let mut build_cmd = Command::new(runner);
-  build_cmd.arg("build");
-  build_cmd.args(args);
-
   Ok(build_cmd)
 }
 


Test patch: