Commit fc516c61 authored by Agoston Szepessy's avatar Agoston Szepessy

Add dependency handler for package installation

Dependencies are handled by looking for a package manifest file in the
local package database. The manifest contains all the dependencies for a
package, and a dependency graph is built from all of them. Then a
topological sort is performed and an OrderMap (linked hash map) of
package names in the order which they are to be installed in is
returned. An OrderMap is used because it preserves the order of the
packages and it has fast lookup times for checking duplicate packages.
parent c3403548
......@@ -2,13 +2,16 @@
name = "pkgutils"
version = "0.1.1"
dependencies = [
"bidir-map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.26.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper-rustls 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libflate 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"liner 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"octavo 0.1.1 (git+https://github.com/libOctavo/octavo.git)",
"ordermap 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"pbr 1.0.0 (git+https://github.com/ids1024/pb?branch=duration)",
"petgraph 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"tar 0.4.13 (git+https://github.com/redox-os/tar-rs)",
......@@ -38,6 +41,11 @@ dependencies = [
"safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bidir-map"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "0.7.0"
......@@ -97,6 +105,11 @@ name = "either"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fixedbitset"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futures"
version = "0.1.14"
......@@ -262,6 +275,11 @@ dependencies = [
"typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ordermap"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pbr"
version = "1.0.0"
......@@ -279,6 +297,15 @@ name = "percent-encoding"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "petgraph"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fixedbitset 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"ordermap 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "0.3.15"
......@@ -592,6 +619,7 @@ dependencies = [
"checksum adler32 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce93f29e3642662cac79d45e9c27ead906b91ac9921c1cf6f4801d01b4e19a8b"
"checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557"
"checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9"
"checksum bidir-map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6c3d05037e57974413eef55a8505df19de3cb4dc7a8f8389e1588ec492ae9c73"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
"checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855"
......@@ -601,6 +629,7 @@ dependencies = [
"checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299"
"checksum custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
"checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a"
"checksum fixedbitset 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b0cb3d75726fa0c5ed3dce5dfcf0796affa2a60b33967f45012d86fb95a886f2"
"checksum futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4b63a4792d4f8f686defe3b39b92127fea6344de5d38202b2ee5a11bbbf29d6a"
"checksum gcc 0.3.52 (registry+https://github.com/rust-lang/crates.io-index)" = "1b7d19683108136d21d32723077e69cd5df2bfd6d102c74a01d743cf2b65cf97"
"checksum generic-array 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3406a3975bc944fdd85b7964d53296a0ff11f4b6c4704fa4972c9a7c8ba27367"
......@@ -622,8 +651,10 @@ dependencies = [
"checksum num_cpus 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aec53c34f2d0247c5ca5d32cca1478762f301740468ee9ee6dcb7a0dd7a0c584"
"checksum octavo 0.1.1 (git+https://github.com/libOctavo/octavo.git)" = "<none>"
"checksum octavo-digest 0.1.2 (git+https://github.com/libOctavo/octavo.git)" = "<none>"
"checksum ordermap 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e2df2a1933286f9d5f370ce42c3056a426845c5491b42ebcab900715bf2c5e"
"checksum pbr 1.0.0 (git+https://github.com/ids1024/pb?branch=duration)" = "<none>"
"checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356"
"checksum petgraph 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "14c6ae5ccb73b438781abc93d35615019b1ad6e24b44116377fb819cfd7587de"
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
"checksum rand 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "eb250fd207a4729c976794d03db689c9be1d634ab5a1c9da9492a13d8fecbcdf"
"checksum rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a77c51c07654ddd93f6cb543c7a849863b03abc7e82591afda6dc8ad4ac3ac4a"
......
......@@ -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