Skip to content

Commit 5aa45b2

Browse files
committed
Account for the lock file in resolution (only lightly tested)
1 parent 66f7bba commit 5aa45b2

File tree

5 files changed

+156
-32
lines changed

5 files changed

+156
-32
lines changed

Cargo.lock

+6-15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/src/package.rs

-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ impl PackageCommand {
6969
Command::DebugResolution => {
7070
let path = self.find_manifest()?;
7171
let manifest = ManifestFile::from_path(path.clone())?;
72-
// TODO: if a lockfile exists, account for it in resolution
7372
let resolution = manifest.resolve()?;
7473
let package_map = resolution.package_map(&manifest)?;
7574
print_package_map(&package_map);

package/src/lib.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ mod serde_url {
6666
}
6767
}
6868

69+
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
70+
pub struct IndexPrecise {
71+
id: index::Id,
72+
version: Version,
73+
}
74+
6975
/// A precise package version, in a format suitable for putting into a lockfile.
7076
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
7177
pub enum Precise {
@@ -84,10 +90,9 @@ pub enum Precise {
8490
/// to the top-level package manifest.
8591
///
8692
/// Note that when normalizing we only look at the path and not at the actual filesystem.
87-
Path {
88-
path: PathBuf,
89-
},
93+
Path { path: PathBuf },
9094
Index {
95+
// TODO: IndexPrecise
9196
id: index::Id,
9297
version: Version,
9398
},
@@ -132,6 +137,13 @@ impl Precise {
132137
x => x,
133138
}
134139
}
140+
141+
pub fn version(&self) -> Option<Version> {
142+
match self {
143+
Precise::Index { version, .. } => Some(version.clone()),
144+
_ => None,
145+
}
146+
}
135147
}
136148

137149
fn repo_root(id: &ObjectId) -> PathBuf {

package/src/manifest.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ impl ManifestFile {
5858
// Evaluate the manifest with an extra contract applied, so that nice error message will be generated.
5959
// (Probably they applied the Manifest contract already, but just in case...)
6060
// `contract` is `std.package.Manifest`
61-
use nickel_lang_core::term::UnaryOp::StaticAccess;
61+
use nickel_lang_core::term::UnaryOp::RecordAccess;
6262
let contract = make::op1(
63-
StaticAccess("Manifest".into()),
64-
make::op1(StaticAccess("package".into()), Term::Var("std".into())),
63+
RecordAccess("Manifest".into()),
64+
make::op1(RecordAccess("package".into()), Term::Var("std".into())),
6565
);
6666
prog.add_contract(RuntimeContract::new(contract, Label::default()));
6767

@@ -109,7 +109,8 @@ impl ManifestFile {
109109
/// Path dependencies aren't resolved recursively: path dependencies
110110
/// don't get locked because they can change at any time.
111111
pub fn resolve(&self) -> Result<Resolution, Error> {
112-
crate::resolve::resolve(self)
112+
let lock = self.find_lockfile().unwrap_or_default();
113+
crate::resolve::resolve_with_lock(self, &lock)
113114
}
114115

115116
/// Determine the fully-resolved dependencies.

package/src/resolve.rs

+130-9
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,18 @@ use crate::{
3636
index::{Id, IndexDependency, PackageIndex},
3737
lock::{LockFile, LockFileEntry},
3838
manifest::Realization,
39-
Dependency, ManifestFile, Precise,
39+
util::semver_to_pg,
40+
Dependency, IndexPrecise, ManifestFile, Precise,
4041
};
4142

4243
type VersionRange = pubgrub::range::Range<SemanticVersion>;
4344

4445
pub struct PackageRegistry {
46+
// The packages whose versions were locked in a lockfile; we'll try to prefer using
47+
// those same versions. We won't absolutely insist on it, because if the manifest
48+
// changed (or some path-dependency changed) then the old locked versions might not
49+
// resolve anymore.
50+
previously_locked: HashMap<VirtualPackage, SemanticVersion>,
4551
index: PackageIndex,
4652
realized_unversioned: Realization,
4753
}
@@ -51,7 +57,8 @@ impl PackageRegistry {
5157
&'a self,
5258
package: &VirtualPackage,
5359
) -> impl Iterator<Item = SemanticVersion> + 'a {
54-
match package {
60+
let locked_version = self.previously_locked.get(package).cloned();
61+
let rest = match package {
5562
VirtualPackage::Package(Package::Unversioned(_)) => {
5663
Box::new(std::iter::once(SemanticVersion::zero())) as Box<dyn Iterator<Item = _>>
5764
}
@@ -81,7 +88,12 @@ impl PackageRegistry {
8188

8289
Box::new(iter)
8390
}
84-
}
91+
};
92+
93+
// Put the locked version first, and then the other versions in any order (filtering to ensure that the locked version isn't repeated).
94+
locked_version
95+
.into_iter()
96+
.chain(rest.filter(move |v| Some(v) != locked_version.as_ref()))
8597
}
8698

8799
pub fn dep(&self, pkg: &Package, version: &SemanticVersion, dep_id: &Id) -> VersionReq {
@@ -270,6 +282,22 @@ impl std::fmt::Display for Package {
270282
}
271283
}
272284

285+
// Makes the precise less precise, by returning the bucket that it falls into.
286+
impl From<Precise> for Package {
287+
fn from(p: Precise) -> Self {
288+
match p {
289+
Precise::Git { repo, .. } => {
290+
Package::Unversioned(UnversionedPackage::Git { url: repo })
291+
}
292+
Precise::Path { path } => Package::Unversioned(UnversionedPackage::Path { path }),
293+
Precise::Index { id, version } => Package::Bucket(Bucket {
294+
id,
295+
version: semver_to_pg(version).into(),
296+
}),
297+
}
298+
}
299+
}
300+
273301
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
274302
pub enum VirtualPackage {
275303
Package(Package),
@@ -319,10 +347,25 @@ impl DependencyProvider<VirtualPackage, SemanticVersion> for PackageRegistry {
319347
&self,
320348
potential_packages: impl Iterator<Item = (T, U)>,
321349
) -> Result<(T, Option<SemanticVersion>), Box<dyn std::error::Error>> {
322-
Ok(pubgrub::solver::choose_package_with_fewest_versions(
323-
|p| self.list_versions(p),
324-
potential_packages,
325-
))
350+
// We try to choose the package with the fewest available versions, as the pubgrub
351+
// docs recommend this as a reasonably-performant heuristic. We count a previously locked package
352+
// as having one version (even if we'd theoretically be willing to ignore the lock).
353+
let count_valid = |(p, range): &(T, U)| {
354+
if self.previously_locked.contains_key(p.borrow()) {
355+
1
356+
} else {
357+
self.list_versions(p.borrow())
358+
.filter(|v| range.borrow().contains(v))
359+
.count()
360+
}
361+
};
362+
let (pkg, range) = potential_packages
363+
.min_by_key(count_valid)
364+
.expect("potential_packages gave us an empty iterator");
365+
let version = self
366+
.list_versions(pkg.borrow())
367+
.find(|v| range.borrow().contains(v));
368+
Ok((pkg, version))
326369
}
327370

328371
fn get_dependencies(
@@ -428,6 +471,72 @@ pub struct Resolution {
428471
}
429472

430473
pub fn resolve(manifest: &ManifestFile) -> Result<Resolution, Error> {
474+
resolve_with_lock(manifest, &LockFile::default())
475+
}
476+
477+
fn previously_locked(
478+
top_level: &Package,
479+
lock: &LockFile,
480+
) -> HashMap<VirtualPackage, SemanticVersion> {
481+
fn precise_to_index(p: &Precise) -> Option<IndexPrecise> {
482+
match p {
483+
Precise::Index { id, version } => Some(IndexPrecise {
484+
id: id.clone(),
485+
version: version.clone(),
486+
}),
487+
_ => None,
488+
}
489+
}
490+
491+
// A list of (package: Package, version of the package: SemanticVersion, dependency: IndexPrecise)
492+
let pkg_deps = lock
493+
.dependencies
494+
.values()
495+
.filter_map(precise_to_index)
496+
// FIXME: another place we're hardcoding an unversioned version
497+
.map(|dep| (top_level.clone(), SemanticVersion::zero(), dep))
498+
.chain(lock.packages.iter().flat_map(|(parent, entry)| {
499+
let pkg = Package::from(parent.clone());
500+
let pkg_version = parent
501+
.version()
502+
.map(semver_to_pg)
503+
.unwrap_or(SemanticVersion::zero());
504+
entry
505+
.dependencies
506+
.values()
507+
.filter_map(precise_to_index)
508+
.map(move |v| (pkg.clone(), pkg_version, v))
509+
}));
510+
511+
// Because of the virtual package system, each parent->dep needs two entries
512+
// in the locked map: one for recording which of the "union" packages the parent
513+
// depends on, and another for recording which precise version the "union" package depends on.
514+
pkg_deps
515+
.flat_map(|(pkg, version, dep)| {
516+
let dep_version = semver_to_pg(dep.version);
517+
let dep_bucket: BucketVersion = dep_version.into();
518+
[
519+
(
520+
VirtualPackage::Union {
521+
source: pkg,
522+
source_version: version,
523+
target: dep.id.clone(),
524+
},
525+
dep_bucket.into(),
526+
),
527+
(
528+
VirtualPackage::Package(Package::Bucket(Bucket {
529+
id: dep.id,
530+
version: dep_bucket,
531+
})),
532+
dep_version,
533+
),
534+
]
535+
})
536+
.collect()
537+
}
538+
539+
pub fn resolve_with_lock(manifest: &ManifestFile, lock: &LockFile) -> Result<Resolution, Error> {
431540
let mut realization = Realization::default();
432541

433542
// pubgrub insists on resolving a top-level package. We'll represent it as a `Path` dependency,
@@ -448,10 +557,14 @@ pub fn resolve(manifest: &ManifestFile) -> Result<Resolution, Error> {
448557
};
449558
realization
450559
.precise
451-
.insert(top_level_dep.into(), precise.clone());
560+
.insert(top_level_dep.clone().into(), precise.clone());
452561
realization.manifests.insert(precise, manifest.clone());
453562

454563
let registry = PackageRegistry {
564+
previously_locked: dbg!(previously_locked(
565+
&Package::Unversioned(top_level_dep),
566+
lock
567+
)),
455568
index: PackageIndex::new(),
456569
realized_unversioned: realization,
457570
};
@@ -488,6 +601,12 @@ pub fn resolve(manifest: &ManifestFile) -> Result<Resolution, Error> {
488601
}
489602

490603
impl Resolution {
604+
/// Finds the precise resolved version of this dependency.
605+
///
606+
/// # Panics
607+
///
608+
/// Panics if the dependency was not part of the dependency tree that this resolution
609+
/// was generated for.
491610
pub fn precise(&self, dep: &Dependency) -> Precise {
492611
match dep {
493612
Dependency::Git { url } => Precise::Git {
@@ -510,6 +629,7 @@ impl Resolution {
510629
}
511630
}
512631

632+
/// Returns all the dependencies of a package, along with their package-local names.
513633
pub fn dependencies(&self, pkg: &Precise) -> HashMap<Ident, Precise> {
514634
match pkg {
515635
p @ Precise::Git { .. } | p @ Precise::Path { .. } => {
@@ -546,6 +666,7 @@ impl Resolution {
546666
}
547667
}
548668

669+
/// Returns all the resolved packages in the dependency tree.
549670
pub fn all_precises(&self) -> Vec<Precise> {
550671
let mut ret: Vec<_> = self.realization.precise.values().cloned().collect();
551672
ret.sort();
@@ -595,7 +716,7 @@ impl Resolution {
595716
)
596717
})
597718
})
598-
.collect(), //,realized_packages.chain(index_packages).collect(),
719+
.collect(),
599720
})
600721
}
601722

0 commit comments

Comments
 (0)