use std::{
collections::{HashMap, HashSet},
env,
fs::{self, File},
io::Write,
path::{Path, PathBuf},
thread,
time::Duration,
};
use walkdir::WalkDir;
#[derive( Debug )]
pub struct Downstream {
pub packages : Vec<Package>,
}
impl Default for Downstream {
fn default() -> Self {
Downstream{ packages: Vec::new() }
}
}
#[derive( Debug )]
pub struct Package {
pub name : String,
pub manifest : PathBuf,
pub metadata : toml::Value,
pub rs_paths : Option<Vec<PathBuf>>,
}
fn scan_rs_paths( current_dir: impl AsRef<Path>, rs_paths: &mut Vec<PathBuf> ) {
if let Ok( entries ) = current_dir.as_ref().read_dir() {
for entry in entries {
if let Ok( entry ) = entry {
let path = entry.path();
if path.is_dir() {
scan_rs_paths( path, rs_paths );
} else if let Some( extention ) = path.extension() {
if extention == "rs" {
rs_paths.push( path );
}
}
}
}
}
}
pub struct Opts {
pub watch_manifest : bool,
pub watch_rs_files : bool,
pub dump_rs_paths : bool,
}
impl Default for Opts {
fn default() -> Opts {
Opts {
watch_manifest : true,
watch_rs_files : false,
dump_rs_paths : false,
}
}
}
pub fn collect_downstream( Opts{ watch_manifest, watch_rs_files, dump_rs_paths }: Opts ) -> Downstream {
let build_name = env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME");
let manifest_paths = locate_manifest_paths();
manifest_paths.into_iter().fold( Downstream::default(), |mut inwelling, (manifest_path, upstreams)| {
if upstreams.contains( &build_name ) {
let cargo_toml =
fs::read_to_string( PathBuf::from( &manifest_path ))
.expect( &format!( "to read {:?}", manifest_path ))
.parse::<toml::Table>()
.expect( &format!( "{:?} should be a valid manifest", manifest_path ));
let package = cargo_toml.get( "package" )
.expect( &format!( "{:?} should contain '[package]' section", manifest_path ));
let package_name = package.as_table()
.expect( &format!( "[package] section in {:?} should contain key-value pair(s)", manifest_path ))
.get( "name" )
.expect( &format!( "{:?} should contain package name", manifest_path ))
.as_str()
.expect( &format!( "{:?}'s package name should be a string", manifest_path ))
.to_owned();
let mut rs_paths = Vec::new();
if watch_manifest {
println!( "cargo:rerun-if-changed={}", manifest_path.to_str().unwrap() );
}
if dump_rs_paths || watch_rs_files {
let manifest_dir = manifest_path.parent().unwrap();
scan_rs_paths( &manifest_dir.join( "src" ), &mut rs_paths );
scan_rs_paths( &manifest_dir.join( "examples" ), &mut rs_paths );
scan_rs_paths( &manifest_dir.join( "tests" ), &mut rs_paths );
if watch_rs_files {
rs_paths.iter().for_each( |rs_file|
println!( "cargo:rerun-if-changed={}", rs_file.to_str().unwrap() ));
}
}
if let Some( metadata ) = package.get( "metadata" ) {
if let Some( metadata_inwelling ) = metadata.get("inwelling") {
if let Some( metadata_inwelling_build ) = metadata_inwelling.get( &build_name ) {
inwelling.packages.push( Package{
name : package_name,
manifest : manifest_path,
metadata : metadata_inwelling_build.clone(),
rs_paths : if dump_rs_paths { Some( rs_paths )} else { None },
});
}
}
}
}
inwelling
})
}
const MANIFEST_DIR_INWELLING: &'static str = "manifest_dir.inwelling";
fn wait_for_other_builds( build_dir: &Path ) {
let mut generated = HashSet::<PathBuf>::new();
let mut waiting = true;
while waiting {
thread::sleep( Duration::from_secs(5) );
waiting = false;
for entry in WalkDir::new( build_dir ) {
let entry = entry.unwrap();
let path = entry.path();
if generated.insert( path.to_owned() ) {
waiting = true;
}
}
}
eprintln!("{generated:#?}");
}
fn locate_manifest_paths() -> HashMap<PathBuf,Vec<String>> {
let mut path_bufs = HashMap::new();
let out_dir = PathBuf::from( env::var( "OUT_DIR" ).expect( "$OUT_DIR should exist." ));
let ancestors = out_dir.ancestors();
let build_dir = ancestors.skip(2).next().expect( "'build' directory should exist." );
wait_for_other_builds( &build_dir );
let mut pending = true;
while pending {
pending = false;
for entry in build_dir.read_dir().expect( &format!( "to list all sub dirs in {:?}", build_dir )) {
if let Ok( entry ) = entry {
let path = entry.path();
if path.is_dir() {
let inwelling_file_path = path.join("out").join( MANIFEST_DIR_INWELLING );
if inwelling_file_path.exists() {
let contents = fs::read_to_string( &inwelling_file_path )
.expect( &format!( "to read {:?} to get one manifest path", inwelling_file_path ));
let mut lines = contents.lines();
let manifest_dir = lines.next()
.expect( &format!( "{:?} should contain the line of manifest dir.", inwelling_file_path ));
path_bufs
.entry( PathBuf::from( manifest_dir ).join( "Cargo.toml" ))
.or_insert_with( || lines.map( ToOwned::to_owned ).collect() );
}}}}}
path_bufs
}
pub fn to( upstream: &str ) {
let out_path =
PathBuf::from(
env::var( "OUT_DIR" )
.expect( "$OUT_DIR should exist." )
).join( MANIFEST_DIR_INWELLING );
if out_path.exists() {
let mut f = File::options().append( true ).open( &out_path )
.expect( &format!( "{:?} should be opened for appending.", out_path ));
writeln!( &mut f, "{}", upstream )
.expect( &format!( "An upstream name should be appended to {:?}.", out_path ));
} else {
let manifest_dir =
env::var( "CARGO_MANIFEST_DIR" )
.expect( "$CARGO_MANIFEST_DIR should exist." );
fs::write(
out_path,
format!( "{}\n{}\n", manifest_dir, upstream )
).expect( "manifest_dir.txt generated." );
}
}