Skip to content
This repository was archived by the owner on Oct 25, 2023. It is now read-only.

Commit 82c0823

Browse files
committed
feat: multi lib & exe targets w/ updated build CLI syntax
1 parent 43140c9 commit 82c0823

18 files changed

+252
-66
lines changed

Diff for: Lake/Build/Package.lean

+5-3
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@ def Package.buildLibModules (self : Package) (lib : LeanLibConfig)
5252
(·.task) <$> ActiveTarget.collectOpaqueArray modTargets
5353
buildMods.catchFailure fun _ => pure <| failure
5454

55-
def Package.oleanTarget (self : Package) : OpaqueTarget :=
56-
Target.opaque <| self.buildLibModules self.builtinLibConfig
57-
recBuildModuleOleanTargetWithLocalImports
55+
def Package.mkLibTarget (self : Package) (lib : LeanLibConfig) : OpaqueTarget :=
56+
Target.opaque <| self.buildLibModules lib recBuildModuleOleanTargetWithLocalImports
57+
58+
def Package.libTarget (self : Package) : OpaqueTarget :=
59+
self.mkLibTarget self.builtinLibConfig
5860

5961
-- # Build Specific Modules of the Package
6062

Diff for: Lake/CLI/Build.lean

+87-40
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ def Package.defaultTarget (self : Package) : OpaqueTarget :=
1616
| .bin => self.exeTarget.withoutInfo
1717
| .staticLib => self.staticLibTarget.withoutInfo
1818
| .sharedLib => self.sharedLibTarget.withoutInfo
19-
| .oleans => self.oleanTarget.withoutInfo
19+
| .leanLib => self.libTarget.withoutInfo
20+
| .oleans => self.libTarget.withoutInfo
2021

2122
def parsePkgSpec (ws : Workspace) (spec : String) : Except CliError Package :=
2223
if spec.isEmpty then
@@ -26,57 +27,103 @@ def parsePkgSpec (ws : Workspace) (spec : String) : Except CliError Package :=
2627
| some pkg => return pkg
2728
| none => throw <| CliError.unknownPackage spec
2829

29-
def parseTargetBaseSpec (ws : Workspace) (spec : String) : Except CliError (Package × Option Name) := do
30+
def resolveModuleTarget (pkg : Package) (mod : Name) (facet : String) : Except CliError OpaqueTarget :=
31+
if pkg.hasModule mod then
32+
if facet.isEmpty || facet == "olean" then
33+
return pkg.moduleOleanTarget mod |>.withoutInfo
34+
else if facet == "c" then
35+
return pkg.moduleOleanAndCTarget mod |>.withoutInfo
36+
else if facet == "o" then
37+
return pkg.moduleOTarget mod |>.withoutInfo
38+
else
39+
throw <| CliError.unknownFacet "module" facet
40+
else
41+
throw <| CliError.missingModule pkg.name mod
42+
43+
def resolveLibTarget (pkg : Package) (lib : LeanLibConfig) (facet : String) : Except CliError OpaqueTarget :=
44+
if facet.isEmpty || facet == "lean" || facet == "oleans" then
45+
return pkg.mkLibTarget lib
46+
else if facet == "static" then
47+
return pkg.mkStaticLibTarget lib |>.withoutInfo
48+
else if facet == "shared" then
49+
return pkg.mkSharedLibTarget lib |>.withoutInfo
50+
else
51+
throw <| CliError.unknownFacet "library" facet
52+
53+
def resolveExeTarget (pkg : Package) (exe : LeanExeConfig) (facet : String) : Except CliError OpaqueTarget :=
54+
if facet.isEmpty || facet == "exe" then
55+
return pkg.mkExeTarget exe |>.withoutInfo
56+
else
57+
throw <| CliError.unknownFacet "executable" facet
58+
59+
def resolvePackageTarget (pkg : Package) (facet : String) : Except CliError OpaqueTarget :=
60+
if facet.isEmpty then
61+
return pkg.defaultTarget
62+
else if facet == "exe" || facet == "bin" then
63+
return pkg.exeTarget.withoutInfo
64+
else if facet == "staticLib" then
65+
return pkg.staticLibTarget.withoutInfo
66+
else if facet == "sharedLib" then
67+
return pkg.sharedLibTarget.withoutInfo
68+
else if facet == "leanLib" || facet == "oleans" then
69+
return pkg.libTarget.withoutInfo
70+
else
71+
throw <| CliError.unknownFacet "package" facet
72+
73+
def resolveTargetBaseSpec (ws : Workspace) (spec : String) (facet := "") : Except CliError OpaqueTarget := do
3074
match spec.splitOn "/" with
31-
| [pkgStr] =>
32-
return (← parsePkgSpec ws pkgStr, none)
33-
| [pkgStr, modStr] =>
34-
let mod := modStr.toName
35-
let pkg ← parsePkgSpec ws pkgStr
36-
if pkg.hasModule mod then
37-
return (pkg, mod)
75+
| [spec] =>
76+
if spec.isEmpty || spec.startsWith "@" then
77+
let pkg ← parsePkgSpec ws <| spec.drop 1
78+
resolvePackageTarget pkg facet
79+
else if spec.startsWith "+" then
80+
let mod := spec.drop 1 |>.toName
81+
if let some pkg := ws.packageForModule? mod then
82+
resolveModuleTarget pkg mod facet
83+
else
84+
throw <| CliError.unknownModule mod
85+
else
86+
if let some (pkg, exe) := ws.findExe? spec then
87+
resolveExeTarget pkg exe facet
88+
else if let some (pkg, lib) := ws.findLib? spec then
89+
resolveLibTarget pkg lib facet
90+
else if let some pkg := ws.packageByName? spec then
91+
resolvePackageTarget pkg facet
92+
else if let some pkg := ws.packageForModule? spec then
93+
resolveModuleTarget pkg spec facet
94+
else
95+
throw <| CliError.unknownTarget spec
96+
| [pkgSpec, targetSpec] =>
97+
let pkgSpec := if pkgSpec.startsWith "@" then pkgSpec.drop 1 else pkgSpec
98+
let pkg ← parsePkgSpec ws pkgSpec
99+
if targetSpec.startsWith "+" then
100+
let mod := targetSpec.drop 1 |>.toName
101+
resolveModuleTarget pkg mod facet
38102
else
39-
throw <| CliError.missingModule pkgStr modStr
103+
if let some exe := pkg.findExe? spec then
104+
resolveExeTarget pkg exe facet
105+
else if let some lib := pkg.findLib? spec then
106+
resolveLibTarget pkg lib facet
107+
else if pkg.hasModule spec then
108+
resolveModuleTarget pkg spec facet
109+
else
110+
throw <| CliError.missingTarget pkg.name targetSpec
40111
| _ =>
41112
throw <| CliError.invalidTargetSpec spec '/'
42113

43-
def parseTargetSpec (ws : Workspace) (spec : String) : Except CliError (BuildTarget PUnit) := do
114+
def parseTargetSpec (ws : Workspace) (spec : String) : Except CliError OpaqueTarget := do
44115
match spec.splitOn ":" with
45-
| [rootSpec] =>
46-
let (pkg, mod?) ← parseTargetBaseSpec ws rootSpec
47-
if let some mod := mod? then
48-
return pkg.moduleOleanTarget mod |>.withoutInfo
49-
else
50-
return pkg.defaultTarget |>.withoutInfo
116+
| [spec] =>
117+
resolveTargetBaseSpec ws spec
51118
| [rootSpec, facet] =>
52-
let (pkg, mod?) ← parseTargetBaseSpec ws rootSpec
53-
if let some mod := mod? then
54-
if facet == "olean" then
55-
return pkg.moduleOleanTarget mod |>.withoutInfo
56-
else if facet == "c" then
57-
return pkg.moduleOleanAndCTarget mod |>.withoutInfo
58-
else if facet == "o" then
59-
return pkg.moduleOTarget mod |>.withoutInfo
60-
else
61-
throw <| CliError.unknownModuleFacet facet
62-
else
63-
if facet == "exe" || facet == "bin" then
64-
return pkg.exeTarget.withoutInfo
65-
else if facet == "staticLib" then
66-
return pkg.staticLibTarget.withoutInfo
67-
else if facet == "sharedLib" then
68-
return pkg.sharedLibTarget.withoutInfo
69-
else if facet == "oleans" then
70-
return pkg.oleanTarget.withoutInfo
71-
else
72-
throw <| CliError.unknownPackageFacet facet
119+
resolveTargetBaseSpec ws rootSpec facet
73120
| _ =>
74121
throw <| CliError.invalidTargetSpec spec ':'
75122

76-
def parseTargetSpecs (ws : Workspace) (specs : List String) : Except CliError (List (BuildTarget PUnit)) :=
123+
def parseTargetSpecs (ws : Workspace) (specs : List String) : Except CliError (List OpaqueTarget) :=
77124
specs.mapM <| parseTargetSpec ws
78125

79-
def build (targets : List (BuildTarget PUnit)) : BuildM PUnit := do
126+
def build (targets : List OpaqueTarget) : BuildM PUnit := do
80127
let ws ← getWorkspace
81128
if targets.isEmpty then
82129
ws.root.defaultTarget.build

Diff for: Lake/CLI/Error.lean

+11-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Authors: Mac Malone
55
-/
66

77
namespace Lake
8+
open Lean (Name)
89

910
inductive CliError
1011
/- CLI Errors -/
@@ -16,10 +17,12 @@ inductive CliError
1617
| unknownLongOption (opt : String)
1718
| unexpectedArguments (args : List String)
1819
/- Build CLI Errors -/
20+
| unknownModule (mod : Name)
1921
| unknownPackage (spec : String)
20-
| unknownPackageFacet (spec : String)
21-
| unknownModuleFacet (spec : String)
22-
| missingModule (pkg mod : String)
22+
| unknownFacet (type facet : String)
23+
| unknownTarget (spec : String)
24+
| missingModule (pkg : Name) (mod : Name)
25+
| missingTarget (pkg : Name) (spec : String)
2326
| invalidTargetSpec (spec : String) (tooMany : Char)
2427
/- Script CLI Error -/
2528
| unknownScript (script : String)
@@ -41,10 +44,12 @@ def toString : CliError → String
4144
| unknownShortOption opt => s!"unknown short option '-{opt}'"
4245
| unknownLongOption opt => s!"unknown long option '{opt}'"
4346
| unexpectedArguments as => s!"unexpected arguments: {" ".intercalate as}"
47+
| unknownModule mod => s!"unknown module `{mod.toString false}`"
4448
| unknownPackage spec => s!"unknown package `{spec}`"
45-
| unknownPackageFacet f => s!"unknown package facet `{f}`"
46-
| unknownModuleFacet f => s!"unknown module facet `{f}`"
47-
| missingModule pkg mod => s!"package '{pkg}' has no module '{mod}'"
49+
| unknownFacet ty f => s!"unknown {ty} facet `{f}`"
50+
| unknownTarget spec => s!"unknown target `{spec}`"
51+
| missingModule pkg mod => s!"package '{pkg.toString false}' has no module '{mod.toString false}'"
52+
| missingTarget pkg spec => s!"package '{pkg.toString false}' has no target '{spec}'"
4853
| invalidTargetSpec s c => s!"invalid script spec '{s}' (too many '{c}')"
4954
| unknownScript s => s!"unknown script {s}"
5055
| missingScriptDoc s => s!"no documentation provided for `{s}`"

Diff for: Lake/CLI/Help.lean

+20-11
Original file line numberDiff line numberDiff line change
@@ -56,27 +56,36 @@ def helpBuild :=
5656
USAGE:
5757
lake build [<targets>...] [-- <args>...]
5858
59-
A target is specified with a string of the form '<package>/<module>:<facet>'.
60-
The package must be the root package or one of its direct dependencies
61-
(i.e., those listed in the root configuration file).
59+
A target is specified with a string of the form:
60+
61+
[[@]<package>/][<target>|[+]<module>][:<facet>]
62+
63+
The optional `@` and `+` markers can be used to disambiguate packages
64+
and modules from other kinds of targets (i.e., executables and libraries).
6265
6366
PACKAGE FACETS:
64-
bin build the package's binary
67+
exe build the package's binary executable
68+
leanLib build the package's lean library (*.olean, *.ilean)
6569
staticLib build the package's static library
6670
sharedLib build the package's shared library
67-
oleans build the package's *.olean files
71+
72+
LIBRARY FACETS:
73+
lean build the library's lean binaries (*.olean, *.ilean)
74+
static build the library's static binary
75+
shared build the library's shared binary
6876
6977
MODULE FACETS:
70-
olean build the module's *.olean file
78+
[default] build the module's *.olean and *.ilean files
7179
c build the module's *.c file
7280
o build the module's *.o file
7381
7482
TARGET EXAMPLES:
75-
a build the default facet of package `a`
76-
a/A build the .olean file of module `A` of package `a`
77-
a/A:c build the .c file of module `A` of package `a`
78-
a:oleans build the olean files of package `a`
79-
:bin build the root package's binary
83+
a build the default facet of target `a`
84+
+A build the .olean and .ilean files of module `A`
85+
a/b build the default facet of target `b` of package `a`
86+
a/+A:c build the .c file of module `A` of package `a`
87+
@a:leanLib build the lean library of package `a`
88+
:exe build the root package's executable
8089
8190
A bare `build` command will build the default facet of the root package.
8291
Arguments to the `Packager` itself can be specified with `args`.

Diff for: Lake/Config/Package.lean

+31-4
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ inductive PackageFacet
5050
| /-- The package's binary executable. **DEPRECATED:** Use `exe` instead. -/ bin
5151
| /-- The package's static library. -/ staticLib
5252
| /-- The package's shared library. -/ sharedLib
53-
| /-- The package's `.olean` files. -/ oleans
53+
| /-- The package's lean library (e.g. `olean` / `ilean` files). -/ leanLib
54+
| /-- The package's `.olean` files. **DEPRECATED:** Use `leanLib` instead. -/ oleans
5455
deriving BEq, DecidableEq, Repr
5556
instance : Inhabited PackageFacet := ⟨PackageFacet.bin⟩
5657

@@ -216,6 +217,12 @@ structure PackageConfig extends WorkspaceConfig where
216217
-/
217218
moreLinkArgs : Array String := #[]
218219

220+
/-- Additional library targets for the package. -/
221+
libConfigs : NameMap LeanLibConfig := {}
222+
223+
/-- Additional binary executable targets for the package. -/
224+
exeConfigs : NameMap LeanExeConfig := {}
225+
219226
deriving Inhabited
220227

221228
namespace PackageConfig
@@ -305,9 +312,25 @@ def extraDepTarget (self : Package) : OpaqueTarget :=
305312
def defaultFacet (self : Package) : PackageFacet :=
306313
self.config.defaultFacet
307314

315+
/-- The package's `libConfigs` configuration. -/
316+
def libConfigs (self : Package) : NameMap LeanLibConfig :=
317+
self.config.libConfigs
318+
319+
/-- Get the package's library configuration with the given name. -/
320+
def findLib? (self : Package) (name : Name) : Option LeanLibConfig :=
321+
self.libConfigs.find? name
322+
323+
/-- The package's `exeConfigs` configuration. -/
324+
def exeConfigs (self : Package) : NameMap LeanExeConfig :=
325+
self.config.exeConfigs
326+
327+
/-- Get the package's executable configuration with the given name. -/
328+
def findExe? (self : Package) (name : Name) : Option LeanExeConfig :=
329+
self.exeConfigs.find? name
330+
308331
/-- The package's `moreServerArgs` configuration. -/
309332
def moreServerArgs (self : Package) : Array String :=
310-
self.config.moreServerArgs
333+
self.config.moreServerArgs
311334

312335
/-- The package's `dir` joined with its `srcDir` configuration. -/
313336
def srcDir (self : Package) : FilePath :=
@@ -395,8 +418,12 @@ def getModuleArray (self : Package) : IO (Array Name) :=
395418

396419
/-- Whether the given module is considered local to the package. -/
397420
def isLocalModule (mod : Name) (self : Package) : Bool :=
421+
self.libConfigs.any (fun _ lib => lib.isLocalModule mod) ||
398422
self.builtinLibConfig.isLocalModule mod
399423

400-
/-- Whether the given module is in the package. -/
424+
/-- Whether the given module is in the package (i.e., can build it). -/
401425
def hasModule (mod : Name) (self : Package) : Bool :=
402-
self.config.binRoot == mod || self.config.libGlobs.any fun glob => glob.matches mod
426+
self.libConfigs.any (fun _ lib => lib.hasModule mod) ||
427+
self.exeConfigs.any (fun _ exe => exe.root == mod) ||
428+
self.builtinLibConfig.hasModule mod ||
429+
self.config.binRoot == mod

Diff for: Lake/Config/Targets.lean

+4
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ def isLocalModule (mod : Name) (self : LeanLibConfig) : Bool :=
5959
self.roots.any (fun root => root.isPrefixOf mod) ||
6060
self.globs.any (fun glob => glob.matches mod)
6161

62+
/-- Whether the given module is in the library (i.e., is built as part of it). -/
63+
def hasModule (mod : Name) (self : LeanLibConfig) : Bool :=
64+
self.globs.any fun glob => glob.matches mod
65+
6266
/-- Get an `Array` of the library's modules. -/
6367
def getModuleArray (self : LeanLibConfig) (srcDir : FilePath) : IO (Array Name) := do
6468
let mut mods := #[]

Diff for: Lake/Config/Workspace.lean

+8
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ def isLocalModule (mod : Name) (self : Workspace) : Bool :=
9595
def packageForModule? (mod : Name) (self : Workspace) : Option Package :=
9696
self.findPackage? (·.isLocalModule mod)
9797

98+
/-- Get the workspace's library configuration with the given name. -/
99+
def findLib? (name : Name) (self : Workspace) : Option (Package × LeanLibConfig) :=
100+
self.packageArray.findSome? fun pkg => pkg.findLib? name <&> (pkg, ·)
101+
102+
/-- Get the workspace's executable configuration with the given name. -/
103+
def findExe? (name : Name) (self : Workspace) : Option (Package × LeanExeConfig) :=
104+
self.packageArray.findSome? fun pkg => pkg.findExe? name <&> (pkg, ·)
105+
98106
/-- The `LEAN_PATH` of the workspace. -/
99107
def oleanPath (self : Workspace) : SearchPath :=
100108
self.packageList.map (·.oleanDir)

Diff for: examples/Makefile

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ LAKE ?= ../build/bin/lake
33
all: check-lake test time-bootstrap check-bootstrap test-bootstrapped
44

55
test: test-init test-hello test-io test-deps\
6-
test-git test-ffi test-scripts
6+
test-git test-ffi test-targets test-scripts
77

88
clean: clean-init clean-hello clean-io clean-deps\
9-
clean-git clean-ffi clean-bootstrap
9+
clean-git clean-ffi clean-targets clean-bootstrap
1010

1111
check-lake:
1212
$(LAKE) self-check
@@ -47,6 +47,12 @@ test-ffi:
4747
clean-ffi:
4848
cd ffi && ./clean.sh
4949

50+
test-targets:
51+
cd targets && ./test.sh
52+
53+
clean-targets:
54+
cd targets && ./clean.sh
55+
5056
test-scripts:
5157
cd scripts && ./test.sh
5258

Diff for: examples/targets/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

Diff for: examples/targets/clean.sh

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rm -rf build

0 commit comments

Comments
 (0)