use cargo_metadata::{
CargoOpt,
MetadataCommand,
};
use pals::Pid;
use std::{
collections::HashMap,
env,
path::{Path, PathBuf},
process,
};
#[derive( Debug )]
pub struct Inwelling {
pub sections : Vec<Section>,
}
impl Default for Inwelling {
fn default() -> Self {
Inwelling{ sections: Vec::new() }
}
}
#[derive( Debug )]
pub struct Section {
pub pkg : String,
pub manifest : PathBuf,
pub metadata : serde_json::value::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 inwelling( Opts{ watch_manifest, watch_rs_files, dump_rs_paths }: Opts ) -> Inwelling {
let mut command = MetadataCommand::new();
let mut manifest_path = None;
let mut target_dir_defined_in_cmdline = false;
let pals = pals::pals();
if let Ok( pals ) = pals {
if let Some( parent ) = pals.parent_of( Pid( process::id() )) {
if let Some( parent ) = pals.parent_of( parent.ppid ) {
let mut argv = parent.argv();
while let Some( arg ) = argv.next() {
match arg {
"--all-features" => {
command.features( CargoOpt::AllFeatures );
},
"--features" => if let Some( features ) = argv.next() {
command.features( CargoOpt::SomeFeatures( features
.split_ascii_whitespace()
.map( ToOwned::to_owned )
.collect()
));
},
"--no-default-features" => {
command.features( CargoOpt::NoDefaultFeatures );
},
"--manifest-path" if cfg!( unix ) => if let Some( path ) = argv.next() {
manifest_path = Some( PathBuf::from( path ));
}
"--target-dir" => target_dir_defined_in_cmdline = true,
_ => (),
}
}
}
}
}
let manifest_path = manifest_path.unwrap_or_else( ||
if let Ok( cwd ) = env::var("PWD") {
return PathBuf::from( cwd ).join( "Cargo.toml" );
} else {
if !target_dir_defined_in_cmdline {
if let Ok( out_dir ) = env::var("OUT_DIR") {
let out_dir = Path::new( &out_dir );
let ancestors = out_dir.ancestors();
if let Some( manifest_dir ) = ancestors.skip(5).next() {
return manifest_dir.join( "Cargo.toml" );
}
}
}
panic!("Failed to locate manifest path. Consider providing PWD environment variable.")
}
);
if !manifest_path.exists() {
panic!( "{:?} should be manifest file", manifest_path );
}
let metadata = command
.manifest_path( &manifest_path )
.exec()
.expect("cargo metadata command should be executed successfully.");
let build_name = env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME");
let enabled_features = metadata
.resolve
.expect("package dependencies resolved.")
.nodes.iter().fold( HashMap::new(), |mut map, node| {
map.insert( node.id.clone(), node.features.clone() );
map
});
metadata.packages.into_iter().fold( Inwelling::default(), |mut inwelling, mut pkg| {
let pkg_id = pkg.id.clone();
let mut rs_paths = Vec::new();
let enabled = pkg.metadata.get( &format!( "inwelling-{}", &build_name ))
.and_then( |section| section.get( "feature" ))
.map( |feature| {
let feature = feature.as_str().expect("feature name should be str.");
enabled_features[ &pkg_id ]
.iter()
.find( |&enabled_feature| enabled_feature == &feature )
.is_some()
})
.unwrap_or( true );
if enabled {
if watch_manifest {
println!( "cargo:rerun-if-changed={}", pkg.manifest_path.to_str().unwrap() );
}
if dump_rs_paths || watch_rs_files {
let manifest_path = pkg.manifest_path.parent().unwrap();
scan_rs_paths( &manifest_path.join( "src" ), &mut rs_paths );
scan_rs_paths( &manifest_path.join( "examples" ), &mut rs_paths );
scan_rs_paths( &manifest_path.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( section ) = pkg.metadata.get_mut("inwelling") {
if let Some( metadata ) = section.get_mut( &build_name ) {
inwelling.sections.push( Section{
pkg : pkg.name,
manifest : pkg.manifest_path,
metadata : metadata.take(),
rs_paths : if dump_rs_paths { Some( rs_paths )} else { None },
});
}
}
}
inwelling
})
}