Basic `blame` variable functionality
Prints the spans of the source contents for non-incremental variables.
Change-Id: I0ad62d2326591e26414bae69978632816dd3e582
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/infra/build/drydock/+/2464718
diff --git a/Cargo.toml b/Cargo.toml
index 6848cc9..d71ddc6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@
nom_locate = "2.0.0"
regex = "1.3.9"
rental = "0.5.4"
+source-span = "2.2.0"
walkdir = "2.3.1"
[dependencies.clap]
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index a78e1fd..f145fff 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -1,7 +1,8 @@
-use std::fmt::Write;
+use std::{cmp::max, collections::HashMap, fmt::Write, path::PathBuf};
use std::{collections::HashSet, str::FromStr};
use clap::ArgMatches;
+use source_span::{fmt::Style, Position};
use crate::{graph, portage::profile::is_incremental_variable, portage::profile_parser::Span};
@@ -59,6 +60,22 @@
Ok(())
}
+pub fn blame(config: &config::Config, sub_args: &ArgMatches) -> anyhow::Result<()> {
+ let target = sub_args.value_of("profile").unwrap();
+ let profile = ProfileKey::from_str(target)?;
+
+ let target_var = sub_args.value_of("variable").unwrap();
+ let overlay_table = build_overlay_map(&config)?;
+ if is_incremental_variable(target_var) {
+ unimplemented!();
+ } else {
+ let vals = overlay_table.compute_variable(&profile, target_var)?;
+ blame_format(&vals, config);
+ }
+
+ Ok(())
+}
+
pub fn dump_debug(config: &config::Config, sub_args: &ArgMatches) -> anyhow::Result<()> {
let target = sub_args.value_of("overlay").unwrap();
let overlay_table = build_overlay_map(&config)?;
@@ -75,3 +92,58 @@
}
output
}
+
+fn blame_format(tokens: &[Span], config: &config::Config) {
+ let mut seen = HashMap::new();
+ for t in tokens {
+ let idx = seen.len();
+ seen.entry(t.extra).or_insert(idx);
+ }
+ let mut f = source_span::fmt::Formatter::new();
+ f.set_viewbox(None);
+ f.hide_line_numbers();
+
+ let metrics = source_span::DEFAULT_METRICS;
+ let src_buf: source_span::SourceBuffer<(), _, _> = source_span::SourceBuffer::new(
+ tokens
+ .into_iter()
+ .flat_map(|t| t.fragment().chars().map(|c| Ok(c))),
+ Position::default(),
+ metrics,
+ );
+
+ let total_len: usize = tokens
+ .into_iter()
+ .map(|t| t.fragment().chars().count())
+ .sum();
+ let mut chars_seen: usize = 0;
+
+ for t in tokens {
+ let token_len = t.fragment().chars().count();
+ let span = source_span::Span::new(
+ Position::new(0, chars_seen),
+ Position::new(0, chars_seen + token_len),
+ Position::new(0, max(chars_seen + token_len + 1, total_len)),
+ );
+ chars_seen += token_len;
+ f.add(span, Some(span_label(t, config)), Style::Help);
+ }
+ let display_span = source_span::Span::new(
+ Position::new(0, 0),
+ Position::new(0, chars_seen),
+ Position::new(0, chars_seen + 1),
+ );
+ let formatted = f.render(src_buf.iter(), display_span, &metrics).unwrap();
+ println!("{}", formatted);
+}
+
+fn span_label(p: &Span, config: &config::Config) -> String {
+ for common_path in config.get_array("overlay_paths").unwrap() {
+ let common_prefix: PathBuf = PathBuf::from(common_path.into_str().unwrap());
+ if let Ok(new_path) = p.extra.strip_prefix(common_prefix) {
+ return format!("{}:L{}", new_path.display(), p.location_line());
+ }
+ }
+
+ format!("{}:L{}", p.extra.display(), p.location_line())
+}
diff --git a/src/main.rs b/src/main.rs
index 995105d..44c2c8b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -61,6 +61,24 @@
),
)
.subcommand(
+ SubCommand::with_name("blame")
+ .about("Show the value of a variable for a profile annotated with the sources of that variable's contents.")
+ .arg(
+ Arg::with_name("profile")
+ .short("p")
+ .long("profile")
+ .takes_value(true)
+ .required(true)
+ .help("The target profile to query."),
+ )
+ .arg(
+ Arg::with_name("variable")
+ .takes_value(true)
+ .required(true)
+ .multiple(false),
+ ),
+ )
+ .subcommand(
SubCommand::with_name("dump_debug")
.about("Dump debug information for an overlay.")
.arg(
@@ -74,16 +92,13 @@
)
.get_matches();
- if let Some(sub_args) = args.subcommand_matches("dump_debug") {
- commands::dump_debug(&config, sub_args)?;
+ match args.subcommand() {
+ ("blame", Some(sub_args)) => commands::blame(&config, sub_args)?,
+ ("dump_debug", Some(sub_args)) => commands::dump_debug(&config, sub_args)?,
+ ("eval", Some(sub_args)) => commands::eval(&config, sub_args)?,
+ ("parents", Some(sub_args)) => commands::parents(&config, sub_args)?,
+ _ => unimplemented!(),
};
- if let Some(sub_args) = args.subcommand_matches("parents") {
- commands::parents(&config, sub_args)?;
- };
-
- if let Some(sub_args) = args.subcommand_matches("eval") {
- commands::eval(&config, sub_args)?;
- }
Ok(())
}
diff --git a/src/portage/overlay/mod.rs b/src/portage/overlay/mod.rs
index 63b8620..5824c15 100644
--- a/src/portage/overlay/mod.rs
+++ b/src/portage/overlay/mod.rs
@@ -91,12 +91,13 @@
)
}) {
Ok(p) => p,
- Err(e) => {
- eprintln!(
- "Malformed profile found at {:?}\n\tProblem: {}",
- entry.path(),
- e
- );
+ Err(_e) => {
+ // TODO: Replace with logging.
+ // eprintln!(
+ // "Malformed profile found at {:?}\n\tProblem: {}",
+ // entry.path(),
+ // e
+ // );
continue;
}
};
diff --git a/src/portage/overlay/traversal.rs b/src/portage/overlay/traversal.rs
index 7bfef0e..cb4e6e6 100644
--- a/src/portage/overlay/traversal.rs
+++ b/src/portage/overlay/traversal.rs
@@ -60,6 +60,7 @@
}
impl<'a> ProfileIter<'a> {
+ #[allow(dead_code)]
pub(super) fn new(
overlay_table: &'a OverlayTable,
start: <&'a OverlayTable as GraphBase>::NodeId,