feat: added command functionality to install
All checks were successful
/ check (push) Successful in 39s
All checks were successful
/ check (push) Successful in 39s
This commit is contained in:
parent
1a95046b87
commit
d9d4728dd6
20 changed files with 464 additions and 76 deletions
|
@ -1,14 +1,25 @@
|
|||
[package]
|
||||
name = "pkgr"
|
||||
description = "A package manager and build tool."
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"Didier <dev@faulty.nl>"
|
||||
]
|
||||
license-file = "../LICENSE"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
manifest = { path = "../manifest" }
|
||||
pkgfile = { path = "../pkgfile" }
|
||||
fern = "0.6.2"
|
||||
log = "0.4.19"
|
||||
regex = "1.9.1"
|
||||
manifest = { path = "../manifest" }
|
||||
clap = { version = "4.3.12", features = ["derive"] }
|
||||
colored = "2.0.4"
|
||||
toml = "0.7.6"
|
||||
serde = { version = "1.0.171", features = ["derive"] }
|
||||
libc = "0.2.80"
|
||||
reqwest = { version = "0.11.18", features = ["blocking"] }
|
||||
tar = "0.4.39"
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
use std::process::exit;
|
||||
use crate::package::identifier::PackageIdentifier;
|
||||
use clap::{Parser, Subcommand};
|
||||
use log::error;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use manifest::Manifest;
|
||||
use crate::package::builder::{InstallType, PackageInstaller};
|
||||
use crate::package::fetch::fetch_package;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(name = "pkgr", version)]
|
||||
|
@ -33,6 +37,8 @@ pub enum Command {
|
|||
/// Update packages on the system
|
||||
Update,
|
||||
#[command(hide = true)]
|
||||
Debug,
|
||||
#[command(hide = true)]
|
||||
None,
|
||||
}
|
||||
|
||||
|
@ -41,20 +47,47 @@ impl Command {
|
|||
match self {
|
||||
Command::Install {
|
||||
build,
|
||||
package_identifier: _,
|
||||
package_identifier,
|
||||
} => {
|
||||
if *build {
|
||||
error!("Build is not yet implemented.");
|
||||
} else {
|
||||
error!("Install is not yet implemented.");
|
||||
warn!("Installer does not run in isolation.");
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
let unix_start = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
|
||||
trace!("Fetching package: {}", package_identifier);
|
||||
let pkgfile = fetch_package(package_identifier.clone()).unwrap();
|
||||
debug!("size: manifest({}kb) data({}kb)", pkgfile.manifest.len() / 1024, pkgfile.data.len() / 1024);
|
||||
trace!("parsing manifest");
|
||||
let manifest = Manifest::try_from(pkgfile.manifest.clone()).unwrap();
|
||||
debug!("manifest pkg name: {}", manifest.package.name);
|
||||
trace!("creating installer");
|
||||
let installer = PackageInstaller::new(manifest.clone(), pkgfile, if *build { InstallType::Build } else { InstallType::Bin });
|
||||
|
||||
trace!("starting install");
|
||||
match installer.install() {
|
||||
Ok(_) => {
|
||||
info!("Sucessfully installed: {}", &manifest.package.name);
|
||||
()
|
||||
},
|
||||
Err(e) => {
|
||||
error!("{}", e.to_string())
|
||||
}
|
||||
}
|
||||
let unix_end = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap();
|
||||
let end = std::time::Instant::now();
|
||||
// float ms
|
||||
let duration = (end - start).as_nanos() as f64;
|
||||
info!("Install took: {}ms", (duration / 1_000_000.0));
|
||||
}
|
||||
Command::Remove { package_identifier: _, index } => {
|
||||
if *index {
|
||||
error!("Index removal is not yet implemented.");
|
||||
} else {
|
||||
error!("Remove is not yet implemented.");
|
||||
Command::Remove { package_identifier, index } => {
|
||||
if let PackageIdentifier::URI(_) = package_identifier {
|
||||
error!("URI is unsupported when removing applications.");
|
||||
exit(1);
|
||||
}
|
||||
info!("Index: {}", index);
|
||||
info!("Package identifier: {}", package_identifier);
|
||||
}
|
||||
Command::List { installed: _ } => {
|
||||
error!("List is not yet implemented.");
|
||||
|
@ -62,6 +95,19 @@ impl Command {
|
|||
Command::Update => {
|
||||
error!("Update is not yet implemented.");
|
||||
}
|
||||
Command::Debug => {
|
||||
trace!("Trace message.");
|
||||
debug!("Debug message.");
|
||||
info!("Info message.");
|
||||
warn!("Warning message.");
|
||||
error!("Error message.");
|
||||
info!("");
|
||||
info!("PKGR VERSION: {}", env!("CARGO_PKG_VERSION"));
|
||||
info!("PKGR AUTHORS: {}", env!("CARGO_PKG_AUTHORS"));
|
||||
info!("PKGR DESCRIPTION: {}", env!("CARGO_PKG_DESCRIPTION"));
|
||||
info!("");
|
||||
info!("PKGR_LOG_LEVEL: {}", std::env::var("PKGR_LOG_LEVEL").unwrap_or_else(|_| "info".to_string()));
|
||||
}
|
||||
Command::None => {
|
||||
error!("No command was specified.");
|
||||
}
|
||||
|
|
|
@ -1,25 +1,35 @@
|
|||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
use crate::commands::Cli;
|
||||
use log::{debug, info, SetLoggerError, trace};
|
||||
use log::{SetLoggerError, trace};
|
||||
use std::env;
|
||||
|
||||
mod commands;
|
||||
mod package;
|
||||
mod tmp;
|
||||
|
||||
fn main() {
|
||||
setup_logger().expect("Unable to setup logger.");
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
#[cfg(target_family = "unix")]
|
||||
if unsafe { libc::getuid() } != 0 {
|
||||
use log::error;
|
||||
error!("pkgr must be run as root.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Parsing command line arguments...");
|
||||
let c = Cli::parse();
|
||||
debug!("Command line arguments parsed.");
|
||||
trace!("Command line arguments: {:?}", c);
|
||||
|
||||
trace!("Executing command...");
|
||||
c.command.execute();
|
||||
debug!("Command executed.");
|
||||
trace!("Command executed.");
|
||||
|
||||
trace!("Exiting...");
|
||||
info!("Done.");
|
||||
}
|
||||
|
||||
fn setup_logger() -> Result<(), SetLoggerError> {
|
||||
|
@ -27,16 +37,30 @@ fn setup_logger() -> Result<(), SetLoggerError> {
|
|||
.format(|out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{} {}",
|
||||
match record.level().to_string().chars().nth(0).unwrap_or('I') {
|
||||
'T' => "##".cyan(),
|
||||
'D' => "::".yellow(),
|
||||
'E' | 'W' => "!!".red(),
|
||||
_ => "**".blue(),
|
||||
},
|
||||
// Some logic so messages look nice
|
||||
if message.to_string().len() > 0 {
|
||||
match record
|
||||
.level()
|
||||
.to_string()
|
||||
.chars()
|
||||
.nth(0)
|
||||
.unwrap_or('T')
|
||||
{
|
||||
'T' => "[TRACE]".cyan(),
|
||||
'D' => "??".green(),
|
||||
'I' => "=>".blue(),
|
||||
'W' => "##".yellow(),
|
||||
'E' => "!!".red(),
|
||||
_ => "**".blue(),
|
||||
}.to_string()
|
||||
} else { "".to_string() },
|
||||
message.to_string().bright_white()
|
||||
))
|
||||
})
|
||||
.level(env::var("PKGR_LOG_LEVEL").unwrap_or_else(|_| "info".to_string()).parse().unwrap_or(log::LevelFilter::Info))
|
||||
.level(env::var("PKGR_LOG_LEVEL")
|
||||
.unwrap_or_else(|_| "info".to_string())
|
||||
.parse()
|
||||
.unwrap_or(log::LevelFilter::Info))
|
||||
.chain(std::io::stdout())
|
||||
.apply()
|
||||
}
|
||||
|
|
71
pkgr/src/package/builder/mod.rs
Normal file
71
pkgr/src/package/builder/mod.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use libc::fork;
|
||||
use log::{debug, trace};
|
||||
use manifest::Manifest;
|
||||
use pkgfile::PKGFile;
|
||||
use crate::tmp::TempDir;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InstallType {
|
||||
Build,
|
||||
Bin
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InstallError {
|
||||
BuildError(String),
|
||||
BinError(String),
|
||||
InstallError
|
||||
}
|
||||
|
||||
impl ToString for InstallError {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
InstallError::BuildError(e) => format!("Build error: {}", e),
|
||||
InstallError::BinError(e) => format!("Bin error: {}", e),
|
||||
InstallError::InstallError => "Install error".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PackageInstaller {
|
||||
manifest: Manifest,
|
||||
pkgfile: PKGFile,
|
||||
install_type: InstallType
|
||||
}
|
||||
|
||||
impl PackageInstaller {
|
||||
pub fn new(m: Manifest, p: PKGFile, i: InstallType) -> PackageInstaller {
|
||||
PackageInstaller {
|
||||
manifest: m,
|
||||
pkgfile: p,
|
||||
install_type: i
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_to<S: Into<String>>(&self, path: S) -> Result<(), String> {
|
||||
tar::Archive::new(self.pkgfile.data.as_slice())
|
||||
.unpack(path.into())
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn bin(&self) -> Result<(), String> {
|
||||
let mut tmpdir = TempDir::default();
|
||||
tmpdir.push(&self.manifest.package.name);
|
||||
trace!("extracting package into: {}", tmpdir.to_string());
|
||||
self.extract_to(tmpdir.to_string())?;
|
||||
debug!("extracted package in: {}", tmpdir.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build(&self) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install(&self) -> Result<(), InstallError> {
|
||||
match self.install_type {
|
||||
InstallType::Bin => self.bin().map_err(|e| InstallError::BinError(e)),
|
||||
InstallType::Build => self.build().map_err(|e| InstallError::BuildError(e))
|
||||
}
|
||||
}
|
||||
}
|
59
pkgr/src/package/fetch.rs
Normal file
59
pkgr/src/package/fetch.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use log::warn;
|
||||
use pkgfile::PKGFile;
|
||||
use crate::package::identifier::PackageIdentifier;
|
||||
use reqwest::blocking::get;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FetchError {
|
||||
HTTPError(reqwest::Error),
|
||||
IOError(std::io::Error),
|
||||
ParseError,
|
||||
}
|
||||
|
||||
impl Display for FetchError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
FetchError::HTTPError(e) => write!(f, "HTTP Error: {}", e),
|
||||
FetchError::IOError(e) => write!(f, "IO Error: {}", e),
|
||||
FetchError::ParseError => write!(f, "Parse Error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for FetchError {}
|
||||
|
||||
pub fn fetch_package(package_identifier: PackageIdentifier) -> Result<PKGFile, FetchError> {
|
||||
match package_identifier {
|
||||
PackageIdentifier::Path(path) => {
|
||||
std::fs::read(path)
|
||||
.map_err(|e| FetchError::IOError(e)).and_then(|bytes| {
|
||||
PKGFile::try_from(bytes).map_err(|_| FetchError::ParseError)
|
||||
})
|
||||
}
|
||||
PackageIdentifier::URI(uri) => {
|
||||
// get file contents as bytes
|
||||
let mut bytes = Vec::new();
|
||||
match get(&uri) {
|
||||
Ok(mut response) => {
|
||||
match response.take(1024 * 1024).read_to_end(&mut bytes) {
|
||||
Ok(_) => (),
|
||||
Err(e) => return Err(FetchError::IOError(e)),
|
||||
};
|
||||
}
|
||||
Err(e) => return Err(FetchError::HTTPError(e)),
|
||||
};
|
||||
// parse bytes as PKGFile
|
||||
match PKGFile::try_from(bytes) {
|
||||
Ok(pkgfile) => Ok(pkgfile),
|
||||
Err(e) => Err(FetchError::ParseError),
|
||||
}
|
||||
}
|
||||
PackageIdentifier::PackageLocator(package_locator) => {
|
||||
Ok(PKGFile::default())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ impl Error for PackageIdentifierError {}
|
|||
pub enum PackageIdentifier {
|
||||
PackageLocator(PackageLocator),
|
||||
URI(String),
|
||||
Path(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PackageIdentifier {
|
||||
|
@ -33,6 +34,7 @@ impl std::fmt::Display for PackageIdentifier {
|
|||
match self {
|
||||
PackageIdentifier::PackageLocator(pl) => write!(f, "{}", pl),
|
||||
PackageIdentifier::URI(uri) => write!(f, "{}", uri),
|
||||
PackageIdentifier::Path(path) => write!(f, "{}", path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +63,7 @@ impl FromStr for PackageIdentifier {
|
|||
type Err = PackageIdentifierError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let uri_re = regex::Regex::new(r"^[a-zA-Z0-9]+://").unwrap();
|
||||
let uri_re = Regex::new(r"^[a-zA-Z0-9]+://").unwrap();
|
||||
if uri_re.is_match(s) {
|
||||
// there needs to be stuff after the protocol
|
||||
let split = s.split("://").collect::<Vec<&str>>();
|
||||
|
@ -69,6 +71,8 @@ impl FromStr for PackageIdentifier {
|
|||
return Err(PackageIdentifierError::InvalidURI(s.to_string()));
|
||||
}
|
||||
Ok(PackageIdentifier::URI(s.to_string()))
|
||||
} else if std::path::Path::new(s).exists() {
|
||||
return Ok(PackageIdentifier::Path(s.to_string()));
|
||||
} else {
|
||||
let pl = match PackageLocator::from_str(s) {
|
||||
Ok(pl) => pl,
|
||||
|
@ -84,7 +88,7 @@ impl FromStr for PackageLocator {
|
|||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
#[allow(unused_assignments)] // false positive
|
||||
let mut name = None;
|
||||
let mut name = None;
|
||||
let mut version = None;
|
||||
let mut tags = None;
|
||||
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
pub mod identifier;
|
||||
pub mod builder;
|
||||
pub mod fetch;
|
48
pkgr/src/tmp.rs
Normal file
48
pkgr/src/tmp.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
pub struct TempDir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl TempDir {
|
||||
pub fn new(path: PathBuf) -> TempDir {
|
||||
if !path.exists() {
|
||||
std::fs::create_dir_all(&path).unwrap();
|
||||
}
|
||||
TempDir {
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push<S: Into<String>>(&mut self, path: S) {
|
||||
self.path.push(path.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TempDir {
|
||||
fn default() -> TempDir {
|
||||
TempDir::new({
|
||||
let mut t = std::env::temp_dir();
|
||||
t.push("pkgr");
|
||||
t
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<PathBuf> for TempDir {
|
||||
fn into(self) -> PathBuf {
|
||||
self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for TempDir {
|
||||
fn into(self) -> String {
|
||||
self.path.to_str().unwrap().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for TempDir {
|
||||
fn to_string(&self) -> String {
|
||||
self.path.to_str().unwrap().to_string()
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue