#[allow(unused)]
use {
anyhow::{Context, Error, Result},
clap::Parser,
jlogger::{jdebug, jerror, jinfo, jwarn},
log::{debug, error, info, warn, LevelFilter},
std::{
collections::HashMap,
env,
ffi::OsStr,
fs,
io::Write,
path::{Path, PathBuf},
},
ydevlib::{
base_config::BaseConfig,
layer::Layer,
meta::MetaTopDir,
recipe::Recipe,
template::TemplateFile,
utils::{scan_dir, scan_dir_for_dir, scan_dir_for_file, verify_content},
yocto::{DataStore, YoctoVar},
},
};
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about= None)]
struct Cli {
#[clap(short = 'l', long = "log-file")]
log_file: Option<String>,
#[clap(short, long, parse(from_occurrences))]
verbose: usize,
#[clap(subcommand)]
command: YdevCmd,
}
#[derive(clap::Subcommand, Debug)]
enum YdevCmd {
Create {
#[clap(short='b', long="build-dir", default_value_t= String::from("build"))]
build_dir: String,
#[clap(short = 'y')]
yocto_top_dir: Option<String>,
#[clap(short = 'd')]
default_config_file_dir: Option<String>,
#[clap(short = 'm', long)]
machine: Option<String>,
features: Vec<String>,
},
InfoRecipe {
#[clap(short = 'r')]
recipe: String,
#[clap(short = 'y')]
yocto_top_dir: Option<String>,
#[clap(short = 's')]
start_with: bool,
#[clap(short = 'v')]
version: Option<String>,
},
}
pub fn machines_features(
templates: &[PathBuf],
) -> (HashMap<String, TemplateFile>, HashMap<String, TemplateFile>) {
let mut machines = HashMap::<String, TemplateFile>::new();
let mut features = HashMap::<String, TemplateFile>::new();
for t in templates {
scan_dir_for_dir(t.as_path(), &mut |dir: PathBuf| -> bool {
if let Some(f) = dir.file_name() {
if f == OsStr::new("machine") {
scan_dir_for_dir(dir.as_path(), &mut |mdir: PathBuf| -> bool {
if let Some(t) = TemplateFile::new(mdir.as_path()) {
if let Some(entry) = machines.get(t.get_filename()) {
jerror!("Multiple machine found:");
jerror!("\t{}", entry.get_pathname());
jerror!("\t{}", t.get_pathname());
std::process::exit(1);
}
machines.insert(t.get_filename().to_string(), t);
}
true
});
}
if f == OsStr::new("feature") {
scan_dir_for_dir(dir.as_path(), &mut |fdir: PathBuf| -> bool {
if let Some(t) = TemplateFile::new(fdir.as_path()) {
if let Some(entry) = machines.get(t.get_filename()) {
jerror!("Multiple feature found:");
jerror!("\t{}", entry.get_pathname());
jerror!("\t{}", t.get_pathname());
std::process::exit(1);
}
features.insert(t.get_filename().to_string(), t);
}
true
});
}
}
true
});
}
(machines, features)
}
pub fn templates(metas: &[PathBuf]) -> Vec<PathBuf> {
let mut templates = Vec::<PathBuf>::new();
for t in metas {
scan_dir_for_dir(t.as_path(), &mut |dir: PathBuf| -> bool {
if let Some(f) = dir.file_name() {
if f == OsStr::new("templates") {
templates.push(dir);
}
}
true
});
}
templates
}
pub fn metas(topdir: &Path) -> Vec<PathBuf> {
let mut metas = Vec::<PathBuf>::new();
if topdir.is_dir() {
scan_dir_for_dir(topdir, &mut |p: PathBuf| -> bool {
if let Some(l) = p.file_name() {
if let Some(l) = l.to_str() {
if l.starts_with("meta-") {
metas.push(p);
}
}
}
true
});
}
metas
}
fn do_create(
yocto_top_dir: Option<String>,
build_dir: String,
default_config_file_dir: Option<String>,
cli_machine: Option<String>,
cli_features: Vec<String>,
) -> Result<()> {
let meta_top: MetaTopDir;
if let Some(p) = yocto_top_dir {
meta_top = MetaTopDir::new(p.as_str())?;
} else {
let d = env::var("YDEV_META_TOP").unwrap_or("yocto".to_string());
meta_top = MetaTopDir::new(d.as_str())?;
}
let build_dir = env::current_dir().unwrap().join(build_dir);
info!("BUILD-DIR : {}", build_dir.display());
info!("METATOP : {}", meta_top);
if build_dir.exists() {
jerror!("Build directory {} already exists", build_dir.display());
std::process::exit(1);
}
let base_config = BaseConfig::new(
meta_top.as_str(),
build_dir.to_str().unwrap(),
default_config_file_dir,
)?;
let mut target_features = Vec::<String>::new();
let metas = metas(meta_top.pathbuf().as_path());
let templates = templates(&metas);
let (mut machines, mut features) = machines_features(&templates);
let mut conf_result = String::new();
let mut bblayer_result = String::new();
if let Some(content) = base_config.conf() {
conf_result.push_str(&content);
conf_result.push('\n');
}
if let Some(content) = base_config.bblayer() {
bblayer_result.push_str(&content);
bblayer_result.push('\n');
}
let mut machine_string = String::new();
machine_string.push_str("Available Machines:\n");
for m in machines.values() {
machine_string
.push_str(format!(" {:<20}({})\n", m.get_filename(), m.get_pathname()).as_str());
}
let target_machine_name = cli_machine.ok_or_else(|| {
let mut error_string = String::new();
error_string.push_str("No machine specified.\n");
error_string.push_str(&machine_string.as_str());
Error::msg(error_string)
})?;
let target_machine = machines
.get_mut(target_machine_name.as_str())
.ok_or_else(|| {
let mut error_string = String::new();
error_string.push_str(format!("No machine {} found.\n", target_machine_name).as_str());
error_string.push_str(&machine_string.as_str());
Error::msg(error_string)
})?;
target_machine
.load_content()
.with_context(|| String::from("Failed to load machine content"))?;
if let Some(conf) = target_machine.get_conf() {
conf_result.push_str(&conf);
conf_result.push('\n');
}
if let Some(bblayer) = target_machine.get_bblayer() {
bblayer_result.push_str(&bblayer);
bblayer_result.push('\n');
}
let request_features = cli_features;
let mut feature_string = String::new();
feature_string.push_str("Features:\n");
for f in features.values() {
feature_string
.push_str(format!(" {:<20}({})\n", f.get_filename(), f.get_pathname()).as_str());
}
for f in &request_features {
let fe = features.get_mut(f).ok_or_else(|| {
let mut error_string = String::new();
error_string.push_str(format!("No feature {} found.\n", f).as_str());
error_string.push_str(feature_string.as_str());
Error::msg(error_string)
})?;
fe.load_content()
.with_context(|| format!("Failed to load feature {} content", fe.get_pathname()))?;
for de in features.get(f).unwrap().get_depends().keys() {
if features.get(de).is_some() {
target_features.push(de.to_string());
} else {
let mut error_string = String::new();
error_string
.push_str(format!("No feature {} required in {} found.", de, f).as_str());
error_string.push_str(feature_string.as_str());
return Err(Error::msg(error_string));
}
}
target_features.push(f.to_string());
}
let mut target_features_list = String::new();
for f in &target_features {
let fe = features.get_mut(f).unwrap();
fe.load_content()
.with_context(|| format!("Failed to load feature {} content", fe.get_pathname()))?;
target_features_list.push_str(format!("{}\n", fe.get_pathname()).as_str());
if let Some(content) = fe.get_conf() {
conf_result.push_str(&content);
conf_result.push('\n');
}
if let Some(content) = fe.get_bblayer() {
bblayer_result.push_str(&content);
bblayer_result.push('\n');
}
}
fs::create_dir(&build_dir)
.with_context(|| format!("Failed to create {}", build_dir.display()))?;
let build_conf_dir = build_dir.join("conf");
fs::create_dir(&build_conf_dir)
.with_context(|| format!("Failed to create {}", build_conf_dir.display()))?;
let local_conf_file = build_conf_dir.join("local.conf");
fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&local_conf_file)
.with_context(|| format!("Failed to create {}", local_conf_file.display()))?
.write_all(conf_result.as_bytes())
.with_context(|| format!("Failed to write {}", local_conf_file.display()))?;
let bblayer_conf_file = build_conf_dir.join("bblayers.conf");
fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&bblayer_conf_file)
.with_context(|| format!("Failed to create {}", bblayer_conf_file.display()))?
.write_all(bblayer_result.as_bytes())
.with_context(|| format!("Failed to write {}", bblayer_conf_file.display()))?;
let feature_files = build_conf_dir.join("ydev-features");
fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(feature_files.as_path())
.with_context(|| format!("Failed to create {}", bblayer_conf_file.display()))?
.write_all(target_features_list.as_bytes())
.with_context(|| format!("Failed to write {}", bblayer_conf_file.display()))?;
if let Some(dev_setup_str) = base_config.dev_setup() {
let dev_setup_file = build_dir.join("dev-init-build-env");
fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&dev_setup_file)
.with_context(|| format!("Failed to create {}", dev_setup_file.display()))?
.write_all(dev_setup_str.as_bytes())
.with_context(|| format!("Failed to write {}", dev_setup_file.display()))?;
}
Ok(())
}
fn do_info_recipe_all(
recipe: &str,
start_with: bool,
version: Option<&str>,
topdir: &str,
) -> Result<()> {
let mut process = Vec::<String>::new();
let mut bb_recipes = HashMap::<String, Vec<Recipe>>::new();
let mut bbappend_recipes = HashMap::<String, Vec<Recipe>>::new();
process.push(topdir.to_owned());
while !process.is_empty() {
let mut next = Vec::<String>::new();
for d in process.iter() {
let _ = scan_dir(Path::new(d), &mut |p| -> bool {
if p.is_dir() {
next.push(p.to_str().unwrap().to_string());
} else if p.is_file() {
if let Ok(r) = Recipe::new(p.to_str().unwrap()) {
let mut should_add = false;
if start_with && r.name().starts_with(recipe) {
if version.is_none() {
should_add = true;
} else if r.version().is_some() {
if version == Some("%") || r.version() == Some("%") {
should_add = true;
} else if version == r.version() {
should_add = true;
}
}
} else if r.name() == recipe {
if version.is_none() {
should_add = true;
} else if r.version().is_some() {
if version == Some("%") || r.version() == Some("%") {
should_add = true;
} else if version == r.version() {
should_add = true;
}
}
}
if should_add {
if r.is_append() {
if let Some(v) = bbappend_recipes.get_mut(r.name()) {
v.push(r);
} else {
let mut v = Vec::new();
let key = r.name().to_string();
v.push(r);
bbappend_recipes.insert(key, v);
}
} else {
if let Some(v) = bb_recipes.get_mut(r.name()) {
v.push(r);
} else {
let mut v = Vec::new();
let key = r.name().to_string();
v.push(r);
bb_recipes.insert(key, v);
}
}
}
}
}
true
});
}
process = next;
}
if bb_recipes.is_empty() && bbappend_recipes.is_empty() {
return Err(Error::msg(format!("No recipes for {} found.", recipe)));
}
let mut printed = HashMap::<String, bool>::new();
for k in bb_recipes.keys() {
info!("Recipes for {}:", k);
if let Some(bbv) = bb_recipes.get(k) {
if !bbv.is_empty() {
for t in bbv {
info!(" {}", t.path());
}
}
}
if let Some(bbappendv) = bbappend_recipes.get(k) {
if !bbappendv.is_empty() {
for t in bbappendv {
info!(" {}", t.path());
}
}
}
printed.insert(k.to_string(), true);
}
for k in bbappend_recipes.keys() {
if printed.get(k).is_some() {
continue;
}
info!("Recipes for {}:", k);
if let Some(bbappendv) = bbappend_recipes.get(k) {
if !bbappendv.is_empty() {
for t in bbappendv {
info!(" {}", t.path());
}
}
}
}
Ok(())
}
fn do_info_recipe_build(recipe: &str, start_with: bool, version: Option<&str>) -> Result<()> {
let current_dir = env::current_dir()?;
let build_dir = current_dir
.to_str()
.ok_or(Error::msg("Invalid build directory"))?;
let mut data_store = DataStore::new();
data_store.from_conf_file(format!("{}/conf/bblayers.conf", build_dir).as_str(), None)?;
let bblayer = data_store
.value("BBLAYERS")
.ok_or(Error::msg("No BBLAYERS found."))?;
let mut layers = Vec::<Layer>::new();
let expand_value = data_store.expand_value(bblayer.value(None), None)?;
for l in expand_value.split(" ").filter(|a| !a.trim().is_empty()) {
layers.push(Layer::new(l, Some(&data_store))?);
}
let mut bb_recipes = HashMap::<String, Vec<&Recipe>>::new();
let mut bbappend_recipes = HashMap::<String, Vec<&Recipe>>::new();
for l in &layers {
let rhash = l.recipes();
for (r, rvec) in rhash {
let mut should_add = false;
if start_with && r.starts_with(recipe) {
should_add = true;
} else if r == recipe {
should_add = true;
}
if should_add {
for b in rvec {
let match_version = || -> bool {
if version.is_none() {
return true;
}
if b.version().is_none() {
return false;
}
if version == Some("%") || b.version() == Some("%") {
return true;
}
if version == b.version() {
true
} else {
false
}
};
if !match_version() {
continue;
}
if b.is_append() {
if let Some(vec) = bbappend_recipes.get_mut(r) {
vec.push(b);
} else {
let mut vec = Vec::new();
vec.push(b);
bbappend_recipes.insert(r.clone(), vec);
}
} else {
if let Some(vec) = bb_recipes.get_mut(r) {
vec.push(b);
} else {
let mut vec = Vec::new();
vec.push(b);
bb_recipes.insert(r.clone(), vec);
}
}
}
}
}
}
if bb_recipes.is_empty() && bbappend_recipes.is_empty() {
return Err(Error::msg(format!("No recipes for {} found.", recipe)));
}
let mut printed = HashMap::<String, bool>::new();
for k in bb_recipes.keys() {
info!("Recipes for {}:", k);
if let Some(bbv) = bb_recipes.get(k) {
if !bbv.is_empty() {
for t in bbv {
info!(" {}", t.path());
}
}
}
if let Some(bbappendv) = bbappend_recipes.get(k) {
if !bbappendv.is_empty() {
for t in bbappendv {
info!(" {}", t.path());
}
}
}
printed.insert(k.to_string(), true);
}
for k in bbappend_recipes.keys() {
if printed.get(k).is_some() {
continue;
}
info!("Recipes for {}:", k);
if let Some(bbappendv) = bbappend_recipes.get(k) {
if !bbappendv.is_empty() {
for t in bbappendv {
info!(" {}", t.path());
}
}
}
}
Ok(())
}
fn do_info_recipe(
recipe: String,
start_with: bool,
version: Option<String>,
yocto_top_dir: Option<String>,
) -> Result<()> {
if let Some(topdir) = yocto_top_dir {
do_info_recipe_all(
recipe.as_str(),
start_with,
version.as_deref(),
topdir.as_str(),
)
} else {
do_info_recipe_build(recipe.as_str(), start_with, version.as_deref())
}
}
fn main() {
let cli = Cli::parse();
let max_level = match cli.verbose {
0 => LevelFilter::Info,
1 => LevelFilter::Debug,
_ => LevelFilter::Trace,
};
if let Some(log) = &cli.log_file {
jlogger::JloggerBuilder::new()
.log_console(false)
.log_file(&log)
.log_time(false)
.max_level(max_level)
.build();
} else {
jlogger::JloggerBuilder::new()
.log_console(true)
.max_level(max_level)
.log_time(false)
.build();
}
match cli.command {
YdevCmd::Create {
build_dir,
yocto_top_dir,
default_config_file_dir,
machine,
features,
} => {
info!("Creating build enviornment...");
if let Err(e) = do_create(
yocto_top_dir,
build_dir,
default_config_file_dir,
machine,
features,
) {
error!("{}", e);
}
}
YdevCmd::InfoRecipe {
recipe,
start_with,
yocto_top_dir,
version,
} => {
if let Err(e) = do_info_recipe(recipe, start_with, version, yocto_top_dir) {
error!("{}", e);
}
}
}
}