compiletest/header/
needs.rs

1use crate::common::{Config, KNOWN_CRATE_TYPES, KNOWN_TARGET_HAS_ATOMIC_WIDTHS, Sanitizer};
2use crate::header::{IgnoreDecision, llvm_has_libzstd};
3
4pub(super) fn handle_needs(
5    cache: &CachedNeedsConditions,
6    config: &Config,
7    ln: &str,
8) -> IgnoreDecision {
9    // Note that we intentionally still put the needs- prefix here to make the file show up when
10    // grepping for a directive name, even though we could technically strip that.
11    let needs = &[
12        Need {
13            name: "needs-asm-support",
14            condition: config.has_asm_support(),
15            ignore_reason: "ignored on targets without inline assembly support",
16        },
17        Need {
18            name: "needs-sanitizer-support",
19            condition: cache.sanitizer_support,
20            ignore_reason: "ignored on targets without sanitizers support",
21        },
22        Need {
23            name: "needs-sanitizer-address",
24            condition: cache.sanitizer_address,
25            ignore_reason: "ignored on targets without address sanitizer",
26        },
27        Need {
28            name: "needs-sanitizer-cfi",
29            condition: cache.sanitizer_cfi,
30            ignore_reason: "ignored on targets without CFI sanitizer",
31        },
32        Need {
33            name: "needs-sanitizer-dataflow",
34            condition: cache.sanitizer_dataflow,
35            ignore_reason: "ignored on targets without dataflow sanitizer",
36        },
37        Need {
38            name: "needs-sanitizer-kcfi",
39            condition: cache.sanitizer_kcfi,
40            ignore_reason: "ignored on targets without kernel CFI sanitizer",
41        },
42        Need {
43            name: "needs-sanitizer-kasan",
44            condition: cache.sanitizer_kasan,
45            ignore_reason: "ignored on targets without kernel address sanitizer",
46        },
47        Need {
48            name: "needs-sanitizer-leak",
49            condition: cache.sanitizer_leak,
50            ignore_reason: "ignored on targets without leak sanitizer",
51        },
52        Need {
53            name: "needs-sanitizer-memory",
54            condition: cache.sanitizer_memory,
55            ignore_reason: "ignored on targets without memory sanitizer",
56        },
57        Need {
58            name: "needs-sanitizer-thread",
59            condition: cache.sanitizer_thread,
60            ignore_reason: "ignored on targets without thread sanitizer",
61        },
62        Need {
63            name: "needs-sanitizer-hwaddress",
64            condition: cache.sanitizer_hwaddress,
65            ignore_reason: "ignored on targets without hardware-assisted address sanitizer",
66        },
67        Need {
68            name: "needs-sanitizer-memtag",
69            condition: cache.sanitizer_memtag,
70            ignore_reason: "ignored on targets without memory tagging sanitizer",
71        },
72        Need {
73            name: "needs-sanitizer-shadow-call-stack",
74            condition: cache.sanitizer_shadow_call_stack,
75            ignore_reason: "ignored on targets without shadow call stacks",
76        },
77        Need {
78            name: "needs-sanitizer-safestack",
79            condition: cache.sanitizer_safestack,
80            ignore_reason: "ignored on targets without SafeStack support",
81        },
82        Need {
83            name: "needs-enzyme",
84            condition: config.has_enzyme,
85            ignore_reason: "ignored when LLVM Enzyme is disabled",
86        },
87        Need {
88            name: "needs-run-enabled",
89            condition: config.run_enabled(),
90            ignore_reason: "ignored when running the resulting test binaries is disabled",
91        },
92        Need {
93            name: "needs-threads",
94            condition: config.has_threads(),
95            ignore_reason: "ignored on targets without threading support",
96        },
97        Need {
98            name: "needs-subprocess",
99            condition: config.has_subprocess_support(),
100            ignore_reason: "ignored on targets without subprocess support",
101        },
102        Need {
103            name: "needs-unwind",
104            condition: config.can_unwind(),
105            ignore_reason: "ignored on targets without unwinding support",
106        },
107        Need {
108            name: "needs-profiler-runtime",
109            condition: config.profiler_runtime,
110            ignore_reason: "ignored when the profiler runtime is not available",
111        },
112        Need {
113            name: "needs-force-clang-based-tests",
114            condition: config.run_clang_based_tests_with.is_some(),
115            ignore_reason: "ignored when RUSTBUILD_FORCE_CLANG_BASED_TESTS is not set",
116        },
117        Need {
118            name: "needs-xray",
119            condition: cache.xray,
120            ignore_reason: "ignored on targets without xray tracing",
121        },
122        Need {
123            name: "needs-rust-lld",
124            condition: cache.rust_lld,
125            ignore_reason: "ignored on targets without Rust's LLD",
126        },
127        Need {
128            name: "needs-dlltool",
129            condition: cache.dlltool,
130            ignore_reason: "ignored when dlltool for the current architecture is not present",
131        },
132        Need {
133            name: "needs-git-hash",
134            condition: config.git_hash,
135            ignore_reason: "ignored when git hashes have been omitted for building",
136        },
137        Need {
138            name: "needs-dynamic-linking",
139            condition: config.target_cfg().dynamic_linking,
140            ignore_reason: "ignored on targets without dynamic linking",
141        },
142        Need {
143            name: "needs-relocation-model-pic",
144            condition: config.target_cfg().relocation_model == "pic",
145            ignore_reason: "ignored on targets without PIC relocation model",
146        },
147        Need {
148            name: "needs-deterministic-layouts",
149            condition: !config.rust_randomized_layout,
150            ignore_reason: "ignored when randomizing layouts",
151        },
152        Need {
153            name: "needs-wasmtime",
154            condition: config.runner.as_ref().is_some_and(|r| r.contains("wasmtime")),
155            ignore_reason: "ignored when wasmtime runner is not available",
156        },
157        Need {
158            name: "needs-symlink",
159            condition: cache.symlinks,
160            ignore_reason: "ignored if symlinks are unavailable",
161        },
162        Need {
163            name: "needs-llvm-zstd",
164            condition: cache.llvm_zstd,
165            ignore_reason: "ignored if LLVM wasn't build with zstd for ELF section compression",
166        },
167        Need {
168            name: "needs-rustc-debug-assertions",
169            condition: config.with_rustc_debug_assertions,
170            ignore_reason: "ignored if rustc wasn't built with debug assertions",
171        },
172        Need {
173            name: "needs-std-debug-assertions",
174            condition: config.with_std_debug_assertions,
175            ignore_reason: "ignored if std wasn't built with debug assertions",
176        },
177    ];
178
179    let (name, rest) = match ln.split_once([':', ' ']) {
180        Some((name, rest)) => (name, Some(rest)),
181        None => (ln, None),
182    };
183
184    // FIXME(jieyouxu): tighten up this parsing to reject using both `:` and ` ` as means to
185    // delineate value.
186    if name == "needs-target-has-atomic" {
187        let Some(rest) = rest else {
188            return IgnoreDecision::Error {
189                message: "expected `needs-target-has-atomic` to have a comma-separated list of atomic widths".to_string(),
190            };
191        };
192
193        // Expect directive value to be a list of comma-separated atomic widths.
194        let specified_widths = rest
195            .split(',')
196            .map(|width| width.trim())
197            .map(ToString::to_string)
198            .collect::<Vec<String>>();
199
200        for width in &specified_widths {
201            if !KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width.as_str()) {
202                return IgnoreDecision::Error {
203                    message: format!(
204                        "unknown width specified in `needs-target-has-atomic`: `{width}` is not a \
205                        known `target_has_atomic_width`, known values are `{:?}`",
206                        KNOWN_TARGET_HAS_ATOMIC_WIDTHS
207                    ),
208                };
209            }
210        }
211
212        let satisfies_all_specified_widths = specified_widths
213            .iter()
214            .all(|specified| config.target_cfg().target_has_atomic.contains(specified));
215        if satisfies_all_specified_widths {
216            return IgnoreDecision::Continue;
217        } else {
218            return IgnoreDecision::Ignore {
219                reason: format!(
220                    "skipping test as target does not support all of the required `target_has_atomic` widths `{:?}`",
221                    specified_widths
222                ),
223            };
224        }
225    }
226
227    // FIXME(jieyouxu): share multi-value directive logic with `needs-target-has-atomic` above.
228    if name == "needs-crate-type" {
229        let Some(rest) = rest else {
230            return IgnoreDecision::Error {
231                message:
232                    "expected `needs-crate-type` to have a comma-separated list of crate types"
233                        .to_string(),
234            };
235        };
236
237        // Expect directive value to be a list of comma-separated crate-types.
238        let specified_crate_types = rest
239            .split(',')
240            .map(|crate_type| crate_type.trim())
241            .map(ToString::to_string)
242            .collect::<Vec<String>>();
243
244        for crate_type in &specified_crate_types {
245            if !KNOWN_CRATE_TYPES.contains(&crate_type.as_str()) {
246                return IgnoreDecision::Error {
247                    message: format!(
248                        "unknown crate type specified in `needs-crate-type`: `{crate_type}` is not \
249                        a known crate type, known values are `{:?}`",
250                        KNOWN_CRATE_TYPES
251                    ),
252                };
253            }
254        }
255
256        let satisfies_all_crate_types = specified_crate_types
257            .iter()
258            .all(|specified| config.supported_crate_types().contains(specified));
259        if satisfies_all_crate_types {
260            return IgnoreDecision::Continue;
261        } else {
262            return IgnoreDecision::Ignore {
263                reason: format!(
264                    "skipping test as target does not support all of the crate types `{:?}`",
265                    specified_crate_types
266                ),
267            };
268        }
269    }
270
271    if !name.starts_with("needs-") {
272        return IgnoreDecision::Continue;
273    }
274
275    // Handled elsewhere.
276    if name == "needs-llvm-components" {
277        return IgnoreDecision::Continue;
278    }
279
280    let mut found_valid = false;
281    for need in needs {
282        if need.name == name {
283            if need.condition {
284                found_valid = true;
285                break;
286            } else {
287                return IgnoreDecision::Ignore {
288                    reason: if let Some(comment) = rest {
289                        format!("{} ({})", need.ignore_reason, comment.trim())
290                    } else {
291                        need.ignore_reason.into()
292                    },
293                };
294            }
295        }
296    }
297
298    if found_valid {
299        IgnoreDecision::Continue
300    } else {
301        IgnoreDecision::Error { message: format!("invalid needs directive: {name}") }
302    }
303}
304
305struct Need {
306    name: &'static str,
307    condition: bool,
308    ignore_reason: &'static str,
309}
310
311pub(super) struct CachedNeedsConditions {
312    sanitizer_support: bool,
313    sanitizer_address: bool,
314    sanitizer_cfi: bool,
315    sanitizer_dataflow: bool,
316    sanitizer_kcfi: bool,
317    sanitizer_kasan: bool,
318    sanitizer_leak: bool,
319    sanitizer_memory: bool,
320    sanitizer_thread: bool,
321    sanitizer_hwaddress: bool,
322    sanitizer_memtag: bool,
323    sanitizer_shadow_call_stack: bool,
324    sanitizer_safestack: bool,
325    xray: bool,
326    rust_lld: bool,
327    dlltool: bool,
328    symlinks: bool,
329    /// Whether LLVM built with zstd, for the `needs-llvm-zstd` directive.
330    llvm_zstd: bool,
331}
332
333impl CachedNeedsConditions {
334    pub(super) fn load(config: &Config) -> Self {
335        let target = &&*config.target;
336        let sanitizers = &config.target_cfg().sanitizers;
337        Self {
338            sanitizer_support: std::env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(),
339            sanitizer_address: sanitizers.contains(&Sanitizer::Address),
340            sanitizer_cfi: sanitizers.contains(&Sanitizer::Cfi),
341            sanitizer_dataflow: sanitizers.contains(&Sanitizer::Dataflow),
342            sanitizer_kcfi: sanitizers.contains(&Sanitizer::Kcfi),
343            sanitizer_kasan: sanitizers.contains(&Sanitizer::KernelAddress),
344            sanitizer_leak: sanitizers.contains(&Sanitizer::Leak),
345            sanitizer_memory: sanitizers.contains(&Sanitizer::Memory),
346            sanitizer_thread: sanitizers.contains(&Sanitizer::Thread),
347            sanitizer_hwaddress: sanitizers.contains(&Sanitizer::Hwaddress),
348            sanitizer_memtag: sanitizers.contains(&Sanitizer::Memtag),
349            sanitizer_shadow_call_stack: sanitizers.contains(&Sanitizer::ShadowCallStack),
350            sanitizer_safestack: sanitizers.contains(&Sanitizer::Safestack),
351            xray: config.target_cfg().xray,
352
353            // For tests using the `needs-rust-lld` directive (e.g. for `-Clink-self-contained=+linker`),
354            // we need to find whether `rust-lld` is present in the compiler under test.
355            //
356            // The --compile-lib-path is the path to host shared libraries, but depends on the OS. For
357            // example:
358            // - on linux, it can be <sysroot>/lib
359            // - on windows, it can be <sysroot>/bin
360            //
361            // However, `rust-lld` is only located under the lib path, so we look for it there.
362            rust_lld: config
363                .compile_lib_path
364                .parent()
365                .expect("couldn't traverse to the parent of the specified --compile-lib-path")
366                .join("lib")
367                .join("rustlib")
368                .join(target)
369                .join("bin")
370                .join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" })
371                .exists(),
372
373            llvm_zstd: llvm_has_libzstd(&config),
374            dlltool: find_dlltool(&config),
375            symlinks: has_symlinks(),
376        }
377    }
378}
379
380fn find_dlltool(config: &Config) -> bool {
381    let path = std::env::var_os("PATH").expect("missing PATH environment variable");
382    let path = std::env::split_paths(&path).collect::<Vec<_>>();
383
384    // dlltool is used ony by GNU based `*-*-windows-gnu`
385    if !(config.matches_os("windows") && config.matches_env("gnu") && config.matches_abi("")) {
386        return false;
387    }
388
389    // On Windows, dlltool.exe is used for all architectures.
390    // For non-Windows, there are architecture specific dlltool binaries.
391    let dlltool_found = if cfg!(windows) {
392        path.iter().any(|dir| dir.join("dlltool.exe").is_file())
393    } else if config.matches_arch("i686") {
394        path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file())
395    } else if config.matches_arch("x86_64") {
396        path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file())
397    } else {
398        false
399    };
400    dlltool_found
401}
402
403// FIXME(#135928): this is actually not quite right because this detection is run on the **host**.
404// This however still helps the case of windows -> windows local development in case symlinks are
405// not available.
406#[cfg(windows)]
407fn has_symlinks() -> bool {
408    if std::env::var_os("CI").is_some() {
409        return true;
410    }
411    let link = std::env::temp_dir().join("RUST_COMPILETEST_SYMLINK_CHECK");
412    if std::os::windows::fs::symlink_file("DOES NOT EXIST", &link).is_ok() {
413        std::fs::remove_file(&link).unwrap();
414        true
415    } else {
416        false
417    }
418}
419
420#[cfg(not(windows))]
421fn has_symlinks() -> bool {
422    true
423}