@@ -7,6 +7,7 @@ Authors: Arthur Paulino
7
7
import Lean.Data.HashMap
8
8
import Lean.Data.RBMap
9
9
import Lean.Data.RBTree
10
+ import Lean.Data.Json.Printer
10
11
11
12
/-- Removes a parent path from the beginning of a path -/
12
13
def System.FilePath.withoutParent (path parent : FilePath) : FilePath :=
@@ -16,8 +17,15 @@ def System.FilePath.withoutParent (path parent : FilePath) : FilePath :=
16
17
| x, [] => x
17
18
mkFilePath $ aux path.components parent.components
18
19
19
- def UInt64.asTarGz (n : UInt64) : String :=
20
- s! "{ n} .tar.gz"
20
+ def Nat.toHexDigits (n : Nat) : Nat → (res : String := "" ) → String
21
+ | 0 , s => s
22
+ | len+1 , s =>
23
+ let b := UInt8.ofNat (n >>> (len * 8 ))
24
+ Nat.toHexDigits n len <|
25
+ s.push (Nat.digitChar (b >>> 4 ).toNat) |>.push (Nat.digitChar (b &&& 15 ).toNat)
26
+
27
+ def UInt64.asLTar (n : UInt64) : String :=
28
+ s! "{ Nat.toHexDigits n.toNat 8 } .ltar"
21
29
22
30
namespace Cache.IO
23
31
@@ -51,12 +59,23 @@ def CURLBIN :=
51
59
-- change file name if we ever need a more recent version to trigger re-download
52
60
IO.CACHEDIR / s! "curl-{ CURLVERSION} "
53
61
62
+ /-- leantar version at https://github.com/digama0/leangz -/
63
+ def LEANTARVERSION :=
64
+ "0.1.3"
65
+
66
+ def LEANTARBIN :=
67
+ -- change file name if we ever need a more recent version to trigger re-download
68
+ IO.CACHEDIR / s! "leantar-{ LEANTARVERSION}{if System.Platform.isWindows then ".exe" else "" } "
69
+
54
70
def LAKEPACKAGESDIR : FilePath :=
55
71
⟨"lake-packages" ⟩
56
72
57
73
def getCurl : IO String := do
58
74
return if (← CURLBIN.pathExists) then CURLBIN.toString else "curl"
59
75
76
+ def getLeanTar : IO String := do
77
+ return if (← LEANTARBIN.pathExists) then LEANTARBIN.toString else "leantar"
78
+
60
79
abbrev PackageDirs := Lean.RBMap String FilePath compare
61
80
62
81
/-- Whether this is running on Mathlib repo or not -/
@@ -72,6 +91,7 @@ def getPackageDirs : IO PackageDirs := return .ofList [
72
91
("MathlibExtras" , if ← isMathlibRoot then "." else mathlibDepPath),
73
92
("Aesop" , LAKEPACKAGESDIR / "aesop" ),
74
93
("Std" , LAKEPACKAGESDIR / "std" ),
94
+ ("Cli" , LAKEPACKAGESDIR / "Cli" ),
75
95
("ProofWidgets" , LAKEPACKAGESDIR / "proofwidgets" ),
76
96
("Qq" , LAKEPACKAGESDIR / "Qq" )
77
97
]
@@ -136,6 +156,45 @@ def validateCurl : IO Bool := do
136
156
| _ => throw $ IO.userError "Invalidly formatted version of `curl`"
137
157
| _ => throw $ IO.userError "Invalidly formatted response from `curl --version`"
138
158
159
+ def Version := Nat × Nat × Nat
160
+ deriving Inhabited, DecidableEq
161
+
162
+ instance : Ord Version := let _ := @lexOrd; lexOrd
163
+ instance : LE Version := leOfOrd
164
+
165
+ def parseVersion (s : String) : Option Version := do
166
+ let [maj, min, patch] := s.splitOn "." | none
167
+ some (maj.toNat!, min.toNat!, patch.toNat!)
168
+
169
+ def validateLeanTar : IO Unit := do
170
+ if (← LEANTARBIN.pathExists) then return
171
+ if let some version ← some <$> runCmd "leantar" #["--version" ] <|> pure none then
172
+ let "leantar" :: v :: _ := version.splitOn " "
173
+ | throw $ IO.userError "Invalidly formatted response from `leantar --version`"
174
+ let some v := parseVersion v | throw $ IO.userError "Invalidly formatted version of `leantar`"
175
+ -- currently we need exactly one version of leantar, change this to reflect compatibility
176
+ if v = (parseVersion LEANTARVERSION).get! then return
177
+ let win := System.Platform.getIsWindows ()
178
+ let target ← if win then
179
+ pure "x86_64-pc-windows-msvc"
180
+ else
181
+ let mut arch ← (·.trim) <$> runCmd "uname" #["-m" ] false
182
+ if arch = "arm64" then arch := "aarch64"
183
+ unless arch ∈ ["x86_64" , "aarch64" ] do
184
+ throw $ IO.userError s! "unsupported architecture { arch} "
185
+ pure <|
186
+ if System.Platform.getIsOSX () then s! "{ arch} -apple-darwin"
187
+ else s! "{ arch} -unknown-linux-musl"
188
+ IO.println s! "leantar is too old; downloading more recent version"
189
+ IO.FS.createDirAll IO.CACHEDIR
190
+ let ext := if win then "zip" else "tar.gz"
191
+ let _ ← runCmd "curl" #[
192
+ s! "https://github.com/digama0/leangz/releases/download/v{ LEANTARVERSION} /leantar-v{ LEANTARVERSION} -{ target} .{ ext} " ,
193
+ "-L" , "-o" , s! "{ LEANTARBIN} .{ ext} " ]
194
+ let _ ← runCmd "tar" #["-xf" , s! "{ LEANTARBIN} .{ ext} " ,
195
+ "-C" , IO.CACHEDIR.toString, "--strip-components=1" ]
196
+ let _ ← runCmd "mv" #[(IO.CACHEDIR / s! "leantar" ).toString, LEANTARBIN.toString]
197
+
139
198
/-- Recursively gets all files from a directory with a certain extension -/
140
199
partial def getFilesWithExtension
141
200
(fp : FilePath) (extension : String) (acc : Array FilePath := #[]) :
@@ -150,7 +209,7 @@ namespace HashMap
150
209
151
210
def filter (hashMap : HashMap) (set : Lean.RBTree String compare) (keep : Bool) : HashMap :=
152
211
hashMap.fold (init := default) fun acc path hash =>
153
- let contains := set.contains hash.asTarGz
212
+ let contains := set.contains hash.asLTar
154
213
let add := if keep then contains else !contains
155
214
if add then acc.insert path hash else acc
156
215
@@ -169,9 +228,9 @@ Each build file also has a `Bool` indicating whether that file is required for c
169
228
def mkBuildPaths (path : FilePath) : IO $ Array (FilePath × Bool) := do
170
229
let packageDir ← getPackageDir path
171
230
return #[
231
+ (packageDir / LIBDIR / path.withExtension "trace" , true ),
172
232
(packageDir / LIBDIR / path.withExtension "olean" , true ),
173
233
(packageDir / LIBDIR / path.withExtension "ilean" , true ),
174
- (packageDir / LIBDIR / path.withExtension "trace" , true ),
175
234
(packageDir / IRDIR / path.withExtension "c" , true ),
176
235
(packageDir / LIBDIR / path.withExtension "extra" , false )]
177
236
@@ -185,21 +244,25 @@ def allExist (paths : Array (FilePath × Bool)) : IO Bool := do
185
244
def packCache (hashMap : HashMap) (overwrite : Bool) : IO $ Array String := do
186
245
mkDir CACHEDIR
187
246
IO.println "Compressing cache"
188
- let mut acc := default
247
+ let mut acc := #[]
248
+ let mut tasks := #[]
189
249
for (path, hash) in hashMap.toList do
190
- let zip := hash.asTarGz
250
+ let zip := hash.asLTar
191
251
let zipPath := CACHEDIR / zip
192
252
let buildPaths ← mkBuildPaths path
193
253
if ← allExist buildPaths then
194
254
if overwrite || !(← zipPath.pathExists) then
195
- discard $ runCmd "tar" $ #["-I" , "gzip -9" , "-cf" , zipPath.toString] ++
196
- ((← buildPaths.filterM (·.1 .pathExists)) |>.map (·.1 .toString))
255
+ tasks := tasks.push <| ← IO.asTask do
256
+ runCmd (← getLeanTar) $ #[zipPath.toString] ++
257
+ ((← buildPaths.filterM (·.1 .pathExists)) |>.map (·.1 .toString))
197
258
acc := acc.push zip
259
+ for task in tasks do
260
+ _ ← IO.ofExcept task.get
198
261
return acc
199
262
200
263
/-- Gets the set of all cached files -/
201
264
def getLocalCacheSet : IO $ Lean.RBTree String compare := do
202
- let paths ← getFilesWithExtension CACHEDIR "gz "
265
+ let paths ← getFilesWithExtension CACHEDIR "ltar "
203
266
return .fromList (paths.data.map (·.withoutParent CACHEDIR |>.toString)) _
204
267
205
268
def isPathFromMathlib (path : FilePath) : Bool :=
@@ -215,19 +278,22 @@ def unpackCache (hashMap : HashMap) : IO Unit := do
215
278
let hashMap := hashMap.filter (← getLocalCacheSet) true
216
279
let size := hashMap.size
217
280
if size > 0 then
281
+ let now ← IO.monoMsNow
218
282
IO.println s! "Decompressing { size} file(s)"
219
283
let isMathlibRoot ← isMathlibRoot
220
- hashMap.forM fun path hash => do
221
- let _ ← IO.asTask do
222
- match path.parent with
223
- | none | some path => do
224
- let packageDir ← getPackageDir path
225
- mkDir $ packageDir / LIBDIR / path
226
- mkDir $ packageDir / IRDIR / path
284
+ let child ← IO.Process.spawn
285
+ { cmd := ← getLeanTar, args := #["-x" , "-j" , "-" ], stdin := .piped }
286
+ let (stdin, child) ← child.takeStdin
287
+ let config : Array Lean.Json := hashMap.fold (init := #[]) fun config path hash =>
288
+ let pathStr := s! "{ CACHEDIR / hash.asLTar} "
227
289
if isMathlibRoot || !isPathFromMathlib path then
228
- runCmd "tar" #[ "-xzf" , s! " { CACHEDIR / hash.asTarGz } " ]
290
+ config.push <| .str pathStr
229
291
else -- only mathlib files, when not in the mathlib4 repo, need to be redirected
230
- runCmd "tar" #["-xzf" , s! "{ CACHEDIR / hash.asTarGz} " , "-C" , mathlibDepPath.toString]
292
+ config.push <| .mkObj [("file" , pathStr), ("base" , mathlibDepPath.toString)]
293
+ stdin.putStr <| Lean.Json.compress <| .arr config
294
+ let exitCode ← child.wait
295
+ if exitCode != 0 then throw $ IO.userError s! "leantar failed with error code { exitCode} "
296
+ IO.println s! "unpacked in { (← IO.monoMsNow) - now} ms"
231
297
else IO.println "No cache files to decompress"
232
298
233
299
/-- Retrieves the azure token from the environment -/
@@ -241,7 +307,7 @@ instance : Ord FilePath where
241
307
242
308
/-- Removes all cache files except for what's in the `keep` set -/
243
309
def cleanCache (keep : Lean.RBTree FilePath compare := default) : IO Unit := do
244
- for path in ← getFilesWithExtension CACHEDIR "gz " do
310
+ for path in ← getFilesWithExtension CACHEDIR "ltar " do
245
311
if !keep.contains path then IO.FS.removeFile path
246
312
247
313
end Cache.IO
0 commit comments