diff --git a/core/xvm/Cargo.toml b/core/xvm/Cargo.toml index 5b4c5ed..72a9c49 100644 --- a/core/xvm/Cargo.toml +++ b/core/xvm/Cargo.toml @@ -14,3 +14,5 @@ path = "src/main.rs" serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.8" indexmap = { version = "1.9", features = ["serde"] } +clap = { version = "4.3", features = ["derive"] } # 用于解析命令行参数 +anyhow = "1.0" \ No newline at end of file diff --git a/core/xvm/config/cmd.template b/core/xvm/config/cmd.template new file mode 100644 index 0000000..fce618a --- /dev/null +++ b/core/xvm/config/cmd.template @@ -0,0 +1,13 @@ +xvm add target version --path xxx +xvm add target version --filename xxx +xvm add target version --env name=value +xvm remove target version + +xvm use target version +xvm current target +xvm run target version --command xxx +xvm list target + +xvm workspace target +xvm workspace target --enable +xvm workspace target --disable \ No newline at end of file diff --git a/core/xvm/config/config.xvm.yaml b/core/xvm/config/config.xvm.yaml index 4f65e3e..b5af984 100644 --- a/core/xvm/config/config.xvm.yaml +++ b/core/xvm/config/config.xvm.yaml @@ -1 +1,15 @@ -bindir: xxx \ No newline at end of file +bindir: xxx + +xvm add target version --path xxx +xvm add target version --filename xxx +xvm add target version --env name=value +xvm remove target version + +xvm use target version +xvm current target +xvm run target version --args xxx +xvm list target + +xvm workspace target +xvm workspace target --enable +xvm workspace target --disable \ No newline at end of file diff --git a/core/xvm/config/versions.xvm.yaml b/core/xvm/config/versions.xvm.yaml index 218ee9f..cee0ab5 100644 --- a/core/xvm/config/versions.xvm.yaml +++ b/core/xvm/config/versions.xvm.yaml @@ -1,11 +1,10 @@ --- python: 3.12.3: + filename: python3 path: /usr/bin 2.7.18: path: /usr/bin - 3.12.1: - path: /usr/bin java: 8.0.0: path: /usr/local/java/8 @@ -25,3 +24,8 @@ test2: path: /usr/bin 3.12.2: path: /usr/bin +mytest: + 3.12.1: + path: /home/speak/workspace/github/d2learn/xlings/core/xvm + 3.12.2: + path: /home/speak/workspace/github/d2learn/xlings/core/xvm diff --git a/core/xvm/config/workspace.xvm.yaml b/core/xvm/config/workspace.xvm.yaml index 3901129..5f0f0f6 100644 --- a/core/xvm/config/workspace.xvm.yaml +++ b/core/xvm/config/workspace.xvm.yaml @@ -1,8 +1,9 @@ --- xvm-wmetadata: - name: demo + name: global active: true versions: - python: 2.7.18 + python: 3.12.3 java: 8.0.0 node: 21.7.3 + mytest: 3.12.2 diff --git a/core/xvm/mytest b/core/xvm/mytest new file mode 100755 index 0000000..d1c5d5f --- /dev/null +++ b/core/xvm/mytest @@ -0,0 +1 @@ +xvm run mytest $@ \ No newline at end of file diff --git a/core/xvm/src/baseinfo.rs b/core/xvm/src/baseinfo.rs new file mode 100644 index 0000000..53a1e30 --- /dev/null +++ b/core/xvm/src/baseinfo.rs @@ -0,0 +1,4 @@ +//use std::env; + +pub static BINDIR: &str = "/home/speak/workspace/github/d2learn/xlings/core/xvm"; +//static RUNDIR: &str = env::current_dir().unwrap().to_str().unwrap(); \ No newline at end of file diff --git a/core/xvm/src/cmdprocessor.rs b/core/xvm/src/cmdprocessor.rs new file mode 100644 index 0000000..d2b95a1 --- /dev/null +++ b/core/xvm/src/cmdprocessor.rs @@ -0,0 +1,331 @@ +use std::fs; + +use clap::{Arg, ArgAction, Command, ArgMatches}; +use anyhow::{Result, Context}; + +use xvmlib::shims; +use xvmlib::Workspace; + +use crate::baseinfo; +use crate::helper; + +pub fn run() -> Result<()> { + let matches = Command::new("xvm") + .version("prev-0.0.2") + .author("d2learn ") + .about("a simple and generic version management tool") + .subcommand( + Command::new("add") + .about("Add a target") + .arg( + Arg::new("target") + .required(true) + .help("The name of the target"), + ) + .arg( + Arg::new("version") + .required(true) + .help("The version of the target"), + ) + .arg( + Arg::new("path") + .long("path") + .value_name("PATH") + .action(ArgAction::Set) + .help("Specify the installation path for the target"), + ) + .arg( + Arg::new("filename") + .long("filename") + .value_name("FILENAME") + .action(ArgAction::Set) + .help("Specify a filename for the target"), + ) + .arg( + Arg::new("env") + .long("env") + .value_name("ENV") + .action(ArgAction::Append) + .help("Set environment variables for the target (format: name=value)"), + ), + ) + .subcommand( + Command::new("remove") + .about("Remove a target") + .arg( + Arg::new("target") + .required(true) + .help("The name of the target"), + ) + .arg( + Arg::new("version") + .required(true) + .help("The version of the target"), + ), + ) + .subcommand( + Command::new("use") + .about("Use a specific target and version") + .arg( + Arg::new("target") + .required(true) + .help("The name of the target"), + ) + .arg( + Arg::new("version") + .required(true) + .help("The version of the target"), + ), + ) + .subcommand( + Command::new("current") + .about("Show the current target's version") + .arg( + Arg::new("target") + .required(true) + .help("The name of the target"), + ), + ) + .subcommand( + Command::new("list") + .about("List all versions for a target") + .arg( + Arg::new("target") + .required(true) + .help("The name of the target"), + ), + ) + .subcommand( + Command::new("run") + .about("Run a target program") + .arg( + Arg::new("target") + .required(true) + .help("The name of the target"), + ) + .arg( + Arg::new("version") + //.required(true) + .help("The version of the target"), + ) + .arg( + Arg::new("args") + //.required(true) + .long("args") + //.value_name("ARGS") + .action(ArgAction::Set) + .num_args(1..) + .allow_hyphen_values(true) + .help("Arguments to pass to the command") + ), + ) + .subcommand( + Command::new("workspace") + .about("Manage xvm's workspaces") + .arg( + Arg::new("target") + .required(true) + .help("The name of the target"), + ) + .arg( + Arg::new("enable") + .long("enable") + .action(ArgAction::SetTrue) + .help("Enable the workspace"), + ) + .arg( + Arg::new("disable") + .long("disable") + .action(ArgAction::SetTrue) + .help("Disable the workspace"), + ), + ) + .get_matches(); + + match matches.subcommand() { + Some(("add", sub_matches)) => handle_add(sub_matches)?, + Some(("remove", sub_matches)) => handle_remove(sub_matches)?, + Some(("use", sub_matches)) => handle_use(sub_matches)?, + Some(("current", sub_matches)) => handle_current(sub_matches)?, + Some(("run", sub_matches)) => handle_run(sub_matches)?, + Some(("list", sub_matches)) => handle_list(sub_matches)?, + Some(("workspace", sub_matches)) => handle_workspace(sub_matches)?, + _ => println!("Unknown command. Use --help for usage information."), + } + + Ok(()) +} + +fn handle_add(matches: &ArgMatches) -> Result<()> { + let target = matches.get_one::("target").context("Target is required")?; + let version = matches.get_one::("version").context("Version is required")?; + let path = matches.get_one::("path"); + let filename = matches.get_one::("filename"); + let env_vars: Vec = matches + .get_many::("env") + .unwrap_or_default() + .map(|s| s.to_string()) + .collect(); + + println!("Adding target: {}, version: {}", target, version); + + let mut program = shims::Program::new(target, version); + + if let Some(p) = path { + println!("Path: {}", p); + program.set_path(p); + } + + if let Some(f) = filename { + println!("Filename: {}", f); + program.set_filename(f); + } + + if !env_vars.is_empty() { + println!("Environment variables: {:?}", env_vars); + program.add_envs( + &env_vars + .iter() + .map(|s| { + let parts: Vec<&str> = s.split('=').collect(); + (parts[0], parts[1]) + }) + .collect::>(), + ); + } + + let mut vdb = xvmlib::get_versiondb().clone(); + + if vdb.is_empty(target) { + xvmlib::shims::create(target, baseinfo::BINDIR); + } + + vdb.set_vdata(target, version, program.vdata()); + + vdb.save_to_local().context("Failed to save VersionDB")?; + + Ok(()) +} + +fn handle_remove(matches: &ArgMatches) -> Result<()> { + let target = matches.get_one::("target").context("Target is required")?; + let version = matches.get_one::("version").context("Version is required")?; + println!("Removing target: {}, version: {}", target, version); + + let mut vdb = xvmlib::get_versiondb().clone(); + vdb.remove_vdata(target, version); + vdb.save_to_local().context("Failed to save VersionDB")?; + + if vdb.is_empty(target) { + xvmlib::shims::delete(target, baseinfo::BINDIR); + } + + Ok(()) +} + +fn handle_use(matches: &ArgMatches) -> Result<()> { + let target = matches.get_one::("target").context("Target is required")?; + let version = matches.get_one::("version").context("Version is required")?; + println!("Using target: {}, version: {}", target, version); + + let vdb = xvmlib::get_versiondb(); + + if vdb.get_vdata(target, version).is_none() { + panic!("Version not found"); + } + + let mut workspace = helper::get_workspace(); + + if workspace.version(target) != Some(version) { + workspace.set_version(target, version); + workspace.save_to_local().context("Failed to save Workspace")?; + } + + Ok(()) +} + +fn handle_current(matches: &ArgMatches) -> Result<()> { + let target = matches.get_one::("target").context("Target is required")?; + let workspace = helper::get_workspace(); + + if let Some(version) = workspace.version(target) { + println!("{}: {}", target, version); + } else { + println!("No version selected"); + } + + Ok(()) +} + +fn handle_run(matches: &ArgMatches) -> Result<()> { + let target = matches.get_one::("target").context("Target is required")?; + let args: Vec = matches + .get_many::("args") + .unwrap_or_default() + .map(|s| s.to_string()) + .collect(); + + let workspace = helper::get_workspace(); // TODO: optimize + let version = matches.get_one::("version").unwrap_or_else(|| { + workspace.version(target).expect("No version selected") + }); + + let mut program = shims::Program::new(target, version); + let vdb = xvmlib::get_versiondb(); + let vdata = vdb + .get_vdata(target, version) + .expect("Version data not found"); + + program.set_vdata(vdata); + + if !args.is_empty() { + program.add_args(&args); + } + + program.run(); + + Ok(()) +} + +fn handle_list(matches: &ArgMatches) -> Result<()> { + let target = matches.get_one::("target").context("Target is required")?; + + let vdb = xvmlib::get_versiondb(); + let versions = vdb.get_all_version(target).unwrap_or_default(); + + for version in versions { + println!("\t{}", version); + } + + Ok(()) +} + +fn handle_workspace(matches: &ArgMatches) -> Result<()> { + let target = matches.get_one::("target").context("Target is required")?; + let enable = matches.get_flag("enable"); + let disable = matches.get_flag("disable"); + + let mut workspace = if target == "global" { + xvmlib::get_global_workspace().clone() + } else { + if fs::metadata("workspace.xvm.yaml").is_ok() { + helper::get_local_workspace() + } else { + println!("create new workspace [{}]", target); + Workspace::new("workspace.xvm.yaml", target) + } + }; + + if enable { + println!("active workspace [{}]", target); + workspace.set_active(true); + } + if disable { + println!("deactive workspace [{}]", target); + workspace.set_active(false); + } + + workspace.save_to_local().context("Failed to save Workspace")?; + + Ok(()) +} \ No newline at end of file diff --git a/core/xvm/src/helper.rs b/core/xvm/src/helper.rs new file mode 100644 index 0000000..ab751db --- /dev/null +++ b/core/xvm/src/helper.rs @@ -0,0 +1,21 @@ +// helper +use std::fs; + +use xvmlib::Workspace; + +pub fn get_workspace() -> Workspace { + let mut workspace; + if fs::metadata("workspace.xvm.yaml").is_ok() { + workspace = get_local_workspace(); + if !workspace.active() { + workspace = xvmlib::get_global_workspace().clone() + } + } else { + workspace = xvmlib::get_global_workspace().clone() + } + workspace +} + +pub fn get_local_workspace() -> Workspace { + Workspace::from("workspace.xvm.yaml").expect("Failed to initialize Workspace") +} \ No newline at end of file diff --git a/core/xvm/src/main.rs b/core/xvm/src/main.rs index 86d5e2b..8c58f49 100644 --- a/core/xvm/src/main.rs +++ b/core/xvm/src/main.rs @@ -1,27 +1,11 @@ extern crate xvmlib; -//use std::env; - -use xvmlib::VersionDB; -use xvmlib::Workspace; +mod baseinfo; +mod helper; +mod cmdprocessor; fn main() { - - let target = "python"; - //let current_dir = env::current_dir().unwrap(); - xvmlib::init_versiondb("config/versions.xvm.yaml"); xvmlib::init_global_workspace("config/workspace.xvm.yaml"); - - let mut wspace = Workspace::new("config/workspace.xvm.yaml").unwrap(); - let mut program = xvmlib::load_program_from_workspace(target, &wspace); - program.add_arg("--version"); - program.run(); - wspace.set_active(true); - wspace.set_version(target, "2.7.18"); - wspace.save_to_local().unwrap(); - - let mut vdb = VersionDB::new("config/versions.xvm.yaml").unwrap(); - vdb.set_vdata("test2", "3.12.2", program.vdata()); - vdb.save_to_local().unwrap(); + let _ = cmdprocessor::run(); } \ No newline at end of file diff --git a/core/xvm/workspace.xvm.yaml b/core/xvm/workspace.xvm.yaml new file mode 100644 index 0000000..1876fae --- /dev/null +++ b/core/xvm/workspace.xvm.yaml @@ -0,0 +1,9 @@ +--- +xvm-wmetadata: + name: demo + active: false +versions: + python: 2.7.18 + java: 8.0.0 + node: 21.7.3 + mytest: 3.12.1 diff --git a/core/xvm/xvmlib/lib.rs b/core/xvm/xvmlib/lib.rs index 0eff608..9ff1bfc 100644 --- a/core/xvm/xvmlib/lib.rs +++ b/core/xvm/xvmlib/lib.rs @@ -11,12 +11,13 @@ use std::sync::OnceLock; pub use versiondb::VersionDB; pub use workspace::Workspace; -pub static VERSION_DB: OnceLock = OnceLock::new(); -pub static GLOBAL_WORKSPACE: OnceLock = OnceLock::new(); +// read-only global state +static VERSION_DB: OnceLock = OnceLock::new(); +static GLOBAL_WORKSPACE: OnceLock = OnceLock::new(); pub fn init_versiondb(yaml_file: &str) { VERSION_DB.get_or_init(|| { - VersionDB::new(yaml_file).expect("Failed to initialize VersionDB") + VersionDB::from(yaml_file).expect("Failed to initialize VersionDB") }); } @@ -26,7 +27,7 @@ pub fn get_versiondb() -> &'static VersionDB { pub fn init_global_workspace(yaml_file: &str) { GLOBAL_WORKSPACE.get_or_init(|| { - Workspace::new(yaml_file).expect("Failed to initialize Workspace") + Workspace::from(yaml_file).expect("Failed to initialize Workspace") }); } diff --git a/core/xvm/xvmlib/shims.rs b/core/xvm/xvmlib/shims.rs index 76e90ef..696ef8b 100644 --- a/core/xvm/xvmlib/shims.rs +++ b/core/xvm/xvmlib/shims.rs @@ -38,6 +38,10 @@ impl Program { &self.version } + pub fn set_filename(&mut self, filename: &str) { + self.filename = Some(filename.to_string()); + } + pub fn add_env(&mut self, key: &str, value: &str) { self.envs.push((key.to_string(), value.to_string())); } @@ -56,9 +60,9 @@ impl Program { self.args.push(arg.to_string()); } - pub fn add_args(&mut self, args: &[&str]) { + pub fn add_args(&mut self, args: &Vec) { for arg in args { - self.args.push(arg.to_string()); + self.args.push(arg.clone()); } } @@ -143,4 +147,49 @@ impl Program { new_path } +} + +pub fn create(target: &str, dir: &str) { + println!("Saving Program to {}", dir); + + if !fs::metadata(dir).is_ok() { + fs::create_dir_all(dir).unwrap(); + } + + // create shim-script-file for windows and unix + let (filename, args_placeholder) = shim_file(target, dir); + + if !fs::metadata(&filename).is_ok() { + fs::write(&filename, &format!("xvm run {} {}", + target, args_placeholder + )).unwrap(); + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&filename, PermissionsExt::from_mode(0o755)).unwrap(); + } + } +} + +pub fn delete(target: &str, dir: &str) { + let (filename, _) = shim_file(target, dir); + + if fs::metadata(&filename).is_ok() { + fs::remove_file(&filename).unwrap(); + } +} + +fn shim_file<'a>(target: &str, dir: &'a str) -> (String, &'a str) { + let filename: String; + let args_placeholder: &str; + if cfg!(target_os = "windows") { + args_placeholder = "%*"; + filename = format!("{}/{}.bat", dir, target); + } else { + args_placeholder = "$@"; + filename = format!("{}/{}", dir, target); + } + + (filename, args_placeholder) } \ No newline at end of file diff --git a/core/xvm/xvmlib/versiondb.rs b/core/xvm/xvmlib/versiondb.rs index b5bb1f8..331ccb6 100644 --- a/core/xvm/xvmlib/versiondb.rs +++ b/core/xvm/xvmlib/versiondb.rs @@ -20,7 +20,8 @@ pub struct VersionDB { } impl VersionDB { - pub fn new(yaml_file: &str) -> Result { + + pub fn from(yaml_file: &str) -> Result { let root = load_from_file(yaml_file)?; Ok(VersionDB { @@ -29,6 +30,14 @@ impl VersionDB { }) } + pub fn is_empty(&self, name: &str) -> bool { + self.root.get(name).is_none() + } + + pub fn get_all_version(&self, name: &str) -> Option> { + self.root.get(name).map(|versions| versions.keys().cloned().collect()) + } + pub fn get_vdata(&self, name: &str, version: &str) -> Option<&VData> { self.root.get(name)?.get(version) } @@ -40,6 +49,16 @@ impl VersionDB { .insert(version.to_string(), vdata); } + pub fn remove_vdata(&mut self, name: &str, version: &str) { + if let Some(versions) = self.root.get_mut(name) { + versions.remove(version); + // Remove the target if it has no versions + if versions.is_empty() { + self.root.remove(name); + } + } + } + pub fn save_to_local(&self) -> Result<(), io::Error> { save_to_file(&self.filename, &self.root) } diff --git a/core/xvm/xvmlib/workspace.rs b/core/xvm/xvmlib/workspace.rs index c0955d6..1c5acc1 100644 --- a/core/xvm/xvmlib/workspace.rs +++ b/core/xvm/xvmlib/workspace.rs @@ -22,7 +22,25 @@ pub struct Workspace { } impl Workspace { - pub fn new(yaml_file: &str) -> Result { + + pub fn new(yaml_file: &str, name: &str) -> Self { + let root = WorkspaceRoot { + wmetadata: WMetadata { + name: name.to_string(), + active: false, + }, + versions: IndexMap::new(), + }; + + save_to_file(yaml_file, &root).expect("Failed to save Workspace"); + + Workspace { + file: yaml_file.to_string(), + root: root, + } + } + + pub fn from(yaml_file: &str) -> Result { let root = load_from_file(yaml_file)?; Ok(Workspace {