Zulip Chat Archive
Stream: lean4
Topic: Lake script for automatically generating files with imports
Anand Rao Tadipatri (Jun 29 2023 at 11:34):
I have written a Lake script to automatically detect the top-level folders in a Lean repository and generate the corresponding top-level files that import all the files from these folders. Here is the code, in case anyone else finds it useful:
script mkImports do
let ws ← Lake.getWorkspace
let [pkg] := ws.packageList.filter (·.dir = ⟨"."⟩) | throw <| IO.userError "Current package not found"
IO.println s!"Creating imports for package {pkg.name} ...\n"
for (libName, _) in pkg.leanLibConfigs do
let dir ← FilePath.walkDir libName.toString >>= Array.filterM (not <$> ·.isDir)
let filePathToImport : FilePath → String := fun fp ↦ fp.toString.takeWhile (· != FilePath.extSeparator) |>.map <|
fun c ↦ if c = FilePath.pathSeparator then '.' else c
let imports := dir.foldl (init := "") <| fun s f ↦ s ++ s!"import {filePathToImport f}\n"
IO.FS.writeFile (libName.toString ++ ".lean") imports
IO.println s!"Created imports file for {libName} library."
return 1
This can be inserted at the end of the lakefile
of any Lean4 repository and run using the command lake run mkImports
.
Scott Morrison (Jun 29 2023 at 11:41):
@Anand Rao Tadipatri, would you mind PRing it to mathlib4?
(A question about scripts: do downstream projects inherit them?)
Anand Rao Tadipatri (Jun 29 2023 at 11:45):
Sure, I will do that in a short while.
(I'm not sure about the answer, actually. I'll try it out on a test set-up.)
Arthur Paulino (Jun 29 2023 at 12:02):
You might want to look at https://github.com/lurk-lab/yatima/blob/main/lakefile.lean#L133-L144
Arthur Paulino (Jun 29 2023 at 12:03):
You can run lake run import_all?
on CI. Or lake run import_all
to generate the file.
The code from the link I posted has a two-fold role, such that the bash script could be scrapped
Anand Rao Tadipatri (Jun 29 2023 at 14:45):
@Arthur Paulino Thanks for the link. The idea of having a separate import_all?
function is a neat one; I have modified my code to emulate the design of the Yatima
lakefile
.
def getCurrentPackage : ScriptM Package := do
let ws ← Lake.getWorkspace
let [pkg] := ws.packageList.filter (·.dir = ⟨"."⟩) | throw <| IO.userError "Current package not found"
return pkg
def importsForLib (lib : Name) : IO String := do
let dir ← FilePath.walkDir lib.toString >>= Array.filterM (not <$> ·.isDir)
let filePathToImport : FilePath → String := fun fp ↦ fp.toString.takeWhile (· != FilePath.extSeparator) |>.map <|
fun c ↦ if c = FilePath.pathSeparator then '.' else c
let imports := dir.foldl (init := "") <| fun s f ↦ s ++ s!"import {filePathToImport f}\n"
return imports
script import_all do
let pkg ← getCurrentPackage
IO.println s!"Creating imports for package {pkg.name} ...\n"
for (lib, _) in pkg.leanLibConfigs do
let fileName : FilePath := lib.toString ++ ".lean"
let imports ← importsForLib lib
IO.FS.writeFile fileName imports
IO.println s!"Created imports file for {lib} library."
return 0
script import_all? do
let pkg ← getCurrentPackage
IO.println s!"Checking imports for package {pkg.name} ...\n"
for (lib, _) in pkg.leanLibConfigs do
let fileName : FilePath := lib.toString ++ ".lean"
let allImports ← importsForLib lib
let existingImports ← IO.FS.readFile fileName
unless existingImports = allImports do
IO.eprintln s!"Invalid import list for {lib} library."
IO.eprintln s!"Try running `lake run mkImports`."
return 1
IO.println s!"The imports for package {pkg.name} are up to date."
return 0
Anand Rao Tadipatri (Jun 29 2023 at 15:05):
Unfortunately, it seems that downstream projects do not inherit lake scripts.
Anand Rao Tadipatri (Jun 29 2023 at 15:34):
@Scott Morrison I have created a pull request for this on mathlib4
: #5593. The existing order of imports in Mathlib.lean
is alphabetical, while the order of imports generated by the script import_all
is according to the depth-first traversal of the Mathlib
directory, which is why import_all?
may fail initially (but will succeed after running lake run import_all
).
Mac Malone (Jun 29 2023 at 22:02):
@Anand Rao Tadipatri Some tips. The "current package" (as your script appears to define it) is always simply ws.root
. Also, your getCurrentPackage
will break if lake is run from another directory via lake -d /some/pkg
).
Mac Malone (Jun 29 2023 at 22:12):
Also, there does not need to be correlation between Lean library's name and its root module. For example, the following is a permissible style:
package foo
lean_lib lib {roots := #[`Foo]}
lean_exe exe {root := `Main}
Thus, I would suggest you use libConfig.roots
to find the root module(s) instead. Similarly, I would suggest you iterator over leanLibs
instead ofleanLibConfigs
and use lib.srcDir
to determine the directory where the root modules are stored (as it may not be the current working directory).
Anand Rao Tadipatri (Jun 30 2023 at 09:21):
Thanks a lot for the suggestions, @Mac. I have updated the script on #5593.
Last updated: Dec 20 2023 at 11:08 UTC