diff --git a/Cargo.lock b/Cargo.lock index 8cbed05..2e606b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,12 @@ version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.1.1" @@ -57,6 +63,26 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "directories" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "errno" version = "0.2.8" @@ -78,6 +104,17 @@ dependencies = [ "libc", ] +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -203,6 +240,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "rustix" version = "0.36.6" @@ -278,6 +335,7 @@ name = "system-updater" version = "0.1.0" dependencies = [ "clap", + "directories", "serde", "serde_yaml", ] @@ -291,6 +349,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.6" @@ -309,6 +387,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index b1a7be5..c3a1924 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,6 @@ name = "sup" [dependencies] clap = { version = "4.1", features = ["derive"] } +directories = "4.0" serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9" diff --git a/examples/apt.yaml b/examples/apt.yaml new file mode 100644 index 0000000..9fc978f --- /dev/null +++ b/examples/apt.yaml @@ -0,0 +1,25 @@ +packages: + - name: Apt + fetch: + exe: sudo + params: + - apt + - update + current_dir: null + env: {} + compile: null + install: + exe: sudo + params: + - apt + - upgrade + - --yes + current_dir: null + env: {} + post_install: + - exe: sudo + params: + - apt + - autoremove + current_dir: null + env: {} diff --git a/examples/debian.yml b/examples/debian.yml deleted file mode 100644 index ccd0404..0000000 --- a/examples/debian.yml +++ /dev/null @@ -1,40 +0,0 @@ -systems: -- name: Apt - fetch: - exe: sudo - params: - - apt - - update - current_dir: null - env: {} - compile: null - install: - exe: sudo - params: - - apt - - upgrade - - --yes - current_dir: null - env: {} - post_install: - - exe: sudo - params: - - apt - - autoremove - current_dir: null - env: {} -- name: Rustup - install: - exe: rustup - params: - - update - current_dir: null - env: {} -- name: Cargo - install: - exe: cargo - params: - - install-update - - -a - current_dir: null - env: {} diff --git a/examples/flatpack.yaml b/examples/flatpack.yaml new file mode 100644 index 0000000..be7af86 --- /dev/null +++ b/examples/flatpack.yaml @@ -0,0 +1,29 @@ +packages: + - name: Flatpack + fetch: + exe: flatpak + params: + - update + - --no-deploy + - --assumeyes + - --noninteractive + current_dir: null + env: {} + install: + exe: flatpak + params: + - update + - --no-pull + - --assumeyes + - --noninteractive + current_dir: null + env: {} + post-install: + - exe: flatpak + params: + - uninstall + - --unused + - --assumeyes + - --noninteractive + current_dir: null + env: {} diff --git a/examples/openSUSE.yml b/examples/openSUSE.yml deleted file mode 100644 index 74427c9..0000000 --- a/examples/openSUSE.yml +++ /dev/null @@ -1,29 +0,0 @@ -systems: -- fetch: - exe: sudo - params: - - zypper - - refresh - current_dir: null - env: {} - compile: null - install: - exe: sudo - params: - - zypper - - dup - current_dir: null - env: {} -- install: - exe: rustup - params: - - update - current_dir: null - env: {} -- install: - exe: cargo - params: - - install-update - - -a - current_dir: null - env: {} diff --git a/examples/pipx.yaml b/examples/pipx.yaml new file mode 100644 index 0000000..60c2d52 --- /dev/null +++ b/examples/pipx.yaml @@ -0,0 +1,8 @@ +packages: + - name: Pipx + install: + exe: pipx + params: + - upgrade-all + current_dir: null + env: {} diff --git a/examples/rust.yaml b/examples/rust.yaml new file mode 100644 index 0000000..4a774b2 --- /dev/null +++ b/examples/rust.yaml @@ -0,0 +1,16 @@ +packages: + - name: Rustup + install: + exe: rustup + params: + - update + current_dir: null + env: {} + - name: Cargo + install: + exe: cargo + params: + - install-update + - -a + current_dir: null + env: {} diff --git a/src/command.rs b/src/command.rs index 486b1eb..eccb64f 100644 --- a/src/command.rs +++ b/src/command.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use std::io::{stdout, Write}; use std::path::PathBuf; use std::process::{Command, ExitStatus, Stdio}; -use std::{fmt, io}; +use std::{fmt, fs, io}; #[derive(Debug, Serialize, Deserialize, PartialEq)] enum UpdateSteps { @@ -16,11 +16,17 @@ enum UpdateSteps { #[derive(Debug, Serialize, Deserialize)] pub struct Updater { - systems: Vec, + pub systems: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct System { + packages: Vec, + // TODO: => make a system dependend on another? This will allow to give a "Rust" config which update "rustup", and a custom "git helix" could then be executed after (with the updated toolchain, and NOT concurrently) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Package { pub name: String, fetch: Option, compile: Option, @@ -74,54 +80,120 @@ impl From<&String> for UpdateSteps { } } +pub fn get_packages_folder(opt: &Opt) -> io::Result { + let config_folder = directories::ProjectDirs::from("net", "ZykiCorp", "System Updater") + .ok_or(io::Error::new( + io::ErrorKind::NotFound, + "System’s configuration folder: for its standard location see https://docs.rs/directories/latest/directories/struct.ProjectDirs.html#method.config_dir", + ))? + .config_dir() + .join("packages"); + + Ok(config_folder) +} + impl Updater { fn new() -> Updater { - let mut up = Updater { systems: vec![] }; - up.systems.push(System { - name: Default::default(), - fetch: None, - compile: None, - install: Cmd::new(), - post_install: None, - }); - up + Updater { systems: vec![] } } - /// To create a sample config from code + // /// To create a sample config from code #[doc(hidden)] fn write_config(&self, opt: &Opt) { use std::fs::OpenOptions; + let config_folder = get_packages_folder(&opt).unwrap(); + let mut f = OpenOptions::new() .write(true) .create(true) .truncate(true) - .open(&opt.config_file) + .open(config_folder.join("default.yaml")) .unwrap(); + fs::create_dir_all(&config_folder).unwrap(); + f.write_all(serde_yaml::to_string(&self).unwrap().as_bytes()) .unwrap(); } - pub fn from_config(opt: &Opt) -> Result { - // let u = Updater::new(); - // u.write_config(opt); + // TODO: add option to use &opt.config_file (or folder?) instead + pub fn from_config(opt: &Opt) -> io::Result { + let mut updater = Updater::new(); - let file = std::fs::read_to_string(&opt.config_file).unwrap(); - Ok(serde_yaml::from_str(&file).unwrap()) + if false { + updater.systems.push(System { packages: vec![] }); + updater.systems[0].packages.push(Package { + name: "apt".to_owned(), + fetch: None, + compile: None, + install: Cmd { + exe: "()".to_owned(), + params: vec![], + current_dir: None, + env: HashMap::new(), + }, + post_install: None, + }); + + updater.systems[0].packages.push(Package { + name: "Rustup".to_owned(), + fetch: None, + compile: None, + install: Cmd { + exe: "rustup".to_owned(), + params: vec!["self".to_owned(), "update".to_owned()], + current_dir: None, + env: HashMap::new(), + }, + post_install: None, + }); + updater.write_config(opt); + + panic!("Wrote a config sample."); + } + + let packages_folder = get_packages_folder(&opt)?; + match packages_folder.try_exists() { + Ok(true) => {} // Ok: Exist and should be readable + Ok(false) => { + return Err(io::Error::new( + io::ErrorKind::Other, + format!( + "Configuration folder not accessible at: {}. (broken symlink?)", + packages_folder.display() + ), + )) + } + Err(e) => return Err(e), + } + + for file in packages_folder.read_dir()? { + let file = packages_folder.join(file?.path()); // "default.yaml"); + let sys = std::fs::read_to_string(&file).unwrap(); + let toto = serde_yaml::from_str(&sys).unwrap(); + updater.systems.push(toto); + } + + eprintln!("{:#?}", updater); + + Ok(updater) } pub fn update_all(&self, opt: &Opt) -> Summary { - // self.systems.iter().collect(); let mut status: Vec<_> = vec![]; + + // XXX: We may parallelise (iter_par from rayon?) this loop. But the UI will be problematic to handle for sys in &self.systems { - status.push((sys.name.clone(), self.update(&sys, opt).into())); + for pkg in &sys.packages { + status.push((pkg.name.clone(), self.update(&pkg, opt).into())); + } } Summary { status } } - fn update(&self, sys: &System, opt: &Opt) -> Result<()> { + fn update(&self, sys: &Package, opt: &Opt) -> Result<()> { // TODO: compute once before calling this function, maybe? let steps = if opt.steps.is_empty() { vec![ @@ -151,7 +223,7 @@ impl Updater { } } -impl System { +impl Package { pub fn fetch(&self, opt: &Opt) -> Result<()> { if let Some(fetch) = &self.fetch { let cmd = fetch.clone().prepare(opt); diff --git a/src/lib.rs b/src/lib.rs index 2910cb6..fb03103 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,25 +1,18 @@ mod command; -mod errors; +pub mod errors; use command::*; use errors::*; use clap::Parser; use std::fmt::Display; +use std::io; use std::path::PathBuf; #[derive(Parser)] pub struct Opt { - // TODO: transform this to "base" folder? - // XXX: use the directory/directory-next crate to get the "~/.config/sup" folder? - // ++ A config subFolder and execute in alphabetical order? - // + One config file? -> confy crate? - // - A master config file that list the sub/real files? no if it mean parsing 2 differents formats - // - // Default to something like -> "~/.config/system-updater/list.yml".into(), - // - //#[arg(default_value_t = PathBuf::from("examples/debian.yml"))] - pub config_file: PathBuf, + #[arg(hide = true)] // TODO: hidden option for debug? or usefull for everyone? + pub config_file: Option, #[arg(short, long)] pub quiet: bool, // TODO: use clap_verbosity_flag instead @@ -72,8 +65,19 @@ impl Display for Summary { } } -pub fn run(opt: &Opt) -> Summary { - let updater = Updater::from_config(opt).unwrap(); +pub fn run(opt: &Opt) -> io::Result { + let updater = Updater::from_config(opt)?; - updater.update_all(opt) + if updater.systems.is_empty() { + let package_folder = get_packages_folder(&opt)?; + return Err(io::Error::new( + io::ErrorKind::Other, + format!( + "No package found in configuration folder. Add them in: {}", + package_folder.display() + ), + )); + } + + Ok(updater.update_all(opt)) } diff --git a/src/main.rs b/src/main.rs index 3222b1a..8d66e08 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ -use clap::Parser; use system_updater::*; -fn main() { +use clap::Parser; + +fn main() -> std::io::Result<()> { let opt = Opt::parse(); - let summary = run(&opt); + let summary = run(&opt)?; println!("{}", summary); + Ok(()) }