aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_project_model/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_project_model/src')
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs105
-rw-r--r--crates/ra_project_model/src/lib.rs135
2 files changed, 123 insertions, 117 deletions
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 291594e2a..b50cda06f 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -1,17 +1,18 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::{ 3use std::{
4 env,
5 ffi::OsStr,
4 ops, 6 ops,
5 path::{Path, PathBuf}, 7 path::{Path, PathBuf},
8 process::Command,
6}; 9};
7 10
8use anyhow::{Context, Result}; 11use anyhow::{Context, Result};
9use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId}; 12use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId};
10use ra_arena::{Arena, Idx}; 13use ra_arena::{Arena, Idx};
11use ra_cargo_watch::run_cargo;
12use ra_db::Edition; 14use ra_db::Edition;
13use rustc_hash::FxHashMap; 15use rustc_hash::FxHashMap;
14use serde::Deserialize;
15 16
16/// `CargoWorkspace` represents the logical structure of, well, a Cargo 17/// `CargoWorkspace` represents the logical structure of, well, a Cargo
17/// workspace. It pretty closely mirrors `cargo metadata` output. 18/// workspace. It pretty closely mirrors `cargo metadata` output.
@@ -41,9 +42,8 @@ impl ops::Index<Target> for CargoWorkspace {
41 } 42 }
42} 43}
43 44
44#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] 45#[derive(Clone, Debug, PartialEq, Eq)]
45#[serde(rename_all = "camelCase", default)] 46pub struct CargoConfig {
46pub struct CargoFeatures {
47 /// Do not activate the `default` feature. 47 /// Do not activate the `default` feature.
48 pub no_default_features: bool, 48 pub no_default_features: bool,
49 49
@@ -58,9 +58,9 @@ pub struct CargoFeatures {
58 pub load_out_dirs_from_check: bool, 58 pub load_out_dirs_from_check: bool,
59} 59}
60 60
61impl Default for CargoFeatures { 61impl Default for CargoConfig {
62 fn default() -> Self { 62 fn default() -> Self {
63 CargoFeatures { 63 CargoConfig {
64 no_default_features: false, 64 no_default_features: false,
65 all_features: true, 65 all_features: true,
66 features: Vec::new(), 66 features: Vec::new(),
@@ -75,6 +75,7 @@ pub type Target = Idx<TargetData>;
75 75
76#[derive(Debug, Clone)] 76#[derive(Debug, Clone)]
77pub struct PackageData { 77pub struct PackageData {
78 pub version: String,
78 pub name: String, 79 pub name: String,
79 pub manifest: PathBuf, 80 pub manifest: PathBuf,
80 pub targets: Vec<Target>, 81 pub targets: Vec<Target>,
@@ -138,7 +139,7 @@ impl PackageData {
138impl CargoWorkspace { 139impl CargoWorkspace {
139 pub fn from_cargo_metadata( 140 pub fn from_cargo_metadata(
140 cargo_toml: &Path, 141 cargo_toml: &Path,
141 cargo_features: &CargoFeatures, 142 cargo_features: &CargoConfig,
142 ) -> Result<CargoWorkspace> { 143 ) -> Result<CargoWorkspace> {
143 let mut meta = MetadataCommand::new(); 144 let mut meta = MetadataCommand::new();
144 meta.manifest_path(cargo_toml); 145 meta.manifest_path(cargo_toml);
@@ -161,7 +162,7 @@ impl CargoWorkspace {
161 let mut out_dir_by_id = FxHashMap::default(); 162 let mut out_dir_by_id = FxHashMap::default();
162 let mut proc_macro_dylib_paths = FxHashMap::default(); 163 let mut proc_macro_dylib_paths = FxHashMap::default();
163 if cargo_features.load_out_dirs_from_check { 164 if cargo_features.load_out_dirs_from_check {
164 let resources = load_extern_resources(cargo_toml, cargo_features); 165 let resources = load_extern_resources(cargo_toml, cargo_features)?;
165 out_dir_by_id = resources.out_dirs; 166 out_dir_by_id = resources.out_dirs;
166 proc_macro_dylib_paths = resources.proc_dylib_paths; 167 proc_macro_dylib_paths = resources.proc_dylib_paths;
167 } 168 }
@@ -173,13 +174,15 @@ impl CargoWorkspace {
173 let ws_members = &meta.workspace_members; 174 let ws_members = &meta.workspace_members;
174 175
175 for meta_pkg in meta.packages { 176 for meta_pkg in meta.packages {
176 let cargo_metadata::Package { id, edition, name, manifest_path, .. } = meta_pkg; 177 let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } =
178 meta_pkg;
177 let is_member = ws_members.contains(&id); 179 let is_member = ws_members.contains(&id);
178 let edition = edition 180 let edition = edition
179 .parse::<Edition>() 181 .parse::<Edition>()
180 .with_context(|| format!("Failed to parse edition {}", edition))?; 182 .with_context(|| format!("Failed to parse edition {}", edition))?;
181 let pkg = packages.alloc(PackageData { 183 let pkg = packages.alloc(PackageData {
182 name, 184 name,
185 version: version.to_string(),
183 manifest: manifest_path, 186 manifest: manifest_path,
184 targets: Vec::new(), 187 targets: Vec::new(),
185 is_member, 188 is_member,
@@ -249,6 +252,18 @@ impl CargoWorkspace {
249 pub fn workspace_root(&self) -> &Path { 252 pub fn workspace_root(&self) -> &Path {
250 &self.workspace_root 253 &self.workspace_root
251 } 254 }
255
256 pub fn package_flag(&self, package: &PackageData) -> String {
257 if self.is_unique(&*package.name) {
258 package.name.clone()
259 } else {
260 format!("{}:{}", package.name, package.version)
261 }
262 }
263
264 fn is_unique(&self, name: &str) -> bool {
265 self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
266 }
252} 267}
253 268
254#[derive(Debug, Clone, Default)] 269#[derive(Debug, Clone, Default)]
@@ -257,48 +272,60 @@ pub struct ExternResources {
257 proc_dylib_paths: FxHashMap<PackageId, PathBuf>, 272 proc_dylib_paths: FxHashMap<PackageId, PathBuf>,
258} 273}
259 274
260pub fn load_extern_resources(cargo_toml: &Path, cargo_features: &CargoFeatures) -> ExternResources { 275pub fn load_extern_resources(
261 let mut args: Vec<String> = vec![ 276 cargo_toml: &Path,
262 "check".to_string(), 277 cargo_features: &CargoConfig,
263 "--message-format=json".to_string(), 278) -> Result<ExternResources> {
264 "--manifest-path".to_string(), 279 let mut cmd = Command::new(cargo_binary());
265 cargo_toml.display().to_string(), 280 cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml);
266 ];
267
268 if cargo_features.all_features { 281 if cargo_features.all_features {
269 args.push("--all-features".to_string()); 282 cmd.arg("--all-features");
270 } else if cargo_features.no_default_features { 283 } else if cargo_features.no_default_features {
271 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` 284 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
272 // https://github.com/oli-obk/cargo_metadata/issues/79 285 // https://github.com/oli-obk/cargo_metadata/issues/79
273 args.push("--no-default-features".to_string()); 286 cmd.arg("--no-default-features");
274 } else { 287 } else {
275 args.extend(cargo_features.features.iter().cloned()); 288 cmd.args(&cargo_features.features);
276 } 289 }
277 290
278 let mut acc = ExternResources::default(); 291 let output = cmd.output()?;
279 let res = run_cargo(&args, cargo_toml.parent(), &mut |message| {
280 match message {
281 Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => {
282 acc.out_dirs.insert(package_id, out_dir);
283 }
284 292
285 Message::CompilerArtifact(message) => { 293 let mut res = ExternResources::default();
286 if message.target.kind.contains(&"proc-macro".to_string()) { 294
287 let package_id = message.package_id; 295 for message in cargo_metadata::parse_messages(output.stdout.as_slice()) {
288 if let Some(filename) = message.filenames.get(0) { 296 if let Ok(message) = message {
289 acc.proc_dylib_paths.insert(package_id, filename.clone()); 297 match message {
298 Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => {
299 res.out_dirs.insert(package_id, out_dir);
300 }
301
302 Message::CompilerArtifact(message) => {
303 if message.target.kind.contains(&"proc-macro".to_string()) {
304 let package_id = message.package_id;
305 // Skip rmeta file
306 if let Some(filename) =
307 message.filenames.iter().filter(|name| is_dylib(name)).next()
308 {
309 res.proc_dylib_paths.insert(package_id, filename.clone());
310 }
290 } 311 }
291 } 312 }
313 Message::CompilerMessage(_) => (),
314 Message::Unknown => (),
292 } 315 }
293 Message::CompilerMessage(_) => (),
294 Message::Unknown => (),
295 } 316 }
296 true 317 }
297 }); 318 Ok(res)
319}
298 320
299 if let Err(err) = res { 321// FIXME: File a better way to know if it is a dylib
300 log::error!("Failed to load outdirs: {:?}", err); 322fn is_dylib(path: &Path) -> bool {
323 match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) {
324 None => false,
325 Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
301 } 326 }
327}
302 328
303 acc 329fn cargo_binary() -> String {
330 env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
304} 331}
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 444d3bb3f..0ab64a1e0 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -19,7 +19,7 @@ use rustc_hash::FxHashMap;
19use serde_json::from_reader; 19use serde_json::from_reader;
20 20
21pub use crate::{ 21pub use crate::{
22 cargo_workspace::{CargoFeatures, CargoWorkspace, Package, Target, TargetKind}, 22 cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind},
23 json_project::JsonProject, 23 json_project::JsonProject,
24 sysroot::Sysroot, 24 sysroot::Sysroot,
25}; 25};
@@ -62,30 +62,30 @@ pub struct PackageRoot {
62 /// Is a member of the current workspace 62 /// Is a member of the current workspace
63 is_member: bool, 63 is_member: bool,
64} 64}
65
66impl PackageRoot { 65impl PackageRoot {
67 pub fn new(path: PathBuf, is_member: bool) -> PackageRoot { 66 pub fn new_member(path: PathBuf) -> PackageRoot {
68 PackageRoot { path, is_member } 67 Self { path, is_member: true }
69 } 68 }
70 69 pub fn new_non_member(path: PathBuf) -> PackageRoot {
71 pub fn path(&self) -> &PathBuf { 70 Self { path, is_member: false }
71 }
72 pub fn path(&self) -> &Path {
72 &self.path 73 &self.path
73 } 74 }
74
75 pub fn is_member(&self) -> bool { 75 pub fn is_member(&self) -> bool {
76 self.is_member 76 self.is_member
77 } 77 }
78} 78}
79 79
80impl ProjectWorkspace { 80impl ProjectWorkspace {
81 pub fn discover(path: &Path, cargo_features: &CargoFeatures) -> Result<ProjectWorkspace> { 81 pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> {
82 ProjectWorkspace::discover_with_sysroot(path, true, cargo_features) 82 ProjectWorkspace::discover_with_sysroot(path, true, cargo_features)
83 } 83 }
84 84
85 pub fn discover_with_sysroot( 85 pub fn discover_with_sysroot(
86 path: &Path, 86 path: &Path,
87 with_sysroot: bool, 87 with_sysroot: bool,
88 cargo_features: &CargoFeatures, 88 cargo_features: &CargoConfig,
89 ) -> Result<ProjectWorkspace> { 89 ) -> Result<ProjectWorkspace> {
90 match find_rust_project_json(path) { 90 match find_rust_project_json(path) {
91 Some(json_path) => { 91 Some(json_path) => {
@@ -130,70 +130,45 @@ impl ProjectWorkspace {
130 pub fn to_roots(&self) -> Vec<PackageRoot> { 130 pub fn to_roots(&self) -> Vec<PackageRoot> {
131 match self { 131 match self {
132 ProjectWorkspace::Json { project } => { 132 ProjectWorkspace::Json { project } => {
133 let mut roots = Vec::with_capacity(project.roots.len()); 133 project.roots.iter().map(|r| PackageRoot::new_member(r.path.clone())).collect()
134 for root in &project.roots {
135 roots.push(PackageRoot::new(root.path.clone(), true));
136 }
137 roots
138 }
139 ProjectWorkspace::Cargo { cargo, sysroot } => {
140 let mut roots = Vec::with_capacity(cargo.packages().len() + sysroot.crates().len());
141 for pkg in cargo.packages() {
142 let root = cargo[pkg].root().to_path_buf();
143 let member = cargo[pkg].is_member;
144 roots.push(PackageRoot::new(root, member));
145 }
146 for krate in sysroot.crates() {
147 roots.push(PackageRoot::new(sysroot[krate].root_dir().to_path_buf(), false))
148 }
149 roots
150 } 134 }
135 ProjectWorkspace::Cargo { cargo, sysroot } => cargo
136 .packages()
137 .map(|pkg| PackageRoot {
138 path: cargo[pkg].root().to_path_buf(),
139 is_member: cargo[pkg].is_member,
140 })
141 .chain(sysroot.crates().map(|krate| {
142 PackageRoot::new_non_member(sysroot[krate].root_dir().to_path_buf())
143 }))
144 .collect(),
151 } 145 }
152 } 146 }
153 147
154 pub fn out_dirs(&self) -> Vec<PathBuf> { 148 pub fn out_dirs(&self) -> Vec<PathBuf> {
155 match self { 149 match self {
156 ProjectWorkspace::Json { project } => { 150 ProjectWorkspace::Json { project } => {
157 let mut out_dirs = Vec::with_capacity(project.crates.len()); 151 project.crates.iter().filter_map(|krate| krate.out_dir.as_ref()).cloned().collect()
158 for krate in &project.crates {
159 if let Some(out_dir) = &krate.out_dir {
160 out_dirs.push(out_dir.to_path_buf());
161 }
162 }
163 out_dirs
164 } 152 }
165 ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => { 153 ProjectWorkspace::Cargo { cargo, sysroot: _ } => {
166 let mut out_dirs = Vec::with_capacity(cargo.packages().len()); 154 cargo.packages().filter_map(|pkg| cargo[pkg].out_dir.as_ref()).cloned().collect()
167 for pkg in cargo.packages() {
168 if let Some(out_dir) = &cargo[pkg].out_dir {
169 out_dirs.push(out_dir.to_path_buf());
170 }
171 }
172 out_dirs
173 } 155 }
174 } 156 }
175 } 157 }
176 158
177 pub fn proc_macro_dylib_paths(&self) -> Vec<PathBuf> { 159 pub fn proc_macro_dylib_paths(&self) -> Vec<PathBuf> {
178 match self { 160 match self {
179 ProjectWorkspace::Json { project } => { 161 ProjectWorkspace::Json { project } => project
180 let mut proc_macro_dylib_paths = Vec::with_capacity(project.crates.len()); 162 .crates
181 for krate in &project.crates { 163 .iter()
182 if let Some(out_dir) = &krate.proc_macro_dylib_path { 164 .filter_map(|krate| krate.proc_macro_dylib_path.as_ref())
183 proc_macro_dylib_paths.push(out_dir.to_path_buf()); 165 .cloned()
184 } 166 .collect(),
185 } 167 ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => cargo
186 proc_macro_dylib_paths 168 .packages()
187 } 169 .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref())
188 ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => { 170 .cloned()
189 let mut proc_macro_dylib_paths = Vec::with_capacity(cargo.packages().len()); 171 .collect(),
190 for pkg in cargo.packages() {
191 if let Some(dylib_path) = &cargo[pkg].proc_macro_dylib_path {
192 proc_macro_dylib_paths.push(dylib_path.to_path_buf());
193 }
194 }
195 proc_macro_dylib_paths
196 }
197 } 172 }
198 } 173 }
199 174
@@ -216,10 +191,12 @@ impl ProjectWorkspace {
216 let mut crate_graph = CrateGraph::default(); 191 let mut crate_graph = CrateGraph::default();
217 match self { 192 match self {
218 ProjectWorkspace::Json { project } => { 193 ProjectWorkspace::Json { project } => {
219 let mut crates = FxHashMap::default(); 194 let crates: FxHashMap<_, _> = project
220 for (id, krate) in project.crates.iter().enumerate() { 195 .crates
221 let crate_id = json_project::CrateId(id); 196 .iter()
222 if let Some(file_id) = load(&krate.root_module) { 197 .enumerate()
198 .filter_map(|(seq_index, krate)| {
199 let file_id = load(&krate.root_module)?;
223 let edition = match krate.edition { 200 let edition = match krate.edition {
224 json_project::Edition::Edition2015 => Edition::Edition2015, 201 json_project::Edition::Edition2015 => Edition::Edition2015,
225 json_project::Edition::Edition2018 => Edition::Edition2018, 202 json_project::Edition::Edition2018 => Edition::Edition2018,
@@ -249,8 +226,8 @@ impl ProjectWorkspace {
249 .clone() 226 .clone()
250 .map(|it| proc_macro_client.by_dylib_path(&it)); 227 .map(|it| proc_macro_client.by_dylib_path(&it));
251 // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env 228 // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env
252 crates.insert( 229 Some((
253 crate_id, 230 json_project::CrateId(seq_index),
254 crate_graph.add_crate_root( 231 crate_graph.add_crate_root(
255 file_id, 232 file_id,
256 edition, 233 edition,
@@ -261,9 +238,9 @@ impl ProjectWorkspace {
261 extern_source, 238 extern_source,
262 proc_macro.unwrap_or_default(), 239 proc_macro.unwrap_or_default(),
263 ), 240 ),
264 ); 241 ))
265 } 242 })
266 } 243 .collect();
267 244
268 for (id, krate) in project.crates.iter().enumerate() { 245 for (id, krate) in project.crates.iter().enumerate() {
269 for dep in &krate.deps { 246 for dep in &krate.deps {
@@ -287,9 +264,11 @@ impl ProjectWorkspace {
287 } 264 }
288 } 265 }
289 ProjectWorkspace::Cargo { cargo, sysroot } => { 266 ProjectWorkspace::Cargo { cargo, sysroot } => {
290 let mut sysroot_crates = FxHashMap::default(); 267 let sysroot_crates: FxHashMap<_, _> = sysroot
291 for krate in sysroot.crates() { 268 .crates()
292 if let Some(file_id) = load(&sysroot[krate].root) { 269 .filter_map(|krate| {
270 let file_id = load(&sysroot[krate].root)?;
271
293 // Crates from sysroot have `cfg(test)` disabled 272 // Crates from sysroot have `cfg(test)` disabled
294 let cfg_options = { 273 let cfg_options = {
295 let mut opts = default_cfg_options.clone(); 274 let mut opts = default_cfg_options.clone();
@@ -300,22 +279,22 @@ impl ProjectWorkspace {
300 let env = Env::default(); 279 let env = Env::default();
301 let extern_source = ExternSource::default(); 280 let extern_source = ExternSource::default();
302 let proc_macro = vec![]; 281 let proc_macro = vec![];
282 let crate_name = CrateName::new(&sysroot[krate].name)
283 .expect("Sysroot crate names should not contain dashes");
303 284
304 let crate_id = crate_graph.add_crate_root( 285 let crate_id = crate_graph.add_crate_root(
305 file_id, 286 file_id,
306 Edition::Edition2018, 287 Edition::Edition2018,
307 Some( 288 Some(crate_name),
308 CrateName::new(&sysroot[krate].name)
309 .expect("Sysroot crate names should not contain dashes"),
310 ),
311 cfg_options, 289 cfg_options,
312 env, 290 env,
313 extern_source, 291 extern_source,
314 proc_macro, 292 proc_macro,
315 ); 293 );
316 sysroot_crates.insert(krate, crate_id); 294 Some((krate, crate_id))
317 } 295 })
318 } 296 .collect();
297
319 for from in sysroot.crates() { 298 for from in sysroot.crates() {
320 for &to in sysroot[from].deps.iter() { 299 for &to in sysroot[from].deps.iter() {
321 let name = &sysroot[to].name; 300 let name = &sysroot[to].name;