@@ -211,6 +211,40 @@ public structure Cache where
211211 dir : FilePath
212212 deriving Inhabited
213213
214+ /-- The current version of the output file format. -/
215+ def CacheOutput.schemaVersion : String := "2026-02-10"
216+
217+ structure CacheOutput where
218+ service? : Option String := none
219+ data : Json
220+ deriving ToJson
221+
222+ namespace CacheOutput
223+
224+ protected def toJson (out : CacheOutput) : Json :=
225+ JsonObject.empty
226+ |>.insert "schemaVersion" schemaVersion
227+ |>.insert "service" out.service?
228+ |>.insert "data" out.data
229+
230+ instance : ToJson CacheOutput := ⟨CacheOutput.toJson⟩
231+
232+ protected def fromJson? (json : Json) : Except String CacheOutput := do
233+ if let .obj obj := json then
234+ let obj := JsonObject.mk obj
235+ if obj.contains "schemaVersion" then
236+ -- presumably the new format
237+ -- (the edge case of a custom output with a `schemaVersion` is not worth covering)
238+ let service? ← obj.get? "service"
239+ let data ← obj.get "data"
240+ return {service?, data}
241+ -- old format: just the data
242+ return {data := json}
243+
244+ instance : FromJson CacheOutput := ⟨CacheOutput.fromJson?⟩
245+
246+ end CacheOutput
247+
214248namespace Cache
215249
216250/-- Returns the artifact directory for the Lake cache. -/
@@ -260,29 +294,34 @@ public def getArtifactPaths
260294 cache.outputsDir / scope / s! "{ inputHash} .json"
261295
262296/-- Cache the outputs corresponding to the given input for the package. -/
263- public def writeOutputsCore
297+ def writeOutputsCore
264298 (cache : Cache) (scope : String) (inputHash : Hash) (outputs : Json)
299+ (service? : Option String := none)
265300: IO Unit := do
266301 let file := cache.outputsFile scope inputHash
267302 createParentDirs file
268- IO.FS.writeFile file outputs.compress
303+ let out := {service?, data := outputs : CacheOutput}
304+ IO.FS.writeFile file (toJson out).pretty
269305
270306/-- Cache the outputs corresponding to the given input for the package. -/
271307@[inline] public def writeOutputs
272308 [ToJson α] (cache : Cache) (scope : String) (inputHash : Hash) (outputs : α)
273- : IO Unit := cache.writeOutputsCore scope inputHash (toJson outputs)
309+ (service? : Option String := none)
310+ : IO Unit := cache.writeOutputsCore scope inputHash (toJson outputs) service?
274311
275312/-- Cache the input-to-outputs mappings from a `CacheMap`. -/
276- public def writeMap (cache : Cache) (scope : String) (map : CacheMap) : IO Unit :=
277- map.forM fun i o => cache.writeOutputsCore scope i o
313+ public def writeMap
314+ (cache : Cache) (scope : String) (map : CacheMap) (service? : Option String := none)
315+ : IO Unit := map.forM fun i o => cache.writeOutputsCore scope i o service?
278316
279317/-- Retrieve the cached outputs corresponding to the given input for the package (if any). -/
280318public def readOutputs? (cache : Cache) (scope : String) (inputHash : Hash) : LogIO (Option Json) := do
281319 let path := cache.outputsFile scope inputHash
282320 match (← IO.FS.readFile path |>.toBaseIO) with
283321 | .ok contents =>
284- match Json.parse contents with
285- | .ok outputs => return outputs
322+ match Json.parse contents >>= fromJson? with
323+ | .ok out =>
324+ return CacheOutput.data out
286325 | .error e =>
287326 logWarning s! "{ path} : invalid JSON: { e} "
288327 return none
@@ -336,6 +375,7 @@ the desired functions by using `CacheService`'s smart constructors
336375-/
337376public structure CacheService where
338377 private mk ::
378+ private name? : Option String := none
339379 /- S3 Bucket -/
340380 private key : String := ""
341381 private artifactEndpoint : String := ""
@@ -353,19 +393,22 @@ namespace CacheService
353393
354394/-- Constructs a `CacheService` for a Reservoir endpoint. -/
355395@[inline] public def reservoirService (apiEndpoint : String) (repoScope := false ) : CacheService :=
356- {isReservoir := true , apiEndpoint, repoScope}
396+ {name? := some "reservoir" , isReservoir := true , apiEndpoint, repoScope}
357397
358398/-- Constructs a `CacheService` to upload artifacts and/or outputs to an S3 endpoint. -/
359- @[inline] public def uploadService (key artifactEndpoint revisionEndpoint : String) : CacheService :=
360- {key, artifactEndpoint, revisionEndpoint}
399+ @[inline] public def uploadService
400+ (key artifactEndpoint revisionEndpoint : String)
401+ : CacheService := {key, artifactEndpoint, revisionEndpoint}
361402
362- /-- Constructs a `CacheService` to download artifacts and/or outputs from to an S3 endpoint. -/
363- @[inline] public def downloadService (artifactEndpoint revisionEndpoint : String) : CacheService :=
364- {artifactEndpoint, revisionEndpoint}
403+ /-- Constructs a `CacheService` to download artifacts and/or outputs from an S3 endpoint. -/
404+ @[inline] public def downloadService
405+ (artifactEndpoint revisionEndpoint : String) (name? : Option String := none)
406+ : CacheService := {name?, artifactEndpoint, revisionEndpoint}
365407
366- /-- Constructs a `CacheService` to download just artifacts from to an S3 endpoint. -/
367- @[inline] public def downloadArtsService (artifactEndpoint : String) : CacheService :=
368- {artifactEndpoint}
408+ /-- Constructs a `CacheService` to download just artifacts from an S3 endpoint. -/
409+ @[inline] public def downloadArtsService
410+ (artifactEndpoint : String) (name? : Option String := none)
411+ : CacheService := {name?, artifactEndpoint}
369412
370413/--
371414Reconfigures the cache service to interpret scopes as repositories (or not if `false`).
@@ -385,10 +428,10 @@ private def appendScope (endpoint : String) (scope : String) : String :=
385428 scope.split '/' |>.fold (init := endpoint) fun s component =>
386429 uriEncode component.copy s |>.push '/'
387430
388- private def s3ArtifactUrl (contentHash : Hash) (service : CacheService) (scope : String) : String :=
431+ private def s3ArtifactUrl (contentHash : Hash) (service : CacheService) (scope : String) : String :=
389432 appendScope s! "{ service.artifactEndpoint} /" scope ++ s! "{ contentHash.hex} .art"
390433
391- public def artifactUrl (contentHash : Hash) (service : CacheService) (scope : String) : String :=
434+ public def artifactUrl (contentHash : Hash) (service : CacheService) (scope : String) : String :=
392435 if service.isReservoir then
393436 let endpoint :=
394437 if service.repoScope then
@@ -435,7 +478,7 @@ public def downloadOutputArtifacts
435478 (map : CacheMap) (cache : Cache) (service : CacheService)
436479 (localScope remoteScope : String) (force := false )
437480: LoggerIO Unit := do
438- cache.writeMap localScope map
481+ cache.writeMap localScope map service.name?
439482 let descrs ← map.collectOutputDescrs
440483 service.downloadArtifacts descrs cache remoteScope force
441484
0 commit comments