Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for conflicts #94

Closed
wants to merge 10 commits into from
32 changes: 32 additions & 0 deletions crates/dag/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
// SPDX-License-Identifier: MPL-2.0

use petgraph::{
algo::tarjan_scc,
prelude::DiGraph,
visit::{Dfs, Topo, Walker},
Direction,
};

use self::subgraph::subgraph;
Expand Down Expand Up @@ -34,6 +36,10 @@ where
Self::default()
}

pub fn get_node_from_index(&self, index: NodeIndex) -> &N {
&self.0[index]
}

/// Adds node N to the graph and returns the index.
/// If N already exists, it'll return the index of that node.
pub fn add_node_or_get_index(&mut self, node: N) -> NodeIndex {
Expand Down Expand Up @@ -82,6 +88,18 @@ where
self.0.node_indices().map(|i| &self.0[i])
}

pub fn neighbors_incoming(&self, node: &N) -> impl Iterator<Item = &'_ N> {
self.0
.neighbors_directed(self.get_index(node).unwrap(), Direction::Incoming)
.map(|neighbor| &self.0[neighbor])
}

pub fn neighbors_outgoing(&self, node: &N) -> impl Iterator<Item = &'_ N> {
self.0
.neighbors_directed(self.get_index(node).unwrap(), Direction::Outgoing)
.map(|neighbor| &self.0[neighbor])
}

/// Perform a depth-first search, given the start index
pub fn dfs(&self, start: NodeIndex) -> impl Iterator<Item = &'_ N> {
let dfs = Dfs::new(&self.0, start);
Expand All @@ -103,6 +121,20 @@ where
Self(transposed)
}

pub fn scc(&self) -> Vec<Vec<NodeIndex>> {
// Note: tarjan and kosaraju have the same time complexity, but tarjan
// has a better constant factor. They should produce the equivalent
// result regardless.
tarjan_scc(&self.0)
}

pub fn scc_nodes(&self) -> Vec<Vec<&N>> {
tarjan_scc(&self.0)
.iter()
.map(|component| component.iter().map(|node_id| &self.0[*node_id]).collect())
.collect()
}

/// Split the graph at the given start node(s) - returning a new graph
pub fn subgraph(&self, starting_nodes: &[N]) -> Self {
Self(subgraph(&self.0, starting_nodes))
Expand Down
6 changes: 6 additions & 0 deletions crates/moss/src/db/meta/migrations/20230912204438_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,9 @@ CREATE TABLE IF NOT EXISTS meta_providers (
provider TEXT NOT NULL,
FOREIGN KEY (package) REFERENCES meta(package) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS meta_conflicts (
package TEXT NOT NULL,
conflict TEXT NOT NULL,
FOREIGN KEY (package) REFERENCES meta(package) ON DELETE CASCADE
);
59 changes: 55 additions & 4 deletions crates/moss/src/db/meta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum Table {
Licenses,
Dependencies,
Providers,
Conflicts,
}

#[derive(Debug)]
Expand Down Expand Up @@ -169,14 +170,22 @@ impl Database {
",
);

let mut conflicts_query = sqlx::QueryBuilder::new(
"
SELECT package, conflict
FROM meta_conflicts
",
);

if let Some(filter) = filter {
filter.append(Table::Meta, &mut entry_query);
filter.append(Table::Licenses, &mut licenses_query);
filter.append(Table::Dependencies, &mut dependencies_query);
filter.append(Table::Providers, &mut providers_query);
filter.append(Table::Conflicts, &mut conflicts_query)
}

let (entries, licenses, dependencies, providers) = futures::try_join!(
let (entries, licenses, dependencies, providers, conflicts) = futures::try_join!(
entry_query
.build_query_as::<encoding::Entry>()
.fetch_all(&self.pool),
Expand All @@ -189,6 +198,9 @@ impl Database {
providers_query
.build_query_as::<encoding::Provider>()
.fetch_all(&self.pool),
conflicts_query
.build_query_as::<encoding::Conflict>()
.fetch_all(&self.pool)
)?;

Ok(entries
Expand Down Expand Up @@ -221,6 +233,11 @@ impl Database {
.filter(|l| l.id.0 == entry.id.0)
.map(|p| p.provider.0.clone())
.collect(),
conflicts: conflicts
.iter()
.filter(|l| l.id.0 == entry.id.0)
.map(|p| p.conflict.0.clone())
.collect(),
uri: entry.uri,
hash: entry.hash,
download_size: entry.download_size.map(|i| i as u64),
Expand Down Expand Up @@ -279,11 +296,21 @@ impl Database {
)
.bind(package.encode());

let (entry, licenses, dependencies, providers) = futures::try_join!(
let conflicts_query = sqlx::query_as::<_, encoding::Conflict>(
"
SELECT package, conflict
FROM meta_conflicts
WHERE package = ?;
",
)
.bind(package.encode());

let (entry, licenses, dependencies, providers, conflicts) = futures::try_join!(
entry_query.fetch_one(&self.pool),
licenses_query.fetch_all(&self.pool),
dependencies_query.fetch_all(&self.pool),
providers_query.fetch_all(&self.pool),
conflicts_query.fetch_all(&self.pool),
)?;

Ok(Meta {
Expand All @@ -299,6 +326,7 @@ impl Database {
licenses: licenses.into_iter().map(|l| l.license).collect(),
dependencies: dependencies.into_iter().map(|d| d.dependency.0).collect(),
providers: providers.into_iter().map(|p| p.provider.0).collect(),
conflicts: conflicts.into_iter().map(|p| p.conflict.0).collect(),
uri: entry.uri,
hash: entry.hash,
download_size: entry.download_size.map(|i| i as u64),
Expand Down Expand Up @@ -449,6 +477,26 @@ impl Database {
.await?;
}

// Conflicts
let conflicts = packages
.iter()
.flat_map(|(id, meta)| meta.conflicts.iter().map(move |conflict| (id, conflict)))
.collect::<Vec<_>>();
println!("conflicts: {conflicts:?}");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder to remove this println

if !conflicts.is_empty() {
sqlx::QueryBuilder::new(
"
INSERT INTO meta_conflicts (package, conflict)
",
)
.push_values(conflicts, |mut b, (id, conflict)| {
b.push_bind(id.encode()).push_bind(conflict.encode());
})
.build()
.execute(transaction.acquire().await?)
.await?;
}

transaction.commit().await?;

Ok(())
Expand Down Expand Up @@ -553,8 +601,10 @@ mod encoding {
}

#[derive(FromRow)]
pub struct ProviderPackage {
pub package: Decoder<package::Id>,
pub struct Conflict {
#[sqlx(rename = "package")]
pub id: Decoder<package::Id>,
pub conflict: Decoder<crate::Conflict>,
}
}

Expand Down Expand Up @@ -593,6 +643,7 @@ mod test {
database.add(id.clone(), meta.clone()).await.unwrap();

assert_eq!(&meta.name, &"bash-completion".to_string().into());
assert_eq!(meta.conflicts, HashSet::default());

// Now retrieve by provider.
let lookup = Filter::Provider(Provider {
Expand Down
16 changes: 15 additions & 1 deletion crates/moss/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ mod encoding {
use sqlx::{Sqlite, Type};
use thiserror::Error;

use crate::{dependency, package, state, Dependency, Provider};
use crate::{dependency, package, state, Conflict, Dependency, Provider};

/// Decode from a database type using [`Encoding::decode`]
#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -115,6 +115,20 @@ mod encoding {
}
}

/// Encoding of Conflict type
impl<'a> Encoding<'a> for Conflict {
type Encoded = String;
type Error = dependency::ParseError;

fn decode(encoded: String) -> Result<Self, Self::Error> {
encoded.parse()
}

fn encode(&self) -> String {
self.to_string()
}
}

impl<'a> Encoding<'a> for state::Id {
type Encoded = i64;
type Error = Infallible;
Expand Down
28 changes: 28 additions & 0 deletions crates/moss/src/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,34 @@ impl FromStr for Dependency {
}
}

/// A Conflict in moss is a provider that cannot be co-installed with the
/// current package
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Conflict {
/// Tag for the table-type of dependency
pub kind: Kind,

/// Bare target
pub name: String,
}

/// Pretty-printing of dependencies (e.g.: `binary(whoami)`)
impl fmt::Display for Conflict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({})", self.kind, self.name)
}
}

impl FromStr for Conflict {
type Err = ParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let (kind, name) = parse(s)?;

Ok(Self { kind, name })
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Provider {
pub kind: Kind,
Expand Down
2 changes: 1 addition & 1 deletion crates/moss/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#![allow(unused_variables, dead_code)]

pub use self::client::Client;
pub use self::dependency::{Dependency, Provider};
pub use self::dependency::{Conflict, Dependency, Provider};
pub use self::installation::Installation;
pub use self::package::Package;
pub use self::registry::Registry;
Expand Down
33 changes: 25 additions & 8 deletions crates/moss/src/package/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{collections::HashSet, fmt};
use stone::payload;
use thiserror::Error;

use crate::{dependency, Dependency, Provider};
use crate::{dependency, Conflict, Dependency, Provider};

/// A package identifier constructed from metadata fields
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -74,6 +74,8 @@ pub struct Meta {
pub dependencies: HashSet<Dependency>,
/// All providers, including name()
pub providers: HashSet<Provider>,
/// All conflicts
pub conflicts: HashSet<Conflict>,
/// If relevant: uri to fetch from
pub uri: Option<String>,
/// If relevant: hash for the download
Expand Down Expand Up @@ -104,6 +106,7 @@ impl Meta {
.filter_map(|meta| meta_string(meta, payload::meta::Tag::License))
.collect();
let dependencies = payload.iter().filter_map(meta_dependency).collect();
let conflicts = payload.iter().filter_map(meta_conflict).collect();
let providers = payload
.iter()
.filter_map(meta_provider)
Expand All @@ -127,6 +130,7 @@ impl Meta {
licenses,
dependencies,
providers,
conflicts,
uri,
hash,
download_size,
Expand Down Expand Up @@ -244,13 +248,26 @@ fn meta_dependency(meta: &payload::Meta) -> Option<Dependency> {
}

fn meta_provider(meta: &payload::Meta) -> Option<Provider> {
if let payload::meta::Kind::Provider(kind, name) = meta.kind.clone() {
Some(Provider {
kind: dependency::Kind::from(kind),
name,
})
} else {
None
match (meta.tag, &meta.kind) {
(payload::meta::Tag::Provides, payload::meta::Kind::Provider(kind, name)) => {
Some(Provider {
kind: dependency::Kind::from(*kind),
name: name.clone(),
})
}
_ => None,
}
}

fn meta_conflict(meta: &payload::Meta) -> Option<Conflict> {
match (meta.tag, &meta.kind) {
(payload::meta::Tag::Conflicts, payload::meta::Kind::Provider(kind, name)) => {
Some(Conflict {
kind: dependency::Kind::from(*kind),
name: name.clone(),
})
}
_ => None,
}
}

Expand Down
Loading
Loading