Repository: esp-rs/espmonitor (language: rust)

Issue:
When run in workspace, an error occurs.
When I run `cargo espmonitor` in workspace, I get an error and
If I `cd` to the flushed package directory and then run it, it works fine.

I want to run espmonitor by specifying the package from the workspace directory.

```powershell
> cargo espmonitor com4
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error { inner: ErrorInner { kind: Custom, line: None, col: 0, message: "missing field `package`", key: [] 
} }', C:\Users\<user name>\.cargo\registry\src\github.com-1ecc6299db9ec823\cargo-espmonitor-0.9.0\src\main.rs:141:39
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

> cd .\<sub package>\

<subpackage > cargo espmonitor com4
ESPMonitor 0.9.0

Commands:
    CTRL+R    Reset chip
    CTRL+C    Exit

Opening com4 with speed 115200
WARNING: Unable to open flash image \\?\C:\<workspace dir>\<subpackage>\target\xtensa-esp32-espidf\debug\<subpackage>: 指定されたパスが見つかりません。 (os error 3)
Resetting device... done
ets Jul 29 2019 12:21:46

~
~
~

It worked properly.

```


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 7a5b34c..e92294c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -31,6 +31,12 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "anyhow"
+version = "1.0.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
+
 [[package]]
 name = "atty"
 version = "0.2.14"
@@ -73,9 +79,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 name = "cargo-espmonitor"
 version = "0.9.1-alpha.1"
 dependencies = [
+ "anyhow",
  "cargo-project",
  "clap",
  "espmonitor",
+ "serde",
+ "toml 0.5.9",
 ]
 
 [[package]]
@@ -90,7 +99,7 @@ dependencies = [
  "rustc-cfg",
  "serde",
  "serde_derive",
- "toml",
+ "toml 0.4.10",
 ]
 
 [[package]]
@@ -520,6 +529,9 @@ name = "serde"
 version = "1.0.137"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
+dependencies = [
+ "serde_derive",
+]
 
 [[package]]
 name = "serde_derive"
@@ -678,6 +690,15 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "toml"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.0"
diff --git a/cargo-espmonitor/Cargo.toml b/cargo-espmonitor/Cargo.toml
index 9247e5f..2ce8646 100644
--- a/cargo-espmonitor/Cargo.toml
+++ b/cargo-espmonitor/Cargo.toml
@@ -23,6 +23,9 @@ keywords = [
 ]
 
 [dependencies]
+anyhow = "1"
 cargo-project = "0.2"
-espmonitor = { version = "^0.9.1-alpha.1", path = "../espmonitor" }
 clap = { version = "3.1", features = ["derive"] }
+espmonitor = { version = "^0.9.1-alpha.1", path = "../espmonitor" }
+serde = { version = "1", features = ["derive"] }
+toml = "0.5"
diff --git a/cargo-espmonitor/src/main.rs b/cargo-espmonitor/src/main.rs
index 75e94cd..a5d4132 100644
--- a/cargo-espmonitor/src/main.rs
+++ b/cargo-espmonitor/src/main.rs
@@ -15,10 +15,20 @@
 // You should have received a copy of the GNU General Public License
 // along with ESPMonitor.  If not, see <https://www.gnu.org/licenses/>.
 
+#[macro_use]
+extern crate anyhow;
+#[macro_use]
+extern crate serde;
+
 use cargo_project::{Artifact, Profile, Project};
-use clap::{ArgGroup, Parser};
+use clap::Parser;
 use espmonitor::{run, AppArgs, Chip, Framework};
-use std::{error::Error, io, process::Command};
+use std::{
+    ffi::OsString,
+    fs, io,
+    path::{Path, PathBuf},
+    process::Command,
+};
 
 #[derive(Parser)]
 #[clap(name = "cargo")]
@@ -29,14 +39,6 @@ enum Cargo {
 
 #[derive(clap::Args)]
 #[clap(author, version, about)]
-// None of the arguments related to flashing can appear without "--flash", but they aren't required
-// (e.g. if "--flash" isn't specified) and multiple of them can appear
-#[clap(group(
-        ArgGroup::new("will_flash")
-            .required(false)
-            .multiple(true)
-            .args(&["FLASH_BAUD", "release", "example", "features"])
-            .requires("flash")))]
 struct CargoAppArgs {
     /// Reset the chip on start [default]
     #[clap(short, long)]
@@ -55,9 +57,18 @@ struct CargoAppArgs {
     flash: bool,
 
     /// Baud rate when flashing
-    #[clap(long, default_value_t = 460800, name = "FLASH_BAUD")]
+    #[clap(
+        long,
+        default_value_t = 460800,
+        name = "FLASH_BAUD",
+        requires = "flash"
+    )]
     flash_speed: u32,
 
+    /// If flashing, build with these features first
+    #[clap(long, requires = "flash")]
+    features: Option<String>,
+
     /// Which ESP chip to target
     #[clap(short, long, arg_enum, default_value_t = Chip::ESP32)]
     chip: Chip,
@@ -70,13 +81,13 @@ struct CargoAppArgs {
     #[clap(long)]
     release: bool,
 
-    /// If flashing, flash this example app
-    #[clap(long)]
+    /// Example app to use
+    #[clap(long, conflicts_with = "bin")]
     example: Option<String>,
 
-    /// If flashing, build with these features first
-    #[clap(long)]
-    features: Option<String>,
+    /// Bin target to use
+    #[clap(long, conflicts_with = "example")]
+    bin: Option<String>,
 
     /// Infer chip and framework from target triple
     #[clap(
@@ -119,14 +130,18 @@ fn main() {
     }
 }
 
-fn run_flash(cargo_app_args: &mut CargoAppArgs) -> Result<(), Box<dyn Error>> {
+fn run_flash(cargo_app_args: &mut CargoAppArgs) -> anyhow::Result<()> {
     let mut args = vec!["espflash".to_string()];
     if cargo_app_args.release {
         args.push("--release".to_string());
     }
-    if let Some(example) = cargo_app_args.example.take() {
+    if let Some(example) = &cargo_app_args.example {
         args.push("--example".to_string());
-        args.push(example);
+        args.push(example.clone());
+    }
+    if let Some(bin) = &cargo_app_args.bin {
+        args.push("--package".to_string());
+        args.push(bin.clone());
     }
     if let Some(features) = cargo_app_args.features.take() {
         args.push("--features".to_string());
@@ -144,7 +159,7 @@ fn run_flash(cargo_app_args: &mut CargoAppArgs) -> Result<(), Box<dyn Error>> {
     }
 }
 
-fn handle_args(args: &mut CargoAppArgs) -> Result<AppArgs, Box<dyn Error>> {
+fn handle_args(args: &mut CargoAppArgs) -> anyhow::Result<AppArgs> {
     let (chip, framework) = match args.target {
         Some(ref target) => (Chip::from_target(target)?, Framework::from_target(target)?),
         None => (
@@ -155,19 +170,18 @@ fn handle_args(args: &mut CargoAppArgs) -> Result<AppArgs, Box<dyn Error>> {
         ),
     };
 
-    let project = Project::query(".").unwrap();
-    let artifact = match args.example.as_ref() {
-        Some(example) => Artifact::Example(example.as_str()),
-        None => Artifact::Bin(project.name()),
-    };
     let profile = if args.release {
         Profile::Release
     } else {
         Profile::Dev
     };
-
-    let host = "x86_64-unknown-linux-gnu"; // FIXME: does this even matter?
-    let bin = project.path(artifact, profile, Some(&chip.target(framework)), host)?;
+    let target = chip.target(framework);
+    let artifact = match (&args.example, &args.bin) {
+        (Some(example), _) => Some(Artifact::Example(example.as_str())),
+        (_, Some(bin)) => Some(Artifact::Bin(bin.as_str())),
+        _ => None,
+    };
+    let bin = find_artifact_path(&artifact, profile, &target, PathBuf::from("."))?;
 
     args.reset = !args.no_reset;
 
@@ -175,7 +189,61 @@ fn handle_args(args: &mut CargoAppArgs) -> Result<AppArgs, Box<dyn Error>> {
         reset: args.reset,
         no_reset: args.no_reset,
         speed: args.speed,
-        bin: Some(bin.as_os_str().to_os_string()),
+        bin: Some(bin),
         serial: args.serial.clone(),
     })
 }
+
+#[derive(Deserialize)]
+struct CargoTomlWorkspace {
+    members: Vec<String>,
+}
+
+#[derive(Deserialize)]
+struct CargoToml {
+    workspace: CargoTomlWorkspace,
+}
+
+fn find_artifact_path<P: AsRef<Path>>(
+    artifact: &Option<Artifact>,
+    profile: Profile,
+    target: &String,
+    project_root: P,
+) -> anyhow::Result<OsString> {
+    match Project::query(project_root.as_ref()) {
+        Ok(project) => {
+            let our_artifact = artifact.unwrap_or(Artifact::Bin(project.name()));
+            let host = "x86_64-unknown-linux-gnu"; // FIXME: does this even matter?
+            let bin = project
+                .path(our_artifact, profile, Some(target), host)
+                .map_err(|err| anyhow!("{}", err))?;
+            Ok(bin.as_os_str().to_os_string())
+        }
+        Err(_) => {
+            let mut cargo_toml_path = project_root.as_ref().to_path_buf();
+            cargo_toml_path.push("Cargo.toml");
+            let members: anyhow::Result<Vec<String>> = fs::read(cargo_toml_path)
+                .map_err(|_| anyhow!("No Cargo.toml found at this location"))
+                .and_then(|cargo_toml_bytes| {
+                    toml::from_slice::<CargoToml>(&cargo_toml_bytes)
+                        .map_err(|_| anyhow!("No valid cargo project found at this location"))
+                })
+                .map(|mut cargo_toml| std::mem::take(&mut cargo_toml.workspace.members));
+            for member in members? {
+                let mut member_path = project_root.as_ref().to_path_buf();
+                member_path.push(member);
+                if let Ok(path) = find_artifact_path(artifact, profile, target, &member_path) {
+                    return Ok(path);
+                }
+            }
+            Err(match artifact {
+                Some(Artifact::Example(example)) => {
+                    anyhow!("Could not find example '{}' in project", example)
+                }
+                Some(Artifact::Bin(bin)) => anyhow!("Could not find bin '{}' in project", bin),
+                None => anyhow!("Couldn't find a binary; try passing --bin or --example"),
+                _ => unreachable!(),
+            })
+        }
+    }
+}


Test patch: