Skip to content

Commit 42802f9

Browse files
committed
feat: lake: postUpdate? + test
1 parent 275af93 commit 42802f9

File tree

10 files changed

+134
-52
lines changed

10 files changed

+134
-52
lines changed

RELEASES.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ v4.3.0 (development in progress)
1414
* The derive handler for `DecidableEq` [now handles](https://github.com/leanprover/lean4/pull/2591) mutual inductive types.
1515
* [Show path of failed import in Lake](https://github.com/leanprover/lean4/pull/2616).
1616
* [Fix linker warnings on macOS](https://github.com/leanprover/lean4/pull/2598).
17+
* **Lake:** Add `postUpdate?` package configuration option. Used by a package to specify some code which should be run after a successful `lake update` of the package or one of its downstream dependencies. ([lake#185](https://github.com/leanprover/lake/issues/185))
1718

1819
v4.2.0
1920
---------

src/lake/Lake/Config/Package.lean

+30
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,32 @@ structure PackageConfig extends WorkspaceConfig, LeanConfig where
9090
/-- An `Array` of target names to build whenever the package is used. -/
9191
extraDepTargets : Array Name := #[]
9292

93+
/--
94+
A post-`lake update` hook. The monadic action is run after a successful
95+
`lake update` execution on this package or one of its downstream dependents.
96+
Defaults to `none`.
97+
98+
As an example, Mathlib can use this feature to synchronize the Lean toolchain
99+
and run `cache get`:
100+
101+
```
102+
package mathlib where
103+
postUpdate? := some do
104+
let some pkg ← findPackage? `mathlib
105+
| error "mathlib is missing from workspace"
106+
let wsToolchainFile := (← getRootPackage).dir / "lean-toolchain"
107+
let mathlibToolchain ← IO.FS.readFile <| pkg.dir / "lean-toolchain"
108+
IO.FS.writeFile wsToolchainFile mathlibToolchain
109+
let some exe := pkg.findLeanExe? `cache
110+
| error s!"{pkg.name}: cache is missing from the package"
111+
let exeFile ← runBuild (exe.build >>= (·.await))
112+
let exitCode ← env exeFile.toString #["get"]
113+
if exitCode ≠ 0 then
114+
error s!"{pkg.name}: failed to fetch cache"
115+
```
116+
-/
117+
postUpdate? : Option (LakeT LogIO PUnit) := none
118+
93119
/--
94120
Whether to compile each of the package's module into a native shared library
95121
that is loaded whenever the module is imported. This speeds up evaluation of
@@ -263,6 +289,10 @@ namespace Package
263289
@[inline] def extraDepTargets (self : Package) : Array Name :=
264290
self.config.extraDepTargets
265291

292+
/-- The package's `postUpdate?` configuration. -/
293+
@[inline] def postUpdate? (self : Package) :=
294+
self.config.postUpdate?
295+
266296
/-- The package's `releaseRepo?` configuration. -/
267297
@[inline] def releaseRepo? (self : Package) : Option String :=
268298
self.config.releaseRepo?

src/lake/Lake/Load/Main.lean

+56-52
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,50 @@ def loadDepPackage (wsDir : FilePath) (dep : MaterializedDep)
4040
remoteUrl? := dep.remoteUrl?
4141
}
4242

43+
/--
44+
Load a `Workspace` for a Lake package by elaborating its configuration file.
45+
Does not resolve dependencies.
46+
-/
47+
def loadWorkspaceRoot (config : LoadConfig) : LogIO Workspace := do
48+
Lean.searchPathRef.set config.env.leanSearchPath
49+
let configEnv ← importConfigFile config.rootDir config.rootDir
50+
config.configOpts config.leanOpts config.configFile config.reconfigure
51+
let pkgConfig ← IO.ofExcept <| PackageConfig.loadFromEnv configEnv config.leanOpts
52+
let root := {
53+
configEnv, leanOpts := config.leanOpts
54+
dir := config.rootDir, config := pkgConfig
55+
}
56+
return {
57+
root, lakeEnv := config.env
58+
moduleFacetConfigs := initModuleFacetConfigs
59+
packageFacetConfigs := initPackageFacetConfigs
60+
libraryFacetConfigs := initLibraryFacetConfigs
61+
}
62+
63+
/--
64+
Finalize the workspace's root and its transitive dependencies
65+
and add them to the workspace.
66+
-/
67+
def Workspace.finalize (ws : Workspace) : LogIO Workspace := do
68+
have : MonadStore Name Package (StateT Workspace LogIO) := {
69+
fetch? := fun name => return (← get).findPackage? name
70+
store := fun _ pkg => modify (·.addPackage pkg)
71+
}
72+
let (res, ws) ← EStateT.run ws do
73+
buildTop (·.name) ws.root fun pkg load => do
74+
let depPkgs ← pkg.deps.mapM load
75+
set <| ← IO.ofExcept <| (← get).addFacetsFromEnv pkg.configEnv pkg.leanOpts
76+
let pkg ← pkg.finalize depPkgs
77+
return pkg
78+
match res with
79+
| Except.ok root =>
80+
return {ws with root}
81+
| Except.error cycle => do
82+
let cycle := cycle.map (s!" {·}")
83+
error <|
84+
s!"oops! dependency load cycle detected (this likely indicates a bug in Lake):\n" ++
85+
"\n".intercalate cycle
86+
4387
/--
4488
Rebuild the workspace's Lake manifest and materialize missing dependencies.
4589
@@ -51,7 +95,7 @@ root dependencies. Otherwise, only update the root dependencies specified.
5195
If `reconfigure`, elaborate configuration files while updating, do not use OLeans.
5296
-/
5397
def buildUpdatedManifest (ws : Workspace)
54-
(toUpdate : NameSet := {}) (reconfigure := true) : LogIO (Workspace × Manifest) := do
98+
(toUpdate : NameSet := {}) (reconfigure := true) : LogIO Workspace := do
5599
let res ← StateT.run (s := mkOrdNameMap MaterializedDep) <| EStateT.run' (mkNameMap Package) do
56100
-- Use manifest versions of root packages that should not be updated
57101
unless toUpdate.isEmpty do
@@ -86,57 +130,19 @@ def buildUpdatedManifest (ws : Workspace)
86130
return {pkg with opaqueDeps := ← deps.mapM (.mk <$> resolve ·)}
87131
match res with
88132
| (.ok root, deps) =>
133+
let ws : Workspace ← {ws with root}.finalize
134+
LakeT.run ⟨ws⟩ <| ws.packages.forM fun pkg => do
135+
if let some postUpdate := pkg.postUpdate? then
136+
logInfo s!"{pkg.name}: running post-update hook"
137+
postUpdate
89138
let manifest : Manifest := {name? := ws.root.name, packagesDir? := ws.relPkgsDir}
90-
let manifest := deps.foldl (fun m d => m.addPackage d.manifestEntry) manifest
91-
return ({ws with root}, manifest)
139+
let manifest := deps.foldl (·.addPackage ·.manifestEntry) manifest
140+
manifest.saveToFile ws.manifestFile
141+
return ws
92142
| (.error cycle, _) =>
93143
let cycle := cycle.map (s!" {·}")
94144
error s!"dependency cycle detected:\n{"\n".intercalate cycle}"
95145

96-
/--
97-
Load a `Workspace` for a Lake package by elaborating its configuration file.
98-
Does not resolve dependencies.
99-
-/
100-
def loadWorkspaceRoot (config : LoadConfig) : LogIO Workspace := do
101-
Lean.searchPathRef.set config.env.leanSearchPath
102-
let configEnv ← importConfigFile config.rootDir config.rootDir
103-
config.configOpts config.leanOpts config.configFile config.reconfigure
104-
let pkgConfig ← IO.ofExcept <| PackageConfig.loadFromEnv configEnv config.leanOpts
105-
let root := {
106-
configEnv, leanOpts := config.leanOpts
107-
dir := config.rootDir, config := pkgConfig
108-
}
109-
return {
110-
root, lakeEnv := config.env
111-
moduleFacetConfigs := initModuleFacetConfigs
112-
packageFacetConfigs := initPackageFacetConfigs
113-
libraryFacetConfigs := initLibraryFacetConfigs
114-
}
115-
116-
/--
117-
Finalize the workspace's root and its transitive dependencies
118-
and add them to the workspace.
119-
-/
120-
def Workspace.finalize (ws : Workspace) : LogIO Workspace := do
121-
have : MonadStore Name Package (StateT Workspace LogIO) := {
122-
fetch? := fun name => return (← get).findPackage? name
123-
store := fun _ pkg => modify (·.addPackage pkg)
124-
}
125-
let (res, ws) ← EStateT.run ws do
126-
buildTop (·.name) ws.root fun pkg load => do
127-
let depPkgs ← pkg.deps.mapM load
128-
set <| ← IO.ofExcept <| (← get).addFacetsFromEnv pkg.configEnv pkg.leanOpts
129-
let pkg ← pkg.finalize depPkgs
130-
return pkg
131-
match res with
132-
| Except.ok root =>
133-
return {ws with root}
134-
| Except.error cycle => do
135-
let cycle := cycle.map (s!" {·}")
136-
error <|
137-
s!"oops! dependency load cycle detected (this likely indicates a bug in Lake):\n" ++
138-
"\n".intercalate cycle
139-
140146
/--
141147
Resolving a workspace's dependencies using a manifest,
142148
downloading and/or updating them as necessary.
@@ -200,15 +206,13 @@ def loadWorkspace (config : LoadConfig) (updateDeps := false) : LogIO Workspace
200206
let rc := config.reconfigure
201207
let ws ← loadWorkspaceRoot config
202208
if updateDeps then
203-
let (ws, manifest) ← buildUpdatedManifest ws {} rc
204-
manifest.saveToFile ws.manifestFile
205-
ws.finalize
209+
buildUpdatedManifest ws {} rc
206210
else
207211
ws.materializeDeps (← Manifest.loadOrEmpty ws.manifestFile) rc
208212

209213
/-- Updates the manifest for the loaded Lake workspace (see `buildUpdatedManifest`). -/
210214
def updateManifest (config : LoadConfig) (toUpdate : NameSet := {}) : LogIO Unit := do
211215
let rc := config.reconfigure
212216
let ws ← loadWorkspaceRoot config
213-
let (ws, manifest) ← buildUpdatedManifest ws toUpdate rc
214-
manifest.saveToFile ws.manifestFile
217+
discard <| buildUpdatedManifest ws toUpdate rc
218+

src/lake/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ Lake provides a large assortment of configuration options for packages.
164164

165165
### Build & Run
166166

167+
* `postUpdate?`: A post-`lake update` hook. The monadic action is run after a successful `lake update` execution on this package or one of its downstream dependents. Defaults to `none`. See the option's docstring for a complete example.
167168
* `precompileModules`: Whether to compile each module into a native shared library that is loaded whenever the module is imported. This speeds up the evaluation of metaprograms and enables the interpreter to run functions marked `@[extern]`. Defaults to `false`.
168169
* `moreServerArgs`: Additional arguments to pass to the Lean language server (i.e., `lean --server`) launched by `lake serve`.
169170
* `buildType`: The `BuildType` of targets in the package (see [`CMAKE_BUILD_TYPE`](https://stackoverflow.com/a/59314670)). One of `debug`, `relWithDebInfo`, `minSizeRel`, or `release`. Defaults to `release`.

src/lake/tests/postUpdate/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
lakefile.olean
2+
lake-manifest.json
3+
toolchain

src/lake/tests/postUpdate/clean.sh

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rm -rf dep/build dep/lakefile.olean dep/toolchain
2+
rm -f lake-manifest.json lakefile.olean toolchain
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def main (args : List String) : IO Unit := do
2+
IO.println s!"post-update hello w/ arguments: {args}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Lake
2+
open Lake DSL
3+
4+
package dep where
5+
postUpdate? := some do
6+
let some pkg ← findPackage? `dep
7+
| error "dep is missing from workspace"
8+
let wsToolchainFile := (← getRootPackage).dir / "toolchain"
9+
let depToolchain ← IO.FS.readFile <| pkg.dir / "toolchain"
10+
IO.FS.writeFile wsToolchainFile depToolchain
11+
let some exe := pkg.findLeanExe? `hello
12+
| error s!"{pkg.name}: hello is missing from the package"
13+
let exeFile ← runBuild (exe.build >>= (·.await))
14+
let exitCode ← env exeFile.toString #["get"]
15+
if exitCode ≠ 0 then
16+
error s!"{pkg.name}: failed to fetch hello"
17+
18+
lean_exe hello
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Lake
2+
open Lake DSL
3+
4+
package test
5+
require dep from "dep"

src/lake/tests/postUpdate/test.sh

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env bash
2+
set -euxo pipefail
3+
4+
LAKE=${LAKE:-../../build/bin/lake}
5+
6+
./clean.sh
7+
8+
# Test the `postUpdate?` configuration option and the docstring example.
9+
# If the Lake API experiences changes, this test and the docstring should be
10+
# updated in tandem.
11+
12+
echo "root" > toolchain
13+
echo "dep" > dep/toolchain
14+
$LAKE update | grep -F "post-update hello w/ arguments: [get]"
15+
test "`cat toolchain`" = dep
16+

0 commit comments

Comments
 (0)