Commit d06a4167 authored by Jeremy Soller's avatar Jeremy Soller

Merge branch 'pkg-depends' of https://github.com/AgostonSzepessy/pkgutils

parents 8ae44669 fc516c61
......@@ -22,6 +22,9 @@ serde_derive = "1.0"
tar = { git = "https://github.com/redox-os/tar-rs" }
toml = "0.4"
version-compare = "0.0.4"
petgraph = "0.4.5"
bidir-map = "0.6.0"
ordermap = "0.2.11"
[dependencies.hyper]
version = "0.10"
......
......@@ -4,8 +4,9 @@ extern crate liner;
extern crate pkgutils;
extern crate version_compare;
extern crate clap;
extern crate ordermap;
use pkgutils::{Repo, Package, PackageMeta, PackageMetaList};
use pkgutils::{Database, Repo, Package, PackageMeta, PackageMetaList};
use std::{env, process};
use std::error::Error;
use std::fs::{self, File};
......@@ -13,6 +14,7 @@ use std::io::{self, Read};
use std::path::Path;
use version_compare::{VersionCompare, CompOp};
use clap::{App, SubCommand, Arg};
use ordermap::OrderMap;
fn upgrade(repo: Repo) -> io::Result<()> {
let mut local_list = PackageMetaList::new();
......@@ -136,6 +138,7 @@ fn main() {
let target = matches.value_of("target").unwrap_or(env!("PKG_DEFAULT_TARGET"));
let repo = Repo::new(target);
let database = Database::open("/pkg", "/etc/pkg.d/pkglist");
let mut success = true;
......@@ -183,16 +186,97 @@ fn main() {
}
}
("install", Some(m)) => {
let mut dependencies = OrderMap::new();
let mut tar_gz_pkgs = Vec::new();
// Calculate dependencies for packages listed in database
for package in m.values_of("package").unwrap() {
let pkg = if package.ends_with(".tar.gz") {
let path = env::current_dir().unwrap().join(package);
Package::from_path(&path)
// Check if package is in current directory
if package.ends_with(".tar.gz") {
let path = env::current_dir().unwrap().join(&package);
// Extract package report errors
let extracted_pkg = match Package::from_path(&path) {
Ok(p) => Some(p),
Err(e) => {
eprintln!("error: {}", e);
if let Some(cause) = e.cause() {
eprintln!("cause: {}", cause);
}
success = false;
None
}
};
// Try to calculate dependencies for package if extraction was
// successful.
if let Some(mut pkg) = extracted_pkg {
// Obtain meta data from package to figure out its dependencies
let res: Option<()> = match pkg.meta() {
Ok(m) => {
// Calculate all dependencies and check for errors
if let Err(e) = database.calculate_depends(&m.name, &mut dependencies) {
eprintln!("error: {}", e);
if let Some(cause) = e.cause() {
eprintln!("cause: {}", cause);
success = false;
}
None
} else { // Dependency calculation was successful
Some(())
}
},
// Something went wrong when the meta data was obtained
Err(e) => {
eprintln!("error: {}", e);
if let Some(cause) = e.cause() {
eprintln!("cause: {}", cause);
}
success = false;
None
}
};
// Only install package if dependency calculation was successful
match res {
Some(_) => tar_gz_pkgs.push(pkg),
None => (),
}
}
} else {
repo.fetch(package)
};
// Package is not in current directory so calculate dependencies
// from database
match database.calculate_depends(package, &mut dependencies) {
Ok(_) => {
dependencies.insert(package.to_string(), ());
},
Err(e) => {
eprintln!("error during dependency calculation: {}", e);
if let Some(cause) = e.cause() {
eprintln!("cause: {}", cause);
success = false;
}
},
}
}
}
// Download each package, except *.tar.gz, and then install each package.
for package in dependencies.keys() {
let pkg = repo.fetch(package);
let dest = m.value_of("root").unwrap_or("/");
print_result!(pkg.and_then(|mut p| p.install(dest)), "succeeded", package);
}
for mut package in tar_gz_pkgs {
let dest = m.value_of("root").unwrap_or("/");
print_result!(package.install(dest), "succeeded");
}
}
("list", Some(m)) => {
for package in m.values_of("package").unwrap() {
......
use std::fs::File;
use std::path::{Path, PathBuf};
use std::io;
use std::io::Read;
use std::error;
use std::fmt;
use petgraph;
use petgraph::graphmap::DiGraphMap;
use bidir_map::BidirMap;
use ordermap::OrderMap;
use toml::de;
use PackageMeta;
/// Error type for the `Database`. It's a combination of an `std::io::Error`,
/// `toml::de::Error`, and a cyclic error that can occur during dependency
/// resolution.
#[derive(Debug)]
pub enum DatabaseError {
Io(io::Error),
Toml(de::Error),
Cycle(String),
}
impl fmt::Display for DatabaseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
DatabaseError::Io(ref err) => write!(f, "IO error: {}", err),
DatabaseError::Toml(ref err) => write!(f, "TOML parsing error: {}", err),
DatabaseError::Cycle(ref err) => write!(f, "Cyclic dependency: {}", err),
}
}
}
impl error::Error for DatabaseError {
fn description(&self) -> &str {
match *self {
DatabaseError::Io(ref err) => err.description(),
DatabaseError::Toml(ref err) => err.description(),
DatabaseError::Cycle(_) => "Cyclic dependency",
}
}
fn cause(&self) -> Option<&error::Error> {
match *self {
DatabaseError::Io(ref err) => Some(err),
DatabaseError::Toml(ref err) => Some(err),
DatabaseError::Cycle(_) => None,
}
}
}
impl From<io::Error> for DatabaseError {
fn from(err: io::Error) -> DatabaseError {
DatabaseError::Io(err)
}
}
impl From<de::Error> for DatabaseError {
fn from(err: de::Error) -> DatabaseError {
DatabaseError::Toml(err)
}
}
/// The `Database` contains a list of all packages that are available for
/// install, as well as a list of all the packages installed on the system.
/// It is used to calculate the dependencies of a package and for checking if
/// a package is installed.
#[derive(Debug)]
pub struct Database {
/// The path to the directory that contains the manifests of the packages
/// installed
installed_path: PathBuf,
/// The path to the directory that contains the manifests of the packages
/// available for install
pkglist_path: PathBuf,
}
/// The `Database` contains a list of all packages that are available for
/// install, as well as a list of all the packages installed on the system.
/// It is used to calculate the dependencies of a package and for checking if
/// a package is installed.
impl Database {
/// Opens a database from the specified path.
pub fn open<P: AsRef<Path>>(installed_path: P, pkglist_path: P) -> Self {
Database {
installed_path: installed_path.as_ref().to_path_buf(),
pkglist_path: pkglist_path.as_ref().to_path_buf(),
}
}
/// Checks if a package is installed
pub fn is_pkg_installed(&self, pkg_name: &str) -> bool {
let pkg_path_buf = self.installed_path.as_path().join(format!("{}.toml", pkg_name));
let installed = pkg_path_buf.as_path().exists();
installed
}
/// Retrieves the dependencies of a package that are listed in its manifest
/// file.
pub fn get_pkg_depends(&self, pkg_name: &str) -> Result<Vec<String>, DatabaseError> {
let path = self.pkglist_path.as_path().join(format!("{}.toml", pkg_name));
let mut input = String::new();
File::open(path.as_path().to_str().unwrap()).and_then(|mut f| {
f.read_to_string(&mut input)
})?;
Ok(PackageMeta::from_toml(&input)?.depends)
}
/// Calculates the dependencies of the specified package, and appends them to
/// `ordered_dependencies`.
pub fn calculate_depends(&self, pkg_name: &str, ordered_dependencies: &mut OrderMap<String, ()>) -> Result<(), DatabaseError> {
let mut graph = DiGraphMap::new();
// Use bimap to intern strings and use integers for keys in graph because
// String doesn't implement Copy and graphmap requires Copy
let mut map = BidirMap::new();
map.insert(pkg_name.to_string(), 0);
self.calculate_depends_rec(pkg_name, &mut map, &mut graph)?;
// Convert integers back to package names and calculate install order
let dependency_ids = petgraph::algo::toposort(&graph, None).or_else(|err| {
// There was a cyclic dependency. Since the graph is made up of numbers, the
// name of the package that caused the cyclic dependency must be retrieved for
// human readability.
Err(DatabaseError::Cycle(map.get_by_second(&err.node_id()).unwrap().to_string()))
})?;
for i in dependency_ids {
if !ordered_dependencies.contains_key(map.get_by_second(&i).unwrap()) {
if let Some((name, _)) = map.remove_by_second(&i) {
ordered_dependencies.insert(name, ());
}
}
}
Ok(())
}
/// Helper function to calculate package dependencies.
fn calculate_depends_rec(&self, pkg_name: &str, map: &mut BidirMap<String, usize>, graph: &mut DiGraphMap<usize, u8>) -> Result<(), DatabaseError> {
let curr_node = *map.get_by_first(pkg_name).unwrap();
let mut depends = self.get_pkg_depends(pkg_name)?;
if depends.len() == 0 {
return Ok(());
}
// Copy all dependencies from vector into map, using the map length as the key
while !depends.is_empty() {
let index = depends.len() - 1;
let dependency = depends.remove(index);
// Check if package is already installed
if !self.is_pkg_installed(&dependency) {
// Check if the package is already in the graph. If it is, its
// dependencies don't need to be calculated.
if !map.contains_first_key(&dependency) {
let dependency_node = map.len();
graph.add_node(dependency_node);
map.insert(dependency, dependency_node);
graph.add_edge(dependency_node, curr_node, 0);
let dependency_name = map.get_mut_by_second(&dependency_node).unwrap().clone();
self.calculate_depends_rec(&dependency_name, map, graph)?;
} else {
// Dependencies don't need to be calculated; the package only needs to get
// linked to the current node
let dependency_node = *map.get_by_first(&dependency).unwrap();
graph.add_edge(dependency_node, curr_node, 0);
}
}
}
Ok(())
}
}
......@@ -9,6 +9,9 @@ extern crate serde_derive;
extern crate tar;
extern crate toml;
extern crate pbr;
extern crate petgraph;
extern crate bidir_map;
extern crate ordermap;
use libflate::gzip::Encoder;
use octavo::octavo_digest::Digest;
......@@ -21,10 +24,12 @@ use std::path::Path;
pub use download::download;
pub use packagemeta::{PackageMeta, PackageMetaList};
pub use package::Package;
pub use database::Database;
mod download;
mod packagemeta;
mod package;
mod database;
pub struct Repo {
local: String,
......
......@@ -6,14 +6,16 @@ pub struct PackageMeta {
pub name: String,
pub version: String,
pub target: String,
pub depends: Vec<String>,
}
impl PackageMeta {
pub fn new(name: &str, version: &str, target: &str) -> Self {
pub fn new(name: &str, version: &str, target: &str, depends: Vec<String>) -> Self {
PackageMeta {
name: name.to_string(),
version: version.to_string(),
target: target.to_string(),
depends: depends,
}
}
......
extern crate pkgutils;
pub fn db_location() -> String {
format!("{}/tests/test_db/", env!("CARGO_MANIFEST_DIR"))
}
pub fn get_db() -> pkgutils::Database {
let path = db_location();
pkgutils::Database::open(format!("{}/pkg", path), format!("{}/etc/pkg.d/pkglist", path))
}
extern crate pkgutils;
extern crate ordermap;
use ordermap::OrderMap;
mod common;
#[test]
fn test_is_installed() {
let db = common::get_db();
// Check if pkg4 is installed, and check if pkg3 is not installed
assert!(db.is_pkg_installed("pkg4"));
assert!(!db.is_pkg_installed("pkg3"));
}
#[test]
fn test_get_pkg_depends() {
let db = common::get_db();
let pkgs = db.get_pkg_depends("pkg2").unwrap();
assert_eq!(pkgs, vec!["pkg3", "pkg4"]);
}
#[test]
fn test_calc_depends() {
let db = common::get_db();
let mut pkgs = OrderMap::new();
db.calculate_depends("pkg1", &mut pkgs).unwrap();
let pkgs_vec: Vec<String> = pkgs.keys().map(|x| x.to_string()).collect();
assert_eq!(pkgs_vec, vec!["pkg3", "pkg2", "pkg1"]);
}
name = "pkg1"
version = "0.0.1"
target = "x86_64"
depends = ["pkg2"]
name = "pkg2"
version = "0.0.1"
target = "x86_64"
depends = ["pkg3", "pkg4"]
name = "pkg3"
version = "0.0.1"
target = "x86_64"
depends = ["pkg4"]
name = "pkg4"
version = "0.0.1"
target = "x86_64"
depends = []
name = "pkg4"
version = "0.0.1"
target = "x86_64"
depends = []
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment