fix: lake: library load & link order (#7809)

This PR fixes the order of libraries when loading them via
`--load-dynlib` or `--plugin` in `lean` and when linking them into a
shared library or executable. A `Dynlib` now tracks its dependencies and
they are topologically sorted before being passed to either linking or
loading.

Closes #7790.
This commit is contained in:
Mac Malone 2025-04-06 13:41:08 -04:00 committed by GitHub
parent f4b54a2b18
commit c3ff4334cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 246 additions and 206 deletions

View file

@ -209,14 +209,14 @@ def fetchFileTrace (file : FilePath) (text := false) : JobM BuildTrace := do
/--
Builds `file` using `build` unless it already exists and the current job's
trace matches the trace stored in the `.trace` file. If built, save the new
trace and cache `file`'s hash in a `.hash` file. Otherwise, try to fetch the
trace matches the trace stored in the `.trace` file. If built, saves the new
trace and caches `file`'s hash in a `.hash` file. Otherwise, tries to fetch the
hash from the `.hash` file using `fetchFileTrace`. Build logs (if any) are
saved to the trace file and replayed from there if the build is skipped.
For example, given `file := "foo.c"`, compares `getTrace` with that in
`foo.c.trace` with the hash cached in `foo.c.hash` and the log cached in
`foo.c.trace`.
`foo.c.trace`. If built, the hash is cached in `foo.c.hash` and the new
trace is saved to `foo.c.trace` (including the build log).
If `text := true`, `file` is hashed as a text file rather than a binary file.
-/
@ -395,6 +395,29 @@ private def mkLinkObjArgs
args := args.push s!"-l{lib.name}"
return args
/--
Topologically sorts the library dependency tree by name.
Libraries come *before* their dependencies.
-/
private partial def mkLinkOrder (libs : Array Dynlib) : JobM (Array Dynlib) := do
let r := libs.foldlM (m := Except (Cycle String)) (init := ({}, #[])) fun (v, o) lib =>
go lib [] v o
match r with
| .ok (_, order) => pure order
| .error cycle => error s!"library dependency cycle:\n{formatCycle cycle}"
where
go lib (ps : List String) (v : RBMap String Unit compare) (o : Array Dynlib) := do
let o := o.push lib
if v.contains lib.name then
return (v, o)
if ps.contains lib.name then
throw (lib.name :: ps)
let ps := lib.name :: ps
let v := v.insert lib.name ()
let (v, o) ← lib.deps.foldlM (init := (v, o)) fun (v, o) lib =>
go lib ps v o
return (v, o)
/--
Build a shared library by linking the results of `linkJobs`
using the Lean toolchain's C compiler.
@ -404,7 +427,7 @@ def buildSharedLib
(linkObjs : Array (Job FilePath)) (linkLibs : Array (Job Dynlib))
(weakArgs traceArgs : Array String := #[]) (linker := "c++")
(extraDepTrace : JobM _ := pure BuildTrace.nil)
(plugin := false)
(plugin := false) (linkDeps := Platform.isWindows)
: SpawnM (Job Dynlib) :=
(Job.collectArray linkObjs).bindM fun objs => do
(Job.collectArray linkLibs).mapM (sync := true) fun libs => do
@ -412,9 +435,10 @@ def buildSharedLib
addPlatformTrace -- shared libraries are platform-dependent artifacts
addTrace (← extraDepTrace)
buildFileUnlessUpToDate' libFile do
let libs ← if linkDeps then mkLinkOrder libs else pure #[]
let args := mkLinkObjArgs objs libs ++ weakArgs ++ traceArgs
compileSharedLib libFile args linker
return {name := libName, path := libFile, plugin}
return {name := libName, path := libFile, deps := libs, plugin}
/--
Build a shared library by linking the results of `linkJobs`
@ -424,6 +448,7 @@ def buildLeanSharedLib
(libName : String) (libFile : FilePath)
(linkObjs : Array (Job FilePath)) (linkLibs : Array (Job Dynlib))
(weakArgs traceArgs : Array String := #[]) (plugin := false)
(linkDeps := Platform.isWindows)
: SpawnM (Job Dynlib) :=
(Job.collectArray linkObjs).bindM fun objs => do
(Job.collectArray linkLibs).mapM (sync := true) fun libs => do
@ -432,10 +457,11 @@ def buildLeanSharedLib
addPlatformTrace -- shared libraries are platform-dependent artifacts
buildFileUnlessUpToDate' libFile do
let lean ← getLeanInstall
let libs ← if linkDeps then mkLinkOrder libs else pure #[]
let args := mkLinkObjArgs objs libs ++
weakArgs ++ traceArgs ++ lean.ccLinkSharedFlags
compileSharedLib libFile args lean.cc
return {name := libName, path := libFile, plugin}
return {name := libName, path := libFile, deps := libs, plugin}
/--
Build an executable by linking the results of `linkJobs`
@ -453,6 +479,7 @@ def buildLeanExe
addPlatformTrace -- executables are platform-dependent artifacts
buildFileUnlessUpToDate' exeFile do
let lean ← getLeanInstall
let libs ← mkLinkOrder libs
let args := mkLinkObjArgs objs libs ++
weakArgs ++ traceArgs ++ lean.ccLinkFlags sharedLean
compileExe exeFile args lean.cc

View file

@ -36,7 +36,7 @@ abbrev RecBuildT (m : Type → Type) :=
/-- Log build cycle and error. -/
@[specialize] def buildCycleError [MonadError m] (cycle : Cycle BuildKey) : m α :=
error s!"build cycle detected:\n{"\n".intercalate <| cycle.map (s!" {·}")}"
error s!"build cycle detected:\n{formatCycle cycle}"
instance [Monad m] [MonadError m] : MonadCycleOf BuildKey (RecBuildT m) where
throwCycle := buildCycleError

View file

@ -37,8 +37,8 @@ def buildImportsAndDeps
let dynlibsJob ← root.dynlibs.fetchIn root
let pluginsJob ← root.plugins.fetchIn root
modJob.bindM fun _ =>
impLibsJob.bindM fun impLibs =>
dynlibsJob.bindM fun dynlibs =>
pluginsJob.bindM fun plugins =>
externLibsJob.mapM fun externLibs => do
return computeModuleDeps impLibs externLibs dynlibs plugins
impLibsJob.bindM (sync := true) fun impLibs =>
dynlibsJob.bindM (sync := true) fun dynlibs =>
pluginsJob.bindM (sync := true) fun plugins =>
externLibsJob.mapM (sync := true) fun externLibs => do
computeModuleDeps impLibs externLibs dynlibs plugins

View file

@ -96,27 +96,22 @@ protected def LeanLib.recBuildShared (self : LeanLib) : FetchM (Job Dynlib) := d
let objJobs ← self.moreLinkObjs.foldlM (init := objJobs)
(·.push <$> ·.fetchIn self.pkg)
let libJobs ← id do
-- Fetch dependnecy dynlibs
-- for platforms that must link to them (e.g., Windows)
if Platform.isWindows then
let imps ← mods.foldlM (init := OrdModuleSet.empty) fun imps mod => do
return imps.appendArray (← (← mod.transImports.fetch).await)
let s := (NameSet.empty.insert self.name, #[])
let (_, jobs) ← imps.foldlM (init := s) fun (libs, jobs) imp => do
if libs.contains imp.lib.name then
return (libs, jobs)
else
let job ← imp.lib.shared.fetch
let moreJobs ← imp.lib.moreLinkLibs.mapM (·.fetchIn self.pkg)
let jobs := jobs.push job ++ moreJobs
return (libs.insert imp.lib.name, jobs)
let jobs ← self.moreLinkLibs.foldlM
(·.push <$> ·.fetchIn self.pkg) jobs
let jobs ← self.pkg.externLibs.foldlM
(·.push <$> ·.dynlib.fetch) jobs
return jobs
else
return #[]
-- Fetch dependency dynlibs
-- for situations that need them (see `Dynlib.deps`)
let imps ← mods.foldlM (init := OrdModuleSet.empty) fun imps mod => do
return imps.appendArray (← (← mod.transImports.fetch).await)
let s := (NameSet.empty.insert self.name, #[])
let (_, jobs) ← imps.foldlM (init := s) fun (libs, jobs) imp => do
if libs.contains imp.lib.name then
return (libs, jobs)
else
let jobs ← jobs.push <$> imp.lib.shared.fetch
return (libs.insert imp.lib.name, jobs)
let jobs ← self.moreLinkLibs.foldlM
(·.push <$> ·.fetchIn self.pkg) jobs
let jobs ← self.pkg.externLibs.foldlM
(·.push <$> ·.dynlib.fetch) jobs
return jobs
buildLeanSharedLib self.libName self.sharedLibFile objJobs libJobs
self.weakLinkArgs self.linkArgs (plugin := self.roots.size == 1)

View file

@ -106,54 +106,70 @@ def fetchExternLibs (pkgs : Array Package) : FetchM (Job (Array Dynlib)) :=
Job.collectArray <$> pkgs.flatMapM (·.externLibs.mapM (·.dynlib.fetch))
private def Module.fetchImportLibsCore
(self : Module) (imps : Array Module)
(init : NameSet × Array (Job Dynlib)): FetchM (NameSet × Array (Job Dynlib))
:= imps.foldlM (init := init) fun (libs, jobs) imp => do
if libs.contains imp.lib.name then
return (libs, jobs)
else if self.lib.name = imp.lib.name then
let job ← imp.dynlib.fetch
return (libs, jobs.push job)
else
let jobs ← imp.lib.moreLinkLibs.foldlM (init := jobs)
(·.push <$> ·.fetchIn imp.pkg)
-- Lean wants the external library symbols before module symbols.
let jobs ← jobs.push <$> imp.lib.shared.fetch
return (libs.insert imp.lib.name, jobs)
(self : Module) (imps : Array Module) (compileSelf : Bool)
(init : NameSet × Array (Job Dynlib))
: FetchM (NameSet × Array (Job Dynlib)) :=
imps.foldlM (init := init) fun (libs, jobs) imp => do
if libs.contains imp.lib.name then
return (libs, jobs)
else if compileSelf && self.lib.name = imp.lib.name then
let job ← imp.dynlib.fetch
return (libs, jobs.push job)
else if compileSelf || imp.shouldPrecompile then
let jobs ← jobs.push <$> imp.lib.shared.fetch
return (libs.insert imp.lib.name, jobs)
else
return (libs, jobs)
/--
Computes the transitive dynamic libraries of a module's imports.
Modules from the same library are loaded individually, while modules
from other libraries are loaded as part of the whole library.
-/
@[inline] private def Module.fetchImportLibs
(self : Module) (imps : Array Module) : FetchM (Job (Array Dynlib))
:= do
let s ← self.fetchImportLibsCore imps ({}, #[])
return Job.collectArray s.2
(self : Module) (imps : Array Module) (compileSelf : Bool)
: FetchM (Array (Job Dynlib)) :=
(·.2) <$> self.fetchImportLibsCore imps compileSelf ({}, #[])
/-- Fetch the dynlibs of a list of imports. **For internal use.** -/
@[inline] def fetchImportLibs
(mods : Array Module) : FetchM (Job (Array Dynlib))
:= do
let (_, jobs) ← mods.foldlM (init := ({}, #[])) fun s mod => do
let precompileImports ←
if mod.shouldPrecompile
then mod.transImports.fetch
else mod.precompileImports.fetch
let precompileImports ← precompileImports.await
mod.fetchImportLibsCore precompileImports s
let imports ← (← mod.imports.fetch).await
mod.fetchImportLibsCore imports mod.shouldPrecompile s
let jobs ← mods.foldlM (init := jobs) fun jobs mod => do
if mod.shouldPrecompile
then jobs.push <$> mod.dynlib.fetch
if mod.shouldPrecompile then
jobs.push <$> mod.dynlib.fetch
else return jobs
return Job.collectArray jobs
/--
Topologically sorts the library dependency tree by name.
Libraries come *after* their dependencies.
-/
private partial def mkLoadOrder (libs : Array Dynlib) : FetchM (Array Dynlib) := do
let r := libs.foldlM (m := Except (Cycle String)) (init := ({}, #[])) fun (v, o) lib =>
go lib [] v o
match r with
| .ok (_, order) => pure order
| .error cycle => error s!"library dependency cycle:\n{formatCycle cycle}"
where
go lib (ps : List String) (v : RBMap String Unit compare) (o : Array Dynlib) := do
if v.contains lib.name then
return (v, o)
if ps.contains lib.name then
throw (lib.name :: ps)
let ps := lib.name :: ps
let v := v.insert lib.name ()
let (v, o) ← lib.deps.foldlM (init := (v, o)) fun (v, o) lib =>
go lib ps v o
let o := o.push lib
return (v, o)
def computeModuleDeps
(impLibs : Array Dynlib) (externLibs : Array Dynlib)
(dynlibs : Array Dynlib) (plugins : Array Dynlib)
: ModuleDeps := Id.run do
: FetchM ModuleDeps := do
/-
Requirements:
* Lean wants the external library symbols before module symbols.
@ -162,8 +178,9 @@ def computeModuleDeps
Everything else loads fine with just the augmented library path.
* Linux needs the augmented path to resolve nested dependencies in dynlibs.
-/
let mut plugins := plugins
let impLibs ← mkLoadOrder impLibs
let mut dynlibs := externLibs ++ dynlibs
let mut plugins := plugins
for impLib in impLibs do
if impLib.plugin then
plugins := plugins.push impLib
@ -190,20 +207,17 @@ def Module.recBuildDeps (mod : Module) : FetchM (Job ModuleDeps) := ensureJob do
if imp.name = mod.name then
logError s!"{mod.leanFile}: module imports itself"
imp.olean.fetch
let precompileImports ← if mod.shouldPrecompile then
mod.transImports.fetch else mod.precompileImports.fetch
let precompileImports ← precompileImports.await
let importLibsJob ← mod.fetchImportLibs precompileImports
let pkgs := precompileImports.foldl (·.insert ·.pkg) OrdPackageSet.empty
let pkgs := if mod.shouldPrecompile then pkgs.insert mod.pkg else pkgs
let externLibsJob ← fetchExternLibs pkgs.toArray
let impLibsJob ← Job.collectArray <$>
mod.fetchImportLibs directImports mod.shouldPrecompile
let externLibsJob ← Job.collectArray <$>
mod.pkg.externLibs.mapM (·.dynlib.fetch)
let dynlibsJob ← mod.dynlibs.fetchIn mod.pkg
let pluginsJob ← mod.plugins.fetchIn mod.pkg
extraDepJob.bindM fun _ => do
importJob.bindM (sync := true) fun _ => do
let depTrace ← takeTrace
importLibsJob.bindM (sync := true) fun impLibs => do
impLibsJob.bindM (sync := true) fun impLibs => do
externLibsJob.bindM (sync := true) fun externLibs => do
dynlibsJob.bindM (sync := true) fun dynlibs => do
pluginsJob.mapM (sync := true) fun plugins => do
@ -211,7 +225,7 @@ def Module.recBuildDeps (mod : Module) : FetchM (Job ModuleDeps) := ensureJob do
| none => addTrace depTrace
| some false => addTrace depTrace; addPlatformTrace
| some true => setTrace depTrace
return computeModuleDeps impLibs externLibs dynlibs plugins
computeModuleDeps impLibs externLibs dynlibs plugins
/-- The `ModuleFacetConfig` for the builtin `depsFacet`. -/
def Module.depsFacetConfig : ModuleFacetConfig depsFacet :=
@ -366,44 +380,21 @@ def Module.oFacetConfig : ModuleFacetConfig oFacet :=
Recursively build the shared library of a module
(e.g., for `--load-dynlib` or `--plugin`).
-/
-- TODO: Return `Job OrdModuleSet × OrdPackageSet` or `OrdRBSet Dynlib`?
def Module.recBuildDynlib (mod : Module) : FetchM (Job Dynlib) :=
withRegisterJob s!"{mod.name}:dynlib" do
-- Fetch object files
let modLinkJobs ← mod.nativeFacets true |>.mapM (·.fetch mod)
let modLinksJob := Job.collectArray modLinkJobs
-- Build dynlib
let buildDynlib (moreLibs : Array Dynlib) :=
modLinksJob.mapM fun modLinks => do
addLeanTrace
addPlatformTrace -- shared libraries are platform-dependent artifacts
addPureTrace mod.linkArgs
buildFileUnlessUpToDate' mod.dynlibFile do
let lean ← getLeanInstall
let args :=
modLinks.map toString ++
moreLibs.map (·.path.toString) ++
mod.weakLinkArgs ++ mod.linkArgs ++ lean.ccLinkSharedFlags
compileSharedLib mod.dynlibFile args lean.cc
return ⟨mod.dynlibFile, mod.dynlibName, true⟩
let objJobs ← (mod.nativeFacets true).mapM (·.fetch mod)
-- Fetch dependencies' dynlibs
-- for platforms that must link to them (e.g., Windows)
if Platform.isWindows then
let imps ← (← mod.transImports.fetch).await
let impLibs ← mod.fetchImportLibs imps
let dynlibsJob ← (mod.dynlibs ++ mod.plugins).fetchIn mod.pkg
let pkgs := imps.foldl (·.insert ·.pkg) OrdPackageSet.empty
|>.insert mod.pkg |>.toArray
let externLibsJob ← fetchExternLibs pkgs
impLibs.bindM fun impLibs =>
dynlibsJob.bindM (sync := true) fun dynlibs =>
externLibsJob.bindM (sync := true) fun externLibs =>
buildDynlib (impLibs ++ externLibs ++ dynlibs)
else
buildDynlib #[]
let libJobs ← id do
let imps ← (← mod.imports.fetch).await
let libJobs ← mod.fetchImportLibs imps true
let libJobs ← mod.lib.moreLinkLibs.foldlM
(·.push <$> ·.fetchIn mod.pkg) libJobs
let libJobs ← mod.pkg.externLibs.foldlM
(·.push <$> ·.dynlib.fetch) libJobs
return libJobs
buildLeanSharedLib mod.dynlibName mod.dynlibFile objJobs libJobs
mod.weakLinkArgs mod.linkArgs (plugin := true)
/-- The `ModuleFacetConfig` for the builtin `dynlibFacet`. -/
def Module.dynlibFacetConfig : ModuleFacetConfig dynlibFacet :=

View file

@ -4,7 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mac Malone
-/
prelude
import Lake.Build.Data
import Lake.Build.Key
/-!
# Lake Targets

View file

@ -5,6 +5,7 @@ Authors: Mac Malone
-/
prelude
import Lake.Config.OutFormat
import Lake.Build.Target.Basic
open System Lean
@ -18,6 +19,11 @@ structure Dynlib where
name : String
/-- Whether this library can be loaded as a plugin. -/
plugin := false
/--
Transitive dependencies of this library for situations that need them
(e.g., linking on Windows, loading via `lean`).
-/
deps : Array Dynlib := #[]
deriving Inhabited, Repr
/-- Optional library directory (for `-L`). -/

View file

@ -81,7 +81,7 @@ abbrev DepStackT m := CallStackT Name m
/-- Log dependency cycle and error. -/
@[specialize] def depCycleError [MonadError m] (cycle : Cycle Name) : m α :=
error s!"dependency cycle detected:\n{"\n".intercalate <| cycle.map (s!" {·}")}"
error s!"dependency cycle detected:\n{formatCycle cycle}"
instance [Monad m] [MonadError m] : MonadCycleOf Name (DepStackT m) where
throwCycle := depCycleError

View file

@ -6,6 +6,7 @@ Authors: Mac Malone
prelude
import Init.Control.Except
import Init.Data.List.Basic
import Init.Data.ToString
namespace Lake
@ -15,6 +16,9 @@ abbrev CallStack κ := List κ
/-- A `CallStack` ending in a cycle. -/
abbrev Cycle κ := CallStack κ
def formatCycle [ToString κ] (cycle : Cycle κ) : String :=
"\n".intercalate <| cycle.map (s!" {·}")
/-- A monad equipped with a call stack. -/
class MonadCallStackOf (κ : semiOutParam (Type u)) (m : Type u → Type v) where
getCallStack : m (CallStack κ)

View file

@ -1,9 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
exit 0 # TODO: flaky test disabled
LAKE=${LAKE:-../../.lake/build/bin/lake}
source ../common.sh
./clean.sh
@ -14,36 +10,6 @@ LAKE=${LAKE:-../../.lake/build/bin/lake}
# https://github.com/leanprover/lean4/issues/3351
# https://github.com/leanprover/lean4/issues/3809
test_run() {
echo "[Command]"
echo "$> lake" "$@"
if $LAKE "$@" >produced.out 2>&1; then
rc=$?
else
rc=$?
fi
echo "Lake exited with code $rc"
echo "[Output]"
cat produced.out
return $rc
}
test_err() {
expected=$1; shift
if test_run "$@"; then rc=$?; else rc=$?; fi
echo "[Match \"$expected\"]"
if grep --color -F "$expected" produced.out; then
if [ $rc != 1 ]; then
echo "[Outcome]"
echo "Lake unexpectedly succeeded."
return 1
fi
else
echo "No match found."
return 1
fi
}
# Test a module with a bad import does not kill the whole build
test_err "Building Etc" build Lib.U Etc
# Test importing a missing module from outside the workspace

View file

@ -1,41 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
exit 0 # TODO: flaky test disabled
LAKE=${LAKE:-../../.lake/build/bin/lake}
source ../common.sh
./clean.sh
# Test that changing `moreLean/Leanc/LinkArgs` triggers a rebuild
# Test that changing `weakLean/Leanc/LinkArgs` does not
test_run() {
echo "[Command]"
echo "$> lake" "$@"
if $LAKE "$@" >produced.out 2>&1; then
rc=$?
else
rc=$?
fi
echo "Lake exited with code $rc"
echo "[Output]"
cat produced.out
return $rc
}
test_out() {
expected=$1; shift
if test_run "$@"; then rc=$?; else rc=$?; fi
echo "[Match \"$expected\"]"
if grep --color -F "$expected" produced.out; then
return $rc
else
echo "No match found."
return 1
fi
}
# Test `leanArgs`
test_run build +Hello -R
# see https://github.com/leanprover/lake/issues/50

65
src/lake/tests/common.sh Normal file
View file

@ -0,0 +1,65 @@
set -euo pipefail
LAKE=${LAKE:-lake}
if [ "${OS:-}" = Windows_NT ]; then
LIB_PREFIX=
SHARED_LIB_EXT=dll
elif [ "`uname`" = Darwin ]; then
LIB_PREFIX=lib
SHARED_LIB_EXT=dylib
else
LIB_PREFIX=lib
SHARED_LIB_EXT=so
fi
test_run() {
echo "[COMMAND]"
echo "$> lake" "$@"
if $LAKE "$@" >produced.out 2>&1; then
rc=$?
else
rc=$?
fi
echo "Lake exited with code $rc"
echo "[OUTPUT]"
cat produced.out
return $rc
}
match_out() {
expected=$1; shift
echo "[MATCH \"$expected\"]"
if grep --color -F -- "$expected" produced.out; then
return 0
else
echo "No match found."
return 1
fi
}
test_out() {
expected=$1; shift
if test_run "$@"; then rc=$?; else rc=$?; fi
match_out "$expected"
return $rc
}
test_err() {
expected=$1; shift
if test_run "$@"; then rc=$?; else rc=$?; fi
if match_out "$expected"; then
if [ $rc != 1 ]; then
echo "[OUTCOME]"
echo "Lake unexpectedly succeeded."
return 1
fi
fi
}
test_maybe_err() {
expected=$1; shift
test_run "$@" || true
match_out "$expected"
}

View file

@ -1 +0,0 @@
rm -rf .lake lake-manifest.json

View file

@ -1,29 +0,0 @@
#!/usr/bin/env bash
set -exo pipefail
LAKE=${LAKE:-../../.lake/build/bin/lake}
if [ "$OS" = Windows_NT ]; then
LIB_PREFIX=
SHARED_LIB_EXT=dll
elif [ "`uname`" = Darwin ]; then
LIB_PREFIX=lib
SHARED_LIB_EXT=dylib
else
LIB_PREFIX=lib
SHARED_LIB_EXT=so
fi
./clean.sh
# Test that `moreLinkArgs` are included when linking precompiled modules
($LAKE build -KlinkArgs=-lBaz 2>&1 || true) | grep --color -- "-lBaz"
# Test that dynlibs are part of the module trace unless `platformIndependent` is set
$LAKE build -R
echo foo > .lake/build/lib/lean/Foo_Bar.$SHARED_LIB_EXT
($LAKE build 2>&1 --rehash && exit 1 || true) | grep --color "Building Foo"
rm .lake/build/lib/lean/Foo_Bar.$SHARED_LIB_EXT
$LAKE build -R -KplatformIndependent=true
echo foo > .lake/build/lib/lean/Foo_Bar.$SHARED_LIB_EXT
$LAKE build --rehash --no-build

View file

@ -1 +1,2 @@
import Foo.Bar
import Foo.Baz

View file

@ -0,0 +1,3 @@
import FooDep
initialize greetingRef : IO.Ref String ← IO.mkRef greeting

View file

@ -0,0 +1,3 @@
import FooDepDep
def greeting := hello

View file

@ -0,0 +1 @@
def hello := "Hello"

View file

@ -0,0 +1 @@
rm -rf .lake lake-manifest.json produced.out

View file

@ -8,3 +8,7 @@ lean_lib Foo where
precompileModules := true
platformIndependent := if get_config? platformIndependent |>.isSome then true else none
moreLinkArgs := if let some cfg := get_config? linkArgs then cfg.splitOn " " |>.toArray else #[]
lean_lib FooDep
lean_lib FooDepDep
lean_exe orderTest

View file

@ -0,0 +1,7 @@
-- import a module of lib `Foo` which does not import `FooDep`
import Foo.Bar
-- then, import a module which does
import Foo.Baz
def main : IO Unit := do
IO.println (← greetingRef.get)

View file

@ -0,0 +1,25 @@
#!/usr/bin/env bash
source ../common.sh
./clean.sh
# Test that the link & load order of precompiled libraries is correct
# https://github.com/leanprover/lean4/issues/7790
test_run -v exe orderTest
# Test that `moreLinkArgs` are included when linking precompiled modules
./clean.sh
test_maybe_err "-lBogus" build -KlinkArgs=-lBogus
./clean.sh
# Test that dynlibs are part of the module trace unless `platformIndependent` is set
test_run build -R
echo foo > .lake/build/lib/lean/Foo_Bar.$SHARED_LIB_EXT
test_err "Building Foo" build --rehash
rm .lake/build/lib/lean/Foo_Bar.$SHARED_LIB_EXT
test_run build -R -KplatformIndependent=true
echo foo > .lake/build/lib/lean/Foo_Bar.$SHARED_LIB_EXT
test_run build --rehash --no-build
# cleanup
rm -f produced.out

View file

@ -185,11 +185,12 @@ ENDFOREACH(T)
# bootstrap: too slow
# toolchain: requires elan to download toolchain
# online: downloads remote repositories
# badImport/buildArgs: flaky for unknown reasons
file(GLOB_RECURSE LEANLAKETESTS
"${LEAN_SOURCE_DIR}/lake/tests/test.sh"
"${LEAN_SOURCE_DIR}/lake/examples/test.sh")
FOREACH(T ${LEANLAKETESTS})
if(NOT T MATCHES ".*(lake-packages|bootstrap|toolchain|online).*")
if(NOT T MATCHES ".*(lake-packages|bootstrap|toolchain|online|buildArgs|badImport).*")
GET_FILENAME_COMPONENT(T_DIR ${T} DIRECTORY)
GET_FILENAME_COMPONENT(DIR_NAME ${T_DIR} NAME)
add_test(NAME "leanlaketest_${DIR_NAME}"