Compare commits
No commits in common. "v0.1.0-alpha2" and "main" have entirely different histories.
v0.1.0-alp
...
main
56 changed files with 1159 additions and 337 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
|
**/dist/
|
||||||
**/target
|
**/target
|
||||||
**/Cargo.lock
|
**/Cargo.lock
|
||||||
*.pkg
|
*.pkg
|
25
.idea/jsonSchemas.xml
Normal file
25
.idea/jsonSchemas.xml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JsonSchemaMappingsProjectConfiguration">
|
||||||
|
<state>
|
||||||
|
<map>
|
||||||
|
<entry key="Woodpecker pipeline config">
|
||||||
|
<value>
|
||||||
|
<SchemaInfo>
|
||||||
|
<option name="name" value="Woodpecker pipeline config" />
|
||||||
|
<option name="relativePathToSchema" value="https://raw.githubusercontent.com/woodpecker-ci/woodpecker/master/pipeline/schema/schema.json" />
|
||||||
|
<option name="applicationDefined" value="true" />
|
||||||
|
<option name="patterns">
|
||||||
|
<list>
|
||||||
|
<Item>
|
||||||
|
<option name="path" value=".woodpecker.yml" />
|
||||||
|
</Item>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</SchemaInfo>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
|
</project>
|
43
README.md
43
README.md
|
@ -1,8 +1,47 @@
|
||||||
# Packager
|
# Packager
|
||||||
|
|
||||||
|
[![CI Status](https://ci.ixvd.net/api/badges/2/status.svg)](https://ci.ixvd.net/repos/2)
|
||||||
|
|
||||||
> "A package manager and builder but like rust."
|
> "A package manager and builder but like rust."
|
||||||
> -- Gandhi (2050)
|
> -- Gandhi (2050)
|
||||||
|
|
||||||
> ***not even close to done*** :)
|
> ***Almost to a point you can use it*** :)
|
||||||
|
|
||||||
|
Packager is a simple yet powerful package manager
|
||||||
|
|
||||||
|
## Install a package
|
||||||
|
pkgr supports fetching packages with a:
|
||||||
|
- path
|
||||||
|
- url (http/https)
|
||||||
|
- package locator
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# example with path
|
||||||
|
pkgr install ./foxy/snek.pkg
|
||||||
|
# example with url
|
||||||
|
pkgr install https://example.com/doggo.pkg
|
||||||
|
# example with package locator
|
||||||
|
pkgr install foo:stable,bar
|
||||||
|
```
|
||||||
|
|
||||||
|
## Composing a package
|
||||||
|
Right now, pkgr does not have a compose/package command. (it's in the works dw!)
|
||||||
|
You can create a package with `pkg.py`!
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Usage: pkg.py <package_toml_path> <output_path> [<directories_to_include> ...]
|
||||||
|
# example:
|
||||||
|
python pkg.py manifest.toml example.pkg root/ source/ scripts/
|
||||||
|
# this will create a package with an archive that look like this:
|
||||||
|
# [header+manifest]
|
||||||
|
# ...
|
||||||
|
# [archive:
|
||||||
|
# root/
|
||||||
|
# source/
|
||||||
|
# scripts/
|
||||||
|
# ]
|
||||||
|
```
|
||||||
|
|
||||||
|
It's not perfect, but it'll get the job done :)
|
||||||
|
|
||||||
|
|
||||||
Packager is a simple yet powerful package manager
|
|
31
bodge-pkg.py
31
bodge-pkg.py
|
@ -1,31 +0,0 @@
|
||||||
import os
|
|
||||||
import tarfile
|
|
||||||
|
|
||||||
with open("./package.toml", mode='r') as mani:
|
|
||||||
data = mani.read()
|
|
||||||
with open("./sample.pkg", mode='wb') as pkg:
|
|
||||||
print("building header...")
|
|
||||||
pkg.write(bytes([0x01, (len(data) >> 8) & 0xFF, len(data) & 0xFF]))
|
|
||||||
print("writing manifest into pkg...")
|
|
||||||
pkg.write(data.encode("utf-8"))
|
|
||||||
with tarfile.TarFile("/tmp/pkgtar", 'w') as pkgtar:
|
|
||||||
print("tarring ./pkg...")
|
|
||||||
os.chdir("pkg")
|
|
||||||
for root, dirs, files in os.walk("."):
|
|
||||||
for file in files:
|
|
||||||
print(f"\33[2Kadd: {os.path.join(root, file)}", end="\r", flush=True)
|
|
||||||
pkgtar.add(os.path.join(root, file))
|
|
||||||
os.chdir("..")
|
|
||||||
for root, dirs, files in os.walk("pkgr"):
|
|
||||||
for file in files:
|
|
||||||
print(f"\33[2Kadd: {os.path.join(root, file)}", end="\r", flush=True)
|
|
||||||
pkgtar.add(os.path.join(root, file))
|
|
||||||
print("\33[2K", end="\r", flush=True)
|
|
||||||
with open("/tmp/pkgtar", 'rb') as pkgtar:
|
|
||||||
print("appending pkgtar to pkg...")
|
|
||||||
pkg.write(pkgtar.read())
|
|
||||||
print("deleting /tmp/pkgtar...")
|
|
||||||
os.unlink("/tmp/pkgtar")
|
|
||||||
print("closing write stream")
|
|
||||||
pkg.close()
|
|
||||||
|
|
|
@ -14,3 +14,4 @@ regex = "1.9.1"
|
||||||
reqwest = { version = "0.11.18", features = ["blocking"] }
|
reqwest = { version = "0.11.18", features = ["blocking"] }
|
||||||
uuid = { version = "1.4.0", features = ["serde", "v4"] }
|
uuid = { version = "1.4.0", features = ["serde", "v4"] }
|
||||||
tar = "0.4.39"
|
tar = "0.4.39"
|
||||||
|
libc = "0.2.80"
|
||||||
|
|
10
bootpkg/package.toml
Normal file
10
bootpkg/package.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "bootpkg" #*
|
||||||
|
description = "A tool to strap pkgs" #*
|
||||||
|
version = 1
|
||||||
|
tags = []
|
||||||
|
type = "application"
|
||||||
|
arch = "x86_64"
|
||||||
|
|
||||||
|
[bin]
|
||||||
|
root = "/root"
|
|
@ -18,9 +18,20 @@ pub struct Args {
|
||||||
|
|
||||||
impl From<Vec<String>> for Args {
|
impl From<Vec<String>> for Args {
|
||||||
fn from(value: Vec<String>) -> Self {
|
fn from(value: Vec<String>) -> Self {
|
||||||
|
if value.len() == 0 {
|
||||||
|
return Args {
|
||||||
|
command: Command::from(String::default()),
|
||||||
|
args: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Args {
|
Args {
|
||||||
command: Command::from(value[0].to_owned()),
|
command: Command::from(value[0].to_owned()),
|
||||||
args: value[1..].to_owned(),
|
args: if value.len() > 1 {
|
||||||
|
value[1..].to_owned()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,14 @@ mod args;
|
||||||
mod prelude;
|
mod prelude;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
if unsafe { libc::getuid() } != 0 {
|
||||||
|
println!("bootpkg must be run as root.");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let args = Args::from(env::args().collect::<Vec<String>>()[1..].to_owned());
|
let args = Args::from(env::args().collect::<Vec<String>>()[1..].to_owned());
|
||||||
match args.command {
|
match args.command {
|
||||||
Command::Strap => {
|
Command::Strap => {
|
||||||
|
|
|
@ -30,12 +30,12 @@ pub fn mani_from_str(s: &str) -> Manifest<Option<PKGR>> {
|
||||||
fs: mani.fs,
|
fs: mani.fs,
|
||||||
bin: mani.bin,
|
bin: mani.bin,
|
||||||
build: mani.build,
|
build: mani.build,
|
||||||
pkgr: bmani.pkgr,
|
ext: bmani.pkgr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_bootstrap(mani: Manifest<Option<PKGR>>) -> bool {
|
pub fn run_bootstrap(mani: Manifest<Option<PKGR>>) -> bool {
|
||||||
if let Some(pkgr) = mani.pkgr {
|
if let Some(pkgr) = mani.ext {
|
||||||
if let Some(bootstrap) = pkgr.bootstrap {
|
if let Some(bootstrap) = pkgr.bootstrap {
|
||||||
fn run_command<S: Into<String>>(s: S) -> i32 {
|
fn run_command<S: Into<String>>(s: S) -> i32 {
|
||||||
std::process::Command::new("sh")
|
std::process::Command::new("sh")
|
||||||
|
|
39
build
Executable file
39
build
Executable file
|
@ -0,0 +1,39 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
build_pkgr() {
|
||||||
|
cd pkgr
|
||||||
|
mkdir -p dist/root/{usr/bin,etc/pkgr}
|
||||||
|
echo -e "You can't use pkgr to update pkgr because the file will be in use while updating.\nuse bootpkg" > dist/root/etc/pkgr.d/YOU-CAN-NOT-USE-PKGR-TO-UPDATE-PKGR.txt
|
||||||
|
|
||||||
|
# for bin
|
||||||
|
cargo build -r
|
||||||
|
cp target/release/pkgr dist/root/usr/bin/pkgr
|
||||||
|
|
||||||
|
# for build
|
||||||
|
mkdir -p dist/pkgr
|
||||||
|
cp -r src/ Cargo.toml dist/pkgr
|
||||||
|
cp -r ../manifest dist/manifest
|
||||||
|
cp -r ../pkgfile dist/pkgfile
|
||||||
|
|
||||||
|
cd dist
|
||||||
|
python ../../pkg.py ../package.toml pkgr.pkg "*"
|
||||||
|
cd ../..
|
||||||
|
}
|
||||||
|
|
||||||
|
build_bootpkg() {
|
||||||
|
cd bootpkg
|
||||||
|
mkdir -p dist/root/usr/bin
|
||||||
|
|
||||||
|
cargo build -r
|
||||||
|
cp target/release/bootpkg dist/root/usr/bin/bootpkg
|
||||||
|
|
||||||
|
|
||||||
|
cd dist
|
||||||
|
python ../../pkg.py ../package.toml bootpkg.pkg "*"
|
||||||
|
cd ../..
|
||||||
|
}
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
build_bootpkg
|
||||||
|
build_pkgr
|
|
@ -9,7 +9,9 @@ pkgr is the main tool for the packager.
|
||||||
Because this tool is so feature rich, it has its own [README](./pkgr/README.md).
|
Because this tool is so feature rich, it has its own [README](./pkgr/README.md).
|
||||||
|
|
||||||
## bootpkg
|
## bootpkg
|
||||||
bootpkg is a tool primariliy used to bootstrap the packager.
|
`bootpkg` is a simple pkgr variant.
|
||||||
|
It doesn't contain anything fancy and is only used to install and update pkgr.
|
||||||
|
Using it for anything other than pkgr is not recommended.
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
|
@ -17,4 +19,4 @@ bootpkg is a tool primariliy used to bootstrap the packager.
|
||||||
bootpkg strap ./pkgr.pkg
|
bootpkg strap ./pkgr.pkg
|
||||||
```
|
```
|
||||||
|
|
||||||
This will extract the pkgfile and read the `[pkgr.bootstrap]` section to bootstrap the packager or any other package.
|
This will extract the pkgfile and read the `[pkgr.bootstrap]` section to bootstrap the packager or any other package.
|
||||||
|
|
5
docs/manifest.md
Normal file
5
docs/manifest.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Manifest
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The manifest spec is located in `/package.example.toml` as an example.
|
|
@ -1,24 +0,0 @@
|
||||||
# PKGFILE
|
|
||||||
This file is essentially a tar with the manifest in the header.
|
|
||||||
|
|
||||||
## Format
|
|
||||||
The format is as follows:
|
|
||||||
```
|
|
||||||
[PKGFILE version (1 byte)][manifest length x 256 (1 byte)][manifest length (1 byte)]
|
|
||||||
[manifest (manifest length bytes)]
|
|
||||||
[archive]
|
|
||||||
```
|
|
||||||
|
|
||||||
The file is Big Endian.
|
|
||||||
|
|
||||||
## Unpacking
|
|
||||||
To unpack a PKGFILE, you can use the following command:
|
|
||||||
```bash
|
|
||||||
pkgr unpack <pkgfile> <output>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Packing
|
|
||||||
You can write your own packer, or use the following command:
|
|
||||||
```bash
|
|
||||||
pkgr pack <manifest path> <archive path> <output file>
|
|
||||||
```
|
|
25
docs/pkgfiles/current.md
Normal file
25
docs/pkgfiles/current.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# PKGFILE
|
||||||
|
|
||||||
|
version 1
|
||||||
|
|
||||||
|
---
|
||||||
|
PKGFiles are an all-in-one solution to install a package.
|
||||||
|
|
||||||
|
## Overhead
|
||||||
|
|
||||||
|
Because pkgfiles include everything, many times you're downloading more than what is needed.
|
||||||
|
This may seem stupid but pkgfiles are *not* meant to stay on your system.
|
||||||
|
|
||||||
|
## Format
|
||||||
|
|
||||||
|
The format is as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
[PKGFILE version (1 byte)][manifest length x 256 (1 byte)][manifest length (1 byte)]
|
||||||
|
[manifest (manifest length bytes)]
|
||||||
|
[archive]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Endianness
|
||||||
|
|
||||||
|
The file is Big Endian.
|
22
docs/pkgfiles/planned.md
Normal file
22
docs/pkgfiles/planned.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Planned PKGFILE features.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Compression
|
||||||
|
|
||||||
|
> Planned for next PKGFILE version.
|
||||||
|
|
||||||
|
Currently, compression is not supported, since PKGFILE is an all-in-one solution this is very unpleasant.
|
||||||
|
When compression will be implemented, there is a big possibility that the manifest won't be compressed.
|
||||||
|
This is done to allow servers to easily parse and inspect manifests.
|
||||||
|
The compression will likely be versatile in the way that the format will be stated in the header and the pkgr
|
||||||
|
implementation can work it out by itself.
|
||||||
|
There will be a default compression format, likely gzip.
|
||||||
|
|
||||||
|
### Compact Manifest
|
||||||
|
|
||||||
|
> Not planned yet.
|
||||||
|
|
||||||
|
Right now, the manifest is an utf-8 blob sandwiched between the header and the archive.
|
||||||
|
This also requires a fixed max size for the manifest which is unfavourable.
|
||||||
|
In the future, the manifest may turn into a versatile binary blob with no size limitations.
|
|
@ -5,5 +5,15 @@ use serde::{Deserialize, Serialize};
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Bin {
|
pub struct Bin {
|
||||||
pub root: String,
|
pub root: String,
|
||||||
|
#[serde(default)]
|
||||||
pub checksums: HashMap<String, String>,
|
pub checksums: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Bin {
|
||||||
|
fn default() -> Self {
|
||||||
|
Bin {
|
||||||
|
root: String::default(),
|
||||||
|
checksums: HashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,3 +8,13 @@ pub struct Build {
|
||||||
pub install_script: String,
|
pub install_script: String,
|
||||||
pub dependencies: HashMap<String, String>,
|
pub dependencies: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Build {
|
||||||
|
fn default() -> Self {
|
||||||
|
Build {
|
||||||
|
dependencies: HashMap::default(),
|
||||||
|
build_script: String::default(),
|
||||||
|
install_script: String::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct PKGR {}
|
pub struct Extension {}
|
||||||
|
|
||||||
impl Display for PKGR {
|
impl Display for Extension {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "")
|
write!(f, "")
|
||||||
}
|
}
|
|
@ -6,17 +6,17 @@ pub mod bin;
|
||||||
pub mod build;
|
pub mod build;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod package;
|
pub mod package;
|
||||||
pub mod pkgr;
|
pub mod ext;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Manifest<P: Clone = Option<pkgr::PKGR>> {
|
pub struct Manifest<E: Clone = Option<ext::Extension>> {
|
||||||
pub package: package::Package,
|
pub package: package::Package,
|
||||||
pub dependencies: HashMap<String, String>,
|
pub dependencies: HashMap<String, String>,
|
||||||
pub fs: fs::FS,
|
pub fs: fs::FS,
|
||||||
pub bin: Option<bin::Bin>,
|
pub bin: Option<bin::Bin>,
|
||||||
pub build: Option<build::Build>,
|
pub build: Option<build::Build>,
|
||||||
pub pkgr: P,
|
pub ext: E,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Clone> Manifest<P> {
|
impl<P: Clone> Manifest<P> {
|
||||||
|
@ -50,7 +50,7 @@ impl Default for Manifest {
|
||||||
fs: fs::FS::default(),
|
fs: fs::FS::default(),
|
||||||
bin: None,
|
bin: None,
|
||||||
build: None,
|
build: None,
|
||||||
pkgr: None,
|
ext: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,16 @@ pub enum PackageType {
|
||||||
Meta,
|
Meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToString for PackageType {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match &self {
|
||||||
|
PackageType::Application => "application",
|
||||||
|
PackageType::Library => "library",
|
||||||
|
PackageType::Meta => "meta"
|
||||||
|
}.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Package {
|
pub struct Package {
|
||||||
|
|
|
@ -84,16 +84,16 @@ install_script = "scripts/install" # relative to pkg
|
||||||
[build.dependencies]
|
[build.dependencies]
|
||||||
base = "latest,stable" # selected by default
|
base = "latest,stable" # selected by default
|
||||||
|
|
||||||
## pkgr.*
|
## ext.*
|
||||||
# packager is the official client but you may use other clients supporting the "pkgr v1 spec".
|
# packager is the official client but you may use other clients supporting the "pkgr v1 spec".
|
||||||
# other clients may offer extra functionality this must be put under "pkgr.*"
|
# other clients may offer extra functionality this must be put under "ext.*"
|
||||||
[pkgr]
|
[ext]
|
||||||
|
|
||||||
## pkgr.bootstrap
|
## ext.bootstrap
|
||||||
# This section is used for bootpkg. An edition of packager that bootstraps the full version.
|
# This section is used for bootpkg. An edition of packager that bootstraps the full version.
|
||||||
# This exists so that packager is easy to install on anything!
|
# This exists so that packager is easy to install on anything!
|
||||||
# and only 1 release channel for pkgr
|
# and only 1 release channel for pkgr
|
||||||
[pkgr.bootstrap]
|
[ext.bootstrap]
|
||||||
## any non-zero = installed
|
## any non-zero = installed
|
||||||
check_installed_commands = [
|
check_installed_commands = [
|
||||||
"sh scripts/check_installed"
|
"sh scripts/check_installed"
|
||||||
|
|
69
package.toml
69
package.toml
|
@ -1,69 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "packager" #*
|
|
||||||
description = "A package installation tool" #*
|
|
||||||
version = 1 # this can automatically be incremented when publishing by running `pkgr publish -i ...`
|
|
||||||
tags = [ "prod", "pkgr-spec-1" ]
|
|
||||||
type = "application"
|
|
||||||
|
|
||||||
arch = "x86_64" # this is automatically filled by `pkgr publish ...`
|
|
||||||
|
|
||||||
## dependencies
|
|
||||||
# you may use the following syntax
|
|
||||||
# "<version/tag>" or "<version/tag>,<version/tag>,..."
|
|
||||||
[dependencies]
|
|
||||||
|
|
||||||
## bin
|
|
||||||
# Used for systems that don't want to build pkgs.
|
|
||||||
[bin] # binary files root
|
|
||||||
|
|
||||||
## bin.root
|
|
||||||
# ** RELATIVE TO PACKAGE ROOT **
|
|
||||||
# bin.root specifies the root of the installed tree.
|
|
||||||
# anything in here will be overlayed on top of the system.
|
|
||||||
root = "/root" #*
|
|
||||||
|
|
||||||
## bin.checksums
|
|
||||||
# ** KEYS are relative to BIN.ROOT **
|
|
||||||
# ** VALUES are relative to PKG ROOT **
|
|
||||||
# checksums is used to perform checksums before install
|
|
||||||
# values may be paths or a uri. You may include variables.
|
|
||||||
# supported variables:
|
|
||||||
# - @name
|
|
||||||
# - @version
|
|
||||||
# - @display_version
|
|
||||||
[bin.checksums]
|
|
||||||
"/usr/bin/pkgr" = "https://ixvd.net/checksums/packager/@version"
|
|
||||||
|
|
||||||
## build
|
|
||||||
# Scripts will be copied to a custom root.
|
|
||||||
# After the pacakge is built, the install script is ran.
|
|
||||||
# After the install script the build directory will be deleted and an CTREE (changed tree) will be generated.
|
|
||||||
# Then the CTREE will be used to copy the files over.
|
|
||||||
[build]
|
|
||||||
build_script = "scripts/build" # relative to pkg
|
|
||||||
install_script = "scripts/install" # relative to pkg
|
|
||||||
|
|
||||||
[build.dependencies]
|
|
||||||
base = "latest,stable" # selected by default
|
|
||||||
|
|
||||||
## pkgr.*
|
|
||||||
# packager is the official client but you may use other clients supporting the "pkgr v1 spec".
|
|
||||||
# other clients may offer extra functionality this must be put under "pkgr.*"
|
|
||||||
[pkgr]
|
|
||||||
|
|
||||||
## pkgr.bootstrap
|
|
||||||
# This section is used for bootpkg. An edition of packager that bootstraps the full version.
|
|
||||||
# This exists so that packager is easy to install on anything!
|
|
||||||
# and only 1 release channel for pkgr
|
|
||||||
[pkgr.bootstrap]
|
|
||||||
## any non-zero = installed
|
|
||||||
check_installed_commands = [
|
|
||||||
"sh scripts/check_installed"
|
|
||||||
]
|
|
||||||
|
|
||||||
# any non-zero = fail
|
|
||||||
commands = [
|
|
||||||
"sh scripts/bootstrap/download_latest @version /tmp/pkgr.pkg",
|
|
||||||
"sh scripts/bootstrap/dirty_install /tmp/pkgr.pkg",
|
|
||||||
"sh scripts/check_installed"
|
|
||||||
]
|
|
79
pkg.py
Normal file
79
pkg.py
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
|
||||||
|
|
||||||
|
def build_package(package_toml_path, output_path, directories_to_include):
|
||||||
|
data = read_package_toml(package_toml_path)
|
||||||
|
header = build_header(data)
|
||||||
|
pkg_path = output_path
|
||||||
|
|
||||||
|
with open(pkg_path, mode='wb') as pkg:
|
||||||
|
pkg.write(header)
|
||||||
|
write_manifest(pkg, data)
|
||||||
|
build_tarball(pkg, directories_to_include)
|
||||||
|
|
||||||
|
return pkg_path
|
||||||
|
|
||||||
|
|
||||||
|
def read_package_toml(package_toml_path):
|
||||||
|
with open(package_toml_path, mode='r') as mani:
|
||||||
|
return mani.read()
|
||||||
|
|
||||||
|
|
||||||
|
def build_header(data):
|
||||||
|
header = bytes([0x01, (len(data) >> 8) & 0xFF, len(data) & 0xFF])
|
||||||
|
return header
|
||||||
|
|
||||||
|
|
||||||
|
def write_manifest(pkg, data):
|
||||||
|
pkg.write(data.encode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def build_tarball(pkg, directories_to_include):
|
||||||
|
with tarfile.TarFile("/tmp/pkgtar", 'w') as pkgtar:
|
||||||
|
for directory in directories_to_include:
|
||||||
|
add_files_to_tar(pkgtar, directory, True)
|
||||||
|
|
||||||
|
append_tar_to_pkg(pkg)
|
||||||
|
cleanup_tmp_files()
|
||||||
|
|
||||||
|
|
||||||
|
def add_files_to_tar(pkgtar, directory, skip_target=False):
|
||||||
|
for root, dirs, files in os.walk(directory):
|
||||||
|
for file in files:
|
||||||
|
if skip_target and "target" in root:
|
||||||
|
continue
|
||||||
|
print(f"\33[2Kadd: {os.path.join(root, file)}", end="\r", flush=True)
|
||||||
|
# print()
|
||||||
|
pkgtar.add(os.path.join(root, file))
|
||||||
|
print("\33[2K", end="\r", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def append_tar_to_pkg(pkg):
|
||||||
|
with open("/tmp/pkgtar", 'rb') as pkgtar:
|
||||||
|
pkg.write(pkgtar.read())
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_tmp_files():
|
||||||
|
print("deleting /tmp/pkgtar...")
|
||||||
|
os.unlink("/tmp/pkgtar")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("Usage: pkg.py <package_toml_path> <output_path> [<directories_to_include> ...]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
package_toml_path = sys.argv[1]
|
||||||
|
output_path = sys.argv[2]
|
||||||
|
directories_to_include = glob.glob(sys.argv[3]) if len(sys.argv) == 4 else sys.argv[3:]
|
||||||
|
|
||||||
|
try:
|
||||||
|
pkg_path = build_package(package_toml_path, output_path, directories_to_include)
|
||||||
|
print(f"Package created: {pkg_path}")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Error: File not found.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error occurred: {e}")
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
printf ""
|
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
fetch_latest_version() {
|
|
||||||
printf ""
|
|
||||||
}
|
|
||||||
|
|
||||||
download_file() {
|
|
||||||
printf ""
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
printf ""
|
|
|
@ -1,4 +1,9 @@
|
||||||
#[derive(Debug, Clone)]
|
use std::error::Error;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct PKGFile {
|
pub struct PKGFile {
|
||||||
pub manifest: String,
|
pub manifest: String,
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
|
@ -23,28 +28,56 @@ impl Default for PKGFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<Vec<u8>> for PKGFile {
|
#[derive(Debug)]
|
||||||
type Error = ();
|
pub enum PKGFileError {
|
||||||
|
IOError(io::Error),
|
||||||
|
ParsingError(String)
|
||||||
|
}
|
||||||
|
|
||||||
fn try_from(value: Vec<u8>) -> Result<Self, ()> {
|
impl Display for PKGFileError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
PKGFileError::IOError(e) => write!(f, "IOError: {}", e),
|
||||||
|
PKGFileError::ParsingError(s) => write!(f, "ParsingError: {}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for PKGFileError {}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a Path> for PKGFile {
|
||||||
|
type Error = PKGFileError;
|
||||||
|
fn try_from(path: &'a Path) -> Result<Self, Self::Error> {
|
||||||
|
let d = match std::fs::read(path) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Err(PKGFileError::IOError(e))
|
||||||
|
};
|
||||||
|
PKGFile::try_from(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for PKGFile {
|
||||||
|
type Error = PKGFileError;
|
||||||
|
|
||||||
|
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
match value[0] {
|
match value[0] {
|
||||||
1 => {
|
1 => {
|
||||||
let header: Vec<u32> = value[..3].iter().map(|v| u32::from(*v)).collect();
|
let header: Vec<u32> = value[..3].iter().map(|v| u32::from(*v)).collect();
|
||||||
let manifest_size: u32 = (header[1] << 8) | header[2];
|
let manifest_size: u32 = (header[1] << 8) | header[2];
|
||||||
if manifest_size > value.len() as u32 {
|
if manifest_size > value.len() as u32 {
|
||||||
return Err(());
|
return Err(PKGFileError::ParsingError("Invalid header length".into()));
|
||||||
}
|
}
|
||||||
Ok(PKGFile {
|
Ok(PKGFile {
|
||||||
manifest: match String::from_utf8(
|
manifest: match String::from_utf8(
|
||||||
value[3..(manifest_size as usize + 3)].to_vec(),
|
value[3..(manifest_size as usize + 3)].to_vec(),
|
||||||
) {
|
) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
_ => return Err(()),
|
_ => return Err(PKGFileError::ParsingError("Could not parse manifest".into())),
|
||||||
},
|
},
|
||||||
data: value[(manifest_size as usize + 3)..].to_vec(),
|
data: value[(manifest_size as usize + 3)..].to_vec(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(()),
|
_ => Err(PKGFileError::ParsingError("Unknown pkgfile version".into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,3 +23,7 @@ serde = { version = "1.0.171", features = ["derive"] }
|
||||||
libc = "0.2.80"
|
libc = "0.2.80"
|
||||||
reqwest = { version = "0.11.18", features = ["blocking"] }
|
reqwest = { version = "0.11.18", features = ["blocking"] }
|
||||||
tar = "0.4.39"
|
tar = "0.4.39"
|
||||||
|
humantime = "2.1.0"
|
||||||
|
expanduser = "1.2.2"
|
||||||
|
url = { version = "2.4.0", features = ["serde"] }
|
||||||
|
dns-lookup = "2.0.3"
|
||||||
|
|
32
pkgr/package.toml
Normal file
32
pkgr/package.toml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
[package]
|
||||||
|
name = "packager"
|
||||||
|
description = "A package installation tool"
|
||||||
|
version = 1
|
||||||
|
tags = [ "prod", "pkgr-spec-1" ]
|
||||||
|
type = "application"
|
||||||
|
arch = "x86_64"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[bin]
|
||||||
|
root = "/root"
|
||||||
|
|
||||||
|
[bin.checksums]
|
||||||
|
"/usr/bin/pkgr" = "https://ixvd.net/checksums/packager/@version"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
build_script = "scripts/build"
|
||||||
|
install_script = "scripts/install"
|
||||||
|
|
||||||
|
[build.dependencies]
|
||||||
|
|
||||||
|
[ext]
|
||||||
|
|
||||||
|
[ext.bootstrap]
|
||||||
|
check_installed_commands = [
|
||||||
|
"file /usr/bin/pkgr"
|
||||||
|
]
|
||||||
|
|
||||||
|
commands = [
|
||||||
|
"cp root/usr/bin/pkgr /usr/bin/pkgr"
|
||||||
|
]
|
4
pkgr/skel/etc/pkgr.d/repos.toml
Normal file
4
pkgr/skel/etc/pkgr.d/repos.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[repo.main]
|
||||||
|
name = "Main"
|
||||||
|
url = "tcp://pkgs.ixvd.net:1050"
|
||||||
|
|
7
pkgr/skel/etc/pkgr.toml
Normal file
7
pkgr/skel/etc/pkgr.toml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
build_by_default = false
|
||||||
|
tmp_dir = "/tmp/pkgr"
|
||||||
|
|
||||||
|
[storage]
|
||||||
|
repo_file = "/etc/pkgr.d/repos.toml"
|
||||||
|
data_dir = "/var/lib/pkgr/packages"
|
||||||
|
index_dir = "/var/lib/pkgr/indexes"
|
20
pkgr/skel/var/lib/pkgr/indexes/repo.toml
Normal file
20
pkgr/skel/var/lib/pkgr/indexes/repo.toml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
[index.main]
|
||||||
|
last_update = "1610219288"
|
||||||
|
[index.main.packager]
|
||||||
|
versions = [1, 2, 3]
|
||||||
|
tags = ["tag"]
|
||||||
|
|
||||||
|
## rust
|
||||||
|
# IndexedPackage
|
||||||
|
# - name -> packager
|
||||||
|
# - origin_repo -> main
|
||||||
|
# - versions -> [1,2,3]
|
||||||
|
# - tags -> ["tag"]
|
||||||
|
# - get_package() -> Package
|
||||||
|
|
||||||
|
## rust
|
||||||
|
# Repo
|
||||||
|
# - name -> main
|
||||||
|
# - last_update -> 1610219288
|
||||||
|
# - update_repo() -> io::Result<()>
|
||||||
|
# - searchIndex(p: PackageIdentifier) -> IndexedPackage
|
0
pkgr/src/api/client.rs
Normal file
0
pkgr/src/api/client.rs
Normal file
63
pkgr/src/api/mod.rs
Normal file
63
pkgr/src/api/mod.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use serde;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub mod client;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum Query {
|
||||||
|
#[serde(rename = "pull")]
|
||||||
|
Pull {
|
||||||
|
name: String,
|
||||||
|
version: Option<u128>,
|
||||||
|
tags: Vec<String>
|
||||||
|
},
|
||||||
|
#[serde(rename = "push")]
|
||||||
|
Push {
|
||||||
|
// todo: review me pls
|
||||||
|
_data: Vec<u8>
|
||||||
|
},
|
||||||
|
#[serde(rename = "index")]
|
||||||
|
Index {
|
||||||
|
request_update: bool,
|
||||||
|
fetch: bool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Request {
|
||||||
|
version: u32,
|
||||||
|
id: String,
|
||||||
|
token: Option<String>,
|
||||||
|
query: HashMap<String, Query>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct DataErrorDetails {
|
||||||
|
actor: String,
|
||||||
|
detailed_cause: String,
|
||||||
|
recovery_options: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct DataError {
|
||||||
|
name: String,
|
||||||
|
cause: Option<String>,
|
||||||
|
details: Option<DataErrorDetails>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum Data {
|
||||||
|
Pull {
|
||||||
|
_data: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Response {
|
||||||
|
version: u32,
|
||||||
|
id: String,
|
||||||
|
reply_to: String,
|
||||||
|
errors: HashMap<String, DataError>,
|
||||||
|
data: HashMap<String, Data>
|
||||||
|
}
|
|
@ -1,12 +1,17 @@
|
||||||
use crate::package::identifier::PackageIdentifier;
|
use std::process::exit;
|
||||||
|
|
||||||
use crate::package::Package;
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
use colored::Colorize;
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
use std::process::exit;
|
use manifest::package::PackageType;
|
||||||
use crate::CONFIG;
|
|
||||||
|
use crate::package::identifier::PackageIdentifier;
|
||||||
|
use crate::package::Package;
|
||||||
|
use crate::package::queue::PackageQueue;
|
||||||
use crate::process::Process;
|
use crate::process::Process;
|
||||||
|
use crate::types::fetch::TryFetch;
|
||||||
|
use crate::util::prompts::prompt_bool;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(name = "pkgr", version)]
|
#[clap(name = "pkgr", version)]
|
||||||
|
@ -21,6 +26,8 @@ pub enum Command {
|
||||||
Install {
|
Install {
|
||||||
#[arg(short, long, default_value_t = false)]
|
#[arg(short, long, default_value_t = false)]
|
||||||
build: bool,
|
build: bool,
|
||||||
|
#[arg(short, long, default_value_t = false)]
|
||||||
|
ask: bool,
|
||||||
package_identifier: PackageIdentifier,
|
package_identifier: PackageIdentifier,
|
||||||
},
|
},
|
||||||
/// Remove a package from the system
|
/// Remove a package from the system
|
||||||
|
@ -38,7 +45,11 @@ pub enum Command {
|
||||||
},
|
},
|
||||||
/// Update packages on the system
|
/// Update packages on the system
|
||||||
Update,
|
Update,
|
||||||
#[command(hide = true)]
|
/// Get info about a package
|
||||||
|
Info {
|
||||||
|
package_identifier: Option<PackageIdentifier>
|
||||||
|
},
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
Debug,
|
Debug,
|
||||||
#[command(hide = true)]
|
#[command(hide = true)]
|
||||||
None,
|
None,
|
||||||
|
@ -49,6 +60,7 @@ impl Command {
|
||||||
match self {
|
match self {
|
||||||
Command::Install {
|
Command::Install {
|
||||||
build,
|
build,
|
||||||
|
ask,
|
||||||
package_identifier,
|
package_identifier,
|
||||||
} => {
|
} => {
|
||||||
warn!("Installer does not run in isolation.");
|
warn!("Installer does not run in isolation.");
|
||||||
|
@ -59,8 +71,7 @@ impl Command {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
info!("Parsing package...");
|
info!("Parsing package...");
|
||||||
trace!("Fetching package: {}", package_identifier);
|
let mut pkg = Package::try_fetch(package_identifier.clone()).unwrap();
|
||||||
let mut pkg = Package::fetch(package_identifier.clone()).unwrap();
|
|
||||||
debug!("manifest size: {}kb", pkg.pkgfile.manifest.len() / 1024);
|
debug!("manifest size: {}kb", pkg.pkgfile.manifest.len() / 1024);
|
||||||
debug!("files size: {}kb", pkg.pkgfile.data.len() / 1024);
|
debug!("files size: {}kb", pkg.pkgfile.data.len() / 1024);
|
||||||
|
|
||||||
|
@ -70,14 +81,19 @@ impl Command {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("Starting install...");
|
let mut queue = PackageQueue::new();
|
||||||
match pkg.install(CONFIG.with(|c| if !*build { c.build_by_default } else { *build })) {
|
queue.add_package(pkg, *build);
|
||||||
Ok(_) => (),
|
trace!("Installing queue...");
|
||||||
Err(e) => {
|
{
|
||||||
error!("Install failed: {}", e.to_string());
|
if *ask {
|
||||||
exit(1);
|
info!("Install following packages?");
|
||||||
|
info!(target: "item", "{}", queue);
|
||||||
|
if !prompt_bool("Continue?", false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
queue.install(*build);
|
||||||
|
|
||||||
let end = std::time::Instant::now();
|
let end = std::time::Instant::now();
|
||||||
let _unix_end = std::time::SystemTime::now()
|
let _unix_end = std::time::SystemTime::now()
|
||||||
|
@ -87,7 +103,7 @@ impl Command {
|
||||||
let duration = end.duration_since(start);
|
let duration = end.duration_since(start);
|
||||||
|
|
||||||
info!("Install complete.");
|
info!("Install complete.");
|
||||||
info!("Install took {}ms.", duration.as_nanos() as f64 / 1000000.0);
|
info!("Install took {}.", humantime::format_duration(duration));
|
||||||
}
|
}
|
||||||
Command::Remove {
|
Command::Remove {
|
||||||
package_identifier,
|
package_identifier,
|
||||||
|
@ -106,6 +122,74 @@ impl Command {
|
||||||
Command::Update => {
|
Command::Update => {
|
||||||
error!("Update is not yet implemented.");
|
error!("Update is not yet implemented.");
|
||||||
}
|
}
|
||||||
|
Command::Info {
|
||||||
|
package_identifier
|
||||||
|
} => {
|
||||||
|
if let Some(p) = package_identifier {
|
||||||
|
if let Ok(mut pkg) = Package::try_fetch(p.clone()) {
|
||||||
|
trace!("{}", pkg.pkgfile.manifest);
|
||||||
|
info!(target: "item", "Identifier: {:?}", pkg.identifier);
|
||||||
|
info!(target: "item", "");
|
||||||
|
let mani = pkg.manifest();
|
||||||
|
info!(target: "item", "Package name: {}", mani.package.name);
|
||||||
|
info!(target: "item", "Package description: {}", mani.package.description);
|
||||||
|
info!(target: "item", "Package version: {}", mani.package.version);
|
||||||
|
info!(target: "item", "Package tags: {}", mani.package.tags.join(", "));
|
||||||
|
info!(target: "item", "Package type: {}", mani.package.package_type.to_string());
|
||||||
|
info!(target: "item", "");
|
||||||
|
info!(target: "item", "Supported install types: {}", {
|
||||||
|
let mut types = vec![];
|
||||||
|
if let Some(_) = mani.bin { types.push("bin") }
|
||||||
|
if let Some(_) = mani.build { types.push("build") }
|
||||||
|
if let PackageType::Meta = mani.package.package_type { types.push("meta") }
|
||||||
|
types.join(", ")
|
||||||
|
});
|
||||||
|
info!(target: "item", "");
|
||||||
|
info!(target: "item", "Dependencies: {}", {
|
||||||
|
let deps = pkg.dependencies();
|
||||||
|
if deps.len() == 0 {
|
||||||
|
String::from("None")
|
||||||
|
} else {
|
||||||
|
deps
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
p.identifier.to_string()
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
info!(target: "item", "Build Dependencies: {}", {
|
||||||
|
let deps = pkg.build_dependencies();
|
||||||
|
if deps.len() == 0 {
|
||||||
|
String::from("None")
|
||||||
|
} else {
|
||||||
|
deps
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
p.identifier.to_string()
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
error!("Could not find {p}");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("Welcome to pkgr!\n\
|
||||||
|
{}\n\
|
||||||
|
To get help please run \"{} -h\"", env!("CARGO_PKG_DESCRIPTION"), std::env::args().nth(0).unwrap());
|
||||||
|
info!("");
|
||||||
|
info!("version: {}", env!("CARGO_PKG_VERSION"));
|
||||||
|
info!("authors: {}", env!("CARGO_PKG_AUTHORS"));
|
||||||
|
|
||||||
|
info!("");
|
||||||
|
info!("If you can't seem to figure something out, use environment variable: PKGR_LOG_LEVEL=debug");
|
||||||
|
trace!("{}", "The trace log level should really only be used by PKGR devs.".red());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
Command::Debug => {
|
Command::Debug => {
|
||||||
trace!("Trace message.\nWith newline.");
|
trace!("Trace message.\nWith newline.");
|
||||||
debug!("Debug message.\nWith newline.");
|
debug!("Debug message.\nWith newline.");
|
||||||
|
@ -123,10 +207,6 @@ impl Command {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("");
|
|
||||||
info!("PKGR VERSION: {}", env!("CARGO_PKG_VERSION"));
|
|
||||||
info!("PKGR AUTHORS: {}", env!("CARGO_PKG_AUTHORS"));
|
|
||||||
info!("PKGR DESCRIPTION: {}", env!("CARGO_PKG_DESCRIPTION"));
|
|
||||||
info!("");
|
info!("");
|
||||||
info!(
|
info!(
|
||||||
"PKGR_LOG_LEVEL: {}",
|
"PKGR_LOG_LEVEL: {}",
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use storage::Storage;
|
||||||
|
|
||||||
|
mod storage;
|
||||||
|
pub mod repos;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -6,13 +10,16 @@ pub struct Config {
|
||||||
pub build_by_default: bool,
|
pub build_by_default: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub tmp_dir: Option<String>,
|
pub tmp_dir: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub storage: Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Config {
|
fn default() -> Config {
|
||||||
Config {
|
Config {
|
||||||
build_by_default: false,
|
build_by_default: false,
|
||||||
tmp_dir: Some(String::from("/tmp/pkgr"))
|
tmp_dir: Some(String::from("/tmp/pkgr")),
|
||||||
|
storage: Storage::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
37
pkgr/src/config/repos.rs
Normal file
37
pkgr/src/config/repos.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Repo {
|
||||||
|
#[serde(default)]
|
||||||
|
pub name: String,
|
||||||
|
pub uri: Url
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Repo {
|
||||||
|
fn default() -> Self {
|
||||||
|
Repo {
|
||||||
|
name: String::from("Repo"),
|
||||||
|
uri: Url::parse("tcp://0.0.0.0:0000").unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct RepoFile {
|
||||||
|
pub repos: HashMap<String, Repo>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepoFile {
|
||||||
|
pub fn from_path(path: &str) -> Result<RepoFile, String> {
|
||||||
|
match std::fs::read_to_string(path) {
|
||||||
|
Ok(s) => match toml::from_str(&s) {
|
||||||
|
Ok(c) => Ok(c),
|
||||||
|
Err(e) => Err(format!("failed to parse config: {}", e)),
|
||||||
|
},
|
||||||
|
Err(e) => Err(format!("failed to read config: {}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
pkgr/src/config/storage.rs
Normal file
24
pkgr/src/config/storage.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Storage {
|
||||||
|
/// Where the repositories are defined.
|
||||||
|
#[serde(default)]
|
||||||
|
pub repo_file: String,
|
||||||
|
/// Where to store pkgs data
|
||||||
|
#[serde(default)]
|
||||||
|
pub data_dir: String,
|
||||||
|
/// Where to store repo indexes
|
||||||
|
#[serde(default)]
|
||||||
|
pub index_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Storage {
|
||||||
|
fn default() -> Self {
|
||||||
|
Storage {
|
||||||
|
repo_file: String::from("/etc/pkgr.d/repos.toml"),
|
||||||
|
data_dir: String::from("/var/lib/pkgr/packages"),
|
||||||
|
index_dir: String::from("/var/lib/pkgr/indexes"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,19 @@
|
||||||
|
use std::env;
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use fern::Dispatch;
|
use fern::Dispatch;
|
||||||
use log::{Record, SetLoggerError};
|
use log::{Record, SetLoggerError};
|
||||||
use std::env;
|
|
||||||
|
|
||||||
fn format_regular<S: Into<String>>(log: S, record: &Record) -> String {
|
fn format_regular<S: Into<String>>(log: S, record: &Record) -> String {
|
||||||
let log = log.into();
|
let log = log.into();
|
||||||
let line_prefix = |line: String, extend: bool| {
|
let line_prefix = |line: String, extend: bool| {
|
||||||
let prefix = if extend {
|
let prefix = if extend {
|
||||||
match record.level() {
|
match record.level() {
|
||||||
log::Level::Trace => " :".bright_blue(),
|
log::Level::Trace => " ]".bright_blue(),
|
||||||
log::Level::Debug => " :".green(),
|
log::Level::Debug => " ?".green(),
|
||||||
log::Level::Info => " :".blue(),
|
log::Level::Info => " >".blue(),
|
||||||
log::Level::Warn => " :".yellow(),
|
log::Level::Warn => " #".yellow(),
|
||||||
log::Level::Error => " :".red(),
|
log::Level::Error => " !".red(),
|
||||||
}.to_string()
|
}.to_string()
|
||||||
} else {
|
} else {
|
||||||
match record.level() {
|
match record.level() {
|
||||||
|
@ -43,14 +44,12 @@ pub fn setup_logger() -> Result<(), SetLoggerError> {
|
||||||
Dispatch::new()
|
Dispatch::new()
|
||||||
.format(|out, message, record| {
|
.format(|out, message, record| {
|
||||||
match record.metadata().target() {
|
match record.metadata().target() {
|
||||||
"command:stdout" => {
|
// command output logging
|
||||||
out.finish(format_args!("{} {}", ">>".cyan(), message.to_string()));
|
"command:stdout" => out.finish(format_args!("{} {}", ">>".cyan(), message.to_string())),
|
||||||
return;
|
"command:stderr" => out.finish(format_args!("{} {}", ">>".red(), message.to_string())),
|
||||||
}
|
// this target means, it's an item and not a log.
|
||||||
"command:stderr" => {
|
"item" => out.finish(format_args!("{} {}", "*".blue(), message.to_string())),
|
||||||
out.finish(format_args!("{} {}", ">>".red(), message.to_string()));
|
// default logging
|
||||||
return;
|
|
||||||
}
|
|
||||||
_ => out.finish(format_args!("{}", format_regular(message.to_string(), record))),
|
_ => out.finish(format_args!("{}", format_regular(message.to_string(), record))),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,16 +1,27 @@
|
||||||
use crate::commands::Cli;
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
|
use crate::commands::Cli;
|
||||||
|
|
||||||
|
/// pkgr's commands.
|
||||||
mod commands;
|
mod commands;
|
||||||
|
/// Logging implementations for pkgr.
|
||||||
mod logging;
|
mod logging;
|
||||||
|
/// Package and helpers.
|
||||||
mod package;
|
mod package;
|
||||||
|
/// Repo and helpers
|
||||||
|
mod repo;
|
||||||
|
/// Process wrapper with logging wrapper.
|
||||||
mod process;
|
mod process;
|
||||||
|
/// tmpfs wrapper.
|
||||||
mod tmpfs;
|
mod tmpfs;
|
||||||
|
/// pkgr's optional config.
|
||||||
mod config;
|
mod config;
|
||||||
|
/// custom types used by pkgr
|
||||||
|
mod types;
|
||||||
|
/// utils
|
||||||
|
mod util;
|
||||||
|
mod api;
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static CONFIG: config::Config = config::Config::from_path("/etc/pkgr.toml")
|
static CONFIG: config::Config = config::Config::from_path("/etc/pkgr.toml")
|
||||||
|
@ -18,7 +29,8 @@ thread_local! {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
logging::setup_logger().expect("Unable to setup logger.");
|
logging::setup_logger()
|
||||||
|
.expect("unable to set logger.");
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
use crate::package::identifier::PackageLocator;
|
|
||||||
use pkgfile::PKGFile;
|
|
||||||
use reqwest::blocking::get;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
#[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_by_path<S: Into<String>>(path: S) -> Result<PKGFile, FetchError> {
|
|
||||||
std::fs::read(path.into())
|
|
||||||
.map_err(|e| FetchError::IOError(e))
|
|
||||||
.and_then(|bytes| PKGFile::try_from(bytes).map_err(|_| FetchError::ParseError))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fetch_by_uri<S: Into<String>>(uri: S) -> Result<PKGFile, FetchError> {
|
|
||||||
// get file contents as bytes
|
|
||||||
let mut bytes = Vec::new();
|
|
||||||
match get(uri.into()) {
|
|
||||||
Ok(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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fetch_by_package_locator(_package_locator: PackageLocator) -> Result<PKGFile, FetchError> {
|
|
||||||
// TODO: search index for package locator
|
|
||||||
Ok(PKGFile::default())
|
|
||||||
}
|
|
|
@ -1,7 +1,9 @@
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::fmt::Formatter;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use serde::Serializer;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum PackageIdentifierError {
|
pub enum PackageIdentifierError {
|
||||||
|
@ -22,11 +24,29 @@ impl std::fmt::Display for PackageIdentifierError {
|
||||||
|
|
||||||
impl Error for PackageIdentifierError {}
|
impl Error for PackageIdentifierError {}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub enum PackageIdentifier {
|
pub enum PackageIdentifier {
|
||||||
PackageLocator(PackageLocator),
|
PackageLocator(PackageLocator),
|
||||||
URI(String),
|
URI(String),
|
||||||
Path(String),
|
Path(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for PackageIdentifier {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if f.alternate() {
|
||||||
|
match self {
|
||||||
|
PackageIdentifier::PackageLocator(pl) => write!(f, "PackageLocator({:#?})", pl),
|
||||||
|
PackageIdentifier::URI(uri) => write!(f, "URI({:?})", uri),
|
||||||
|
PackageIdentifier::Path(path) => write!(f, "Path({:?})", path),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match self {
|
||||||
|
PackageIdentifier::PackageLocator(pl) => write!(f, "PL: {:?}", pl),
|
||||||
|
PackageIdentifier::URI(uri) => write!(f, "URI: {:?}", uri),
|
||||||
|
PackageIdentifier::Path(path) => write!(f, "Path: {:?}", path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for PackageIdentifier {
|
impl std::fmt::Display for PackageIdentifier {
|
||||||
|
@ -39,7 +59,7 @@ impl std::fmt::Display for PackageIdentifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct PackageLocator {
|
pub struct PackageLocator {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub version: Option<u32>,
|
pub version: Option<u32>,
|
||||||
|
@ -140,8 +160,9 @@ impl FromStr for PackageLocator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(String, String)> for PackageLocator {
|
impl TryFrom<(String, String)> for PackageLocator {
|
||||||
fn from((name, locate_str): (String, String)) -> Self {
|
type Error = PackageIdentifierError;
|
||||||
|
fn try_from((name, locate_str): (String, String)) -> Result<PackageLocator, Self::Error> {
|
||||||
// name = "pkg"
|
// name = "pkg"
|
||||||
// locate_str = "1.0.0:tag1,tag2" or "1.0.0" or "tag1,tag2"
|
// locate_str = "1.0.0:tag1,tag2" or "1.0.0" or "tag1,tag2"
|
||||||
let mut version = None;
|
let mut version = None;
|
||||||
|
@ -165,10 +186,10 @@ impl From<(String, String)> for PackageLocator {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
PackageLocator {
|
Ok(PackageLocator {
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
tags,
|
tags,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,26 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum BinError {
|
pub enum BinError {
|
||||||
UnpackError(String),
|
UnpackError(String),
|
||||||
|
IOError(io::Error),
|
||||||
|
Cancelled,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for BinError {
|
impl Display for BinError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
BinError::UnpackError(e) => write!(f, "Unpack error: {}", e),
|
BinError::UnpackError(e) => write!(f, "Unpack error: {}", e),
|
||||||
|
BinError::IOError(e) => write!(f, "IO error: {}", e),
|
||||||
|
BinError::Cancelled => write!(f, "Cancelled by user"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum BuildError {
|
pub enum BuildError {
|
||||||
InvalidManifest,
|
InvalidManifest(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for BuildError {
|
impl Display for BuildError {
|
||||||
|
@ -30,17 +35,15 @@ impl Display for BuildError {
|
||||||
pub enum InstallError {
|
pub enum InstallError {
|
||||||
BuildError(BuildError),
|
BuildError(BuildError),
|
||||||
BinError(BinError),
|
BinError(BinError),
|
||||||
InvalidManifest,
|
InvalidManifest(String),
|
||||||
Generic,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for InstallError {
|
impl ToString for InstallError {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
InstallError::BuildError(e) => format!("Build error: \n{}", e),
|
InstallError::BuildError(e) => format!("{}", e),
|
||||||
InstallError::BinError(e) => format!("Bin error: \n{}", e),
|
InstallError::BinError(e) => format!("{}", e),
|
||||||
InstallError::InvalidManifest => "Invalid manifest".to_string(),
|
InstallError::InvalidManifest(s) => format!("{}", s),
|
||||||
InstallError::Generic => "Install error".to_string(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
use crate::tmpfs::TempDir;
|
use std::io;
|
||||||
|
use std::fs::remove_dir_all;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use log::{debug, trace};
|
||||||
|
|
||||||
use errors::{BinError, BuildError, InstallError};
|
use errors::{BinError, BuildError, InstallError};
|
||||||
use log::{debug, error, info, trace};
|
|
||||||
use manifest::Manifest;
|
use manifest::Manifest;
|
||||||
use pkgfile::PKGFile;
|
use pkgfile::PKGFile;
|
||||||
|
|
||||||
use std::process::exit;
|
|
||||||
use crate::CONFIG;
|
use crate::CONFIG;
|
||||||
use crate::package::identifier::{PackageIdentifier, PackageLocator};
|
|
||||||
use crate::package::Package;
|
use crate::tmpfs::TempDir;
|
||||||
|
use crate::types::fetch::TryFetch;
|
||||||
|
use crate::util::fs::copy_recursively;
|
||||||
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
|
|
||||||
|
@ -37,11 +41,14 @@ impl PackageInstaller {
|
||||||
if !self.pkgfile.has_data() {
|
if !self.pkgfile.has_data() {
|
||||||
return Err(BinError::UnpackError("package has no data".to_string()));
|
return Err(BinError::UnpackError("package has no data".to_string()));
|
||||||
}
|
}
|
||||||
if std::path::Path::new(&path).exists() {
|
let path = std::path::Path::new(&path);
|
||||||
return Err(BinError::UnpackError(format!(
|
if path.exists() {
|
||||||
"path already exists: {}",
|
trace!("cache already exists..");
|
||||||
path
|
debug!("removing cache dir...");
|
||||||
)));
|
match remove_dir_all(path) {
|
||||||
|
Ok(_) => debug!("removed cache directory"),
|
||||||
|
Err(e) => return Err(BinError::UnpackError(format!("unable to remove directory ({}): {}", path.to_str().unwrap_or("unknown"), e.to_string())))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tar::Archive::new(self.pkgfile.data.as_slice())
|
tar::Archive::new(self.pkgfile.data.as_slice())
|
||||||
.unpack(&path)
|
.unpack(&path)
|
||||||
|
@ -52,39 +59,32 @@ impl PackageInstaller {
|
||||||
let mut tmpdir = TempDir::default();
|
let mut tmpdir = TempDir::default();
|
||||||
tmpdir.push(&self.manifest.package.name);
|
tmpdir.push(&self.manifest.package.name);
|
||||||
trace!("extracting package into: {}", tmpdir.to_string());
|
trace!("extracting package into: {}", tmpdir.to_string());
|
||||||
match self.extract_to(tmpdir.to_string()) {
|
if let Err(e) = self.extract_to(tmpdir.to_string()) {
|
||||||
Ok(_) => {}
|
return Err(e);
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
}
|
||||||
debug!("extracted package in: {}", tmpdir.to_string());
|
debug!("extracted package in: {}", tmpdir.to_string());
|
||||||
|
match self.apply_overlay() {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(BinError::IOError(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_overlay(&self) -> Result<(), io::Error> {
|
||||||
|
let mut tmpdir = TempDir::default();
|
||||||
|
tmpdir.push(&self.manifest.package.name);
|
||||||
|
tmpdir.push(&self.manifest.bin.clone().unwrap().root);
|
||||||
|
if let Err(e) = copy_recursively(&tmpdir.path(), Path::new("/")) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(&self) -> Result<(), BuildError> {
|
fn build(&self) -> Result<(), BuildError> {
|
||||||
if let None = self.manifest.build.clone() {
|
if let None = self.manifest.build.clone() {
|
||||||
return Err(BuildError::InvalidManifest);
|
return Err(BuildError::InvalidManifest(String::from("No build manifest")));
|
||||||
}
|
}
|
||||||
let build_manifest = self.manifest.build.clone().unwrap();
|
let build_manifest = self.manifest.build.clone().unwrap();
|
||||||
// TODO: Check dependencies
|
// TODO: Check dependencies
|
||||||
for pkg_tuple in build_manifest.dependencies {
|
|
||||||
let mut pkg = Package::fetch(
|
|
||||||
PackageIdentifier::PackageLocator(
|
|
||||||
PackageLocator::from(pkg_tuple)
|
|
||||||
)
|
|
||||||
).expect("no pkg");
|
|
||||||
|
|
||||||
if !pkg.is_installed() {
|
|
||||||
match pkg.install(CONFIG.with(|c| {
|
|
||||||
c.build_by_default
|
|
||||||
})) {
|
|
||||||
Ok(_) => { info!("Installed dependency: \"{}\"", pkg.manifest().package.name) },
|
|
||||||
Err(_) => {
|
|
||||||
error!("Could not install dependency: \"{}\"", pkg.identifier);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ impl PackageInstaller {
|
||||||
if let None = self.manifest.bin {
|
if let None = self.manifest.bin {
|
||||||
self.install_type = InstallType::Build;
|
self.install_type = InstallType::Build;
|
||||||
if let None = self.manifest.build {
|
if let None = self.manifest.build {
|
||||||
return Err(InstallError::InvalidManifest);
|
return Err(InstallError::InvalidManifest(String::from("no bin or build manifest")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,17 +103,23 @@ impl PackageInstaller {
|
||||||
if let None = self.manifest.build {
|
if let None = self.manifest.build {
|
||||||
self.install_type = InstallType::Bin;
|
self.install_type = InstallType::Bin;
|
||||||
if let None = self.manifest.bin {
|
if let None = self.manifest.bin {
|
||||||
return Err(InstallError::InvalidManifest);
|
return Err(InstallError::InvalidManifest(String::from("no build or bin manifest")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.install_type {
|
let r = match self.install_type {
|
||||||
InstallType::Bin => self.bin()
|
InstallType::Bin => self.bin()
|
||||||
.map_err(|e| InstallError::BinError(e)),
|
.map_err(|e| InstallError::BinError(e)),
|
||||||
InstallType::Build => self.build()
|
InstallType::Build => self.build()
|
||||||
.map_err(|e| InstallError::BuildError(e)),
|
.map_err(|e| InstallError::BuildError(e)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = r {
|
||||||
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,31 @@
|
||||||
use log::{info, trace};
|
use std::fmt::Display;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
use log::{debug, error, info, trace};
|
||||||
|
use reqwest::blocking::get;
|
||||||
|
|
||||||
|
use manifest::build::Build;
|
||||||
|
use pkgfile::PKGFile;
|
||||||
|
|
||||||
|
use crate::package::identifier::{PackageIdentifier, PackageLocator};
|
||||||
|
use crate::types::fetch::TryFetch;
|
||||||
|
|
||||||
pub mod fetch;
|
|
||||||
pub mod identifier;
|
pub mod identifier;
|
||||||
pub mod installer;
|
pub mod installer;
|
||||||
|
pub mod queue;
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
pub struct Package {
|
pub struct Package {
|
||||||
pub identifier: identifier::PackageIdentifier,
|
pub identifier: PackageIdentifier,
|
||||||
pub pkgfile: pkgfile::PKGFile,
|
pub pkgfile: PKGFile,
|
||||||
is_installed: bool,
|
is_installed: bool,
|
||||||
is_indexed: bool,
|
is_indexed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Package {
|
impl Package {
|
||||||
/// Create a new package from a package identifier and a package file.
|
/// Create a new package from a package identifier and a package file.
|
||||||
pub fn new(identifier: identifier::PackageIdentifier, pkgfile: pkgfile::PKGFile) -> Package {
|
pub fn new(identifier: PackageIdentifier, pkgfile: PKGFile) -> Package {
|
||||||
Package {
|
Package {
|
||||||
identifier,
|
identifier,
|
||||||
pkgfile,
|
pkgfile,
|
||||||
|
@ -22,34 +34,60 @@ impl Package {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch a package from a package identifier.
|
|
||||||
pub fn fetch(
|
|
||||||
package_identifier: identifier::PackageIdentifier,
|
|
||||||
) -> Result<Package, fetch::FetchError> {
|
|
||||||
match &package_identifier {
|
|
||||||
identifier::PackageIdentifier::Path(path) => {
|
|
||||||
trace!("fetching package from path: {}", path);
|
|
||||||
let pkgfile = fetch::fetch_by_path(path).unwrap();
|
|
||||||
Ok(Package::new(package_identifier, pkgfile))
|
|
||||||
}
|
|
||||||
identifier::PackageIdentifier::URI(url) => {
|
|
||||||
trace!("fetching package from uri: {}", url);
|
|
||||||
let pkgfile = fetch::fetch_by_uri(url).unwrap();
|
|
||||||
Ok(Package::new(package_identifier, pkgfile))
|
|
||||||
}
|
|
||||||
identifier::PackageIdentifier::PackageLocator(locator) => {
|
|
||||||
trace!("fetching package from locator: {}", locator);
|
|
||||||
let pkgfile = fetch::fetch_by_package_locator(locator.clone()).unwrap();
|
|
||||||
Ok(Package::new(package_identifier, pkgfile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the package manifest.
|
/// Get the package manifest.
|
||||||
pub fn manifest(&self) -> manifest::Manifest {
|
pub fn manifest(&self) -> manifest::Manifest {
|
||||||
manifest::Manifest::try_from(self.pkgfile.manifest.clone()).unwrap()
|
manifest::Manifest::try_from(self.pkgfile.manifest.clone()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get package dependencies
|
||||||
|
pub fn dependencies(&self) -> Vec<Package> {
|
||||||
|
let mut dependencies = vec![];
|
||||||
|
for dependency in self.manifest().dependencies {
|
||||||
|
let pkglocate = if let Ok(pl) = PackageLocator::try_from(dependency.clone()) {
|
||||||
|
trace!("parsed pl successfully...");
|
||||||
|
pl
|
||||||
|
} else {
|
||||||
|
error!("Could not parse package locator: {:?} in dependencies.", &dependency);
|
||||||
|
exit(1);
|
||||||
|
};
|
||||||
|
let pkg = match Package::try_fetch(PackageIdentifier::PackageLocator(pkglocate)) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Could not fetch dependency: {}", e);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
dependencies.push(pkg);
|
||||||
|
}
|
||||||
|
dependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_dependencies(&self) -> Vec<Package> {
|
||||||
|
let mut dependencies = vec![];
|
||||||
|
for dependency in self
|
||||||
|
.manifest()
|
||||||
|
.build
|
||||||
|
.unwrap_or(Build::default())
|
||||||
|
.dependencies {
|
||||||
|
let pkglocate = if let Ok(pl) = PackageLocator::try_from(dependency.clone()) {
|
||||||
|
trace!("parsed pl successfully...");
|
||||||
|
pl
|
||||||
|
} else {
|
||||||
|
error!("Could not parse package locator: {:?} in dependencies.", &dependency);
|
||||||
|
exit(1);
|
||||||
|
};
|
||||||
|
let pkg = match Package::try_fetch(PackageIdentifier::PackageLocator(pkglocate)) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Could not fetch dependency: {}", e);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
dependencies.push(pkg);
|
||||||
|
}
|
||||||
|
dependencies
|
||||||
|
}
|
||||||
|
|
||||||
/// Install the package.
|
/// Install the package.
|
||||||
pub fn install(&mut self, build: bool) -> Result<(), installer::errors::InstallError> {
|
pub fn install(&mut self, build: bool) -> Result<(), installer::errors::InstallError> {
|
||||||
let manifest = self.manifest();
|
let manifest = self.manifest();
|
||||||
|
@ -96,3 +134,57 @@ impl Package {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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 TryFetch<PackageIdentifier> for Package {
|
||||||
|
type Error = FetchError;
|
||||||
|
|
||||||
|
/// Fetch a package from a package identifier.
|
||||||
|
fn try_fetch(query: PackageIdentifier) -> Result<Package, Self::Error> {
|
||||||
|
trace!("Fetching: {query:#?}");
|
||||||
|
let pkgfile = match &query {
|
||||||
|
PackageIdentifier::Path(s) => match PKGFile::try_from(Path::new(&s)) {
|
||||||
|
Ok(p) => Ok(p),
|
||||||
|
Err(e) => Err(FetchError::ParseError)
|
||||||
|
},
|
||||||
|
PackageIdentifier::URI(s) => {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
debug!("sending GET request...");
|
||||||
|
match get::<String>(s.into()) {
|
||||||
|
Ok(response) => {
|
||||||
|
debug!("Got response!");
|
||||||
|
if let Ok(b) = response.bytes() {
|
||||||
|
bytes.extend(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(l) => unimplemented!()
|
||||||
|
};
|
||||||
|
|
||||||
|
pkgfile
|
||||||
|
.map(|p| Package::new(query, p))
|
||||||
|
}
|
||||||
|
}
|
51
pkgr/src/package/queue.rs
Normal file
51
pkgr/src/package/queue.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
use log::trace;
|
||||||
|
|
||||||
|
use crate::package::Package;
|
||||||
|
|
||||||
|
pub struct PackageQueue {
|
||||||
|
packages: Vec<Package>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageQueue {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
PackageQueue {
|
||||||
|
packages: vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_package(&mut self, package: Package, build: bool) {
|
||||||
|
let dependencies = package.dependencies();
|
||||||
|
for dependency in dependencies {
|
||||||
|
trace!("Checking package: {}", &dependency.identifier);
|
||||||
|
if self.packages.contains(&dependency) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
trace!("Adding package: {}", &dependency.identifier);
|
||||||
|
self.packages.push(dependency);
|
||||||
|
}
|
||||||
|
if !self.packages.contains(&package) {
|
||||||
|
self.packages.push(package);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn install(&mut self, build: bool) {
|
||||||
|
self.packages
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|pkg| {
|
||||||
|
pkg.install(build).expect("TODO: panic message");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PackageQueue {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for pkg in &self.packages {
|
||||||
|
if let Err(e) = write!(f, "{}", pkg.identifier) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use log::{info, trace};
|
|
||||||
|
|
||||||
|
use log::{info, trace};
|
||||||
|
|
||||||
pub struct Process {
|
pub struct Process {
|
||||||
pub command: Vec<String>,
|
pub command: Vec<String>,
|
||||||
|
|
18
pkgr/src/repo/index/index_package.rs
Normal file
18
pkgr/src/repo/index/index_package.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
use pkgfile::PKGFile;
|
||||||
|
use crate::api::Query;
|
||||||
|
use crate::CONFIG;
|
||||||
|
use crate::package::Package;
|
||||||
|
use crate::repo::index::RepoIndex;
|
||||||
|
|
||||||
|
/// This struct solely exists for indexing and has no real functionality.
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct IndexPackage {
|
||||||
|
name: String,
|
||||||
|
versions: Vec<u32>,
|
||||||
|
tags: Vec<String>,
|
||||||
|
uri: Url
|
||||||
|
}
|
58
pkgr/src/repo/index/mod.rs
Normal file
58
pkgr/src/repo/index/mod.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::api::Request;
|
||||||
|
use crate::CONFIG;
|
||||||
|
use crate::repo::index::index_package::IndexPackage;
|
||||||
|
use crate::repo::Repo;
|
||||||
|
use crate::types::fetch::{Fetch, TryFetch};
|
||||||
|
use crate::util::create_uuid;
|
||||||
|
|
||||||
|
pub mod index_package;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct RepoIndex {
|
||||||
|
origin_repo: String,
|
||||||
|
packages: HashMap<String, IndexPackage>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepoIndex {
|
||||||
|
// /// Fetch existing index or create a new one.
|
||||||
|
// pub fn from_repo(repo: Repo) -> Self {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// Create new index.
|
||||||
|
// pub fn create_with_repo(repo: Repo) -> Self {
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// Get repo
|
||||||
|
pub fn get_repo(&self) -> io::Result<Repo> {
|
||||||
|
Ok(Repo::from_name(&self.origin_repo)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_path(path: &str) -> Result<RepoIndex, String> {
|
||||||
|
match std::fs::read_to_string(path) {
|
||||||
|
Ok(s) => match toml::from_str(&s) {
|
||||||
|
Ok(c) => Ok(c),
|
||||||
|
Err(e) => Err(format!("failed to parse config: {}", e)),
|
||||||
|
},
|
||||||
|
Err(e) => Err(format!("failed to read config: {}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFetch<Repo> for RepoIndex {
|
||||||
|
type Error = ();
|
||||||
|
/// Fetch
|
||||||
|
fn try_fetch(query: Repo) -> Result<Self, Self::Error> {
|
||||||
|
let path = CONFIG.with(|c| c.storage.index_dir.clone()) + query.uri.as_str() + ".toml";
|
||||||
|
if Path::new(path.as_str()).exists() {
|
||||||
|
RepoIndex::from_path(path.as_str())
|
||||||
|
.map_err(|_| ())
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
pkgr/src/repo/mod.rs
Normal file
42
pkgr/src/repo/mod.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Error, ErrorKind};
|
||||||
|
use std::path::Path;
|
||||||
|
use url::Url;
|
||||||
|
use crate::CONFIG;
|
||||||
|
use crate::config::repos::RepoFile;
|
||||||
|
|
||||||
|
/// Indexed repos
|
||||||
|
pub mod index;
|
||||||
|
|
||||||
|
pub struct Repo {
|
||||||
|
name: String,
|
||||||
|
uri: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Repo {
|
||||||
|
pub fn from_name(name: &String) -> io::Result<Repo> {
|
||||||
|
let r = RepoFile::from_path(CONFIG.with(|c| c.storage.repo_file.clone()).as_str())
|
||||||
|
.map_err(|e| Error::new(ErrorKind::Other, e))?;
|
||||||
|
let r = r
|
||||||
|
.repos
|
||||||
|
.get(name)
|
||||||
|
.ok_or(Error::new(ErrorKind::InvalidData, "Could not get repo"))?;
|
||||||
|
Ok(Repo {
|
||||||
|
name: r.name.clone(),
|
||||||
|
uri: r.uri.clone()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_name(&self) -> String {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_uri(&self) -> Url {
|
||||||
|
self.uri.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch indexed repo
|
||||||
|
pub fn get_index(&self) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,32 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::CONFIG;
|
use crate::CONFIG;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct TempDir {
|
pub struct TempDir {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TempDir {
|
impl TempDir {
|
||||||
pub fn new(path: PathBuf) -> TempDir {
|
pub fn new(path: PathBuf) -> TempDir {
|
||||||
|
let pbs: String = path.to_str().unwrap().into();
|
||||||
|
let path = expanduser::expanduser(&pbs).unwrap();
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
std::fs::create_dir_all(&path).unwrap();
|
std::fs::create_dir_all(&path).unwrap();
|
||||||
}
|
}
|
||||||
TempDir { path }
|
TempDir { path }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> PathBuf {
|
||||||
|
self.path.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push<S: Into<String>>(&mut self, path: S) {
|
pub fn push<S: Into<String>>(&mut self, path: S) {
|
||||||
self.path.push(path.into());
|
let mut path_str = path.into();
|
||||||
|
if path_str.starts_with('/') {
|
||||||
|
path_str = path_str[1..].to_string();
|
||||||
|
}
|
||||||
|
self.path.push(path_str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
pkgr/src/types/fetch.rs
Normal file
10
pkgr/src/types/fetch.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/// Get a result from an external source
|
||||||
|
pub trait Fetch<Q, R = Self> {
|
||||||
|
fn fetch(query: Q) -> R;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to get a result from an external source
|
||||||
|
pub trait TryFetch<Q, R = Self> {
|
||||||
|
type Error;
|
||||||
|
fn try_fetch(query: Q) -> Result<R, Self::Error>;
|
||||||
|
}
|
1
pkgr/src/types/mod.rs
Normal file
1
pkgr/src/types/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod fetch;
|
41
pkgr/src/util/fs.rs
Normal file
41
pkgr/src/util/fs.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use std::{fs, io};
|
||||||
|
use std::fs::DirEntry;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use log::trace;
|
||||||
|
|
||||||
|
pub fn visit_dirs(dir: &Path, cb: &dyn Fn(&DirEntry)) -> std::io::Result<()> {
|
||||||
|
if dir.is_dir() {
|
||||||
|
for entry in std::fs::read_dir(dir)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() {
|
||||||
|
visit_dirs(&path, cb)?;
|
||||||
|
} else {
|
||||||
|
cb(&entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy_recursively(source: &Path, target: &Path) -> io::Result<()> {
|
||||||
|
if source.is_file() {
|
||||||
|
trace!("source: {:?}, target: {:?}", source, target);
|
||||||
|
fs::copy(source, target)?;
|
||||||
|
} else if source.is_dir() {
|
||||||
|
if !target.exists() {
|
||||||
|
fs::create_dir(target)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in fs::read_dir(source)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let file_name = entry.file_name();
|
||||||
|
let source_path = entry.path();
|
||||||
|
let target_path = target.join(&file_name);
|
||||||
|
|
||||||
|
copy_recursively(&source_path, &target_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
9
pkgr/src/util/mod.rs
Normal file
9
pkgr/src/util/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
pub mod prompts;
|
||||||
|
/// Helpers for fs
|
||||||
|
pub mod fs;
|
||||||
|
|
||||||
|
/// Create a UUID
|
||||||
|
pub fn create_uuid() -> String {
|
||||||
|
// TODO
|
||||||
|
String::from("rand")
|
||||||
|
}
|
33
pkgr/src/util/prompts.rs
Normal file
33
pkgr/src/util/prompts.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use log::trace;
|
||||||
|
|
||||||
|
pub fn is_noninteractive() -> bool {
|
||||||
|
if let Ok(v) = std::env::var("PKGR_NON_INTERACTIVE") {
|
||||||
|
trace!("PKGR_NON_INTERACTIVE={}", v);
|
||||||
|
match v.as_str() {
|
||||||
|
"1" => true,
|
||||||
|
"true" => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
} else { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prompt_bool<S: Into<String>>(prompt: S, default: bool) -> bool {
|
||||||
|
if is_noninteractive() { return default; }
|
||||||
|
print!("{} [{}]: ", prompt.into(), if default { "Y/n" } else { "y/N" });
|
||||||
|
match std::io::stdout().flush() {
|
||||||
|
Ok(_) => (),
|
||||||
|
_ => println!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
std::io::stdin().read_line(&mut input).expect("Failed to read input.");
|
||||||
|
let answer = input.trim().to_lowercase();
|
||||||
|
|
||||||
|
if answer.is_empty() {
|
||||||
|
default
|
||||||
|
} else {
|
||||||
|
answer == "y" || answer == "yes"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue