github.com/charypar/monobuild@v0.0.0-20211122220434-fd884ed50212/rs/src/main.rs (about)

     1  use std::collections::{BTreeMap, HashSet};
     2  use std::fs;
     3  use std::io::{self, BufRead};
     4  use std::path::Path;
     5  use std::process;
     6  
     7  use anyhow::{anyhow, Result};
     8  use ignore::WalkBuilder;
     9  use structopt::StructOpt;
    10  
    11  mod cli;
    12  mod core;
    13  mod git;
    14  mod graph;
    15  mod read;
    16  mod write;
    17  
    18  use crate::{core::Dependency, write::DotFormat};
    19  use cli::{Command, DiffOpts, InputOpts, Opts, OutputOpts};
    20  use git::{Git, Mode};
    21  use graph::{Graph, Subgraph};
    22  use read::Warning;
    23  use write::TextFormat;
    24  
    25  fn main() {
    26      let opts = Opts::from_args();
    27      let result = match opts.cmd {
    28          Command::Print(opts) => print(opts.input_opts, opts.output_opts),
    29          Command::Diff(opts) => diff(&opts),
    30      };
    31  
    32      match result {
    33          Ok(out) => {
    34              println!("{}", out);
    35              process::exit(0);
    36          }
    37          Err(err) => {
    38              eprintln!("{}", err);
    39              process::exit(1);
    40          }
    41      }
    42  }
    43  
    44  // Commands
    45  
    46  fn print(input_opts: InputOpts, output_opts: OutputOpts) -> Result<String> {
    47      let (graph, warnings) = load_graph(&input_opts)?;
    48      for warning in warnings {
    49          eprint!("{}", warning);
    50      }
    51  
    52      if output_opts.full {
    53          return Ok(write::to_text(&graph, TextFormat::Full).to_string());
    54      }
    55  
    56      // FIXME this is here purely to star us off with a Subgraph
    57      let mut graph = graph.filter_vertices(|_| true);
    58  
    59      graph = scope_graph(graph, &output_opts);
    60  
    61      print_output(graph, &output_opts)
    62  }
    63  
    64  fn diff(opts: &DiffOpts) -> Result<String> {
    65      let (graph, warnings) = load_graph(&opts.input_opts)?;
    66      for warning in warnings {
    67          eprint!("{}", warning);
    68      }
    69      let impact_graph = graph.reverse();
    70  
    71      // Get changes
    72  
    73      let components: Vec<&Path> = graph.vertices().map(|path| Path::new(path)).collect();
    74  
    75      let changed = changed_components(components, &opts)?;
    76      let affected: HashSet<_> = impact_graph
    77          .filter_vertices(|v| changed.contains(v))
    78          .expand()
    79          .vertices()
    80          .collect();
    81  
    82      // Scope
    83  
    84      // FIXME this is here purely to star us off with a Subgraph
    85      let mut graph = graph.filter_vertices(|_| true);
    86      graph = scope_graph(graph, &opts.output_opts);
    87  
    88      graph = graph.filter_vertices(|v| affected.contains(&v));
    89  
    90      if opts.rebuild_strong {
    91          graph = graph.expand_via(|e| *e == Dependency::Strong)
    92      };
    93  
    94      // Output
    95  
    96      if opts.output_opts.full {
    97          return Ok(write::to_text(&graph, TextFormat::Full).to_string());
    98      }
    99  
   100      print_output(graph, &opts.output_opts)
   101  }
   102  
   103  // Support functions
   104  
   105  fn changed_components(components: Vec<&Path>, opts: &DiffOpts) -> Result<HashSet<String>> {
   106      Ok(match opts.changes {
   107          cli::Source::Stdin => io::stdin().lock().lines().flatten().collect(),
   108          cli::Source::Git => {
   109              let mut git = Git::new(execute);
   110  
   111              let mode = if opts.main_branch {
   112                  Mode::Main(opts.base_branch.clone())
   113              } else {
   114                  Mode::Feature(opts.base_commit.clone())
   115              };
   116  
   117              git.diff(mode)?
   118          }
   119      }
   120      .into_iter()
   121      .flat_map(|file_path| {
   122          let file_path = Path::new(&file_path);
   123  
   124          components
   125              .iter()
   126              .filter(|component| file_path.starts_with(component))
   127              .flat_map(|component| component.to_str())
   128              .map(ToOwned::to_owned)
   129              .collect::<Vec<_>>()
   130      })
   131      .collect())
   132  }
   133  
   134  fn execute(command: Vec<String>) -> Result<String, String> {
   135      let prog = &command[0];
   136      let args = &command[1..];
   137  
   138      let out = process::Command::new(prog)
   139          .args(args)
   140          .output()
   141          .map_err(|e| format!("Git call failed: {}", e))?;
   142  
   143      if out.status.success() {
   144          std::str::from_utf8(&out.stdout)
   145              .map(|s| s.to_string())
   146              .map_err(|e| format!("Could not convert git output to string: {}", e))
   147      } else {
   148          let error = std::str::from_utf8(&out.stderr)
   149              .map_err(|e| format!("Could not convert git output to string: {}", e))?;
   150  
   151          Err(error.to_string())
   152      }
   153  }
   154  
   155  fn load_graph(input_opts: &InputOpts) -> Result<(Graph<String, Dependency>, Vec<Warning>)> {
   156      if let Some(path) = &input_opts.full_manifest {
   157          let content = fs::read_to_string(path)?;
   158          Ok(read::repo_manifest(content))
   159      } else {
   160          let manifests = read_manifest_files(&input_opts.dependency_files_glob)?;
   161          Ok(read::manifests(manifests))
   162      }
   163  }
   164  
   165  fn read_manifest_files(glob: &str) -> Result<BTreeMap<String, String>> {
   166      let pattern = glob::Pattern::new(&glob).expect("Malformed manifest search pattern"); // FIXME handle this better
   167  
   168      let manifests: BTreeMap<String, String> = WalkBuilder::new("./")
   169          .hidden(false)
   170          .build()
   171          .filter_map(|r| r.ok())
   172          .filter(|entry| pattern.matches_path(entry.path()))
   173          .flat_map(|manifest| -> Result<_> {
   174              let path = manifest.path();
   175              let component = manifest
   176                  .path()
   177                  .parent()
   178                  .ok_or(anyhow!("cannot find a directory path for: {:?}", path))
   179                  .and_then(|dir| {
   180                      dir.to_str()
   181                          .ok_or(anyhow!("Cannot convert path to string: {:?}", dir))
   182                  })
   183                  .map(|component_path| component_path.trim_start_matches("./").to_owned())?;
   184  
   185              let manifest_content = fs::read_to_string(path)?;
   186  
   187              Ok((component, manifest_content))
   188          })
   189          .collect();
   190  
   191      Ok(manifests)
   192  }
   193  
   194  fn scope_graph<'g>(
   195      graph: Subgraph<'g, String, Dependency>,
   196      opts: &OutputOpts,
   197  ) -> Subgraph<'g, String, Dependency> {
   198      let mut graph = graph;
   199  
   200      if let Some(scope) = &opts.scope {
   201          graph = graph.filter_vertices(|v| v == scope).expand();
   202      };
   203  
   204      if opts.top_level {
   205          graph = graph.roots();
   206      };
   207  
   208      graph
   209  }
   210  
   211  fn print_output<'g>(
   212      graph: Subgraph<'g, String, Dependency>,
   213      output_opts: &OutputOpts,
   214  ) -> Result<String> {
   215      let mut graph = graph;
   216  
   217      let dot_format = if !output_opts.dependencies {
   218          graph = graph.filter_edges(|e| *e == Dependency::Strong);
   219  
   220          DotFormat::Schedule
   221      } else {
   222          DotFormat::Dependencies
   223      };
   224  
   225      if output_opts.dot {
   226          return Ok(write::to_dot(&graph, dot_format).to_string());
   227      }
   228  
   229      Ok(write::to_text(&graph, TextFormat::Simple).to_string())
   230  }