fix: lake: visit direct deps before adding manifest entries (#4485)

Fixes a bug in #4371 where the version of a package used by a dependency
would take precedence over that of a the same package as a direct
dependency if that package had a a manifest. This was because the direct
dependency's manifest entries were added before all the direct
dependencies were visited.
This commit is contained in:
Mac Malone 2024-06-18 22:49:59 -04:00 committed by GitHub
parent f9952e8c39
commit bd45c0cd04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 32 additions and 22 deletions

View file

@ -57,9 +57,9 @@ instance [Monad m] [MonadError m] : MonadCycleOf Name (ResolveT m) where
/--
Recursively visits the workspace dependency graph, starting from `root`.
At each package, performs `f` to resolve the dependencies of a package,
recurses, then adds the package to workspace's package set.
Errors if a cycle is encountered.
At each package, loops through each direct dependency performing just `breath`.
Them, loops again through the results applying `depth`, recursing, and adding
the package to workspace's package set. Errors if a cycle is encountered.
**Traversal Order**
@ -87,12 +87,13 @@ the order `R`, `A`, `B`, `X`, `Y`, `C`. If `X` and `C` are both the package
`foo`, Lake would use the configuration of `foo` found in `B` rather than in
the root `R`, which would likely confuse the user.
-/
@[inline] def Workspace.resolveDeps
@[specialize] def Workspace.resolveDeps
[Monad m] [MonadError m] (ws : Workspace)
(f : Package → Dependency → ResolveT m Package)
(breadth : Package → Dependency → ResolveT m Package)
(depth : Package → m PUnit := fun _ => pure ())
: m Workspace := do
let (root, ws) ← ResolveT.run ws <| StateT.run' (s := {}) <|
recFetchAcyclic (·.name) go ws.root
inline <| recFetchAcyclic (·.name) go ws.root
return {ws with root}
where
@[specialize] go pkg resolve : StateT (NameMap Package) (ResolveT m) Package := do
@ -103,12 +104,13 @@ where
return -- already resolved in another branch
if pkg.name = dep.name then
error s!"{pkg.name}: package requires itself (or a package with the same name)"
let dep ← f pkg dep -- package w/o dependencies
store dep.name dep
let pre ← breadth pkg dep -- package w/o dependencies
store dep.name pre
let deps ← pkg.depConfigs.mapM fun dep => do
if let some dep ← fetch? dep.name then
if let some pre ← fetch? dep.name then
modifyThe (NameMap Package) (·.erase dep.name) -- for `dep` linearity
return OpaquePackage.mk (← resolve dep)
depth pre
return OpaquePackage.mk (← resolve pre)
if let some dep ← findPackage? dep.name then
return OpaquePackage.mk dep -- already resolved in another branch
error s!"{dep.name}: impossible resolution state reached"
@ -171,6 +173,19 @@ def reuseManifest (ws : Workspace) (toUpdate : NameSet) : UpdateT LogIO PUnit :=
liftM (m := IO) <| throw e -- only ignore manifest on a bare `lake update`
logWarning s!"{rootName}: ignoring previous manifest because it failed to load: {e}"
/-- Add a package dependency's manifest entries to the update state. -/
def addDependencyEntries (pkg : Package) : UpdateT LogIO PUnit := do
match (← Manifest.load pkg.manifestFile |>.toBaseIO) with
| .ok manifest =>
manifest.packages.forM fun entry => do
unless (← getThe (NameMap PackageEntry)).contains entry.name do
let entry := entry.setInherited.inDirectory pkg.relDir
store entry.name entry
| .error (.noFileOrDirectory ..) =>
logWarning s!"{pkg.name}: ignoring missing manifest '{pkg.manifestFile}'"
| .error e =>
logWarning s!"{pkg.name}: ignoring manifest because it failed to load: {e}"
/-- Update a single dependency. -/
def updateDep
(pkg : Package) (dep : Dependency) (leanOpts : Options := {})
@ -199,17 +214,6 @@ def updateDep
logError s!"'{dep.name}' was downloaded incorrectly; \
you will need to manually delete '{depPkg.dir}': {e}"
error s!"{pkg.name}: package '{depPkg.name}' was required as '{dep.name}'"
-- Add the dependencies' locked dependencies to the manifest
match (← Manifest.load depPkg.manifestFile |>.toBaseIO) with
| .ok manifest =>
manifest.packages.forM fun entry => do
unless (← getThe (NameMap PackageEntry)).contains entry.name do
let entry := entry.setInherited.inDirectory depPkg.relDir
store entry.name entry
| .error (.noFileOrDirectory ..) =>
logWarning s!"{depPkg.name}: ignoring missing dependency manifest '{depPkg.manifestFile}'"
| .error e =>
logWarning s!"{depPkg.name}: ignoring dependency manifest because it failed to load: {e}"
return depPkg
/--
@ -227,7 +231,7 @@ def Workspace.updateAndMaterialize
: LogIO Workspace := do
let (ws, entries) ← UpdateT.run do
reuseManifest ws toUpdate
ws.resolveDeps fun pkg dep => updateDep pkg dep leanOpts
ws.resolveDeps (updateDep · · leanOpts) (addDependencyEntries ·)
let manifestEntries := ws.packages.foldl (init := #[]) fun arr pkg =>
match entries.find? pkg.name with
| some entry => arr.push <|

View file

@ -26,3 +26,9 @@ $LAKE exe Y | grep --color root
cp lake-manifest.json lake-manifest-1.json
$LAKE update foo
diff --strip-trailing-cr lake-manifest-1.json lake-manifest.json
# Tests that order does not change in the presence of dep manifests
$LAKE -d foo update
$LAKE -d bar update
$LAKE update
diff --strip-trailing-cr lake-manifest-1.json lake-manifest.json