Skip to content

Commit

Permalink
Support generating a new workspace (not full app, just workspace). So…
Browse files Browse the repository at this point in the history
…me robustness updates for launching windows commands.
  • Loading branch information
origen committed Oct 18, 2023
1 parent 2ffa013 commit e897f1f
Show file tree
Hide file tree
Showing 21 changed files with 267 additions and 120 deletions.
10 changes: 6 additions & 4 deletions python/origen/origen/helpers/regressions/cli/origen.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,17 +226,19 @@ class Names:
pls = _CommonNames.pls_cmd()
pl = _CommonNames.pl_cmd()
# proj = Cmd(names.proj)
# new = Cmd(names.new)
new = Cmd(
names.new,
help="Create a new origen environment (e.g., app, workspace)",
)
creds = _CommonNames.creds_cmd()
i = _CommonNames.interactive_cmd()
# fmt = Cmd(names.fmt)
# build = Cmd(names.build)
v = _CommonNames.v_cmd()

commands = [
# proj, new, creds, eval, exec, i,
# pls, pl, aux_cmds, fmt, build
creds, eval, exec, i,
# proj, fmt, build
creds, eval, exec, i, new,
pls, pl, aux_cmds,
]
cmds = commands
Expand Down
2 changes: 1 addition & 1 deletion rust/origen/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion rust/origen/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ lazy_static = "1.4.0"
regex = "1"
toml = "0.5"
serde = {version = "1.0", features = ["derive"]}
tera = "1"
indexmap = {version = "1.3.0", features = ["serde-1"]}
flate2 = "1.0"
tempfile = "3"
Expand Down
19 changes: 16 additions & 3 deletions rust/origen/cli/src/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,17 @@ fn main() -> Result<()> {
let plugins = match Plugins::new(&mut extensions) {
Ok(pl) => pl,
Err(e) => {
log_error!("Failed to collect plugins. Encountered error: {}", e);
None
if python::is_backend_origen_mod_missing_err(&e) {
// _origen is available but plugins failed to load
log_error!("Failed to collect plugins. Encountered error: {}", e);
None
} else {
// _origen isn't available. This could be an error is retrieving plugins.
// Print a warning instead of error, while logging the error
log_trace!("Failed to collect plugins. Encountered error: {}", e);
log_warning!("Failed to collect plugins: _origen module missing");
None
}
}
};
let aux_cmds = AuxCmds::new(&mut extensions)?;
Expand Down Expand Up @@ -428,6 +437,8 @@ fn main() -> Result<()> {
commands::env::add_helps(&mut helps);
commands::generate::add_helps(&mut helps);
commands::target::add_helps(&mut helps);
} else {
commands::new::add_helps(&mut helps);
}

if STATUS.is_origen_present {
Expand Down Expand Up @@ -789,6 +800,8 @@ fn main() -> Result<()> {
// .help("Update all CHANGED file references from the last generate run"),
// ),
// );
} else {
app = commands::new::add_commands(app, &helps, &extensions)?;
}

let mut all_cmds_and_aliases = vec![];
Expand Down Expand Up @@ -1021,7 +1034,7 @@ fn main() -> Result<()> {

match matches.subcommand_name() {
Some(commands::app::BASE_CMD) => commands::app::run(matches.subcommand_matches(commands::app::BASE_CMD).unwrap(), &app, &extensions, plugins.as_ref(), &app_cmds.as_ref().unwrap())?,
// Some("new") => commands::new::run(matches.subcommand_matches("new").unwrap()),
Some(commands::new::BASE_CMD) => run_non_ext_cmd_match_case!(new),
// Some("proj") => commands::proj::run(matches.subcommand_matches("proj").unwrap()),
Some(commands::env::BASE_CMD) => run_non_ext_cmd_match_case!(env),
Some(commands::eval::BASE_CMD) => run_cmd_match_case!(eval),
Expand Down
1 change: 1 addition & 0 deletions rust/origen/cli/src/commands/_prelude/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub use crate::framework::{
};
pub use crate::framework::core_cmds::SubCmd;
pub use crate::{output_dir_opt, ref_dir_opt};
pub use crate::{req_sv_arg, sv_opt};

// TODO clap4.0 remove after update to next clap version
pub type RunInput<'a> = &'a clap::ArgMatches;
Expand Down
2 changes: 1 addition & 1 deletion rust/origen/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod exec;
// pub mod fmt;
pub mod interactive;
// pub mod mode;
// pub mod new;
pub mod new;
// pub mod proj;
// pub mod save_ref;
pub mod target;
Expand Down
214 changes: 115 additions & 99 deletions rust/origen/cli/src/commands/new/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// This implements the new application command, for the code generators, e.g. 'origen new dut' etc.,
// see new_resource.rs
use crate::commands::_prelude::*;
use origen_metal::tera::{Context, Tera};
use std::process::exit;
use std::env;
use std::fs::{create_dir, File};
use std::path::PathBuf;
use crate::_generated::python::PYTHONS;

mod new_resource;
pub const BASE_CMD: &'static str = "new";
pub const WS_CMD: &'static str = "workspace";
pub const APP_CMD: &'static str = "application";

use clap::ArgMatches;
use origen::STATUS;
use phf::map::Map;
use phf::phf_map;
use std::path::PathBuf;
use tera::{Context, Tera};

// This includes a map of all template files, it is built by cli/build.rs at compile time.
// All files in each sub-directory of commands/new/templates are accessible via a map named after the
Expand All @@ -19,102 +21,116 @@ use tera::{Context, Tera};
// automatically be picked up and included in the new app.
include!(concat!(env!("OUT_DIR"), "/new_app_templates.rs"));

struct App {
name: String,
dir: PathBuf,
}

pub fn run(matches: &ArgMatches) {
if STATUS.is_app_present {
new_resource::run(matches);
return;
}
let name = matches.get_one::<&str>("name").unwrap();
if &name.to_lowercase() != name {
display_red!("ERROR: ");
displayln!("The application name must be lowercased");
std::process::exit(1);
}
let app_dir = std::env::current_dir().unwrap().join(name);

if app_dir.exists() {
if !app_dir.read_dir().unwrap().next().is_none() {
display_red!("ERROR: ");
displayln!("A directory with that name already exists and is not empty, please delete it or use a new name and try again");
std::process::exit(1);
}
} else {
std::fs::create_dir(&app_dir)
.expect("Could you create the new application directory, do you have permission?");
}
gen_core_cmd_funcs__no_exts__no_app_opts!(
BASE_CMD,
"Create a new origen environment (e.g., app, workspace)",
{ |cmd: App<'a>| {
cmd.arg_required_else_help(true)
}},
core_subcmd__no_exts__no_app_opts!(WS_CMD, "Create a new workspace", { |cmd: App| {
cmd.visible_alias("ws")
.arg(req_sv_arg!("name", "NAME", "Workspace name"))
.arg(sv_opt!("desc", "DESC", "Description of the workspace").visible_alias("description"))
.arg(sv_opt!("path", "PATH", "Path to build the new workspace").short('p'))
}})
// TODO origen new - support new app
// core_subcmd__no_exts__no_app_opts!(APP_CMD, "Create a new application", { |cmd: App| {
// cmd.visible_alias("app")
// }})
);

pub fn run(invocation: &clap::ArgMatches) -> origen::Result<()> {
if let Some((n, subcmd)) = invocation.subcommand() {
match n {
WS_CMD => {
let mut tera = match Tera::new("templates/workspace/*.tera") {
Ok(t) => t,
Err(e) => {
println!("Failed to parse workspace templates: {}", e);
exit(1);
}
};
let mut context = Context::new();
let name = subcmd.get_one::<String>("name").unwrap();

let mut out_dir;
if let Some(p) = subcmd.get_one::<PathBuf>("path") {
if p.is_relative() {
out_dir = env::current_dir()?;
out_dir.push(p);
} else {
out_dir = p.to_path_buf();
}
} else {
out_dir = env::current_dir()?;
out_dir.push(&name);
}

let mut context = Context::new();
//// Converting this to a vector here as the template was printing out the package list
//// in reverse order when given the index map
//let packages: Vec<&Package> = bom.packages.iter().map(|(_id, pkg)| pkg).collect();
context.insert("app_name", name);
context.insert("origen_version", &origen::STATUS.origen_version.to_string());
let mut user_info = "".to_string();
let users = crate::om::users();
if let Ok(u) = users.current_user() {
if let Ok(username) = u.username() {
user_info += &username;
match u.get_email() {
Ok(e) => {
if let Some(email) = e {
user_info += &format!(" <{}>", &email);
if out_dir.exists() {
// Check directory is empty
if !out_dir.read_dir()?.next().is_none() {
log_error!("Target directory {} is not empty!", &out_dir.display());
exit(1);
}
} else {
create_dir(&out_dir)?;
}
Err(e) => {
display_redln!("{}", e.msg);
println!("Creating new workspace at {}", &out_dir.display());

context.insert("name", name);
context.insert("desc", subcmd.get_one::<String>("desc").unwrap_or(&"".to_string()));
context.insert("app_gen", &false);

let users = origen_metal::users();
let mut author = "".to_string();
context.insert("python_version", &format!(
">={},<={}",
PYTHONS[2].strip_prefix("python").unwrap(),
PYTHONS.last().unwrap().strip_prefix("python").unwrap()
));
if let Ok(u) = users.current_user() {
match u.username() {
Ok(username) => {
match u.get_email() {
Ok(e) => {
if let Some(email) = e {
author += &format!("{} <{}>", &username, &email);
}
}
Err(e) => {
log_warning!("Cannot retrieve current user's email: {}", e.msg);
}
}
},
Err(e) => {
log_warning!("Cannot retrieve current user: {}", e.msg);
}
}
} else {
log_warning!("Cannot populate current user");
}
}
}
}
context.insert("user_info", &user_info);
context.insert("author", &author);
context.insert("origen_version", &origen::STATUS.origen_version.to_string());
// TODO origen new - find a better way than hard-coding pytest version
context.insert("pytest_version", "^7");

let new_app = App {
name: name.to_string(),
dir: app_dir,
};

new_app.apply_template(&PY_APP, &context);

if !matches.contains_id("no-setup") {
new_app.setup();
}
}

impl App {
fn apply_template(&self, template: &Map<&str, &str>, context: &Context) {
let mut tera = Tera::default();

for (file, content) in template.entries() {
let contents = tera.render_str(content, &context).unwrap();

let file = file.replace("app_namespace_dir", &self.name);
let path = self.dir.join(file.clone());

if !path.parent().unwrap().exists() {
std::fs::create_dir_all(&path.parent().unwrap())
.expect("Couldn't create dir within the new app");
}

display_green!(" create ");
displayln!("{}", &file);
for (n, contents) in SHARED.entries() {
tera.add_raw_template(&format!("shared/{}", n), contents)?;
}
for (n, contents) in WORKSPACE.entries() {
tera.add_raw_template(&format!("workspace/{}", n), contents)?;
}

std::fs::write(&path, &contents).expect("Couldn't create a file within the new app");
for (n, _) in WORKSPACE.entries() {
let f = File::create(out_dir.join(n))?;
tera.render_to(&format!("workspace/{}", n), &context, f)?;
}
Ok(())
},
APP_CMD => todo!(), // TODO origen enw - support new app
_ => unreachable_invalid_subc!(n)
}
} else {
unreachable!()
}

fn setup(&self) {
std::env::set_current_dir(&self.dir).expect("Couldn't cd to the new app");

let _ = std::process::Command::new("origen")
.arg("env")
.arg("setup")
.spawn()
.expect("Couldn't execute origen setup")
.wait();
}
}
}
29 changes: 29 additions & 0 deletions rust/origen/cli/src/commands/new/templates/shared/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Workspace directories
/.origen
/tmp
/.ref
/log
/.session

# Output directories
/output
/web/source/_static/build
/web/source/interbuild
/dist

{% if app_gen -%}
# Release scribe dry-run output and release notes
config/history.generated.dry_run.toml
release_note.txt
{% endif -%}

# Cache directories
__pycache__
.pytest_cache

# Editor cruft
*.swp
*.swo
*~
*.pylintrc
.vscode
14 changes: 14 additions & 0 deletions rust/origen/cli/src/commands/new/templates/shared/origen.toml.tera
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Use this to define your application-specific Origen configuration
# Do not delete it even if you don't use it since it is also used by the Origen
# command line interface to determine when it is in an Origen application workspace

# Specify what command should be used to invoke python, if not specified
# Origen will try python, python3, python3.8, etc. until one is found that
# satisfies the minimum Python version requirement
#python_cmd = "mypython"

# If your company has an internal package server enter it here:
#pkg_server = "https://pkgs.company.net:9292"
# or here, if you need to use different urls for push and pull (write and read):
#pkg_server_push = "https://pkgs.company.net:9292"
#pkg_server_pull = "https://pkgs.company.net:9292"
Loading

0 comments on commit e897f1f

Please sign in to comment.