diff --git a/CHANGELOG.md b/CHANGELOG.md index cb107ea9..2cd8c3a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [#859]: `defmt`: Satisfy clippy - [#858]: `defmt`: Implement "passthrough" trait impls for *2Format wrappers - [#857]: Add an octal display hint (`:o`) +- [#856]: `defmt`: Add a `Format` impl for `PanicInfo` and related types. - [#855]: `defmt-print`: Now uses tokio to make tcp and stdin reads async (in preparation for a `watch elf` flag) - [#852]: `CI`: Update mdbook to v0.4.40 - [#848]: `decoder`: add optional one-line format @@ -17,11 +18,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [#845]: `decoder`: fix println!() records being printed with formatting - [#843]: `defmt`: Sort IDs of log msgs by severity to allow runtime filtering by severity - [#822]: `CI`: Run `cargo semver-checks` on every PR -- [#856]: `defmt`: Add a `Format` impl for `PanicInfo` and related types. +- [#807]: `defmt-print`: Add `watch_elf` flag to allow ELF file reload without restarting `defmt-print` [#859]: https://github.com/knurling-rs/defmt/pull/859 [#858]: https://github.com/knurling-rs/defmt/pull/858 [#857]: https://github.com/knurling-rs/defmt/pull/857 +[#856]: https://github.com/knurling-rs/defmt/pull/856 [#855]: https://github.com/knurling-rs/defmt/pull/855 [#852]: https://github.com/knurling-rs/defmt/pull/852 [#848]: https://github.com/knurling-rs/defmt/pull/848 @@ -29,7 +31,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). [#845]: https://github.com/knurling-rs/defmt/pull/845 [#843]: https://github.com/knurling-rs/defmt/pull/843 [#822]: https://github.com/knurling-rs/defmt/pull/822 -[#856]: https://github.com/knurling-rs/defmt/pull/856 +[#807]: https://github.com/knurling-rs/defmt/pull/807 ## [v0.3.8] - 2024-05-17 diff --git a/decoder/Cargo.toml b/decoder/Cargo.toml index c843ca5e..814064b0 100644 --- a/decoder/Cargo.toml +++ b/decoder/Cargo.toml @@ -43,6 +43,7 @@ object = { version = "0.35", default-features = false, features = [ serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["arbitrary_precision"] } regex = "1" +alterable_logger = "1" [features] # WARNING: API and wire format subject to change. diff --git a/decoder/src/log/mod.rs b/decoder/src/log/mod.rs index 9858a6f8..18e9b6cc 100644 --- a/decoder/src/log/mod.rs +++ b/decoder/src/log/mod.rs @@ -139,8 +139,8 @@ pub fn init_logger( JsonLogger::new(formatter, host_formatter, should_log) } }; - log::set_boxed_logger(logger).unwrap(); - log::set_max_level(LevelFilter::Trace); + alterable_logger::set_boxed_logger(logger); + alterable_logger::set_max_level(LevelFilter::Trace); } fn timestamp_and_level_from_frame(frame: &Frame<'_>) -> (String, Option) { diff --git a/print/Cargo.toml b/print/Cargo.toml index 3ac6d491..bdb023ee 100644 --- a/print/Cargo.toml +++ b/print/Cargo.toml @@ -19,4 +19,5 @@ defmt-decoder = { version = "=0.3.11", path = "../decoder", features = [ "unstable", ] } log = "0.4" +notify = "6.1" tokio = { version = "1.38", features = ["full"] } diff --git a/print/src/main.rs b/print/src/main.rs index ae04f35c..56236c31 100644 --- a/print/src/main.rs +++ b/print/src/main.rs @@ -12,15 +12,17 @@ use defmt_decoder::{ }, DecodeError, Frame, Locations, Table, DEFMT_VERSIONS, }; - +use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; use tokio::{ fs, io::{self, AsyncReadExt, Stdin}, net::TcpStream, + select, + sync::mpsc::Receiver, }; /// Prints defmt-encoded logs to stdout -#[derive(Parser)] +#[derive(Parser, Clone)] #[command(name = "defmt-print")] struct Opts { #[arg(short, required = true, conflicts_with("version"))] @@ -44,11 +46,14 @@ struct Opts { #[arg(short = 'V', long)] version: bool, + #[arg(short, long)] + watch_elf: bool, + #[command(subcommand)] command: Option, } -#[derive(Subcommand)] +#[derive(Subcommand, Clone)] enum Command { /// Read defmt frames from stdin (default) Stdin, @@ -94,6 +99,64 @@ const READ_BUFFER_SIZE: usize = 1024; #[tokio::main] async fn main() -> anyhow::Result<()> { + let opts = Opts::parse(); + + if opts.version { + return print_version(); + } + + // We create the source outside of the run command since recreating the stdin looses us some frames + let mut source = match opts.command.clone() { + None | Some(Command::Stdin) => Source::stdin(), + Some(Command::Tcp { host, port }) => Source::tcp(host, port).await?, + }; + + if opts.watch_elf { + run_and_watch(opts, &mut source).await + } else { + run(opts, &mut source).await + } +} + +async fn has_file_changed(rx: &mut Receiver>, path: &PathBuf) -> bool { + loop { + if let Some(Ok(event)) = rx.recv().await { + if event.paths.contains(path) { + if let notify::EventKind::Create(_) | notify::EventKind::Modify(_) = event.kind { + break; + } + } + } + } + true +} + +async fn run_and_watch(opts: Opts, source: &mut Source) -> anyhow::Result<()> { + let (tx, mut rx) = tokio::sync::mpsc::channel(1); + + let path = opts.elf.clone().unwrap().canonicalize().unwrap(); + + // We want the elf directory instead of the elf, since some editors remove + // and recreate the file on save which will remove the notifier + let directory_path = path.parent().unwrap(); + + let mut watcher = RecommendedWatcher::new( + move |res| { + let _ = tx.blocking_send(res); + }, + Config::default(), + )?; + watcher.watch(directory_path.as_ref(), RecursiveMode::NonRecursive)?; + + loop { + select! { + r = run(opts.clone(), source) => r?, + _ = has_file_changed(&mut rx, &path) => () + } + } +} + +async fn run(opts: Opts, source: &mut Source) -> anyhow::Result<()> { let Opts { elf, json, @@ -101,13 +164,8 @@ async fn main() -> anyhow::Result<()> { host_log_format, show_skipped_frames, verbose, - version, - command, - } = Opts::parse(); - - if version { - return print_version(); - } + .. + } = opts; // read and parse elf file let bytes = fs::read(elf.unwrap()).await?; @@ -162,11 +220,6 @@ async fn main() -> anyhow::Result<()> { let mut stream_decoder = table.new_stream_decoder(); let current_dir = env::current_dir()?; - let mut source = match command { - None | Some(Command::Stdin) => Source::stdin(), - Some(Command::Tcp { host, port }) => Source::tcp(host, port).await?, - }; - loop { // read from stdin or tcpstream and push it to the decoder let (n, eof) = source.read(&mut buf).await?;