Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
eerii committed Dec 9, 2024
1 parent a386864 commit d52c97b
Show file tree
Hide file tree
Showing 9 changed files with 814 additions and 105 deletions.
2 changes: 1 addition & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub fn main() {
mod binary {
use std::{collections::HashMap, fs, path::Path};

use system_deps_meta::{read_metadata, Binary, BinaryPaths, BUILD_TARGET_DIR};
use system_deps_meta::BUILD_TARGET_DIR;

pub fn build() {
// Add pkg-config paths to the overrides
Expand Down
2 changes: 1 addition & 1 deletion meta/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ apple-flat-package = { version = "0.20", optional = true }
toml = "0.8"

[features]
default = [ ]
default = [ "binary" ]
binary = [ "dep:sha256", "dep:reqwest" ]
gz = [ "dep:flate2", "dep:tar" ]
xz = [ "dep:xz", "dep:tar" ]
Expand Down
146 changes: 78 additions & 68 deletions meta/src/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ use std::{
};

use serde::Deserialize;
use serde_json::Value;

use crate::error::BinaryError;
use crate::{
error::{BinaryError, Error},
parse::MetadataList,
utils::merge_default,
};

/// The extension of the binary archive.
/// Support for different extensions is enabled using features.
Expand All @@ -25,11 +30,16 @@ enum Extension {
// Pkg,
}

// TODO: Change follow and global for includes
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum Binary {
Url(UrlBinary),
Follow(FollowBinary),
}

/// Represents one location from where to download library binaries.
#[derive(Debug, Deserialize)]
pub struct Binary {
pub struct UrlBinary {
/// The url from which to download the archived binaries. It suppports:
///
/// - Web urls, in the form `http[s]://website/archive.ext`.
Expand All @@ -47,67 +57,44 @@ pub struct Binary {
/// A list of relative paths inside the binary archive that point to a folder containing
/// package config files. These directories will be prepended to the `PKG_CONFIG_PATH` when
/// compiling the affected libraries.
pkg_paths: Vec<String>,
paths: Option<Vec<String>>,
///
provides: Option<Vec<String>>,

Check warning on line 62 in meta/src/binary.rs

View workflow job for this annotation

GitHub Actions / Test Suite (macOS-latest)

field `provides` is never read

Check warning on line 62 in meta/src/binary.rs

View workflow job for this annotation

GitHub Actions / Clippy

field `provides` is never read

Check warning on line 62 in meta/src/binary.rs

View workflow job for this annotation

GitHub Actions / Test Suite (ubuntu-latest)

field `provides` is never read

Check warning on line 62 in meta/src/binary.rs

View workflow job for this annotation

GitHub Actions / Check

field `provides` is never read

Check warning on line 62 in meta/src/binary.rs

View workflow job for this annotation

GitHub Actions / Test Suite (Windows)

field `provides` is never read
}

/// Controls if the paths from this binary apply to all packages or just to this one.
global: Option<bool>,
/// The `system-deps` formatted name of another library which has binaries specified.
/// This library will alias the configuration of the followed one. If `url` is specified
/// alongside this field, it will no longer follow the original configuration.
_follows: Option<String>,
#[derive(Debug, Deserialize)]
pub struct FollowBinary {
follows: String,
}

impl Binary {
pub fn paths(&self, name: &str) -> Result<HashSet<PathBuf>, BinaryError> {
// TODO: Set this binary to follow
//if let Some(follows) = self.follows {
// follow_list.insert(name.clone(), follows);
//}
pub trait BinaryMetadataListExt {
fn paths(&self, package: &str) -> Result<HashSet<PathBuf>, Error>;
}

impl BinaryMetadataListExt for MetadataList {
fn paths(&self, package: &str) -> Result<HashSet<PathBuf>, Error> {
// The binaries are stored in the target dir set by `system_deps_meta`.

// If they are specific to a dependency, they live in a subfolder.
let mut dst = PathBuf::from(&crate::BUILD_TARGET_DIR);
if !name.is_empty() {
dst.push(name);
};
let binary_list: HashMap<String, Binary> = self.get(package, merge_binary)?;

// Only download the binaries if there isn't already a valid copy
if !check_valid_dir(&dst, self.checksum.as_deref())? {
download(&self.url, &dst)?;
match binary_list.get(package).unwrap() {
Binary::Url(bin) => {
let dst = Path::new(&crate::BUILD_TARGET_DIR).join(package);
// Only download the binaries if there isn't already a valid copy
if !check_valid_dir(&dst, bin.checksum.as_deref())? {
download(&bin.url, &dst)?;
}
Ok(bin.paths.iter().flatten().map(|p| dst.join(p)).collect())
}
Binary::Follow(dep) => self.paths(&dep.follows),
}

Ok(self.pkg_paths.iter().map(|p| dst.join(p)).collect())
}

pub fn is_global(&self) -> bool {
self.global.unwrap_or_default()
}
}

#[derive(Debug)]
pub struct BinaryPaths(HashMap<String, Vec<PathBuf>>);

impl<I: IntoIterator<Item = (String, Binary)>> From<I> for BinaryPaths {
/// Uses the metadata from the cargo manifests and the environment to build a list of urls
/// from where to download binaries for dependencies and adds them to their `PKG_CONFIG_PATH`.
fn from(binaries: I) -> Self {
let mut paths: HashMap<String, Vec<PathBuf>> = HashMap::new();

for (name, bin) in binaries {
let p = bin.paths(&name).unwrap();
if bin.is_global() {
paths
.entry("".into())
.or_default()
.extend(p.iter().cloned())
}
paths.entry(name).or_default().extend(p.into_iter());
}

Self(paths)
}
}

impl BinaryPaths {
pub fn build(self) -> String {
let options = self
Expand All @@ -119,7 +106,6 @@ impl BinaryPaths {

format!(
r#"
/// TODO:
pub fn get_path(name: &str) -> &[&'static str] {{
match name {{
{}
Expand Down Expand Up @@ -156,23 +142,6 @@ pub fn get_path(name: &str) -> &[&'static str] {{
// }),
// );
//}

// Go through the list of follows and if they don't already have binaries,
// link them to the followed one.
//for (from, to) in follow_list {
// if !paths.contains_key(&from) {
// let followed = paths
// .get(to.as_str())
// .unwrap_or_else(|| {
// panic!(
// "The library `{}` tried to follow `{}` but it doesn't exist",
// from, to,
// )
// })
// .clone();
// paths.insert(from, followed);
// };
//}
}

/// Checks if the target directory is valid and if binaries need to be redownloaded.
Expand Down Expand Up @@ -314,3 +283,44 @@ fn decompress(file: &[u8], dst: &Path, ext: Extension) -> Result<(), BinaryError
}
Ok(())
}

pub fn merge_binary(rhs: Value, lhs: &Value, overwrite: bool) -> Result<Value, Error> {
let mut res = merge_default(rhs, lhs, overwrite)?;

Check warning on line 288 in meta/src/binary.rs

View workflow job for this annotation

GitHub Actions / Test Suite (macOS-latest)

variable does not need to be mutable

Check warning on line 288 in meta/src/binary.rs

View workflow job for this annotation

GitHub Actions / Clippy

variable does not need to be mutable

Check warning on line 288 in meta/src/binary.rs

View workflow job for this annotation

GitHub Actions / Test Suite (ubuntu-latest)

variable does not need to be mutable

Check warning on line 288 in meta/src/binary.rs

View workflow job for this annotation

GitHub Actions / Check

variable does not need to be mutable

Check warning on line 288 in meta/src/binary.rs

View workflow job for this annotation

GitHub Actions / Test Suite (Windows)

variable does not need to be mutable

//if overwrite {
// resolve_follow(&mut res);
//}

Ok(res)
}

//fn resolve_follow(res: &mut Value) -> Option<()> {
// let mut follows = Vec::new();
//
// for value in res.as_object_mut()?.values_mut() {
// let Some(provides) = value.get_mut("provides") else {
// continue;
// };
// let provides = provides.take();
// let Some(arr) = provides.as_array().cloned() else {
// continue;
// };
// follows.push((arr, value.clone()));
// }
//
// let res = res.as_object_mut()?;
// for (provides, value) in follows {
// for to in provides {
// let to = to.as_str()?;
// let entry = res.entry(to).or_insert(Value::Object(Map::new()));
// let Some(map) = entry.as_object_mut() else {
// continue;
// };
// let _ = map.entry("follows").or_insert(to.into());
// }
// }
//
// println!("res {:?}", res);
//
// Some(())
//}
26 changes: 3 additions & 23 deletions meta/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,18 +132,10 @@ impl MetadataList {
/// and whether it should allow the second value to overwrite the first. When traveling up the tree
/// this is true since we want dependent crates to have priority, but when comparing horizontally
/// it is false to avoid conflicts.
///
/// `reduce` will take the output of merge and apply rules to modify the returned structure.
/// For example, checking cfg conditions and library versions.
pub fn get<
T: DeserializeOwned,
M: Fn(Value, &Value, bool) -> Result<Value, Error>,
R: Fn(Value, &M) -> Result<Value, Error>,
>(
pub fn get<T: DeserializeOwned>(
&self,
package: &str,
merge: M,
reduce: R,
merge: impl Fn(Value, &Value, bool) -> Result<Value, Error>,
) -> Result<T, Error> {
let base = self
.nodes
Expand All @@ -154,7 +146,6 @@ impl MetadataList {
let mut res = Value::Null;
let mut curr = Value::Null;

// Merge
while let Some(node) = nodes.pop_front() {
for p in node.parents.iter().rev() {
let next = self.nodes.get(p).ok_or(Error::PackageNotFound(p.into()))?;
Expand All @@ -167,17 +158,6 @@ impl MetadataList {
}
}

// Reduce
res = reduce(res, &merge)?;

// Get package
let Value::Object(mut map) = res else {
return Err(Error::IncompatibleMerge);
};
let Some(value) = map.remove(package) else {
return Err(Error::PackageNotFound(package.into()));
};

from_value::<T>(value).map_err(Error::SerializationError)
from_value::<T>(res).map_err(Error::SerializationError)
}
}
114 changes: 114 additions & 0 deletions meta/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::{
collections::HashSet,
fs, io,
path::{Path, PathBuf},
};

use toml::{Table, Value};

use crate::{error::Error, parse::MetadataList, utils::merge_default};

#[cfg(feature = "binary")]
mod binary;
mod conditional;
mod metadata;

macro_rules! entry {
($table:expr, $key:expr) => {
$table
.entry($key)
.or_insert_with(|| Value::Table(Table::default()))
.as_table_mut()
.unwrap()
};
}

#[derive(Debug, Default)]
struct Package {
name: &'static str,
deps: Vec<&'static str>,
config: Table,
}

impl Package {
fn write_toml(self, test_name: &str) -> io::Result<PathBuf> {
let mut table = self.config;

let package = entry!(table, "package");
package.insert("name".into(), self.name.into());

if !self.deps.is_empty() {
let dependencies = entry!(table, "dependencies");
for name in self.deps {
let dep = entry!(dependencies, name);
dep.insert("path".into(), format!("../{}", name).into());
}
}

let mut out = Path::new(env!("OUT_DIR")).join(format!("tests/{}/{}", test_name, self.name));
let _ = fs::remove_dir_all(&out);

out.push("src");
fs::create_dir_all(&out)?;
fs::write(out.join("lib.rs"), "")?;
out.pop();

out.push("Cargo.toml");
fs::write(&out, table.to_string())?;

Ok(out)
}
}

#[derive(Debug)]
struct Test {
metadata: MetadataList,
manifest: PathBuf,
}

impl Test {
fn new(name: impl AsRef<str>, packages: Vec<Package>) -> Self {
assert!(!packages.is_empty());

println!("\n# Dependencies\n");
let mut manifest = None;
for pkg in packages {
let out = pkg
.write_toml(name.as_ref())
.expect("Error writing Cargo.toml for test package");
println!("- {}", out.display());
manifest.get_or_insert(out);
}

let manifest = manifest.expect("There is no main test case");
let metadata = MetadataList::from(&manifest, "system-deps");

Self { metadata, manifest }
}

fn check(&self, key: &str) -> Result<Table, Error> {
println!("{} {:#?}", key, self.metadata);
let resolved = self
.metadata
.get::<Table>(key, merge_default)?
.remove(key)
.ok_or(Error::PackageNotFound(key.into()))?;

println!("\n# Final\n");
println!("{}", toml::to_string_pretty(&resolved).unwrap());

match resolved {
Value::Table(v) => Ok(v),
_ => Err(Error::IncompatibleMerge),
}
}
}

fn assert_set<T: std::fmt::Debug + Eq + std::hash::Hash>(
rhs: impl IntoIterator<Item = T>,
lhs: impl IntoIterator<Item = T>,
) {
let r = rhs.into_iter().collect::<HashSet<_>>();
let l = lhs.into_iter().collect::<HashSet<_>>();
assert_eq!(r, l);
}
Loading

0 comments on commit d52c97b

Please sign in to comment.