aboutsummaryrefslogtreecommitdiff
path: root/crates/project_model/src/build_data.rs
diff options
context:
space:
mode:
authorEdwin Cheng <[email protected]>2021-01-28 15:33:02 +0000
committerEdwin Cheng <[email protected]>2021-01-28 17:04:14 +0000
commit9358eecc042d8b551f58d2d5ddb9c88d258880c1 (patch)
tree7188b0e27d9d00640b5c76319ee59b2d5cab1b05 /crates/project_model/src/build_data.rs
parentf421ee672253499b8ca8d1badf98db42525a5216 (diff)
Async Loading outdir and proc-macro
Diffstat (limited to 'crates/project_model/src/build_data.rs')
-rw-r--r--crates/project_model/src/build_data.rs288
1 files changed, 168 insertions, 120 deletions
diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs
index 3ff347e2c..a5c564e0a 100644
--- a/crates/project_model/src/build_data.rs
+++ b/crates/project_model/src/build_data.rs
@@ -5,10 +5,11 @@ use std::{
5 io::BufReader, 5 io::BufReader,
6 path::{Path, PathBuf}, 6 path::{Path, PathBuf},
7 process::{Command, Stdio}, 7 process::{Command, Stdio},
8 sync::Arc,
8}; 9};
9 10
10use anyhow::Result; 11use anyhow::Result;
11use cargo_metadata::{BuildScript, Message, Package, PackageId}; 12use cargo_metadata::{BuildScript, Message};
12use itertools::Itertools; 13use itertools::Itertools;
13use paths::{AbsPath, AbsPathBuf}; 14use paths::{AbsPath, AbsPathBuf};
14use rustc_hash::FxHashMap; 15use rustc_hash::FxHashMap;
@@ -16,150 +17,195 @@ use stdx::JodChild;
16 17
17use crate::{cfg_flag::CfgFlag, CargoConfig}; 18use crate::{cfg_flag::CfgFlag, CargoConfig};
18 19
19#[derive(Debug, Clone, Default)]
20pub(crate) struct BuildDataMap {
21 data: FxHashMap<PackageId, BuildData>,
22}
23#[derive(Debug, Clone, Default, PartialEq, Eq)] 20#[derive(Debug, Clone, Default, PartialEq, Eq)]
24pub struct BuildData { 21pub(crate) struct BuildData {
25 /// List of config flags defined by this package's build script 22 /// List of config flags defined by this package's build script
26 pub cfgs: Vec<CfgFlag>, 23 pub(crate) cfgs: Vec<CfgFlag>,
27 /// List of cargo-related environment variables with their value 24 /// List of cargo-related environment variables with their value
28 /// 25 ///
29 /// If the package has a build script which defines environment variables, 26 /// If the package has a build script which defines environment variables,
30 /// they can also be found here. 27 /// they can also be found here.
31 pub envs: Vec<(String, String)>, 28 pub(crate) envs: Vec<(String, String)>,
32 /// Directory where a build script might place its output 29 /// Directory where a build script might place its output
33 pub out_dir: Option<AbsPathBuf>, 30 pub(crate) out_dir: Option<AbsPathBuf>,
34 /// Path to the proc-macro library file if this package exposes proc-macros 31 /// Path to the proc-macro library file if this package exposes proc-macros
35 pub proc_macro_dylib_path: Option<AbsPathBuf>, 32 pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
36} 33}
37 34
38impl BuildDataMap { 35#[derive(Clone, Debug)]
39 pub(crate) fn new( 36pub(crate) struct BuildDataConfig {
40 cargo_toml: &AbsPath, 37 cargo_toml: AbsPathBuf,
41 cargo_features: &CargoConfig, 38 cargo_features: CargoConfig,
42 packages: &Vec<Package>, 39 packages: Arc<Vec<cargo_metadata::Package>>,
43 progress: &dyn Fn(String), 40}
44 ) -> Result<BuildDataMap> {
45 let mut cmd = Command::new(toolchain::cargo());
46 cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"])
47 .arg(cargo_toml.as_ref());
48
49 // --all-targets includes tests, benches and examples in addition to the
50 // default lib and bins. This is an independent concept from the --targets
51 // flag below.
52 cmd.arg("--all-targets");
53
54 if let Some(target) = &cargo_features.target {
55 cmd.args(&["--target", target]);
56 }
57 41
58 if cargo_features.all_features { 42impl PartialEq for BuildDataConfig {
59 cmd.arg("--all-features"); 43 fn eq(&self, other: &Self) -> bool {
60 } else { 44 Arc::ptr_eq(&self.packages, &other.packages)
61 if cargo_features.no_default_features { 45 }
62 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` 46}
63 // https://github.com/oli-obk/cargo_metadata/issues/79
64 cmd.arg("--no-default-features");
65 }
66 if !cargo_features.features.is_empty() {
67 cmd.arg("--features");
68 cmd.arg(cargo_features.features.join(" "));
69 }
70 }
71 47
72 cmd.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null()); 48impl Eq for BuildDataConfig {}
73
74 let mut child = cmd.spawn().map(JodChild)?;
75 let child_stdout = child.stdout.take().unwrap();
76 let stdout = BufReader::new(child_stdout);
77
78 let mut res = BuildDataMap::default();
79 for message in cargo_metadata::Message::parse_stream(stdout) {
80 if let Ok(message) = message {
81 match message {
82 Message::BuildScriptExecuted(BuildScript {
83 package_id,
84 out_dir,
85 cfgs,
86 env,
87 ..
88 }) => {
89 let cfgs = {
90 let mut acc = Vec::new();
91 for cfg in cfgs {
92 match cfg.parse::<CfgFlag>() {
93 Ok(it) => acc.push(it),
94 Err(err) => {
95 anyhow::bail!("invalid cfg from cargo-metadata: {}", err)
96 }
97 };
98 }
99 acc
100 };
101 let res = res.data.entry(package_id.clone()).or_default();
102 // cargo_metadata crate returns default (empty) path for
103 // older cargos, which is not absolute, so work around that.
104 if out_dir != PathBuf::default() {
105 let out_dir = AbsPathBuf::assert(out_dir);
106 res.out_dir = Some(out_dir);
107 res.cfgs = cfgs;
108 }
109 49
110 res.envs = env; 50#[derive(Debug, Default)]
111 } 51pub struct BuildDataCollector {
112 Message::CompilerArtifact(message) => { 52 configs: FxHashMap<AbsPathBuf, BuildDataConfig>,
113 progress(format!("metadata {}", message.target.name)); 53}
114 54
115 if message.target.kind.contains(&"proc-macro".to_string()) { 55#[derive(Debug, Default, PartialEq, Eq)]
116 let package_id = message.package_id; 56pub struct BuildDataResult {
117 // Skip rmeta file 57 data: FxHashMap<AbsPathBuf, BuildDataMap>,
118 if let Some(filename) = 58}
119 message.filenames.iter().find(|name| is_dylib(name)) 59
120 { 60pub(crate) type BuildDataMap = FxHashMap<String, BuildData>;
121 let filename = AbsPathBuf::assert(filename.clone()); 61
122 let res = res.data.entry(package_id.clone()).or_default(); 62impl BuildDataCollector {
123 res.proc_macro_dylib_path = Some(filename); 63 pub(crate) fn add_config(&mut self, workspace_root: &AbsPath, config: BuildDataConfig) {
124 } 64 self.configs.insert(workspace_root.to_path_buf().clone(), config);
125 } 65 }
126 } 66
127 Message::CompilerMessage(message) => { 67 pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> {
128 progress(message.target.name.clone()); 68 let mut res = BuildDataResult::default();
129 } 69 for (path, config) in self.configs.iter() {
130 Message::Unknown => (), 70 res.data.insert(
131 Message::BuildFinished(_) => {} 71 path.clone(),
132 Message::TextLine(_) => {} 72 collect_from_workspace(
133 } 73 &config.cargo_toml,
134 } 74 &config.cargo_features,
75 &config.packages,
76 progress,
77 )?,
78 );
135 } 79 }
136 res.inject_cargo_env(packages);
137 Ok(res) 80 Ok(res)
138 } 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}
139 89
140 pub(crate) fn with_cargo_env(packages: &Vec<Package>) -> Self { 90impl BuildDataConfig {
141 let mut res = Self::default(); 91 pub(crate) fn new(
142 res.inject_cargo_env(packages); 92 cargo_toml: AbsPathBuf,
143 res 93 cargo_features: CargoConfig,
94 packages: Arc<Vec<cargo_metadata::Package>>,
95 ) -> Self {
96 Self { cargo_toml, cargo_features, packages }
144 } 97 }
98}
145 99
146 pub(crate) fn get(&self, id: &PackageId) -> Option<&BuildData> { 100fn collect_from_workspace(
147 self.data.get(id) 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]);
148 } 117 }
149 118
150 fn inject_cargo_env(&mut self, packages: &Vec<Package>) { 119 if cargo_features.all_features {
151 for meta_pkg in packages { 120 cmd.arg("--all-features");
152 let resource = self.data.entry(meta_pkg.id.clone()).or_default(); 121 } else {
153 inject_cargo_env(meta_pkg, &mut resource.envs); 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());
154 134
155 if let Some(out_dir) = &resource.out_dir { 135 let mut child = cmd.spawn().map(JodChild)?;
156 // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() 136 let child_stdout = child.stdout.take().unwrap();
157 if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { 137 let stdout = BufReader::new(child_stdout);
158 resource.envs.push(("OUT_DIR".to_string(), out_dir)); 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 != PathBuf::default() {
166 let out_dir = AbsPathBuf::assert(out_dir);
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(filename.clone());
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());
159 } 189 }
190 Message::Unknown => (),
191 Message::BuildFinished(_) => {}
192 Message::TextLine(_) => {}
160 } 193 }
161 } 194 }
162 } 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)
163} 209}
164 210
165// FIXME: File a better way to know if it is a dylib 211// FIXME: File a better way to know if it is a dylib
@@ -173,7 +219,9 @@ fn is_dylib(path: &Path) -> bool {
173/// Recreates the compile-time environment variables that Cargo sets. 219/// Recreates the compile-time environment variables that Cargo sets.
174/// 220///
175/// Should be synced with <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates> 221/// Should be synced with <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
176fn inject_cargo_env(package: &cargo_metadata::Package, env: &mut Vec<(String, String)>) { 222fn inject_cargo_env(package: &cargo_metadata::Package, build_data: &mut BuildData) {
223 let env = &mut build_data.envs;
224
177 // FIXME: Missing variables: 225 // FIXME: Missing variables:
178 // CARGO_PKG_HOMEPAGE, CARGO_CRATE_NAME, CARGO_BIN_NAME, CARGO_BIN_EXE_<name> 226 // CARGO_PKG_HOMEPAGE, CARGO_CRATE_NAME, CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
179 227