1use std::{
51 collections::{HashMap, HashSet},
52 env,
53 fs::{self, File},
54 io::Write,
55 path::{Path, PathBuf},
56 thread,
57 time::Duration,
58};
59
60use walkdir::WalkDir;
61
62#[derive( Debug )]
64pub struct Downstream {
65 pub packages : Vec<Package>,
66}
67
68impl Default for Downstream {
69 fn default() -> Self {
70 Downstream{ packages: Vec::new() }
71 }
72}
73
74#[derive( Debug )]
84pub struct Package {
85 pub name : String,
87 pub manifest : PathBuf,
89 pub metadata : toml::Value,
91 pub rs_paths : Option<Vec<PathBuf>>,
94}
95
96fn scan_rs_paths( current_dir: impl AsRef<Path>, rs_paths: &mut Vec<PathBuf> ) {
97 if let Ok( entries ) = current_dir.as_ref().read_dir() {
98 for entry in entries {
99 if let Ok( entry ) = entry {
100 let path = entry.path();
101 if path.is_dir() {
102 scan_rs_paths( path, rs_paths );
103 } else if let Some( extention ) = path.extension() {
104 if extention == "rs" {
105 rs_paths.push( path );
106 }
107 }
108 }
109 }
110 }
111}
112
113pub struct Opts {
115 pub watch_manifest : bool,
117 pub watch_rs_files : bool,
119 pub dump_rs_paths : bool,
121}
122
123impl Default for Opts {
124 fn default() -> Opts {
125 Opts {
126 watch_manifest : true,
127 watch_rs_files : false,
128 dump_rs_paths : false,
129 }
130 }
131}
132
133pub fn collect_downstream( Opts{ watch_manifest, watch_rs_files, dump_rs_paths }: Opts ) -> Downstream {
143 let build_name = env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME");
144
145 let manifest_paths = locate_manifest_paths();
146
147 manifest_paths.into_iter().fold( Downstream::default(), |mut inwelling, (manifest_path, upstreams)| {
148 if upstreams.contains( &build_name ) {
149 let cargo_toml =
150 fs::read_to_string( PathBuf::from( &manifest_path ))
151 .expect( &format!( "to read {:?}", manifest_path ))
152 .parse::<toml::Table>()
153 .expect( &format!( "{:?} should be a valid manifest", manifest_path ));
154 let package = cargo_toml.get( "package" )
155 .expect( &format!( "{:?} should contain '[package]' section", manifest_path ));
156 let package_name = package.as_table()
157 .expect( &format!( "[package] section in {:?} should contain key-value pair(s)", manifest_path ))
158 .get( "name" )
159 .expect( &format!( "{:?} should contain package name", manifest_path ))
160 .as_str()
161 .expect( &format!( "{:?}'s package name should be a string", manifest_path ))
162 .to_owned();
163
164 let mut rs_paths = Vec::new();
165
166 if watch_manifest {
167 println!( "cargo:rerun-if-changed={}", manifest_path.to_str().unwrap() );
168 }
169 if dump_rs_paths || watch_rs_files {
170 let manifest_dir = manifest_path.parent().unwrap();
171 scan_rs_paths( &manifest_dir.join( "src" ), &mut rs_paths );
172 scan_rs_paths( &manifest_dir.join( "examples" ), &mut rs_paths );
173 scan_rs_paths( &manifest_dir.join( "tests" ), &mut rs_paths );
174 if watch_rs_files {
175 rs_paths.iter().for_each( |rs_file|
176 println!( "cargo:rerun-if-changed={}", rs_file.to_str().unwrap() ));
177 }
178 }
179 if let Some( metadata ) = package.get( "metadata" ) {
180 if let Some( metadata_inwelling ) = metadata.get("inwelling") {
181 if let Some( metadata_inwelling_build ) = metadata_inwelling.get( &build_name ) {
182 inwelling.packages.push( Package{
183 name : package_name,
184 manifest : manifest_path,
185 metadata : metadata_inwelling_build.clone(),
186 rs_paths : if dump_rs_paths { Some( rs_paths )} else { None },
187 });
188 }
189 }
190 }
191 }
192
193 inwelling
194 })
195}
196
197const MANIFEST_DIR_INWELLING: &'static str = "manifest_dir.inwelling";
199
200fn wait_for_other_builds( build_dir: &Path ) {
201 let mut generated = HashSet::<PathBuf>::new();
202 let mut waiting = true;
203 while waiting {
204 thread::sleep( Duration::from_secs(5) );
205 waiting = false;
206 for entry in WalkDir::new( build_dir ) {
207 let entry = entry.unwrap();
208 let path = entry.path();
209 if generated.insert( path.to_owned() ) {
210 waiting = true;
211 }
212 }
213 }
214 eprintln!("{generated:#?}");
215}
216
217fn locate_manifest_paths() -> HashMap<PathBuf,Vec<String>> {
218 let mut path_bufs = HashMap::new();
219
220 let out_dir = PathBuf::from( env::var( "OUT_DIR" ).expect( "$OUT_DIR should exist." ));
221 let ancestors = out_dir.ancestors();
222 let build_dir = ancestors.skip(2).next().expect( "'build' directory should exist." );
223
224 wait_for_other_builds( &build_dir );
225
226 let mut pending = true;
227 while pending {
228 pending = false;
229 for entry in build_dir.read_dir().expect( &format!( "to list all sub dirs in {:?}", build_dir )) {
230 if let Ok( entry ) = entry {
231 let path = entry.path();
232 if path.is_dir() {
233 let inwelling_file_path = path.join("out").join( MANIFEST_DIR_INWELLING );
234 if inwelling_file_path.exists() {
235 let contents = fs::read_to_string( &inwelling_file_path )
236 .expect( &format!( "to read {:?} to get one manifest path", inwelling_file_path ));
237 let mut lines = contents.lines();
238 let manifest_dir = lines.next()
239 .expect( &format!( "{:?} should contain the line of manifest dir.", inwelling_file_path ));
240 path_bufs
241 .entry( PathBuf::from( manifest_dir ).join( "Cargo.toml" ))
242 .or_insert_with( || lines.map( ToOwned::to_owned ).collect() );
243 }}}}}
244 path_bufs
245}
246
247pub fn to( upstream: &str ) {
251 let out_path =
252 PathBuf::from(
253 env::var( "OUT_DIR" )
254 .expect( "$OUT_DIR should exist." )
255 ).join( MANIFEST_DIR_INWELLING );
256 if out_path.exists() {
257 let mut f = File::options().append( true ).open( &out_path )
258 .expect( &format!( "{:?} should be opened for appending.", out_path ));
259 writeln!( &mut f, "{}", upstream )
260 .expect( &format!( "An upstream name should be appended to {:?}.", out_path ));
261 } else {
262 let manifest_dir =
263 env::var( "CARGO_MANIFEST_DIR" )
264 .expect( "$CARGO_MANIFEST_DIR should exist." );
265 fs::write(
266 out_path,
267 format!( "{}\n{}\n", manifest_dir, upstream )
268 ).expect( "manifest_dir.txt generated." );
269 }
270}