diff options
Diffstat (limited to 'crates/project_model/src/build_data.rs')
-rw-r--r-- | crates/project_model/src/build_data.rs | 288 |
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 | ||
10 | use anyhow::Result; | 11 | use anyhow::Result; |
11 | use cargo_metadata::{BuildScript, Message, Package, PackageId}; | 12 | use cargo_metadata::{BuildScript, Message}; |
12 | use itertools::Itertools; | 13 | use itertools::Itertools; |
13 | use paths::{AbsPath, AbsPathBuf}; | 14 | use paths::{AbsPath, AbsPathBuf}; |
14 | use rustc_hash::FxHashMap; | 15 | use rustc_hash::FxHashMap; |
@@ -16,150 +17,195 @@ use stdx::JodChild; | |||
16 | 17 | ||
17 | use crate::{cfg_flag::CfgFlag, CargoConfig}; | 18 | use crate::{cfg_flag::CfgFlag, CargoConfig}; |
18 | 19 | ||
19 | #[derive(Debug, Clone, Default)] | ||
20 | pub(crate) struct BuildDataMap { | ||
21 | data: FxHashMap<PackageId, BuildData>, | ||
22 | } | ||
23 | #[derive(Debug, Clone, Default, PartialEq, Eq)] | 20 | #[derive(Debug, Clone, Default, PartialEq, Eq)] |
24 | pub struct BuildData { | 21 | pub(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 | ||
38 | impl BuildDataMap { | 35 | #[derive(Clone, Debug)] |
39 | pub(crate) fn new( | 36 | pub(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 { | 42 | impl 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()); | 48 | impl 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 | } | 51 | pub 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; | 56 | pub 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 | { | 60 | pub(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(); | 62 | impl 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 | |||
84 | impl 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 { | 90 | impl 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> { | 100 | fn 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> |
176 | fn inject_cargo_env(package: &cargo_metadata::Package, env: &mut Vec<(String, String)>) { | 222 | fn 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 | ||