Skip to main content

compiletest/directives/
needs.rs

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        // Expect directive value to be a list of comma-separated atomic widths.
27        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    // FIXME(jieyouxu): share multi-value directive logic with `needs-target-has-atomic` above.
61    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        // Expect directive value to be a list of comma-separated crate-types.
71        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    // Handled elsewhere.
105    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    /// The `//@ needs-*` conditions that can be treated as a simple name->boolean mapping.
134    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    // Note that we intentionally still put the needs- prefix here to make the file show up when
142    // grepping for a directive name, even though we could technically strip that.
143    let simple_needs = vec![
144        // This used to be a more general `//@ needs-asm-mnemonic: ret` directive,
145        // but was simplified to just `//@ needs-asm-ret` because there are very
146        // few other mnemonics (`nop`?) that it could ever be useful with.
147        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                // For tests using the `needs-rust-lld` directive (e.g. for `-Clink-self-contained=+linker`),
281                // we need to find whether `rust-lld` is present in the compiler under test.
282                //
283                // The --compile-lib-path is the path to host shared libraries, but depends on the OS. For
284                // example:
285                // - on linux, it can be <sysroot>/lib
286                // - on windows, it can be <sysroot>/bin
287                //
288                // However, `rust-lld` is only located under the lib path, so we look for it there.
289                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    // dlltool is used ony by GNU based `*-*-windows-gnu`
381    if !(config.matches_os("windows") && config.matches_env("gnu") && config.matches_abi("")) {
382        return false;
383    }
384
385    // On Windows, dlltool.exe is used for all architectures.
386    // For non-Windows, there are architecture specific dlltool binaries.
387    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// FIXME(#135928): this is actually not quite right because this detection is run on the **host**.
400// This however still helps the case of windows -> windows local development in case symlinks are
401// not available.
402#[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    // FIXME(#149764): This actually queries the compiler's _default_ backend,
423    // which is usually LLVM, but can be another backend depending on the value
424    // of `rust.codegen-backends` in bootstrap.toml.
425
426    // The compiler already knows whether LLVM was built with zstd or not,
427    // so compiletest can just ask the compiler.
428    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}