Skip to content

Commit eb98ecd

Browse files
committed
SAT solver for requirements
Closes #204
1 parent 4beff27 commit eb98ecd

20 files changed

+747
-90
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
out
44
src/tests/RCMD/
55
example_projects/*/rv
6+
example_projects/test
67
example_projects/*/rv.lock
78
!example_projects/archive/rv.lock
89
from*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[project]
2+
name = "projects"
3+
r_version = "4.4"
4+
5+
repositories = [
6+
{ alias = "rv-test-repo1", url = "https://a2-ai.github.io/rv-test-repo/repo1" },
7+
{ alias = "rv-test-repo2", url = "https://a2-ai.github.io/rv-test-repo/repo2" }
8+
]
9+
10+
dependencies = [
11+
"rv.git.pkgD",
12+
{ name = "rv.git.pkgA", repository = "rv-test-repo2" },
13+
]

example_projects/url-dep/rproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "simple"
33
r_version = "4.4"
44
repositories = [
5-
{alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/"}
5+
{alias = "posit", url = "https://packagemanager.posit.co/cran/2024-12-16/", force_source = true}
66
]
77
dependencies = [
88
{name = "dplyr", url = "https://cran.r-project.org/src/contrib/Archive/dplyr/dplyr_1.1.3.tar.gz"}

src/activate.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,7 @@ fn scripts_as_paths(is_home: bool) -> (PathBuf, PathBuf) {
9797

9898
fn write_activate_file(dir: impl AsRef<Path>, is_home: bool) -> Result<(), ActivateError> {
9999
let template = ACTIVATE_FILE_TEMPLATE.to_string();
100-
let global_wd_content = if is_home
101-
{
100+
let global_wd_content = if is_home {
102101
r#"
103102
owd <- getwd()
104103
setwd("~")

src/lockfile.rs

+11-11
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ use std::io::Write;
55
use std::path::{Path, PathBuf};
66
use std::str::FromStr;
77

8+
use crate::package::Dependency;
9+
use crate::{ConfigDependency, Repository, ResolvedDependency, Version};
810
use fs_err as fs;
911
use serde::Deserialize;
1012
use toml_edit::{Array, ArrayOfTables, InlineTable, Item, Table, Value};
1113

12-
use crate::{ConfigDependency, Repository, ResolvedDependency, Version};
13-
1414
const CURRENT_LOCKFILE_VERSION: i64 = 1;
1515
const INITIAL_COMMENT: &str = r#"# This file is automatically @generated by rv.
1616
# It is not intended for manual editing.
@@ -231,11 +231,11 @@ impl fmt::Display for Source {
231231
}
232232
}
233233

234-
fn format_array(deps: &[String]) -> Array {
234+
fn format_array(deps: &[Dependency]) -> Array {
235235
let mut deps = deps
236236
.iter()
237237
.map(|d| {
238-
let mut value = Value::from(d);
238+
let mut value = d.as_toml_value();
239239
value.decor_mut().set_prefix("\n ");
240240
value
241241
})
@@ -257,10 +257,10 @@ pub struct LockedPackage {
257257
pub source: Source,
258258
pub path: Option<String>,
259259
pub force_source: bool,
260-
pub dependencies: Vec<String>,
260+
pub dependencies: Vec<Dependency>,
261261
/// Only filled if the package had install_suggests=True in the config file
262262
#[serde(default)]
263-
pub suggests: Vec<String>,
263+
pub suggests: Vec<Dependency>,
264264
}
265265

266266
impl LockedPackage {
@@ -274,9 +274,9 @@ impl LockedPackage {
274274
dependencies: dep
275275
.dependencies
276276
.into_iter()
277-
.map(|d| d.into_owned())
277+
.map(|x| x.into_owned())
278278
.collect(),
279-
suggests: dep.suggests.into_iter().map(|d| d.into_owned()).collect(),
279+
suggests: dep.suggests.into_iter().map(|x| x.into_owned()).collect(),
280280
}
281281
}
282282

@@ -350,8 +350,8 @@ impl Lockfile {
350350

351351
for p in &self.packages {
352352
for d in &p.dependencies {
353-
if !package_names.contains(d.as_str()) {
354-
not_found.insert(d.as_str());
353+
if !package_names.contains(d.name()) {
354+
not_found.insert(d.name());
355355
}
356356
}
357357
}
@@ -456,7 +456,7 @@ impl Lockfile {
456456
let mut out = HashSet::new();
457457
out.insert(p.name.as_str());
458458
for p in &p.dependencies {
459-
out.extend(self.get_package_tree(p.as_str(), None));
459+
out.extend(self.get_package_tree(p.name(), None));
460460
}
461461
out
462462
} else {

src/main.rs

+13
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,22 @@ fn resolve_dependencies(context: &CliContext) -> Vec<ResolvedDependency> {
158158
);
159159
if !resolution.is_success() {
160160
eprintln!("Failed to resolve all dependencies");
161+
161162
for d in resolution.failed {
162163
eprintln!(" {d}");
163164
}
165+
166+
for (name, requirements) in resolution.req_failures {
167+
eprintln!(
168+
" {name}: {}",
169+
requirements
170+
.iter()
171+
.map(|x| x.to_string())
172+
.collect::<Vec<_>>()
173+
.join(", ")
174+
);
175+
}
176+
164177
::std::process::exit(1)
165178
}
166179

src/package/mod.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
44
use std::collections::HashMap;
55
use std::fmt;
66
use std::path::Path;
7+
use toml_edit::{InlineTable, Value};
78

89
mod description;
910
mod parser;
@@ -30,8 +31,9 @@ impl fmt::Display for PackageType {
3031
}
3132
}
3233

33-
#[derive(Debug, PartialEq, Clone, Encode, Decode, Serialize, Deserialize)]
34-
pub(crate) enum Dependency {
34+
#[derive(Debug, Hash, Eq, PartialEq, Clone, Encode, Decode, Serialize, Deserialize)]
35+
#[serde(untagged)]
36+
pub enum Dependency {
3537
Simple(String),
3638
Pinned {
3739
name: String,
@@ -55,6 +57,18 @@ impl Dependency {
5557
} => Some(requirement),
5658
}
5759
}
60+
61+
pub(crate) fn as_toml_value(&self) -> Value {
62+
match self {
63+
Self::Simple(name) => Value::from(name.as_str()),
64+
Self::Pinned { name, requirement } => {
65+
let mut table = InlineTable::new();
66+
table.insert("name", Value::from(name.as_str()));
67+
table.insert("requirement", Value::from(&requirement.to_string()));
68+
Value::InlineTable(table)
69+
}
70+
}
71+
}
5872
}
5973

6074
#[derive(Debug, Default, PartialEq, Clone, Encode, Decode)]

src/package/version.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ use bincode::{Decode, Encode};
22
use serde::{Deserialize, Serialize};
33
use std::cmp::Ordering;
44
use std::fmt;
5+
use std::hash::Hash;
56
use std::str::FromStr;
67

7-
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize, Encode, Decode)]
8+
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone, Serialize, Deserialize, Encode, Decode)]
89
pub enum Operator {
910
Equal,
1011
Greater,
@@ -99,6 +100,12 @@ impl PartialEq for Version {
99100
}
100101
}
101102

103+
impl Hash for Version {
104+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
105+
self.original.hash(state);
106+
}
107+
}
108+
102109
impl Eq for Version {}
103110

104111
impl Ord for Version {
@@ -127,7 +134,7 @@ where
127134
/// A package can require specific version for some versions.
128135
/// Most of the time it's using >= but there are also some
129136
/// >, <, <= here and there and a couple of ==
130-
#[derive(Debug, PartialEq, Clone, Encode, Decode, Serialize, Deserialize)]
137+
#[derive(Debug, Hash, Eq, PartialEq, Clone, Encode, Decode, Serialize, Deserialize)]
131138
pub struct VersionRequirement {
132139
pub(crate) version: Version,
133140
op: Operator,

src/resolver/dependency.rs

+13-37
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use serde::Deserialize;
33
use crate::cache::InstallationStatus;
44
use crate::http::HttpError;
55
use crate::lockfile::{LockedPackage, Source};
6-
use crate::package::{InstallationDependencies, Package, PackageRemote, PackageType};
6+
use crate::package::{Dependency, InstallationDependencies, Package, PackageRemote, PackageType};
77
use crate::resolver::QueueItem;
88
use crate::{Http, HttpDownload, Version, VersionRequirement};
99
use std::borrow::Cow;
@@ -20,8 +20,8 @@ pub struct ResolvedDependency<'d> {
2020
pub(crate) name: Cow<'d, str>,
2121
pub(crate) version: Cow<'d, Version>,
2222
pub(crate) source: Source,
23-
pub(crate) dependencies: Vec<Cow<'d, str>>,
24-
pub(crate) suggests: Vec<Cow<'d, str>>,
23+
pub(crate) dependencies: Vec<Cow<'d, Dependency>>,
24+
pub(crate) suggests: Vec<Cow<'d, Dependency>>,
2525
pub(crate) force_source: bool,
2626
pub(crate) install_suggests: bool,
2727
pub(crate) kind: PackageType,
@@ -59,13 +59,9 @@ impl<'d> ResolvedDependency<'d> {
5959
dependencies: package
6060
.dependencies
6161
.iter()
62-
.map(|d| Cow::Borrowed(d.as_str()))
63-
.collect(),
64-
suggests: package
65-
.suggests
66-
.iter()
67-
.map(|s| Cow::Borrowed(s.as_str()))
62+
.map(|d| Cow::Borrowed(d))
6863
.collect(),
64+
suggests: package.suggests.iter().map(|d| Cow::Borrowed(d)).collect(),
6965
// TODO: what should we do here?
7066
kind: if package.force_source {
7167
PackageType::Source
@@ -113,16 +109,8 @@ impl<'d> ResolvedDependency<'d> {
113109
name: Cow::Borrowed(&package.name),
114110
version: Cow::Borrowed(&package.version),
115111
source,
116-
dependencies: deps
117-
.direct
118-
.iter()
119-
.map(|d| Cow::Borrowed(d.name()))
120-
.collect(),
121-
suggests: deps
122-
.suggests
123-
.iter()
124-
.map(|d| Cow::Borrowed(d.name()))
125-
.collect(),
112+
dependencies: deps.direct.iter().map(|d| Cow::Borrowed(*d)).collect(),
113+
suggests: deps.suggests.iter().map(|d| Cow::Borrowed(*d)).collect(),
126114
kind: package_type,
127115
force_source,
128116
install_suggests,
@@ -148,15 +136,11 @@ impl<'d> ResolvedDependency<'d> {
148136
let deps = package.dependencies_to_install(install_suggests);
149137

150138
let res = Self {
151-
dependencies: deps
152-
.direct
153-
.iter()
154-
.map(|d| Cow::Owned(d.name().to_string()))
155-
.collect(),
139+
dependencies: deps.direct.iter().map(|&d| Cow::Owned(d.clone())).collect(),
156140
suggests: deps
157141
.suggests
158142
.iter()
159-
.map(|s| Cow::Owned(s.name().to_string()))
143+
.map(|&d| Cow::Owned(d.clone()))
160144
.collect(),
161145
kind: PackageType::Source,
162146
force_source: true,
@@ -183,15 +167,11 @@ impl<'d> ResolvedDependency<'d> {
183167
) -> (Self, InstallationDependencies) {
184168
let deps = package.dependencies_to_install(install_suggests);
185169
let res = Self {
186-
dependencies: deps
187-
.direct
188-
.iter()
189-
.map(|d| Cow::Owned(d.name().to_string()))
190-
.collect(),
170+
dependencies: deps.direct.iter().map(|&d| Cow::Owned(d.clone())).collect(),
191171
suggests: deps
192172
.suggests
193173
.iter()
194-
.map(|s| Cow::Owned(s.name().to_string()))
174+
.map(|&d| Cow::Owned(d.clone()))
195175
.collect(),
196176
kind: PackageType::Source,
197177
force_source: true,
@@ -219,15 +199,11 @@ impl<'d> ResolvedDependency<'d> {
219199
) -> (Self, InstallationDependencies) {
220200
let deps = package.dependencies_to_install(install_suggests);
221201
let res = Self {
222-
dependencies: deps
223-
.direct
224-
.iter()
225-
.map(|d| Cow::Owned(d.name().to_string()))
226-
.collect(),
202+
dependencies: deps.direct.iter().map(|&d| Cow::Owned(d.clone())).collect(),
227203
suggests: deps
228204
.suggests
229205
.iter()
230-
.map(|s| Cow::Owned(s.name().to_string()))
206+
.map(|&d| Cow::Owned(d.clone()))
231207
.collect(),
232208
kind,
233209
force_source: false,

0 commit comments

Comments
 (0)