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 }