From 594d6cfbdd1be34685ccbaaa95fa90d11a7c78bb Mon Sep 17 00:00:00 2001 From: Jesse Braham Date: Tue, 8 Sep 2020 16:55:54 -0700 Subject: [PATCH] Add initial implementation of cargo-espflash --- Cargo.toml | 1 + cargo-espflash/Cargo.toml | 18 ++++ LICENSE => cargo-espflash/LICENSE | 0 cargo-espflash/src/main.rs | 156 ++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 cargo-espflash/Cargo.toml rename LICENSE => cargo-espflash/LICENSE (100%) create mode 100644 cargo-espflash/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 48fedc2..8360228 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] members = [ + "cargo-espflash", "espflash", ] diff --git a/cargo-espflash/Cargo.toml b/cargo-espflash/Cargo.toml new file mode 100644 index 0000000..7b7f425 --- /dev/null +++ b/cargo-espflash/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "cargo-espflash" +version = "0.1.0" +description = "Cargo subcommand for flashing the ESP8266 and ESP32 over serial" +license = "GPL-2.0" +authors = [ + "Robin Appelman ", + "Jesse Braham ", +] +repository = "https://github.com/icewind1991/espflash" +edition = "2018" + +[dependencies] +cargo-project = "0.2.4" +espflash = { version = "0.1.0", path = "../espflash" } +main_error = "0.1.1" +pico-args = "0.3.4" +serial = "0.4" diff --git a/LICENSE b/cargo-espflash/LICENSE similarity index 100% rename from LICENSE rename to cargo-espflash/LICENSE diff --git a/cargo-espflash/src/main.rs b/cargo-espflash/src/main.rs new file mode 100644 index 0000000..468bff4 --- /dev/null +++ b/cargo-espflash/src/main.rs @@ -0,0 +1,156 @@ +use std::ffi::OsString; +use std::fs::read; +use std::path::PathBuf; +use std::process::{exit, Command, ExitStatus, Stdio}; + +use cargo_project::{Artifact, Profile, Project}; +use espflash::Flasher; +use main_error::MainError; +use pico_args::Arguments; +use serial::{BaudRate, SerialPort}; + +fn main() -> Result<(), MainError> { + let args = parse_args().expect("Unable to parse command-line arguments"); + + if args.help || args.chip.is_none() || args.serial.is_none() { + return usage(); + } + + let chip = args.chip.unwrap().to_lowercase(); + let target = match chip.as_str() { + "esp32" => "xtensa-esp32-none-elf", + "esp8266" => "xtensa-esp8266-none-elf", + _ => return usage(), + }; + + let path = get_artifact_path(target, args.release, &args.example) + .expect("Could not find the build artifact path"); + + let status = build(args.release, args.example); + if !status.success() { + exit_with_process_status(status) + } + + let port = args.serial.unwrap(); + let mut serial = serial::open(&port)?; + serial.reconfigure(&|settings| { + settings.set_baud_rate(BaudRate::Baud115200)?; + + Ok(()) + })?; + + let mut flasher = Flasher::connect(serial)?; + let elf_data = read(&path)?; + + if args.ram { + flasher.load_elf_to_ram(&elf_data)?; + } else { + flasher.load_elf_to_flash(&elf_data)?; + } + + Ok(()) +} + +#[derive(Debug)] +struct AppArgs { + help: bool, + ram: bool, + release: bool, + example: Option, + chip: Option, + serial: Option, +} + +fn usage() -> Result<(), MainError> { + let mut usage = String::from("Usage: cargo espflash "); + usage += "[--ram] [--release] [--example EXAMPLE] "; + usage += "--chip {{esp32,esp8266}} "; + + println!("{}", usage); + + Ok(()) +} + +fn parse_args() -> Result { + // Skip the command and subcommand (ie. 'cargo espflash') and convert the + // remaining arguments to the expected type. + let args = std::env::args() + .skip(2) + .map(|arg| OsString::from(arg)) + .collect(); + + let mut args = Arguments::from_vec(args); + + let app_args = AppArgs { + help: args.contains("--help"), + ram: args.contains("--ram"), + release: args.contains("--release"), + example: args.opt_value_from_str("--example")?, + chip: args.opt_value_from_str("--chip")?, + serial: args.free_from_str()?, + }; + + Ok(app_args) +} + +fn get_artifact_path( + target: &str, + release: bool, + example: &Option, +) -> Result { + let project = Project::query(".").unwrap(); + + let artifact = match example { + Some(example) => Artifact::Example(example.as_str()), + None => Artifact::Bin(project.name()), + }; + + let profile = if release { + Profile::Release + } else { + Profile::Dev + }; + + let host = "x86_64-unknown-linux-gnu"; + let path = project.path(artifact, profile, Some(target), host); + + path.map_err(|e| MainError::from(e)) +} + +fn build(release: bool, example: Option) -> ExitStatus { + let mut args: Vec = vec![]; + + if release { + args.push("--release".to_string()); + } + + if example.is_some() { + args.push("--example".to_string()); + args.push(example.unwrap()); + } + + Command::new("xargo") + .arg("build") + .args(args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .unwrap() + .wait() + .unwrap() +} + +#[cfg(unix)] +fn exit_with_process_status(status: ExitStatus) -> ! { + use std::os::unix::process::ExitStatusExt; + let code = status.code().or_else(|| status.signal()).unwrap_or(1); + + exit(code) +} + +#[cfg(not(unix))] +fn exit_with_process_status(status: ExitStatus) -> ! { + let code = status.code().unwrap_or(1); + + exit(code) +}