diff --git a/Cargo.lock b/Cargo.lock index b8cc917f..22e24c5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1527,6 +1527,7 @@ dependencies = [ "cpclib-disc", "cpclib-imgconverter", "cpclib-xfertool", + "dot-writer", "glob", "globmatch", "lazy-regex", @@ -2187,6 +2188,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dot-writer" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1b11bd5e7e98406c6ff39fbc94d6e910a489b978ce7f17c19fce91a1195b7a" + [[package]] name = "downcast-rs" version = "1.2.0" diff --git a/cpclib-bndbuild/Cargo.toml b/cpclib-bndbuild/Cargo.toml index 85707e8d..7ab1fa10 100644 --- a/cpclib-bndbuild/Cargo.toml +++ b/cpclib-bndbuild/Cargo.toml @@ -19,6 +19,7 @@ exclude = ["examples", "tests/dummy"] shlex = "1.3.0" topologic = "1.1.0" lazy-regex = "3.1.0" +dot-writer = "0.1.3" cpclib-common = {workspace=true, features=["cmdline"]} cpclib-basm = { workspace=true, default-features=false, features=["xferlib"] } diff --git a/cpclib-bndbuild/src/builder.rs b/cpclib-bndbuild/src/builder.rs index 0e90266b..5144289b 100644 --- a/cpclib-bndbuild/src/builder.rs +++ b/cpclib-bndbuild/src/builder.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; use std::io::{BufReader, Read}; +use std::ops::Deref; use std::path::Path; use cpclib_common::itertools::Itertools; @@ -21,6 +22,14 @@ pub struct BndBuilder { inner: BndBuilderInner } +impl Deref for BndBuilder { + type Target = rules::Rules; + + fn deref(&self) -> &Self::Target { + &self.inner.borrow_owner() + } +} + impl BndBuilder { pub fn add_default_rule>( self, @@ -63,7 +72,7 @@ impl BndBuilder { ) -> Result { if let Some(working_directory) = working_directory { let working_directory = working_directory.as_ref(); - println!( + eprintln!( "> Set working directory to: {}", working_directory.display() ); diff --git a/cpclib-bndbuild/src/main.rs b/cpclib-bndbuild/src/main.rs index a561aa80..bae3f6ad 100644 --- a/cpclib-bndbuild/src/main.rs +++ b/cpclib-bndbuild/src/main.rs @@ -50,6 +50,13 @@ fn inner_main() -> Result<(), BndBuilderError> { .help("Print version") .action(ArgAction::SetTrue) ) + .arg( + Arg::new("dot") + .long("dot") + .alias("grapĥviz") + .help("Generate the .dot representation of the selected bndbuild.yml file") + .action(ArgAction::SetTrue) + ) .arg( Arg::new("file") .short('f') @@ -66,6 +73,7 @@ fn inner_main() -> Result<(), BndBuilderError> { .long("watch") .action(ArgAction::SetTrue) .help("Watch the targets and permanently rebuild them when needed.") + .conflicts_with("dot") ) .arg( Arg::new("list") @@ -73,18 +81,21 @@ fn inner_main() -> Result<(), BndBuilderError> { .long("list") .action(ArgAction::SetTrue) .help("List the available targets") + .conflicts_with("dot") ) .arg( Arg::new("init") .long("init") .action(ArgAction::SetTrue) .help("Init a new project by creating it") + .conflicts_with("dot") ) .arg( Arg::new("add") .long("add") .short('a') .help("Add a new basm target in an existing bndbuild.yml (or create it)") + .conflicts_with("dot") .action(ArgAction::Set) ) .arg( @@ -282,31 +293,37 @@ fn inner_main() -> Result<(), BndBuilderError> { .collect::>() }; - // Execute the targets - let mut first_loop = true; - let watch_requested = matches.get_flag("watch"); - loop { - for tgt in targets.iter() { - if first_loop || builder.outdated(tgt).unwrap_or(false) { - builder.execute(tgt).map_err(|e| { - if targets_provided { - e - } - else { - BndBuilderError::DefaultTargetError { - source: Box::new(e) + if matches.get_flag("dot") { + let dot = builder.to_dot(); + println!("{dot}") + + } else { + // Execute the targets + let mut first_loop = true; + let watch_requested = matches.get_flag("watch"); + loop { + for tgt in targets.iter() { + if first_loop || builder.outdated(tgt).unwrap_or(false) { + builder.execute(tgt).map_err(|e| { + if targets_provided { + e + } + else { + BndBuilderError::DefaultTargetError { + source: Box::new(e) + } } - } - })?; + })?; + } } - } - if !watch_requested { - break; - } + if !watch_requested { + break; + } - std::thread::sleep(std::time::Duration::from_millis(1000)); // sleep 1s before trying to build - first_loop = false; + std::thread::sleep(std::time::Duration::from_millis(1000)); // sleep 1s before trying to build + first_loop = false; + } } } diff --git a/cpclib-bndbuild/src/rules/rules.rs b/cpclib-bndbuild/src/rules/rules.rs index 85f69f7d..08397a8f 100644 --- a/cpclib-bndbuild/src/rules/rules.rs +++ b/cpclib-bndbuild/src/rules/rules.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::fmt::Display; use std::path::Path; +use dot_writer::{Attributes, DotWriter, Style}; use serde::{self, Deserialize}; use topologic::AcyclicDependencyGraph; @@ -109,3 +110,39 @@ impl Rules { }) } } + + + +impl Rules { + pub fn to_dot(&self) -> String { + let mut output_bytes = Vec::new(); + { + let mut writer = DotWriter::from(&mut output_bytes); + writer.set_pretty_print(true); + + let mut digraph = writer.digraph(); + digraph + .set_rank_direction(dot_writer::RankDirection::BottomTop) + .node_attributes() + .set_style(Style::Filled) + .set_shape(dot_writer::Shape::Rectangle) + ; + + for rule in self.rules() { + let deps = rule.dependencies(); + let tgts = rule.targets(); + + for dep in deps { + for tgt in tgts { + digraph.edge( + format!("\"{}\"", dep.display().to_string()), + format!("\"{}\"", tgt.display().to_string())); + } + } + + } + } + + String::from_utf8_lossy(&output_bytes).into_owned() + } +} \ No newline at end of file