Skip to content

Commit aa7ba1b

Browse files
authored
Merge pull request #750 from imageworks/progress-bar-proc-macro
Progress bar proc macro
2 parents 7e44a7b + 03bf817 commit aa7ba1b

File tree

11 files changed

+257
-327
lines changed

11 files changed

+257
-327
lines changed

.github/workflows/rust.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ env:
1010
jobs:
1111
build-and-test:
1212
runs-on: ubuntu-latest
13-
timeout-minutes: 30
13+
timeout-minutes: 45
1414
container:
1515
image: rust:1.70.0
1616
options: --security-opt seccomp=unconfined --privileged

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ codegen-units = 1
44

55
[workspace]
66
members = [
7+
"crates/progress_bar_derive_macro",
78
"crates/spfs-encoding",
89
"crates/spfs",
910
"crates/spk-build",
@@ -37,6 +38,7 @@ dyn-clone = "1.0"
3738
expanduser = "1.2"
3839
futures = "0.3.24"
3940
fuser = "0.12"
41+
indicatif = "0.16.2"
4042
lazy_static = "1.4"
4143
libc = "0.2.80"
4244
nix = "0.26.2"
@@ -45,6 +47,7 @@ nom-supreme = "0.8"
4547
once_cell = "1.8"
4648
pin-project-lite = "0.2.0"
4749
procfs = "0.13.2"
50+
progress_bar_derive_macro = { path = "crates/progress_bar_derive_macro" }
4851
rstest = "0.15.0"
4952
sentry = { version = "0.27.0" }
5053
sentry-anyhow = { version = "0.27.0" }
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "progress_bar_derive_macro"
3+
version = { workspace = true }
4+
edition = { workspace = true }
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
syn = "2.0"
11+
quote = "1.0"
12+
13+
[dev-dependencies]
14+
indicatif = { workspace = true }
15+
tracing = { workspace = true }
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright (c) Sony Pictures Imageworks, et al.
2+
// SPDX-License-Identifier: Apache-2.0
3+
// https://github.com/imageworks/spk
4+
5+
use proc_macro::TokenStream;
6+
use quote::{format_ident, quote};
7+
use syn::LitStr;
8+
9+
/// Derive macro for generating boilerplate `Default` and `Drop` impls
10+
/// for a struct with `indicatif::ProgressBar` fields.
11+
///
12+
/// The struct is required to have a field named `renderer` and one or more
13+
/// fields of type `indicatif::ProgressBar`. Each progress bar field requires
14+
/// a `#[progress_bar]` attribute with a `message` argument. A `template`
15+
/// argument is also required either at the struct level or the field level.
16+
///
17+
/// # Example
18+
///
19+
/// ```
20+
/// use progress_bar_derive_macro::ProgressBar;
21+
/// #[derive(ProgressBar)]
22+
/// struct MyStruct {
23+
/// renderer: Option<std::thread::JoinHandle<()>>,
24+
/// #[progress_bar(
25+
/// message = "processing widgets",
26+
/// template = " {spinner} {msg:<16.green} [{bar:40.cyan/dim}] {pos:>8}/{len:6}"
27+
/// )]
28+
/// widgets: indicatif::ProgressBar,
29+
/// }
30+
/// ```
31+
#[proc_macro_derive(ProgressBar, attributes(progress_bar))]
32+
pub fn proc_macro_derive(input: TokenStream) -> TokenStream {
33+
let ast = syn::parse(input).unwrap();
34+
impl_proc_macro_derive(&ast)
35+
}
36+
37+
fn impl_proc_macro_derive(ast: &syn::DeriveInput) -> TokenStream {
38+
let name = &ast.ident;
39+
40+
let mut progress_bar_field_names = Vec::new();
41+
let mut bars = Vec::new();
42+
43+
if let syn::Data::Struct(s) = &ast.data {
44+
let mut template = None;
45+
46+
for attr in &ast.attrs {
47+
if !attr.path().is_ident("progress_bar") {
48+
continue;
49+
}
50+
51+
if let Err(err) = attr.parse_nested_meta(|meta| {
52+
if meta.path.is_ident("template") {
53+
let value = meta.value()?;
54+
let s: LitStr = value.parse()?;
55+
template = Some(s.value());
56+
return Ok(());
57+
}
58+
Ok(())
59+
}) {
60+
return err.to_compile_error().into();
61+
}
62+
}
63+
64+
for field in &s.fields {
65+
let Some(ident) = &field.ident else { continue; };
66+
if let syn::Type::Path(p) = &field.ty {
67+
if let Some(field_type) = p.path.segments.last().map(|s| &s.ident) {
68+
if field_type != "ProgressBar" {
69+
continue;
70+
}
71+
72+
let mut message = None;
73+
74+
for attr in &field.attrs {
75+
if !attr.path().is_ident("progress_bar") {
76+
continue;
77+
}
78+
79+
if let Err(err) = attr.parse_nested_meta(|meta| {
80+
if meta.path.is_ident("message") {
81+
let value = meta.value()?;
82+
let s: LitStr = value.parse()?;
83+
message = Some(s.value());
84+
return Ok(());
85+
}
86+
if meta.path.is_ident("template") {
87+
let value = meta.value()?;
88+
let s: LitStr = value.parse()?;
89+
template = Some(s.value());
90+
return Ok(());
91+
}
92+
Ok(())
93+
}) {
94+
return err.to_compile_error().into();
95+
}
96+
}
97+
98+
let Some(message) = message else {
99+
return syn::Error::new_spanned(
100+
field,
101+
"Missing #[progress_bar(message = \"...\")] attribute",
102+
)
103+
.to_compile_error()
104+
.into();
105+
};
106+
107+
let Some(template) = &template else {
108+
return syn::Error::new_spanned(
109+
field,
110+
"Missing #[progress_bar(template = \"...\")] attribute",
111+
)
112+
.to_compile_error()
113+
.into();
114+
};
115+
116+
let ident_style = format_ident!("{ident}_style");
117+
118+
bars.push(quote! {
119+
let #ident_style = indicatif::ProgressStyle::default_bar()
120+
.template(#template)
121+
.tick_strings(TICK_STRINGS)
122+
.progress_chars(PROGRESS_CHARS);
123+
let #ident = bars.add(
124+
indicatif::ProgressBar::new(0)
125+
.with_style(#ident_style)
126+
.with_message(#message),
127+
);
128+
});
129+
130+
progress_bar_field_names.push(quote! { #ident });
131+
}
132+
}
133+
}
134+
};
135+
136+
let gen = quote! {
137+
impl Default for #name {
138+
fn default() -> Self {
139+
static TICK_STRINGS: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
140+
static PROGRESS_CHARS: &str = "=>-";
141+
142+
let bars = indicatif::MultiProgress::new();
143+
#(#bars)*
144+
#(#progress_bar_field_names.enable_steady_tick(100);)*
145+
// the progress bar must be awaited from some thread
146+
// or nothing will be shown in the terminal
147+
let renderer = Some(std::thread::spawn(move || {
148+
if let Err(err) = bars.join() {
149+
tracing::error!("Failed to render commit progress: {err}");
150+
}
151+
}));
152+
Self {
153+
#(#progress_bar_field_names,)*
154+
renderer,
155+
}
156+
}
157+
}
158+
159+
impl Drop for #name {
160+
fn drop(&mut self) {
161+
#(self.#progress_bar_field_names.finish_and_clear();)*
162+
if let Some(r) = self.renderer.take() {
163+
let _ = r.join();
164+
}
165+
}
166+
}
167+
};
168+
gen.into()
169+
}

crates/spfs/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,15 @@ futures = { workspace = true }
3535
gitignore = "1.0"
3636
glob = "0.3.0"
3737
hyper = { version = "0.14.16", features = ["client"] }
38-
indicatif = "0.16.2"
38+
indicatif = { workspace = true }
3939
itertools = "0.10.3"
4040
libc = { workspace = true }
4141
nix = { workspace = true }
4242
nonempty = "0.8.1"
4343
once_cell = { workspace = true }
4444
pin-project-lite = { workspace = true }
4545
procfs = { workspace = true }
46+
progress_bar_derive_macro = { workspace = true }
4647
prost = "0.11"
4748
rand = "0.8.5"
4849
relative-path = "1.3"

crates/spfs/src/check.rs

Lines changed: 14 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::sync::Arc;
99
use colored::Colorize;
1010
use futures::stream::{FuturesUnordered, TryStreamExt};
1111
use once_cell::sync::OnceCell;
12+
use progress_bar_derive_macro::ProgressBar;
1213
use tokio::sync::Semaphore;
1314

1415
use crate::prelude::*;
@@ -594,79 +595,26 @@ impl CheckReporter for ConsoleCheckReporter {
594595
}
595596
}
596597

598+
#[derive(ProgressBar)]
597599
struct ConsoleCheckReporterBars {
598600
renderer: Option<std::thread::JoinHandle<()>>,
601+
#[progress_bar(
602+
message = "scanning objects",
603+
template = " {spinner} {msg:<18.green} {pos:>9} reached in {elapsed:.cyan} [{per_sec}]"
604+
)]
599605
objects: indicatif::ProgressBar,
606+
#[progress_bar(
607+
message = "finding issues",
608+
template = " {spinner} {msg:<18.green} {len:>9} errors, {pos} repaired ({percent}%)"
609+
)]
600610
missing: indicatif::ProgressBar,
611+
#[progress_bar(
612+
message = "payloads footprint",
613+
template = " {spinner} {msg:<18.green} {total_bytes:>9} seen, {bytes} pulled"
614+
)]
601615
bytes: indicatif::ProgressBar,
602616
}
603617

604-
impl Default for ConsoleCheckReporterBars {
605-
fn default() -> Self {
606-
static TICK_STRINGS: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
607-
static PROGRESS_CHARS: &str = "=>-";
608-
let layers_style = indicatif::ProgressStyle::default_bar()
609-
.template(
610-
" {spinner} {msg:<18.green} {pos:>9} reached in {elapsed:.cyan} [{per_sec}]",
611-
)
612-
.tick_strings(TICK_STRINGS)
613-
.progress_chars(PROGRESS_CHARS);
614-
let payloads_style = indicatif::ProgressStyle::default_bar()
615-
.template(
616-
" {spinner} {msg:<18.green} {len:>9} errors, {pos} repaired ({percent}%)",
617-
)
618-
.tick_strings(TICK_STRINGS)
619-
.progress_chars(PROGRESS_CHARS);
620-
let bytes_style = indicatif::ProgressStyle::default_bar()
621-
.template(" {spinner} {msg:<18.green} {total_bytes:>9} seen, {bytes} pulled")
622-
.tick_strings(TICK_STRINGS)
623-
.progress_chars(PROGRESS_CHARS);
624-
let bars = indicatif::MultiProgress::new();
625-
let objects = bars.add(
626-
indicatif::ProgressBar::new(0)
627-
.with_style(layers_style)
628-
.with_message("scanning objects"),
629-
);
630-
let payloads = bars.add(
631-
indicatif::ProgressBar::new(0)
632-
.with_style(payloads_style)
633-
.with_message("finding issues"),
634-
);
635-
let bytes = bars.add(
636-
indicatif::ProgressBar::new(0)
637-
.with_style(bytes_style)
638-
.with_message("payloads footprint"),
639-
);
640-
objects.enable_steady_tick(100);
641-
payloads.enable_steady_tick(100);
642-
bytes.enable_steady_tick(100);
643-
// the progress bar must be awaited from some thread
644-
// or nothing will be shown in the terminal
645-
let renderer = Some(std::thread::spawn(move || {
646-
if let Err(err) = bars.join() {
647-
tracing::error!("Failed to render check progress: {err}");
648-
}
649-
}));
650-
Self {
651-
renderer,
652-
objects,
653-
missing: payloads,
654-
bytes,
655-
}
656-
}
657-
}
658-
659-
impl Drop for ConsoleCheckReporterBars {
660-
fn drop(&mut self) {
661-
self.bytes.finish_and_clear();
662-
self.missing.finish_and_clear();
663-
self.objects.finish_and_clear();
664-
if let Some(r) = self.renderer.take() {
665-
let _ = r.join();
666-
}
667-
}
668-
}
669-
670618
#[derive(Default, Debug)]
671619
pub struct CheckSummary {
672620
/// The number of missing tags

0 commit comments

Comments
 (0)