aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/.gitignore2
-rw-r--r--scripts/Makefile4
-rw-r--r--scripts/Makefile.build5
-rw-r--r--scripts/Makefile.host6
-rw-r--r--scripts/Makefile.modfinal2
-rwxr-xr-xscripts/checkpatch.pl24
-rwxr-xr-xscripts/clang-tools/gen_compile_commands.py2
-rw-r--r--scripts/coccinelle/api/debugfs/debugfs_simple_attr.cocci68
-rw-r--r--scripts/gcc-plugins/gcc-common.h4
-rwxr-xr-xscripts/generate_rust_analyzer.py39
-rwxr-xr-xscripts/is_rust_module.sh16
-rw-r--r--scripts/kallsyms.c7
-rw-r--r--scripts/kconfig/gconf.c11
-rwxr-xr-xscripts/min-tool-version.sh4
-rwxr-xr-xscripts/rust_is_available.sh233
-rwxr-xr-xscripts/rust_is_available_test.py346
-rw-r--r--scripts/rustdoc_test_builder.rs72
-rw-r--r--scripts/rustdoc_test_gen.rs260
-rw-r--r--scripts/spelling.txt1
19 files changed, 929 insertions, 177 deletions
diff --git a/scripts/.gitignore b/scripts/.gitignore
index 6e9ce6720a05..3dbb8bb2457b 100644
--- a/scripts/.gitignore
+++ b/scripts/.gitignore
@@ -5,6 +5,8 @@
/kallsyms
/module.lds
/recordmcount
+/rustdoc_test_builder
+/rustdoc_test_gen
/sign-file
/sorttable
/target.json
diff --git a/scripts/Makefile b/scripts/Makefile
index 32b6ba722728..576cf64be667 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -9,6 +9,8 @@ hostprogs-always-$(CONFIG_BUILDTIME_TABLE_SORT) += sorttable
hostprogs-always-$(CONFIG_ASN1) += asn1_compiler
hostprogs-always-$(CONFIG_MODULE_SIG_FORMAT) += sign-file
hostprogs-always-$(CONFIG_SYSTEM_EXTRA_CERTIFICATE) += insert-sys-cert
+hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_builder
+hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_gen
always-$(CONFIG_RUST) += target.json
filechk_rust_target = $< < include/config/auto.conf
@@ -18,6 +20,8 @@ $(obj)/target.json: scripts/generate_rust_target include/config/auto.conf FORCE
hostprogs += generate_rust_target
generate_rust_target-rust := y
+rustdoc_test_builder-rust := y
+rustdoc_test_gen-rust := y
HOSTCFLAGS_sorttable.o = -I$(srctree)/tools/include
HOSTLDLIBS_sorttable = -lpthread
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index 6413342a03f4..82e3fb19fdaf 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -264,6 +264,9 @@ $(obj)/%.lst: $(src)/%.c FORCE
rust_allowed_features := new_uninit
+# `--out-dir` is required to avoid temporaries being created by `rustc` in the
+# current working directory, which may be not accessible in the out-of-tree
+# modules case.
rust_common_cmd = \
RUST_MODFILE=$(modfile) $(RUSTC_OR_CLIPPY) $(rust_flags) \
-Zallow-features=$(rust_allowed_features) \
@@ -272,7 +275,7 @@ rust_common_cmd = \
--extern alloc --extern kernel \
--crate-type rlib -L $(objtree)/rust/ \
--crate-name $(basename $(notdir $@)) \
- --emit=dep-info=$(depfile)
+ --out-dir $(dir $@) --emit=dep-info=$(depfile)
# `--emit=obj`, `--emit=asm` and `--emit=llvm-ir` imply a single codegen unit
# will be used. We explicitly request `-Ccodegen-units=1` in any case, and
diff --git a/scripts/Makefile.host b/scripts/Makefile.host
index 7aea9005e497..8f7f842b54f9 100644
--- a/scripts/Makefile.host
+++ b/scripts/Makefile.host
@@ -86,7 +86,11 @@ hostc_flags = -Wp,-MMD,$(depfile) \
hostcxx_flags = -Wp,-MMD,$(depfile) \
$(KBUILD_HOSTCXXFLAGS) $(HOST_EXTRACXXFLAGS) \
$(HOSTCXXFLAGS_$(target-stem).o)
-hostrust_flags = --emit=dep-info=$(depfile) \
+
+# `--out-dir` is required to avoid temporaries being created by `rustc` in the
+# current working directory, which may be not accessible in the out-of-tree
+# modules case.
+hostrust_flags = --out-dir $(dir $@) --emit=dep-info=$(depfile) \
$(KBUILD_HOSTRUSTFLAGS) $(HOST_EXTRARUSTFLAGS) \
$(HOSTRUSTFLAGS_$(target-stem))
diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index fc19f67039bd..b3a6aa8fbe8c 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -41,8 +41,6 @@ quiet_cmd_btf_ko = BTF [M] $@
cmd_btf_ko = \
if [ ! -f vmlinux ]; then \
printf "Skipping BTF generation for %s due to unavailability of vmlinux\n" $@ 1>&2; \
- elif [ -n "$(CONFIG_RUST)" ] && $(srctree)/scripts/is_rust_module.sh $@; then \
- printf "Skipping BTF generation for %s because it's a Rust module\n" $@ 1>&2; \
else \
LLVM_OBJCOPY="$(OBJCOPY)" $(PAHOLE) -J $(PAHOLE_FLAGS) --btf_base vmlinux $@; \
$(RESOLVE_BTFIDS) -b vmlinux $@; \
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 880fde13d9b8..a9841148cde2 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -7457,6 +7457,30 @@ sub process {
}
}
+# Complain about RCU Tasks Trace used outside of BPF (and of course, RCU).
+ our $rcu_trace_funcs = qr{(?x:
+ rcu_read_lock_trace |
+ rcu_read_lock_trace_held |
+ rcu_read_unlock_trace |
+ call_rcu_tasks_trace |
+ synchronize_rcu_tasks_trace |
+ rcu_barrier_tasks_trace |
+ rcu_request_urgent_qs_task
+ )};
+ our $rcu_trace_paths = qr{(?x:
+ kernel/bpf/ |
+ include/linux/bpf |
+ net/bpf/ |
+ kernel/rcu/ |
+ include/linux/rcu
+ )};
+ if ($line =~ /\b($rcu_trace_funcs)\s*\(/) {
+ if ($realfile !~ m{^$rcu_trace_paths}) {
+ WARN("RCU_TASKS_TRACE",
+ "use of RCU tasks trace is incorrect outside BPF or core RCU code\n" . $herecurr);
+ }
+ }
+
# check for lockdep_set_novalidate_class
if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ ||
$line =~ /__lockdep_no_validate__\s*\)/ ) {
diff --git a/scripts/clang-tools/gen_compile_commands.py b/scripts/clang-tools/gen_compile_commands.py
index 15ba56527acd..a84cc5737c2c 100755
--- a/scripts/clang-tools/gen_compile_commands.py
+++ b/scripts/clang-tools/gen_compile_commands.py
@@ -19,7 +19,7 @@ _DEFAULT_OUTPUT = 'compile_commands.json'
_DEFAULT_LOG_LEVEL = 'WARNING'
_FILENAME_PATTERN = r'^\..*\.cmd$'
-_LINE_PATTERN = r'^savedcmd_[^ ]*\.o := (.* )([^ ]*\.c) *(;|$)'
+_LINE_PATTERN = r'^savedcmd_[^ ]*\.o := (.* )([^ ]*\.[cS]) *(;|$)'
_VALID_LOG_LEVELS = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
# The tools/ directory adopts a different build system, and produces .cmd
# files in a different format. Do not support it.
diff --git a/scripts/coccinelle/api/debugfs/debugfs_simple_attr.cocci b/scripts/coccinelle/api/debugfs/debugfs_simple_attr.cocci
deleted file mode 100644
index 7c312310547c..000000000000
--- a/scripts/coccinelle/api/debugfs/debugfs_simple_attr.cocci
+++ /dev/null
@@ -1,68 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/// Use DEFINE_DEBUGFS_ATTRIBUTE rather than DEFINE_SIMPLE_ATTRIBUTE
-/// for debugfs files.
-///
-//# Rationale: DEFINE_SIMPLE_ATTRIBUTE + debugfs_create_file()
-//# imposes some significant overhead as compared to
-//# DEFINE_DEBUGFS_ATTRIBUTE + debugfs_create_file_unsafe().
-//
-// Copyright (C): 2016 Nicolai Stange
-// Options: --no-includes
-//
-
-virtual context
-virtual patch
-virtual org
-virtual report
-
-@dsa@
-declarer name DEFINE_SIMPLE_ATTRIBUTE;
-identifier dsa_fops;
-expression dsa_get, dsa_set, dsa_fmt;
-position p;
-@@
-DEFINE_SIMPLE_ATTRIBUTE@p(dsa_fops, dsa_get, dsa_set, dsa_fmt);
-
-@dcf@
-expression name, mode, parent, data;
-identifier dsa.dsa_fops;
-@@
-debugfs_create_file(name, mode, parent, data, &dsa_fops)
-
-
-@context_dsa depends on context && dcf@
-declarer name DEFINE_DEBUGFS_ATTRIBUTE;
-identifier dsa.dsa_fops;
-expression dsa.dsa_get, dsa.dsa_set, dsa.dsa_fmt;
-@@
-* DEFINE_SIMPLE_ATTRIBUTE(dsa_fops, dsa_get, dsa_set, dsa_fmt);
-
-
-@patch_dcf depends on patch expression@
-expression name, mode, parent, data;
-identifier dsa.dsa_fops;
-@@
-- debugfs_create_file(name, mode, parent, data, &dsa_fops)
-+ debugfs_create_file_unsafe(name, mode, parent, data, &dsa_fops)
-
-@patch_dsa depends on patch_dcf && patch@
-identifier dsa.dsa_fops;
-expression dsa.dsa_get, dsa.dsa_set, dsa.dsa_fmt;
-@@
-- DEFINE_SIMPLE_ATTRIBUTE(dsa_fops, dsa_get, dsa_set, dsa_fmt);
-+ DEFINE_DEBUGFS_ATTRIBUTE(dsa_fops, dsa_get, dsa_set, dsa_fmt);
-
-
-@script:python depends on org && dcf@
-fops << dsa.dsa_fops;
-p << dsa.p;
-@@
-msg="%s should be defined with DEFINE_DEBUGFS_ATTRIBUTE" % (fops)
-coccilib.org.print_todo(p[0], msg)
-
-@script:python depends on report && dcf@
-fops << dsa.dsa_fops;
-p << dsa.p;
-@@
-msg="WARNING: %s should be defined with DEFINE_DEBUGFS_ATTRIBUTE" % (fops)
-coccilib.report.print_report(p[0], msg)
diff --git a/scripts/gcc-plugins/gcc-common.h b/scripts/gcc-plugins/gcc-common.h
index 84c730da36dd..1ae39b9f4a95 100644
--- a/scripts/gcc-plugins/gcc-common.h
+++ b/scripts/gcc-plugins/gcc-common.h
@@ -440,4 +440,8 @@ static inline void debug_gimple_stmt(const_gimple s)
#define SET_DECL_MODE(decl, mode) DECL_MODE(decl) = (mode)
#endif
+#if BUILDING_GCC_VERSION >= 14000
+#define last_stmt(x) last_nondebug_stmt(x)
+#endif
+
#endif
diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py
index 946e250c1b2a..fc52bc41d3e7 100755
--- a/scripts/generate_rust_analyzer.py
+++ b/scripts/generate_rust_analyzer.py
@@ -6,10 +6,19 @@
import argparse
import json
import logging
+import os
import pathlib
import sys
-def generate_crates(srctree, objtree, sysroot_src):
+def args_crates_cfgs(cfgs):
+ crates_cfgs = {}
+ for cfg in cfgs:
+ crate, vals = cfg.split("=", 1)
+ crates_cfgs[crate] = vals.replace("--cfg", "").split()
+
+ return crates_cfgs
+
+def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs):
# Generate the configuration list.
cfg = []
with open(objtree / "include" / "generated" / "rustc_cfg") as fd:
@@ -23,6 +32,7 @@ def generate_crates(srctree, objtree, sysroot_src):
# Avoid O(n^2) iterations by keeping a map of indexes.
crates = []
crates_indexes = {}
+ crates_cfgs = args_crates_cfgs(cfgs)
def append_crate(display_name, root_module, deps, cfg=[], is_workspace_member=True, is_proc_macro=False):
crates_indexes[display_name] = len(crates)
@@ -44,6 +54,7 @@ def generate_crates(srctree, objtree, sysroot_src):
"core",
sysroot_src / "core" / "src" / "lib.rs",
[],
+ cfg=crates_cfgs.get("core", []),
is_workspace_member=False,
)
@@ -57,6 +68,7 @@ def generate_crates(srctree, objtree, sysroot_src):
"alloc",
srctree / "rust" / "alloc" / "lib.rs",
["core", "compiler_builtins"],
+ cfg=crates_cfgs.get("alloc", []),
)
append_crate(
@@ -65,7 +77,7 @@ def generate_crates(srctree, objtree, sysroot_src):
[],
is_proc_macro=True,
)
- crates[-1]["proc_macro_dylib_path"] = "rust/libmacros.so"
+ crates[-1]["proc_macro_dylib_path"] = f"{objtree}/rust/libmacros.so"
append_crate(
"build_error",
@@ -95,19 +107,26 @@ def generate_crates(srctree, objtree, sysroot_src):
"exclude_dirs": [],
}
+ def is_root_crate(build_file, target):
+ try:
+ return f"{target}.o" in open(build_file).read()
+ except FileNotFoundError:
+ return False
+
# Then, the rest outside of `rust/`.
#
# We explicitly mention the top-level folders we want to cover.
- for folder in ("samples", "drivers"):
- for path in (srctree / folder).rglob("*.rs"):
+ extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers"))
+ if external_src is not None:
+ extra_dirs = [external_src]
+ for folder in extra_dirs:
+ for path in folder.rglob("*.rs"):
logging.info("Checking %s", path)
name = path.name.replace(".rs", "")
# Skip those that are not crate roots.
- try:
- if f"{name}.o" not in open(path.parent / "Makefile").read():
- continue
- except FileNotFoundError:
+ if not is_root_crate(path.parent / "Makefile", name) and \
+ not is_root_crate(path.parent / "Kbuild", name):
continue
logging.info("Adding %s", name)
@@ -123,9 +142,11 @@ def generate_crates(srctree, objtree, sysroot_src):
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', '-v', action='store_true')
+ parser.add_argument('--cfgs', action='append', default=[])
parser.add_argument("srctree", type=pathlib.Path)
parser.add_argument("objtree", type=pathlib.Path)
parser.add_argument("sysroot_src", type=pathlib.Path)
+ parser.add_argument("exttree", type=pathlib.Path, nargs="?")
args = parser.parse_args()
logging.basicConfig(
@@ -134,7 +155,7 @@ def main():
)
rust_project = {
- "crates": generate_crates(args.srctree, args.objtree, args.sysroot_src),
+ "crates": generate_crates(args.srctree, args.objtree, args.sysroot_src, args.exttree, args.cfgs),
"sysroot_src": str(args.sysroot_src),
}
diff --git a/scripts/is_rust_module.sh b/scripts/is_rust_module.sh
deleted file mode 100755
index 464761a7cf7f..000000000000
--- a/scripts/is_rust_module.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-# SPDX-License-Identifier: GPL-2.0
-#
-# is_rust_module.sh module.ko
-#
-# Returns `0` if `module.ko` is a Rust module, `1` otherwise.
-
-set -e
-
-# Using the `16_` prefix ensures other symbols with the same substring
-# are not picked up (even if it would be unlikely). The last part is
-# used just in case LLVM decides to use the `.` suffix.
-#
-# In the future, checking for the `.comment` section may be another
-# option, see https://github.com/rust-lang/rust/pull/97550.
-${NM} "$*" | grep -qE '^[0-9a-fA-F]+ [Rr] _R[^[:space:]]+16___IS_RUST_MODULE[^[:space:]]*$'
diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c
index d387c9381650..653b92f6d4c8 100644
--- a/scripts/kallsyms.c
+++ b/scripts/kallsyms.c
@@ -129,6 +129,7 @@ static struct sym_entry *read_symbol(FILE *in, char **buf, size_t *buf_len)
ssize_t readlen;
struct sym_entry *sym;
+ errno = 0;
readlen = getline(buf, buf_len, in);
if (readlen < 0) {
if (errno) {
@@ -349,10 +350,10 @@ static void cleanup_symbol_name(char *s)
* ASCII[_] = 5f
* ASCII[a-z] = 61,7a
*
- * As above, replacing '.' with '\0' does not affect the main sorting,
- * but it helps us with subsorting.
+ * As above, replacing the first '.' in ".llvm." with '\0' does not
+ * affect the main sorting, but it helps us with subsorting.
*/
- p = strchr(s, '.');
+ p = strstr(s, ".llvm.");
if (p)
*p = '\0';
}
diff --git a/scripts/kconfig/gconf.c b/scripts/kconfig/gconf.c
index 17adabfd6e6b..9709aca3a30f 100644
--- a/scripts/kconfig/gconf.c
+++ b/scripts/kconfig/gconf.c
@@ -636,7 +636,7 @@ void on_introduction1_activate(GtkMenuItem * menuitem, gpointer user_data)
{
GtkWidget *dialog;
const gchar *intro_text =
- "Welcome to gkc, the GTK+ graphical configuration tool\n"
+ "Welcome to gconfig, the GTK+ graphical configuration tool.\n"
"For each option, a blank box indicates the feature is disabled, a\n"
"check indicates it is enabled, and a dot indicates that it is to\n"
"be compiled as a module. Clicking on the box will cycle through the three states.\n"
@@ -647,10 +647,7 @@ void on_introduction1_activate(GtkMenuItem * menuitem, gpointer user_data)
"Although there is no cross reference yet to help you figure out\n"
"what other options must be enabled to support the option you\n"
"are interested in, you can still view the help of a grayed-out\n"
- "option.\n"
- "\n"
- "Toggling Show Debug Info under the Options menu will show \n"
- "the dependencies, which you can then match by examining other options.";
+ "option.";
dialog = gtk_message_dialog_new(GTK_WINDOW(main_wnd),
GTK_DIALOG_DESTROY_WITH_PARENT,
@@ -667,7 +664,7 @@ void on_about1_activate(GtkMenuItem * menuitem, gpointer user_data)
{
GtkWidget *dialog;
const gchar *about_text =
- "gkc is copyright (c) 2002 Romain Lievin <roms@lpg.ticalc.org>.\n"
+ "gconfig is copyright (c) 2002 Romain Lievin <roms@lpg.ticalc.org>.\n"
"Based on the source code from Roman Zippel.\n";
dialog = gtk_message_dialog_new(GTK_WINDOW(main_wnd),
@@ -685,7 +682,7 @@ void on_license1_activate(GtkMenuItem * menuitem, gpointer user_data)
{
GtkWidget *dialog;
const gchar *license_text =
- "gkc is released under the terms of the GNU GPL v2.\n"
+ "gconfig is released under the terms of the GNU GPL v2.\n"
"For more information, please see the source code or\n"
"visit http://www.fsf.org/licenses/licenses.html\n";
diff --git a/scripts/min-tool-version.sh b/scripts/min-tool-version.sh
index 2ade63149466..d65ab8bfeaf4 100755
--- a/scripts/min-tool-version.sh
+++ b/scripts/min-tool-version.sh
@@ -31,10 +31,10 @@ llvm)
fi
;;
rustc)
- echo 1.68.2
+ echo 1.71.1
;;
bindgen)
- echo 0.56.0
+ echo 0.65.1
;;
*)
echo "$1: unknown tool" >&2
diff --git a/scripts/rust_is_available.sh b/scripts/rust_is_available.sh
index aebbf1913970..117018946b57 100755
--- a/scripts/rust_is_available.sh
+++ b/scripts/rust_is_available.sh
@@ -2,8 +2,6 @@
# SPDX-License-Identifier: GPL-2.0
#
# Tests whether a suitable Rust toolchain is available.
-#
-# Pass `-v` for human output and more checks (as warnings).
set -e
@@ -21,102 +19,208 @@ get_canonical_version()
echo $((100000 * $1 + 100 * $2 + $3))
}
+# Print a reference to the Quick Start guide in the documentation.
+print_docs_reference()
+{
+ echo >&2 "***"
+ echo >&2 "*** Please see Documentation/rust/quick-start.rst for details"
+ echo >&2 "*** on how to set up the Rust support."
+ echo >&2 "***"
+}
+
+# Print an explanation about the fact that the script is meant to be called from Kbuild.
+print_kbuild_explanation()
+{
+ echo >&2 "***"
+ echo >&2 "*** This script is intended to be called from Kbuild."
+ echo >&2 "*** Please use the 'rustavailable' target to call it instead."
+ echo >&2 "*** Otherwise, the results may not be meaningful."
+ exit 1
+}
+
+# If the script fails for any reason, or if there was any warning, then
+# print a reference to the documentation on exit.
+warning=0
+trap 'if [ $? -ne 0 ] || [ $warning -ne 0 ]; then print_docs_reference; fi' EXIT
+
+# Check that the expected environment variables are set.
+if [ -z "${RUSTC+x}" ]; then
+ echo >&2 "***"
+ echo >&2 "*** Environment variable 'RUSTC' is not set."
+ print_kbuild_explanation
+fi
+
+if [ -z "${BINDGEN+x}" ]; then
+ echo >&2 "***"
+ echo >&2 "*** Environment variable 'BINDGEN' is not set."
+ print_kbuild_explanation
+fi
+
+if [ -z "${CC+x}" ]; then
+ echo >&2 "***"
+ echo >&2 "*** Environment variable 'CC' is not set."
+ print_kbuild_explanation
+fi
+
# Check that the Rust compiler exists.
if ! command -v "$RUSTC" >/dev/null; then
- if [ "$1" = -v ]; then
- echo >&2 "***"
- echo >&2 "*** Rust compiler '$RUSTC' could not be found."
- echo >&2 "***"
- fi
+ echo >&2 "***"
+ echo >&2 "*** Rust compiler '$RUSTC' could not be found."
+ echo >&2 "***"
exit 1
fi
# Check that the Rust bindings generator exists.
if ! command -v "$BINDGEN" >/dev/null; then
- if [ "$1" = -v ]; then
- echo >&2 "***"
- echo >&2 "*** Rust bindings generator '$BINDGEN' could not be found."
- echo >&2 "***"
- fi
+ echo >&2 "***"
+ echo >&2 "*** Rust bindings generator '$BINDGEN' could not be found."
+ echo >&2 "***"
exit 1
fi
# Check that the Rust compiler version is suitable.
#
# Non-stable and distributions' versions may have a version suffix, e.g. `-dev`.
+rust_compiler_output=$( \
+ LC_ALL=C "$RUSTC" --version 2>/dev/null
+) || rust_compiler_code=$?
+if [ -n "$rust_compiler_code" ]; then
+ echo >&2 "***"
+ echo >&2 "*** Running '$RUSTC' to check the Rust compiler version failed with"
+ echo >&2 "*** code $rust_compiler_code. See output and docs below for details:"
+ echo >&2 "***"
+ echo >&2 "$rust_compiler_output"
+ echo >&2 "***"
+ exit 1
+fi
rust_compiler_version=$( \
- LC_ALL=C "$RUSTC" --version 2>/dev/null \
- | head -n 1 \
- | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' \
+ echo "$rust_compiler_output" \
+ | sed -nE '1s:.*rustc ([0-9]+\.[0-9]+\.[0-9]+).*:\1:p'
)
+if [ -z "$rust_compiler_version" ]; then
+ echo >&2 "***"
+ echo >&2 "*** Running '$RUSTC' to check the Rust compiler version did not return"
+ echo >&2 "*** an expected output. See output and docs below for details:"
+ echo >&2 "***"
+ echo >&2 "$rust_compiler_output"
+ echo >&2 "***"
+ exit 1
+fi
rust_compiler_min_version=$($min_tool_version rustc)
rust_compiler_cversion=$(get_canonical_version $rust_compiler_version)
rust_compiler_min_cversion=$(get_canonical_version $rust_compiler_min_version)
if [ "$rust_compiler_cversion" -lt "$rust_compiler_min_cversion" ]; then
- if [ "$1" = -v ]; then
- echo >&2 "***"
- echo >&2 "*** Rust compiler '$RUSTC' is too old."
- echo >&2 "*** Your version: $rust_compiler_version"
- echo >&2 "*** Minimum version: $rust_compiler_min_version"
- echo >&2 "***"
- fi
+ echo >&2 "***"
+ echo >&2 "*** Rust compiler '$RUSTC' is too old."
+ echo >&2 "*** Your version: $rust_compiler_version"
+ echo >&2 "*** Minimum version: $rust_compiler_min_version"
+ echo >&2 "***"
exit 1
fi
-if [ "$1" = -v ] && [ "$rust_compiler_cversion" -gt "$rust_compiler_min_cversion" ]; then
+if [ "$rust_compiler_cversion" -gt "$rust_compiler_min_cversion" ]; then
echo >&2 "***"
echo >&2 "*** Rust compiler '$RUSTC' is too new. This may or may not work."
echo >&2 "*** Your version: $rust_compiler_version"
echo >&2 "*** Expected version: $rust_compiler_min_version"
echo >&2 "***"
+ warning=1
fi
# Check that the Rust bindings generator is suitable.
#
# Non-stable and distributions' versions may have a version suffix, e.g. `-dev`.
+rust_bindings_generator_output=$( \
+ LC_ALL=C "$BINDGEN" --version 2>/dev/null
+) || rust_bindings_generator_code=$?
+if [ -n "$rust_bindings_generator_code" ]; then
+ echo >&2 "***"
+ echo >&2 "*** Running '$BINDGEN' to check the Rust bindings generator version failed with"
+ echo >&2 "*** code $rust_bindings_generator_code. See output and docs below for details:"
+ echo >&2 "***"
+ echo >&2 "$rust_bindings_generator_output"
+ echo >&2 "***"
+ exit 1
+fi
rust_bindings_generator_version=$( \
- LC_ALL=C "$BINDGEN" --version 2>/dev/null \
- | head -n 1 \
- | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' \
+ echo "$rust_bindings_generator_output" \
+ | sed -nE '1s:.*bindgen ([0-9]+\.[0-9]+\.[0-9]+).*:\1:p'
)
+if [ -z "$rust_bindings_generator_version" ]; then
+ echo >&2 "***"
+ echo >&2 "*** Running '$BINDGEN' to check the bindings generator version did not return"
+ echo >&2 "*** an expected output. See output and docs below for details:"
+ echo >&2 "***"
+ echo >&2 "$rust_bindings_generator_output"
+ echo >&2 "***"
+ exit 1
+fi
rust_bindings_generator_min_version=$($min_tool_version bindgen)
rust_bindings_generator_cversion=$(get_canonical_version $rust_bindings_generator_version)
rust_bindings_generator_min_cversion=$(get_canonical_version $rust_bindings_generator_min_version)
if [ "$rust_bindings_generator_cversion" -lt "$rust_bindings_generator_min_cversion" ]; then
- if [ "$1" = -v ]; then
- echo >&2 "***"
- echo >&2 "*** Rust bindings generator '$BINDGEN' is too old."
- echo >&2 "*** Your version: $rust_bindings_generator_version"
- echo >&2 "*** Minimum version: $rust_bindings_generator_min_version"
- echo >&2 "***"
- fi
+ echo >&2 "***"
+ echo >&2 "*** Rust bindings generator '$BINDGEN' is too old."
+ echo >&2 "*** Your version: $rust_bindings_generator_version"
+ echo >&2 "*** Minimum version: $rust_bindings_generator_min_version"
+ echo >&2 "***"
exit 1
fi
-if [ "$1" = -v ] && [ "$rust_bindings_generator_cversion" -gt "$rust_bindings_generator_min_cversion" ]; then
+if [ "$rust_bindings_generator_cversion" -gt "$rust_bindings_generator_min_cversion" ]; then
echo >&2 "***"
echo >&2 "*** Rust bindings generator '$BINDGEN' is too new. This may or may not work."
echo >&2 "*** Your version: $rust_bindings_generator_version"
echo >&2 "*** Expected version: $rust_bindings_generator_min_version"
echo >&2 "***"
+ warning=1
fi
# Check that the `libclang` used by the Rust bindings generator is suitable.
+#
+# In order to do that, first invoke `bindgen` to get the `libclang` version
+# found by `bindgen`. This step may already fail if, for instance, `libclang`
+# is not found, thus inform the user in such a case.
+bindgen_libclang_output=$( \
+ LC_ALL=C "$BINDGEN" $(dirname $0)/rust_is_available_bindgen_libclang.h 2>&1 >/dev/null
+) || bindgen_libclang_code=$?
+if [ -n "$bindgen_libclang_code" ]; then
+ echo >&2 "***"
+ echo >&2 "*** Running '$BINDGEN' to check the libclang version (used by the Rust"
+ echo >&2 "*** bindings generator) failed with code $bindgen_libclang_code. This may be caused by"
+ echo >&2 "*** a failure to locate libclang. See output and docs below for details:"
+ echo >&2 "***"
+ echo >&2 "$bindgen_libclang_output"
+ echo >&2 "***"
+ exit 1
+fi
+
+# `bindgen` returned successfully, thus use the output to check that the version
+# of the `libclang` found by the Rust bindings generator is suitable.
+#
+# Unlike other version checks, note that this one does not necessarily appear
+# in the first line of the output, thus no `sed` address is provided.
bindgen_libclang_version=$( \
- LC_ALL=C "$BINDGEN" $(dirname $0)/rust_is_available_bindgen_libclang.h 2>&1 >/dev/null \
- | grep -F 'clang version ' \
- | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' \
- | head -n 1 \
+ echo "$bindgen_libclang_output" \
+ | sed -nE 's:.*clang version ([0-9]+\.[0-9]+\.[0-9]+).*:\1:p'
)
+if [ -z "$bindgen_libclang_version" ]; then
+ echo >&2 "***"
+ echo >&2 "*** Running '$BINDGEN' to check the libclang version (used by the Rust"
+ echo >&2 "*** bindings generator) did not return an expected output. See output"
+ echo >&2 "*** and docs below for details:"
+ echo >&2 "***"
+ echo >&2 "$bindgen_libclang_output"
+ echo >&2 "***"
+ exit 1
+fi
bindgen_libclang_min_version=$($min_tool_version llvm)
bindgen_libclang_cversion=$(get_canonical_version $bindgen_libclang_version)
bindgen_libclang_min_cversion=$(get_canonical_version $bindgen_libclang_min_version)
if [ "$bindgen_libclang_cversion" -lt "$bindgen_libclang_min_cversion" ]; then
- if [ "$1" = -v ]; then
- echo >&2 "***"
- echo >&2 "*** libclang (used by the Rust bindings generator '$BINDGEN') is too old."
- echo >&2 "*** Your version: $bindgen_libclang_version"
- echo >&2 "*** Minimum version: $bindgen_libclang_min_version"
- echo >&2 "***"
- fi
+ echo >&2 "***"
+ echo >&2 "*** libclang (used by the Rust bindings generator '$BINDGEN') is too old."
+ echo >&2 "*** Your version: $bindgen_libclang_version"
+ echo >&2 "*** Minimum version: $bindgen_libclang_min_version"
+ echo >&2 "***"
exit 1
fi
@@ -125,21 +229,20 @@ fi
#
# In the future, we might be able to perform a full version check, see
# https://github.com/rust-lang/rust-bindgen/issues/2138.
-if [ "$1" = -v ]; then
- cc_name=$($(dirname $0)/cc-version.sh "$CC" | cut -f1 -d' ')
- if [ "$cc_name" = Clang ]; then
- clang_version=$( \
- LC_ALL=C "$CC" --version 2>/dev/null \
- | sed -nE '1s:.*version ([0-9]+\.[0-9]+\.[0-9]+).*:\1:p'
- )
- if [ "$clang_version" != "$bindgen_libclang_version" ]; then
- echo >&2 "***"
- echo >&2 "*** libclang (used by the Rust bindings generator '$BINDGEN')"
- echo >&2 "*** version does not match Clang's. This may be a problem."
- echo >&2 "*** libclang version: $bindgen_libclang_version"
- echo >&2 "*** Clang version: $clang_version"
- echo >&2 "***"
- fi
+cc_name=$($(dirname $0)/cc-version.sh $CC | cut -f1 -d' ')
+if [ "$cc_name" = Clang ]; then
+ clang_version=$( \
+ LC_ALL=C $CC --version 2>/dev/null \
+ | sed -nE '1s:.*version ([0-9]+\.[0-9]+\.[0-9]+).*:\1:p'
+ )
+ if [ "$clang_version" != "$bindgen_libclang_version" ]; then
+ echo >&2 "***"
+ echo >&2 "*** libclang (used by the Rust bindings generator '$BINDGEN')"
+ echo >&2 "*** version does not match Clang's. This may be a problem."
+ echo >&2 "*** libclang version: $bindgen_libclang_version"
+ echo >&2 "*** Clang version: $clang_version"
+ echo >&2 "***"
+ warning=1
fi
fi
@@ -150,11 +253,9 @@ rustc_sysroot=$("$RUSTC" $KRUSTFLAGS --print sysroot)
rustc_src=${RUST_LIB_SRC:-"$rustc_sysroot/lib/rustlib/src/rust/library"}
rustc_src_core="$rustc_src/core/src/lib.rs"
if [ ! -e "$rustc_src_core" ]; then
- if [ "$1" = -v ]; then
- echo >&2 "***"
- echo >&2 "*** Source code for the 'core' standard library could not be found"
- echo >&2 "*** at '$rustc_src_core'."
- echo >&2 "***"
- fi
+ echo >&2 "***"
+ echo >&2 "*** Source code for the 'core' standard library could not be found"
+ echo >&2 "*** at '$rustc_src_core'."
+ echo >&2 "***"
exit 1
fi
diff --git a/scripts/rust_is_available_test.py b/scripts/rust_is_available_test.py
new file mode 100755
index 000000000000..57613fe5ed75
--- /dev/null
+++ b/scripts/rust_is_available_test.py
@@ -0,0 +1,346 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""Tests the `rust_is_available.sh` script.
+
+Some of the tests require the real programs to be available in `$PATH`
+under their canonical name (and with the expected versions).
+"""
+
+import enum
+import os
+import pathlib
+import stat
+import subprocess
+import tempfile
+import unittest
+
+class TestRustIsAvailable(unittest.TestCase):
+ @enum.unique
+ class Expected(enum.Enum):
+ SUCCESS = enum.auto()
+ SUCCESS_WITH_WARNINGS = enum.auto()
+ SUCCESS_WITH_EXTRA_OUTPUT = enum.auto()
+ FAILURE = enum.auto()
+
+ @classmethod
+ def generate_executable(cls, content):
+ path = pathlib.Path(cls.tempdir.name)
+ name = str(len(tuple(path.iterdir())))
+ path = path / name
+ with open(path, "w") as file_:
+ file_.write(content)
+ os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR)
+ return path
+
+ @classmethod
+ def generate_clang(cls, stdout):
+ return cls.generate_executable(f"""#!/usr/bin/env python3
+import sys
+if "-E" in " ".join(sys.argv):
+ print({repr("Clang " + " ".join(cls.llvm_default_version.split(" ")))})
+else:
+ print({repr(stdout)})
+""")
+
+ @classmethod
+ def generate_rustc(cls, stdout):
+ return cls.generate_executable(f"""#!/usr/bin/env python3
+import sys
+if "--print sysroot" in " ".join(sys.argv):
+ print({repr(cls.rust_default_sysroot)})
+else:
+ print({repr(stdout)})
+""")
+
+ @classmethod
+ def generate_bindgen(cls, version_stdout, libclang_stderr):
+ return cls.generate_executable(f"""#!/usr/bin/env python3
+import sys
+if "rust_is_available_bindgen_libclang.h" in " ".join(sys.argv):
+ print({repr(libclang_stderr)}, file=sys.stderr)
+else:
+ print({repr(version_stdout)})
+""")
+
+ @classmethod
+ def generate_bindgen_version(cls, stdout):
+ return cls.generate_bindgen(stdout, cls.bindgen_default_bindgen_libclang_stderr)
+
+ @classmethod
+ def generate_bindgen_libclang(cls, stderr):
+ return cls.generate_bindgen(cls.bindgen_default_bindgen_version_stdout, stderr)
+
+ @classmethod
+ def setUpClass(cls):
+ cls.tempdir = tempfile.TemporaryDirectory()
+
+ cls.missing = pathlib.Path(cls.tempdir.name) / "missing"
+
+ cls.nonexecutable = pathlib.Path(cls.tempdir.name) / "nonexecutable"
+ with open(cls.nonexecutable, "w") as file_:
+ file_.write("nonexecutable")
+
+ cls.unexpected_binary = "true"
+
+ cls.rustc_default_version = subprocess.check_output(("scripts/min-tool-version.sh", "rustc")).decode().strip()
+ cls.bindgen_default_version = subprocess.check_output(("scripts/min-tool-version.sh", "bindgen")).decode().strip()
+ cls.llvm_default_version = subprocess.check_output(("scripts/min-tool-version.sh", "llvm")).decode().strip()
+ cls.rust_default_sysroot = subprocess.check_output(("rustc", "--print", "sysroot")).decode().strip()
+
+ cls.bindgen_default_bindgen_version_stdout = f"bindgen {cls.bindgen_default_version}"
+ cls.bindgen_default_bindgen_libclang_stderr = f"scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {cls.llvm_default_version} [-W#pragma-messages], err: false"
+
+ cls.default_rustc = cls.generate_rustc(f"rustc {cls.rustc_default_version}")
+ cls.default_bindgen = cls.generate_bindgen(cls.bindgen_default_bindgen_version_stdout, cls.bindgen_default_bindgen_libclang_stderr)
+ cls.default_cc = cls.generate_clang(f"clang version {cls.llvm_default_version}")
+
+ def run_script(self, expected, override_env):
+ env = {
+ "RUSTC": self.default_rustc,
+ "BINDGEN": self.default_bindgen,
+ "CC": self.default_cc,
+ }
+
+ for key, value in override_env.items():
+ if value is None:
+ del env[key]
+ continue
+ env[key] = value
+
+ result = subprocess.run("scripts/rust_is_available.sh", env=env, capture_output=True)
+
+ # The script should never output anything to `stdout`.
+ self.assertEqual(result.stdout, b"")
+
+ if expected == self.Expected.SUCCESS:
+ # When expecting a success, the script should return 0
+ # and it should not output anything to `stderr`.
+ self.assertEqual(result.returncode, 0)
+ self.assertEqual(result.stderr, b"")
+ elif expected == self.Expected.SUCCESS_WITH_EXTRA_OUTPUT:
+ # When expecting a success with extra output (that is not warnings,
+ # which is the common case), the script should return 0 and it
+ # should output at least something to `stderr` (the output should
+ # be checked further by the test).
+ self.assertEqual(result.returncode, 0)
+ self.assertNotEqual(result.stderr, b"")
+ elif expected == self.Expected.SUCCESS_WITH_WARNINGS:
+ # When expecting a success with warnings, the script should return 0
+ # and it should output at least the instructions to `stderr`.
+ self.assertEqual(result.returncode, 0)
+ self.assertIn(b"Please see Documentation/rust/quick-start.rst for details", result.stderr)
+ else:
+ # When expecting a failure, the script should return non-0
+ # and it should output at least the instructions to `stderr`.
+ self.assertNotEqual(result.returncode, 0)
+ self.assertIn(b"Please see Documentation/rust/quick-start.rst for details", result.stderr)
+
+ # The output will generally be UTF-8 (i.e. unless the user has
+ # put strange values in the environment).
+ result.stderr = result.stderr.decode()
+
+ return result
+
+ def test_rustc_unset(self):
+ result = self.run_script(self.Expected.FAILURE, { "RUSTC": None })
+ self.assertIn("Environment variable 'RUSTC' is not set.", result.stderr)
+ self.assertIn("This script is intended to be called from Kbuild.", result.stderr)
+
+ def test_bindgen_unset(self):
+ result = self.run_script(self.Expected.FAILURE, { "BINDGEN": None })
+ self.assertIn("Environment variable 'BINDGEN' is not set.", result.stderr)
+ self.assertIn("This script is intended to be called from Kbuild.", result.stderr)
+
+ def test_cc_unset(self):
+ result = self.run_script(self.Expected.FAILURE, { "CC": None })
+ self.assertIn("Environment variable 'CC' is not set.", result.stderr)
+ self.assertIn("This script is intended to be called from Kbuild.", result.stderr)
+
+ def test_rustc_missing(self):
+ result = self.run_script(self.Expected.FAILURE, { "RUSTC": self.missing })
+ self.assertIn(f"Rust compiler '{self.missing}' could not be found.", result.stderr)
+
+ def test_bindgen_missing(self):
+ result = self.run_script(self.Expected.FAILURE, { "BINDGEN": self.missing })
+ self.assertIn(f"Rust bindings generator '{self.missing}' could not be found.", result.stderr)
+
+ def test_rustc_nonexecutable(self):
+ result = self.run_script(self.Expected.FAILURE, { "RUSTC": self.nonexecutable })
+ self.assertIn(f"Running '{self.nonexecutable}' to check the Rust compiler version failed with", result.stderr)
+
+ def test_rustc_unexpected_binary(self):
+ result = self.run_script(self.Expected.FAILURE, { "RUSTC": self.unexpected_binary })
+ self.assertIn(f"Running '{self.unexpected_binary}' to check the Rust compiler version did not return", result.stderr)
+
+ def test_rustc_unexpected_name(self):
+ rustc = self.generate_rustc(f"unexpected {self.rustc_default_version} (a8314ef7d 2022-06-27)")
+ result = self.run_script(self.Expected.FAILURE, { "RUSTC": rustc })
+ self.assertIn(f"Running '{rustc}' to check the Rust compiler version did not return", result.stderr)
+
+ def test_rustc_unexpected_version(self):
+ rustc = self.generate_rustc("rustc unexpected (a8314ef7d 2022-06-27)")
+ result = self.run_script(self.Expected.FAILURE, { "RUSTC": rustc })
+ self.assertIn(f"Running '{rustc}' to check the Rust compiler version did not return", result.stderr)
+
+ def test_rustc_no_minor(self):
+ rustc = self.generate_rustc(f"rustc {'.'.join(self.rustc_default_version.split('.')[:2])} (a8314ef7d 2022-06-27)")
+ result = self.run_script(self.Expected.FAILURE, { "RUSTC": rustc })
+ self.assertIn(f"Running '{rustc}' to check the Rust compiler version did not return", result.stderr)
+
+ def test_rustc_old_version(self):
+ rustc = self.generate_rustc("rustc 1.60.0 (a8314ef7d 2022-06-27)")
+ result = self.run_script(self.Expected.FAILURE, { "RUSTC": rustc })
+ self.assertIn(f"Rust compiler '{rustc}' is too old.", result.stderr)
+
+ def test_rustc_new_version(self):
+ rustc = self.generate_rustc("rustc 1.999.0 (a8314ef7d 2099-06-27)")
+ result = self.run_script(self.Expected.SUCCESS_WITH_WARNINGS, { "RUSTC": rustc })
+ self.assertIn(f"Rust compiler '{rustc}' is too new. This may or may not work.", result.stderr)
+
+ def test_bindgen_nonexecutable(self):
+ result = self.run_script(self.Expected.FAILURE, { "BINDGEN": self.nonexecutable })
+ self.assertIn(f"Running '{self.nonexecutable}' to check the Rust bindings generator version failed with", result.stderr)
+
+ def test_bindgen_unexpected_binary(self):
+ result = self.run_script(self.Expected.FAILURE, { "BINDGEN": self.unexpected_binary })
+ self.assertIn(f"Running '{self.unexpected_binary}' to check the bindings generator version did not return", result.stderr)
+
+ def test_bindgen_unexpected_name(self):
+ bindgen = self.generate_bindgen_version(f"unexpected {self.bindgen_default_version}")
+ result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen })
+ self.assertIn(f"Running '{bindgen}' to check the bindings generator version did not return", result.stderr)
+
+ def test_bindgen_unexpected_version(self):
+ bindgen = self.generate_bindgen_version("bindgen unexpected")
+ result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen })
+ self.assertIn(f"Running '{bindgen}' to check the bindings generator version did not return", result.stderr)
+
+ def test_bindgen_no_minor(self):
+ bindgen = self.generate_bindgen_version(f"bindgen {'.'.join(self.bindgen_default_version.split('.')[:2])}")
+ result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen })
+ self.assertIn(f"Running '{bindgen}' to check the bindings generator version did not return", result.stderr)
+
+ def test_bindgen_old_version(self):
+ bindgen = self.generate_bindgen_version("bindgen 0.50.0")
+ result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen })
+ self.assertIn(f"Rust bindings generator '{bindgen}' is too old.", result.stderr)
+
+ def test_bindgen_new_version(self):
+ bindgen = self.generate_bindgen_version("bindgen 0.999.0")
+ result = self.run_script(self.Expected.SUCCESS_WITH_WARNINGS, { "BINDGEN": bindgen })
+ self.assertIn(f"Rust bindings generator '{bindgen}' is too new. This may or may not work.", result.stderr)
+
+ def test_bindgen_libclang_failure(self):
+ for env in (
+ { "LLVM_CONFIG_PATH": self.missing },
+ { "LIBCLANG_PATH": self.missing },
+ { "CLANG_PATH": self.missing },
+ ):
+ with self.subTest(env=env):
+ result = self.run_script(self.Expected.FAILURE, env | { "PATH": os.environ["PATH"], "BINDGEN": "bindgen" })
+ self.assertIn("Running 'bindgen' to check the libclang version (used by the Rust", result.stderr)
+ self.assertIn("bindings generator) failed with code ", result.stderr)
+
+ def test_bindgen_libclang_unexpected_version(self):
+ bindgen = self.generate_bindgen_libclang("scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version unexpected [-W#pragma-messages], err: false")
+ result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen })
+ self.assertIn(f"Running '{bindgen}' to check the libclang version (used by the Rust", result.stderr)
+ self.assertIn("bindings generator) did not return an expected output. See output", result.stderr)
+
+ def test_bindgen_libclang_old_version(self):
+ bindgen = self.generate_bindgen_libclang("scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version 10.0.0 [-W#pragma-messages], err: false")
+ result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen })
+ self.assertIn(f"libclang (used by the Rust bindings generator '{bindgen}') is too old.", result.stderr)
+
+ def test_clang_matches_bindgen_libclang_different_bindgen(self):
+ bindgen = self.generate_bindgen_libclang("scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version 999.0.0 [-W#pragma-messages], err: false")
+ result = self.run_script(self.Expected.SUCCESS_WITH_WARNINGS, { "BINDGEN": bindgen })
+ self.assertIn("version does not match Clang's. This may be a problem.", result.stderr)
+
+ def test_clang_matches_bindgen_libclang_different_clang(self):
+ cc = self.generate_clang("clang version 999.0.0")
+ result = self.run_script(self.Expected.SUCCESS_WITH_WARNINGS, { "CC": cc })
+ self.assertIn("version does not match Clang's. This may be a problem.", result.stderr)
+
+ def test_rustc_src_core_krustflags(self):
+ result = self.run_script(self.Expected.FAILURE, { "PATH": os.environ["PATH"], "RUSTC": "rustc", "KRUSTFLAGS": f"--sysroot={self.missing}" })
+ self.assertIn("Source code for the 'core' standard library could not be found", result.stderr)
+
+ def test_rustc_src_core_rustlibsrc(self):
+ result = self.run_script(self.Expected.FAILURE, { "RUST_LIB_SRC": self.missing })
+ self.assertIn("Source code for the 'core' standard library could not be found", result.stderr)
+
+ def test_success_cc_unknown(self):
+ result = self.run_script(self.Expected.SUCCESS_WITH_EXTRA_OUTPUT, { "CC": self.missing })
+ self.assertIn("unknown C compiler", result.stderr)
+
+ def test_success_cc_multiple_arguments_ccache(self):
+ clang = self.generate_clang(f"""Ubuntu clang version {self.llvm_default_version}-1ubuntu1
+Target: x86_64-pc-linux-gnu
+Thread model: posix
+InstalledDir: /usr/bin
+""")
+ result = self.run_script(self.Expected.SUCCESS, { "CC": f"{clang} clang" })
+
+ def test_success_rustc_version(self):
+ for rustc_stdout in (
+ f"rustc {self.rustc_default_version} (a8314ef7d 2022-06-27)",
+ f"rustc {self.rustc_default_version}-dev (a8314ef7d 2022-06-27)",
+ f"rustc {self.rustc_default_version}-1.60.0 (a8314ef7d 2022-06-27)",
+ ):
+ with self.subTest(rustc_stdout=rustc_stdout):
+ rustc = self.generate_rustc(rustc_stdout)
+ result = self.run_script(self.Expected.SUCCESS, { "RUSTC": rustc })
+
+ def test_success_bindgen_version(self):
+ for bindgen_stdout in (
+ f"bindgen {self.bindgen_default_version}",
+ f"bindgen {self.bindgen_default_version}-dev",
+ f"bindgen {self.bindgen_default_version}-0.999.0",
+ ):
+ with self.subTest(bindgen_stdout=bindgen_stdout):
+ bindgen = self.generate_bindgen_version(bindgen_stdout)
+ result = self.run_script(self.Expected.SUCCESS, { "BINDGEN": bindgen })
+
+ def test_success_bindgen_libclang(self):
+ for stderr in (
+ f"scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} (https://github.com/llvm/llvm-project.git 4a2c05b05ed07f1f620e94f6524a8b4b2760a0b1) [-W#pragma-messages], err: false",
+ f"/home/jd/Documents/dev/kernel-module-flake/linux-6.1/outputs/dev/lib/modules/6.1.0-development/source/scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} [-W#pragma-messages], err: false",
+ f"scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} (Fedora 13.0.0-3.fc35) [-W#pragma-messages], err: false",
+ f"""
+/nix/store/dsd5gz46hdbdk2rfdimqddhq6m8m8fqs-bash-5.1-p16/bin/bash: warning: setlocale: LC_ALL: cannot change locale (c)
+scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} [-W#pragma-messages], err: false
+""",
+ f"""
+/nix/store/dsd5gz46hdbdk2rfdimqddhq6m8m8fqs-bash-5.1.0-p16/bin/bash: warning: setlocale: LC_ALL: cannot change locale (c)
+/home/jd/Documents/dev/kernel-module-flake/linux-6.1/outputs/dev/lib/modules/6.1.0-development/source/scripts/rust_is_available_bindgen_libclang.h:2:9: warning: clang version {self.llvm_default_version} (Fedora 13.0.0-3.fc35) [-W#pragma-messages], err: false
+"""
+ ):
+ with self.subTest(stderr=stderr):
+ bindgen = self.generate_bindgen_libclang(stderr)
+ result = self.run_script(self.Expected.SUCCESS, { "BINDGEN": bindgen })
+
+ def test_success_clang_version(self):
+ for clang_stdout in (
+ f"clang version {self.llvm_default_version} (https://github.com/llvm/llvm-project.git 4a2c05b05ed07f1f620e94f6524a8b4b2760a0b1)",
+ f"clang version {self.llvm_default_version}-dev",
+ f"clang version {self.llvm_default_version}-2~ubuntu20.04.1",
+ f"Ubuntu clang version {self.llvm_default_version}-2~ubuntu20.04.1",
+ ):
+ with self.subTest(clang_stdout=clang_stdout):
+ clang = self.generate_clang(clang_stdout)
+ result = self.run_script(self.Expected.SUCCESS, { "CC": clang })
+
+ def test_success_real_programs(self):
+ for cc in ["gcc", "clang"]:
+ with self.subTest(cc=cc):
+ result = self.run_script(self.Expected.SUCCESS, {
+ "PATH": os.environ["PATH"],
+ "RUSTC": "rustc",
+ "BINDGEN": "bindgen",
+ "CC": cc,
+ })
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/scripts/rustdoc_test_builder.rs b/scripts/rustdoc_test_builder.rs
new file mode 100644
index 000000000000..e5894652f12c
--- /dev/null
+++ b/scripts/rustdoc_test_builder.rs
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Test builder for `rustdoc`-generated tests.
+//!
+//! This script is a hack to extract the test from `rustdoc`'s output. Ideally, `rustdoc` would
+//! have an option to generate this information instead, e.g. as JSON output.
+//!
+//! The `rustdoc`-generated test names look like `{file}_{line}_{number}`, e.g.
+//! `...path_rust_kernel_sync_arc_rs_42_0`. `number` is the "test number", needed in cases like
+//! a macro that expands into items with doctests is invoked several times within the same line.
+//!
+//! However, since these names are used for bisection in CI, the line number makes it not stable
+//! at all. In the future, we would like `rustdoc` to give us the Rust item path associated with
+//! the test, plus a "test number" (for cases with several examples per item) and generate a name
+//! from that. For the moment, we generate ourselves a new name, `{file}_{number}` instead, in
+//! the `gen` script (done there since we need to be aware of all the tests in a given file).
+
+use std::io::Read;
+
+fn main() {
+ let mut stdin = std::io::stdin().lock();
+ let mut body = String::new();
+ stdin.read_to_string(&mut body).unwrap();
+
+ // Find the generated function name looking for the inner function inside `main()`.
+ //
+ // The line we are looking for looks like one of the following:
+ //
+ // ```
+ // fn main() { #[allow(non_snake_case)] fn _doctest_main_rust_kernel_file_rs_28_0() {
+ // fn main() { #[allow(non_snake_case)] fn _doctest_main_rust_kernel_file_rs_37_0() -> Result<(), impl core::fmt::Debug> {
+ // ```
+ //
+ // It should be unlikely that doctest code matches such lines (when code is formatted properly).
+ let rustdoc_function_name = body
+ .lines()
+ .find_map(|line| {
+ Some(
+ line.split_once("fn main() {")?
+ .1
+ .split_once("fn ")?
+ .1
+ .split_once("()")?
+ .0,
+ )
+ .filter(|x| x.chars().all(|c| c.is_alphanumeric() || c == '_'))
+ })
+ .expect("No test function found in `rustdoc`'s output.");
+
+ // Qualify `Result` to avoid the collision with our own `Result` coming from the prelude.
+ let body = body.replace(
+ &format!("{rustdoc_function_name}() -> Result<(), impl core::fmt::Debug> {{"),
+ &format!("{rustdoc_function_name}() -> core::result::Result<(), impl core::fmt::Debug> {{"),
+ );
+
+ // For tests that get generated with `Result`, like above, `rustdoc` generates an `unwrap()` on
+ // the return value to check there were no returned errors. Instead, we use our assert macro
+ // since we want to just fail the test, not panic the kernel.
+ //
+ // We save the result in a variable so that the failed assertion message looks nicer.
+ let body = body.replace(
+ &format!("}} {rustdoc_function_name}().unwrap() }}"),
+ &format!("}} let test_return_value = {rustdoc_function_name}(); assert!(test_return_value.is_ok()); }}"),
+ );
+
+ // Figure out a smaller test name based on the generated function name.
+ let name = rustdoc_function_name.split_once("_rust_kernel_").unwrap().1;
+
+ let path = format!("rust/test/doctests/kernel/{name}");
+
+ std::fs::write(path, body.as_bytes()).unwrap();
+}
diff --git a/scripts/rustdoc_test_gen.rs b/scripts/rustdoc_test_gen.rs
new file mode 100644
index 000000000000..5ebd42ae4a3f
--- /dev/null
+++ b/scripts/rustdoc_test_gen.rs
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Generates KUnit tests from saved `rustdoc`-generated tests.
+//!
+//! KUnit passes a context (`struct kunit *`) to each test, which should be forwarded to the other
+//! KUnit functions and macros.
+//!
+//! However, we want to keep this as an implementation detail because:
+//!
+//! - Test code should not care about the implementation.
+//!
+//! - Documentation looks worse if it needs to carry extra details unrelated to the piece
+//! being described.
+//!
+//! - Test code should be able to define functions and call them, without having to carry
+//! the context.
+//!
+//! - Later on, we may want to be able to test non-kernel code (e.g. `core`, `alloc` or
+//! third-party crates) which likely use the standard library `assert*!` macros.
+//!
+//! For this reason, instead of the passed context, `kunit_get_current_test()` is used instead
+//! (i.e. `current->kunit_test`).
+//!
+//! Note that this means other threads/tasks potentially spawned by a given test, if failing, will
+//! report the failure in the kernel log but will not fail the actual test. Saving the pointer in
+//! e.g. a `static` per test does not fully solve the issue either, because currently KUnit does
+//! not support assertions (only expectations) from other tasks. Thus leave that feature for
+//! the future, which simplifies the code here too. We could also simply not allow `assert`s in
+//! other tasks, but that seems overly constraining, and we do want to support them, eventually.
+
+use std::{
+ fs,
+ fs::File,
+ io::{BufWriter, Read, Write},
+ path::{Path, PathBuf},
+};
+
+/// Find the real path to the original file based on the `file` portion of the test name.
+///
+/// `rustdoc` generated `file`s look like `sync_locked_by_rs`. Underscores (except the last one)
+/// may represent an actual underscore in a directory/file, or a path separator. Thus the actual
+/// file might be `sync_locked_by.rs`, `sync/locked_by.rs`, `sync_locked/by.rs` or
+/// `sync/locked/by.rs`. This function walks the file system to determine which is the real one.
+///
+/// This does require that ambiguities do not exist, but that seems fair, especially since this is
+/// all supposed to be temporary until `rustdoc` gives us proper metadata to build this. If such
+/// ambiguities are detected, they are diagnosed and the script panics.
+fn find_real_path<'a>(srctree: &Path, valid_paths: &'a mut Vec<PathBuf>, file: &str) -> &'a str {
+ valid_paths.clear();
+
+ let potential_components: Vec<&str> = file.strip_suffix("_rs").unwrap().split('_').collect();
+
+ find_candidates(srctree, valid_paths, Path::new(""), &potential_components);
+ fn find_candidates(
+ srctree: &Path,
+ valid_paths: &mut Vec<PathBuf>,
+ prefix: &Path,
+ potential_components: &[&str],
+ ) {
+ // The base case: check whether all the potential components left, joined by underscores,
+ // is a file.
+ let joined_potential_components = potential_components.join("_") + ".rs";
+ if srctree
+ .join("rust/kernel")
+ .join(prefix)
+ .join(&joined_potential_components)
+ .is_file()
+ {
+ // Avoid `srctree` here in order to keep paths relative to it in the KTAP output.
+ valid_paths.push(
+ Path::new("rust/kernel")
+ .join(prefix)
+ .join(joined_potential_components),
+ );
+ }
+
+ // In addition, check whether each component prefix, joined by underscores, is a directory.
+ // If not, there is no need to check for combinations with that prefix.
+ for i in 1..potential_components.len() {
+ let (components_prefix, components_rest) = potential_components.split_at(i);
+ let prefix = prefix.join(components_prefix.join("_"));
+ if srctree.join("rust/kernel").join(&prefix).is_dir() {
+ find_candidates(srctree, valid_paths, &prefix, components_rest);
+ }
+ }
+ }
+
+ assert!(
+ valid_paths.len() > 0,
+ "No path candidates found. This is likely a bug in the build system, or some files went \
+ away while compiling."
+ );
+
+ if valid_paths.len() > 1 {
+ eprintln!("Several path candidates found:");
+ for path in valid_paths {
+ eprintln!(" {path:?}");
+ }
+ panic!(
+ "Several path candidates found, please resolve the ambiguity by renaming a file or \
+ folder."
+ );
+ }
+
+ valid_paths[0].to_str().unwrap()
+}
+
+fn main() {
+ let srctree = std::env::var("srctree").unwrap();
+ let srctree = Path::new(&srctree);
+
+ let mut paths = fs::read_dir("rust/test/doctests/kernel")
+ .unwrap()
+ .map(|entry| entry.unwrap().path())
+ .collect::<Vec<_>>();
+
+ // Sort paths.
+ paths.sort();
+
+ let mut rust_tests = String::new();
+ let mut c_test_declarations = String::new();
+ let mut c_test_cases = String::new();
+ let mut body = String::new();
+ let mut last_file = String::new();
+ let mut number = 0;
+ let mut valid_paths: Vec<PathBuf> = Vec::new();
+ let mut real_path: &str = "";
+ for path in paths {
+ // The `name` follows the `{file}_{line}_{number}` pattern (see description in
+ // `scripts/rustdoc_test_builder.rs`). Discard the `number`.
+ let name = path.file_name().unwrap().to_str().unwrap().to_string();
+
+ // Extract the `file` and the `line`, discarding the `number`.
+ let (file, line) = name.rsplit_once('_').unwrap().0.rsplit_once('_').unwrap();
+
+ // Generate an ID sequence ("test number") for each one in the file.
+ if file == last_file {
+ number += 1;
+ } else {
+ number = 0;
+ last_file = file.to_string();
+
+ // Figure out the real path, only once per file.
+ real_path = find_real_path(srctree, &mut valid_paths, file);
+ }
+
+ // Generate a KUnit name (i.e. test name and C symbol) for this test.
+ //
+ // We avoid the line number, like `rustdoc` does, to make things slightly more stable for
+ // bisection purposes. However, to aid developers in mapping back what test failed, we will
+ // print a diagnostics line in the KTAP report.
+ let kunit_name = format!("rust_doctest_kernel_{file}_{number}");
+
+ // Read the test's text contents to dump it below.
+ body.clear();
+ File::open(path).unwrap().read_to_string(&mut body).unwrap();
+
+ // Calculate how many lines before `main` function (including the `main` function line).
+ let body_offset = body
+ .lines()
+ .take_while(|line| !line.contains("fn main() {"))
+ .count()
+ + 1;
+
+ use std::fmt::Write;
+ write!(
+ rust_tests,
+ r#"/// Generated `{name}` KUnit test case from a Rust documentation test.
+#[no_mangle]
+pub extern "C" fn {kunit_name}(__kunit_test: *mut kernel::bindings::kunit) {{
+ /// Overrides the usual [`assert!`] macro with one that calls KUnit instead.
+ #[allow(unused)]
+ macro_rules! assert {{
+ ($cond:expr $(,)?) => {{{{
+ kernel::kunit_assert!("{kunit_name}", "{real_path}", __DOCTEST_ANCHOR - {line}, $cond);
+ }}}}
+ }}
+
+ /// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead.
+ #[allow(unused)]
+ macro_rules! assert_eq {{
+ ($left:expr, $right:expr $(,)?) => {{{{
+ kernel::kunit_assert_eq!("{kunit_name}", "{real_path}", __DOCTEST_ANCHOR - {line}, $left, $right);
+ }}}}
+ }}
+
+ // Many tests need the prelude, so provide it by default.
+ #[allow(unused)]
+ use kernel::prelude::*;
+
+ // Unconditionally print the location of the original doctest (i.e. rather than the location in
+ // the generated file) so that developers can easily map the test back to the source code.
+ //
+ // This information is also printed when assertions fail, but this helps in the successful cases
+ // when the user is running KUnit manually, or when passing `--raw_output` to `kunit.py`.
+ //
+ // This follows the syntax for declaring test metadata in the proposed KTAP v2 spec, which may
+ // be used for the proposed KUnit test attributes API. Thus hopefully this will make migration
+ // easier later on.
+ kernel::kunit::info(format_args!(" # {kunit_name}.location: {real_path}:{line}\n"));
+
+ /// The anchor where the test code body starts.
+ #[allow(unused)]
+ static __DOCTEST_ANCHOR: i32 = core::line!() as i32 + {body_offset} + 1;
+ {{
+ {body}
+ main();
+ }}
+}}
+
+"#
+ )
+ .unwrap();
+
+ write!(c_test_declarations, "void {kunit_name}(struct kunit *);\n").unwrap();
+ write!(c_test_cases, " KUNIT_CASE({kunit_name}),\n").unwrap();
+ }
+
+ let rust_tests = rust_tests.trim();
+ let c_test_declarations = c_test_declarations.trim();
+ let c_test_cases = c_test_cases.trim();
+
+ write!(
+ BufWriter::new(File::create("rust/doctests_kernel_generated.rs").unwrap()),
+ r#"//! `kernel` crate documentation tests.
+
+const __LOG_PREFIX: &[u8] = b"rust_doctests_kernel\0";
+
+{rust_tests}
+"#
+ )
+ .unwrap();
+
+ write!(
+ BufWriter::new(File::create("rust/doctests_kernel_generated_kunit.c").unwrap()),
+ r#"/*
+ * `kernel` crate documentation tests.
+ */
+
+#include <kunit/test.h>
+
+{c_test_declarations}
+
+static struct kunit_case test_cases[] = {{
+ {c_test_cases}
+ {{ }}
+}};
+
+static struct kunit_suite test_suite = {{
+ .name = "rust_doctests_kernel",
+ .test_cases = test_cases,
+}};
+
+kunit_test_suite(test_suite);
+
+MODULE_LICENSE("GPL");
+"#
+ )
+ .unwrap();
+}
diff --git a/scripts/spelling.txt b/scripts/spelling.txt
index fc7ba95e86a0..855c4863124b 100644
--- a/scripts/spelling.txt
+++ b/scripts/spelling.txt
@@ -1541,7 +1541,6 @@ temeprature||temperature
temorary||temporary
temproarily||temporarily
temperture||temperature
-thead||thread
theads||threads
therfore||therefore
thier||their