Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge working state #7

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ directories = "4.0.1"
log = "0.4.17"
os-release = "0.1.0"
pomsky-macro = "0.10.0"
procfs = "0.16.0"
regex = "1.10.2"
reqwest = {version = "0.11.21", default-features = false, features = ["default-tls", "blocking"]}
serde = {version = "1.0.152", features = ["derive"]}
Expand Down
13 changes: 8 additions & 5 deletions src/impaccable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,23 @@ type Result<T> = std::result::Result<T, Error>;
pub type PackageId = String;
pub type GroupId = String;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PackageGroup {
pub members: BTreeSet<PackageId>
pub members: BTreeSet<PackageId>,
}

impl PackageGroup {
pub fn new() -> Self {
Self {
members: BTreeSet::new()
..Default::default()
}
}
pub fn from_members(members: BTreeSet<PackageId>) -> Self {
Self { members }
}
Self {
members,
..Default::default()
}
}
}

/// Default data structure to store Package groups
Expand Down
36 changes: 23 additions & 13 deletions src/impaccable/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ impl ConfigManager {
// verify all groups configured in the config file actually exist in the package configuration
for (_target_id, target) in &config.targets {
for configured_group in &target.root_groups {
if package_group_names.contains(configured_group){
return Err(impaccable::Error::GroupNotFound { group: configured_group.to_owned() });
if !package_group_names.contains(configured_group){
return Err(impaccable::Error::GroupNotFound { group: configured_group.to_owned(), package_dir: package_config_path });
}
}
}
Expand All @@ -55,6 +55,7 @@ impl ConfigManager {
}

pub fn config(&self) -> &Config { &self.config }
pub fn config_path(&self) -> &Path { &self.config_path }
pub fn package_config(&self) -> &PackageConfiguration { &self.package_config }
pub fn package_config_mut(&mut self) -> &mut PackageConfiguration { &mut self.package_config }

Expand Down Expand Up @@ -95,6 +96,7 @@ impl ConfigManager {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Config {
pub package_dir: PathBuf,
pub pacman_wrapper: String,
pub targets: BTreeMap<TargetId, TargetConfig>
}

Expand All @@ -115,6 +117,7 @@ impl Config {
);
Ok(Self {
package_dir : "./packages".into(),
pacman_wrapper: "paru".into(),
targets,
})
}
Expand All @@ -125,19 +128,12 @@ pub struct TargetConfig {
pub root_groups: BTreeSet<GroupId>
}

/// Represents the parsed form of the entire package configuration of a system.
/// Files are indexed by their absolute paths.
#[derive(Debug, Default, Clone)]
pub struct PackageConfiguration {
pub files : HashMap<PathBuf, PackageFile>
}

#[derive(Debug, Default, Clone)]
pub struct PackageFile {
pub groups: PackageGroupMap
}

impl PackageFile {
impl PackageFile {
pub fn new() -> Self {
Self {
groups: BTreeMap::new()
Expand All @@ -148,10 +144,24 @@ impl PackageFile {
}
}

/// Represents the parsed form of the entire package configuration of a system.
/// Files are indexed by their absolute paths.
#[derive(Debug, Default, Clone)]
pub struct PackageConfiguration {
pub files : HashMap<PathBuf, PackageFile>,
package_dir : PathBuf
}

impl PackageConfiguration{
fn new(package_dir: &Path) -> Self {
Self {
package_dir: package_dir.into(),
..Default::default()
}
}
/// Parses a package directory to generate a corresponding `PackageConfiguration`
fn parse(package_dir: &Path) -> impaccable::Result<Self> {
let mut package_configuration = PackageConfiguration::default();
let mut package_configuration = PackageConfiguration::new(package_dir);

for entry in WalkDir::new(package_dir)
.into_iter()
Expand Down Expand Up @@ -243,7 +253,7 @@ impl PackageConfiguration{
}
}
let (Some(package_group), Some(file)) = (package_group_ref, file_to_save) else {
return Err(Error::GroupNotFound { group: group_id.clone() });
return Err(Error::GroupNotFound { group: group_id.clone(), package_dir: self.package_dir.clone() });
};
package_group.extend(packages);
self.write_file_to_disk(&file)?;
Expand All @@ -268,7 +278,7 @@ impl PackageConfiguration{
}
let Some(file) = file_to_save else {
if !group_found {
return Err(Error::GroupNotFound { group: group_id.to_owned() });
return Err(Error::GroupNotFound { group: group_id.to_owned(), package_dir: self.package_dir.clone() });
} else {
return Err(Error::PackageNotFound { package: package_id.clone() });
}
Expand Down
31 changes: 26 additions & 5 deletions src/impaccable/distro.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{collections::BTreeMap, fmt::Display};
use std::{collections::BTreeMap, fmt::Display, io::BufReader, fs::File};

use anyhow::bail;
use anyhow::{bail, Context};
use procfs::{self, FromBufRead};

use crate::impaccable::{PackageGroupMap, PackageGroup};

Expand Down Expand Up @@ -39,9 +40,9 @@ pub fn generate_configuration(system_config: &SystemConfiguration) -> anyhow::Re
let response = reqwest::blocking::get(package_url)?.text()?;
println!("{}", response);

let package_group = PackageGroup {
members: response.lines().map(|x| x.to_owned()).collect()
};
let package_group = PackageGroup::from_members(
response.lines().map(|x| x.to_owned()).collect()
);
group_map.insert(format!("{}-{}", system_config.distro, url_path), package_group);
}
Ok(group_map)
Expand All @@ -52,6 +53,26 @@ pub fn generate_configuration(system_config: &SystemConfiguration) -> anyhow::Re
}
}


// TODO: use this for proper AMD and intel ucode handling
fn get_cpu_vendor() -> anyhow::Result<CpuVendor> {
let file = File::open("/proc/cpuinfo")?;
let reader = BufReader::new(file);
let cpuinfo = procfs::CpuInfo::from_buf_read(reader)?;
let vendor_id = cpuinfo.fields.get("vendor_id").context("Failed to retrieve vendor_id")?;
match &vendor_id[..] {
"Amd" => Ok(CpuVendor::Amd),
"Intel" => Ok(CpuVendor::Intel),
// TODO: Improve error reporting
_ => bail!("Unsupported Cpu Vendor")
}
}

enum CpuVendor {
Amd,
Intel,
}

#[derive(Debug, Clone)]
pub struct SystemConfiguration {
pub distro: String,
Expand Down
3 changes: 2 additions & 1 deletion src/impaccable/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ pub enum Error {
PackageFileNotFound {
package_file: PathBuf,
},
#[error("Group `{group}` not found")]
#[error("Group `{group}` not found in package dir `{package_dir}`")]
GroupNotFound {
group: GroupId,
package_dir: PathBuf,
},

#[error("Package `{package}` not found")]
Expand Down
42 changes: 27 additions & 15 deletions src/impaccable/pacman.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ use anyhow::{Context, bail};
use pomsky_macro::pomsky;
use regex::Regex;

pub struct PacmanWrapper {
pub wrapper: String
}

const RE_PACKAGE_REQUIRED_BY: &str = pomsky!(
let package_name_char = ['a'-'z' '0'-'9' '@' '.' '_' '+' '-'];
"Required By"[s]+": ":(((package_name_char+)' '*)+ | "None")
Expand All @@ -18,9 +22,13 @@ fn re_package_required_by() -> &'static Regex {
}

/// Queries what packages are installed on the system
pub fn query_explicitly_installed() -> anyhow::Result<BTreeSet<String>> {
pub fn query_installed(explicit: bool) -> anyhow::Result<BTreeSet<String>> {
let mut pacman_args = String::from("-Qq");
if explicit {
pacman_args.push('e')
}
let pacman_output_bytes = Command::new("pacman")
.arg("-Qqe")
.arg(&pacman_args)
.output()
.context("Failed to run pacman -Qqe")?
.stdout;
Expand All @@ -32,22 +40,26 @@ pub fn query_explicitly_installed() -> anyhow::Result<BTreeSet<String>> {
Ok(installed_set)
}

/// Installs the supplied packages.
pub fn install_packages<I, S>(packages: I) -> anyhow::Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let _pacman_command = Command::new("pacman")
.arg("-S")
.args(packages)
.stdin(Stdio::inherit())
.status()
.context("Failed to run pacman")?;
Ok(())
impl PacmanWrapper {
/// Installs the supplied packages.
pub fn install_packages<I, S>(&self, packages: I) -> anyhow::Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let _pacman_command = Command::new(self.wrapper.clone())
.arg("-S")
.args(packages)
.stdin(Stdio::inherit())
.status()
.context("Failed to run pacman")?;
Ok(())
}
}




/// Uninstalls the supplied packages.
pub fn uninstall_packages<I, S>(packages: I) -> anyhow::Result<ExitStatus>
where
Expand Down
21 changes: 16 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ use clap::Parser;
use impaccable::{config::{ConfigManager, ActiveTarget}, pacman, PackageId};
use dialoguer::{Confirm, Editor, theme::ColorfulTheme, Input, FuzzySelect, MultiSelect, Select};
use directories::ProjectDirs;
use serde::de::IntoDeserializer;
use std::{path::PathBuf, fs::{self, File}, env, io, collections::BTreeSet};
use std::io::Write;
use anyhow::{Context, bail, anyhow};
use cli::{Cli, CliCommand, Target, Groups};

use crate::impaccable::{pacman::packages_required_by, PackageGroup, GroupId};
use crate::impaccable::{pacman::{packages_required_by, PacmanWrapper}, GroupId, PackageGroup};

fn main() -> std::result::Result<(), anyhow::Error> {
let cli = Cli::parse();
Expand All @@ -33,6 +34,8 @@ fn main() -> std::result::Result<(), anyhow::Error> {
}
};

println!("active target path: {}", active_target_path.to_string_lossy());

let mut config_manager : impaccable::config::ConfigManager = {
let config_path = {
if let Some(cli_config_override) = cli.config {
Expand All @@ -46,6 +49,8 @@ fn main() -> std::result::Result<(), anyhow::Error> {
default_config_path
}
};
println!("config path: {}", config_path.to_string_lossy());


// Parse the config file. If it is not found, offer to create it instead.
let config_manager = match ConfigManager::parse(config_path.clone()) {
Expand Down Expand Up @@ -81,6 +86,7 @@ fn main() -> std::result::Result<(), anyhow::Error> {
};
config_manager
};


let mut active_target = match std::fs::read_to_string(&active_target_path) {
Ok(s) => ActiveTarget::parse(&s).context(format!("Failed to parse active target at '{}'", &active_target_path.to_string_lossy()))?,
Expand Down Expand Up @@ -108,20 +114,25 @@ fn main() -> std::result::Result<(), anyhow::Error> {
},
};

println!("active target: {}", active_target.target());

// The following code handles the different CLI (sub)commands, then exits.
match &cli.command {
None => {},
Some(CliCommand::Config) => {
println!("config: {:?}", config_manager.config());
}
Some(CliCommand::Sync { remove_untracked }) => {
let pacman_installed = impaccable::pacman::query_explicitly_installed().context("Failed to query installed packages")?;
// TODO: get from config
let pacman_wrapper = PacmanWrapper{ wrapper: "paru".into() };

let pacman_installed = impaccable::pacman::query_installed(true).context("Failed to query installed packages")?;

let target_config = config_manager.config().targets.get(active_target.target()).ok_or_else(|| anyhow!(impaccable::Error::TargetNotFound(active_target.target().clone())))?;
let should_be_installed : BTreeSet<&PackageId> = config_manager.package_config().packages_of_groups(&target_config.root_groups).collect();

let not_installed = should_be_installed.iter().filter(|package| !pacman_installed.contains(**package));
pacman::install_packages(not_installed).context("Failed to install missing packages")?;
pacman_wrapper.install_packages(not_installed).context("Failed to install missing packages")?;

if *remove_untracked {
let untracked_packages = pacman_installed.iter().cloned().filter(|package| should_be_installed.contains(package));
Expand Down Expand Up @@ -168,7 +179,7 @@ fn main() -> std::result::Result<(), anyhow::Error> {
}
}
Some(CliCommand::Plan { remove_untracked }) => {
let pacman_installed = impaccable::pacman::query_explicitly_installed().context("Failed to query installed packages")?;
let pacman_installed = impaccable::pacman::query_installed(true).context("Failed to query installed packages")?;

let target = config_manager.config().targets.get(active_target.target()).context(format!("Failed to find root group {} in config", active_target.target()))?;

Expand Down Expand Up @@ -230,7 +241,7 @@ fn main() -> std::result::Result<(), anyhow::Error> {
}

Some(CliCommand::Import) => {
let pacman_installed = impaccable::pacman::query_explicitly_installed().context("Failed to query installed packages")?;
let pacman_installed = impaccable::pacman::query_installed(true).context("Failed to query installed packages")?;

let target = config_manager
.config()
Expand Down
Loading