Skip to content

Commit 41b2fe8

Browse files
authored
feat: lake: ModuleLinkInfo & build*Sync (#14254)
This PR adds two new module facets: `linkInfoExport` and `linkInfoNoExport`. They provide information on how to link a module. It also provides `Sync` variants for `buildSharedLib`, `buildLeanSharedLib`, and `buildLeanExe` that work from within a `Job` rather than across them. Together, this new API allows custom build targets to link shared libraries or executables from a module without having to copy the code previously in `LeanExe.recBuildExe`.
1 parent 7433046 commit 41b2fe8

6 files changed

Lines changed: 243 additions & 76 deletions

File tree

src/lake/Lake/Build/Common.lean

Lines changed: 148 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -880,9 +880,13 @@ public def buildStaticLib
880880
compileStaticLib libFile oFiles (← getLeanAr) thin
881881
return art.path
882882

883+
/--
884+
Returns linker arguments to statically link in `objs` (e.g., object files or
885+
static libraries) and dynamically link to `libs` (but *not* their `deps`).
886+
-/
883887
def mkLinkObjArgs
884-
(objs : Array FilePath) (libs : Array Dynlib) : Array String
885-
:= Id.run do
888+
(objs : Array FilePath) (libs : Array Dynlib)
889+
: Array String := Id.run do
886890
let mut args := #[]
887891
for obj in objs do
888892
args := args.push obj.toString
@@ -896,7 +900,7 @@ def mkLinkObjArgs
896900
Topologically sorts the library dependency tree by name.
897901
Libraries come *before* their dependencies.
898902
-/
899-
partial def mkLinkOrder (libs : Array Dynlib) : JobM (Array Dynlib) := do
903+
public partial def mkLinkOrder (libs : Array Dynlib) : JobM (Array Dynlib) := do
900904
let r := libs.foldlM (m := Except (Cycle String)) (init := ({}, #[])) fun (v, o) lib =>
901905
go lib [] v o
902906
match r with
@@ -916,8 +920,66 @@ where
916920
return (v, o)
917921

918922
/--
919-
Build a shared library by linking the results of `linkJobs`
920-
using the Lean toolchain's C compiler.
923+
Returns linker arguments to statically link in `objs` (e.g., object files or
924+
static libraries) and, if `linkDeps := true`, dynamically link to `libs` (and their
925+
transitive `deps`).
926+
-/
927+
@[inline] public def mkLinkArgs
928+
(objs : Array FilePath) (libs : Array Dynlib)
929+
(linkDeps := Platform.isWindows)
930+
: JobM (Array String) := do
931+
let libs ← if linkDeps then mkLinkOrder libs else pure #[]
932+
return mkLinkObjArgs objs libs
933+
934+
@[inline] def mkLeanLinkArgs
935+
(objs : Array FilePath) (libs : Array Dynlib) (args : Array String)
936+
(linkDeps := Platform.isWindows) (sharedLean := true)
937+
: JobM (Array String) := do
938+
let lean ← getLeanInstall
939+
let baseArgs ← mkLinkArgs objs libs linkDeps
940+
return baseArgs ++ args ++ #["-L", lean.leanLibDir.toString] ++ lean.ccLinkFlags sharedLean
941+
942+
/--
943+
Build a shared library using `linker`.
944+
945+
The library will statically link in `linkObjs` (e.g., object files or
946+
static libraries) and, if `linkDeps := true`, dynamically link to `linkLibs`
947+
(and their transitive `deps`).
948+
949+
Additional arguments to the linker can be provided via `args`.
950+
These will come *after* any other arguments.
951+
952+
If `plugin := true`, the resulting `Dynlib` will be marked as a Lean plugin.
953+
This means it is expected to have a `initialize_<libName>` symbol.
954+
-/
955+
public def buildSharedLibSync
956+
(libName : String) (libFile : FilePath)
957+
(linkObjs : Array FilePath) (linkLibs : Array Dynlib)
958+
(args : Array String := #[]) (linker := "c++")
959+
(plugin := false) (linkDeps := Platform.isWindows)
960+
: JobM Dynlib := do
961+
-- shared libraries are platform-dependent artifacts
962+
addPlatformTrace
963+
-- Lean plugins are required to have a specific name
964+
-- and thus need to restored from the cache with that name
965+
let art ← buildArtifactUnlessUpToDate libFile (ext := sharedLibExt) (restore := true) do
966+
let baseArgs ← mkLinkArgs linkObjs linkLibs linkDeps
967+
compileSharedLib libFile (baseArgs ++ args) linker
968+
return {name := libName, path := art.path, deps := linkLibs, plugin}
969+
970+
/--
971+
Build a shared library using `linker`.
972+
973+
The library will statically link in the results of `linkObjs` (e.g., object
974+
files or static libraries) and, if `linkDeps := true`, dynamically link to the
975+
results of `linkLibs`.
976+
977+
Additional arguments to the linker can be provided via `weakArgs` and `traceArgs`.
978+
These will come *after* any other arguments. `traceArgs` will be included in
979+
the build's input trace, `weakArgs` will not.
980+
981+
If `plugin := true`, the resulting `Dynlib` will be marked as a Lean plugin.
982+
This means it is expected to have a `initialize_<libName>` symbol.
921983
-/
922984
public def buildSharedLib
923985
(libName : String) (libFile : FilePath)
@@ -928,20 +990,50 @@ public def buildSharedLib
928990
: SpawnM (Job Dynlib) :=
929991
(Job.collectArray linkObjs "linkObjs").bindM (sync := true) fun objs => do
930992
(Job.collectArray linkLibs "linkLibs").mapM fun libs => do
931-
addPureTrace traceArgs "traceArgs"
932-
addPlatformTrace -- shared libraries are platform-dependent artifacts
933993
addTrace (← extraDepTrace)
934-
-- Lean plugins are required to have a specific name
935-
-- and thus need to copied from the cache with that name
936-
let art ← buildArtifactUnlessUpToDate libFile (ext := sharedLibExt) (restore := true) do
937-
let libs ← if linkDeps then mkLinkOrder libs else pure #[]
938-
let args := mkLinkObjArgs objs libs ++ weakArgs ++ traceArgs
939-
compileSharedLib libFile args linker
940-
return {name := libName, path := art.path, deps := libs, plugin}
994+
addPureTrace traceArgs "traceArgs"
995+
buildSharedLibSync libName libFile objs libs (weakArgs ++ traceArgs) linker plugin linkDeps
941996

942997
/--
943-
Build a shared library by linking the results of `linkJobs`
944-
using `linker`.
998+
Build a shared library linking Lean by using the Lean toolchain's linker.
999+
1000+
The library will statically link in `linkObjs` (e.g., object files or
1001+
static libraries) and, if `linkDeps := true`, dynamically link to `linkLibs`
1002+
(and their transitive `deps`).
1003+
1004+
Additional arguments to the linker can be provided via `weakArgs` and `traceArgs`.
1005+
`traceArgs` will be included in the build's input trace, `weakArgs` will not.
1006+
1007+
If `plugin := true`, the resulting `Dynlib` will be marked as a Lean plugin.
1008+
This means it is expected to have a `initialize_<libName>` symbol.
1009+
-/
1010+
public def buildLeanSharedLibSync
1011+
(libName : String) (libFile : FilePath)
1012+
(linkObjs : Array FilePath) (linkLibs : Array Dynlib)
1013+
(args : Array String := #[]) (plugin := false)
1014+
(linkDeps := Platform.isWindows)
1015+
: JobM Dynlib := do
1016+
addLeanTrace
1017+
addPlatformTrace -- shared libraries are platform-dependent artifacts
1018+
-- Lean plugins are required to have a specific name
1019+
-- and thus need to restored from the cache with that name
1020+
let art ← buildArtifactUnlessUpToDate libFile (ext := sharedLibExt) (restore := true) do
1021+
let args ← mkLeanLinkArgs linkObjs linkLibs args linkDeps (sharedLean := true)
1022+
compileSharedLib libFile args (← getLeanCc)
1023+
return {name := libName, path := art.path, deps := linkLibs, plugin}
1024+
1025+
/--
1026+
Build a shared library linking Lean by using the Lean toolchain's linker.
1027+
1028+
The library will statically link in the results of `linkObjs` (e.g., object
1029+
files or static libraries) and, if `linkDeps := true`, dynamically link to the
1030+
results of `linkLibs` (and their transitive `deps`).
1031+
1032+
Additional arguments to the linker can be provided via `weakArgs` and `traceArgs`.
1033+
`traceArgs` will be included in the build's input trace, `weakArgs` will not.
1034+
1035+
If `plugin := true`, the resulting `Dynlib` will be marked as a Lean plugin.
1036+
This means it is expected to have a `initialize_<libName>` symbol.
9451037
-/
9461038
public def buildLeanSharedLib
9471039
(libName : String) (libFile : FilePath)
@@ -951,22 +1043,48 @@ public def buildLeanSharedLib
9511043
: SpawnM (Job Dynlib) :=
9521044
(Job.collectArray linkObjs "linkObjs").bindM (sync := true) fun objs => do
9531045
(Job.collectArray linkLibs "linkLibs").mapM fun libs => do
954-
addLeanTrace
9551046
addPureTrace traceArgs "traceArgs"
956-
addPlatformTrace -- shared libraries are platform-dependent artifacts
957-
-- Lean plugins are required to have a specific name
958-
-- and thus need to copied from the cache with that name
959-
let art ← buildArtifactUnlessUpToDate libFile (ext := sharedLibExt) (restore := true) do
960-
let lean ← getLeanInstall
961-
let libs ← if linkDeps then mkLinkOrder libs else pure #[]
962-
let args := mkLinkObjArgs objs libs ++ weakArgs ++ traceArgs ++
963-
#["-L", lean.leanLibDir.toString] ++ lean.ccLinkSharedFlags
964-
compileSharedLib libFile args lean.cc
965-
return {name := libName, path := art.path, deps := libs, plugin}
1047+
buildLeanSharedLibSync libName libFile objs libs (weakArgs ++ traceArgs) plugin linkDeps
1048+
1049+
/--
1050+
Build an executable linking Lean by using the Lean toolchain's linker.
1051+
1052+
The executable will statically link in `linkObjs` (e.g., object files or
1053+
static libraries) and dynamically link to `linkLibs` (and their transitive
1054+
`deps`).
1055+
1056+
By default, Lean will be statically linked to the exeutable.
1057+
If `sharedLean := true`, it will instead be dynamically linked.
1058+
This means users of the resulting executable will need to have Lean's
1059+
shared libraries on their system.
1060+
1061+
Additional arguments to the linker can be provided via `args`.
1062+
-/
1063+
public def buildLeanExeSync
1064+
(exeFile : FilePath) (linkObjs : Array FilePath) (linkLibs : Array Dynlib)
1065+
(args : Array String := #[]) (sharedLean : Bool := false)
1066+
: JobM FilePath := do
1067+
addLeanTrace
1068+
addPlatformTrace -- executables are platform-dependent artifacts
1069+
let art ← buildArtifactUnlessUpToDate exeFile (ext := FilePath.exeExtension) (exe := true) (restore := true) do
1070+
let args ← mkLeanLinkArgs linkObjs linkLibs args (linkDeps := true) sharedLean
1071+
compileExe exeFile args (← getLeanCc)
1072+
return art.path
9661073

9671074
/--
968-
Build an executable by linking the results of `linkJobs`
969-
using the Lean toolchain's linker.
1075+
Build an executable linking Lean by using the Lean toolchain's linker.
1076+
1077+
The executable will statically link in the results of `linkObjs` (e.g., object
1078+
files or static libraries) and dynamically link to the results of `linkLibs`
1079+
(and their transitive `deps`).
1080+
1081+
By default, Lean will be statically linked to the exeutable.
1082+
If `sharedLean := true`, it will instead be dynamically linked.
1083+
This means users of the resulting executable will need to have Lean's
1084+
shared libraries on their system.
1085+
1086+
Additional arguments to the linker can be provided via `weakArgs` and `traceArgs`.
1087+
`traceArgs` will be included in the build's input trace, `weakArgs` will not.
9701088
-/
9711089
public def buildLeanExe
9721090
(exeFile : FilePath)
@@ -975,13 +1093,5 @@ public def buildLeanExe
9751093
: SpawnM (Job FilePath) :=
9761094
(Job.collectArray linkObjs "linkObjs").bindM (sync := true) fun objs => do
9771095
(Job.collectArray linkLibs "linkLibs").mapM fun libs => do
978-
addLeanTrace
9791096
addPureTrace traceArgs "traceArgs"
980-
addPlatformTrace -- executables are platform-dependent artifacts
981-
let art ← buildArtifactUnlessUpToDate exeFile (ext := FilePath.exeExtension) (exe := true) (restore := true) do
982-
let lean ← getLeanInstall
983-
let libs ← mkLinkOrder libs
984-
let args := mkLinkObjArgs objs libs ++ weakArgs ++ traceArgs ++
985-
#["-L", lean.leanLibDir.toString] ++ lean.ccLinkFlags sharedLean
986-
compileExe exeFile args lean.cc
987-
return art.path
1097+
buildLeanExeSync exeFile objs libs (weakArgs ++ traceArgs) sharedLean

src/lake/Lake/Build/Executable.lean

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,14 @@ The build function definition for a Lean executable.
2121

2222
def LeanExe.recBuildExe (self : LeanExe) : FetchM (Job FilePath) :=
2323
withRegisterJob s!"{self.name}:exe" <| withCurrPackage self.pkg do
24-
/-
25-
Remark: We must build the root before we fetch the transitive imports
26-
so that errors in the import block of transitive imports will not kill this
27-
job before the root is built.
28-
-/
29-
let mut objJobs := #[]
30-
let mut libJobs := #[]
31-
let shouldExport := self.supportInterpreter
32-
for facet in self.root.nativeFacets shouldExport do
33-
objJobs := objJobs.push <| ← facet.fetch self.root
34-
let .ok imports _ ← (← self.root.transImports.fetch).wait
35-
| error s!"bad imports (see the '{self.root.name.toString}' job for details)"
36-
for mod in imports do
37-
for facet in mod.nativeFacets shouldExport do
38-
objJobs := objJobs.push <| ← facet.fetch mod
39-
for link in self.moreLinkObjs do
40-
objJobs := objJobs.push <| ← link.fetchIn self.pkg
41-
let libs := imports.foldl (·.insert ·.lib) OrdHashSet.empty |>.toArray
42-
for lib in libs do
43-
for link in lib.moreLinkObjs do
44-
objJobs := objJobs.push <| ← link.fetchIn lib.pkg
45-
for link in lib.moreLinkLibs do
46-
libJobs := libJobs.push <| ← link.fetchIn lib.pkg
47-
for link in self.moreLinkLibs do
48-
libJobs := libJobs.push <| ← link.fetchIn self.pkg
49-
let deps := (← (← self.pkg.transDeps.fetch).await).push self.pkg
50-
for dep in deps do
51-
for lib in dep.externLibs do
52-
objJobs := objJobs.push <| ← lib.static.fetch
53-
buildLeanExe self.file objJobs libJobs self.weakLinkArgs self.linkArgs self.sharedLean
24+
let infoJob ←
25+
if self.supportInterpreter
26+
then self.root.linkInfoExport.fetch
27+
else self.root.linkInfoNoExport.fetch
28+
infoJob.mapM fun info => do
29+
let args := self.exeOnlyLinkArgs ++ info.args
30+
addPureTrace self.exeOnlyLinkArgs "LeanExe.exeOnlyLinkArgs"
31+
buildLeanExeSync self.file info.objs info.libs args self.sharedLean
5432

5533
/-- The facet configuration for the builtin `LeanExe.exeFacet`. -/
5634
public def LeanExe.exeFacetConfig : LeanExeFacetConfig exeFacet :=

src/lake/Lake/Build/Facets.lean

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,18 @@ builtin_facet oExportFacet @ o.export : Module => FilePath
181181
/-- The object file built from `c`/`bc` (without Lean symbols exported). -/
182182
builtin_facet oNoExportFacet @ o.noexport : Module => FilePath
183183

184+
/-- Information useful for linking to a module and its dependencies. -/
185+
public structure ModuleLinkInfo where
186+
args : Array String
187+
objs : Array FilePath
188+
libs : Array Dynlib
189+
deriving Inhabited
190+
191+
/-- Link information for the module with Lean symbols exported. -/
192+
builtin_facet linkInfoExport : Module => ModuleLinkInfo
193+
194+
/-- Link information for the module without Lean symbols exported. -/
195+
builtin_facet linkInfoNoExport : Module => ModuleLinkInfo
184196

185197
/-! ## Package Facets -/
186198

src/lake/Lake/Build/Infos.lean

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,12 @@ namespace Module
207207
@[inherit_doc bcoFacet] public abbrev bco (self : Module) :=
208208
self.facetCore bcoFacet
209209

210+
@[inherit_doc linkInfoExportFacet] public abbrev linkInfoExport (self : Module) :=
211+
self.facetCore linkInfoExportFacet
212+
213+
@[inherit_doc linkInfoNoExportFacet] public abbrev linkInfoNoExport (self : Module) :=
214+
self.facetCore linkInfoNoExportFacet
215+
210216
@[inherit_doc dynlibFacet] public abbrev dynlib (self : Module) :=
211217
self.facetCore dynlibFacet
212218

src/lake/Lake/Build/Module.lean

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,54 @@ public def Module.oFacetConfig : ModuleFacetConfig oFacet :=
12361236
| .default | .c => mod.co.fetch
12371237
| .llvm => mod.bco.fetch
12381238

1239+
def recComputeModuleLinkInfo
1240+
(root : Module) (shouldExport : Bool)
1241+
: FetchM (Job ModuleLinkInfo) := do
1242+
/-
1243+
Remark: We must build the root before we fetch the transitive imports
1244+
so that errors in the import block of transitive imports will not kill this
1245+
job before the root is built.
1246+
-/
1247+
let mut objJobs := #[]
1248+
let mut libJobs := #[]
1249+
for facet in root.nativeFacets shouldExport do
1250+
objJobs := objJobs.push <| ← facet.fetch root
1251+
let .ok imports _ ← (← root.transImports.fetch).wait
1252+
| error s!"bad imports (see the '{root.name.toString}' job for details)"
1253+
for mod in imports do
1254+
for facet in mod.nativeFacets shouldExport do
1255+
objJobs := objJobs.push <| ← facet.fetch mod
1256+
for link in root.lib.moreLinkObjs do
1257+
objJobs := objJobs.push <| ← link.fetchIn root.pkg
1258+
let libs := imports.foldl (·.insert ·.lib) OrdHashSet.empty |>.toArray
1259+
for lib in libs do
1260+
for link in lib.moreLinkObjs do
1261+
objJobs := objJobs.push <| ← link.fetchIn lib.pkg
1262+
for link in lib.moreLinkLibs do
1263+
libJobs := libJobs.push <| ← link.fetchIn lib.pkg
1264+
for link in root.lib.moreLinkLibs do
1265+
libJobs := libJobs.push <| ← link.fetchIn root.pkg
1266+
let deps := (← (← root.pkg.transDeps.fetch).await).push root.pkg
1267+
for dep in deps do
1268+
for lib in dep.externLibs do
1269+
objJobs := objJobs.push <| ← lib.static.fetch
1270+
let objsJob := Job.collectArray objJobs "Module.moreLinkObjs"
1271+
let libsJob := Job.collectArray libJobs "Module.moreLinkLibs"
1272+
objsJob.bindM (sync := true) fun objs =>
1273+
libsJob.mapM (sync := true) fun libs => do
1274+
addPureTrace root.lib.linkArgs "Module.moreLinkArgs"
1275+
setTraceCaption s!"{root.name.toString}:linkInfo"
1276+
let args := root.lib.weakLinkArgs ++ root.lib.linkArgs
1277+
return {args, objs, libs}
1278+
1279+
/-- The `ModuleFacetConfig` for the builtin `linkInfoExportFacet`. -/
1280+
public def Module.linkInfoExportFacetConfig : ModuleFacetConfig linkInfoExportFacet :=
1281+
mkFacetJobConfig (buildable := false) <| recComputeModuleLinkInfo (shouldExport := true)
1282+
1283+
/-- The `ModuleFacetConfig` for the builtin `linkInfoNoExportFacet`. -/
1284+
public def Module.linkInfoNoExportFacetConfig : ModuleFacetConfig linkInfoNoExportFacet :=
1285+
mkFacetJobConfig (buildable := false) <| recComputeModuleLinkInfo (shouldExport := false)
1286+
12391287
/--
12401288
Recursively build the shared library of a module
12411289
(e.g., for `--load-dynlib` or `--plugin`).
@@ -1302,6 +1350,8 @@ public def Module.initFacetConfigs : DNameMap ModuleFacetConfig :=
13021350
|>.insert oFacet oFacetConfig
13031351
|>.insert oExportFacet oExportFacetConfig
13041352
|>.insert oNoExportFacet oNoExportFacetConfig
1353+
|>.insert linkInfoExportFacet linkInfoExportFacetConfig
1354+
|>.insert linkInfoNoExportFacet linkInfoNoExportFacetConfig
13051355
|>.insert dynlibFacet dynlibFacetConfig
13061356

13071357
@[inherit_doc Module.initFacetConfigs]

0 commit comments

Comments
 (0)