Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds a watch flag to watch the elf file and reloads the file if it changed #807

Merged
merged 10 commits into from
Sep 3, 2024
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,28 @@ 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
- [#847]: `decoder`: Fix log format width specifier not working as expected
- [#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
[#847]: https://github.com/knurling-rs/defmt/pull/847
[#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

Expand Down
1 change: 1 addition & 0 deletions decoder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions decoder/src/log/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Level>) {
Expand Down
1 change: 1 addition & 0 deletions print/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
83 changes: 68 additions & 15 deletions print/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand All @@ -44,11 +46,14 @@ struct Opts {
#[arg(short = 'V', long)]
version: bool,

#[arg(short, long)]
watch_elf: bool,

#[command(subcommand)]
command: Option<Command>,
}

#[derive(Subcommand)]
#[derive(Subcommand, Clone)]
enum Command {
/// Read defmt frames from stdin (default)
Stdin,
Expand Down Expand Up @@ -94,20 +99,73 @@ 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<Result<Event, notify::Error>>, 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,
log_format,
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?;
Expand Down Expand Up @@ -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?;
Expand Down
Loading