aboutsummaryrefslogtreecommitdiff
path: root/crates/project_model
diff options
context:
space:
mode:
Diffstat (limited to 'crates/project_model')
-rw-r--r--crates/project_model/Cargo.toml10
-rw-r--r--crates/project_model/src/build_data.rs254
-rw-r--r--crates/project_model/src/cargo_workspace.rs249
-rw-r--r--crates/project_model/src/lib.rs6
-rw-r--r--crates/project_model/src/sysroot.rs40
-rw-r--r--crates/project_model/src/workspace.rs79
6 files changed, 399 insertions, 239 deletions
diff --git a/crates/project_model/Cargo.toml b/crates/project_model/Cargo.toml
index 293cb5bfe..fe3258332 100644
--- a/crates/project_model/Cargo.toml
+++ b/crates/project_model/Cargo.toml
@@ -12,7 +12,7 @@ doctest = false
12[dependencies] 12[dependencies]
13log = "0.4.8" 13log = "0.4.8"
14rustc-hash = "1.1.0" 14rustc-hash = "1.1.0"
15cargo_metadata = "0.12.2" 15cargo_metadata = "0.13"
16serde = { version = "1.0.106", features = ["derive"] } 16serde = { version = "1.0.106", features = ["derive"] }
17serde_json = "1.0.48" 17serde_json = "1.0.48"
18anyhow = "1.0.26" 18anyhow = "1.0.26"
@@ -22,7 +22,7 @@ la-arena = { version = "0.2.0", path = "../../lib/arena" }
22cfg = { path = "../cfg", version = "0.0.0" } 22cfg = { path = "../cfg", version = "0.0.0" }
23base_db = { path = "../base_db", version = "0.0.0" } 23base_db = { path = "../base_db", version = "0.0.0" }
24toolchain = { path = "../toolchain", version = "0.0.0" } 24toolchain = { path = "../toolchain", version = "0.0.0" }
25proc_macro_api = { path = "../proc_macro_api", version = "0.0.0" } 25proc_macro_api = { path = "../proc_macro_api", version = "0.0.0" }
26paths = { path = "../paths", version = "0.0.0" } 26paths = { path = "../paths", version = "0.0.0" }
27stdx = { path = "../stdx", version = "0.0.0" } 27stdx = { path = "../stdx", version = "0.0.0" }
28profile = { path = "../profile", version = "0.0.0" } 28profile = { path = "../profile", version = "0.0.0" }
diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs
new file mode 100644
index 000000000..728a258ea
--- /dev/null
+++ b/crates/project_model/src/build_data.rs
@@ -0,0 +1,254 @@
1//! Handles build script specific information
2
3use std::{
4 io::BufReader,
5 path::PathBuf,
6 process::{Command, Stdio},
7 sync::Arc,
8};
9
10use anyhow::Result;
11use cargo_metadata::camino::Utf8Path;
12use cargo_metadata::{BuildScript, Message};
13use itertools::Itertools;
14use paths::{AbsPath, AbsPathBuf};
15use rustc_hash::FxHashMap;
16use stdx::JodChild;
17
18use crate::{cfg_flag::CfgFlag, CargoConfig};
19
20#[derive(Debug, Clone, Default, PartialEq, Eq)]
21pub(crate) struct BuildData {
22 /// List of config flags defined by this package's build script
23 pub(crate) cfgs: Vec<CfgFlag>,
24 /// List of cargo-related environment variables with their value
25 ///
26 /// If the package has a build script which defines environment variables,
27 /// they can also be found here.
28 pub(crate) envs: Vec<(String, String)>,
29 /// Directory where a build script might place its output
30 pub(crate) out_dir: Option<AbsPathBuf>,
31 /// Path to the proc-macro library file if this package exposes proc-macros
32 pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
33}
34
35#[derive(Clone, Debug)]
36pub(crate) struct BuildDataConfig {
37 cargo_toml: AbsPathBuf,
38 cargo_features: CargoConfig,
39 packages: Arc<Vec<cargo_metadata::Package>>,
40}
41
42impl PartialEq for BuildDataConfig {
43 fn eq(&self, other: &Self) -> bool {
44 Arc::ptr_eq(&self.packages, &other.packages)
45 }
46}
47
48impl Eq for BuildDataConfig {}
49
50#[derive(Debug, Default)]
51pub struct BuildDataCollector {
52 configs: FxHashMap<AbsPathBuf, BuildDataConfig>,
53}
54
55#[derive(Debug, Default, PartialEq, Eq)]
56pub struct BuildDataResult {
57 data: FxHashMap<AbsPathBuf, BuildDataMap>,
58}
59
60pub(crate) type BuildDataMap = FxHashMap<String, BuildData>;
61
62impl BuildDataCollector {
63 pub(crate) fn add_config(&mut self, workspace_root: &AbsPath, config: BuildDataConfig) {
64 self.configs.insert(workspace_root.to_path_buf(), config);
65 }
66
67 pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> {
68 let mut res = BuildDataResult::default();
69 for (path, config) in self.configs.iter() {
70 res.data.insert(
71 path.clone(),
72 collect_from_workspace(
73 &config.cargo_toml,
74 &config.cargo_features,
75 &config.packages,
76 progress,
77 )?,
78 );
79 }
80 Ok(res)
81 }
82}
83
84impl BuildDataResult {
85 pub(crate) fn get(&self, root: &AbsPath) -> Option<&BuildDataMap> {
86 self.data.get(&root.to_path_buf())
87 }
88}
89
90impl BuildDataConfig {
91 pub(crate) fn new(
92 cargo_toml: AbsPathBuf,
93 cargo_features: CargoConfig,
94 packages: Arc<Vec<cargo_metadata::Package>>,
95 ) -> Self {
96 Self { cargo_toml, cargo_features, packages }
97 }
98}
99
100fn collect_from_workspace(
101 cargo_toml: &AbsPath,
102 cargo_features: &CargoConfig,
103 packages: &Vec<cargo_metadata::Package>,
104 progress: &dyn Fn(String),
105) -> Result<BuildDataMap> {
106 let mut cmd = Command::new(toolchain::cargo());
107 cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"])
108 .arg(cargo_toml.as_ref());
109
110 // --all-targets includes tests, benches and examples in addition to the
111 // default lib and bins. This is an independent concept from the --targets
112 // flag below.
113 cmd.arg("--all-targets");
114
115 if let Some(target) = &cargo_features.target {
116 cmd.args(&["--target", target]);
117 }
118
119 if cargo_features.all_features {
120 cmd.arg("--all-features");
121 } else {
122 if cargo_features.no_default_features {
123 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
124 // https://github.com/oli-obk/cargo_metadata/issues/79
125 cmd.arg("--no-default-features");
126 }
127 if !cargo_features.features.is_empty() {
128 cmd.arg("--features");
129 cmd.arg(cargo_features.features.join(" "));
130 }
131 }
132
133 cmd.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null());
134
135 let mut child = cmd.spawn().map(JodChild)?;
136 let child_stdout = child.stdout.take().unwrap();
137 let stdout = BufReader::new(child_stdout);
138
139 let mut res = BuildDataMap::default();
140 for message in cargo_metadata::Message::parse_stream(stdout) {
141 if let Ok(message) = message {
142 match message {
143 Message::BuildScriptExecuted(BuildScript {
144 package_id,
145 out_dir,
146 cfgs,
147 env,
148 ..
149 }) => {
150 let cfgs = {
151 let mut acc = Vec::new();
152 for cfg in cfgs {
153 match cfg.parse::<CfgFlag>() {
154 Ok(it) => acc.push(it),
155 Err(err) => {
156 anyhow::bail!("invalid cfg from cargo-metadata: {}", err)
157 }
158 };
159 }
160 acc
161 };
162 let res = res.entry(package_id.repr.clone()).or_default();
163 // cargo_metadata crate returns default (empty) path for
164 // older cargos, which is not absolute, so work around that.
165 if !out_dir.as_str().is_empty() {
166 let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string()));
167 res.out_dir = Some(out_dir);
168 res.cfgs = cfgs;
169 }
170
171 res.envs = env;
172 }
173 Message::CompilerArtifact(message) => {
174 progress(format!("metadata {}", message.target.name));
175
176 if message.target.kind.contains(&"proc-macro".to_string()) {
177 let package_id = message.package_id;
178 // Skip rmeta file
179 if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name))
180 {
181 let filename = AbsPathBuf::assert(PathBuf::from(&filename));
182 let res = res.entry(package_id.repr.clone()).or_default();
183 res.proc_macro_dylib_path = Some(filename);
184 }
185 }
186 }
187 Message::CompilerMessage(message) => {
188 progress(message.target.name.clone());
189 }
190 Message::BuildFinished(_) => {}
191 Message::TextLine(_) => {}
192 _ => {}
193 }
194 }
195 }
196
197 for package in packages {
198 let build_data = res.entry(package.id.repr.clone()).or_default();
199 inject_cargo_env(package, build_data);
200 if let Some(out_dir) = &build_data.out_dir {
201 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
202 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
203 build_data.envs.push(("OUT_DIR".to_string(), out_dir));
204 }
205 }
206 }
207
208 Ok(res)
209}
210
211// FIXME: File a better way to know if it is a dylib
212fn is_dylib(path: &Utf8Path) -> bool {
213 match path.extension().map(|e| e.to_string().to_lowercase()) {
214 None => false,
215 Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
216 }
217}
218
219/// Recreates the compile-time environment variables that Cargo sets.
220///
221/// Should be synced with <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
222fn inject_cargo_env(package: &cargo_metadata::Package, build_data: &mut BuildData) {
223 let env = &mut build_data.envs;
224
225 // FIXME: Missing variables:
226 // CARGO_PKG_HOMEPAGE, CARGO_CRATE_NAME, CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
227
228 let mut manifest_dir = package.manifest_path.clone();
229 manifest_dir.pop();
230 env.push(("CARGO_MANIFEST_DIR".into(), manifest_dir.into_string()));
231
232 // Not always right, but works for common cases.
233 env.push(("CARGO".into(), "cargo".into()));
234
235 env.push(("CARGO_PKG_VERSION".into(), package.version.to_string()));
236 env.push(("CARGO_PKG_VERSION_MAJOR".into(), package.version.major.to_string()));
237 env.push(("CARGO_PKG_VERSION_MINOR".into(), package.version.minor.to_string()));
238 env.push(("CARGO_PKG_VERSION_PATCH".into(), package.version.patch.to_string()));
239
240 let pre = package.version.pre.iter().map(|id| id.to_string()).format(".");
241 env.push(("CARGO_PKG_VERSION_PRE".into(), pre.to_string()));
242
243 let authors = package.authors.join(";");
244 env.push(("CARGO_PKG_AUTHORS".into(), authors));
245
246 env.push(("CARGO_PKG_NAME".into(), package.name.clone()));
247 env.push(("CARGO_PKG_DESCRIPTION".into(), package.description.clone().unwrap_or_default()));
248 //env.push(("CARGO_PKG_HOMEPAGE".into(), package.homepage.clone().unwrap_or_default()));
249 env.push(("CARGO_PKG_REPOSITORY".into(), package.repository.clone().unwrap_or_default()));
250 env.push(("CARGO_PKG_LICENSE".into(), package.license.clone().unwrap_or_default()));
251
252 let license_file = package.license_file.as_ref().map(|buf| buf.to_string()).unwrap_or_default();
253 env.push(("CARGO_PKG_LICENSE_FILE".into(), license_file));
254}
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs
index c0ed37fc1..f7241b711 100644
--- a/crates/project_model/src/cargo_workspace.rs
+++ b/crates/project_model/src/cargo_workspace.rs
@@ -1,24 +1,16 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::{ 3use std::path::PathBuf;
4 convert::TryInto, 4use std::{convert::TryInto, ops, process::Command, sync::Arc};
5 ffi::OsStr,
6 io::BufReader,
7 ops,
8 path::{Path, PathBuf},
9 process::{Command, Stdio},
10};
11 5
12use anyhow::{Context, Result}; 6use anyhow::{Context, Result};
13use base_db::Edition; 7use base_db::Edition;
14use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId}; 8use cargo_metadata::{CargoOpt, MetadataCommand};
15use itertools::Itertools;
16use la_arena::{Arena, Idx}; 9use la_arena::{Arena, Idx};
17use paths::{AbsPath, AbsPathBuf}; 10use paths::{AbsPath, AbsPathBuf};
18use rustc_hash::FxHashMap; 11use rustc_hash::FxHashMap;
19use stdx::JodChild;
20 12
21use crate::cfg_flag::CfgFlag; 13use crate::build_data::BuildDataConfig;
22use crate::utf8_stdout; 14use crate::utf8_stdout;
23 15
24/// `CargoWorkspace` represents the logical structure of, well, a Cargo 16/// `CargoWorkspace` represents the logical structure of, well, a Cargo
@@ -36,6 +28,7 @@ pub struct CargoWorkspace {
36 packages: Arena<PackageData>, 28 packages: Arena<PackageData>,
37 targets: Arena<TargetData>, 29 targets: Arena<TargetData>,
38 workspace_root: AbsPathBuf, 30 workspace_root: AbsPathBuf,
31 build_data_config: BuildDataConfig,
39} 32}
40 33
41impl ops::Index<Package> for CargoWorkspace { 34impl ops::Index<Package> for CargoWorkspace {
@@ -52,6 +45,15 @@ impl ops::Index<Target> for CargoWorkspace {
52 } 45 }
53} 46}
54 47
48/// Describes how to set the rustc source directory.
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub enum RustcSource {
51 /// Explicit path for the rustc source directory.
52 Path(AbsPathBuf),
53 /// Try to automatically detect where the rustc source directory is.
54 Discover,
55}
56
55#[derive(Default, Clone, Debug, PartialEq, Eq)] 57#[derive(Default, Clone, Debug, PartialEq, Eq)]
56pub struct CargoConfig { 58pub struct CargoConfig {
57 /// Do not activate the `default` feature. 59 /// Do not activate the `default` feature.
@@ -64,9 +66,6 @@ pub struct CargoConfig {
64 /// This will be ignored if `cargo_all_features` is true. 66 /// This will be ignored if `cargo_all_features` is true.
65 pub features: Vec<String>, 67 pub features: Vec<String>,
66 68
67 /// Runs cargo check on launch to figure out the correct values of OUT_DIR
68 pub load_out_dirs_from_check: bool,
69
70 /// rustc target 69 /// rustc target
71 pub target: Option<String>, 70 pub target: Option<String>,
72 71
@@ -75,7 +74,7 @@ pub struct CargoConfig {
75 pub no_sysroot: bool, 74 pub no_sysroot: bool,
76 75
77 /// rustc private crate source 76 /// rustc private crate source
78 pub rustc_source: Option<AbsPathBuf>, 77 pub rustc_source: Option<RustcSource>,
79} 78}
80 79
81pub type Package = Idx<PackageData>; 80pub type Package = Idx<PackageData>;
@@ -99,19 +98,12 @@ pub struct PackageData {
99 pub dependencies: Vec<PackageDependency>, 98 pub dependencies: Vec<PackageDependency>,
100 /// Rust edition for this package 99 /// Rust edition for this package
101 pub edition: Edition, 100 pub edition: Edition,
102 /// List of features to activate 101 /// Features provided by the crate, mapped to the features required by that feature.
103 pub features: Vec<String>, 102 pub features: FxHashMap<String, Vec<String>>,
104 /// List of config flags defined by this package's build script 103 /// List of features enabled on this package
105 pub cfgs: Vec<CfgFlag>, 104 pub active_features: Vec<String>,
106 /// List of cargo-related environment variables with their value 105 // String representation of package id
107 /// 106 pub id: String,
108 /// If the package has a build script which defines environment variables,
109 /// they can also be found here.
110 pub envs: Vec<(String, String)>,
111 /// Directory where a build script might place its output
112 pub out_dir: Option<AbsPathBuf>,
113 /// Path to the proc-macro library file if this package exposes proc-macros
114 pub proc_macro_dylib_path: Option<AbsPathBuf>,
115} 107}
116 108
117#[derive(Debug, Clone, Eq, PartialEq)] 109#[derive(Debug, Clone, Eq, PartialEq)]
@@ -244,18 +236,6 @@ impl CargoWorkspace {
244 ) 236 )
245 })?; 237 })?;
246 238
247 let mut out_dir_by_id = FxHashMap::default();
248 let mut cfgs = FxHashMap::default();
249 let mut envs = FxHashMap::default();
250 let mut proc_macro_dylib_paths = FxHashMap::default();
251 if config.load_out_dirs_from_check {
252 let resources = load_extern_resources(cargo_toml, config, progress)?;
253 out_dir_by_id = resources.out_dirs;
254 cfgs = resources.cfgs;
255 envs = resources.env;
256 proc_macro_dylib_paths = resources.proc_dylib_paths;
257 }
258
259 let mut pkg_by_id = FxHashMap::default(); 239 let mut pkg_by_id = FxHashMap::default();
260 let mut packages = Arena::default(); 240 let mut packages = Arena::default();
261 let mut targets = Arena::default(); 241 let mut targets = Arena::default();
@@ -263,38 +243,34 @@ impl CargoWorkspace {
263 let ws_members = &meta.workspace_members; 243 let ws_members = &meta.workspace_members;
264 244
265 meta.packages.sort_by(|a, b| a.id.cmp(&b.id)); 245 meta.packages.sort_by(|a, b| a.id.cmp(&b.id));
266 for meta_pkg in meta.packages { 246 for meta_pkg in &meta.packages {
267 let id = meta_pkg.id.clone();
268 inject_cargo_env(&meta_pkg, envs.entry(id).or_default());
269
270 let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } = 247 let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } =
271 meta_pkg; 248 meta_pkg;
272 let is_member = ws_members.contains(&id); 249 let is_member = ws_members.contains(&id);
273 let edition = edition 250 let edition = edition
274 .parse::<Edition>() 251 .parse::<Edition>()
275 .with_context(|| format!("Failed to parse edition {}", edition))?; 252 .with_context(|| format!("Failed to parse edition {}", edition))?;
253
276 let pkg = packages.alloc(PackageData { 254 let pkg = packages.alloc(PackageData {
277 name, 255 id: id.repr.clone(),
256 name: name.clone(),
278 version: version.to_string(), 257 version: version.to_string(),
279 manifest: AbsPathBuf::assert(manifest_path), 258 manifest: AbsPathBuf::assert(PathBuf::from(&manifest_path)),
280 targets: Vec::new(), 259 targets: Vec::new(),
281 is_member, 260 is_member,
282 edition, 261 edition,
283 dependencies: Vec::new(), 262 dependencies: Vec::new(),
284 features: Vec::new(), 263 features: meta_pkg.features.clone().into_iter().collect(),
285 cfgs: cfgs.get(&id).cloned().unwrap_or_default(), 264 active_features: Vec::new(),
286 envs: envs.get(&id).cloned().unwrap_or_default(),
287 out_dir: out_dir_by_id.get(&id).cloned(),
288 proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(),
289 }); 265 });
290 let pkg_data = &mut packages[pkg]; 266 let pkg_data = &mut packages[pkg];
291 pkg_by_id.insert(id, pkg); 267 pkg_by_id.insert(id, pkg);
292 for meta_tgt in meta_pkg.targets { 268 for meta_tgt in &meta_pkg.targets {
293 let is_proc_macro = meta_tgt.kind.as_slice() == ["proc-macro"]; 269 let is_proc_macro = meta_tgt.kind.as_slice() == ["proc-macro"];
294 let tgt = targets.alloc(TargetData { 270 let tgt = targets.alloc(TargetData {
295 package: pkg, 271 package: pkg,
296 name: meta_tgt.name, 272 name: meta_tgt.name.clone(),
297 root: AbsPathBuf::assert(meta_tgt.src_path.clone()), 273 root: AbsPathBuf::assert(PathBuf::from(&meta_tgt.src_path)),
298 kind: TargetKind::new(meta_tgt.kind.as_slice()), 274 kind: TargetKind::new(meta_tgt.kind.as_slice()),
299 is_proc_macro, 275 is_proc_macro,
300 }); 276 });
@@ -328,11 +304,18 @@ impl CargoWorkspace {
328 let dep = PackageDependency { name: dep_node.name, pkg }; 304 let dep = PackageDependency { name: dep_node.name, pkg };
329 packages[source].dependencies.push(dep); 305 packages[source].dependencies.push(dep);
330 } 306 }
331 packages[source].features.extend(node.features); 307 packages[source].active_features.extend(node.features);
332 } 308 }
333 309
334 let workspace_root = AbsPathBuf::assert(meta.workspace_root); 310 let workspace_root =
335 Ok(CargoWorkspace { packages, targets, workspace_root: workspace_root }) 311 AbsPathBuf::assert(PathBuf::from(meta.workspace_root.into_os_string()));
312 let build_data_config = BuildDataConfig::new(
313 cargo_toml.to_path_buf(),
314 config.clone(),
315 Arc::new(meta.packages.clone()),
316 );
317
318 Ok(CargoWorkspace { packages, targets, workspace_root, build_data_config })
336 } 319 }
337 320
338 pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + ExactSizeIterator + 'a { 321 pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + ExactSizeIterator + 'a {
@@ -358,153 +341,11 @@ impl CargoWorkspace {
358 } 341 }
359 } 342 }
360 343
361 fn is_unique(&self, name: &str) -> bool { 344 pub(crate) fn build_data_config(&self) -> &BuildDataConfig {
362 self.packages.iter().filter(|(_, v)| v.name == name).count() == 1 345 &self.build_data_config
363 }
364}
365
366#[derive(Debug, Clone, Default)]
367pub(crate) struct ExternResources {
368 out_dirs: FxHashMap<PackageId, AbsPathBuf>,
369 proc_dylib_paths: FxHashMap<PackageId, AbsPathBuf>,
370 cfgs: FxHashMap<PackageId, Vec<CfgFlag>>,
371 env: FxHashMap<PackageId, Vec<(String, String)>>,
372}
373
374pub(crate) fn load_extern_resources(
375 cargo_toml: &Path,
376 cargo_features: &CargoConfig,
377 progress: &dyn Fn(String),
378) -> Result<ExternResources> {
379 let mut cmd = Command::new(toolchain::cargo());
380 cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"]).arg(cargo_toml);
381
382 // --all-targets includes tests, benches and examples in addition to the
383 // default lib and bins. This is an independent concept from the --targets
384 // flag below.
385 cmd.arg("--all-targets");
386
387 if let Some(target) = &cargo_features.target {
388 cmd.args(&["--target", target]);
389 }
390
391 if cargo_features.all_features {
392 cmd.arg("--all-features");
393 } else {
394 if cargo_features.no_default_features {
395 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
396 // https://github.com/oli-obk/cargo_metadata/issues/79
397 cmd.arg("--no-default-features");
398 }
399 if !cargo_features.features.is_empty() {
400 cmd.arg("--features");
401 cmd.arg(cargo_features.features.join(" "));
402 }
403 }
404
405 cmd.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null());
406
407 let mut child = cmd.spawn().map(JodChild)?;
408 let child_stdout = child.stdout.take().unwrap();
409 let stdout = BufReader::new(child_stdout);
410
411 let mut res = ExternResources::default();
412 for message in cargo_metadata::Message::parse_stream(stdout) {
413 if let Ok(message) = message {
414 match message {
415 Message::BuildScriptExecuted(BuildScript {
416 package_id,
417 out_dir,
418 cfgs,
419 env,
420 ..
421 }) => {
422 let cfgs = {
423 let mut acc = Vec::new();
424 for cfg in cfgs {
425 match cfg.parse::<CfgFlag>() {
426 Ok(it) => acc.push(it),
427 Err(err) => {
428 anyhow::bail!("invalid cfg from cargo-metadata: {}", err)
429 }
430 };
431 }
432 acc
433 };
434 // cargo_metadata crate returns default (empty) path for
435 // older cargos, which is not absolute, so work around that.
436 if out_dir != PathBuf::default() {
437 let out_dir = AbsPathBuf::assert(out_dir);
438 res.out_dirs.insert(package_id.clone(), out_dir);
439 res.cfgs.insert(package_id.clone(), cfgs);
440 }
441
442 res.env.insert(package_id, env);
443 }
444 Message::CompilerArtifact(message) => {
445 progress(format!("metadata {}", message.target.name));
446
447 if message.target.kind.contains(&"proc-macro".to_string()) {
448 let package_id = message.package_id;
449 // Skip rmeta file
450 if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name))
451 {
452 let filename = AbsPathBuf::assert(filename.clone());
453 res.proc_dylib_paths.insert(package_id, filename);
454 }
455 }
456 }
457 Message::CompilerMessage(message) => {
458 progress(message.target.name.clone());
459 }
460 Message::Unknown => (),
461 Message::BuildFinished(_) => {}
462 Message::TextLine(_) => {}
463 }
464 }
465 } 346 }
466 Ok(res)
467}
468 347
469// FIXME: File a better way to know if it is a dylib 348 fn is_unique(&self, name: &str) -> bool {
470fn is_dylib(path: &Path) -> bool { 349 self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
471 match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) {
472 None => false,
473 Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
474 }
475}
476
477/// Recreates the compile-time environment variables that Cargo sets.
478///
479/// Should be synced with <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
480fn inject_cargo_env(package: &cargo_metadata::Package, env: &mut Vec<(String, String)>) {
481 // FIXME: Missing variables:
482 // CARGO, CARGO_PKG_HOMEPAGE, CARGO_CRATE_NAME, CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
483
484 let mut manifest_dir = package.manifest_path.clone();
485 manifest_dir.pop();
486 if let Some(cargo_manifest_dir) = manifest_dir.to_str() {
487 env.push(("CARGO_MANIFEST_DIR".into(), cargo_manifest_dir.into()));
488 } 350 }
489
490 env.push(("CARGO_PKG_VERSION".into(), package.version.to_string()));
491 env.push(("CARGO_PKG_VERSION_MAJOR".into(), package.version.major.to_string()));
492 env.push(("CARGO_PKG_VERSION_MINOR".into(), package.version.minor.to_string()));
493 env.push(("CARGO_PKG_VERSION_PATCH".into(), package.version.patch.to_string()));
494
495 let pre = package.version.pre.iter().map(|id| id.to_string()).format(".");
496 env.push(("CARGO_PKG_VERSION_PRE".into(), pre.to_string()));
497
498 let authors = package.authors.join(";");
499 env.push(("CARGO_PKG_AUTHORS".into(), authors));
500
501 env.push(("CARGO_PKG_NAME".into(), package.name.clone()));
502 env.push(("CARGO_PKG_DESCRIPTION".into(), package.description.clone().unwrap_or_default()));
503 //env.push(("CARGO_PKG_HOMEPAGE".into(), package.homepage.clone().unwrap_or_default()));
504 env.push(("CARGO_PKG_REPOSITORY".into(), package.repository.clone().unwrap_or_default()));
505 env.push(("CARGO_PKG_LICENSE".into(), package.license.clone().unwrap_or_default()));
506
507 let license_file =
508 package.license_file.as_ref().map(|buf| buf.display().to_string()).unwrap_or_default();
509 env.push(("CARGO_PKG_LICENSE_FILE".into(), license_file));
510} 351}
diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs
index 970a7e140..a5b35ed95 100644
--- a/crates/project_model/src/lib.rs
+++ b/crates/project_model/src/lib.rs
@@ -6,6 +6,7 @@ mod project_json;
6mod sysroot; 6mod sysroot;
7mod workspace; 7mod workspace;
8mod rustc_cfg; 8mod rustc_cfg;
9mod build_data;
9 10
10use std::{ 11use std::{
11 fs::{read_dir, ReadDir}, 12 fs::{read_dir, ReadDir},
@@ -18,9 +19,10 @@ use paths::{AbsPath, AbsPathBuf};
18use rustc_hash::FxHashSet; 19use rustc_hash::FxHashSet;
19 20
20pub use crate::{ 21pub use crate::{
22 build_data::{BuildDataCollector, BuildDataResult},
21 cargo_workspace::{ 23 cargo_workspace::{
22 CargoConfig, CargoWorkspace, Package, PackageData, PackageDependency, Target, TargetData, 24 CargoConfig, CargoWorkspace, Package, PackageData, PackageDependency, RustcSource, Target,
23 TargetKind, 25 TargetData, TargetKind,
24 }, 26 },
25 project_json::{ProjectJson, ProjectJsonData}, 27 project_json::{ProjectJson, ProjectJsonData},
26 sysroot::Sysroot, 28 sysroot::Sysroot,
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs
index ff44dae4a..3b0ff506d 100644
--- a/crates/project_model/src/sysroot.rs
+++ b/crates/project_model/src/sysroot.rs
@@ -51,11 +51,18 @@ impl Sysroot {
51 pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { 51 pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> {
52 log::debug!("Discovering sysroot for {}", cargo_toml.display()); 52 log::debug!("Discovering sysroot for {}", cargo_toml.display());
53 let current_dir = cargo_toml.parent().unwrap(); 53 let current_dir = cargo_toml.parent().unwrap();
54 let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?; 54 let sysroot_dir = discover_sysroot_dir(current_dir)?;
55 let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, current_dir)?;
55 let res = Sysroot::load(&sysroot_src_dir)?; 56 let res = Sysroot::load(&sysroot_src_dir)?;
56 Ok(res) 57 Ok(res)
57 } 58 }
58 59
60 pub fn discover_rustc(cargo_toml: &AbsPath) -> Option<AbsPathBuf> {
61 log::debug!("Discovering rustc source for {}", cargo_toml.display());
62 let current_dir = cargo_toml.parent().unwrap();
63 discover_sysroot_dir(current_dir).ok().and_then(|sysroot_dir| get_rustc_src(&sysroot_dir))
64 }
65
59 pub fn load(sysroot_src_dir: &AbsPath) -> Result<Sysroot> { 66 pub fn load(sysroot_src_dir: &AbsPath) -> Result<Sysroot> {
60 let mut sysroot = Sysroot { crates: Arena::default() }; 67 let mut sysroot = Sysroot { crates: Arena::default() };
61 68
@@ -110,7 +117,18 @@ impl Sysroot {
110 } 117 }
111} 118}
112 119
113fn discover_sysroot_src_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> { 120fn discover_sysroot_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> {
121 let mut rustc = Command::new(toolchain::rustc());
122 rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
123 log::debug!("Discovering sysroot by {:?}", rustc);
124 let stdout = utf8_stdout(rustc)?;
125 Ok(AbsPathBuf::assert(PathBuf::from(stdout)))
126}
127
128fn discover_sysroot_src_dir(
129 sysroot_path: &AbsPathBuf,
130 current_dir: &AbsPath,
131) -> Result<AbsPathBuf> {
114 if let Ok(path) = env::var("RUST_SRC_PATH") { 132 if let Ok(path) = env::var("RUST_SRC_PATH") {
115 let path = AbsPathBuf::try_from(path.as_str()) 133 let path = AbsPathBuf::try_from(path.as_str())
116 .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; 134 .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?;
@@ -122,14 +140,6 @@ fn discover_sysroot_src_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> {
122 log::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core); 140 log::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core);
123 } 141 }
124 142
125 let sysroot_path = {
126 let mut rustc = Command::new(toolchain::rustc());
127 rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
128 log::debug!("Discovering sysroot by {:?}", rustc);
129 let stdout = utf8_stdout(rustc)?;
130 AbsPathBuf::assert(PathBuf::from(stdout))
131 };
132
133 get_rust_src(&sysroot_path) 143 get_rust_src(&sysroot_path)
134 .or_else(|| { 144 .or_else(|| {
135 let mut rustup = Command::new(toolchain::rustup()); 145 let mut rustup = Command::new(toolchain::rustup());
@@ -149,6 +159,16 @@ try installing the Rust source the same way you installed rustc",
149 }) 159 })
150} 160}
151 161
162fn get_rustc_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
163 let rustc_src = sysroot_path.join("lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml");
164 log::debug!("Checking for rustc source code: {}", rustc_src.display());
165 if rustc_src.exists() {
166 Some(rustc_src)
167 } else {
168 None
169 }
170}
171
152fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> { 172fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
153 // Try the new path first since the old one still exists. 173 // Try the new path first since the old one still exists.
154 let rust_src = sysroot_path.join("lib/rustlib/src/rust"); 174 let rust_src = sysroot_path.join("lib/rustlib/src/rust");
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
index 8e0481ae9..0220efdb4 100644
--- a/crates/project_model/src/workspace.rs
+++ b/crates/project_model/src/workspace.rs
@@ -16,8 +16,13 @@ use proc_macro_api::ProcMacroClient;
16use rustc_hash::{FxHashMap, FxHashSet}; 16use rustc_hash::{FxHashMap, FxHashSet};
17 17
18use crate::{ 18use crate::{
19 cargo_workspace, cfg_flag::CfgFlag, rustc_cfg, sysroot::SysrootCrate, utf8_stdout, CargoConfig, 19 build_data::{BuildData, BuildDataMap, BuildDataResult},
20 CargoWorkspace, ProjectJson, ProjectManifest, Sysroot, TargetKind, 20 cargo_workspace,
21 cfg_flag::CfgFlag,
22 rustc_cfg,
23 sysroot::SysrootCrate,
24 utf8_stdout, BuildDataCollector, CargoConfig, CargoWorkspace, ProjectJson, ProjectManifest,
25 Sysroot, TargetKind,
21}; 26};
22 27
23/// `PackageRoot` describes a package root folder. 28/// `PackageRoot` describes a package root folder.
@@ -51,6 +56,7 @@ pub enum ProjectWorkspace {
51 56
52impl fmt::Debug for ProjectWorkspace { 57impl fmt::Debug for ProjectWorkspace {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 // Make sure this isn't too verbose.
54 match self { 60 match self {
55 ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => f 61 ProjectWorkspace::Cargo { cargo, sysroot, rustc, rustc_cfg } => f
56 .debug_struct("Cargo") 62 .debug_struct("Cargo")
@@ -60,7 +66,7 @@ impl fmt::Debug for ProjectWorkspace {
60 "n_rustc_compiler_crates", 66 "n_rustc_compiler_crates",
61 &rustc.as_ref().map_or(0, |rc| rc.packages().len()), 67 &rustc.as_ref().map_or(0, |rc| rc.packages().len()),
62 ) 68 )
63 .field("rustc_cfg", rustc_cfg) 69 .field("n_rustc_cfg", &rustc_cfg.len())
64 .finish(), 70 .finish(),
65 ProjectWorkspace::Json { project, sysroot, rustc_cfg } => { 71 ProjectWorkspace::Json { project, sysroot, rustc_cfg } => {
66 let mut debug_struct = f.debug_struct("Json"); 72 let mut debug_struct = f.debug_struct("Json");
@@ -68,7 +74,7 @@ impl fmt::Debug for ProjectWorkspace {
68 if let Some(sysroot) = sysroot { 74 if let Some(sysroot) = sysroot {
69 debug_struct.field("n_sysroot_crates", &sysroot.crates().len()); 75 debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
70 } 76 }
71 debug_struct.field("rustc_cfg", rustc_cfg); 77 debug_struct.field("n_rustc_cfg", &rustc_cfg.len());
72 debug_struct.finish() 78 debug_struct.finish()
73 } 79 }
74 } 80 }
@@ -108,6 +114,7 @@ impl ProjectWorkspace {
108 cargo_version 114 cargo_version
109 ) 115 )
110 })?; 116 })?;
117
111 let sysroot = if config.no_sysroot { 118 let sysroot = if config.no_sysroot {
112 Sysroot::default() 119 Sysroot::default()
113 } else { 120 } else {
@@ -119,7 +126,17 @@ impl ProjectWorkspace {
119 })? 126 })?
120 }; 127 };
121 128
122 let rustc = if let Some(rustc_dir) = &config.rustc_source { 129 let rustc_dir = if let Some(rustc_source) = &config.rustc_source {
130 use cargo_workspace::RustcSource;
131 match rustc_source {
132 RustcSource::Path(path) => Some(path.clone()),
133 RustcSource::Discover => Sysroot::discover_rustc(&cargo_toml),
134 }
135 } else {
136 None
137 };
138
139 let rustc = if let Some(rustc_dir) = rustc_dir {
123 Some( 140 Some(
124 CargoWorkspace::from_cargo_metadata(&rustc_dir, config, progress) 141 CargoWorkspace::from_cargo_metadata(&rustc_dir, config, progress)
125 .with_context(|| { 142 .with_context(|| {
@@ -152,7 +169,7 @@ impl ProjectWorkspace {
152 /// Returns the roots for the current `ProjectWorkspace` 169 /// Returns the roots for the current `ProjectWorkspace`
153 /// The return type contains the path and whether or not 170 /// The return type contains the path and whether or not
154 /// the root is a member of the current workspace 171 /// the root is a member of the current workspace
155 pub fn to_roots(&self) -> Vec<PackageRoot> { 172 pub fn to_roots(&self, build_data: Option<&BuildDataResult>) -> Vec<PackageRoot> {
156 match self { 173 match self {
157 ProjectWorkspace::Json { project, sysroot, rustc_cfg: _ } => project 174 ProjectWorkspace::Json { project, sysroot, rustc_cfg: _ } => project
158 .crates() 175 .crates()
@@ -178,7 +195,12 @@ impl ProjectWorkspace {
178 let pkg_root = cargo[pkg].root().to_path_buf(); 195 let pkg_root = cargo[pkg].root().to_path_buf();
179 196
180 let mut include = vec![pkg_root.clone()]; 197 let mut include = vec![pkg_root.clone()];
181 include.extend(cargo[pkg].out_dir.clone()); 198 include.extend(
199 build_data
200 .and_then(|it| it.get(cargo.workspace_root()))
201 .and_then(|map| map.get(&cargo[pkg].id))
202 .and_then(|it| it.out_dir.clone()),
203 );
182 204
183 let mut exclude = vec![pkg_root.join(".git")]; 205 let mut exclude = vec![pkg_root.join(".git")];
184 if is_member { 206 if is_member {
@@ -218,6 +240,7 @@ impl ProjectWorkspace {
218 240
219 pub fn to_crate_graph( 241 pub fn to_crate_graph(
220 &self, 242 &self,
243 build_data: Option<&BuildDataResult>,
221 proc_macro_client: Option<&ProcMacroClient>, 244 proc_macro_client: Option<&ProcMacroClient>,
222 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, 245 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
223 ) -> CrateGraph { 246 ) -> CrateGraph {
@@ -240,8 +263,10 @@ impl ProjectWorkspace {
240 &proc_macro_loader, 263 &proc_macro_loader,
241 load, 264 load,
242 cargo, 265 cargo,
266 build_data.and_then(|it| it.get(cargo.workspace_root())),
243 sysroot, 267 sysroot,
244 rustc, 268 rustc,
269 rustc.as_ref().zip(build_data).and_then(|(it, map)| map.get(it.workspace_root())),
245 ), 270 ),
246 }; 271 };
247 if crate_graph.patch_cfg_if() { 272 if crate_graph.patch_cfg_if() {
@@ -251,6 +276,18 @@ impl ProjectWorkspace {
251 } 276 }
252 crate_graph 277 crate_graph
253 } 278 }
279
280 pub fn collect_build_data_configs(&self, collector: &mut BuildDataCollector) {
281 match self {
282 ProjectWorkspace::Cargo { cargo, rustc, .. } => {
283 collector.add_config(&cargo.workspace_root(), cargo.build_data_config().clone());
284 if let Some(rustc) = rustc {
285 collector.add_config(rustc.workspace_root(), rustc.build_data_config().clone());
286 }
287 }
288 _ => {}
289 }
290 }
254} 291}
255 292
256fn project_json_to_crate_graph( 293fn project_json_to_crate_graph(
@@ -323,8 +360,10 @@ fn cargo_to_crate_graph(
323 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>, 360 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
324 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, 361 load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
325 cargo: &CargoWorkspace, 362 cargo: &CargoWorkspace,
363 build_data_map: Option<&BuildDataMap>,
326 sysroot: &Sysroot, 364 sysroot: &Sysroot,
327 rustc: &Option<CargoWorkspace>, 365 rustc: &Option<CargoWorkspace>,
366 rustc_build_data_map: Option<&BuildDataMap>,
328) -> CrateGraph { 367) -> CrateGraph {
329 let _p = profile::span("cargo_to_crate_graph"); 368 let _p = profile::span("cargo_to_crate_graph");
330 let mut crate_graph = CrateGraph::default(); 369 let mut crate_graph = CrateGraph::default();
@@ -350,6 +389,7 @@ fn cargo_to_crate_graph(
350 let crate_id = add_target_crate_root( 389 let crate_id = add_target_crate_root(
351 &mut crate_graph, 390 &mut crate_graph,
352 &cargo[pkg], 391 &cargo[pkg],
392 build_data_map.and_then(|it| it.get(&cargo[pkg].id)),
353 &cfg_options, 393 &cfg_options,
354 proc_macro_loader, 394 proc_macro_loader,
355 file_id, 395 file_id,
@@ -426,6 +466,7 @@ fn cargo_to_crate_graph(
426 let crate_id = add_target_crate_root( 466 let crate_id = add_target_crate_root(
427 &mut crate_graph, 467 &mut crate_graph,
428 &rustc_workspace[pkg], 468 &rustc_workspace[pkg],
469 rustc_build_data_map.and_then(|it| it.get(&rustc_workspace[pkg].id)),
429 &cfg_options, 470 &cfg_options,
430 proc_macro_loader, 471 proc_macro_loader,
431 file_id, 472 file_id,
@@ -474,6 +515,7 @@ fn cargo_to_crate_graph(
474fn add_target_crate_root( 515fn add_target_crate_root(
475 crate_graph: &mut CrateGraph, 516 crate_graph: &mut CrateGraph,
476 pkg: &cargo_workspace::PackageData, 517 pkg: &cargo_workspace::PackageData,
518 build_data: Option<&BuildData>,
477 cfg_options: &CfgOptions, 519 cfg_options: &CfgOptions,
478 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>, 520 proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
479 file_id: FileId, 521 file_id: FileId,
@@ -481,26 +523,27 @@ fn add_target_crate_root(
481 let edition = pkg.edition; 523 let edition = pkg.edition;
482 let cfg_options = { 524 let cfg_options = {
483 let mut opts = cfg_options.clone(); 525 let mut opts = cfg_options.clone();
484 for feature in pkg.features.iter() { 526 for feature in pkg.active_features.iter() {
485 opts.insert_key_value("feature".into(), feature.into()); 527 opts.insert_key_value("feature".into(), feature.into());
486 } 528 }
487 opts.extend(pkg.cfgs.iter().cloned()); 529 if let Some(cfgs) = build_data.as_ref().map(|it| &it.cfgs) {
530 opts.extend(cfgs.iter().cloned());
531 }
488 opts 532 opts
489 }; 533 };
490 534
491 let mut env = Env::default(); 535 let mut env = Env::default();
492 for (k, v) in &pkg.envs { 536 if let Some(envs) = build_data.map(|it| &it.envs) {
493 env.set(k, v.clone()); 537 for (k, v) in envs {
494 } 538 env.set(k, v.clone());
495 if let Some(out_dir) = &pkg.out_dir {
496 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
497 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
498 env.set("OUT_DIR", out_dir);
499 } 539 }
500 } 540 }
501 541
502 let proc_macro = 542 let proc_macro = build_data
503 pkg.proc_macro_dylib_path.as_ref().map(|it| proc_macro_loader(&it)).unwrap_or_default(); 543 .as_ref()
544 .and_then(|it| it.proc_macro_dylib_path.as_ref())
545 .map(|it| proc_macro_loader(&it))
546 .unwrap_or_default();
504 547
505 let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone()); 548 let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone());
506 let crate_id = crate_graph.add_crate_root( 549 let crate_id = crate_graph.add_crate_root(