-
Notifications
You must be signed in to change notification settings - Fork 437
/
InstallPath.lean
346 lines (305 loc) · 12.2 KB
/
InstallPath.lean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
/-
Copyright (c) 2021 Mac Malone. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mac Malone
-/
prelude
import Init.Control.Option
import Lean.Compiler.FFI
import Lake.Util.NativeLib
import Lake.Config.Defaults
open System Lean.Compiler.FFI
namespace Lake
/-- Convert the string value of an environment variable to a boolean. -/
def envToBool? (o : String) : Option Bool :=
if ["y", "yes", "t", "true", "on", "1"].contains o.toLower then true
else if ["n", "no", "f", "false", "off", "0"].contains o.toLower then false
else none
/-! ## Data Structures -/
/-- Information about the local Elan setup. -/
structure ElanInstall where
home : FilePath
elan : FilePath
binDir := home / "bin"
toolchainsDir := home / "toolchains"
deriving Inhabited, Repr
/-- Standard path of `lean` in a Lean installation. -/
def leanExe (sysroot : FilePath) :=
sysroot / "bin" / "lean" |>.addExtension FilePath.exeExtension
/-- Standard path of `leanc` in a Lean installation. -/
def leancExe (sysroot : FilePath) :=
sysroot / "bin" / "leanc" |>.addExtension FilePath.exeExtension
/-- Standard path of `llvm-ar` in a Lean installation. -/
def leanArExe (sysroot : FilePath) :=
sysroot / "bin" / "llvm-ar" |>.addExtension FilePath.exeExtension
/-- Standard path of `clang` in a Lean installation. -/
def leanCcExe (sysroot : FilePath) :=
sysroot / "bin" / "clang" |>.addExtension FilePath.exeExtension
/-- Standard path of shared libraries in a Lean installation. -/
def leanSharedLibDir (sysroot : FilePath) :=
if Platform.isWindows then
sysroot / "bin"
else
sysroot / "lib" / "lean"
/-- `libleanshared` file name. -/
def leanSharedLib :=
FilePath.addExtension "libleanshared" sharedLibExt
/-- `Init` shared library file name. -/
def initSharedLib : FilePath :=
FilePath.addExtension "libInit_shared" sharedLibExt
/-- Path information about the local Lean installation. -/
structure LeanInstall where
sysroot : FilePath
githash : String := ""
srcDir := sysroot / "src" / "lean"
leanLibDir := sysroot / "lib" / "lean"
includeDir := sysroot / "include"
systemLibDir := sysroot / "lib"
binDir := sysroot / "bin"
lean := leanExe sysroot
leanc := leancExe sysroot
sharedLib := leanSharedLibDir sysroot / leanSharedLib
initSharedLib := leanSharedLibDir sysroot / initSharedLib
ar : FilePath := "ar"
cc : FilePath := "cc"
customCc : Bool := true
cFlags := getCFlags sysroot |>.push "-Wno-unused-command-line-argument"
linkStaticFlags := getLinkerFlags sysroot (linkStatic := true)
linkSharedFlags := getLinkerFlags sysroot (linkStatic := false)
ccFlags := cFlags
ccLinkStaticFlags := linkStaticFlags
ccLinkSharedFlags := linkSharedFlags
deriving Inhabited, Repr
/--
A `SearchPath` including the Lean installation's shared library directories
(i.e., the system library and Lean library directories).
-/
def LeanInstall.sharedLibPath (self : LeanInstall) : SearchPath :=
if Platform.isWindows then
[self.binDir]
else
[self.leanLibDir, self.systemLibDir]
/-- The `LEAN_CC` of the Lean installation. -/
def LeanInstall.leanCc? (self : LeanInstall) : Option String :=
if self.customCc then self.cc.toString else none
/-- The link-time flags for the C compiler of the Lean installation. -/
def LeanInstall.ccLinkFlags (shared : Bool) (self : LeanInstall) : Array String :=
if shared then self.ccLinkSharedFlags else self.ccLinkStaticFlags
/-- Lake executable file name. -/
def lakeExe : FilePath :=
FilePath.addExtension "lake" FilePath.exeExtension
/-- Path information about the local Lake installation. -/
structure LakeInstall where
home : FilePath
srcDir := home
binDir := home / defaultBuildDir / defaultBinDir
libDir := home / defaultBuildDir / defaultLeanLibDir
lake := binDir / lakeExe
deriving Inhabited, Repr
/-- Construct a Lake installation co-located with the specified Lean installation. -/
def LakeInstall.ofLean (lean : LeanInstall) : LakeInstall where
home := lean.sysroot
srcDir := lean.srcDir / "lake"
binDir := lean.binDir
libDir := lean.leanLibDir
lake := lean.binDir / lakeExe
/-! ## Detection Functions -/
/--
Attempt to detect an Elan installation by checking the `ELAN` and `ELAN_HOME`
environment variables. If `ELAN` is set but empty, Elan is considered disabled.
-/
def findElanInstall? : BaseIO (Option ElanInstall) := do
if let some home ← IO.getEnv "ELAN_HOME" then
let elan := (← IO.getEnv "ELAN").getD "elan"
if elan.trim.isEmpty then
return none
else
return some {elan, home}
return none
/--
Attempt to find the sysroot of the given `lean` command (if it exists)
by calling `lean --print-prefix` and returning the path it prints.
Defaults to trying the `lean` in `PATH`.
-/
def findLeanSysroot? (lean := "lean") : BaseIO (Option FilePath) := do
let act : IO _ := do
let out ← IO.Process.output {
cmd := lean,
args := #["--print-prefix"]
}
if out.exitCode == 0 then
pure <| some <| FilePath.mk <| out.stdout.trim
else
pure <| none
act.catchExceptions fun _ => pure none
/--
Construct the `LeanInstall` object for the given Lean sysroot.
Does the following:
1. Find `lean`'s githash.
2. Finds the `ar` and `cc` to use with Lean.
3. Computes the sub-paths of the Lean install.
For (1), If `lake` is not-collocated with `lean`, invoke `lean --githash`;
otherwise, use Lake's `Lean.githash`. If the invocation fails, `githash` is
set to the empty string.
For (2), if `LEAN_AR` or `LEAN_CC` are defined, it uses those paths.
Otherwise, if Lean is packaged with an `llvm-ar` and/or `clang`, use them.
If not, use the `ar` and/or `cc` from the `AR` / `CC` environment variables
or the system's `PATH`. This last step is needed because internal builds of
Lean do not bundle these tools (unlike user-facing releases).
We also track whether `LEAN_CC` was set to determine whether it should
be set in the future for `lake env`. This is because if `LEAN_CC` was not set,
it needs to remain not set for `leanc` to work.
Even setting it to the bundled compiler will break `leanc` -- see
[leanprover/lean4#1281](https://github.com/leanprover/lean4/issues/1281).
For (3), it assumes that the Lean installation is organized the normal way.
That is, with its binaries located in `<lean-sysroot>/bin`, its
Lean libraries in `<lean-sysroot>/lib/lean`, and its system libraries in
`<lean-sysroot>/lib`.
-/
def LeanInstall.get (sysroot : FilePath) (collocated : Bool := false) : BaseIO LeanInstall := do
let githash ← do
if collocated then
pure Lean.githash
else
-- Remark: This is expensive (at least on Windows), so try to avoid it.
getGithash
let ar ← findAr
setCc {sysroot, githash, ar}
where
getGithash := do
EIO.catchExceptions (h := fun _ => pure "") do
let out ← IO.Process.output {
cmd := leanExe sysroot |>.toString,
args := #["--githash"]
}
return out.stdout.trim
findAr := do
if let some ar ← IO.getEnv "LEAN_AR" then
return FilePath.mk ar
else
let ar := leanArExe sysroot
if (← ar.pathExists) then
return ar
else if let some ar ← IO.getEnv "AR" then
return ar
else
return "ar"
setCc i := do
if let some cc ← IO.getEnv "LEAN_CC" then
return withCustomCc i cc
else
let cc := leanCcExe sysroot
if (← cc.pathExists) then
return withInternalCc i cc
else if let some cc ← IO.getEnv "CC" then
return withCustomCc i cc
else
return withCustomCc i "cc"
@[inline] withCustomCc (i : LeanInstall) cc :=
{i with cc}
withInternalCc (i : LeanInstall) cc :=
let ccLinkFlags := getInternalLinkerFlags sysroot
{i with
cc, customCc := false
ccFlags := i.cFlags ++ getInternalCFlags sysroot
ccLinkStaticFlags := ccLinkFlags ++ i.linkStaticFlags
ccLinkSharedFlags := ccLinkFlags ++ i.linkSharedFlags
}
/--
Attempt to detect the installation of the given `lean` command
by calling `findLeanSysroot?`. See `LeanInstall.get` for how it assumes the
Lean install is organized.
-/
def findLeanCmdInstall? (lean := "lean") : BaseIO (Option LeanInstall) :=
OptionT.run do LeanInstall.get (← findLeanSysroot? lean)
/--
Check if the running Lake's executable is co-located with Lean, and, if so,
try to return their joint home by assuming they are both located at `<home>/bin`.
-/
def findLakeLeanJointHome? : BaseIO (Option FilePath) := do
if let .ok appPath ← IO.appPath.toBaseIO then
if let some appDir := appPath.parent then
let leanExe := appDir / "lean" |>.addExtension FilePath.exeExtension
if (← leanExe.pathExists) then
return appDir.parent
return none
/--
Get the root of Lake's installation by assuming the executable
is located at `<lake-home>/.lake/build/bin/lake`.
-/
def lakeBuildHome? (lake : FilePath) : Option FilePath := do
(← (← (← lake.parent).parent).parent).parent
/--
Heuristically validate that `getLakeBuildHome?` is a proper Lake installation
by check for `Lake.olean` in the installation's `lib` directory.
-/
def getLakeInstall? (lake : FilePath) : BaseIO (Option LakeInstall) := do
let some home := lakeBuildHome? lake | return none
let lake : LakeInstall := {home, lake}
if (← lake.libDir / "Lake.olean" |>.pathExists) then
return lake
return none
/--
Attempt to detect Lean's installation by using the `LEAN` and `LEAN_SYSROOT`
environment variables.
If `LEAN_SYSROOT` is set, use it. Otherwise, check `LEAN` for the `lean`
executable. If `LEAN` is set but empty, Lean will be considered disabled.
Otherwise, Lean's location will be determined by trying `findLeanSysroot?`
using value of `LEAN` or, if unset, the `lean` in `PATH`.
See `LeanInstall.get` for how it assumes the Lean install is organized.
-/
def findLeanInstall? : BaseIO (Option LeanInstall) := do
if let some sysroot ← IO.getEnv "LEAN_SYSROOT" then
return some <| ← LeanInstall.get sysroot
let lean ← do
if let some lean ← IO.getEnv "LEAN" then
if lean.trim.isEmpty then
return none
else
pure lean
else
pure "lean"
if let some sysroot ← findLeanSysroot? lean then
return some <| ← LeanInstall.get sysroot
return none
/--
Attempt to detect Lake's installation by first checking the `lakeBuildHome?`
of the running executable, then trying the `LAKE_HOME` environment variable.
It assumes that the Lake installation is organized the same way it is built.
That is, with its binary located at `<lake-home>/.lake/build/bin/lake` and its
static library and `.olean` files in `<lake-home>/.lake/build/lib`, and
its source files located directly in `<lake-home>`.
-/
def findLakeInstall? : BaseIO (Option LakeInstall) := do
if let Except.ok lake ← IO.appPath.toBaseIO then
if let some lake ← getLakeInstall? lake then
return lake
if let some home ← IO.getEnv "LAKE_HOME" then
return some {home}
return none
/--
Attempt to automatically detect an Elan, Lake, and Lean installation.
First, it calls `findElanInstall?` to detect a Elan installation.
Then it attempts to detect if Lake and Lean are part of a single installation
where the `lake` executable is co-located with the `lean` executable (i.e., they
are in the same directory). If Lean and Lake are not co-located, Lake will
attempt to find the their installations separately by calling
`findLeanInstall?` and `findLakeInstall?`. Setting `LAKE_OVERRIDE_LEAN` to true
will force Lake to use `findLeanInstall?` even if co-located.
When co-located, Lake will assume that Lean and Lake's binaries are located in
`<sysroot>/bin`, their Lean libraries in `<sysroot>/lib/lean`, Lean's source files
in `<sysroot>/src/lean`, and Lake's source files in `<sysroot>/src/lean/lake`,
following the pattern of a regular Lean toolchain.
-/
def findInstall? : BaseIO (Option ElanInstall × Option LeanInstall × Option LakeInstall) := do
let elan? ← findElanInstall?
if let some sysroot ← findLakeLeanJointHome? then
if (← IO.getEnv "LAKE_OVERRIDE_LEAN").bind envToBool? |>.getD false then
let lake := LakeInstall.ofLean {sysroot}
return (elan?, ← findLeanInstall?, lake)
else
let lean ← LeanInstall.get sysroot (collocated := true)
let lake := LakeInstall.ofLean lean
return (elan?, lean, lake)
else
return (elan?, ← findLeanInstall?, ← findLakeInstall?)