feat: server support for new module setup (#8699)
This PR adds support to the server for the new module setup process by changing how `lake setup-file` is used. In the new server setup, `lake setup-file` is invoked with the file name of the edited module passed as a CLI argument and with the parsed header passed to standard input in JSON form. Standard input is used to avoid potentially exceeding the CLI length limits on Windows. Lake will build the module's imports along with any other dependencies and then return the module's workspace configuration via JSON (now in the form of `ModuleSetup`). The server then post-processes this configuration a bit and returns it back to the Lean language processor. The server's header is currently only fully respected by Lake for external modules (files that are not part of any workspace library). For workspace modules, the saved module header is currently used to build imports (as has been done since #7909). A follow-up Lake PR will align both cases to follow the server's header. Lean search paths (e.g., `LEAN_PATH`, `LEAN_SRC_PATH`) are no longer negotiated between the server and Lake. These environment variables are already configured during sever setup by `lake serve` and do not change on a per-file basis. Lake can also pre-resolve the `.olean` files of imports via the `importArts` field of `ModuleSetup`, limiting the potential utility of communicating `LEAN_PATH`.
This commit is contained in:
parent
e0a793ae20
commit
dd64678f07
20 changed files with 157 additions and 177 deletions
|
|
@ -158,10 +158,10 @@ def runFrontend
|
|||
return .ok {
|
||||
trustLevel
|
||||
mainModuleName := setup.name
|
||||
isModule := setup.isModule
|
||||
imports := setup.imports
|
||||
isModule := strictOr setup.isModule stx.isModule
|
||||
imports := setup.imports?.getD stx.imports
|
||||
plugins := plugins ++ setup.plugins
|
||||
modules := setup.modules
|
||||
importArts := setup.importArts
|
||||
-- override cmdline options with setup options
|
||||
opts := opts.mergeBy (fun _ _ hOpt => hOpt) setup.options.toOptions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ Authors: Leonardo de Moura, Sebastian Ullrich
|
|||
-/
|
||||
prelude
|
||||
import Lean.Parser.Module
|
||||
import Lean.Util.Paths
|
||||
import Lean.CoreM
|
||||
|
||||
namespace Lean.Elab
|
||||
|
|
@ -29,13 +28,17 @@ def HeaderSyntax.imports (stx : HeaderSyntax) (includeInit : Bool := true) : Arr
|
|||
| _ => unreachable!
|
||||
| _ => unreachable!
|
||||
|
||||
def HeaderSyntax.toModuleHeader (stx : HeaderSyntax) : ModuleHeader where
|
||||
isModule := stx.isModule
|
||||
imports := stx.imports
|
||||
|
||||
abbrev headerToImports := @HeaderSyntax.imports
|
||||
|
||||
def processHeaderCore
|
||||
(startPos : String.Pos) (imports : Array Import) (isModule : Bool)
|
||||
(opts : Options) (messages : MessageLog) (inputCtx : Parser.InputContext)
|
||||
(trustLevel : UInt32 := 0) (plugins : Array System.FilePath := #[]) (leakEnv := false)
|
||||
(mainModule := Name.anonymous) (arts : NameMap ModuleArtifacts := {})
|
||||
(mainModule := Name.anonymous) (arts : NameMap ImportArtifacts := {})
|
||||
: IO (Environment × MessageLog) := do
|
||||
let level := if isModule then
|
||||
if Elab.inServer.get opts then
|
||||
|
|
|
|||
|
|
@ -1876,18 +1876,6 @@ abbrev ImportStateM := StateRefT ImportState IO
|
|||
@[inline] nonrec def ImportStateM.run (x : ImportStateM α) (s : ImportState := {}) : IO (α × ImportState) :=
|
||||
x.run s
|
||||
|
||||
def ModuleArtifacts.oleanParts (arts : ModuleArtifacts) : Array System.FilePath := Id.run do
|
||||
let mut fnames := #[]
|
||||
-- Opportunistically load all available parts.
|
||||
-- Producer (e.g., Lake) should limit parts to the proper import level.
|
||||
if let some mFile := arts.olean? then
|
||||
fnames := fnames.push mFile
|
||||
if let some sFile := arts.oleanServer? then
|
||||
fnames := fnames.push sFile
|
||||
if let some pFile := arts.oleanPrivate? then
|
||||
fnames := fnames.push pFile
|
||||
return fnames
|
||||
|
||||
private def findOLeanParts (mod : Name) : IO (Array System.FilePath) := do
|
||||
let mFile ← findOLean mod
|
||||
unless (← mFile.pathExists) do
|
||||
|
|
@ -1904,7 +1892,7 @@ private def findOLeanParts (mod : Name) : IO (Array System.FilePath) := do
|
|||
return fnames
|
||||
|
||||
partial def importModulesCore
|
||||
(imports : Array Import) (isModule := false) (arts : NameMap ModuleArtifacts := {}) :
|
||||
(imports : Array Import) (isModule := false) (arts : NameMap ImportArtifacts := {}) :
|
||||
ImportStateM Unit := do
|
||||
go imports (importAll := true) (isExported := isModule) (isMeta := false)
|
||||
if isModule then
|
||||
|
|
@ -1977,10 +1965,9 @@ where go (imports : Array Import) (importAll isExported isMeta : Bool) := do
|
|||
continue
|
||||
let fnames ←
|
||||
if let some arts := arts.find? i.module then
|
||||
let fnames := arts.oleanParts
|
||||
if fnames.isEmpty then
|
||||
findOLeanParts i.module
|
||||
else pure fnames
|
||||
-- Opportunistically load all available parts.
|
||||
-- Producer (e.g., Lake) should limit parts to the proper import level.
|
||||
pure arts.oleanParts
|
||||
else
|
||||
findOLeanParts i.module
|
||||
let parts ← readModuleDataParts fnames
|
||||
|
|
@ -2146,7 +2133,7 @@ as if no `module` annotations were present in the imports.
|
|||
-/
|
||||
def importModules (imports : Array Import) (opts : Options) (trustLevel : UInt32 := 0)
|
||||
(plugins : Array System.FilePath := #[]) (leakEnv := false) (loadExts := false)
|
||||
(level := OLeanLevel.private) (arts : NameMap ModuleArtifacts := {})
|
||||
(level := OLeanLevel.private) (arts : NameMap ImportArtifacts := {})
|
||||
: IO Environment := profileitIO "import" opts do
|
||||
for imp in imports do
|
||||
if imp.module matches .anonymous then
|
||||
|
|
|
|||
|
|
@ -291,8 +291,8 @@ structure SetupImportsResult where
|
|||
opts : Options
|
||||
/-- Kernel trust level. -/
|
||||
trustLevel : UInt32 := 0
|
||||
/-- Pre-resolved artifacts of related modules (e.g., this module's transitive imports). -/
|
||||
modules : NameMap ModuleArtifacts := {}
|
||||
/-- Pre-resolved artifacts of transitively imported modules. -/
|
||||
importArts : NameMap ImportArtifacts := {}
|
||||
/-- Lean plugins to load as part of the environment setup. -/
|
||||
plugins : Array System.FilePath := #[]
|
||||
|
||||
|
|
@ -491,7 +491,7 @@ where
|
|||
-- allows `headerEnv` to be leaked, which would live until the end of the process anyway
|
||||
let (headerEnv, msgLog) ← Elab.processHeaderCore (leakEnv := true)
|
||||
stx.startPos setup.imports setup.isModule setup.opts .empty ctx.toInputContext
|
||||
setup.trustLevel setup.plugins setup.mainModuleName setup.modules
|
||||
setup.trustLevel setup.plugins setup.mainModuleName setup.importArts
|
||||
let stopTime := (← IO.monoNanosNow).toFloat / 1000000000
|
||||
let diagnostics := (← Snapshot.Diagnostics.ofMessageLog msgLog)
|
||||
if msgLog.hasErrors then
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ Authors: Marc Huisinga
|
|||
-/
|
||||
prelude
|
||||
import Lean.Data.NameTrie
|
||||
import Lean.Util.Paths
|
||||
import Lean.Util.LakePath
|
||||
import Lean.Server.Completion.CompletionItemData
|
||||
import Lean.Parser.Module
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import Lean.Environment
|
|||
import Lean.Data.Lsp
|
||||
import Lean.Data.Json.FromToJson
|
||||
|
||||
import Lean.Util.FileSetupInfo
|
||||
import Lean.LoadDynlib
|
||||
import Lean.Language.Lean
|
||||
|
||||
|
|
@ -372,7 +371,7 @@ def setupImports
|
|||
(doc : DocumentMeta)
|
||||
(cmdlineOpts : Options)
|
||||
(chanOut : Std.Channel JsonRpc.Message)
|
||||
(stx : TSyntax ``Parser.Module.header)
|
||||
(stx : Elab.HeaderSyntax)
|
||||
: Language.ProcessingT IO (Except Language.Lean.HeaderProcessedSnapshot SetupImportsResult) := do
|
||||
let importsAlreadyLoaded ← importsLoadedRef.modifyGet ((·, true))
|
||||
if importsAlreadyLoaded then
|
||||
|
|
@ -384,9 +383,9 @@ def setupImports
|
|||
-- should not be visible to user as task is already canceled
|
||||
return .error { diagnostics := .empty, result? := none, metaSnap := default }
|
||||
|
||||
let header := stx.toModuleHeader
|
||||
chanOut.sync.send <| mkInitialIleanInfoUpdateNotification doc <| collectImports stx
|
||||
let imports := Elab.headerToImports stx
|
||||
let fileSetupResult ← setupFile doc imports fun stderrLine => do
|
||||
let fileSetupResult ← setupFile doc header fun stderrLine => do
|
||||
let progressDiagnostic := {
|
||||
range := ⟨⟨0, 0⟩, ⟨1, 0⟩⟩
|
||||
-- make progress visible anywhere in the file
|
||||
|
|
@ -414,8 +413,10 @@ def setupImports
|
|||
}
|
||||
| _ => pure ()
|
||||
|
||||
let setup := fileSetupResult.setup
|
||||
|
||||
-- override cmdline options with file options
|
||||
let opts := cmdlineOpts.mergeBy (fun _ _ fileOpt => fileOpt) fileSetupResult.fileOptions
|
||||
let opts := cmdlineOpts.mergeBy (fun _ _ fileOpt => fileOpt) setup.options.toOptions
|
||||
|
||||
-- default to async elaboration; see also `Elab.async` docs
|
||||
let opts := Elab.async.setIfNotSet opts true
|
||||
|
|
@ -424,10 +425,11 @@ def setupImports
|
|||
|
||||
return .ok {
|
||||
mainModuleName := doc.mod
|
||||
isModule := Elab.HeaderSyntax.isModule stx
|
||||
imports
|
||||
isModule := strictOr setup.isModule header.isModule
|
||||
imports := setup.imports?.getD header.imports
|
||||
opts
|
||||
plugins := fileSetupResult.plugins
|
||||
importArts := setup.importArts
|
||||
plugins := setup.plugins
|
||||
}
|
||||
|
||||
/- Worker initialization sequence. -/
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ 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
|
||||
import Lean.Server.ServerTask
|
||||
|
|
@ -24,20 +23,22 @@ structure LakeSetupFileOutput where
|
|||
partial def runLakeSetupFile
|
||||
(m : DocumentMeta)
|
||||
(lakePath filePath : System.FilePath)
|
||||
(imports : Array Import)
|
||||
(header : ModuleHeader)
|
||||
(handleStderr : String → IO Unit)
|
||||
: IO LakeSetupFileOutput := do
|
||||
let mut args := #["setup-file", filePath.toString] ++ imports.map (toString ·.module)
|
||||
let mut args := #["setup-file", filePath.toString, "-"]
|
||||
if m.dependencyBuildMode matches .never then
|
||||
args := args.push "--no-build" |>.push "--no-cache"
|
||||
let spawnArgs : Process.SpawnArgs := {
|
||||
stdin := Process.Stdio.null
|
||||
stdin := Process.Stdio.piped
|
||||
stdout := Process.Stdio.piped
|
||||
stderr := Process.Stdio.piped
|
||||
cmd := lakePath.toString
|
||||
args
|
||||
}
|
||||
let lakeProc ← Process.spawn spawnArgs
|
||||
let (stdin, lakeProc) ← lakeProc.takeStdin
|
||||
stdin.putStrLn (toJson header).compress
|
||||
|
||||
let rec processStderr (acc : String) : IO String := do
|
||||
let line ← lakeProc.stderr.getLine
|
||||
|
|
@ -68,61 +69,53 @@ inductive FileSetupResultKind where
|
|||
structure FileSetupResult where
|
||||
/-- Kind of outcome. -/
|
||||
kind : FileSetupResultKind
|
||||
/-- Additional options from successful setup, or else empty. -/
|
||||
fileOptions : Options
|
||||
/-- Lean plugins from successful setup, or else empty. -/
|
||||
plugins : Array System.FilePath
|
||||
/-- Configuration from a successful setup, or else the default. -/
|
||||
setup : ModuleSetup
|
||||
|
||||
def FileSetupResult.ofSuccess (fileOptions : Options)
|
||||
(plugins : Array System.FilePath) : IO FileSetupResult := do return {
|
||||
def FileSetupResult.ofSuccess (setup : ModuleSetup) : IO FileSetupResult := do return {
|
||||
kind := FileSetupResultKind.success
|
||||
fileOptions, plugins
|
||||
setup
|
||||
}
|
||||
|
||||
def FileSetupResult.ofNoLakefile : IO FileSetupResult := do return {
|
||||
def FileSetupResult.ofNoLakefile (m : DocumentMeta) (header : ModuleHeader) : IO FileSetupResult := do return {
|
||||
kind := FileSetupResultKind.noLakefile
|
||||
fileOptions := Options.empty
|
||||
plugins := #[]
|
||||
setup := {name := m.mod, isModule := header.isModule}
|
||||
}
|
||||
|
||||
def FileSetupResult.ofImportsOutOfDate : IO FileSetupResult := do return {
|
||||
def FileSetupResult.ofImportsOutOfDate (m : DocumentMeta) (header : ModuleHeader) : IO FileSetupResult := do return {
|
||||
kind := FileSetupResultKind.importsOutOfDate
|
||||
fileOptions := Options.empty
|
||||
plugins := #[]
|
||||
setup := {name := m.mod, isModule := header.isModule}
|
||||
}
|
||||
|
||||
def FileSetupResult.ofError (msg : String) : IO FileSetupResult := do return {
|
||||
def FileSetupResult.ofError (m : DocumentMeta) (header : ModuleHeader) (msg : String) : IO FileSetupResult := do return {
|
||||
kind := FileSetupResultKind.error msg
|
||||
fileOptions := Options.empty
|
||||
plugins := #[]
|
||||
setup := {name := m.mod, isModule := header.isModule}
|
||||
}
|
||||
|
||||
/-- 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
|
||||
partial def setupFile (m : DocumentMeta) (header : ModuleHeader) (handleStderr : String → IO Unit) : IO FileSetupResult := do
|
||||
let some filePath := System.Uri.fileUriToPath? m.uri
|
||||
| return ← FileSetupResult.ofNoLakefile -- untitled files have no lakefile
|
||||
| return ← FileSetupResult.ofNoLakefile m header -- untitled files have no lakefile
|
||||
|
||||
let lakePath ← determineLakePath
|
||||
if !(← System.FilePath.pathExists lakePath) then
|
||||
return ← FileSetupResult.ofNoLakefile
|
||||
return ← FileSetupResult.ofNoLakefile m header
|
||||
|
||||
let result ← runLakeSetupFile m lakePath filePath imports handleStderr
|
||||
let result ← runLakeSetupFile m lakePath filePath header 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 pluginPaths ← info.paths.pluginPaths.mapM realPathNormalized
|
||||
FileSetupResult.ofSuccess info.setupOptions.toOptions pluginPaths
|
||||
let Except.ok (setup : ModuleSetup) := Json.parse result.stdout >>= fromJson?
|
||||
| return ← FileSetupResult.ofError m header s!"Invalid output from `{cmdStr}`:\n{result.stdout}\nstderr:\n{result.stderr}"
|
||||
setup.dynlibs.forM loadDynlib
|
||||
FileSetupResult.ofSuccess setup
|
||||
| 2 => -- exit code for lake reporting that there is no lakefile
|
||||
FileSetupResult.ofNoLakefile
|
||||
FileSetupResult.ofNoLakefile m header
|
||||
| 3 => -- exit code for `--no-build`
|
||||
FileSetupResult.ofImportsOutOfDate
|
||||
FileSetupResult.ofImportsOutOfDate m header
|
||||
| _ =>
|
||||
FileSetupResult.ofError s!"`{cmdStr}` failed:\n{result.stdout}\nstderr:\n{result.stderr}"
|
||||
FileSetupResult.ofError m header s!"`{cmdStr}` failed:\n{result.stdout}\nstderr:\n{result.stderr}"
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ import Std.Data.TreeMap
|
|||
import Init.Data.ByteArray
|
||||
import Lean.Data.RBMap
|
||||
|
||||
import Lean.Util.Paths
|
||||
|
||||
import Lean.Data.FuzzyMatching
|
||||
import Lean.Data.Json
|
||||
import Lean.Data.Lsp
|
||||
|
|
|
|||
|
|
@ -38,6 +38,26 @@ structure ModuleHeader where
|
|||
isModule : Bool
|
||||
deriving Repr, Inhabited, ToJson, FromJson
|
||||
|
||||
/--
|
||||
Module data files used for an `import` statement.
|
||||
This structure is designed for efficient JSON serialization.
|
||||
-/
|
||||
structure ImportArtifacts where
|
||||
oleanParts : Array System.FilePath
|
||||
deriving Repr, Inhabited
|
||||
|
||||
instance : ToJson ImportArtifacts := ⟨(toJson ·.oleanParts)⟩
|
||||
instance : FromJson ImportArtifacts := ⟨(.mk <$> fromJson? ·)⟩
|
||||
|
||||
def ImportArtifacts.olean? (arts : ImportArtifacts) :=
|
||||
arts.oleanParts[0]?
|
||||
|
||||
def ImportArtifacts.oleanServer? (arts : ImportArtifacts) :=
|
||||
arts.oleanParts[1]?
|
||||
|
||||
def ImportArtifacts.oleanPrivate? (arts : ImportArtifacts) :=
|
||||
arts.oleanParts[2]?
|
||||
|
||||
/-- Files containing data for a single module. -/
|
||||
structure ModuleArtifacts where
|
||||
lean? : Option System.FilePath := none
|
||||
|
|
@ -49,19 +69,32 @@ structure ModuleArtifacts where
|
|||
bc? : Option System.FilePath := none
|
||||
deriving Repr, Inhabited, ToJson, FromJson
|
||||
|
||||
/--
|
||||
A module's setup information as described by a JSON file.
|
||||
Supersedes the module's header when the `--setup` CLI option is used.
|
||||
-/
|
||||
def ModuleArtifacts.oleanParts (arts : ModuleArtifacts) : Array System.FilePath := Id.run do
|
||||
let mut fnames := #[]
|
||||
if let some mFile := arts.olean? then
|
||||
fnames := fnames.push mFile
|
||||
if let some sFile := arts.oleanServer? then
|
||||
fnames := fnames.push sFile
|
||||
if let some pFile := arts.oleanPrivate? then
|
||||
fnames := fnames.push pFile
|
||||
return fnames
|
||||
|
||||
/-- A module's setup information as described by a JSON file. -/
|
||||
structure ModuleSetup where
|
||||
/-- Name of the module. -/
|
||||
name : Name
|
||||
/-- Whether the module is participating in the module system. -/
|
||||
/--
|
||||
Whether the module, by default, participates in the module system.
|
||||
Even if `false`, a module can still choose to participate by using `module` in its header.
|
||||
-/
|
||||
isModule : Bool := false
|
||||
/-- The module's direct imports. -/
|
||||
imports : Array Import := #[]
|
||||
/-- Pre-resolved artifacts of related modules (e.g., this module's transitive imports). -/
|
||||
modules : NameMap ModuleArtifacts := {}
|
||||
/--
|
||||
The module's direct imports.
|
||||
If `none`, uses the imports from the module header.
|
||||
-/
|
||||
imports? : Option (Array Import) := none
|
||||
/-- Pre-resolved artifacts of transitively imported modules. -/
|
||||
importArts : NameMap ImportArtifacts := {}
|
||||
/-- Dynamic libraries to load with the module. -/
|
||||
dynlibs : Array System.FilePath := #[]
|
||||
/-- Plugins to initialize with the module. -/
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import Lean.Util.SCC
|
|||
import Lean.Util.TestExtern
|
||||
import Lean.Util.OccursCheck
|
||||
import Lean.Util.HasConstCache
|
||||
import Lean.Util.FileSetupInfo
|
||||
import Lean.Util.Heartbeats
|
||||
import Lean.Util.SearchPath
|
||||
import Lean.Util.SafeExponentiation
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
/-
|
||||
Copyright (c) 2023 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Marc Huisinga
|
||||
-/
|
||||
prelude
|
||||
import Lean.Util.LeanOptions
|
||||
|
||||
/--
|
||||
Information shared between Lake and Lean when calling `lake setup-file`.
|
||||
-/
|
||||
structure Lean.FileSetupInfo where
|
||||
paths : LeanPaths
|
||||
setupOptions : LeanOptions
|
||||
deriving FromJson, ToJson
|
||||
|
|
@ -4,7 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
|
|||
Authors: Marc Huisinga
|
||||
-/
|
||||
prelude
|
||||
import Lean.Util.Paths
|
||||
import Lean.Data.Json
|
||||
|
||||
namespace Lean
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
/-
|
||||
Copyright (c) 2021 Sebastian Ullrich. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
|
||||
Authors: Sebastian Ullrich
|
||||
-/
|
||||
prelude
|
||||
import Lean.Data.Json
|
||||
import Lean.Util.Path
|
||||
|
||||
/-! Communicating Lean search paths between processes -/
|
||||
|
||||
namespace Lean
|
||||
|
||||
open System
|
||||
|
||||
structure LeanPaths where
|
||||
oleanPath : SearchPath
|
||||
srcPath : SearchPath
|
||||
loadDynlibPaths : Array FilePath := #[]
|
||||
pluginPaths : Array FilePath := #[]
|
||||
deriving ToJson, FromJson
|
||||
|
||||
end Lean
|
||||
|
|
@ -266,8 +266,8 @@ def Module.recFetchSetup (mod : Module) : FetchM (Job ModuleSetup) := ensureJob
|
|||
return {
|
||||
name := mod.name
|
||||
isModule := header.isModule
|
||||
imports := header.imports
|
||||
modules := {} -- TODO: modules
|
||||
imports? := none
|
||||
importArts := {} -- TODO
|
||||
dynlibs := dynlibs.map (·.path.toString)
|
||||
plugins := plugins.map (·.path.toString)
|
||||
options := mod.leanOptions
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import Lake.CLI.Serve
|
|||
-- # CLI
|
||||
|
||||
open System
|
||||
open Lean (Json toJson fromJson? LeanPaths NameMap)
|
||||
open Lean (Json toJson fromJson? NameMap)
|
||||
|
||||
namespace Lake
|
||||
|
||||
|
|
@ -445,10 +445,14 @@ protected def setupFile : CliM PUnit := do
|
|||
let loadConfig ← mkLoadConfig opts
|
||||
let buildConfig := mkBuildConfig opts
|
||||
let filePath ← takeArg "file path"
|
||||
-- Additional arguments (imports) are ignored
|
||||
-- TODO: Forbid them once the language server is updated
|
||||
-- noArgsRem do
|
||||
setupFile loadConfig filePath buildConfig
|
||||
let header? ← takeArg?
|
||||
noArgsRem do
|
||||
let header ← header?.mapM fun header => do
|
||||
let header ← if header == "-" then IO.getStdin >>= (·.getLine) else pure header
|
||||
match Json.parse header >>= fromJson? with
|
||||
| .ok header => pure header
|
||||
| .error e => error s!"failed to parse header JSON: {e}"
|
||||
setupFile loadConfig filePath header buildConfig
|
||||
|
||||
protected def test : CliM PUnit := do
|
||||
processOptions lakeOption
|
||||
|
|
@ -544,8 +548,8 @@ private def evalLeanFile
|
|||
withRegisterJob s!"{mod.name}:setup" do mod.setup.fetch
|
||||
mkArgs path setup (some mod.rootDir) mod.leanArgs
|
||||
else
|
||||
let setup ← mkModuleSetup
|
||||
ws leanFile.toString (← IO.FS.readFile path) ws.leanOptions buildConfig
|
||||
let header ← Lean.parseImports' (← IO.FS.readFile path) leanFile.toString
|
||||
let setup ← mkModuleSetup ws leanFile.toString header ws.leanOptions buildConfig
|
||||
mkArgs path setup none ws.root.moreLeanArgs
|
||||
let spawnArgs : IO.Process.SpawnArgs := {
|
||||
args := args ++ moreArgs
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ prelude
|
|||
import Lake.Load
|
||||
import Lake.Build
|
||||
import Lake.Util.MainM
|
||||
import Lean.Util.FileSetupInfo
|
||||
|
||||
open Lean
|
||||
open System (FilePath)
|
||||
|
|
@ -23,52 +22,46 @@ and falls back to plain `lean --server`.
|
|||
-/
|
||||
def invalidConfigEnvVar := "LAKE_INVALID_CONFIG"
|
||||
|
||||
private def mkLeanPaths (ws : Workspace) (setup : ModuleSetup) : LeanPaths where
|
||||
oleanPath := ws.leanPath
|
||||
srcPath := ws.leanSrcPath
|
||||
loadDynlibPaths := setup.dynlibs
|
||||
pluginPaths := setup.plugins
|
||||
|
||||
def mkModuleSetup
|
||||
(ws : Workspace) (fileName : String) (input : String) (opts : LeanOptions)
|
||||
(ws : Workspace) (fileName : String) (header : ModuleHeader) (opts : LeanOptions)
|
||||
(buildConfig : BuildConfig)
|
||||
: IO ModuleSetup := do
|
||||
let header ← Lean.parseImports' input fileName
|
||||
let {dynlibs, plugins} ← ws.runBuild (buildImportsAndDeps fileName header.imports) buildConfig
|
||||
return {
|
||||
name := ← Lean.moduleNameOfFileName fileName none
|
||||
isModule := header.isModule
|
||||
imports := header.imports
|
||||
modules := {} -- TODO
|
||||
imports? := none
|
||||
importArts := {} -- TODO
|
||||
dynlibs := dynlibs.map (·.path.toString)
|
||||
plugins := plugins.map (·.path.toString)
|
||||
options := opts
|
||||
}
|
||||
|
||||
/--
|
||||
Build a list of imports of a file and print the `.olean` and source directories
|
||||
of every used package, as well as the server options for the file.
|
||||
If no configuration file exists, exit silently with `noConfigFileCode` (i.e, 2).
|
||||
Build the dependencies of a Lean file and print the computed module's setup as JSON.
|
||||
If `header?` is not not `none`, it will be used to determine imports instead of the
|
||||
file's own header (for modules external to the workspace).
|
||||
|
||||
Requires a configuration file to succeed. If no configuration file exists, it
|
||||
will exit silently with `noConfigFileCode` (i.e, 2).
|
||||
|
||||
The `setup-file` command is used internally by the Lean 4 server.
|
||||
-/
|
||||
-- TODO: Use `header?` for modules within the workspace as well.
|
||||
def setupFile
|
||||
(loadConfig : LoadConfig) (path : FilePath) (buildConfig : BuildConfig := {})
|
||||
(loadConfig : LoadConfig) (leanFile : FilePath)
|
||||
(header? : Option ModuleHeader := none) (buildConfig : BuildConfig := {})
|
||||
: MainM PUnit := do
|
||||
let path ← resolvePath path
|
||||
let path ← resolvePath leanFile
|
||||
let configFile ← realConfigFile loadConfig.configFile
|
||||
if configFile.toString.isEmpty then
|
||||
exit noConfigFileCode
|
||||
else if configFile == path then do
|
||||
let info : FileSetupInfo := {
|
||||
paths := {
|
||||
oleanPath := loadConfig.lakeEnv.leanPath
|
||||
srcPath := loadConfig.lakeEnv.leanSrcPath
|
||||
pluginPaths := #[loadConfig.lakeEnv.lake.sharedLib]
|
||||
}
|
||||
setupOptions := ⟨∅⟩
|
||||
let setup : ModuleSetup := {
|
||||
name := configModuleName
|
||||
plugins := #[loadConfig.lakeEnv.lake.sharedLib]
|
||||
}
|
||||
IO.println (toJson info).compress
|
||||
IO.println (toJson setup).compress
|
||||
else if let some errLog := (← IO.getEnv invalidConfigEnvVar) then
|
||||
IO.eprint errLog
|
||||
IO.eprintln s!"Failed to configure the Lake workspace. Please restart the server after fixing the error above."
|
||||
|
|
@ -79,19 +72,13 @@ def setupFile
|
|||
if let some mod := ws.findModuleBySrc? path then
|
||||
let setup ← ws.runBuild (cfg := buildConfig) do
|
||||
withRegisterJob s!"{mod.name}:setup" do mod.setup.fetch
|
||||
let info : FileSetupInfo := {
|
||||
paths := mkLeanPaths ws setup
|
||||
setupOptions := mod.serverOptions
|
||||
}
|
||||
IO.println (toJson info).compress
|
||||
IO.println (toJson setup).compress
|
||||
else
|
||||
let header ← header?.getDM do
|
||||
Lean.parseImports' (← IO.FS.readFile path) leanFile.toString
|
||||
let setup ← mkModuleSetup
|
||||
ws path.toString (← IO.FS.readFile path) ws.serverOptions buildConfig
|
||||
let info : FileSetupInfo := {
|
||||
paths := mkLeanPaths ws setup
|
||||
setupOptions := setup.options
|
||||
}
|
||||
IO.println (toJson info).compress
|
||||
ws leanFile.toString header ws.serverOptions buildConfig
|
||||
IO.println (toJson setup).compress
|
||||
|
||||
/--
|
||||
Start the Lean LSP for the `Workspace` loaded from `config`
|
||||
|
|
@ -102,8 +89,7 @@ def serve (config : LoadConfig) (args : Array String) : IO UInt32 := do
|
|||
let (ws?, log) ← (loadWorkspace config).captureLog
|
||||
log.replay (logger := MonadLog.stderr)
|
||||
if let some ws := ws? then
|
||||
let ctx := mkLakeContext ws
|
||||
pure (← LakeT.run ctx getAugmentedEnv, ws.root.moreGlobalServerArgs)
|
||||
pure (ws.augmentedEnvVars, ws.root.moreGlobalServerArgs)
|
||||
else
|
||||
IO.eprintln "warning: package configuration has errors, falling back to plain `lean --server`"
|
||||
pure (config.lakeEnv.baseVars.push (invalidConfigEnvVar, log.toString), #[])
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ Released under Apache 2.0 license as described in the file LICENSE.
|
|||
Authors: Mac Malone
|
||||
-/
|
||||
prelude
|
||||
import Lean.Util.Paths
|
||||
import Lake.Config.FacetConfig
|
||||
import Lake.Config.TargetConfig
|
||||
import Lake.Config.Env
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ $LAKE -d foo build --update
|
|||
./bar/.lake/build/bin/bar
|
||||
|
||||
# Test setup-file works (i.e., does not error)
|
||||
$LAKE -d foo setup-file ./foo/Foo.lean A B
|
||||
$LAKE -d foo setup-file ./foo/Foo.lean
|
||||
|
||||
# Test `lake clean`
|
||||
test -d a/.lake/build
|
||||
|
|
|
|||
|
|
@ -8,24 +8,42 @@ source ../common.sh
|
|||
#---
|
||||
|
||||
# Test that, by default, no plugins are used.
|
||||
test_out '"pluginPaths":[]' setup-file ImportFoo.lean
|
||||
test_out '"plugins":[]' setup-file ImportFoo.lean
|
||||
|
||||
# Test that, by default, no dynlibs are used.
|
||||
test_out '"loadDynlibPaths":[]' setup-file ImportFoo.lean
|
||||
test_out '"dynlibs":[]' setup-file ImportFoo.lean
|
||||
|
||||
# Test that, generally, no options are set.
|
||||
test_out '"setupOptions":{}' setup-file ImportFoo.lean
|
||||
test_out '"options":{}' setup-file ImportFoo.lean
|
||||
|
||||
# Test that a more specific configuration will be used if
|
||||
# Lake can identify the module corresponding to the path.
|
||||
test_out '"setupOptions":{"weak.foo":"bar"}' setup-file Test.lean
|
||||
test_out '"options":{"weak.foo":"bar"}' setup-file Test.lean
|
||||
|
||||
# Test that `setup-file` on an invalid Lean configuration file succeeds.
|
||||
test_run -f invalid.lean setup-file invalid.lean
|
||||
|
||||
# Test that `setup-file` on a configuration file uses the Lake plugin,
|
||||
# even if the file is invalid and/or is not using a `Lake` import.
|
||||
test_not_out '"pluginPaths":[]' -f invalid.lean setup-file invalid.lean
|
||||
test_not_out '"plugins":[]' -f invalid.lean setup-file invalid.lean
|
||||
|
||||
# Test that when a header is provided (via CLI or stdin),
|
||||
# it will be used instead of the file's header for an external module.
|
||||
test_out '"isModule":false' setup-file ImportFoo.lean
|
||||
HEADER_JSON='{"isModule":true,"imports":[]}'
|
||||
test_out '"isModule":true' setup-file ImportFoo.lean "$HEADER_JSON"
|
||||
echo "$HEADER_JSON" | test_out '"isModule":true' setup-file ImportFoo.lean -
|
||||
BOGUS_JSON='{"isModule":false,"imports":[{"module":"Test.Bogus","isMeta":false,"isExported":true,"importAll":false}]}'
|
||||
test_err 'no such file or directory' setup-file ImportFoo.lean "$BOGUS_JSON"
|
||||
|
||||
# Test that when a header is provided (via CLI or stdin),
|
||||
# the header is *NOT* used for an internal module and its imports are not built.
|
||||
# TODO: Use the provided header.
|
||||
test_out '"isModule":false' setup-file Test.lean
|
||||
test_out '"isModule":false' setup-file Test.lean "$HEADER_JSON"
|
||||
echo "$HEADER_JSON" | test_out '"isModule":false' setup-file Test.lean -
|
||||
# If the provided import (Test.Bogus) was built, this would fail.
|
||||
test_run setup-file Test.lean "$BOGUS_JSON"
|
||||
|
||||
# Cleanup
|
||||
rm -f produced.out
|
||||
|
|
|
|||
|
|
@ -9,10 +9,8 @@
|
|||
"isMeta": false
|
||||
}
|
||||
],
|
||||
"modules": {
|
||||
"Dep": {
|
||||
"olean": "Dep.olean"
|
||||
}
|
||||
"importArts": {
|
||||
"Dep": ["Dep.olean"]
|
||||
},
|
||||
"dynlibs": [],
|
||||
"plugins": [],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue