aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_project_model
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_project_model')
-rw-r--r--crates/ra_project_model/Cargo.toml2
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs44
-rw-r--r--crates/ra_project_model/src/json_project.rs95
-rw-r--r--crates/ra_project_model/src/lib.rs135
-rw-r--r--crates/ra_project_model/src/project_json.rs117
-rw-r--r--crates/ra_project_model/src/sysroot.rs23
6 files changed, 202 insertions, 214 deletions
diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml
index e4a60f4c0..b1b44dcf7 100644
--- a/crates/ra_project_model/Cargo.toml
+++ b/crates/ra_project_model/Cargo.toml
@@ -18,6 +18,8 @@ ra_cfg = { path = "../ra_cfg" }
18ra_db = { path = "../ra_db" } 18ra_db = { path = "../ra_db" }
19ra_toolchain = { path = "../ra_toolchain" } 19ra_toolchain = { path = "../ra_toolchain" }
20ra_proc_macro = { path = "../ra_proc_macro" } 20ra_proc_macro = { path = "../ra_proc_macro" }
21paths = { path = "../paths" }
22stdx = { path = "../stdx" }
21 23
22serde = { version = "1.0.106", features = ["derive"] } 24serde = { version = "1.0.106", features = ["derive"] }
23serde_json = "1.0.48" 25serde_json = "1.0.48"
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 4b7444039..3b124020d 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -1,14 +1,10 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::{ 3use std::{ffi::OsStr, ops, path::Path, process::Command};
4 ffi::OsStr,
5 ops,
6 path::{Path, PathBuf},
7 process::Command,
8};
9 4
10use anyhow::{Context, Result}; 5use anyhow::{Context, Result};
11use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId}; 6use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId};
7use paths::{AbsPath, AbsPathBuf};
12use ra_arena::{Arena, Idx}; 8use ra_arena::{Arena, Idx};
13use ra_db::Edition; 9use ra_db::Edition;
14use rustc_hash::FxHashMap; 10use rustc_hash::FxHashMap;
@@ -20,11 +16,14 @@ use rustc_hash::FxHashMap;
20/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates, 16/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates,
21/// while this knows about `Packages` & `Targets`: purely cargo-related 17/// while this knows about `Packages` & `Targets`: purely cargo-related
22/// concepts. 18/// concepts.
19///
20/// We use absolute paths here, `cargo metadata` guarantees to always produce
21/// abs paths.
23#[derive(Debug, Clone)] 22#[derive(Debug, Clone)]
24pub struct CargoWorkspace { 23pub struct CargoWorkspace {
25 packages: Arena<PackageData>, 24 packages: Arena<PackageData>,
26 targets: Arena<TargetData>, 25 targets: Arena<TargetData>,
27 workspace_root: PathBuf, 26 workspace_root: AbsPathBuf,
28} 27}
29 28
30impl ops::Index<Package> for CargoWorkspace { 29impl ops::Index<Package> for CargoWorkspace {
@@ -80,15 +79,15 @@ pub type Target = Idx<TargetData>;
80pub struct PackageData { 79pub struct PackageData {
81 pub version: String, 80 pub version: String,
82 pub name: String, 81 pub name: String,
83 pub manifest: PathBuf, 82 pub manifest: AbsPathBuf,
84 pub targets: Vec<Target>, 83 pub targets: Vec<Target>,
85 pub is_member: bool, 84 pub is_member: bool,
86 pub dependencies: Vec<PackageDependency>, 85 pub dependencies: Vec<PackageDependency>,
87 pub edition: Edition, 86 pub edition: Edition,
88 pub features: Vec<String>, 87 pub features: Vec<String>,
89 pub cfgs: Vec<String>, 88 pub cfgs: Vec<String>,
90 pub out_dir: Option<PathBuf>, 89 pub out_dir: Option<AbsPathBuf>,
91 pub proc_macro_dylib_path: Option<PathBuf>, 90 pub proc_macro_dylib_path: Option<AbsPathBuf>,
92} 91}
93 92
94#[derive(Debug, Clone)] 93#[derive(Debug, Clone)]
@@ -101,7 +100,7 @@ pub struct PackageDependency {
101pub struct TargetData { 100pub struct TargetData {
102 pub package: Package, 101 pub package: Package,
103 pub name: String, 102 pub name: String,
104 pub root: PathBuf, 103 pub root: AbsPathBuf,
105 pub kind: TargetKind, 104 pub kind: TargetKind,
106 pub is_proc_macro: bool, 105 pub is_proc_macro: bool,
107} 106}
@@ -135,7 +134,7 @@ impl TargetKind {
135} 134}
136 135
137impl PackageData { 136impl PackageData {
138 pub fn root(&self) -> &Path { 137 pub fn root(&self) -> &AbsPath {
139 self.manifest.parent().unwrap() 138 self.manifest.parent().unwrap()
140 } 139 }
141} 140}
@@ -193,7 +192,7 @@ impl CargoWorkspace {
193 let pkg = packages.alloc(PackageData { 192 let pkg = packages.alloc(PackageData {
194 name, 193 name,
195 version: version.to_string(), 194 version: version.to_string(),
196 manifest: manifest_path, 195 manifest: AbsPathBuf::assert(manifest_path),
197 targets: Vec::new(), 196 targets: Vec::new(),
198 is_member, 197 is_member,
199 edition, 198 edition,
@@ -210,7 +209,7 @@ impl CargoWorkspace {
210 let tgt = targets.alloc(TargetData { 209 let tgt = targets.alloc(TargetData {
211 package: pkg, 210 package: pkg,
212 name: meta_tgt.name, 211 name: meta_tgt.name,
213 root: meta_tgt.src_path.clone(), 212 root: AbsPathBuf::assert(meta_tgt.src_path.clone()),
214 kind: TargetKind::new(meta_tgt.kind.as_slice()), 213 kind: TargetKind::new(meta_tgt.kind.as_slice()),
215 is_proc_macro, 214 is_proc_macro,
216 }); 215 });
@@ -246,21 +245,22 @@ impl CargoWorkspace {
246 packages[source].features.extend(node.features); 245 packages[source].features.extend(node.features);
247 } 246 }
248 247
249 Ok(CargoWorkspace { packages, targets, workspace_root: meta.workspace_root }) 248 let workspace_root = AbsPathBuf::assert(meta.workspace_root);
249 Ok(CargoWorkspace { packages, targets, workspace_root: workspace_root })
250 } 250 }
251 251
252 pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + ExactSizeIterator + 'a { 252 pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + ExactSizeIterator + 'a {
253 self.packages.iter().map(|(id, _pkg)| id) 253 self.packages.iter().map(|(id, _pkg)| id)
254 } 254 }
255 255
256 pub fn target_by_root(&self, root: &Path) -> Option<Target> { 256 pub fn target_by_root(&self, root: &AbsPath) -> Option<Target> {
257 self.packages() 257 self.packages()
258 .filter_map(|pkg| self[pkg].targets.iter().find(|&&it| self[it].root == root)) 258 .filter_map(|pkg| self[pkg].targets.iter().find(|&&it| &self[it].root == root))
259 .next() 259 .next()
260 .copied() 260 .copied()
261 } 261 }
262 262
263 pub fn workspace_root(&self) -> &Path { 263 pub fn workspace_root(&self) -> &AbsPath {
264 &self.workspace_root 264 &self.workspace_root
265 } 265 }
266 266
@@ -279,8 +279,8 @@ impl CargoWorkspace {
279 279
280#[derive(Debug, Clone, Default)] 280#[derive(Debug, Clone, Default)]
281pub struct ExternResources { 281pub struct ExternResources {
282 out_dirs: FxHashMap<PackageId, PathBuf>, 282 out_dirs: FxHashMap<PackageId, AbsPathBuf>,
283 proc_dylib_paths: FxHashMap<PackageId, PathBuf>, 283 proc_dylib_paths: FxHashMap<PackageId, AbsPathBuf>,
284 cfgs: FxHashMap<PackageId, Vec<String>>, 284 cfgs: FxHashMap<PackageId, Vec<String>>,
285} 285}
286 286
@@ -308,6 +308,7 @@ pub fn load_extern_resources(
308 if let Ok(message) = message { 308 if let Ok(message) = message {
309 match message { 309 match message {
310 Message::BuildScriptExecuted(BuildScript { package_id, out_dir, cfgs, .. }) => { 310 Message::BuildScriptExecuted(BuildScript { package_id, out_dir, cfgs, .. }) => {
311 let out_dir = AbsPathBuf::assert(out_dir);
311 res.out_dirs.insert(package_id.clone(), out_dir); 312 res.out_dirs.insert(package_id.clone(), out_dir);
312 res.cfgs.insert(package_id, cfgs); 313 res.cfgs.insert(package_id, cfgs);
313 } 314 }
@@ -317,7 +318,8 @@ pub fn load_extern_resources(
317 // Skip rmeta file 318 // Skip rmeta file
318 if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) 319 if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name))
319 { 320 {
320 res.proc_dylib_paths.insert(package_id, filename.clone()); 321 let filename = AbsPathBuf::assert(filename.clone());
322 res.proc_dylib_paths.insert(package_id, filename);
321 } 323 }
322 } 324 }
323 } 325 }
diff --git a/crates/ra_project_model/src/json_project.rs b/crates/ra_project_model/src/json_project.rs
deleted file mode 100644
index ee2de4c25..000000000
--- a/crates/ra_project_model/src/json_project.rs
+++ /dev/null
@@ -1,95 +0,0 @@
1//! FIXME: write short doc here
2
3use std::path::PathBuf;
4
5use rustc_hash::FxHashSet;
6use serde::Deserialize;
7
8/// Roots and crates that compose this Rust project.
9#[derive(Clone, Debug, Deserialize)]
10pub struct JsonProject {
11 pub(crate) roots: Vec<Root>,
12 pub(crate) crates: Vec<Crate>,
13}
14
15/// A root points to the directory which contains Rust crates. rust-analyzer watches all files in
16/// all roots. Roots might be nested.
17#[derive(Clone, Debug, Deserialize)]
18#[serde(transparent)]
19pub struct Root {
20 pub(crate) path: PathBuf,
21}
22
23/// A crate points to the root module of a crate and lists the dependencies of the crate. This is
24/// useful in creating the crate graph.
25#[derive(Clone, Debug, Deserialize)]
26pub struct Crate {
27 pub(crate) root_module: PathBuf,
28 pub(crate) edition: Edition,
29 pub(crate) deps: Vec<Dep>,
30
31 #[serde(default)]
32 pub(crate) cfg: FxHashSet<String>,
33
34 pub(crate) out_dir: Option<PathBuf>,
35 pub(crate) proc_macro_dylib_path: Option<PathBuf>,
36}
37
38#[derive(Clone, Copy, Debug, Deserialize)]
39#[serde(rename = "edition")]
40pub enum Edition {
41 #[serde(rename = "2015")]
42 Edition2015,
43 #[serde(rename = "2018")]
44 Edition2018,
45}
46
47/// Identifies a crate by position in the crates array.
48#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
49#[serde(transparent)]
50pub struct CrateId(pub usize);
51
52/// A dependency of a crate, identified by its id in the crates array and name.
53#[derive(Clone, Debug, Deserialize)]
54pub struct Dep {
55 #[serde(rename = "crate")]
56 pub(crate) krate: CrateId,
57 pub(crate) name: String,
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use serde_json::json;
64
65 #[test]
66 fn test_crate_deserialization() {
67 let raw_json = json!( {
68 "crate_id": 2,
69 "root_module": "this/is/a/file/path.rs",
70 "deps": [
71 {
72 "crate": 1,
73 "name": "some_dep_crate"
74 },
75 ],
76 "edition": "2015",
77 "cfg": [
78 "atom_1",
79 "atom_2",
80 "feature=feature_1",
81 "feature=feature_2",
82 "other=value",
83 ],
84
85 });
86
87 let krate: Crate = serde_json::from_value(raw_json).unwrap();
88
89 assert!(krate.cfg.contains(&"atom_1".to_string()));
90 assert!(krate.cfg.contains(&"atom_2".to_string()));
91 assert!(krate.cfg.contains(&"feature=feature_1".to_string()));
92 assert!(krate.cfg.contains(&"feature=feature_2".to_string()));
93 assert!(krate.cfg.contains(&"other=value".to_string()));
94 }
95}
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 9541362f5..8b85b4831 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -1,25 +1,25 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3mod cargo_workspace; 3mod cargo_workspace;
4mod json_project; 4mod project_json;
5mod sysroot; 5mod sysroot;
6 6
7use std::{ 7use std::{
8 fs::{read_dir, File, ReadDir}, 8 fs::{self, read_dir, ReadDir},
9 io::{self, BufReader}, 9 io,
10 path::{Path, PathBuf}, 10 path::Path,
11 process::{Command, Output}, 11 process::{Command, Output},
12}; 12};
13 13
14use anyhow::{bail, Context, Result}; 14use anyhow::{bail, Context, Result};
15use paths::{AbsPath, AbsPathBuf};
15use ra_cfg::CfgOptions; 16use ra_cfg::CfgOptions;
16use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId}; 17use ra_db::{CrateGraph, CrateId, CrateName, Edition, Env, FileId};
17use rustc_hash::{FxHashMap, FxHashSet}; 18use rustc_hash::{FxHashMap, FxHashSet};
18use serde_json::from_reader;
19 19
20pub use crate::{ 20pub use crate::{
21 cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, 21 cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind},
22 json_project::JsonProject, 22 project_json::{ProjectJson, ProjectJsonData},
23 sysroot::Sysroot, 23 sysroot::Sysroot,
24}; 24};
25pub use ra_proc_macro::ProcMacroClient; 25pub use ra_proc_macro::ProcMacroClient;
@@ -29,7 +29,7 @@ pub enum ProjectWorkspace {
29 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. 29 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
30 Cargo { cargo: CargoWorkspace, sysroot: Sysroot }, 30 Cargo { cargo: CargoWorkspace, sysroot: Sysroot },
31 /// Project workspace was manually specified using a `rust-project.json` file. 31 /// Project workspace was manually specified using a `rust-project.json` file.
32 Json { project: JsonProject, project_location: PathBuf }, 32 Json { project: ProjectJson },
33} 33}
34 34
35/// `PackageRoot` describes a package root folder. 35/// `PackageRoot` describes a package root folder.
@@ -38,22 +38,22 @@ pub enum ProjectWorkspace {
38#[derive(Debug, Clone)] 38#[derive(Debug, Clone)]
39pub struct PackageRoot { 39pub struct PackageRoot {
40 /// Path to the root folder 40 /// Path to the root folder
41 path: PathBuf, 41 path: AbsPathBuf,
42 /// Is a member of the current workspace 42 /// Is a member of the current workspace
43 is_member: bool, 43 is_member: bool,
44 out_dir: Option<PathBuf>, 44 out_dir: Option<AbsPathBuf>,
45} 45}
46impl PackageRoot { 46impl PackageRoot {
47 pub fn new_member(path: PathBuf) -> PackageRoot { 47 pub fn new_member(path: AbsPathBuf) -> PackageRoot {
48 Self { path, is_member: true, out_dir: None } 48 Self { path, is_member: true, out_dir: None }
49 } 49 }
50 pub fn new_non_member(path: PathBuf) -> PackageRoot { 50 pub fn new_non_member(path: AbsPathBuf) -> PackageRoot {
51 Self { path, is_member: false, out_dir: None } 51 Self { path, is_member: false, out_dir: None }
52 } 52 }
53 pub fn path(&self) -> &Path { 53 pub fn path(&self) -> &AbsPath {
54 &self.path 54 &self.path
55 } 55 }
56 pub fn out_dir(&self) -> Option<&Path> { 56 pub fn out_dir(&self) -> Option<&AbsPath> {
57 self.out_dir.as_deref() 57 self.out_dir.as_deref()
58 } 58 }
59 pub fn is_member(&self) -> bool { 59 pub fn is_member(&self) -> bool {
@@ -63,12 +63,12 @@ impl PackageRoot {
63 63
64#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] 64#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
65pub enum ProjectManifest { 65pub enum ProjectManifest {
66 ProjectJson(PathBuf), 66 ProjectJson(AbsPathBuf),
67 CargoToml(PathBuf), 67 CargoToml(AbsPathBuf),
68} 68}
69 69
70impl ProjectManifest { 70impl ProjectManifest {
71 pub fn from_manifest_file(path: PathBuf) -> Result<ProjectManifest> { 71 pub fn from_manifest_file(path: AbsPathBuf) -> Result<ProjectManifest> {
72 if path.ends_with("rust-project.json") { 72 if path.ends_with("rust-project.json") {
73 return Ok(ProjectManifest::ProjectJson(path)); 73 return Ok(ProjectManifest::ProjectJson(path));
74 } 74 }
@@ -78,7 +78,7 @@ impl ProjectManifest {
78 bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display()) 78 bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display())
79 } 79 }
80 80
81 pub fn discover_single(path: &Path) -> Result<ProjectManifest> { 81 pub fn discover_single(path: &AbsPath) -> Result<ProjectManifest> {
82 let mut candidates = ProjectManifest::discover(path)?; 82 let mut candidates = ProjectManifest::discover(path)?;
83 let res = match candidates.pop() { 83 let res = match candidates.pop() {
84 None => bail!("no projects"), 84 None => bail!("no projects"),
@@ -91,23 +91,23 @@ impl ProjectManifest {
91 Ok(res) 91 Ok(res)
92 } 92 }
93 93
94 pub fn discover(path: &Path) -> io::Result<Vec<ProjectManifest>> { 94 pub fn discover(path: &AbsPath) -> io::Result<Vec<ProjectManifest>> {
95 if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") { 95 if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") {
96 return Ok(vec![ProjectManifest::ProjectJson(project_json)]); 96 return Ok(vec![ProjectManifest::ProjectJson(project_json)]);
97 } 97 }
98 return find_cargo_toml(path) 98 return find_cargo_toml(path)
99 .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect()); 99 .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect());
100 100
101 fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> { 101 fn find_cargo_toml(path: &AbsPath) -> io::Result<Vec<AbsPathBuf>> {
102 match find_in_parent_dirs(path, "Cargo.toml") { 102 match find_in_parent_dirs(path, "Cargo.toml") {
103 Some(it) => Ok(vec![it]), 103 Some(it) => Ok(vec![it]),
104 None => Ok(find_cargo_toml_in_child_dir(read_dir(path)?)), 104 None => Ok(find_cargo_toml_in_child_dir(read_dir(path)?)),
105 } 105 }
106 } 106 }
107 107
108 fn find_in_parent_dirs(path: &Path, target_file_name: &str) -> Option<PathBuf> { 108 fn find_in_parent_dirs(path: &AbsPath, target_file_name: &str) -> Option<AbsPathBuf> {
109 if path.ends_with(target_file_name) { 109 if path.ends_with(target_file_name) {
110 return Some(path.to_owned()); 110 return Some(path.to_path_buf());
111 } 111 }
112 112
113 let mut curr = Some(path); 113 let mut curr = Some(path);
@@ -123,17 +123,18 @@ impl ProjectManifest {
123 None 123 None
124 } 124 }
125 125
126 fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> { 126 fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<AbsPathBuf> {
127 // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects 127 // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
128 entities 128 entities
129 .filter_map(Result::ok) 129 .filter_map(Result::ok)
130 .map(|it| it.path().join("Cargo.toml")) 130 .map(|it| it.path().join("Cargo.toml"))
131 .filter(|it| it.exists()) 131 .filter(|it| it.exists())
132 .map(AbsPathBuf::assert)
132 .collect() 133 .collect()
133 } 134 }
134 } 135 }
135 136
136 pub fn discover_all(paths: &[impl AsRef<Path>]) -> Vec<ProjectManifest> { 137 pub fn discover_all(paths: &[impl AsRef<AbsPath>]) -> Vec<ProjectManifest> {
137 let mut res = paths 138 let mut res = paths
138 .iter() 139 .iter()
139 .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok()) 140 .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok())
@@ -154,20 +155,15 @@ impl ProjectWorkspace {
154 ) -> Result<ProjectWorkspace> { 155 ) -> Result<ProjectWorkspace> {
155 let res = match manifest { 156 let res = match manifest {
156 ProjectManifest::ProjectJson(project_json) => { 157 ProjectManifest::ProjectJson(project_json) => {
157 let file = File::open(&project_json).with_context(|| { 158 let file = fs::read_to_string(&project_json).with_context(|| {
158 format!("Failed to open json file {}", project_json.display()) 159 format!("Failed to read json file {}", project_json.display())
159 })?; 160 })?;
160 let reader = BufReader::new(file); 161 let data = serde_json::from_str(&file).with_context(|| {
161 let project_location = match project_json.parent() { 162 format!("Failed to deserialize json file {}", project_json.display())
162 Some(parent) => PathBuf::from(parent), 163 })?;
163 None => PathBuf::new(), 164 let project_location = project_json.parent().unwrap().to_path_buf();
164 }; 165 let project = ProjectJson::new(&project_location, data);
165 ProjectWorkspace::Json { 166 ProjectWorkspace::Json { project }
166 project: from_reader(reader).with_context(|| {
167 format!("Failed to deserialize json file {}", project_json.display())
168 })?,
169 project_location: project_location,
170 }
171 } 167 }
172 ProjectManifest::CargoToml(cargo_toml) => { 168 ProjectManifest::CargoToml(cargo_toml) => {
173 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) 169 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
@@ -199,11 +195,9 @@ impl ProjectWorkspace {
199 /// the root is a member of the current workspace 195 /// the root is a member of the current workspace
200 pub fn to_roots(&self) -> Vec<PackageRoot> { 196 pub fn to_roots(&self) -> Vec<PackageRoot> {
201 match self { 197 match self {
202 ProjectWorkspace::Json { project, project_location } => project 198 ProjectWorkspace::Json { project } => {
203 .roots 199 project.roots.iter().map(|r| PackageRoot::new_member(r.path.clone())).collect()
204 .iter() 200 }
205 .map(|r| PackageRoot::new_member(project_location.join(&r.path)))
206 .collect(),
207 ProjectWorkspace::Cargo { cargo, sysroot } => cargo 201 ProjectWorkspace::Cargo { cargo, sysroot } => cargo
208 .packages() 202 .packages()
209 .map(|pkg| PackageRoot { 203 .map(|pkg| PackageRoot {
@@ -218,9 +212,9 @@ impl ProjectWorkspace {
218 } 212 }
219 } 213 }
220 214
221 pub fn proc_macro_dylib_paths(&self) -> Vec<PathBuf> { 215 pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> {
222 match self { 216 match self {
223 ProjectWorkspace::Json { project, .. } => project 217 ProjectWorkspace::Json { project } => project
224 .crates 218 .crates
225 .iter() 219 .iter()
226 .filter_map(|krate| krate.proc_macro_dylib_path.as_ref()) 220 .filter_map(|krate| krate.proc_macro_dylib_path.as_ref())
@@ -246,49 +240,26 @@ impl ProjectWorkspace {
246 pub fn to_crate_graph( 240 pub fn to_crate_graph(
247 &self, 241 &self,
248 target: Option<&str>, 242 target: Option<&str>,
249 extern_source_roots: &FxHashMap<PathBuf, ExternSourceId>,
250 proc_macro_client: &ProcMacroClient, 243 proc_macro_client: &ProcMacroClient,
251 load: &mut dyn FnMut(&Path) -> Option<FileId>, 244 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
252 ) -> CrateGraph { 245 ) -> CrateGraph {
253 let mut crate_graph = CrateGraph::default(); 246 let mut crate_graph = CrateGraph::default();
254 match self { 247 match self {
255 ProjectWorkspace::Json { project, project_location } => { 248 ProjectWorkspace::Json { project } => {
256 let crates: FxHashMap<_, _> = project 249 let crates: FxHashMap<_, _> = project
257 .crates 250 .crates
258 .iter() 251 .iter()
259 .enumerate() 252 .enumerate()
260 .filter_map(|(seq_index, krate)| { 253 .filter_map(|(seq_index, krate)| {
261 let file_path = project_location.join(&krate.root_module); 254 let file_path = &krate.root_module;
262 let file_id = load(&file_path)?; 255 let file_id = load(&file_path)?;
263 let edition = match krate.edition {
264 json_project::Edition::Edition2015 => Edition::Edition2015,
265 json_project::Edition::Edition2018 => Edition::Edition2018,
266 };
267 let cfg_options = {
268 let mut opts = CfgOptions::default();
269 for cfg in &krate.cfg {
270 match cfg.find('=') {
271 None => opts.insert_atom(cfg.into()),
272 Some(pos) => {
273 let key = &cfg[..pos];
274 let value = cfg[pos + 1..].trim_matches('"');
275 opts.insert_key_value(key.into(), value.into());
276 }
277 }
278 }
279 opts
280 };
281 256
282 let mut env = Env::default(); 257 let mut env = Env::default();
283 let mut extern_source = ExternSource::default();
284 if let Some(out_dir) = &krate.out_dir { 258 if let Some(out_dir) = &krate.out_dir {
285 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() 259 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
286 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { 260 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
287 env.set("OUT_DIR", out_dir); 261 env.set("OUT_DIR", out_dir);
288 } 262 }
289 if let Some(&extern_source_id) = extern_source_roots.get(out_dir) {
290 extern_source.set_extern_path(&out_dir, extern_source_id);
291 }
292 } 263 }
293 let proc_macro = krate 264 let proc_macro = krate
294 .proc_macro_dylib_path 265 .proc_macro_dylib_path
@@ -296,15 +267,14 @@ impl ProjectWorkspace {
296 .map(|it| proc_macro_client.by_dylib_path(&it)); 267 .map(|it| proc_macro_client.by_dylib_path(&it));
297 // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env 268 // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env
298 Some(( 269 Some((
299 json_project::CrateId(seq_index), 270 CrateId(seq_index as u32),
300 crate_graph.add_crate_root( 271 crate_graph.add_crate_root(
301 file_id, 272 file_id,
302 edition, 273 krate.edition,
303 // FIXME json definitions can store the crate name 274 // FIXME json definitions can store the crate name
304 None, 275 None,
305 cfg_options, 276 krate.cfg.clone(),
306 env, 277 env,
307 extern_source,
308 proc_macro.unwrap_or_default(), 278 proc_macro.unwrap_or_default(),
309 ), 279 ),
310 )) 280 ))
@@ -313,8 +283,8 @@ impl ProjectWorkspace {
313 283
314 for (id, krate) in project.crates.iter().enumerate() { 284 for (id, krate) in project.crates.iter().enumerate() {
315 for dep in &krate.deps { 285 for dep in &krate.deps {
316 let from_crate_id = json_project::CrateId(id); 286 let from_crate_id = CrateId(id as u32);
317 let to_crate_id = dep.krate; 287 let to_crate_id = dep.crate_id;
318 if let (Some(&from), Some(&to)) = 288 if let (Some(&from), Some(&to)) =
319 (crates.get(&from_crate_id), crates.get(&to_crate_id)) 289 (crates.get(&from_crate_id), crates.get(&to_crate_id))
320 { 290 {
@@ -341,7 +311,6 @@ impl ProjectWorkspace {
341 let file_id = load(&sysroot[krate].root)?; 311 let file_id = load(&sysroot[krate].root)?;
342 312
343 let env = Env::default(); 313 let env = Env::default();
344 let extern_source = ExternSource::default();
345 let proc_macro = vec![]; 314 let proc_macro = vec![];
346 let crate_name = CrateName::new(&sysroot[krate].name) 315 let crate_name = CrateName::new(&sysroot[krate].name)
347 .expect("Sysroot crate names should not contain dashes"); 316 .expect("Sysroot crate names should not contain dashes");
@@ -352,7 +321,6 @@ impl ProjectWorkspace {
352 Some(crate_name), 321 Some(crate_name),
353 cfg_options.clone(), 322 cfg_options.clone(),
354 env, 323 env,
355 extern_source,
356 proc_macro, 324 proc_macro,
357 ); 325 );
358 Some((krate, crate_id)) 326 Some((krate, crate_id))
@@ -409,15 +377,11 @@ impl ProjectWorkspace {
409 opts 377 opts
410 }; 378 };
411 let mut env = Env::default(); 379 let mut env = Env::default();
412 let mut extern_source = ExternSource::default();
413 if let Some(out_dir) = &cargo[pkg].out_dir { 380 if let Some(out_dir) = &cargo[pkg].out_dir {
414 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() 381 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
415 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { 382 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
416 env.set("OUT_DIR", out_dir); 383 env.set("OUT_DIR", out_dir);
417 } 384 }
418 if let Some(&extern_source_id) = extern_source_roots.get(out_dir) {
419 extern_source.set_extern_path(&out_dir, extern_source_id);
420 }
421 } 385 }
422 let proc_macro = cargo[pkg] 386 let proc_macro = cargo[pkg]
423 .proc_macro_dylib_path 387 .proc_macro_dylib_path
@@ -431,7 +395,6 @@ impl ProjectWorkspace {
431 Some(CrateName::normalize_dashes(&cargo[pkg].name)), 395 Some(CrateName::normalize_dashes(&cargo[pkg].name)),
432 cfg_options, 396 cfg_options,
433 env, 397 env,
434 extern_source,
435 proc_macro.clone(), 398 proc_macro.clone(),
436 ); 399 );
437 if cargo[tgt].kind == TargetKind::Lib { 400 if cargo[tgt].kind == TargetKind::Lib {
@@ -537,15 +500,15 @@ impl ProjectWorkspace {
537 crate_graph 500 crate_graph
538 } 501 }
539 502
540 pub fn workspace_root_for(&self, path: &Path) -> Option<&Path> { 503 pub fn workspace_root_for(&self, path: &Path) -> Option<&AbsPath> {
541 match self { 504 match self {
542 ProjectWorkspace::Cargo { cargo, .. } => { 505 ProjectWorkspace::Cargo { cargo, .. } => {
543 Some(cargo.workspace_root()).filter(|root| path.starts_with(root)) 506 Some(cargo.workspace_root()).filter(|root| path.starts_with(root))
544 } 507 }
545 ProjectWorkspace::Json { project: JsonProject { roots, .. }, .. } => roots 508 ProjectWorkspace::Json { project: ProjectJson { roots, .. }, .. } => roots
546 .iter() 509 .iter()
547 .find(|root| path.starts_with(&root.path)) 510 .find(|root| path.starts_with(&root.path))
548 .map(|root| root.path.as_ref()), 511 .map(|root| root.path.as_path()),
549 } 512 }
550 } 513 }
551} 514}
diff --git a/crates/ra_project_model/src/project_json.rs b/crates/ra_project_model/src/project_json.rs
new file mode 100644
index 000000000..4b5dcd634
--- /dev/null
+++ b/crates/ra_project_model/src/project_json.rs
@@ -0,0 +1,117 @@
1//! FIXME: write short doc here
2
3use std::path::PathBuf;
4
5use paths::{AbsPath, AbsPathBuf};
6use ra_cfg::CfgOptions;
7use ra_db::{CrateId, Dependency, Edition};
8use rustc_hash::FxHashSet;
9use serde::Deserialize;
10use stdx::split_delim;
11
12/// Roots and crates that compose this Rust project.
13#[derive(Clone, Debug)]
14pub struct ProjectJson {
15 pub(crate) roots: Vec<Root>,
16 pub(crate) crates: Vec<Crate>,
17}
18
19/// A root points to the directory which contains Rust crates. rust-analyzer watches all files in
20/// all roots. Roots might be nested.
21#[derive(Clone, Debug)]
22pub struct Root {
23 pub(crate) path: AbsPathBuf,
24}
25
26/// A crate points to the root module of a crate and lists the dependencies of the crate. This is
27/// useful in creating the crate graph.
28#[derive(Clone, Debug)]
29pub struct Crate {
30 pub(crate) root_module: AbsPathBuf,
31 pub(crate) edition: Edition,
32 pub(crate) deps: Vec<Dependency>,
33 pub(crate) cfg: CfgOptions,
34 pub(crate) out_dir: Option<AbsPathBuf>,
35 pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
36}
37
38impl ProjectJson {
39 pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson {
40 ProjectJson {
41 roots: data.roots.into_iter().map(|path| Root { path: base.join(path) }).collect(),
42 crates: data
43 .crates
44 .into_iter()
45 .map(|crate_data| Crate {
46 root_module: base.join(crate_data.root_module),
47 edition: crate_data.edition.into(),
48 deps: crate_data
49 .deps
50 .into_iter()
51 .map(|dep_data| Dependency {
52 crate_id: CrateId(dep_data.krate as u32),
53 name: dep_data.name.into(),
54 })
55 .collect::<Vec<_>>(),
56 cfg: {
57 let mut cfg = CfgOptions::default();
58 for entry in &crate_data.cfg {
59 match split_delim(entry, '=') {
60 Some((key, value)) => {
61 cfg.insert_key_value(key.into(), value.into());
62 }
63 None => cfg.insert_atom(entry.into()),
64 }
65 }
66 cfg
67 },
68 out_dir: crate_data.out_dir.map(|it| base.join(it)),
69 proc_macro_dylib_path: crate_data.proc_macro_dylib_path.map(|it| base.join(it)),
70 })
71 .collect::<Vec<_>>(),
72 }
73 }
74}
75
76#[derive(Deserialize)]
77pub struct ProjectJsonData {
78 roots: Vec<PathBuf>,
79 crates: Vec<CrateData>,
80}
81
82#[derive(Deserialize)]
83struct CrateData {
84 root_module: PathBuf,
85 edition: EditionData,
86 deps: Vec<DepData>,
87 #[serde(default)]
88 cfg: FxHashSet<String>,
89 out_dir: Option<PathBuf>,
90 proc_macro_dylib_path: Option<PathBuf>,
91}
92
93#[derive(Deserialize)]
94#[serde(rename = "edition")]
95enum EditionData {
96 #[serde(rename = "2015")]
97 Edition2015,
98 #[serde(rename = "2018")]
99 Edition2018,
100}
101
102impl From<EditionData> for Edition {
103 fn from(data: EditionData) -> Self {
104 match data {
105 EditionData::Edition2015 => Edition::Edition2015,
106 EditionData::Edition2018 => Edition::Edition2018,
107 }
108 }
109}
110
111#[derive(Deserialize)]
112struct DepData {
113 /// Identifies a crate by position in the crates array.
114 #[serde(rename = "crate")]
115 krate: usize,
116 name: String,
117}
diff --git a/crates/ra_project_model/src/sysroot.rs b/crates/ra_project_model/src/sysroot.rs
index a8a196e64..943ff92df 100644
--- a/crates/ra_project_model/src/sysroot.rs
+++ b/crates/ra_project_model/src/sysroot.rs
@@ -1,15 +1,12 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::{ 3use std::{convert::TryFrom, env, ops, path::Path, process::Command};
4 env, ops,
5 path::{Path, PathBuf},
6 process::Command,
7};
8 4
9use anyhow::{bail, Result}; 5use anyhow::{bail, format_err, Result};
10use ra_arena::{Arena, Idx}; 6use ra_arena::{Arena, Idx};
11 7
12use crate::output; 8use crate::output;
9use paths::{AbsPath, AbsPathBuf};
13 10
14#[derive(Default, Debug, Clone)] 11#[derive(Default, Debug, Clone)]
15pub struct Sysroot { 12pub struct Sysroot {
@@ -21,7 +18,7 @@ pub type SysrootCrate = Idx<SysrootCrateData>;
21#[derive(Debug, Clone)] 18#[derive(Debug, Clone)]
22pub struct SysrootCrateData { 19pub struct SysrootCrateData {
23 pub name: String, 20 pub name: String,
24 pub root: PathBuf, 21 pub root: AbsPathBuf,
25 pub deps: Vec<SysrootCrate>, 22 pub deps: Vec<SysrootCrate>,
26} 23}
27 24
@@ -53,7 +50,7 @@ impl Sysroot {
53 self.crates.iter().map(|(id, _data)| id) 50 self.crates.iter().map(|(id, _data)| id)
54 } 51 }
55 52
56 pub fn discover(cargo_toml: &Path) -> Result<Sysroot> { 53 pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> {
57 let src = get_or_install_rust_src(cargo_toml)?; 54 let src = get_or_install_rust_src(cargo_toml)?;
58 let mut sysroot = Sysroot { crates: Arena::default() }; 55 let mut sysroot = Sysroot { crates: Arena::default() };
59 for name in SYSROOT_CRATES.trim().lines() { 56 for name in SYSROOT_CRATES.trim().lines() {
@@ -86,16 +83,18 @@ impl Sysroot {
86 } 83 }
87} 84}
88 85
89fn get_or_install_rust_src(cargo_toml: &Path) -> Result<PathBuf> { 86fn get_or_install_rust_src(cargo_toml: &AbsPath) -> Result<AbsPathBuf> {
90 if let Ok(path) = env::var("RUST_SRC_PATH") { 87 if let Ok(path) = env::var("RUST_SRC_PATH") {
91 return Ok(path.into()); 88 let path = AbsPathBuf::try_from(path.as_str())
89 .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?;
90 return Ok(path);
92 } 91 }
93 let current_dir = cargo_toml.parent().unwrap(); 92 let current_dir = cargo_toml.parent().unwrap();
94 let mut rustc = Command::new(ra_toolchain::rustc()); 93 let mut rustc = Command::new(ra_toolchain::rustc());
95 rustc.current_dir(current_dir).args(&["--print", "sysroot"]); 94 rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
96 let rustc_output = output(rustc)?; 95 let rustc_output = output(rustc)?;
97 let stdout = String::from_utf8(rustc_output.stdout)?; 96 let stdout = String::from_utf8(rustc_output.stdout)?;
98 let sysroot_path = Path::new(stdout.trim()); 97 let sysroot_path = AbsPath::assert(Path::new(stdout.trim()));
99 let src_path = sysroot_path.join("lib/rustlib/src/rust/src"); 98 let src_path = sysroot_path.join("lib/rustlib/src/rust/src");
100 99
101 if !src_path.exists() { 100 if !src_path.exists() {
@@ -116,7 +115,7 @@ fn get_or_install_rust_src(cargo_toml: &Path) -> Result<PathBuf> {
116} 115}
117 116
118impl SysrootCrateData { 117impl SysrootCrateData {
119 pub fn root_dir(&self) -> &Path { 118 pub fn root_dir(&self) -> &AbsPath {
120 self.root.parent().unwrap() 119 self.root.parent().unwrap()
121 } 120 }
122} 121}