Prechádzať zdrojové kódy

Merge branch 'dev' into next

Lucas Nogueira 3 rokov pred
rodič
commit
3d992a8899

+ 6 - 0
.changes/cargo-config-target.md

@@ -0,0 +1,6 @@
+---
+"cli.rs": patch
+"cli.js": patch
+---
+
+Check if the default build target is set in the Cargo configuration.

+ 6 - 0
.changes/cli-detect-target-dir.md

@@ -0,0 +1,6 @@
+---
+"cli.rs": "patch"
+"cli.js": "patch"
+---
+
+Use `cargo metadata` to detect the workspace root and target directory.

+ 11 - 5
core/tauri-runtime-wry/src/lib.rs

@@ -2628,12 +2628,12 @@ fn handle_event_loop<T: UserEvent>(
         for handler in handlers {
           handler(&event);
         }
+      }
 
-        let global_listeners = system_tray_manager.global_listeners.lock().unwrap();
-        let global_listeners_iter = global_listeners.iter();
-        for global_listener in global_listeners_iter {
-          global_listener(tray_id, &event);
-        }
+      let global_listeners = system_tray_manager.global_listeners.lock().unwrap();
+      let global_listeners_iter = global_listeners.iter();
+      for global_listener in global_listeners_iter {
+        global_listener(tray_id, &event);
       }
     }
     #[cfg(all(desktop, feature = "system-tray"))]
@@ -2662,6 +2662,12 @@ fn handle_event_loop<T: UserEvent>(
           handler(&event);
         }
       }
+
+      let global_listeners = system_tray_manager.global_listeners.lock().unwrap();
+      let global_listeners_iter = global_listeners.iter();
+      for global_listener in global_listeners_iter {
+        global_listener(id.0, &event);
+      }
     }
     Event::WindowEvent {
       event, window_id, ..

+ 1 - 1
core/tauri/build.rs

@@ -20,7 +20,7 @@ fn has_feature(feature: &str) -> bool {
     .unwrap()
     .push(feature.to_string());
 
-  // when a feature is enabled, Cargo sets the `CARGO_FEATURE_<name` env var to 1
+  // when a feature is enabled, Cargo sets the `CARGO_FEATURE_<name>` env var to 1
   // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
   std::env::var(format!("CARGO_FEATURE_{}", AsShoutySnakeCase(feature)))
     .map(|x| x == "1")

+ 3 - 3
examples/api/yarn.lock

@@ -960,9 +960,9 @@ unconfig@^0.3.4:
     jiti "^1.13.0"
 
 undici@^5.2.0:
-  version "5.8.0"
-  resolved "https://registry.yarnpkg.com/undici/-/undici-5.8.0.tgz#dec9a8ccd90e5a1d81d43c0eab6503146d649a4f"
-  integrity sha512-1F7Vtcez5w/LwH2G2tGnFIihuWUlc58YidwLiCv+jR2Z50x0tNXpRRw7eOIJ+GvqCqIkg9SB7NWAJ/T9TLfv8Q==
+  version "5.9.1"
+  resolved "https://registry.yarnpkg.com/undici/-/undici-5.9.1.tgz#fc9fd85dd488f965f153314a63d9426a11f3360b"
+  integrity sha512-6fB3a+SNnWEm4CJbgo0/CWR8RGcOCQP68SF4X0mxtYTq2VNN8T88NYrWVBAeSX+zb7bny2dx2iYhP3XHi00omg==
 
 unocss@^0.39.3:
   version "0.39.3"

+ 4 - 4
tooling/api/package.json

@@ -49,9 +49,9 @@
     "@rollup/plugin-node-resolve": "13.3.0",
     "@rollup/plugin-sucrase": "4.0.4",
     "@rollup/plugin-typescript": "8.3.4",
-    "@typescript-eslint/eslint-plugin": "5.33.0",
-    "@typescript-eslint/parser": "5.33.0",
-    "eslint": "8.21.0",
+    "@typescript-eslint/eslint-plugin": "5.33.1",
+    "@typescript-eslint/parser": "5.33.1",
+    "eslint": "8.22.0",
     "eslint-config-prettier": "8.5.0",
     "eslint-config-standard-with-typescript": "21.0.1",
     "eslint-plugin-import": "2.26.0",
@@ -61,7 +61,7 @@
     "prettier": "2.7.1",
     "regenerator-runtime": "0.13.9",
     "rimraf": "3.0.2",
-    "rollup": "2.77.2",
+    "rollup": "2.78.0",
     "rollup-plugin-terser": "7.0.2",
     "tslib": "2.4.0",
     "typedoc": "0.23.10",

+ 56 - 56
tooling/api/yarn.lock

@@ -1359,14 +1359,14 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/eslint-plugin@5.33.0":
-  version "5.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz#059798888720ec52ffa96c5f868e31a8f70fa3ec"
-  integrity sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==
-  dependencies:
-    "@typescript-eslint/scope-manager" "5.33.0"
-    "@typescript-eslint/type-utils" "5.33.0"
-    "@typescript-eslint/utils" "5.33.0"
+"@typescript-eslint/eslint-plugin@5.33.1":
+  version "5.33.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.1.tgz#c0a480d05211660221eda963cc844732fe9b1714"
+  integrity sha512-S1iZIxrTvKkU3+m63YUOxYPKaP+yWDQrdhxTglVDVEVBf+aCSw85+BmJnyUaQQsk5TXFG/LpBu9fa+LrAQ91fQ==
+  dependencies:
+    "@typescript-eslint/scope-manager" "5.33.1"
+    "@typescript-eslint/type-utils" "5.33.1"
+    "@typescript-eslint/utils" "5.33.1"
     debug "^4.3.4"
     functional-red-black-tree "^1.0.1"
     ignore "^5.2.0"
@@ -1374,14 +1374,14 @@
     semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/parser@5.33.0":
-  version "5.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.33.0.tgz#26ec3235b74f0667414613727cb98f9b69dc5383"
-  integrity sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==
+"@typescript-eslint/parser@5.33.1":
+  version "5.33.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.33.1.tgz#e4b253105b4d2a4362cfaa4e184e2d226c440ff3"
+  integrity sha512-IgLLtW7FOzoDlmaMoXdxG8HOCByTBXrB1V2ZQYSEV1ggMmJfAkMWTwUjjzagS6OkfpySyhKFkBw7A9jYmcHpZA==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.33.0"
-    "@typescript-eslint/types" "5.33.0"
-    "@typescript-eslint/typescript-estree" "5.33.0"
+    "@typescript-eslint/scope-manager" "5.33.1"
+    "@typescript-eslint/types" "5.33.1"
+    "@typescript-eslint/typescript-estree" "5.33.1"
     debug "^4.3.4"
 
 "@typescript-eslint/parser@^4.0.0":
@@ -1402,20 +1402,20 @@
     "@typescript-eslint/types" "4.25.0"
     "@typescript-eslint/visitor-keys" "4.25.0"
 
-"@typescript-eslint/scope-manager@5.33.0":
-  version "5.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz#509d7fa540a2c58f66bdcfcf278a3fa79002e18d"
-  integrity sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==
+"@typescript-eslint/scope-manager@5.33.1":
+  version "5.33.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.33.1.tgz#8d31553e1b874210018ca069b3d192c6d23bc493"
+  integrity sha512-8ibcZSqy4c5m69QpzJn8XQq9NnqAToC8OdH/W6IXPXv83vRyEDPYLdjAlUx8h/rbusq6MkW4YdQzURGOqsn3CA==
   dependencies:
-    "@typescript-eslint/types" "5.33.0"
-    "@typescript-eslint/visitor-keys" "5.33.0"
+    "@typescript-eslint/types" "5.33.1"
+    "@typescript-eslint/visitor-keys" "5.33.1"
 
-"@typescript-eslint/type-utils@5.33.0":
-  version "5.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz#92ad1fba973c078d23767ce2d8d5a601baaa9338"
-  integrity sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==
+"@typescript-eslint/type-utils@5.33.1":
+  version "5.33.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.33.1.tgz#1a14e94650a0ae39f6e3b77478baff002cec4367"
+  integrity sha512-X3pGsJsD8OiqhNa5fim41YtlnyiWMF/eKsEZGsHID2HcDqeSC5yr/uLOeph8rNF2/utwuI0IQoAK3fpoxcLl2g==
   dependencies:
-    "@typescript-eslint/utils" "5.33.0"
+    "@typescript-eslint/utils" "5.33.1"
     debug "^4.3.4"
     tsutils "^3.21.0"
 
@@ -1424,10 +1424,10 @@
   resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.25.0.tgz#0e444a5c5e3c22d7ffa5e16e0e60510b3de5af87"
   integrity sha512-+CNINNvl00OkW6wEsi32wU5MhHti2J25TJsJJqgQmJu3B3dYDBcmOxcE5w9cgoM13TrdE/5ND2HoEnBohasxRQ==
 
-"@typescript-eslint/types@5.33.0":
-  version "5.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.33.0.tgz#d41c584831805554b063791338b0220b613a275b"
-  integrity sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==
+"@typescript-eslint/types@5.33.1":
+  version "5.33.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.33.1.tgz#3faef41793d527a519e19ab2747c12d6f3741ff7"
+  integrity sha512-7K6MoQPQh6WVEkMrMW5QOA5FO+BOwzHSNd0j3+BlBwd6vtzfZceJ8xJ7Um2XDi/O3umS8/qDX6jdy2i7CijkwQ==
 
 "@typescript-eslint/typescript-estree@4.25.0":
   version "4.25.0"
@@ -1442,28 +1442,28 @@
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/typescript-estree@5.33.0":
-  version "5.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz#02d9c9ade6f4897c09e3508c27de53ad6bfa54cf"
-  integrity sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==
+"@typescript-eslint/typescript-estree@5.33.1":
+  version "5.33.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.1.tgz#a573bd360790afdcba80844e962d8b2031984f34"
+  integrity sha512-JOAzJ4pJ+tHzA2pgsWQi4804XisPHOtbvwUyqsuuq8+y5B5GMZs7lI1xDWs6V2d7gE/Ez5bTGojSK12+IIPtXA==
   dependencies:
-    "@typescript-eslint/types" "5.33.0"
-    "@typescript-eslint/visitor-keys" "5.33.0"
+    "@typescript-eslint/types" "5.33.1"
+    "@typescript-eslint/visitor-keys" "5.33.1"
     debug "^4.3.4"
     globby "^11.1.0"
     is-glob "^4.0.3"
     semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/utils@5.33.0":
-  version "5.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.33.0.tgz#46797461ce3146e21c095d79518cc0f8ec574038"
-  integrity sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==
+"@typescript-eslint/utils@5.33.1":
+  version "5.33.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.33.1.tgz#171725f924fe1fe82bb776522bb85bc034e88575"
+  integrity sha512-uphZjkMaZ4fE8CR4dU7BquOV6u0doeQAr8n6cQenl/poMaIyJtBu8eys5uk6u5HiDH01Mj5lzbJ5SfeDz7oqMQ==
   dependencies:
     "@types/json-schema" "^7.0.9"
-    "@typescript-eslint/scope-manager" "5.33.0"
-    "@typescript-eslint/types" "5.33.0"
-    "@typescript-eslint/typescript-estree" "5.33.0"
+    "@typescript-eslint/scope-manager" "5.33.1"
+    "@typescript-eslint/types" "5.33.1"
+    "@typescript-eslint/typescript-estree" "5.33.1"
     eslint-scope "^5.1.1"
     eslint-utils "^3.0.0"
 
@@ -1475,12 +1475,12 @@
     "@typescript-eslint/types" "4.25.0"
     eslint-visitor-keys "^2.0.0"
 
-"@typescript-eslint/visitor-keys@5.33.0":
-  version "5.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz#fbcbb074e460c11046e067bc3384b5d66b555484"
-  integrity sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==
+"@typescript-eslint/visitor-keys@5.33.1":
+  version "5.33.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.1.tgz#0155c7571c8cd08956580b880aea327d5c34a18b"
+  integrity sha512-nwIxOK8Z2MPWltLKMLOEZwmfBZReqUdbEoHQXeCpa+sRVARe5twpJGHCB4dk9903Yaf0nMAlGbQfaAH92F60eg==
   dependencies:
-    "@typescript-eslint/types" "5.33.0"
+    "@typescript-eslint/types" "5.33.1"
     eslint-visitor-keys "^3.3.0"
 
 acorn-jsx@^5.3.2:
@@ -2030,10 +2030,10 @@ eslint-visitor-keys@^3.3.0:
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
   integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
 
-eslint@8.21.0:
-  version "8.21.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.21.0.tgz#1940a68d7e0573cef6f50037addee295ff9be9ef"
-  integrity sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==
+eslint@8.22.0:
+  version "8.22.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.22.0.tgz#78fcb044196dfa7eef30a9d65944f6f980402c48"
+  integrity sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==
   dependencies:
     "@eslint/eslintrc" "^1.3.0"
     "@humanwhocodes/config-array" "^0.10.4"
@@ -3105,10 +3105,10 @@ rollup-plugin-terser@7.0.2:
     serialize-javascript "^4.0.0"
     terser "^5.0.0"
 
-rollup@2.77.2:
-  version "2.77.2"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.2.tgz#6b6075c55f9cc2040a5912e6e062151e42e2c4e3"
-  integrity sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g==
+rollup@2.78.0:
+  version "2.78.0"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.78.0.tgz#00995deae70c0f712ea79ad904d5f6b033209d9e"
+  integrity sha512-4+YfbQC9QEVvKTanHhIAFVUFSRsezvQF8vFOJwtGfb9Bb+r014S+qryr9PSmw8x6sMnPkmFBGAvIFVQxvJxjtg==
   optionalDependencies:
     fsevents "~2.3.2"
 

+ 1 - 1
tooling/cli/node/package.json

@@ -37,7 +37,7 @@
     }
   },
   "devDependencies": {
-    "@napi-rs/cli": "2.11.1",
+    "@napi-rs/cli": "2.11.4",
     "cross-env": "7.0.3",
     "cross-spawn": "7.0.3",
     "fs-extra": "10.1.0",

+ 4 - 4
tooling/cli/node/yarn.lock

@@ -669,10 +669,10 @@
     "@jridgewell/resolve-uri" "^3.0.3"
     "@jridgewell/sourcemap-codec" "^1.4.10"
 
-"@napi-rs/cli@2.11.1":
-  version "2.11.1"
-  resolved "https://registry.yarnpkg.com/@napi-rs/cli/-/cli-2.11.1.tgz#1fe30c3b12baa240dbaf8fcdeed53588d8340a15"
-  integrity sha512-JRfUgQTrogU/YrOoOgrZdnSopZemmT9ohet8SMxZXQZFtSIjOTSBQOC+vMrqKIfZmSKTtxE8cNIIlscHSP7PCA==
+"@napi-rs/cli@2.11.4":
+  version "2.11.4"
+  resolved "https://registry.yarnpkg.com/@napi-rs/cli/-/cli-2.11.4.tgz#28bea72dd6758ce7fc94d515493c5bd1ef00b6ec"
+  integrity sha512-rjU651owB4GJetO3pnu3B8TyVM3Fis3AYb+U16bKxYyykp81S+dJlIgWc8Lc0t55PYbHlBM3hxdgy4pultxMAw==
 
 "@sinclair/typebox@^0.24.1":
   version "0.24.19"

+ 4 - 7
tooling/cli/src/info.rs

@@ -794,13 +794,10 @@ pub fn command(_options: Options) -> Result<()> {
         } else {
           None
         };
-      let lock: Option<CargoLock> = if let Ok(lock_contents) =
-        read_to_string(get_workspace_dir(&tauri_dir).join("Cargo.lock"))
-      {
-        toml::from_str(&lock_contents).ok()
-      } else {
-        None
-      };
+      let lock: Option<CargoLock> = get_workspace_dir()
+        .ok()
+        .and_then(|p| read_to_string(p.join("Cargo.lock")).ok())
+        .and_then(|s| toml::from_str(&s).ok());
 
       for (dep, label) in [
         ("tauri", format!("{} {}", "tauri", "[RUST]".dimmed())),

+ 51 - 97
tooling/cli/src/interface/rust.rs

@@ -8,7 +8,7 @@ use std::{
   fs::{File, FileType},
   io::{Read, Write},
   path::{Path, PathBuf},
-  process::ExitStatus,
+  process::{Command, ExitStatus},
   str::FromStr,
   sync::{
     mpsc::{channel, sync_channel},
@@ -20,7 +20,6 @@ use std::{
 use anyhow::Context;
 #[cfg(target_os = "linux")]
 use heck::ToKebabCase;
-use log::warn;
 use log::{debug, info};
 use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
 use serde::Deserialize;
@@ -35,8 +34,10 @@ use crate::helpers::{
   config::{reload as reload_config, wix_settings, Config},
 };
 
+mod cargo_config;
 mod desktop;
 mod manifest;
+use cargo_config::Config as CargoConfig;
 use manifest::{rewrite_manifest, Manifest};
 
 #[derive(Debug, Default, Clone)]
@@ -305,7 +306,7 @@ impl Rust {
     let process = Arc::new(Mutex::new(child));
     let (tx, rx) = channel();
     let tauri_path = tauri_dir();
-    let workspace_path = get_workspace_dir(&tauri_path);
+    let workspace_path = get_workspace_dir()?;
 
     let watch_folders = if tauri_path == workspace_path {
       vec![tauri_path]
@@ -439,22 +440,12 @@ impl CargoSettings {
   }
 }
 
-#[derive(Deserialize)]
-struct CargoBuildConfig {
-  #[serde(rename = "target-dir")]
-  target_dir: Option<String>,
-}
-
-#[derive(Deserialize)]
-struct CargoConfig {
-  build: Option<CargoBuildConfig>,
-}
-
 pub struct RustAppSettings {
   manifest: Manifest,
   cargo_settings: CargoSettings,
   cargo_package_settings: CargoPackageSettings,
   package_settings: PackageSettings,
+  cargo_config: CargoConfig,
 }
 
 impl AppSettings for RustAppSettings {
@@ -642,11 +633,14 @@ impl RustAppSettings {
       default_run: cargo_package_settings.default_run.clone(),
     };
 
+    let cargo_config = CargoConfig::load(&tauri_dir())?;
+
     Ok(Self {
       manifest,
       cargo_settings,
       cargo_package_settings,
       package_settings,
+      cargo_config,
     })
   }
 
@@ -655,100 +649,60 @@ impl RustAppSettings {
   }
 
   pub fn out_dir(&self, target: Option<String>, debug: bool) -> crate::Result<PathBuf> {
-    let tauri_dir = tauri_dir();
-    let workspace_dir = get_workspace_dir(&tauri_dir);
-    get_target_dir(&workspace_dir, target, !debug)
+    get_target_dir(
+      target
+        .as_deref()
+        .or_else(|| self.cargo_config.build().target()),
+      !debug,
+    )
   }
 }
 
-/// This function determines where 'target' dir is and suffixes it with 'release' or 'debug'
+#[derive(Deserialize)]
+struct CargoMetadata {
+  target_directory: PathBuf,
+  workspace_root: PathBuf,
+}
+
+fn get_cargo_metadata() -> crate::Result<CargoMetadata> {
+  let output = Command::new("cargo")
+    .args(["metadata", "--no-deps", "--format-version", "1"])
+    .current_dir(tauri_dir())
+    .output()?;
+
+  if !output.status.success() {
+    return Err(anyhow::anyhow!(
+      "cargo metadata command exited with a non zero exit code: {}",
+      String::from_utf8(output.stderr)?
+    ));
+  }
+
+  Ok(serde_json::from_slice(&output.stdout)?)
+}
+
+/// This function determines the 'target' directory and suffixes it with 'release' or 'debug'
 /// to determine where the compiled binary will be located.
-fn get_target_dir(
-  project_root_dir: &Path,
-  target: Option<String>,
-  is_release: bool,
-) -> crate::Result<PathBuf> {
-  let mut path: PathBuf = match std::env::var_os("CARGO_TARGET_DIR") {
-    Some(target_dir) => target_dir.into(),
-    None => {
-      let mut root_dir = project_root_dir.to_path_buf();
-      let target_path: Option<PathBuf> = loop {
-        // cargo reads configs under .cargo/config.toml or .cargo/config
-        let mut cargo_config_path = root_dir.join(".cargo/config");
-        if !cargo_config_path.exists() {
-          cargo_config_path = root_dir.join(".cargo/config.toml");
-        }
-        // if the path exists, parse it
-        if cargo_config_path.exists() {
-          let mut config_str = String::new();
-          let mut config_file = File::open(&cargo_config_path)
-            .with_context(|| format!("failed to open {:?}", cargo_config_path))?;
-          config_file
-            .read_to_string(&mut config_str)
-            .with_context(|| "failed to read cargo config file")?;
-          let config: CargoConfig =
-            toml::from_str(&config_str).with_context(|| "failed to parse cargo config file")?;
-          if let Some(build) = config.build {
-            if let Some(target_dir) = build.target_dir {
-              break Some(target_dir.into());
-            }
-          }
-        }
-        if !root_dir.pop() {
-          break None;
-        }
-      };
-      target_path.unwrap_or_else(|| project_root_dir.join("target"))
-    }
-  };
+fn get_target_dir(target: Option<&str>, is_release: bool) -> crate::Result<PathBuf> {
+  let mut path = get_cargo_metadata()
+    .with_context(|| "failed to get cargo metadata")?
+    .target_directory;
 
-  if let Some(ref triple) = target {
+  if let Some(triple) = target {
     path.push(triple);
   }
+
   path.push(if is_release { "release" } else { "debug" });
+
   Ok(path)
 }
 
-/// Walks up the file system, looking for a Cargo.toml file
-/// If one is found before reaching the root, then the current_dir's package belongs to that parent workspace if it's listed on [workspace.members].
-///
-/// If this package is part of a workspace, returns the path to the workspace directory
-/// Otherwise returns the current directory.
-pub fn get_workspace_dir(current_dir: &Path) -> PathBuf {
-  let mut dir = current_dir.to_path_buf();
-  let project_path = dir.clone();
-
-  while dir.pop() {
-    if dir.join("Cargo.toml").exists() {
-      match CargoSettings::load(&dir) {
-        Ok(cargo_settings) => {
-          if let Some(workspace_settings) = cargo_settings.workspace {
-            if let Some(members) = workspace_settings.members {
-              if members.iter().any(|member| {
-                glob::glob(&dir.join(member).to_string_lossy())
-                  .unwrap()
-                  .any(|p| p.unwrap() == project_path)
-              }) {
-                return dir;
-              }
-            }
-          }
-        }
-        Err(e) => {
-          warn!(
-              "Found `{}`, which may define a parent workspace, but \
-            failed to parse it. If this is indeed a parent workspace, undefined behavior may occur: \
-            \n    {:#}",
-              dir.display(),
-              e
-            );
-        }
-      }
-    }
-  }
-
-  // Nothing found walking up the file system, return the starting directory
-  current_dir.to_path_buf()
+/// Executes `cargo metadata` to get the workspace directory.
+pub fn get_workspace_dir() -> crate::Result<PathBuf> {
+  Ok(
+    get_cargo_metadata()
+      .with_context(|| "failed to get cargo metadata")?
+      .workspace_root,
+  )
 }
 
 #[allow(unused_variables)]

+ 123 - 0
tooling/cli/src/interface/rust/cargo_config.rs

@@ -0,0 +1,123 @@
+use anyhow::{Context, Result};
+use serde::Deserialize;
+use std::{
+  fs,
+  path::{Path, PathBuf},
+};
+
+struct PathAncestors<'a> {
+  current: Option<&'a Path>,
+}
+
+impl<'a> PathAncestors<'a> {
+  fn new(path: &'a Path) -> PathAncestors<'a> {
+    PathAncestors {
+      current: Some(path),
+    }
+  }
+}
+
+impl<'a> Iterator for PathAncestors<'a> {
+  type Item = &'a Path;
+
+  fn next(&mut self) -> Option<&'a Path> {
+    if let Some(path) = self.current {
+      self.current = path.parent();
+
+      Some(path)
+    } else {
+      None
+    }
+  }
+}
+
+#[derive(Default, Deserialize)]
+pub struct BuildConfig {
+  target: Option<String>,
+}
+
+#[derive(Deserialize)]
+pub struct ConfigSchema {
+  build: Option<BuildConfig>,
+}
+
+#[derive(Default)]
+pub struct Config {
+  build: BuildConfig,
+}
+
+impl Config {
+  pub fn load(path: &Path) -> Result<Self> {
+    let mut config = Self::default();
+
+    for current in PathAncestors::new(path) {
+      if let Some(path) = get_file_path(&current.join(".cargo"), "config", true)? {
+        let contents = fs::read_to_string(&path)
+          .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
+        let toml: ConfigSchema = toml::from_str(&contents)
+          .with_context(|| format!("could not parse TOML configuration in `{}`", path.display()))?;
+
+        if let Some(target) = toml.build.and_then(|b| b.target) {
+          config.build.target = Some(target);
+          break;
+        }
+      }
+    }
+
+    Ok(config)
+  }
+
+  pub fn build(&self) -> &BuildConfig {
+    &self.build
+  }
+}
+
+impl BuildConfig {
+  pub fn target(&self) -> Option<&str> {
+    self.target.as_deref()
+  }
+}
+
+/// The purpose of this function is to aid in the transition to using
+/// .toml extensions on Cargo's config files, which were historically not used.
+/// Both 'config.toml' and 'credentials.toml' should be valid with or without extension.
+/// When both exist, we want to prefer the one without an extension for
+/// backwards compatibility, but warn the user appropriately.
+fn get_file_path(
+  dir: &Path,
+  filename_without_extension: &str,
+  warn: bool,
+) -> Result<Option<PathBuf>> {
+  let possible = dir.join(filename_without_extension);
+  let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
+
+  if possible.exists() {
+    if warn && possible_with_extension.exists() {
+      // We don't want to print a warning if the version
+      // without the extension is just a symlink to the version
+      // WITH an extension, which people may want to do to
+      // support multiple Cargo versions at once and not
+      // get a warning.
+      let skip_warning = if let Ok(target_path) = fs::read_link(&possible) {
+        target_path == possible_with_extension
+      } else {
+        false
+      };
+
+      if !skip_warning {
+        log::warn!(
+          "Both `{}` and `{}` exist. Using `{}`",
+          possible.display(),
+          possible_with_extension.display(),
+          possible.display()
+        );
+      }
+    }
+
+    Ok(Some(possible))
+  } else if possible_with_extension.exists() {
+    Ok(Some(possible_with_extension))
+  } else {
+    Ok(None)
+  }
+}