Start work on installing gopls

This commit is contained in:
Max Brunsfeld 2022-06-07 18:38:37 -07:00
parent 209ff619ef
commit 8f4387a252
7 changed files with 192 additions and 18 deletions

View file

@ -4,6 +4,7 @@ use rust_embed::RustEmbed;
use std::{borrow::Cow, str, sync::Arc};
mod c;
mod go;
mod installation;
mod json;
mod rust;

View file

@ -4,7 +4,11 @@ use client::http::HttpClient;
use futures::{future::BoxFuture, FutureExt, StreamExt};
pub use language::*;
use smol::fs::{self, File};
use std::{any::Any, path::PathBuf, sync::Arc};
use std::{
any::Any,
path::{Path, PathBuf},
sync::Arc,
};
use util::{ResultExt, TryFutureExt};
pub struct CLspAdapter;
@ -39,7 +43,7 @@ impl super::LspAdapter for CLspAdapter {
&self,
version: Box<dyn 'static + Send + Any>,
http: Arc<dyn HttpClient>,
container_dir: PathBuf,
container_dir: Arc<Path>,
) -> BoxFuture<'static, Result<PathBuf>> {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
async move {
@ -88,7 +92,10 @@ impl super::LspAdapter for CLspAdapter {
.boxed()
}
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
fn cached_server_binary(
&self,
container_dir: Arc<Path>,
) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last_clangd_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;

View file

@ -0,0 +1,140 @@
use super::installation::latest_github_release;
use anyhow::{anyhow, Result};
use client::http::HttpClient;
use futures::{future::BoxFuture, FutureExt, StreamExt};
pub use language::*;
use lazy_static::lazy_static;
use regex::Regex;
use smol::{fs, process};
use std::{
any::Any,
path::{Path, PathBuf},
str,
sync::Arc,
};
use util::{ResultExt, TryFutureExt};
#[derive(Copy, Clone)]
pub struct GoLspAdapter;
lazy_static! {
static ref GOPLS_VERSION_REGEX: Regex = Regex::new(r"\d+\.\d+\.\d+").unwrap();
}
impl super::LspAdapter for GoLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName("gopls".into())
}
fn fetch_latest_server_version(
&self,
http: Arc<dyn HttpClient>,
) -> BoxFuture<'static, Result<Box<dyn 'static + Send + Any>>> {
async move {
let release = latest_github_release("golang/tools", http).await?;
let version: Option<String> = release.name.strip_prefix("gopls/v").map(str::to_string);
if version.is_none() {
log::warn!(
"couldn't infer gopls version from github release name '{}'",
release.name
);
}
Ok(Box::new(version) as Box<_>)
}
.boxed()
}
fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
container_dir: Arc<Path>,
) -> BoxFuture<'static, Result<PathBuf>> {
let version = version.downcast::<Option<String>>().unwrap();
let this = *self;
async move {
if let Some(version) = *version {
let binary_path = container_dir.join(&format!("gopls_{version}"));
if let Ok(metadata) = fs::metadata(&binary_path).await {
if metadata.is_file() {
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() {
while let Some(entry) = entries.next().await {
if let Some(entry) = entry.log_err() {
let entry_path = entry.path();
if entry_path.as_path() != binary_path
&& entry.file_name() != "gobin"
{
fs::remove_file(&entry_path).await.log_err();
}
}
}
}
return Ok(binary_path.to_path_buf());
}
}
} else if let Some(path) = this.cached_server_binary(container_dir.clone()).await {
return Ok(path.to_path_buf());
}
let gobin_dir = container_dir.join("gobin");
fs::create_dir_all(&gobin_dir).await?;
let install_output = process::Command::new("go")
.env("GO111MODULE", "on")
.env("GOBIN", &gobin_dir)
.args(["install", "golang.org/x/tools/gopls@latest"])
.output()
.await?;
if !install_output.status.success() {
Err(anyhow!("failed to install gopls"))?;
}
let installed_binary_path = gobin_dir.join("gopls");
let version_output = process::Command::new(&installed_binary_path)
.arg("version")
.output()
.await
.map_err(|e| anyhow!("failed to run installed gopls binary {:?}", e))?;
let version_stdout = str::from_utf8(&version_output.stdout)
.map_err(|_| anyhow!("gopls version produced invalid utf8"))?;
let version = GOPLS_VERSION_REGEX
.shortest_match(version_stdout)
.ok_or_else(|| anyhow!("failed to parse gopls version output"))?;
let binary_path = container_dir.join(&format!("gopls_{version}"));
fs::rename(&installed_binary_path, &binary_path).await?;
Ok(binary_path.to_path_buf())
}
.boxed()
}
fn cached_server_binary(
&self,
container_dir: Arc<Path>,
) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last_binary_path = None;
let mut entries = fs::read_dir(&container_dir).await?;
while let Some(entry) = entries.next().await {
let entry = entry?;
if entry.file_type().await?.is_file()
&& entry
.file_name()
.to_str()
.map_or(false, |name| name.starts_with("gopls_"))
{
last_binary_path = Some(entry.path());
}
}
if let Some(path) = last_binary_path {
Ok(path.to_path_buf())
} else {
Err(anyhow!("no cached binary"))
}
}
.log_err()
.boxed()
}
}

View file

@ -5,7 +5,11 @@ use language::{LanguageServerName, LspAdapter};
use serde::Deserialize;
use serde_json::json;
use smol::fs;
use std::{any::Any, path::PathBuf, sync::Arc};
use std::{
any::Any,
path::{Path, PathBuf},
sync::Arc,
};
use util::{ResultExt, TryFutureExt};
pub struct JsonLspAdapter;
@ -56,7 +60,7 @@ impl LspAdapter for JsonLspAdapter {
&self,
version: Box<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
container_dir: Arc<Path>,
) -> BoxFuture<'static, Result<PathBuf>> {
let version = version.downcast::<String>().unwrap();
async move {
@ -95,7 +99,10 @@ impl LspAdapter for JsonLspAdapter {
.boxed()
}
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
fn cached_server_binary(
&self,
container_dir: Arc<Path>,
) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last_version_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;

View file

@ -7,7 +7,14 @@ pub use language::*;
use lazy_static::lazy_static;
use regex::Regex;
use smol::fs::{self, File};
use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc};
use std::{
any::Any,
borrow::Cow,
env::consts,
path::{Path, PathBuf},
str,
sync::Arc,
};
use util::{ResultExt, TryFutureExt};
pub struct RustLspAdapter;
@ -42,7 +49,7 @@ impl LspAdapter for RustLspAdapter {
&self,
version: Box<dyn 'static + Send + Any>,
http: Arc<dyn HttpClient>,
container_dir: PathBuf,
container_dir: Arc<Path>,
) -> BoxFuture<'static, Result<PathBuf>> {
async move {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
@ -79,7 +86,10 @@ impl LspAdapter for RustLspAdapter {
.boxed()
}
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
fn cached_server_binary(
&self,
container_dir: Arc<Path>,
) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last = None;
let mut entries = fs::read_dir(&container_dir).await?;

View file

@ -5,7 +5,11 @@ use futures::{future::BoxFuture, FutureExt, StreamExt};
use language::{LanguageServerName, LspAdapter};
use serde_json::json;
use smol::fs;
use std::{any::Any, path::PathBuf, sync::Arc};
use std::{
any::Any,
path::{Path, PathBuf},
sync::Arc,
};
use util::{ResultExt, TryFutureExt};
pub struct TypeScriptLspAdapter;
@ -45,7 +49,7 @@ impl LspAdapter for TypeScriptLspAdapter {
&self,
versions: Box<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
container_dir: PathBuf,
container_dir: Arc<Path>,
) -> BoxFuture<'static, Result<PathBuf>> {
let versions = versions.downcast::<Versions>().unwrap();
async move {
@ -88,7 +92,10 @@ impl LspAdapter for TypeScriptLspAdapter {
.boxed()
}
fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
fn cached_server_binary(
&self,
container_dir: Arc<Path>,
) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last_version_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;