/- Copyright (c) 2023 Lean FRO, LLC. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Sebastian Ullrich, Marc Huisinga -/ prelude import Init.System.IO import Lean.Server.Utils import Lean.Util.FileSetupInfo import Lean.Util.LakePath import Lean.LoadDynlib namespace Lean.Server.FileWorker open IO structure LakeSetupFileOutput where spawnArgs : Process.SpawnArgs exitCode : UInt32 stdout : String stderr : String partial def runLakeSetupFile (m : DocumentMeta) (lakePath filePath : System.FilePath) (imports : Array Import) (handleStderr : String → IO Unit) : IO LakeSetupFileOutput := do let mut args := #["setup-file", filePath.toString] ++ imports.map (toString ·.module) if m.dependencyBuildMode matches .never then args := args.push "--no-build" |>.push "--no-cache" let spawnArgs : Process.SpawnArgs := { stdin := Process.Stdio.null stdout := Process.Stdio.piped stderr := Process.Stdio.piped cmd := lakePath.toString args } let lakeProc ← Process.spawn spawnArgs let rec processStderr (acc : String) : IO String := do let line ← lakeProc.stderr.getLine if line == "" then return acc else handleStderr line processStderr (acc ++ line) let stderr ← IO.asTask (processStderr "") Task.Priority.dedicated let stdout := String.trim (← lakeProc.stdout.readToEnd) let stderr ← IO.ofExcept stderr.get let exitCode ← lakeProc.wait return ⟨spawnArgs, exitCode, stdout, stderr⟩ /-- Categorizes possible outcomes of running `lake setup-file`. -/ inductive FileSetupResultKind where /-- File configuration loaded and dependencies updated successfully. -/ | success /-- No Lake project found, no setup was done. -/ | noLakefile /-- Imports must be rebuilt but `--no-build` was specified. -/ | importsOutOfDate /-- Other error during Lake invocation. -/ | error (msg : String) /-- Result of running `lake setup-file`. -/ structure FileSetupResult where /-- Kind of outcome. -/ kind : FileSetupResultKind /-- Search path from successful setup, or else empty. -/ srcSearchPath : SearchPath /-- Additional options from successful setup, or else empty. -/ fileOptions : Options /-- Lean plugins from successful setup, or else empty. -/ plugins : Array System.FilePath def FileSetupResult.ofSuccess (pkgSearchPath : SearchPath) (fileOptions : Options) (plugins : Array System.FilePath) : IO FileSetupResult := do return { kind := FileSetupResultKind.success srcSearchPath := ← initSrcSearchPath pkgSearchPath, fileOptions, plugins } def FileSetupResult.ofNoLakefile : IO FileSetupResult := do return { kind := FileSetupResultKind.noLakefile srcSearchPath := ← initSrcSearchPath fileOptions := Options.empty plugins := #[] } def FileSetupResult.ofImportsOutOfDate : IO FileSetupResult := do return { kind := FileSetupResultKind.importsOutOfDate srcSearchPath := ← initSrcSearchPath fileOptions := Options.empty plugins := #[] } def FileSetupResult.ofError (msg : String) : IO FileSetupResult := do return { kind := FileSetupResultKind.error msg srcSearchPath := ← initSrcSearchPath fileOptions := Options.empty plugins := #[] } /-- Uses `lake setup-file` to compile dependencies on the fly and add them to `LEAN_PATH`. Compilation progress is reported to `handleStderr`. Returns the search path for source files and the options for the file. -/ partial def setupFile (m : DocumentMeta) (imports : Array Import) (handleStderr : String → IO Unit) : IO FileSetupResult := do let some filePath := System.Uri.fileUriToPath? m.uri | return ← FileSetupResult.ofNoLakefile -- untitled files have no lakefile -- NOTE: we assume for now that `lakefile.lean` does not have any non-core-Lean deps -- NOTE: lake does not exist in stage 0 (yet?) if filePath.fileName == "lakefile.lean" then return ← FileSetupResult.ofNoLakefile -- the lakefile itself has no lakefile let lakePath ← determineLakePath if !(← System.FilePath.pathExists lakePath) then return ← FileSetupResult.ofNoLakefile let result ← runLakeSetupFile m lakePath filePath imports handleStderr let cmdStr := " ".intercalate (toString result.spawnArgs.cmd :: result.spawnArgs.args.toList) match result.exitCode with | 0 => let Except.ok (info : FileSetupInfo) := Json.parse result.stdout >>= fromJson? | return ← FileSetupResult.ofError s!"Invalid output from `{cmdStr}`:\n{result.stdout}\nstderr:\n{result.stderr}" initSearchPath (← getBuildDir) info.paths.oleanPath info.paths.loadDynlibPaths.forM loadDynlib let pkgSearchPath ← info.paths.srcPath.mapM realPathNormalized let pluginPaths ← info.paths.pluginPaths.mapM realPathNormalized FileSetupResult.ofSuccess pkgSearchPath info.setupOptions.toOptions pluginPaths | 2 => -- exit code for lake reporting that there is no lakefile FileSetupResult.ofNoLakefile | 3 => -- exit code for `--no-build` FileSetupResult.ofImportsOutOfDate | _ => FileSetupResult.ofError s!"`{cmdStr}` failed:\n{result.stdout}\nstderr:\n{result.stderr}"