M Cargo.lock => Cargo.lock +7 -0
@@ 74,6 74,7 @@ dependencies = [
"regex",
"serde",
"serde_yaml",
+ "topological-sort",
"walkdir",
]
@@ 355,6 356,12 @@ dependencies = [
]
[[package]]
+name = "topological-sort"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d"
+
+[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
M Cargo.toml => Cargo.toml +1 -0
@@ 14,3 14,4 @@ serde_yaml = "0.9"
serde = { version = "1.0", features = [ "derive" ] }
walkdir = "2.3"
regex = { version = "1.9.3", features = [ "std" ] }
+topological-sort = "0.2.2"
A src/cmds/build.rs => src/cmds/build.rs +99 -0
@@ 0,0 1,99 @@
+use std::{path::PathBuf, process::Command, collections::HashSet, fs};
+
+use crate::types::{Database, PackageName};
+
+/// returns true if the given architecture is whitelisted
+/// in the working architecture list
+fn arch_in(def: &HashSet<String>, arch: &str) -> bool {
+ if def.contains(&format!("!{}", arch)) {
+ return false;
+ }
+
+ if def.contains(arch) {
+ return true;
+ }
+
+ if def.contains("all") || def.contains("noarch") {
+ return true;
+ }
+
+ return false;
+}
+
+/// builds a single package without checking the dependencies
+fn build_package(name: PackageName, db: &Database, arch: &Option<String>, verbose:bool) {
+ let pkg = db.packages.get(&name).expect("Package not found");
+
+ let mut child = Command::new("sh");
+
+ if let Some(arch) = arch {
+ // check if arch is supported
+ if ! arch_in(&pkg.arch, &arch) {
+ panic!("{} not available on {}", pkg.name, arch);
+ }
+ // build for target arch
+ child
+ .env("CBUILD_ARCH", arch)
+ .env("APORTSDIR",
+ &format!(
+ "{}/../../",
+ fs::canonicalize(
+ pkg.path.clone()
+ ).expect("Unable to get absolute path")
+ .to_str()
+ .expect("Unable to read path")))
+ .arg("-c")
+ .arg(
+ &format!(
+ "cd {} && echo $CBUILD_ARCH && echo $APORTSDIR && abuild rootbld",
+ pkg.path.to_str().expect("Unable to read path"),
+ ));
+ } else {
+ // build on host arch
+ child
+ .arg("-c")
+ .arg(
+ &format!("cd {} && abuild -r",
+ pkg.path.to_str().expect("Unable to find folder")));
+ }
+
+ if verbose {
+ let mut child = child.spawn()
+ .expect(&format!("Unable to build {}", pkg.name));
+ let ecode = child.wait().expect("failed to wait for builder").success();
+ if !ecode {
+ panic!("Failed to build {}", pkg.name);
+ }
+ } else {
+ let child = child.output()
+ .expect(&format!("Unable to build {}", pkg.name));
+ let ecode = child.status.success();
+ if !ecode {
+ panic!("Failed to build {}", pkg.name);
+ }
+
+ }
+}
+
+/// builds a package including the dependencies
+fn build_group(name: PackageName, db: &Database, arch: &Option<String>, verbose:bool) {
+ let tasks = super::tree::topological_sort(&name, &db)
+ .expect("Unable to resolve dependencies, as there is a cycle in the graph");
+
+ for task in tasks {
+ build_package(task, db, arch, verbose);
+ }
+}
+
+/// attempts to build a package and its dependencies
+pub fn build(name: PackageName, db: Option<PathBuf>, arch: Option<String>, build_deps: bool, verbose:bool) {
+ let db = Database::from_file(db.unwrap_or(PathBuf::from("db.yml")))
+ .expect("Unable to open database");
+
+ if build_deps {
+ // dependency resolution
+ build_group(name, &db, &arch, verbose);
+ } else {
+ build_package(name, &db, &arch, verbose);
+ }
+}
M src/cmds/info.rs => src/cmds/info.rs +3 -4
@@ 35,10 35,9 @@ pub fn info(name: PackageName, db: Option<PathBuf>, show_provides: bool, show_de
if show_provides {
println!("---Provides---");
- if let Some(prv) = db.provides.get(&name) {
- for pr in prv {
- println!("{}", pr);
- }
+ let pkg = db.packages.get(&name).unwrap();
+ for pr in &pkg.provides {
+ println!("{}", pr);
}
}
M src/cmds/mod.rs => src/cmds/mod.rs +4 -0
@@ 1,6 1,10 @@
mod scan;
mod list;
mod info;
+mod build;
+mod tree;
pub use scan::scan;
pub use list::list;
pub use info::info;
+pub use build::build;
+pub use tree::tree;
A src/cmds/tree.rs => src/cmds/tree.rs +157 -0
@@ 0,0 1,157 @@
+use std::{path::PathBuf, collections::{LinkedList, HashSet}};
+
+use topological_sort::TopologicalSort;
+
+use crate::types::{PackageName, Database};
+
+/// returns all packages vaguely associated with the named packed
+/// this might be, because the named package is a subpackage,
+/// or the named package is provided by multiple packages
+fn resolve_package(name: &str, db: &Database) -> HashSet<PackageName> {
+ let mut can_use = HashSet::new();
+
+ if let Some(providers) = db.provides.get(name) {
+ for provider in providers {
+ if let Some(pkg) = db.packages.get(provider) {
+ can_use.insert(pkg.name.to_string());
+ }
+ }
+ }
+ if let Some(pkg) = db.packages.get(name) {
+ can_use.insert(pkg.name.to_string());
+ }
+
+ can_use
+}
+
+/// creates a list of all dependencies used by the package itself,
+/// or by packages required by the start package
+fn list_all_deps(start: PackageName, db: &Database, local_only: bool) -> HashSet<PackageName> {
+ let mut stack = LinkedList::new();
+ let mut marker = HashSet::new();
+ stack.push_front(start);
+
+ while ! stack.is_empty() {
+ let current = stack.pop_back().unwrap();
+
+ if marker.contains(¤t) {
+ // already processed vertex
+ continue;
+ }
+
+ let res = resolve_package(¤t, db);
+ if res.is_empty() {
+ // not a local dependency,
+ // so we can't go any deeper
+ if !local_only {
+ marker.insert(current);
+ }
+ } else {
+ for loc in res {
+ if let Some(deps) = db.depends.get(&loc) {
+ for dep in deps {
+ stack.push_front(dep.to_string());
+ }
+ }
+
+ // if the current package is provided by the given package,
+ // we add the package name of the provider-package
+ // to our list
+ if let Some(pkg) = db.packages.get(&loc) {
+ if pkg.provides.contains(¤t) || pkg.name == current {
+ marker.insert(pkg.name.to_string());
+ }
+ }
+ }
+ }
+
+ }
+
+ return marker;
+}
+
+/// topologically sorts all local dependencies
+/// filters unknown dependencies
+pub fn topological_sort(start: &PackageName, db: &Database) -> Option<Vec<PackageName>> {
+ let mut ts = {
+ let mut stack = LinkedList::new();
+ let mut marker = HashSet::new();
+ stack.push_front(start.to_string());
+
+
+ let mut ts = TopologicalSort::<String>::new();
+ ts.insert(start);
+
+ // setup the topological sort dependency graph
+ while ! stack.is_empty() {
+ let current = stack.pop_back().unwrap();
+
+ if marker.contains(¤t) {
+ // already processed vertex
+ continue;
+ }
+
+ let res = resolve_package(¤t, db);
+ if !res.is_empty() {
+ for loc in res {
+ if let Some(deps) = db.depends.get(&loc) {
+ for dep in deps {
+ let resolved = resolve_package(dep, db);
+ for res in resolved {
+ stack.push_front(res.to_string());
+ ts.add_dependency(
+ res.to_string(),
+ current.to_string()
+ );
+ }
+ }
+ }
+
+ // if the current package is provided by the given package,
+ // we add the package name of the provider-package
+ // to our list
+ if let Some(pkg) = db.packages.get(&loc) {
+ if pkg.provides.contains(¤t) || pkg.name == current {
+ marker.insert(pkg.name.to_string());
+ }
+ }
+ }
+ }
+ }
+
+ ts
+ };
+
+ let mut v = Vec::new();
+
+ while !ts.is_empty() {
+ let mut next = ts.pop_all();
+
+ if next.is_empty() {
+ // cycle detected
+ return None;
+ }
+
+ v.append(&mut next);
+ }
+
+ Some(v)
+}
+
+pub fn tree(name: PackageName, db: Option<PathBuf>, local_only: bool) {
+ let db = Database::from_file(db.unwrap_or(PathBuf::from("db.yml")))
+ .expect("Unable to open database");
+
+ if local_only {
+ let sorted = topological_sort(&name, &db)
+ .expect("Unable to resolve dependencies, as there is a cycle in the graph");
+ for dep in sorted {
+ println!("{}", dep);
+ }
+ } else {
+ let deps = list_all_deps(name, &db, false);
+ for dep in deps {
+ println!("{}", dep);
+ }
+ }
+}
M src/main.rs => src/main.rs +35 -2
@@ 10,6 10,7 @@ enum Commands {
/// Lists all known packages
List {
/// package database path
+ #[arg(short='d',long)]
db: Option<PathBuf>
},
/// Rescans all folders
@@ 22,17 23,33 @@ enum Commands {
Build {
/// the packagename
package: String,
+ /// build the package for a target architecture
+ /// requires abuild-rootbld to be installed,
+ /// and the .rootbld-repositories file to be configured correctly
+ /// (even if set to host architecture)
+ #[arg(short='a',long)]
+ arch: Option<String>,
+ /// disables dependency resolution
+ /// wont compile the dependencies locally
+ /// however this will still pass -r to abuild
+ /// and fetch the prebuild packages from upstream
+ #[arg(short='r', default_value="false")]
+ no_build_dependencies: bool,
/// package database path
- db: Option<PathBuf>
+ #[arg(short='d',long)]
+ db: Option<PathBuf>,
+ #[arg(short='v', default_value="false")]
+ verbose: bool
},
/// Displays package information
Info {
/// the packagename
package: String,
/// package database path
+ #[arg(short='d',long)]
db: Option<PathBuf>,
/// prints the dependencies after the package info
- #[arg(short='d',long)]
+ #[arg(short='r',long)]
show_depends: bool,
/// prints the packages provided by the package after the package info
#[arg(short='p', long)]
@@ 44,7 61,21 @@ enum Commands {
/// search query
query: String,
/// package database path
+ #[arg(short='d',long)]
db: Option<PathBuf>
+ },
+ /// show a list of all dependencies
+ Tree {
+ /// the packagename
+ package: String,
+ /// package database path
+ #[arg(short='d',long)]
+ db: Option<PathBuf>,
+ /// shows only local dependencies,
+ /// that can be found in the local database
+ /// will also topologically sort the dependencies
+ #[arg(short='l', default_value="false")]
+ only_local: bool,
}
}
@@ 55,6 86,8 @@ fn main() {
Commands::Scan { folder, db } => cmds::scan(folder, db),
Commands::List { db } => cmds::list(db),
Commands::Info { package, db, show_depends, show_provides } => cmds::info(package, db, show_provides, show_depends),
+ Commands::Build { package, db, arch, no_build_dependencies, verbose } => cmds::build(package, db, arch, !no_build_dependencies, verbose),
+ Commands::Tree { package, db, only_local } => cmds::tree(package, db, only_local),
_ => panic!("Unimplemented")
}
}
M src/types.rs => src/types.rs +15 -3
@@ 25,7 25,9 @@ pub struct Package {
/// list? of supported/disabled architectures
pub arch: HashSet<String>,
/// relative path to APKBUILD
- pub path: PathBuf
+ pub path: PathBuf,
+ /// list of packages provides by this package
+ pub provides: HashSet<PackageName>
}
/// Package information collection
@@ 83,12 85,22 @@ impl Database {
description: apk.description,
url: apk.url,
rel: apk.rel,
- path: folder
+ path: folder,
+ provides: provides.clone()
};
db.packages.insert(apk.name.to_string(), pkg);
db.depends.insert(apk.name.to_string(), depends);
- db.provides.insert(apk.name.to_string(), provides);
+
+ for provider in provides {
+ if let Some(table) = db.provides.get_mut(&provider) {
+ table.insert(apk.name.to_string());
+ } else {
+ let mut empty = HashSet::new();
+ empty.insert(apk.name.to_string());
+ db.provides.insert(provider, empty);
+ }
+ }
}
db