From fa019c8f562326a720d2ef9165626c4c5703f67b Mon Sep 17 00:00:00 2001
From: Aleksey Kladov <aleksey.kladov@gmail.com>
Date: Wed, 3 Jun 2020 14:48:38 +0200
Subject: [PATCH] Document rust-project.json

---
 crates/ra_project_model/src/lib.rs    | 10 ++++--
 crates/rust-analyzer/src/config.rs    | 23 ++++++++++++
 crates/rust-analyzer/src/main_loop.rs | 35 +++++++++---------
 docs/user/manual.adoc                 | 51 +++++++++++++++++++++++++++
 editors/code/package.json             | 19 ++++++++++
 5 files changed, 118 insertions(+), 20 deletions(-)

diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index a9961269026..7ad94127950 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -32,6 +32,12 @@ pub enum ProjectWorkspace {
     Json { project: JsonProject },
 }
 
+impl From<JsonProject> for ProjectWorkspace {
+    fn from(project: JsonProject) -> ProjectWorkspace {
+        ProjectWorkspace::Json { project }
+    }
+}
+
 /// `PackageRoot` describes a package root folder.
 /// Which may be an external dependency, or a member of
 /// the current workspace.
@@ -144,11 +150,11 @@ impl ProjectManifest {
 
 impl ProjectWorkspace {
     pub fn load(
-        root: ProjectManifest,
+        manifest: ProjectManifest,
         cargo_features: &CargoConfig,
         with_sysroot: bool,
     ) -> Result<ProjectWorkspace> {
-        let res = match root {
+        let res = match manifest {
             ProjectManifest::ProjectJson(project_json) => {
                 let file = File::open(&project_json).with_context(|| {
                     format!("Failed to open json file {}", project_json.display())
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 761bc9c2d92..0e5dc56fd74 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -261,6 +261,22 @@ impl Config {
             self.lens = LensConfig::NO_LENS;
         }
 
+        if let Some(linked_projects) = get::<Vec<ManifestOrJsonProject>>(value, "/linkedProjects") {
+            if !linked_projects.is_empty() {
+                self.linked_projects.clear();
+                for linked_project in linked_projects {
+                    let linked_project = match linked_project {
+                        ManifestOrJsonProject::Manifest(it) => match ProjectManifest::from_manifest_file(it) {
+                            Ok(it) => it.into(),
+                            Err(_) => continue,
+                        }
+                        ManifestOrJsonProject::JsonProject(it) => it.into(),
+                    };
+                    self.linked_projects.push(linked_project);
+                }
+            }
+        }
+
         log::info!("Config::update() = {:#?}", self);
 
         fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option<T> {
@@ -324,3 +340,10 @@ impl Config {
         }
     }
 }
+
+#[derive(Deserialize)]
+#[serde(untagged)]
+enum ManifestOrJsonProject {
+    Manifest(PathBuf),
+    JsonProject(JsonProject),
+}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index d901f21d7e7..1f8f6b97861 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -105,24 +105,23 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
                 .linked_projects
                 .iter()
                 .filter_map(|project| match project {
-                    LinkedProject::ProjectManifest(it) => Some(it),
-                    LinkedProject::JsonProject(_) => None,
-                })
-                .filter_map(|root| {
-                    ra_project_model::ProjectWorkspace::load(
-                        root.clone(),
-                        &config.cargo,
-                        config.with_sysroot,
-                    )
-                    .map_err(|err| {
-                        log::error!("failed to load workspace: {:#}", err);
-                        show_message(
-                            lsp_types::MessageType::Error,
-                            format!("rust-analyzer failed to load workspace: {:#}", err),
-                            &connection.sender,
-                        );
-                    })
-                    .ok()
+                    LinkedProject::ProjectManifest(manifest) => {
+                        ra_project_model::ProjectWorkspace::load(
+                            manifest.clone(),
+                            &config.cargo,
+                            config.with_sysroot,
+                        )
+                        .map_err(|err| {
+                            log::error!("failed to load workspace: {:#}", err);
+                            show_message(
+                                lsp_types::MessageType::Error,
+                                format!("rust-analyzer failed to load workspace: {:#}", err),
+                                &connection.sender,
+                            );
+                        })
+                        .ok()
+                    }
+                    LinkedProject::JsonProject(it) => Some(it.clone().into()),
                 })
                 .collect::<Vec<_>>()
         };
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 202783fd953..ea714f49add 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -269,6 +269,57 @@ Gnome Builder currently has support for RLS, and there's no way to configure the
 1. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`).
 2. Enable the Rust Builder plugin.
 
+== Non-Cargo Based Projects
+
+rust-analyzer does not require Cargo.
+However, if you use some other build system, you'll have to describe the structure of your project for rust-analyzer in the `rust-project.json` format:
+
+[source,TypeScript]
+----
+interface JsonProject {
+   /// The set of paths containing the crates for this project.
+   /// Any `Crate` must be nested inside some `root`.
+   roots: string[];
+   /// The set of crates comprising the current project.
+   /// Must include all transitive dependencies as well as sysroot crate (libstd, libcore and such).
+   crates: Crate[];
+}
+
+interface Crate {
+    /// Path to the root module of the crate.
+    root_module: string;
+    /// Edition of the crate.
+    edition: "2015" | "2018";
+    /// Dependencies
+    deps: Dep[];
+    /// The set of cfgs activated for a given crate, like `["unix", "feature=foo", "feature=bar"]`.
+    cfg: string[];
+
+    /// value of the OUT_DIR env variable.
+    out_dir?: string;
+    /// For proc-macro crates, path to compiles proc-macro (.so file).
+    proc_macro_dylib_path?: string;
+}
+
+interface Dep {
+    /// Index of a crate in the `crates` array.
+    crate: number,
+    /// Name as should appear in the (implicit) `extern crate name` declaration.
+    name: string,
+}
+----
+
+This format is provisional and subject to change.
+Specifically, the `roots` setup will be different eventually.
+
+There are tree ways to feed `rust-project.json` to rust-analyzer:
+
+* Place `rust-project.json` file at the root of the project, and rust-anlayzer will discover it.
+* Specify `"rust-analyzer.linkedProjects": [ "path/to/rust-project.json" ]` in the settings (and make sure that your LSP client sends settings as a part of initialize request).
+* Specify `"rust-analyzer.linkedProjects": [ { "roots": [...], "crates": [...] }]` inline.
+
+See https://github.com/rust-analyzer/rust-project.json-example for a small example.
+
 == Features
 
 include::./generated_features.adoc[]
diff --git a/editors/code/package.json b/editors/code/package.json
index d8f4287fd88..30ab7ba4a9f 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -475,6 +475,25 @@
                     "markdownDescription": "Whether to show Implementations lens. Only applies when `#rust-analyzer.lens.enable#` is set.",
                     "type": "boolean",
                     "default": true
+                },
+                "rust-analyzer.linkedProjects": {
+                    "markdownDescription": [
+                        "Disable project auto-discovery in favor of explicitly specified set of projects.",
+                        "Elements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format"
+                    ],
+                    "type": "array",
+                    "items": {
+                        "type": [
+                            "string",
+                            "object"
+                        ]
+                    },
+                    "default": null
+                },
+                "rust-analyzer.withSysroot": {
+                    "markdownDescription": "Internal config for debugging, disables loading of sysroot crates",
+                    "type": "boolean",
+                    "default": true
                 }
             }
         },