|
3 | 3 | use std::collections::{BTreeMap, BTreeSet, HashMap};
|
4 | 4 | use std::io::BufRead;
|
5 | 5 | use std::path::{Path, PathBuf};
|
6 |
| -use std::process::Child; |
| 6 | +use std::process::Output; |
| 7 | +use std::thread; |
7 | 8 |
|
8 | 9 | use anyhow::{anyhow, bail, Context, Result};
|
9 | 10 | use camino::Utf8Path;
|
@@ -131,84 +132,127 @@ impl TreeResolver {
|
131 | 132 | .insert(triple);
|
132 | 133 | }
|
133 | 134 |
|
134 |
| - for host_triple in cargo_host_triples.keys() { |
135 |
| - // Note that for each host triple `cargo tree` child processes are spawned and then |
136 |
| - // immediately waited upon so that we don't end up with `{HOST_TRIPLES} * {TARGET_TRIPLES}` |
137 |
| - // number of processes (which can be +400 and hit operating system limitations). |
138 |
| - let mut target_triple_to_child = BTreeMap::<String, Child>::new(); |
| 135 | + // Limit the concurrency so we don't concurrently spawn `{HOST_TRIPLES} * {TARGET_TRIPLES}` |
| 136 | + // number of processes (which can be +400 and hit operating system limitations). |
| 137 | + let max_parallel = thread::available_parallelism() |
| 138 | + .map(|n| n.get()) |
| 139 | + .unwrap_or(4); |
| 140 | + |
| 141 | + // Prepare all unique jobs: (cargo_host, cargo_target) |
| 142 | + let mut jobs = Vec::<(String, String)>::new(); |
| 143 | + for cargo_host in cargo_host_triples.keys() { |
| 144 | + for cargo_target in cargo_target_triples.keys() { |
| 145 | + jobs.push((cargo_host.clone(), cargo_target.clone())); |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + // Spawn workers up to the cap; join one whenever the cap is reached. |
| 150 | + let mut in_flight = |
| 151 | + Vec::<thread::JoinHandle<anyhow::Result<(String, String, Output)>>>::new(); |
| 152 | + let mut results = Vec::<(String, String, Output)>::new(); |
| 153 | + |
| 154 | + for (cargo_host, cargo_target) in jobs { |
| 155 | + // If we've hit the limit, free a slot by joining one worker. |
| 156 | + if in_flight.len() >= max_parallel { |
| 157 | + let res = in_flight |
| 158 | + .remove(0) |
| 159 | + .join() |
| 160 | + .expect("worker thread panicked")?; |
| 161 | + results.push(res); |
| 162 | + } |
139 | 163 |
|
140 | 164 | debug!(
|
141 |
| - "Spawning `cargo tree` processes for host `{}`: {}", |
142 |
| - host_triple, |
143 |
| - cargo_target_triples.keys().len(), |
| 165 | + "Spawning `cargo tree` process for host `{}`: {}", |
| 166 | + cargo_host, cargo_target, |
144 | 167 | );
|
145 | 168 |
|
146 |
| - for target_triple in cargo_target_triples.keys() { |
147 |
| - // We use `cargo tree` here because `cargo metadata` doesn't report |
148 |
| - // back target-specific features (enabled with `resolver = "2"`). |
149 |
| - // This is unfortunately a bit of a hack. See: |
150 |
| - // - https://github.com/rust-lang/cargo/issues/9863 |
151 |
| - // - https://github.com/bazelbuild/rules_rust/issues/1662 |
152 |
| - let child = self |
153 |
| - .cargo_bin |
154 |
| - .command()? |
155 |
| - // These next two environment variables are used to hack cargo into using a custom |
156 |
| - // host triple instead of the host triple detected by rustc. |
157 |
| - .env("RUSTC_WRAPPER", rustc_wrapper) |
158 |
| - .env("HOST_TRIPLE", host_triple) |
159 |
| - .env("CARGO_CACHE_RUSTC_INFO", "0") |
160 |
| - .current_dir(manifest_path.parent().expect("All manifests should have a valid parent.")) |
161 |
| - .arg("tree") |
162 |
| - .arg("--manifest-path") |
163 |
| - .arg(manifest_path) |
164 |
| - .arg("--edges") |
165 |
| - .arg("normal,build,dev") |
166 |
| - .arg("--prefix=indent") |
167 |
| - // https://doc.rust-lang.org/cargo/commands/cargo-tree.html#tree-formatting-options |
168 |
| - .arg("--format=;{p};{f};") |
169 |
| - .arg("--color=never") |
170 |
| - .arg("--charset=ascii") |
171 |
| - .arg("--workspace") |
172 |
| - .arg("--target") |
173 |
| - .arg(target_triple) |
174 |
| - .stdout(std::process::Stdio::piped()) |
175 |
| - .stderr(std::process::Stdio::piped()) |
176 |
| - .spawn() |
177 |
| - .with_context(|| { |
178 |
| - format!( |
| 169 | + let manifest_path = manifest_path.to_owned(); |
| 170 | + let rustc_wrapper = rustc_wrapper.to_owned(); |
| 171 | + let cargo_bin = self.cargo_bin.clone(); |
| 172 | + |
| 173 | + in_flight.push(thread::spawn( |
| 174 | + move || -> anyhow::Result<(String, String, Output)> { |
| 175 | + // We use `cargo tree` here because `cargo metadata` doesn't report |
| 176 | + // back target-specific features (enabled with `resolver = "2"`). |
| 177 | + // This is unfortunately a bit of a hack. See: |
| 178 | + // - https://github.com/rust-lang/cargo/issues/9863 |
| 179 | + // - https://github.com/bazelbuild/rules_rust/issues/1662 |
| 180 | + let child = cargo_bin |
| 181 | + .command()? |
| 182 | + // These next two environment variables are used to hack cargo into using a custom |
| 183 | + // host triple instead of the host triple detected by rustc. |
| 184 | + .env("RUSTC_WRAPPER", &rustc_wrapper) |
| 185 | + .env("HOST_TRIPLE", &cargo_host) |
| 186 | + .env("CARGO_CACHE_RUSTC_INFO", "0") |
| 187 | + .current_dir( |
| 188 | + manifest_path |
| 189 | + .parent() |
| 190 | + .expect("All manifests should have a valid parent."), |
| 191 | + ) |
| 192 | + .arg("tree") |
| 193 | + .arg("--manifest-path") |
| 194 | + .arg(&manifest_path) |
| 195 | + .arg("--edges") |
| 196 | + .arg("normal,build,dev") |
| 197 | + .arg("--prefix=indent") |
| 198 | + // https://doc.rust-lang.org/cargo/commands/cargo-tree.html#tree-formatting-options |
| 199 | + .arg("--format=;{p};{f};") |
| 200 | + .arg("--color=never") |
| 201 | + .arg("--charset=ascii") |
| 202 | + .arg("--workspace") |
| 203 | + .arg("--target") |
| 204 | + .arg(&cargo_target) |
| 205 | + .stdout(std::process::Stdio::piped()) |
| 206 | + .stderr(std::process::Stdio::piped()) |
| 207 | + .spawn() |
| 208 | + .with_context(|| { |
| 209 | + format!( |
179 | 210 | "Error spawning cargo in child process to compute features for target '{}', manifest path '{}'",
|
180 |
| - target_triple, |
| 211 | + cargo_target, |
| 212 | + manifest_path.display() |
| 213 | + ) |
| 214 | + })?; |
| 215 | + |
| 216 | + let output = child.wait_with_output().with_context(|| { |
| 217 | + format!( |
| 218 | + "Error running `cargo tree --target={}` (host = '{}'), manifest path '{}'", |
| 219 | + cargo_target, |
| 220 | + cargo_host, |
181 | 221 | manifest_path.display()
|
182 | 222 | )
|
183 | 223 | })?;
|
184 |
| - target_triple_to_child.insert(target_triple.clone(), child); |
185 |
| - } |
186 | 224 |
|
187 |
| - for (target_triple, child) in target_triple_to_child.into_iter() { |
188 |
| - let output = child.wait_with_output().with_context(|| { |
189 |
| - format!( |
190 |
| - "Error running `cargo tree --target={}` (host = '{}'), manifest path '{}'", |
191 |
| - target_triple, |
192 |
| - host_triple, |
193 |
| - manifest_path.display() |
194 |
| - ) |
195 |
| - })?; |
196 |
| - if !output.status.success() { |
197 |
| - tracing::error!("{}", String::from_utf8_lossy(&output.stdout)); |
198 |
| - tracing::error!("{}", String::from_utf8_lossy(&output.stderr)); |
199 |
| - bail!(format!("Failed to run cargo tree: {}", output.status)) |
200 |
| - } |
| 225 | + Ok((cargo_host, cargo_target, output)) |
| 226 | + }, |
| 227 | + )); |
| 228 | + } |
| 229 | + |
| 230 | + for handle in in_flight { |
| 231 | + let res = handle.join().expect("worker thread panicked")?; |
| 232 | + results.push(res); |
| 233 | + } |
201 | 234 |
|
202 |
| - tracing::trace!("`cargo tree --target={}` completed.", target_triple); |
| 235 | + // Process results and replicate outputs for de-duplicated platforms. |
| 236 | + for (cargo_host, cargo_target, output) in results { |
| 237 | + if !output.status.success() { |
| 238 | + tracing::error!("{}", String::from_utf8_lossy(&output.stdout)); |
| 239 | + tracing::error!("{}", String::from_utf8_lossy(&output.stderr)); |
| 240 | + bail!(format!("Failed to run cargo tree: {}", output.status)); |
| 241 | + } |
203 | 242 |
|
204 |
| - // Replicate outputs for any de-duplicated platforms |
205 |
| - for host_plat in cargo_host_triples[host_triple].iter() { |
206 |
| - for target_plat in cargo_target_triples[&target_triple].iter() { |
207 |
| - stdouts |
208 |
| - .entry((*host_plat).clone()) |
209 |
| - .or_default() |
210 |
| - .insert((*target_plat).clone(), output.stdout.clone()); |
211 |
| - } |
| 243 | + tracing::trace!( |
| 244 | + "`cargo tree --target={}` (host `{}`) completed.", |
| 245 | + cargo_target, |
| 246 | + cargo_host |
| 247 | + ); |
| 248 | + |
| 249 | + // Replicate outputs for any de-duplicated platforms |
| 250 | + for host_plat in cargo_host_triples[&cargo_host].iter() { |
| 251 | + for target_plat in cargo_target_triples[&cargo_target].iter() { |
| 252 | + stdouts |
| 253 | + .entry((*host_plat).clone()) |
| 254 | + .or_default() |
| 255 | + .insert((*target_plat).clone(), output.stdout.clone()); |
212 | 256 | }
|
213 | 257 | }
|
214 | 258 | }
|
|
0 commit comments