1use std::collections::HashMap;
2
3use crate::common::{
4 Config, KNOWN_CRATE_TYPES, KNOWN_TARGET_HAS_ATOMIC_WIDTHS, Sanitizer, query_rustc_output,
5};
6use crate::directives::{DirectiveLine, IgnoreDecision, KNOWN_DIRECTIVE_NAMES_SET};
7
8pub(super) fn handle_needs(
9 conditions: &PreparedNeedsConditions,
10 config: &Config,
11 ln: &DirectiveLine<'_>,
12) -> IgnoreDecision {
13 let &DirectiveLine { name, .. } = ln;
14
15 if !name.starts_with("needs-") {
16 return IgnoreDecision::Continue;
17 }
18
19 if name == "needs-target-has-atomic" {
20 let Some(rest) = ln.value_after_colon() else {
21 return IgnoreDecision::Error {
22 message: "expected `needs-target-has-atomic` to have a comma-separated list of atomic widths".to_string(),
23 };
24 };
25
26 let specified_widths = rest
28 .split(',')
29 .map(|width| width.trim())
30 .map(ToString::to_string)
31 .collect::<Vec<String>>();
32
33 for width in &specified_widths {
34 if !KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width.as_str()) {
35 return IgnoreDecision::Error {
36 message: format!(
37 "unknown width specified in `needs-target-has-atomic`: `{width}` is not a \
38 known `target_has_atomic_width`, known values are `{:?}`",
39 KNOWN_TARGET_HAS_ATOMIC_WIDTHS
40 ),
41 };
42 }
43 }
44
45 let satisfies_all_specified_widths = specified_widths
46 .iter()
47 .all(|specified| config.target_cfg().target_has_atomic.contains(specified));
48 if satisfies_all_specified_widths {
49 return IgnoreDecision::Continue;
50 } else {
51 return IgnoreDecision::Ignore {
52 reason: format!(
53 "skipping test as target does not support all of the required `target_has_atomic` widths `{:?}`",
54 specified_widths
55 ),
56 };
57 }
58 }
59
60 if name == "needs-crate-type" {
62 let Some(rest) = ln.value_after_colon() else {
63 return IgnoreDecision::Error {
64 message:
65 "expected `needs-crate-type` to have a comma-separated list of crate types"
66 .to_string(),
67 };
68 };
69
70 let specified_crate_types = rest
72 .split(',')
73 .map(|crate_type| crate_type.trim())
74 .map(ToString::to_string)
75 .collect::<Vec<String>>();
76
77 for crate_type in &specified_crate_types {
78 if !KNOWN_CRATE_TYPES.contains(&crate_type.as_str()) {
79 return IgnoreDecision::Error {
80 message: format!(
81 "unknown crate type specified in `needs-crate-type`: `{crate_type}` is not \
82 a known crate type, known values are `{:?}`",
83 KNOWN_CRATE_TYPES
84 ),
85 };
86 }
87 }
88
89 let satisfies_all_crate_types = specified_crate_types
90 .iter()
91 .all(|specified| config.supported_crate_types().contains(specified));
92 if satisfies_all_crate_types {
93 return IgnoreDecision::Continue;
94 } else {
95 return IgnoreDecision::Ignore {
96 reason: format!(
97 "skipping test as target does not support all of the crate types `{:?}`",
98 specified_crate_types
99 ),
100 };
101 }
102 }
103
104 if name == "needs-llvm-components" || name == "needs-backends" {
106 return IgnoreDecision::Continue;
107 }
108
109 if let Some(need) = conditions.simple_needs.get(name) {
110 if need.condition {
111 IgnoreDecision::Continue
112 } else {
113 IgnoreDecision::Ignore {
114 reason: if let Some(comment) = ln.remark_after_space() {
115 format!("{} ({})", need.ignore_reason, comment.trim())
116 } else {
117 need.ignore_reason.into()
118 },
119 }
120 }
121 } else {
122 IgnoreDecision::Error { message: format!("invalid needs directive: {name}") }
123 }
124}
125
126struct Need {
127 name: &'static str,
128 condition: bool,
129 ignore_reason: &'static str,
130}
131
132pub(crate) struct PreparedNeedsConditions {
133 simple_needs: HashMap<&'static str, Need>,
135}
136
137pub(crate) fn prepare_needs_conditions(config: &Config) -> PreparedNeedsConditions {
138 let target = config.target.as_str();
139 let sanitizers = config.target_cfg().sanitizers.as_slice();
140
141 let simple_needs = vec![
144 Need {
148 name: "needs-asm-ret",
149 condition: has_mnemonic(config, "ret"),
150 ignore_reason: "ignored on targets without a `ret` assembly instruction",
151 },
152 Need {
153 name: "needs-asm-support",
154 condition: config.has_asm_support(),
155 ignore_reason: "ignored on targets without inline assembly support",
156 },
157 Need {
158 name: "needs-sanitizer-support",
159 condition: std::env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(),
160 ignore_reason: "ignored on targets without sanitizers support",
161 },
162 Need {
163 name: "needs-sanitizer-address",
164 condition: sanitizers.contains(&Sanitizer::Address),
165 ignore_reason: "ignored on targets without address sanitizer",
166 },
167 Need {
168 name: "needs-sanitizer-cfi",
169 condition: sanitizers.contains(&Sanitizer::Cfi),
170 ignore_reason: "ignored on targets without CFI sanitizer",
171 },
172 Need {
173 name: "needs-sanitizer-dataflow",
174 condition: sanitizers.contains(&Sanitizer::Dataflow),
175 ignore_reason: "ignored on targets without dataflow sanitizer",
176 },
177 Need {
178 name: "needs-sanitizer-kcfi",
179 condition: sanitizers.contains(&Sanitizer::Kcfi),
180 ignore_reason: "ignored on targets without kernel CFI sanitizer",
181 },
182 Need {
183 name: "needs-sanitizer-kasan",
184 condition: sanitizers.contains(&Sanitizer::KernelAddress),
185 ignore_reason: "ignored on targets without kernel address sanitizer",
186 },
187 Need {
188 name: "needs-sanitizer-khwasan",
189 condition: sanitizers.contains(&Sanitizer::KernelHwaddress),
190 ignore_reason: "ignored on targets without kernel hardware-assisted address sanitizer",
191 },
192 Need {
193 name: "needs-sanitizer-leak",
194 condition: sanitizers.contains(&Sanitizer::Leak),
195 ignore_reason: "ignored on targets without leak sanitizer",
196 },
197 Need {
198 name: "needs-sanitizer-memory",
199 condition: sanitizers.contains(&Sanitizer::Memory),
200 ignore_reason: "ignored on targets without memory sanitizer",
201 },
202 Need {
203 name: "needs-sanitizer-thread",
204 condition: sanitizers.contains(&Sanitizer::Thread),
205 ignore_reason: "ignored on targets without thread sanitizer",
206 },
207 Need {
208 name: "needs-sanitizer-hwaddress",
209 condition: sanitizers.contains(&Sanitizer::Hwaddress),
210 ignore_reason: "ignored on targets without hardware-assisted address sanitizer",
211 },
212 Need {
213 name: "needs-sanitizer-memtag",
214 condition: sanitizers.contains(&Sanitizer::Memtag),
215 ignore_reason: "ignored on targets without memory tagging sanitizer",
216 },
217 Need {
218 name: "needs-sanitizer-realtime",
219 condition: sanitizers.contains(&Sanitizer::Realtime),
220 ignore_reason: "ignored on targets without realtime sanitizer",
221 },
222 Need {
223 name: "needs-sanitizer-shadow-call-stack",
224 condition: sanitizers.contains(&Sanitizer::ShadowCallStack),
225 ignore_reason: "ignored on targets without shadow call stacks",
226 },
227 Need {
228 name: "needs-sanitizer-safestack",
229 condition: sanitizers.contains(&Sanitizer::Safestack),
230 ignore_reason: "ignored on targets without SafeStack support",
231 },
232 Need {
233 name: "needs-enzyme",
234 condition: config.has_enzyme && config.default_codegen_backend.is_llvm(),
235 ignore_reason: "ignored when LLVM Enzyme is disabled or LLVM is not the default codegen backend",
236 },
237 Need {
238 name: "needs-offload",
239 condition: config.has_offload && config.default_codegen_backend.is_llvm(),
240 ignore_reason: "ignored when LLVM Offload is disabled or LLVM is not the default codegen backend",
241 },
242 Need {
243 name: "needs-run-enabled",
244 condition: config.run_enabled(),
245 ignore_reason: "ignored when running the resulting test binaries is disabled",
246 },
247 Need {
248 name: "needs-threads",
249 condition: config.has_threads(),
250 ignore_reason: "ignored on targets without threading support",
251 },
252 Need {
253 name: "needs-subprocess",
254 condition: config.has_subprocess_support(),
255 ignore_reason: "ignored on targets without subprocess support",
256 },
257 Need {
258 name: "needs-unwind",
259 condition: config.can_unwind(),
260 ignore_reason: "ignored on targets without unwinding support",
261 },
262 Need {
263 name: "needs-profiler-runtime",
264 condition: config.profiler_runtime,
265 ignore_reason: "ignored when the profiler runtime is not available",
266 },
267 Need {
268 name: "needs-force-clang-based-tests",
269 condition: config.run_clang_based_tests_with.is_some(),
270 ignore_reason: "ignored when RUSTBUILD_FORCE_CLANG_BASED_TESTS is not set",
271 },
272 Need {
273 name: "needs-xray",
274 condition: config.target_cfg().xray,
275 ignore_reason: "ignored on targets without xray tracing",
276 },
277 Need {
278 name: "needs-rust-lld",
279 condition: {
280 config
290 .host_compile_lib_path
291 .parent()
292 .expect("couldn't traverse to the parent of the specified --compile-lib-path")
293 .join("lib")
294 .join("rustlib")
295 .join(target)
296 .join("bin")
297 .join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" })
298 .exists()
299 },
300 ignore_reason: "ignored on targets without Rust's LLD",
301 },
302 Need {
303 name: "needs-dlltool",
304 condition: find_dlltool(config),
305 ignore_reason: "ignored when dlltool for the current architecture is not present",
306 },
307 Need {
308 name: "needs-git-hash",
309 condition: config.git_hash,
310 ignore_reason: "ignored when git hashes have been omitted for building",
311 },
312 Need {
313 name: "needs-dynamic-linking",
314 condition: config.target_cfg().dynamic_linking,
315 ignore_reason: "ignored on targets without dynamic linking",
316 },
317 Need {
318 name: "needs-relocation-model-pic",
319 condition: config.target_cfg().relocation_model == "pic",
320 ignore_reason: "ignored on targets without PIC relocation model",
321 },
322 Need {
323 name: "needs-deterministic-layouts",
324 condition: !config.rust_randomized_layout,
325 ignore_reason: "ignored when randomizing layouts",
326 },
327 Need {
328 name: "needs-wasmtime",
329 condition: config.runner.as_ref().is_some_and(|r| r.contains("wasmtime")),
330 ignore_reason: "ignored when wasmtime runner is not available",
331 },
332 Need {
333 name: "needs-symlink",
334 condition: has_symlinks(),
335 ignore_reason: "ignored if symlinks are unavailable",
336 },
337 Need {
338 name: "needs-llvm-zstd",
339 condition: config.default_codegen_backend.is_llvm() && llvm_has_zstd(config),
340 ignore_reason: "ignored if LLVM wasn't build with zstd for ELF section compression or LLVM is not the default codegen backend",
341 },
342 Need {
343 name: "needs-rustc-debug-assertions",
344 condition: config.with_rustc_debug_assertions,
345 ignore_reason: "ignored if rustc wasn't built with debug assertions",
346 },
347 Need {
348 name: "needs-std-debug-assertions",
349 condition: config.with_std_debug_assertions,
350 ignore_reason: "ignored if std wasn't built with debug assertions",
351 },
352 Need {
353 name: "needs-std-remap-debuginfo",
354 condition: config.with_std_remap_debuginfo,
355 ignore_reason: "ignored if std wasn't built with remapping of debuginfo",
356 },
357 Need {
358 name: "needs-target-std",
359 condition: build_helper::targets::target_supports_std(&config.target),
360 ignore_reason: "ignored if target does not support std",
361 },
362 ];
363 let simple_needs = simple_needs
364 .into_iter()
365 .map(|need| {
366 let name = need.name;
367 assert!(name.starts_with("needs-"), "must start with `needs-`: {name:?}");
368 assert!(KNOWN_DIRECTIVE_NAMES_SET.contains(name), "unknown directive name: {name:?}");
369 (name, need)
370 })
371 .collect::<HashMap<_, _>>();
372
373 PreparedNeedsConditions { simple_needs }
374}
375
376fn find_dlltool(config: &Config) -> bool {
377 let path = std::env::var_os("PATH").expect("missing PATH environment variable");
378 let path = std::env::split_paths(&path).collect::<Vec<_>>();
379
380 if !(config.matches_os("windows") && config.matches_env("gnu") && config.matches_abi("")) {
382 return false;
383 }
384
385 let dlltool_found = if cfg!(windows) {
388 path.iter().any(|dir| dir.join("dlltool.exe").is_file())
389 } else if config.matches_arch("i686") {
390 path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file())
391 } else if config.matches_arch("x86_64") {
392 path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file())
393 } else {
394 false
395 };
396 dlltool_found
397}
398
399#[cfg(windows)]
403fn has_symlinks() -> bool {
404 if std::env::var_os("CI").is_some() {
405 return true;
406 }
407 let link = std::env::temp_dir().join("RUST_COMPILETEST_SYMLINK_CHECK");
408 if std::os::windows::fs::symlink_file("DOES NOT EXIST", &link).is_ok() {
409 std::fs::remove_file(&link).unwrap();
410 true
411 } else {
412 false
413 }
414}
415
416#[cfg(not(windows))]
417fn has_symlinks() -> bool {
418 true
419}
420
421fn llvm_has_zstd(config: &Config) -> bool {
422 let output = query_rustc_output(
429 config,
430 &["-Zunstable-options", "--print=backend-has-zstd"],
431 Default::default(),
432 );
433 match output.trim() {
434 "true" => true,
435 "false" => false,
436 _ => panic!("unexpected output from `--print=backend-has-zstd`: {output:?}"),
437 }
438}
439
440fn has_mnemonic(config: &Config, mnemonic: &str) -> bool {
441 if !config.default_codegen_backend.is_llvm() {
442 return false;
443 }
444
445 let target_flag = format!("--target={}", config.target);
446 let output = query_rustc_output(
447 config,
448 &[
449 &target_flag,
450 "-Zunstable-options",
451 &format!("--print=backend-has-mnemonic:{}", mnemonic),
452 ],
453 Default::default(),
454 );
455
456 match output.trim() {
457 "true" => true,
458 "false" => false,
459 _ => panic!("unexpected output from `--print=backend-has-mnemonic:{mnemonic}`: {output:?}"),
460 }
461}