diff options
author | Aleksey Kladov <[email protected]> | 2021-04-12 09:04:36 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2021-04-12 13:29:31 +0100 |
commit | 186c5c47cbfde4ae9d81dc67450c958cb6aece2c (patch) | |
tree | 05141f6e9aa7d20c4ce2a8b59324a3d82d43f895 /crates/project_model/src | |
parent | 7be06139b632ee615fc18af04dd67947e2c794b2 (diff) |
feat: avoid checking the whole project during initial loading
Diffstat (limited to 'crates/project_model/src')
-rw-r--r-- | crates/project_model/src/build_data.rs | 236 |
1 files changed, 129 insertions, 107 deletions
diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs index 0d4d39fef..ab5cc8c49 100644 --- a/crates/project_model/src/build_data.rs +++ b/crates/project_model/src/build_data.rs | |||
@@ -58,12 +58,17 @@ impl PartialEq for BuildDataConfig { | |||
58 | 58 | ||
59 | impl Eq for BuildDataConfig {} | 59 | impl Eq for BuildDataConfig {} |
60 | 60 | ||
61 | #[derive(Debug, Default)] | 61 | #[derive(Debug)] |
62 | pub struct BuildDataCollector { | 62 | pub struct BuildDataCollector { |
63 | wrap_rustc: bool, | ||
63 | configs: FxHashMap<AbsPathBuf, BuildDataConfig>, | 64 | configs: FxHashMap<AbsPathBuf, BuildDataConfig>, |
64 | } | 65 | } |
65 | 66 | ||
66 | impl BuildDataCollector { | 67 | impl BuildDataCollector { |
68 | pub fn new(wrap_rustc: bool) -> Self { | ||
69 | Self { wrap_rustc, configs: FxHashMap::default() } | ||
70 | } | ||
71 | |||
67 | pub(crate) fn add_config(&mut self, workspace_root: &AbsPath, config: BuildDataConfig) { | 72 | pub(crate) fn add_config(&mut self, workspace_root: &AbsPath, config: BuildDataConfig) { |
68 | self.configs.insert(workspace_root.to_path_buf(), config); | 73 | self.configs.insert(workspace_root.to_path_buf(), config); |
69 | } | 74 | } |
@@ -71,15 +76,14 @@ impl BuildDataCollector { | |||
71 | pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> { | 76 | pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> { |
72 | let mut res = BuildDataResult::default(); | 77 | let mut res = BuildDataResult::default(); |
73 | for (path, config) in self.configs.iter() { | 78 | for (path, config) in self.configs.iter() { |
74 | res.per_workspace.insert( | 79 | let workspace_build_data = WorkspaceBuildData::collect( |
75 | path.clone(), | 80 | &config.cargo_toml, |
76 | collect_from_workspace( | 81 | &config.cargo_features, |
77 | &config.cargo_toml, | 82 | &config.packages, |
78 | &config.cargo_features, | 83 | self.wrap_rustc, |
79 | &config.packages, | 84 | progress, |
80 | progress, | 85 | )?; |
81 | )?, | 86 | res.per_workspace.insert(path.clone(), workspace_build_data); |
82 | ); | ||
83 | } | 87 | } |
84 | Ok(res) | 88 | Ok(res) |
85 | } | 89 | } |
@@ -120,119 +124,137 @@ impl BuildDataConfig { | |||
120 | } | 124 | } |
121 | } | 125 | } |
122 | 126 | ||
123 | fn collect_from_workspace( | 127 | impl WorkspaceBuildData { |
124 | cargo_toml: &AbsPath, | 128 | fn collect( |
125 | cargo_features: &CargoConfig, | 129 | cargo_toml: &AbsPath, |
126 | packages: &Vec<cargo_metadata::Package>, | 130 | cargo_features: &CargoConfig, |
127 | progress: &dyn Fn(String), | 131 | packages: &Vec<cargo_metadata::Package>, |
128 | ) -> Result<WorkspaceBuildData> { | 132 | wrap_rustc: bool, |
129 | let mut cmd = Command::new(toolchain::cargo()); | 133 | progress: &dyn Fn(String), |
130 | cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"]) | 134 | ) -> Result<WorkspaceBuildData> { |
131 | .arg(cargo_toml.as_ref()); | 135 | let mut cmd = Command::new(toolchain::cargo()); |
132 | 136 | ||
133 | // --all-targets includes tests, benches and examples in addition to the | 137 | if wrap_rustc { |
134 | // default lib and bins. This is an independent concept from the --targets | 138 | // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use |
135 | // flag below. | 139 | // that to compile only proc macros and build scripts during the initial |
136 | cmd.arg("--all-targets"); | 140 | // `cargo check`. |
137 | 141 | let myself = std::env::current_exe()?; | |
138 | if let Some(target) = &cargo_features.target { | 142 | cmd.env("RUSTC_WRAPPER", myself); |
139 | cmd.args(&["--target", target]); | 143 | cmd.env("RA_RUSTC_WRAPPER", "1"); |
140 | } | 144 | } |
145 | |||
146 | cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"]) | ||
147 | .arg(cargo_toml.as_ref()); | ||
141 | 148 | ||
142 | if cargo_features.all_features { | 149 | // --all-targets includes tests, benches and examples in addition to the |
143 | cmd.arg("--all-features"); | 150 | // default lib and bins. This is an independent concept from the --targets |
144 | } else { | 151 | // flag below. |
145 | if cargo_features.no_default_features { | 152 | cmd.arg("--all-targets"); |
146 | // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` | 153 | |
147 | // https://github.com/oli-obk/cargo_metadata/issues/79 | 154 | if let Some(target) = &cargo_features.target { |
148 | cmd.arg("--no-default-features"); | 155 | cmd.args(&["--target", target]); |
149 | } | 156 | } |
150 | if !cargo_features.features.is_empty() { | 157 | |
151 | cmd.arg("--features"); | 158 | if cargo_features.all_features { |
152 | cmd.arg(cargo_features.features.join(" ")); | 159 | cmd.arg("--all-features"); |
160 | } else { | ||
161 | if cargo_features.no_default_features { | ||
162 | // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` | ||
163 | // https://github.com/oli-obk/cargo_metadata/issues/79 | ||
164 | cmd.arg("--no-default-features"); | ||
165 | } | ||
166 | if !cargo_features.features.is_empty() { | ||
167 | cmd.arg("--features"); | ||
168 | cmd.arg(cargo_features.features.join(" ")); | ||
169 | } | ||
153 | } | 170 | } |
154 | } | ||
155 | 171 | ||
156 | cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null()); | 172 | cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null()); |
157 | 173 | ||
158 | let mut child = cmd.spawn().map(JodChild)?; | 174 | let mut child = cmd.spawn().map(JodChild)?; |
159 | let child_stdout = child.stdout.take().unwrap(); | 175 | let child_stdout = child.stdout.take().unwrap(); |
160 | let stdout = BufReader::new(child_stdout); | 176 | let stdout = BufReader::new(child_stdout); |
161 | 177 | ||
162 | let mut res = WorkspaceBuildData::default(); | 178 | let mut res = WorkspaceBuildData::default(); |
163 | for message in cargo_metadata::Message::parse_stream(stdout).flatten() { | 179 | for message in cargo_metadata::Message::parse_stream(stdout).flatten() { |
164 | match message { | 180 | match message { |
165 | Message::BuildScriptExecuted(BuildScript { | 181 | Message::BuildScriptExecuted(BuildScript { |
166 | package_id, out_dir, cfgs, env, .. | 182 | package_id, |
167 | }) => { | 183 | out_dir, |
168 | let cfgs = { | 184 | cfgs, |
169 | let mut acc = Vec::new(); | 185 | env, |
170 | for cfg in cfgs { | 186 | .. |
171 | match cfg.parse::<CfgFlag>() { | 187 | }) => { |
172 | Ok(it) => acc.push(it), | 188 | let cfgs = { |
173 | Err(err) => { | 189 | let mut acc = Vec::new(); |
174 | anyhow::bail!("invalid cfg from cargo-metadata: {}", err) | 190 | for cfg in cfgs { |
175 | } | 191 | match cfg.parse::<CfgFlag>() { |
176 | }; | 192 | Ok(it) => acc.push(it), |
193 | Err(err) => { | ||
194 | anyhow::bail!("invalid cfg from cargo-metadata: {}", err) | ||
195 | } | ||
196 | }; | ||
197 | } | ||
198 | acc | ||
199 | }; | ||
200 | let package_build_data = | ||
201 | res.per_package.entry(package_id.repr.clone()).or_default(); | ||
202 | // cargo_metadata crate returns default (empty) path for | ||
203 | // older cargos, which is not absolute, so work around that. | ||
204 | if !out_dir.as_str().is_empty() { | ||
205 | let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string())); | ||
206 | package_build_data.out_dir = Some(out_dir); | ||
207 | package_build_data.cfgs = cfgs; | ||
177 | } | 208 | } |
178 | acc | ||
179 | }; | ||
180 | let package_build_data = | ||
181 | res.per_package.entry(package_id.repr.clone()).or_default(); | ||
182 | // cargo_metadata crate returns default (empty) path for | ||
183 | // older cargos, which is not absolute, so work around that. | ||
184 | if !out_dir.as_str().is_empty() { | ||
185 | let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string())); | ||
186 | package_build_data.out_dir = Some(out_dir); | ||
187 | package_build_data.cfgs = cfgs; | ||
188 | } | ||
189 | 209 | ||
190 | package_build_data.envs = env; | 210 | package_build_data.envs = env; |
191 | } | 211 | } |
192 | Message::CompilerArtifact(message) => { | 212 | Message::CompilerArtifact(message) => { |
193 | progress(format!("metadata {}", message.target.name)); | 213 | progress(format!("metadata {}", message.target.name)); |
194 | 214 | ||
195 | if message.target.kind.contains(&"proc-macro".to_string()) { | 215 | if message.target.kind.contains(&"proc-macro".to_string()) { |
196 | let package_id = message.package_id; | 216 | let package_id = message.package_id; |
197 | // Skip rmeta file | 217 | // Skip rmeta file |
198 | if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) { | 218 | if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) |
199 | let filename = AbsPathBuf::assert(PathBuf::from(&filename)); | 219 | { |
200 | let package_build_data = | 220 | let filename = AbsPathBuf::assert(PathBuf::from(&filename)); |
201 | res.per_package.entry(package_id.repr.clone()).or_default(); | 221 | let package_build_data = |
202 | package_build_data.proc_macro_dylib_path = Some(filename); | 222 | res.per_package.entry(package_id.repr.clone()).or_default(); |
223 | package_build_data.proc_macro_dylib_path = Some(filename); | ||
224 | } | ||
203 | } | 225 | } |
204 | } | 226 | } |
227 | Message::CompilerMessage(message) => { | ||
228 | progress(message.target.name.clone()); | ||
229 | } | ||
230 | Message::BuildFinished(_) => {} | ||
231 | Message::TextLine(_) => {} | ||
232 | _ => {} | ||
205 | } | 233 | } |
206 | Message::CompilerMessage(message) => { | ||
207 | progress(message.target.name.clone()); | ||
208 | } | ||
209 | Message::BuildFinished(_) => {} | ||
210 | Message::TextLine(_) => {} | ||
211 | _ => {} | ||
212 | } | 234 | } |
213 | } | ||
214 | 235 | ||
215 | for package in packages { | 236 | for package in packages { |
216 | let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default(); | 237 | let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default(); |
217 | inject_cargo_env(package, package_build_data); | 238 | inject_cargo_env(package, package_build_data); |
218 | if let Some(out_dir) = &package_build_data.out_dir { | 239 | if let Some(out_dir) = &package_build_data.out_dir { |
219 | // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() | 240 | // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() |
220 | if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { | 241 | if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { |
221 | package_build_data.envs.push(("OUT_DIR".to_string(), out_dir)); | 242 | package_build_data.envs.push(("OUT_DIR".to_string(), out_dir)); |
243 | } | ||
222 | } | 244 | } |
223 | } | 245 | } |
224 | } | ||
225 | 246 | ||
226 | let output = child.into_inner().wait_with_output()?; | 247 | let output = child.into_inner().wait_with_output()?; |
227 | if !output.status.success() { | 248 | if !output.status.success() { |
228 | let mut stderr = String::from_utf8(output.stderr).unwrap_or_default(); | 249 | let mut stderr = String::from_utf8(output.stderr).unwrap_or_default(); |
229 | if stderr.is_empty() { | 250 | if stderr.is_empty() { |
230 | stderr = "cargo check failed".to_string(); | 251 | stderr = "cargo check failed".to_string(); |
252 | } | ||
253 | res.error = Some(stderr) | ||
231 | } | 254 | } |
232 | res.error = Some(stderr) | ||
233 | } | ||
234 | 255 | ||
235 | Ok(res) | 256 | Ok(res) |
257 | } | ||
236 | } | 258 | } |
237 | 259 | ||
238 | // FIXME: File a better way to know if it is a dylib | 260 | // FIXME: File a better way to know if it is a dylib |