Load locked flake inputs

This commit is contained in:
oxalica 2023-01-27 03:35:21 +08:00
parent 0eb313e73b
commit 6e55610257
3 changed files with 110 additions and 14 deletions

View file

@ -35,6 +35,17 @@ impl VfsPath {
Ok(Self(s))
}
/// Append a path segment at the end and return the new path.
/// Panic if it is empty or contains `/`.
pub fn join_segment(&self, segment: &str) -> Self {
assert!(!segment.is_empty() && !segment.contains('/'));
let mut buf = String::with_capacity(self.0.len() + 1 + segment.len());
buf += &self.0;
buf += "/";
buf += segment;
Self(buf)
}
/// Assume another VfsPath as relative and append it to this one.
pub fn append(&mut self, relative: &Self) {
self.0.push_str(&relative.0);

View file

@ -2,7 +2,7 @@ use crate::config::{Config, CONFIG_KEY};
use crate::{convert, handler, LspError, Vfs};
use anyhow::{bail, Context, Result};
use crossbeam_channel::{Receiver, Sender};
use ide::{Analysis, AnalysisHost, Cancelled};
use ide::{Analysis, AnalysisHost, Cancelled, FileId, VfsPath};
use lsp_server::{ErrorCode, Message, Notification, ReqQueue, Request, RequestId, Response};
use lsp_types::notification::Notification as _;
use lsp_types::{
@ -10,12 +10,13 @@ use lsp_types::{
InitializeParams, MessageType, NumberOrString, PublishDiagnosticsParams, ShowMessageParams,
Url,
};
use nix_interop::{flake_lock, FLAKE_FILE, FLAKE_LOCK_FILE};
use std::cell::Cell;
use std::collections::HashMap;
use std::panic::UnwindSafe;
use std::path::PathBuf;
use std::sync::{Arc, Once, RwLock};
use std::{panic, thread};
use std::{fs, panic, thread};
type ReqHandler = fn(&mut Server, Response);
@ -29,6 +30,15 @@ enum Event {
diagnostics: Vec<Diagnostic>,
},
ClientExited,
LoadFlake(Result<LoadFlakeResult>),
}
enum LoadFlakeResult {
IsFlake {
flake_file: FileId,
input_store_paths: HashMap<String, String>,
},
NotFlake,
}
pub struct Server {
@ -72,9 +82,9 @@ impl Server {
tracing::info!("Started {worker_cnt} workers");
Self {
host: Default::default(),
host: AnalysisHost::default(),
vfs: Arc::new(RwLock::new(Vfs::new())),
opened_files: Default::default(),
opened_files: HashMap::default(),
config: Arc::new(Config::new(root_path)),
is_shutdown: false,
version_counter: 0,
@ -139,6 +149,9 @@ impl Server {
});
}
// TODO: Register file watcher for flake.lock.
self.load_flake();
loop {
crossbeam_channel::select! {
recv(lsp_rx) -> msg => {
@ -194,6 +207,25 @@ impl Server {
Event::ClientExited => {
bail!("The process initializing this server is exited. Exit now")
}
Event::LoadFlake(ret) => match ret {
Err(err) => {
self.show_message(
MessageType::ERROR,
format!("Failed to load workspace: {err}"),
);
}
Ok(LoadFlakeResult::IsFlake {
flake_file: _flake_file,
input_store_paths,
}) => {
tracing::info!("Workspace is a flake with inputs {input_store_paths:?}");
// TODO
}
Ok(LoadFlakeResult::NotFlake) => {
tracing::info!("Workspace is not a flake");
// TODO
}
},
}
Ok(())
}
@ -311,6 +343,55 @@ impl Server {
.finish()
}
/// Enqueue a task to reload the flake.{nix,lock} and the locked inputs.
fn load_flake(&self) {
tracing::info!("Loading flake configuration");
let flake_path = self.config.root_path.join(FLAKE_FILE);
let lock_path = self.config.root_path.join(FLAKE_LOCK_FILE);
let vfs = self.vfs.clone();
let task = move || {
if !flake_path.exists() {
// No flake found.
return Ok(LoadFlakeResult::NotFlake);
}
let flake_vpath = VfsPath::try_from(&*flake_path)?;
let flake_src = fs::read_to_string(&flake_path)
.with_context(|| format!("Failed to read flake root {flake_path:?}"))?;
let flake_file = {
let mut vfs = vfs.write().unwrap();
match vfs.file_for_path(&flake_vpath) {
// If the file is already opened (transferred from client),
// prefer the managed one. It contains more recent unsaved changes.
Ok(file) => file,
// Otherwise, cache the file content from disk.
Err(_) => vfs.set_path_content(flake_vpath, flake_src)?,
}
};
let lock_src = fs::read(&lock_path)?;
// TODO: Customize the command to Nix binary.
let inputs = flake_lock::resolve_flake_locked_inputs("nix".as_ref(), &lock_src)
.context("Failed to resolve flake inputs from lock file")?;
// We only need the map for input -> store path.
let input_store_paths = inputs
.into_iter()
.map(|(key, input)| (key, input.store_path))
.collect();
Ok(LoadFlakeResult::IsFlake {
flake_file,
input_store_paths,
})
};
self.task_tx
.send(Box::new(move || Event::LoadFlake(task())))
.unwrap();
}
fn send_request<R: req::Request>(
&mut self,
params: R::Params,

View file

@ -40,11 +40,11 @@ impl Vfs {
pub fn set_uri_content(&mut self, uri: &Url, text: String) -> Result<()> {
let vpath = uri.to_vfs_path()?;
self.set_path_content(vpath, text);
self.set_path_content(vpath, text)?;
Ok(())
}
pub fn set_path_content(&mut self, path: VfsPath, text: String) {
pub fn set_path_content(&mut self, path: VfsPath, text: String) -> Result<FileId> {
// For invalid files (currently, too large), we store them as empty files in database,
// but remove them from `local_file_set`. Thus any interactions on them would fail.
let (text, line_map, is_valid) = LineMap::normalize(text)
@ -60,18 +60,19 @@ impl Vfs {
self.local_file_set.remove_file(file);
self.root_changed = true;
}
Ok(file)
}
None => {
if !is_valid {
return;
}
// FIXME: Somehow get rid of this validity check from Vfs.
ensure!(is_valid, "File is not valid");
let file = FileId(u32::try_from(self.files.len()).expect("Length overflow"));
self.local_file_set.insert(file, path);
self.root_changed = true;
self.files.push((text.clone(), line_map));
self.change.change_file(file, text);
Ok(file)
}
};
}
}
pub fn change_file_content(
@ -102,11 +103,14 @@ impl Vfs {
Ok(())
}
pub fn file_for_uri(&self, uri: &Url) -> Result<FileId> {
let vpath = uri.to_vfs_path()?;
pub fn file_for_path(&self, path: &VfsPath) -> Result<FileId> {
self.local_file_set
.file_for_path(&vpath)
.with_context(|| format!("URI not found: {uri}"))
.file_for_path(path)
.with_context(|| format!("File not loaded: {path:?}"))
}
pub fn file_for_uri(&self, uri: &Url) -> Result<FileId> {
self.file_for_path(&uri.to_vfs_path()?)
}
pub fn uri_for_file(&self, file: FileId) -> Url {