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        Need {
178            name: "needs-target-std",
179            condition: build_helper::targets::target_supports_std(&config.target),
180            ignore_reason: "ignored if target does not support std",
181        },
182    ];
183
184    let (name, rest) = match ln.split_once([':', ' ']) {
185        Some((name, rest)) => (name, Some(rest)),
186        None => (ln, None),
187    };
188
189    // FIXME(jieyouxu): tighten up this parsing to reject using both `:` and ` ` as means to
190    // delineate value.
191    if name == "needs-target-has-atomic" {
192        let Some(rest) = rest else {
193            return IgnoreDecision::Error {
194                message: "expected `needs-target-has-atomic` to have a comma-separated list of atomic widths".to_string(),
195            };
196        };
197
198        // Expect directive value to be a list of comma-separated atomic widths.
199        let specified_widths = rest
200            .split(',')
201            .map(|width| width.trim())
202            .map(ToString::to_string)
203            .collect::<Vec<String>>();
204
205        for width in &specified_widths {
206            if !KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width.as_str()) {
207                return IgnoreDecision::Error {
208                    message: format!(
209                        "unknown width specified in `needs-target-has-atomic`: `{width}` is not a \
210                        known `target_has_atomic_width`, known values are `{:?}`",
211                        KNOWN_TARGET_HAS_ATOMIC_WIDTHS
212                    ),
213                };
214            }
215        }
216
217        let satisfies_all_specified_widths = specified_widths
218            .iter()
219            .all(|specified| config.target_cfg().target_has_atomic.contains(specified));
220        if satisfies_all_specified_widths {
221            return IgnoreDecision::Continue;
222        } else {
223            return IgnoreDecision::Ignore {
224                reason: format!(
225                    "skipping test as target does not support all of the required `target_has_atomic` widths `{:?}`",
226                    specified_widths
227                ),
228            };
229        }
230    }
231
232    // FIXME(jieyouxu): share multi-value directive logic with `needs-target-has-atomic` above.
233    if name == "needs-crate-type" {
234        let Some(rest) = rest else {
235            return IgnoreDecision::Error {
236                message:
237                    "expected `needs-crate-type` to have a comma-separated list of crate types"
238                        .to_string(),
239            };
240        };
241
242        // Expect directive value to be a list of comma-separated crate-types.
243        let specified_crate_types = rest
244            .split(',')
245            .map(|crate_type| crate_type.trim())
246            .map(ToString::to_string)
247            .collect::<Vec<String>>();
248
249        for crate_type in &specified_crate_types {
250            if !KNOWN_CRATE_TYPES.contains(&crate_type.as_str()) {
251                return IgnoreDecision::Error {
252                    message: format!(
253                        "unknown crate type specified in `needs-crate-type`: `{crate_type}` is not \
254                        a known crate type, known values are `{:?}`",
255                        KNOWN_CRATE_TYPES
256                    ),
257                };
258            }
259        }
260
261        let satisfies_all_crate_types = specified_crate_types
262            .iter()
263            .all(|specified| config.supported_crate_types().contains(specified));
264        if satisfies_all_crate_types {
265            return IgnoreDecision::Continue;
266        } else {
267            return IgnoreDecision::Ignore {
268                reason: format!(
269                    "skipping test as target does not support all of the crate types `{:?}`",
270                    specified_crate_types
271                ),
272            };
273        }
274    }
275
276    if !name.starts_with("needs-") {
277        return IgnoreDecision::Continue;
278    }
279
280    // Handled elsewhere.
281    if name == "needs-llvm-components" {
282        return IgnoreDecision::Continue;
283    }
284
285    let mut found_valid = false;
286    for need in needs {
287        if need.name == name {
288            if need.condition {
289                found_valid = true;
290                break;
291            } else {
292                return IgnoreDecision::Ignore {
293                    reason: if let Some(comment) = rest {
294                        format!("{} ({})", need.ignore_reason, comment.trim())
295                    } else {
296                        need.ignore_reason.into()
297                    },
298                };
299            }
300        }
301    }
302
303    if found_valid {
304        IgnoreDecision::Continue
305    } else {
306        IgnoreDecision::Error { message: format!("invalid needs directive: {name}") }
307    }
308}
309
310struct Need {
311    name: &'static str,
312    condition: bool,
313    ignore_reason: &'static str,
314}
315
316pub(super) struct CachedNeedsConditions {
317    sanitizer_support: bool,
318    sanitizer_address: bool,
319    sanitizer_cfi: bool,
320    sanitizer_dataflow: bool,
321    sanitizer_kcfi: bool,
322    sanitizer_kasan: bool,
323    sanitizer_leak: bool,
324    sanitizer_memory: bool,
325    sanitizer_thread: bool,
326    sanitizer_hwaddress: bool,
327    sanitizer_memtag: bool,
328    sanitizer_shadow_call_stack: bool,
329    sanitizer_safestack: bool,
330    xray: bool,
331    rust_lld: bool,
332    dlltool: bool,
333    symlinks: bool,
334    /// Whether LLVM built with zstd, for the `needs-llvm-zstd` directive.
335    llvm_zstd: bool,
336}
337
338impl CachedNeedsConditions {
339    pub(super) fn load(config: &Config) -> Self {
340        let target = &&*config.target;
341        let sanitizers = &config.target_cfg().sanitizers;
342        Self {
343            sanitizer_support: std::env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(),
344            sanitizer_address: sanitizers.contains(&Sanitizer::Address),
345            sanitizer_cfi: sanitizers.contains(&Sanitizer::Cfi),
346            sanitizer_dataflow: sanitizers.contains(&Sanitizer::Dataflow),
347            sanitizer_kcfi: sanitizers.contains(&Sanitizer::Kcfi),
348            sanitizer_kasan: sanitizers.contains(&Sanitizer::KernelAddress),
349            sanitizer_leak: sanitizers.contains(&Sanitizer::Leak),
350            sanitizer_memory: sanitizers.contains(&Sanitizer::Memory),
351            sanitizer_thread: sanitizers.contains(&Sanitizer::Thread),
352            sanitizer_hwaddress: sanitizers.contains(&Sanitizer::Hwaddress),
353            sanitizer_memtag: sanitizers.contains(&Sanitizer::Memtag),
354            sanitizer_shadow_call_stack: sanitizers.contains(&Sanitizer::ShadowCallStack),
355            sanitizer_safestack: sanitizers.contains(&Sanitizer::Safestack),
356            xray: config.target_cfg().xray,
357
358            // For tests using the `needs-rust-lld` directive (e.g. for `-Clink-self-contained=+linker`),
359            // we need to find whether `rust-lld` is present in the compiler under test.
360            //
361            // The --compile-lib-path is the path to host shared libraries, but depends on the OS. For
362            // example:
363            // - on linux, it can be <sysroot>/lib
364            // - on windows, it can be <sysroot>/bin
365            //
366            // However, `rust-lld` is only located under the lib path, so we look for it there.
367            rust_lld: config
368                .compile_lib_path
369                .parent()
370                .expect("couldn't traverse to the parent of the specified --compile-lib-path")
371                .join("lib")
372                .join("rustlib")
373                .join(target)
374                .join("bin")
375                .join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" })
376                .exists(),
377
378            llvm_zstd: llvm_has_libzstd(&config),
379            dlltool: find_dlltool(&config),
380            symlinks: has_symlinks(),
381        }
382    }
383}
384
385fn find_dlltool(config: &Config) -> bool {
386    let path = std::env::var_os("PATH").expect("missing PATH environment variable");
387    let path = std::env::split_paths(&path).collect::<Vec<_>>();
388
389    // dlltool is used ony by GNU based `*-*-windows-gnu`
390    if !(config.matches_os("windows") && config.matches_env("gnu") && config.matches_abi("")) {
391        return false;
392    }
393
394    // On Windows, dlltool.exe is used for all architectures.
395    // For non-Windows, there are architecture specific dlltool binaries.
396    let dlltool_found = if cfg!(windows) {
397        path.iter().any(|dir| dir.join("dlltool.exe").is_file())
398    } else if config.matches_arch("i686") {
399        path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file())
400    } else if config.matches_arch("x86_64") {
401        path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file())
402    } else {
403        false
404    };
405    dlltool_found
406}
407
408// FIXME(#135928): this is actually not quite right because this detection is run on the **host**.
409// This however still helps the case of windows -> windows local development in case symlinks are
410// not available.
411#[cfg(windows)]
412fn has_symlinks() -> bool {
413    if std::env::var_os("CI").is_some() {
414        return true;
415    }
416    let link = std::env::temp_dir().join("RUST_COMPILETEST_SYMLINK_CHECK");
417    if std::os::windows::fs::symlink_file("DOES NOT EXIST", &link).is_ok() {
418        std::fs::remove_file(&link).unwrap();
419        true
420    } else {
421        false
422    }
423}
424
425#[cfg(not(windows))]
426fn has_symlinks() -> bool {
427    true
428}