A file in a project is checked against the packages bjo fetched for it: the server installs the project's .bjo/roots, .bjo/frameworks and .bjo/nuget/root before its first check, through the compiler's ProjectInputs, so imports from packages check and definition, hover and completion reach them. A server serves one project, the first file's; a warning on a file's first line says when it cannot serve one as bjo would: packages not fetched, the roots changed since it started, or another project. The server no longer has the old Microsoft.Extensions.DependencyInjection .Abstractions next to it. The compiler excludes it, the exclusion does not pass through a project reference, and with it there every ASP.NET type failed to load, so bjoweb did not check. A diagnostic marks the form at the line and column the compiler names now: an atom or a string whole, a bracketed form to its closing bracket, and one over several lines as far as its head. The build no longer asks git for Source Link, which fails on this repository's SHA-256 objects. 🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-5-5) Co-Authored-By: eca-agent <git@eca.dev>
84 lines
4 KiB
FSharp
84 lines
4 KiB
FSharp
/// The project the server checks against: the packages `bjo` fetched for it,
|
|
/// which of them declared a .NET shared framework, and its NuGet packages.
|
|
///
|
|
/// `bjo` writes these under the project's `.bjo` and passes them to the
|
|
/// compiler as `--roots`, `--frameworks` and `--nuget`. A compiler process
|
|
/// serves one roots file, since every cache keyed by a path would answer from
|
|
/// a stale registry if the roots changed under it. So the server installs one
|
|
/// project's, that of the first file it is asked about, before anything is
|
|
/// resolved, and says so on a file it cannot serve as the command line would.
|
|
module BjolangLsp.Project
|
|
|
|
open System
|
|
open System.IO
|
|
|
|
/// The nearest directory at or above `dir` that holds a `manifest.bjodat`.
|
|
let rec private containing (dir: string) : string option =
|
|
if isNull dir then None
|
|
elif File.Exists(Path.Combine(dir, "manifest.bjodat")) then Some dir
|
|
else containing (Path.GetDirectoryName dir)
|
|
|
|
/// The project a file is in, if any.
|
|
let projectOf (path: string) : string option =
|
|
containing (Path.GetDirectoryName(Path.GetFullPath path))
|
|
|
|
let private rootsFile (project: string) = Path.Combine(project, ".bjo", "roots")
|
|
|
|
let private writtenAt (file: string) =
|
|
if File.Exists file then Some(File.GetLastWriteTimeUtc file) else None
|
|
|
|
type private State =
|
|
/// Nothing asked about yet.
|
|
| Undecided
|
|
/// The project whose inputs are installed, and when its roots file was
|
|
/// written then. `None` for a file in no project, or in a project whose
|
|
/// packages were not fetched: the standard library only.
|
|
| Serving of project: string option * roots: DateTime option
|
|
/// The inputs did not install, and why.
|
|
| Failed of project: string * message: string
|
|
|
|
let mutable private state = Undecided
|
|
|
|
/// Installs the inputs of the project `path` is in, the first time it is
|
|
/// called, and answers what the file's diagnostics should say about it: why
|
|
/// its packages are not seen as the command line sees them, or nothing.
|
|
let prepare (path: string) : string option =
|
|
let project = projectOf path
|
|
|
|
if state = Undecided then
|
|
state <-
|
|
match project with
|
|
| Some dir when File.Exists(rootsFile dir) ->
|
|
let frameworks = Path.Combine(dir, ".bjo", "frameworks")
|
|
let nuget = Path.Combine(dir, ".bjo", "nuget", "root")
|
|
|
|
let installed =
|
|
Bjolang.ProjectInputs.install
|
|
(Some(rootsFile dir))
|
|
(if File.Exists frameworks then Some frameworks else None)
|
|
[]
|
|
(if Directory.Exists nuget then Some nuget else None)
|
|
|
|
match installed with
|
|
| Ok() ->
|
|
Rpc.log $"checking against the packages of %s{dir}"
|
|
Serving(Some dir, writtenAt (rootsFile dir))
|
|
| Error message -> Failed(dir, message)
|
|
| Some _
|
|
| None -> Serving(None, None)
|
|
|
|
match state, project with
|
|
| Failed(dir, message), Some p when p = dir ->
|
|
Some $"The packages of this project did not load, so only the standard library is seen:\n%s{message}"
|
|
| Serving(Some dir, roots), Some p when p = dir ->
|
|
if writtenAt (rootsFile dir) <> roots then
|
|
Some "The project's packages changed after the language server started. Restart it to see them."
|
|
else
|
|
None
|
|
| Serving(None, _), Some p when not (File.Exists(rootsFile p)) ->
|
|
Some $"This project's packages are not fetched, so only the standard library is seen. Run `bjo fetch` in %s{p}, then restart the language server."
|
|
| Serving(None, _), Some p ->
|
|
Some $"The language server started on a file outside this project, and sees only the standard library. Restart it from a file in %s{p}."
|
|
| (Serving(Some dir, _) | Failed(dir, _)), Some p ->
|
|
Some $"The language server checks against the packages of %s{dir}. This file is in %s{p}: open it in a server of its own to see its packages."
|
|
| _ -> None
|