mod bom;
mod group;
mod package;
#[cfg(test)]
mod tests;
use crate::origen::revision_control::RevisionControlAPI;
use bom::BOM;
use clap::ArgMatches;
use group::Group;
use origen::core::file_handler::File;
use origen::core::term;
use package::Package;
use std::path::{Path, PathBuf};
use std::process::exit;
use std::{env, fs};
use tera::{Context, Tera};
static BOM_FILE: &str = "bom.toml";
static README_FILE: &str = "README.md";
pub fn run(matches: &ArgMatches) {
match matches.subcommand_name() {
Some("init") => {
let dir = get_dir_or_pwd(matches.subcommand_matches("init").unwrap(), false);
if !dir.exists() {
fs::create_dir_all(&dir).expect(&format!(
"Couldn't create '{}', do you have the required permissions?",
dir.display()
));
}
validate_path(&dir, true, true);
let dir = dir.canonicalize().unwrap();
let f = dir.join(BOM_FILE);
if f.exists() {
error_and_exit(
&format!("Found an existing '{}' in '{}'", BOM_FILE, dir.display()),
Some(1),
);
}
File::create(f).write(include_str!("templates/project_bom.toml"));
let f = dir.join(README_FILE);
if !f.exists() {
File::create(f).write(include_str!("templates/project_README.md"));
}
}
Some("create") => {
let path = matches
.subcommand_matches("create")
.unwrap()
.value_of("path")
.unwrap();
let mut path = PathBuf::from(path);
if !path.is_absolute() {
path = pwd().join(path);
}
let mut dir = path.clone();
while !dir.exists() {
dir.pop();
}
let bom = BOM::for_dir(&dir);
if bom.files.len() == 0 {
error_and_exit(
&format!(
"No BOM files were found within the parent directories of '{}'",
path.display()
),
Some(1),
);
}
if path.exists() && path.is_dir() {
let is_empty: bool = match path.read_dir() {
Ok(mut x) => x.next().is_none(),
Err(_e) => {
error_and_exit(
&format!(
"There was a problem reading directory '{}', do you have permission to read it?:",
path.display(),
),
Some(1),
);
unreachable!()
}
};
if !is_empty {
let b = BOM::for_dir(&path);
if b.is_workspace() {
error_and_exit(
&format!("The workspace '{}' already exists, did you mean to run the 'update' command instead?", path.display()),
Some(1),
);
} else {
error_and_exit(
&format!("The directory '{}' already exists, though it does not appear to be a workspace, did you give the correct path?", path.display()),
Some(1),
);
}
}
} else {
validate_path(&path, false, true);
}
if bom.is_workspace() {
error_and_exit(
&format!("A workspace can't be created within a workspace.\nCan't create a workspace at '{}' because '{}' is a workspace", path.display(),
bom.files.last().unwrap().parent().unwrap().display()
),
Some(1),
);
}
fs::create_dir_all(&path).expect(&format!(
"Couldn't create '{}', do you have the required permissions?",
path.display()
));
let mut tera = Tera::default();
let mut context = Context::new();
let packages: Vec<&Package> = bom.packages.iter().map(|(_id, pkg)| pkg).collect();
context.insert("packages", &packages);
let groups: Vec<&Group> = bom.groups.iter().map(|(_id, grp)| grp).collect();
context.insert("groups", &groups);
let contents = tera
.render_str(include_str!("templates/workspace_bom.toml.tera"), &context)
.unwrap();
File::create(path.join(BOM_FILE)).write(&contents);
log_info!("Fetching {} packages", bom.packages.len());
let mut errors = false;
for (id, package) in &bom.packages {
display!("Populating '{}' ... ", id);
match package.create(&path) {
Ok(()) => display_greenln!("OK"),
Err(e) => {
log_error!("{}", e);
log_error!("Failed to create package '{}'", id);
errors = true;
}
}
}
if !bom.links.is_empty() {
display!("Creating links ... ");
let bom = BOM::for_dir(&path);
match bom.create_links(false) {
Ok(_) => display_greenln!("OK"),
Err(e) => {
log_error!("There was a problem creating the workspace's links:");
log_error!("{}", e);
errors = true;
}
}
}
if errors {
exit_error!();
} else {
exit(0);
}
}
Some("update") => {
let matches = matches.subcommand_matches("update").unwrap();
let force = matches.is_present("force");
let mut links = matches.is_present("links");
if let Some(packages) = matches.values_of("packages") {
if packages.map(|p| p).collect::<Vec<&str>>().contains(&"all") {
links = true;
}
}
let package_ids = get_package_ids_from_args(matches, false);
let bom = BOM::for_dir(&pwd());
if !bom.is_workspace() {
error_and_exit("The update command must be run from within an existing workspace, please cd to your target workspace and try again", Some(1));
}
let mut errors = false;
let mut packages_requiring_force: Vec<&str> = vec![];
let mut packages_with_conflicts: Vec<&str> = vec![];
log_info!("Updating packages: {}", &package_ids.join(" ,"));
for id in package_ids {
if let Some(package) = bom.packages.get(&id) {
display!("Updating '{}' ... ", package.id);
match package.update(bom.root(), force) {
Ok((force_required, conflicts)) => {
if conflicts {
display_redln!("CONFLICTS");
packages_with_conflicts.push(&package.id);
} else if !force_required {
display_greenln!("OK");
} else {
packages_requiring_force.push(&package.id);
}
}
Err(e) => {
log_error!("{}", e);
log_error!("Failed to update package '{}'", package.id);
errors = true;
}
}
} else {
log_warning!(
"A group refers to package '{}' but no package with that ID is defined",
id
);
}
}
let mut links_force_required = false;
if links {
if !bom.links.is_empty() {
display!("Updating links ... ");
match bom.create_links(force) {
Ok(force_required) => {
if !force_required {
display_greenln!("OK");
} else {
links_force_required = true;
}
}
Err(e) => {
log_error!("There was a problem creating the workspace's links:");
log_error!("{}", e);
errors = true;
}
}
}
}
if errors {
exit_error!();
} else {
if links_force_required
|| !packages_requiring_force.is_empty()
|| !packages_with_conflicts.is_empty()
{
if links_force_required || !packages_requiring_force.is_empty() {
displayln!("");
display_redln!("The following packages were not updated successfully due to the possibility of losing local work, you can run the following command to force alignment with the current BOM:");
displayln!("");
let mut command = " origen proj update --force".to_string();
if packages_requiring_force.is_empty() {
command += " --links";
} else {
command += " ";
command += &packages_requiring_force.join(" ");
}
displayln!("{}", command);
}
if !packages_with_conflicts.is_empty() {
displayln!("");
display_redln!("The following packages were not updated successfully due to conflicts when trying to merge local work, you can run the following command to force alignment with the current BOM:");
displayln!("");
let mut command = " origen proj update --force".to_string();
command += " ";
command += &packages_with_conflicts.join(" ");
displayln!("{}", command);
}
exit(1);
} else {
exit(0);
}
}
}
Some("mods") => {
let matches = matches.subcommand_matches("mods").unwrap();
let package_ids = get_package_ids_from_args(matches, true);
let bom = BOM::for_dir(&pwd());
if !bom.is_workspace() {
error_and_exit("The mods command must be run from within an existing workspace, please cd to your target workspace and try again", Some(1));
}
for id in package_ids {
if let Some(package) = bom.packages.get(&id) {
if package.has_repo() {
display!("{} ... ", package.id);
let rc = package.rc(bom.root()).unwrap();
match rc.status(None) {
Err(e) => {
error_and_exit(&e.to_string(), Some(1));
}
Ok(status) => {
if status.is_modified() {
display_redln!("Modified");
if !status.added.is_empty() {
displayln!(" ADDED");
for file in &status.added {
displayln!(" {}", file.display());
}
}
if !status.removed.is_empty() {
displayln!(" DELETED");
for file in &status.removed {
displayln!(" {}", file.display());
}
}
if !status.changed.is_empty() {
displayln!(" CHANGED");
for file in &status.changed {
displayln!(" {}", file.display());
}
}
if !status.conflicted.is_empty() {
displayln!(" CONFLICTED");
for file in &status.conflicted {
display_redln!(" {}", file.display());
}
}
} else {
display_greenln!("Clean");
}
}
}
}
} else {
log_warning!(
"A group refers to package '{}' but no package with that ID is defined",
id
);
}
}
}
Some("packages") => {
let dir = get_dir_or_pwd(matches.subcommand_matches("packages").unwrap(), true);
let bom = BOM::for_dir(&dir);
println!("PACKAGE GROUPS");
for (id, g) in &bom.groups {
println!(" {} ({})", id, g.packages.join(", "));
}
println!("");
println!("PACKAGES");
for (id, p) in &bom.packages {
if let Some(path) = &p.path {
println!(" {} ({})", id, path.display());
} else {
println!(" {}", id);
}
}
}
Some("bom") => {
let dir = get_dir_or_pwd(matches.subcommand_matches("bom").unwrap(), true);
let bom = BOM::for_dir(&dir);
println!("{}", bom);
}
Some("clean") => {
let matches = matches.subcommand_matches("clean").unwrap();
let package_ids = get_package_ids_from_args(matches, true);
let bom = BOM::for_dir(&pwd());
if !bom.is_workspace() {
error_and_exit("The clean command must be run from within an existing workspace, please cd to your target workspace and try again", Some(1));
}
for id in package_ids {
if let Some(package) = bom.packages.get(&id) {
if package.has_repo() {
display!("{} ... ", package.id);
let rc = package.rc(bom.root()).unwrap();
match rc.revert(None) {
Err(e) => {
error_and_exit(&e.to_string(), Some(1));
}
Ok(_status) => {
display_greenln!("OK");
}
}
}
} else {
log_warning!(
"A group refers to package '{}' but no package with that ID is defined",
id
);
}
}
}
Some("tag") => {
let matches = matches.subcommand_matches("tag").unwrap();
let force = matches.is_present("force");
let tagname = matches.value_of("name").unwrap();
let message = matches.value_of("message");
let package_ids = get_package_ids_from_args(matches, true);
let mut packages_with_existing_tag: Vec<&str> = vec![];
let bom = BOM::for_dir(&pwd());
if !bom.is_workspace() {
error_and_exit("The tag command must be run from within an existing workspace, please cd to your target workspace and try again", Some(1));
}
for id in package_ids {
if let Some(package) = bom.packages.get(&id) {
if package.has_repo() {
display!("{} ... ", package.id);
let rc = package.rc(bom.root()).unwrap();
match rc.tag(tagname, force, message) {
Err(e) => {
if e.to_string().contains("tag already exists") {
packages_with_existing_tag.push(&package.id);
display_yellowln!("Tag already exists");
}
}
Ok(_status) => {
display_greenln!("OK");
}
}
}
} else {
log_warning!(
"A group refers to package '{}' but no package with that ID is defined",
id
);
}
}
if !packages_with_existing_tag.is_empty() {
displayln!("");
display_yellowln!("Some packages were not tagged successfully due to the tag already existing, but not necessarily in the same place as your current workspace view.");
display_yellowln!(
"You can run the following command to force the tag onto your current view:"
);
displayln!("");
let mut command = format!(" origen proj tag {} ", &tagname);
command += &packages_with_existing_tag.join(" ");
command += " --force";
if let Some(msg) = message {
command += &format!(" --message \"{}\" ", msg);
}
displayln!("{}", command);
displayln!("");
exit(1);
}
}
None => unreachable!(),
_ => unreachable!(),
}
}
fn get_package_ids_from_args(matches: &ArgMatches, return_all_if_none: bool) -> Vec<String> {
let mut package_args: Vec<&str> = match matches.values_of("packages") {
Some(pkgs) => pkgs.map(|p| p).collect(),
None => vec![],
};
if package_args.is_empty() && return_all_if_none {
package_args = vec!["all"];
}
let bom = BOM::for_dir(&pwd());
let package_ids = bom.resolve_ids(package_args);
if let Err(e) = &package_ids {
error_and_exit(&e.to_string(), Some(1));
}
package_ids.unwrap()
}
fn pwd() -> PathBuf {
let dir = match env::current_dir() {
Err(_e) => {
error_and_exit("Something has gone wrong trying to resolve the PWD, is it stale or do you not have read access to it?", Some(1));
unreachable!();
}
Ok(d) => d,
};
dir
}
fn get_dir_or_pwd(matches: &ArgMatches, validate: bool) -> PathBuf {
let dir = match matches.value_of("dir") {
Some(x) => PathBuf::from(x),
None => pwd(),
};
if validate {
validate_path(&dir, true, true);
dir.canonicalize().unwrap()
} else {
dir
}
}
fn error_and_exit(msg: &str, exit_code: Option<i32>) {
term::red("error: ");
println!("{}", msg);
if let Some(c) = exit_code {
exit(c);
}
}
fn validate_path(path: &Path, is_present: bool, is_dir: bool) {
if is_present {
let t = if is_dir { "directory" } else { "file" }.to_string();
if !path.exists() {
error_and_exit(
&format!("The {} '{}' does not exist", t, path.display()),
Some(1),
);
}
if is_dir && path.is_file() {
error_and_exit(
&format!(
"Expected '{}' to be a directory, but it is a file",
path.display()
),
Some(1),
);
} else if !is_dir && path.is_dir() {
error_and_exit(
&format!(
"Expected '{}' to be a file, but it is a directory",
path.display()
),
Some(1),
);
}
} else if path.exists() {
error_and_exit(
&format!("The path '{}' already exists", path.display()),
Some(1),
);
}
}