Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: rover lsp #2135

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
514 changes: 484 additions & 30 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 11 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ license-file = "./LICENSE"
name = "rover"
readme = "README.md"
repository = "https://github.com/apollographql/rover/"
version = "0.26.2"
version = "0.27.0-alpha.0"
default-run = "rover"

publish = false
Expand Down Expand Up @@ -70,6 +70,12 @@ apollo-encoder = "0.8"

# https://github.com/apollographql/federation-rs
apollo-federation-types = "0.14.1"
# apollo-federation-types = { path = "../federation-rs/apollo-federation-types" }
# apollo-federation-types = { git = "https://github.com/apollographql/federation-rs.git", branch = "connectors"}

# https://github.com/mdg-private/language-server
apollo-language-server = { version = "0.1.1", default-features = false, features = ["tokio"] }
# apollo-language-server = { path = "../language-server/crates/language-server-core", default-features = false, features = ["tokio"] }

# crates.io dependencies
anyhow = "1"
Expand Down Expand Up @@ -165,10 +171,11 @@ zip = "2.0"
### rover specific dependencies
[dependencies]
anyhow = { workspace = true }
assert_fs = { workspace = true }
async-trait = { workspace = true }
apollo-federation-types = { workspace = true }
apollo-language-server = { workspace = true }
apollo-parser = { workspace = true }
assert_fs = { workspace = true }
async-trait = { workspace = true }
billboard = { workspace = true }
binstall = { workspace = true }
buildstructor = { workspace = true }
Expand Down Expand Up @@ -217,6 +224,7 @@ tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros", "proc
tokio-stream = { workspace = true }
toml = { workspace = true }
tower = { workspace = true }
tower-lsp = { version = "0.20.0" }
tracing = { workspace = true }
which = { workspace = true }
uuid = { workspace = true }
Expand Down
18 changes: 9 additions & 9 deletions crates/rover-client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ version = "*"
expression = "(Apache-2.0 OR MIT) AND BSD-3-Clause"
license-files = [{ path = "COPYRIGHT", hash = 0x39f8ad31 }]

[[licenses.clarify]]
name = "apollo-language-server"
version = "*"
expression = "Elastic-2.0"
license-files = [{ path = "LICENSE.md", hash = 0x5fc4a573 }]

[[licenses.exceptions]]
name = "apollo-language-server"
allow = ["Elastic-2.0"]

[[licenses.exceptions]]
name = "apollo-federation"
allow = ["Elastic-2.0"]

[[licenses.exceptions]]
name = "apollo-composition"
allow = ["Elastic-2.0"]

# This section is considered when running `cargo deny check bans`.
# More documentation about the 'bans' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
Expand Down
2 changes: 1 addition & 1 deletion installers/binstall/scripts/nix/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ BINARY_DOWNLOAD_PREFIX="https://github.com/apollographql/rover/releases/download
# Rover version defined in root cargo.toml
# Note: this line is built automatically
# in build.rs. Don't touch it!
PACKAGE_VERSION="v0.26.2"
PACKAGE_VERSION="v0.27.0-alpha.0"

download_binary_and_run_installer() {
downloader --check
Expand Down
2 changes: 1 addition & 1 deletion installers/binstall/scripts/windows/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# version found in Rover's Cargo.toml
# Note: this line is built automatically
# in build.rs. Don't touch it!
$package_version = 'v0.26.2'
$package_version = 'v0.27.0-alpha.0'

function Install-Binary($rover_install_args) {
$old_erroractionpreference = $ErrorActionPreference
Expand Down
16 changes: 8 additions & 8 deletions installers/npm/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion installers/npm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@apollo/rover",
"version": "0.26.2",
"version": "0.27.0-alpha.0",
"description": "The new Apollo CLI",
"main": "index.js",
"bin": {
Expand Down
6 changes: 6 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ impl Rover {
Command::Explain(command) => command.run(),
Command::PersistedQueries(command) => command.run(self.get_client_config()?).await,
Command::License(command) => command.run(self.get_client_config()?).await,
#[cfg(feature = "composition-js")]
Command::Lsp(command) => command.run(self.get_client_config()?).await,
}
}

Expand Down Expand Up @@ -437,6 +439,10 @@ pub enum Command {

/// Commands for fetching offline licenses
License(command::License),

/// Start the language server
#[cfg(feature = "composition-js")]
Lsp(command::Lsp),
}

#[derive(Default, ValueEnum, Debug, Serialize, Clone, Copy, Eq, PartialEq)]
Expand Down
14 changes: 14 additions & 0 deletions src/command/lsp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Build / configuration

1. Clone mdg-private/language-server repo (adjacent to this repo, the language
server dependency is currently specified as a path in Cargo.toml)
> apollo-language-server-core = { path = "../language-server/crates/language-server-core" }
2. From this repo, run `cargo update -p apollo-language-server-core` to
build/rebuild changes made to the language server.
3. To rebuild the rover binary, run `cargo build`
4. Build is located at `target/debug/rover`

In case you're looking to sanity check the binary, there's a sample `input.txt`
file you can paste into stdin to see if the binary is working as expected. I'm
not sure why it breaks when I try to redirect the input from a file, but it
works as expected when I paste the contents of the file into stdin.
9 changes: 9 additions & 0 deletions src/command/lsp/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Content-Length: 75

{"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{}},"id":0}Content-Length: 185

{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///path/to/file","version":1,"languageId":"apollo-graphql","text":"type Query { hello: Strin }"}}}Content-Length: 180

{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///path/to/file","version":2},"contentChanges":[{"text":"type Query { hello: String! }"}]}}Content-Length: 178

{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///path/to/file","version":3},"contentChanges":[{"text":"type Query { hello: Strin }"}]}}Content-Length: 0
162 changes: 162 additions & 0 deletions src/command/lsp/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
use core::panic;

use apollo_federation_types::{
config::{FederationVersion, SupergraphConfig},
javascript::SubgraphDefinition,
rover::BuildErrors,
};
use apollo_language_server::{ApolloLanguageServer, Config};
use clap::Parser;
use futures::{channel::mpsc::Receiver, StreamExt};
use serde::Serialize;
use tower_lsp::Server;

use super::supergraph::compose::Compose;
use crate::{
options::PluginOpts,
utils::{
client::StudioClientConfig, parsers::FileDescriptorType,
supergraph_config::resolve_supergraph_yaml,
},
RoverOutput, RoverResult,
};

#[derive(Debug, Serialize, Parser)]
pub struct Lsp {
#[clap(flatten)]
pub(crate) opts: LspOpts,
}

#[derive(Clone, Debug, Serialize, Parser)]
pub struct LspOpts {
#[clap(flatten)]
pub plugin_opts: PluginOpts,

/// The relative path to the supergraph configuration file. You can pass `-` to use stdin instead of a file.
#[serde(skip_serializing)]
#[arg(long = "supergraph-config")]
supergraph_yaml: Option<FileDescriptorType>,
}

impl Lsp {
pub async fn run(&self, client_config: StudioClientConfig) -> RoverResult<RoverOutput> {
self.opts
.plugin_opts
.prompt_for_license_accept(&client_config)?;

run_lsp(client_config, &self.opts).await;
Ok(RoverOutput::EmptySuccess)
}
}

async fn run_lsp(client_config: StudioClientConfig, lsp_opts: &LspOpts) {
let (service, socket, receiver) = ApolloLanguageServer::build_service(Config {
root_uri: "".into(),
enable_auto_composition: true,
force_federation: false,
disable_telemetry: false,
});

let language_server = service.inner().clone();

tokio::spawn(run_composer_in_thread(
receiver,
lsp_opts.clone(),
client_config,
language_server,
));

let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
let server = Server::new(stdin, stdout, socket);
server.serve(service).await;
}

async fn run_composer_in_thread(
mut receiver: Receiver<Vec<SubgraphDefinition>>,
lsp_opts: LspOpts,
client_config: StudioClientConfig,
language_server: ApolloLanguageServer,
) {
let composer = Compose::new(lsp_opts.plugin_opts.clone());
let federation_version =
dbg!(get_federation_version(lsp_opts.clone(), client_config.clone()).await);
match composer
.maybe_install_supergraph(None, client_config.clone(), federation_version.clone())
.await
{
Ok(_) => {}
Err(err) => {
panic!("Failed to install supergraph plugin: {:?}", err);
}
};

while let Some(mut definitions) = receiver.next().await {
while let Some(next_definitions) = receiver.try_next().ok().flatten() {
definitions = next_definitions
}
tracing::info!("Received message: {:?}", definitions);
dbg!(&definitions);

let mut supergraph_config = SupergraphConfig::from(definitions);
supergraph_config.set_federation_version(federation_version.clone());

match composer
.compose(None, client_config.clone(), &mut supergraph_config, None)
.await
{
Ok(RoverOutput::CompositionResult(composition_output)) => {
dbg!(&composition_output);
language_server
.composition_did_update(
Some(composition_output.supergraph_sdl),
composition_output
.hints
.into_iter()
.map(Into::into)
.collect(),
None,
)
.await
}
Err(rover_error) => {
dbg!(&rover_error);
let build_errors: BuildErrors = rover_error.into();
dbg!(&build_errors);
language_server
.composition_did_update(
None,
build_errors.into_iter().map(Into::into).collect(),
None,
)
.await
}
_ => panic!("Expected CompositionResult"),
}
}
}

async fn get_federation_version(
lsp_opts: LspOpts,
client_config: StudioClientConfig,
) -> FederationVersion {
if let Some(supergraph_yaml) = &lsp_opts.supergraph_yaml {
if let Ok(supergraph_config) = resolve_supergraph_yaml(
supergraph_yaml,
client_config.clone(),
&lsp_opts.plugin_opts.profile,
true,
)
.await
{
supergraph_config
.get_federation_version()
.unwrap_or(FederationVersion::LatestFedTwo)
} else {
tracing::warn!("Failed to resolve supergraph yaml");
FederationVersion::LatestFedTwo
}
} else {
FederationVersion::LatestFedTwo
}
}
Loading
Loading