use cargo_config::has_build_std;
use cargo_metadata::Message;
use clap::{App, Arg, SubCommand};
use error::Error;
use espflash::{Config, Flasher, PartitionTable};
use miette::{IntoDiagnostic, Result, WrapErr};
use monitor::monitor;
use package_metadata::CargoEspFlashMeta;
use serial::{BaudRate, FlowControl, SerialPort};
use std::{
fs,
path::PathBuf,
process::{exit, Command, ExitStatus, Stdio},
string::ToString,
};
mod cargo_config;
mod error;
mod line_endings;
mod monitor;
mod package_metadata;
fn main() -> Result<()> {
let mut app = App::new(env!("CARGO_PKG_NAME"))
.bin_name("cargo")
.subcommand(
SubCommand::with_name("espflash")
.version(env!("CARGO_PKG_VERSION"))
.about(env!("CARGO_PKG_DESCRIPTION"))
.arg(
Arg::with_name("board_info")
.long("board-info")
.help("Display the connected board's information"),
)
.arg(
Arg::with_name("ram")
.long("ram")
.help("Load the application to RAM instead of Flash"),
)
.arg(
Arg::with_name("release")
.long("release")
.help("Build the application using the release profile"),
)
.arg(
Arg::with_name("bootloader")
.long("bootloader")
.takes_value(true)
.value_name("PATH")
.help("Path to a binary (.bin) bootloader file"),
)
.arg(
Arg::with_name("example")
.long("example")
.takes_value(true)
.value_name("EXAMPLE")
.help("Example to build and flash"),
)
.arg(
Arg::with_name("features")
.long("features")
.use_delimiter(true)
.takes_value(true)
.value_name("FEATURES")
.help("Comma delimited list of build features"),
)
.arg(
Arg::with_name("partition_table")
.long("partition-table")
.takes_value(true)
.value_name("PATH")
.help("Path to a CSV file containing partition table"),
)
.arg(
Arg::with_name("speed")
.long("speed")
.takes_value(true)
.value_name("SPEED")
.help("Baud rate at which to flash target device"),
)
.arg(
Arg::with_name("serial")
.takes_value(true)
.value_name("SERIAL")
.help("Serial port connected to target device"),
)
.arg(
Arg::with_name("monitor")
.long("monitor")
.help("Open a serial monitor after flashing"),
),
);
let matches = app.clone().get_matches();
let matches = match matches.subcommand_matches("espflash") {
Some(matches) => matches,
None => {
app.print_help().into_diagnostic()?;
exit(0);
}
};
let config = Config::load();
let metadata = CargoEspFlashMeta::load("Cargo.toml")?;
let port = if let Some(serial) = matches.value_of("serial") {
serial.to_string()
} else if let Some(serial) = config.connection.serial {
serial
} else {
app.print_help().into_diagnostic()?;
exit(0);
};
let show_board_info = matches.is_present("board_info");
let path = if !show_board_info {
let release = matches.is_present("release");
let example = matches.value_of("example");
let features = matches.value_of("features");
let path = build(release, example, features)?;
Some(path)
} else {
None
};
println!("Serial port: {}", port);
println!("Connecting...\n");
let mut serial = serial::open(&port)
.map_err(espflash::Error::from)
.wrap_err_with(|| format!("Failed to open serial port {}", port))?;
serial
.reconfigure(&|settings| {
settings.set_flow_control(FlowControl::FlowNone);
settings.set_baud_rate(BaudRate::Baud115200)?;
Ok(())
})
.into_diagnostic()?;
let speed = if let Some(speed) = matches.value_of("speed") {
let speed = speed.parse::<usize>().into_diagnostic()?;
Some(BaudRate::from_speed(speed))
} else {
None
};
let mut flasher = Flasher::connect(serial, speed)?;
if show_board_info {
board_info(&flasher);
return Ok(());
}
let bootloader = if let Some(path) = matches
.value_of("bootloader")
.or_else(|| metadata.bootloader.as_deref())
{
let path = fs::canonicalize(path).into_diagnostic()?;
let data = fs::read(path).into_diagnostic()?;
Some(data)
} else {
None
};
let partition_table = if let Some(path) = matches
.value_of("partition_table")
.or_else(|| metadata.partition_table.as_deref())
{
let path = fs::canonicalize(path).into_diagnostic()?;
let data = fs::read_to_string(path).into_diagnostic()?;
let table = PartitionTable::try_from_str(data)?;
Some(table)
} else {
None
};
let elf_data = fs::read(path.unwrap()).into_diagnostic()?;
if matches.is_present("ram") {
flasher.load_elf_to_ram(&elf_data)?;
} else {
flasher.load_elf_to_flash(&elf_data, bootloader, partition_table)?;
}
if matches.is_present("monitor") {
monitor(flasher.into_serial()).into_diagnostic()?;
}
Ok(())
}
fn board_info(flasher: &Flasher) {
println!("Chip type: {}", flasher.chip());
println!("Flash size: {}", flasher.flash_size());
}
fn build(release: bool, example: Option<&str>, features: Option<&str>) -> Result<PathBuf> {
if !has_build_std(".") {
return Err(Error::NoBuildStd.into());
};
let mut args = vec![];
if release {
args.push("--release");
}
if let Some(example) = example {
args.push("--example");
args.push(example);
}
if let Some(features) = features {
args.push("--features");
args.push(features);
}
let output = Command::new("cargo")
.arg("build")
.args(args)
.args(&["--message-format", "json-diagnostic-rendered-ansi"])
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.into_diagnostic()?
.wait_with_output()
.into_diagnostic()?;
let messages = Message::parse_stream(&output.stdout[..]);
let mut target_artifact = None;
for message in messages {
match message.into_diagnostic()? {
Message::CompilerArtifact(artifact) => {
if artifact.executable.is_some() {
if target_artifact.is_some() {
return Err(Error::MultipleArtifacts.into());
} else {
target_artifact = Some(artifact);
}
}
}
Message::CompilerMessage(message) => {
if let Some(rendered) = message.message.rendered {
print!("{}", rendered);
}
}
_ => (),
}
}
if !output.status.success() {
exit_with_process_status(output.status);
}
let target_artifact = target_artifact.ok_or(Error::NoArtifact)?;
let artifact_path = target_artifact.executable.unwrap().into();
Ok(artifact_path)
}
#[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)
}