Skip to content

Commit 1bcbd42

Browse files
fix(report): write run data to project-local target/piano/runs/
Add shutdown_to(dir) to the runtime that writes to a specified directory. The CLI now injects shutdown_to with the project's target/piano/runs/ path instead of shutdown(), making run data project-scoped by default. piano report and piano tag check ./target/piano/runs/ first, falling back to ~/.piano/runs/ for backward compatibility with existing global runs. PIANO_RUNS_DIR env var still takes priority in both shutdown and shutdown_to for testing and user overrides. Closes #71
1 parent 85b54cc commit 1bcbd42

File tree

4 files changed

+81
-16
lines changed

4 files changed

+81
-16
lines changed

piano-runtime/src/collector.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,23 @@ pub fn shutdown() {
663663
Some(d) => d,
664664
None => return,
665665
};
666+
shutdown_impl(&dir);
667+
}
668+
669+
/// Like `shutdown`, but writes run files to the specified directory.
670+
///
671+
/// Used by the CLI to write to project-local `target/piano/runs/` instead
672+
/// of the global `~/.piano/runs/`. `PIANO_RUNS_DIR` env var takes priority
673+
/// if set (for testing and user overrides).
674+
pub fn shutdown_to(dir: &str) {
675+
if let Ok(override_dir) = std::env::var("PIANO_RUNS_DIR") {
676+
shutdown_impl(std::path::Path::new(&override_dir));
677+
} else {
678+
shutdown_impl(std::path::Path::new(dir));
679+
}
680+
}
681+
682+
fn shutdown_impl(dir: &std::path::Path) {
666683
let ts = timestamp_ms();
667684

668685
// Write frame-level data if present (NDJSON format).

piano-runtime/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ pub use alloc::PianoAllocator;
1111
pub use collector::collect_invocations;
1212
pub use collector::{
1313
adopt, collect, collect_all, collect_frames, enter, flush, fork, init, register, reset,
14-
shutdown, AdoptGuard, FrameFnSummary, FunctionRecord, Guard, InvocationRecord, SpanContext,
14+
shutdown, shutdown_to, AdoptGuard, FrameFnSummary, FunctionRecord, Guard, InvocationRecord,
15+
SpanContext,
1516
};

src/main.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,14 @@ fn cmd_build(
300300
source,
301301
})?;
302302

303-
let rewritten = inject_shutdown(&rewritten).map_err(|source| Error::ParseError {
304-
path: main_file.clone(),
305-
source,
303+
let target_dir = project.join("target").join("piano");
304+
let runs_dir = target_dir.join("runs");
305+
let runs_dir_str = runs_dir.to_string_lossy().to_string();
306+
let rewritten = inject_shutdown(&rewritten, Some(&runs_dir_str)).map_err(|source| {
307+
Error::ParseError {
308+
path: main_file.clone(),
309+
source,
310+
}
306311
})?;
307312
std::fs::write(&main_file, rewritten)?;
308313
}
@@ -397,6 +402,12 @@ fn default_runs_dir() -> Result<PathBuf, Error> {
397402
if let Ok(dir) = std::env::var("PIANO_RUNS_DIR") {
398403
return Ok(PathBuf::from(dir));
399404
}
405+
// Project-local first: ./target/piano/runs/
406+
let local = PathBuf::from("target/piano/runs");
407+
if local.is_dir() {
408+
return Ok(std::fs::canonicalize(local)?);
409+
}
410+
// Fall back to global ~/.piano/runs/ for backward compat
400411
let home = std::env::var_os("HOME").ok_or(Error::HomeNotFound)?;
401412
Ok(PathBuf::from(home).join(".piano").join("runs"))
402413
}
@@ -405,6 +416,12 @@ fn default_tags_dir() -> Result<PathBuf, Error> {
405416
if let Ok(dir) = std::env::var("PIANO_TAGS_DIR") {
406417
return Ok(PathBuf::from(dir));
407418
}
419+
// Project-local first: ./target/piano/tags/
420+
let local = PathBuf::from("target/piano/tags");
421+
if local.is_dir() {
422+
return Ok(std::fs::canonicalize(local)?);
423+
}
424+
// Fall back to global ~/.piano/tags/ for backward compat
408425
let home = std::env::var_os("HOME").ok_or(Error::HomeNotFound)?;
409426
Ok(PathBuf::from(home).join(".piano").join("tags"))
410427
}

src/rewrite.rs

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -480,15 +480,36 @@ pub fn inject_global_allocator(
480480
Ok(prettyplease::unparse(&file))
481481
}
482482

483-
/// Inject `piano_runtime::shutdown()` as the last statement in `fn main`.
484-
pub fn inject_shutdown(source: &str) -> Result<String, syn::Error> {
483+
/// Inject `piano_runtime::shutdown_to(dir)` as the last statement in `fn main`.
484+
///
485+
/// When `runs_dir` is `Some`, emits `shutdown_to(dir)` to write run data to
486+
/// the given project-local directory. When `None`, falls back to `shutdown()`
487+
/// which uses the global `~/.piano/runs/`.
488+
pub fn inject_shutdown(source: &str, runs_dir: Option<&str>) -> Result<String, syn::Error> {
485489
let mut file: syn::File = syn::parse_str(source)?;
486-
let mut injector = ShutdownInjector;
490+
let mut injector = ShutdownInjector {
491+
runs_dir: runs_dir.map(String::from),
492+
};
487493
injector.visit_file_mut(&mut file);
488494
Ok(prettyplease::unparse(&file))
489495
}
490496

491-
struct ShutdownInjector;
497+
struct ShutdownInjector {
498+
runs_dir: Option<String>,
499+
}
500+
501+
impl ShutdownInjector {
502+
fn shutdown_stmt(&self) -> syn::Stmt {
503+
match &self.runs_dir {
504+
Some(dir) => syn::parse_quote! {
505+
piano_runtime::shutdown_to(#dir);
506+
},
507+
None => syn::parse_quote! {
508+
piano_runtime::shutdown();
509+
},
510+
}
511+
}
512+
}
492513

493514
impl VisitMut for ShutdownInjector {
494515
fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
@@ -498,6 +519,7 @@ impl VisitMut for ShutdownInjector {
498519
// Wrap existing body in a block so all guards drop before shutdown.
499520
// This ensures main's own timing guard records before shutdown collects.
500521
let existing_stmts = std::mem::take(&mut node.block.stmts);
522+
let shutdown_stmt = self.shutdown_stmt();
501523

502524
if has_return_type {
503525
// fn main() -> T: capture the body's result, shutdown, return it.
@@ -509,9 +531,6 @@ impl VisitMut for ShutdownInjector {
509531
let combined: syn::Stmt = syn::parse_quote! {
510532
let __piano_result = #inner_block;
511533
};
512-
let shutdown_stmt: syn::Stmt = syn::parse_quote! {
513-
piano_runtime::shutdown();
514-
};
515534
let return_expr: syn::Stmt =
516535
syn::Stmt::Expr(syn::parse_quote! { __piano_result }, None);
517536
node.block.stmts = vec![combined, shutdown_stmt, return_expr];
@@ -522,9 +541,6 @@ impl VisitMut for ShutdownInjector {
522541
#(#existing_stmts)*
523542
}
524543
};
525-
let shutdown_stmt: syn::Stmt = syn::parse_quote! {
526-
piano_runtime::shutdown();
527-
};
528544
node.block.stmts = vec![inner_block, shutdown_stmt];
529545
}
530546
}
@@ -719,7 +735,7 @@ fn main() {
719735
do_stuff();
720736
}
721737
"#;
722-
let result = inject_shutdown(source).unwrap();
738+
let result = inject_shutdown(source, None).unwrap();
723739
assert!(
724740
result.contains("piano_runtime::shutdown()"),
725741
"should inject shutdown. Got:\n{result}"
@@ -732,6 +748,20 @@ fn main() {
732748
);
733749
}
734750

751+
#[test]
752+
fn injects_shutdown_to_with_dir() {
753+
let source = r#"
754+
fn main() {
755+
do_stuff();
756+
}
757+
"#;
758+
let result = inject_shutdown(source, Some("/tmp/my-project/target/piano/runs")).unwrap();
759+
assert!(
760+
result.contains("piano_runtime::shutdown_to(\"/tmp/my-project/target/piano/runs\")"),
761+
"should inject shutdown_to with dir. Got:\n{result}"
762+
);
763+
}
764+
735765
#[test]
736766
fn injects_shutdown_preserves_main_return_type() {
737767
let source = r#"
@@ -741,7 +771,7 @@ fn main() -> ExitCode {
741771
ExitCode::SUCCESS
742772
}
743773
"#;
744-
let result = inject_shutdown(source).unwrap();
774+
let result = inject_shutdown(source, None).unwrap();
745775
// Must contain shutdown
746776
assert!(
747777
result.contains("piano_runtime::shutdown()"),

0 commit comments

Comments
 (0)