compiletest/directives/
needs.rs

1use crate::common::{Config, KNOWN_CRATE_TYPES, KNOWN_TARGET_HAS_ATOMIC_WIDTHS, Sanitizer};
2use crate::directives::{DirectiveLine, IgnoreDecision, llvm_has_libzstd};
3
4pub(super) fn handle_needs(
5    cache: &CachedNeedsConditions,
6    config: &Config,
7    ln: &DirectiveLine<'_>,
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-realtime",
74            condition: cache.sanitizer_realtime,
75            ignore_reason: "ignored on targets without realtime sanitizer",
76        },
77        Need {
78            name: "needs-sanitizer-shadow-call-stack",
79            condition: cache.sanitizer_shadow_call_stack,
80            ignore_reason: "ignored on targets without shadow call stacks",
81        },
82        Need {
83            name: "needs-sanitizer-safestack",
84            condition: cache.sanitizer_safestack,
85            ignore_reason: "ignored on targets without SafeStack support",
86        },
87        Need {
88            name: "needs-enzyme",
89            condition: config.has_enzyme && config.default_codegen_backend.is_llvm(),
90            ignore_reason: "ignored when LLVM Enzyme is disabled or LLVM is not the default codegen backend",
91        },
92        Need {
93            name: "needs-run-enabled",
94            condition: config.run_enabled(),
95            ignore_reason: "ignored when running the resulting test binaries is disabled",
96        },
97        Need {
98            name: "needs-threads",
99            condition: config.has_threads(),
100            ignore_reason: "ignored on targets without threading support",
101        },
102        Need {
103            name: "needs-subprocess",
104            condition: config.has_subprocess_support(),
105            ignore_reason: "ignored on targets without subprocess support",
106        },
107        Need {
108            name: "needs-unwind",
109            condition: config.can_unwind(),
110            ignore_reason: "ignored on targets without unwinding support",
111        },
112        Need {
113            name: "needs-profiler-runtime",
114            condition: config.profiler_runtime,
115            ignore_reason: "ignored when the profiler runtime is not available",
116        },
117        Need {
118            name: "needs-force-clang-based-tests",
119            condition: config.run_clang_based_tests_with.is_some(),
120            ignore_reason: "ignored when RUSTBUILD_FORCE_CLANG_BASED_TESTS is not set",
121        },
122        Need {
123            name: "needs-xray",
124            condition: cache.xray,
125            ignore_reason: "ignored on targets without xray tracing",
126        },
127        Need {
128            name: "needs-rust-lld",
129            condition: cache.rust_lld,
130            ignore_reason: "ignored on targets without Rust's LLD",
131        },
132        Need {
133            name: "needs-dlltool",
134            condition: cache.dlltool,
135            ignore_reason: "ignored when dlltool for the current architecture is not present",
136        },
137        Need {
138            name: "needs-git-hash",
139            condition: config.git_hash,
140            ignore_reason: "ignored when git hashes have been omitted for building",
141        },
142        Need {
143            name: "needs-dynamic-linking",
144            condition: config.target_cfg().dynamic_linking,
145            ignore_reason: "ignored on targets without dynamic linking",
146        },
147        Need {
148            name: "needs-relocation-model-pic",
149            condition: config.target_cfg().relocation_model == "pic",
150            ignore_reason: "ignored on targets without PIC relocation model",
151        },
152        Need {
153            name: "needs-deterministic-layouts",
154            condition: !config.rust_randomized_layout,
155            ignore_reason: "ignored when randomizing layouts",
156        },
157        Need {
158            name: "needs-wasmtime",
159            condition: config.runner.as_ref().is_some_and(|r| r.contains("wasmtime")),
160            ignore_reason: "ignored when wasmtime runner is not available",
161        },
162        Need {
163            name: "needs-symlink",
164            condition: cache.symlinks,
165            ignore_reason: "ignored if symlinks are unavailable",
166        },
167        Need {
168            name: "needs-llvm-zstd",
169            condition: cache.llvm_zstd && config.default_codegen_backend.is_llvm(),
170            ignore_reason: "ignored if LLVM wasn't build with zstd for ELF section compression or LLVM is not the default codegen backend",
171        },
172        Need {
173            name: "needs-rustc-debug-assertions",
174            condition: config.with_rustc_debug_assertions,
175            ignore_reason: "ignored if rustc wasn't built with debug assertions",
176        },
177        Need {
178            name: "needs-std-debug-assertions",
179            condition: config.with_std_debug_assertions,
180            ignore_reason: "ignored if std wasn't built with debug assertions",
181        },
182        Need {
183            name: "needs-target-std",
184            condition: build_helper::targets::target_supports_std(&config.target),
185            ignore_reason: "ignored if target does not support std",
186        },
187    ];
188
189    let &DirectiveLine { name, .. } = ln;
190
191    if name == "needs-target-has-atomic" {
192        let Some(rest) = ln.value_after_colon() 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) = ln.value_after_colon() 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) = ln.remark_after_space() {
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_realtime: bool,
329    sanitizer_shadow_call_stack: bool,
330    sanitizer_safestack: bool,
331    xray: bool,
332    rust_lld: bool,
333    dlltool: bool,
334    symlinks: bool,
335    /// Whether LLVM built with zstd, for the `needs-llvm-zstd` directive.
336    llvm_zstd: bool,
337}
338
339impl CachedNeedsConditions {
340    pub(super) fn load(config: &Config) -> Self {
341        let target = &&*config.target;
342        let sanitizers = &config.target_cfg().sanitizers;
343        Self {
344            sanitizer_support: std::env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(),
345            sanitizer_address: sanitizers.contains(&Sanitizer::Address),
346            sanitizer_cfi: sanitizers.contains(&Sanitizer::Cfi),
347            sanitizer_dataflow: sanitizers.contains(&Sanitizer::Dataflow),
348            sanitizer_kcfi: sanitizers.contains(&Sanitizer::Kcfi),
349            sanitizer_kasan: sanitizers.contains(&Sanitizer::KernelAddress),
350            sanitizer_leak: sanitizers.contains(&Sanitizer::Leak),
351            sanitizer_memory: sanitizers.contains(&Sanitizer::Memory),
352            sanitizer_thread: sanitizers.contains(&Sanitizer::Thread),
353            sanitizer_hwaddress: sanitizers.contains(&Sanitizer::Hwaddress),
354            sanitizer_memtag: sanitizers.contains(&Sanitizer::Memtag),
355            sanitizer_realtime: sanitizers.contains(&Sanitizer::Realtime),
356            sanitizer_shadow_call_stack: sanitizers.contains(&Sanitizer::ShadowCallStack),
357            sanitizer_safestack: sanitizers.contains(&Sanitizer::Safestack),
358            xray: config.target_cfg().xray,
359
360            // For tests using the `needs-rust-lld` directive (e.g. for `-Clink-self-contained=+linker`),
361            // we need to find whether `rust-lld` is present in the compiler under test.
362            //
363            // The --compile-lib-path is the path to host shared libraries, but depends on the OS. For
364            // example:
365            // - on linux, it can be <sysroot>/lib
366            // - on windows, it can be <sysroot>/bin
367            //
368            // However, `rust-lld` is only located under the lib path, so we look for it there.
369            rust_lld: config
370                .compile_lib_path
371                .parent()
372                .expect("couldn't traverse to the parent of the specified --compile-lib-path")
373                .join("lib")
374                .join("rustlib")
375                .join(target)
376                .join("bin")
377                .join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" })
378                .exists(),
379
380            llvm_zstd: llvm_has_libzstd(&config),
381            dlltool: find_dlltool(&config),
382            symlinks: has_symlinks(),
383        }
384    }
385}
386
387fn find_dlltool(config: &Config) -> bool {
388    let path = std::env::var_os("PATH").expect("missing PATH environment variable");
389    let path = std::env::split_paths(&path).collect::<Vec<_>>();
390
391    // dlltool is used ony by GNU based `*-*-windows-gnu`
392    if !(config.matches_os("windows") && config.matches_env("gnu") && config.matches_abi("")) {
393        return false;
394    }
395
396    // On Windows, dlltool.exe is used for all architectures.
397    // For non-Windows, there are architecture specific dlltool binaries.
398    let dlltool_found = if cfg!(windows) {
399        path.iter().any(|dir| dir.join("dlltool.exe").is_file())
400    } else if config.matches_arch("i686") {
401        path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file())
402    } else if config.matches_arch("x86_64") {
403        path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file())
404    } else {
405        false
406    };
407    dlltool_found
408}
409
410// FIXME(#135928): this is actually not quite right because this detection is run on the **host**.
411// This however still helps the case of windows -> windows local development in case symlinks are
412// not available.
413#[cfg(windows)]
414fn has_symlinks() -> bool {
415    if std::env::var_os("CI").is_some() {
416        return true;
417    }
418    let link = std::env::temp_dir().join("RUST_COMPILETEST_SYMLINK_CHECK");
419    if std::os::windows::fs::symlink_file("DOES NOT EXIST", &link).is_ok() {
420        std::fs::remove_file(&link).unwrap();
421        true
422    } else {
423        false
424    }
425}
426
427#[cfg(not(windows))]
428fn has_symlinks() -> bool {
429    true
430}