From 63b27f4a0074bc6ef987a44ee9ad8bf960b568c2 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Tue, 9 Jul 2024 18:06:01 +0200 Subject: rust: start supporting several compiler versions It is time to start supporting several Rust compiler versions and thus establish a minimum Rust version. We may still want to upgrade the minimum sometimes in the beginning since there may be important features coming into the language that improve how we write code (e.g. field projections), which may or may not make sense to support conditionally. We will start with a window of two stable releases, and widen it over time. Thus this patch does not move the current minimum (1.78.0), but instead adds support for the recently released 1.79.0. This should already be enough for kernel developers in distributions that provide recent Rust compiler versions routinely, such as Arch Linux, Debian Unstable (outside the freeze period), Fedora Linux, Gentoo Linux (especially the testing channel), Nix (unstable) and openSUSE Tumbleweed. See the documentation patch about it later in this series. In addition, Rust for Linux is now being built-tested in Rust's pre-merge CI [1]. That is, every change that is attempting to land into the Rust compiler is tested against the kernel, and it is merged only if it passes -- thanks to the Rust project for that! Thus, with the pre-merge CI in place, both projects hope to avoid unintentional changes to Rust that break the kernel. This means that, in general, apart from intentional changes on their side (that we will need to workaround conditionally on our side), the upcoming Rust compiler versions should generally work. For instance, currently, the beta (1.80.0) and nightly (1.81.0) branches work as well. Of course, the Rust for Linux CI job in the Rust toolchain may still need to be temporarily disabled for different reasons, but the intention is to help bring Rust for Linux into stable Rust. Link: https://github.com/rust-lang/rust/pull/125209 [1] Reviewed-by: Finn Behrens Tested-by: Benno Lossin Tested-by: Andreas Hindborg Link: https://lore.kernel.org/r/20240709160615.998336-7-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- scripts/rust_is_available_test.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'scripts/rust_is_available_test.py') diff --git a/scripts/rust_is_available_test.py b/scripts/rust_is_available_test.py index 57613fe5ed75..a255f79aafc2 100755 --- a/scripts/rust_is_available_test.py +++ b/scripts/rust_is_available_test.py @@ -193,11 +193,6 @@ else: 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) -- cgit From d49082faf6a001019693a837dea7b958048c731c Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Tue, 9 Jul 2024 18:06:02 +0200 Subject: rust: avoid assuming a particular `bindgen` build `bindgen`'s logic to find `libclang` (via `clang-sys`) may change over time, and depends on how it was built (e.g. Linux distributions may decide to build it differently, and we are going to provide documentation on installing it via distributions later in this series). Therefore, clarify that `bindgen` may be built in several ways and simplify the documentation by only mentioning the most prominent environment variable (`LIBCLANG_PATH`) as an example on how to tweak the search of the library at runtime (i.e. when `bindgen` is built as our documentation explains). This also avoids duplicating the documentation, like `bindgen` itself does (i.e. it refers to `clang-sys`). Similarly, replace the test we had for this (which used the real program) with a mocked one, to avoid depending on the particular build as well. Tested-by: Benno Lossin Tested-by: Andreas Hindborg Link: https://lore.kernel.org/r/20240709160615.998336-8-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- Documentation/rust/quick-start.rst | 23 +++++++++-------------- scripts/rust_is_available_test.py | 25 +++++++++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) (limited to 'scripts/rust_is_available_test.py') diff --git a/Documentation/rust/quick-start.rst b/Documentation/rust/quick-start.rst index 89bbfde8c96c..5ea8c8914942 100644 --- a/Documentation/rust/quick-start.rst +++ b/Documentation/rust/quick-start.rst @@ -113,20 +113,15 @@ Install it via (note that this will download and build the tool from source):: cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen-cli -``bindgen`` needs to find a suitable ``libclang`` in order to work. If it is -not found (or a different ``libclang`` than the one found should be used), -the process can be tweaked using the environment variables understood by -``clang-sys`` (the Rust bindings crate that ``bindgen`` uses to access -``libclang``): - -* ``LLVM_CONFIG_PATH`` can be pointed to an ``llvm-config`` executable. - -* Or ``LIBCLANG_PATH`` can be pointed to a ``libclang`` shared library - or to the directory containing it. - -* Or ``CLANG_PATH`` can be pointed to a ``clang`` executable. - -For details, please see ``clang-sys``'s documentation at: +``bindgen`` uses the ``clang-sys`` crate to find a suitable ``libclang`` (which +may be linked statically, dynamically or loaded at runtime). By default, the +``cargo`` command above will produce a ``bindgen`` binary that will load +``libclang`` at runtime. If it is not found (or a different ``libclang`` than +the one found should be used), the process can be tweaked, e.g. by using the +``LIBCLANG_PATH`` environment variable. For details, please see ``clang-sys``'s +documentation at: + + https://github.com/KyleMayes/clang-sys#linking https://github.com/KyleMayes/clang-sys#environment-variables diff --git a/scripts/rust_is_available_test.py b/scripts/rust_is_available_test.py index a255f79aafc2..0481aab862ec 100755 --- a/scripts/rust_is_available_test.py +++ b/scripts/rust_is_available_test.py @@ -55,10 +55,15 @@ else: @classmethod def generate_bindgen(cls, version_stdout, libclang_stderr): + if libclang_stderr is None: + libclang_case = f"raise SystemExit({cls.bindgen_default_bindgen_libclang_failure_exit_code})" + else: + libclang_case = f"print({repr(libclang_stderr)}, file=sys.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) + {libclang_case} else: print({repr(version_stdout)}) """) @@ -67,6 +72,10 @@ else: def generate_bindgen_version(cls, stdout): return cls.generate_bindgen(stdout, cls.bindgen_default_bindgen_libclang_stderr) + @classmethod + def generate_bindgen_libclang_failure(cls): + return cls.generate_bindgen(cls.bindgen_default_bindgen_version_stdout, None) + @classmethod def generate_bindgen_libclang(cls, stderr): return cls.generate_bindgen(cls.bindgen_default_bindgen_version_stdout, stderr) @@ -89,6 +98,7 @@ else: 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_failure_exit_code = 42 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}") @@ -227,15 +237,10 @@ else: 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) + bindgen = self.generate_bindgen_libclang_failure() + 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(f"bindings generator) failed with code {self.bindgen_default_bindgen_libclang_failure_exit_code}. This may be caused by", 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") -- cgit From c844fa64a2d46982fe75e834f4a46c46d2b3b2e5 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Tue, 9 Jul 2024 18:06:04 +0200 Subject: rust: start supporting several `bindgen` versions With both the workaround for `bindgen` 0.69.0 and the warning about 0.66.0 and 0.66.1 in place, start supporting several `bindgen` versions, like it was done for the Rust compiler in a previous patch. All other versions, including the latest 0.69.4, build without errors. The `bindgen` project, like Rust, has also agreed to have the kernel in their CI [1] -- thanks! This should help both projects: `bindgen` will be able to detect early issues like those mentioned above, and the kernel will be very likely build with new releases (at least for the basic configuration being tested). Link: https://github.com/rust-lang/rust-bindgen/pull/2851 [1] Tested-by: Benno Lossin Tested-by: Andreas Hindborg Link: https://lore.kernel.org/r/20240709160615.998336-10-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- Documentation/rust/quick-start.rst | 7 ++++--- scripts/rust_is_available.sh | 8 -------- scripts/rust_is_available_test.py | 5 ----- 3 files changed, 4 insertions(+), 16 deletions(-) (limited to 'scripts/rust_is_available_test.py') diff --git a/Documentation/rust/quick-start.rst b/Documentation/rust/quick-start.rst index 5ea8c8914942..66cefbab8f9a 100644 --- a/Documentation/rust/quick-start.rst +++ b/Documentation/rust/quick-start.rst @@ -107,11 +107,12 @@ bindgen ******* The bindings to the C side of the kernel are generated at build time using -the ``bindgen`` tool. A particular version is required. +the ``bindgen`` tool. -Install it via (note that this will download and build the tool from source):: +Install it, for instance, via (note that this will download and build the tool +from source):: - cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen-cli + cargo install --locked bindgen-cli ``bindgen`` uses the ``clang-sys`` crate to find a suitable ``libclang`` (which may be linked statically, dynamically or loaded at runtime). By default, the diff --git a/scripts/rust_is_available.sh b/scripts/rust_is_available.sh index 1881e8f2a2b9..4531f9dd19d3 100755 --- a/scripts/rust_is_available.sh +++ b/scripts/rust_is_available.sh @@ -161,14 +161,6 @@ if [ "$rust_bindings_generator_cversion" -lt "$rust_bindings_generator_min_cvers echo >&2 "***" exit 1 fi -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. # diff --git a/scripts/rust_is_available_test.py b/scripts/rust_is_available_test.py index 0481aab862ec..d6d54b7ea42a 100755 --- a/scripts/rust_is_available_test.py +++ b/scripts/rust_is_available_test.py @@ -231,11 +231,6 @@ else: 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): bindgen = self.generate_bindgen_libclang_failure() result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen }) -- cgit From 981ad93c89a3c600dee9795d3ead105acc805483 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Tue, 9 Jul 2024 18:06:05 +0200 Subject: rust: warn about `bindgen` versions 0.66.0 and 0.66.1 `bindgen` versions 0.66.0 and 0.66.1 panic due to C string literals with NUL characters [1]: panicked at .cargo/registry/src/index.crates.io-6f17d22bba15001f/bindgen-0.66.0/codegen/mod.rs:717:71: called `Result::unwrap()` on an `Err` value: FromBytesWithNulError { kind: InteriorNul(4) } Thus, in preparation for supporting several `bindgen` versions, add a version check to warn the user about it. Since some distributions may have patched it (e.g. Debian did [2]), check if that seems to be the case (after the version check matches), in order to avoid printing a warning in that case. We could make it an error, but 1) it is going to fail anyway later in the build, 2) we would disable `RUST`, which is also painful, 3) someone could have patched it in a way that still makes our extra check fail (however unlikely), 4) the interior NUL may go away in the headers (however unlikely). Thus just warn about it so that users know why it is failing. In addition, add a couple tests for the new cases. Link: https://github.com/rust-lang/rust-bindgen/pull/2567 [1] Link: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1069047 [2] Link: https://lore.kernel.org/r/20240709160615.998336-11-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- scripts/rust_is_available.sh | 13 +++++++++++++ scripts/rust_is_available_bindgen_0_66.h | 2 ++ scripts/rust_is_available_test.py | 26 +++++++++++++++++++++++--- 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 scripts/rust_is_available_bindgen_0_66.h (limited to 'scripts/rust_is_available_test.py') diff --git a/scripts/rust_is_available.sh b/scripts/rust_is_available.sh index 4531f9dd19d3..5262c56dd674 100755 --- a/scripts/rust_is_available.sh +++ b/scripts/rust_is_available.sh @@ -161,6 +161,19 @@ if [ "$rust_bindings_generator_cversion" -lt "$rust_bindings_generator_min_cvers echo >&2 "***" exit 1 fi +if [ "$rust_bindings_generator_cversion" -eq 6600 ] || + [ "$rust_bindings_generator_cversion" -eq 6601 ]; then + # Distributions may have patched the issue (e.g. Debian did). + if ! "$BINDGEN" $(dirname $0)/rust_is_available_bindgen_0_66.h >/dev/null; then + echo >&2 "***" + echo >&2 "*** Rust bindings generator '$BINDGEN' versions 0.66.0 and 0.66.1 may not" + echo >&2 "*** work due to a bug (https://github.com/rust-lang/rust-bindgen/pull/2567)," + echo >&2 "*** unless patched (like Debian's)." + echo >&2 "*** Your version: $rust_bindings_generator_version" + echo >&2 "***" + warning=1 + fi +fi # Check that the `libclang` used by the Rust bindings generator is suitable. # diff --git a/scripts/rust_is_available_bindgen_0_66.h b/scripts/rust_is_available_bindgen_0_66.h new file mode 100644 index 000000000000..c0431293421c --- /dev/null +++ b/scripts/rust_is_available_bindgen_0_66.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#define A "\0" diff --git a/scripts/rust_is_available_test.py b/scripts/rust_is_available_test.py index d6d54b7ea42a..413741037fb3 100755 --- a/scripts/rust_is_available_test.py +++ b/scripts/rust_is_available_test.py @@ -54,23 +54,30 @@ else: """) @classmethod - def generate_bindgen(cls, version_stdout, libclang_stderr): + def generate_bindgen(cls, version_stdout, libclang_stderr, version_0_66_patched=False): if libclang_stderr is None: libclang_case = f"raise SystemExit({cls.bindgen_default_bindgen_libclang_failure_exit_code})" else: libclang_case = f"print({repr(libclang_stderr)}, file=sys.stderr)" + if version_0_66_patched: + version_0_66_case = "pass" + else: + version_0_66_case = "raise SystemExit(1)" + return cls.generate_executable(f"""#!/usr/bin/env python3 import sys if "rust_is_available_bindgen_libclang.h" in " ".join(sys.argv): {libclang_case} +elif "rust_is_available_bindgen_0_66.h" in " ".join(sys.argv): + {version_0_66_case} else: print({repr(version_stdout)}) """) @classmethod - def generate_bindgen_version(cls, stdout): - return cls.generate_bindgen(stdout, cls.bindgen_default_bindgen_libclang_stderr) + def generate_bindgen_version(cls, stdout, version_0_66_patched=False): + return cls.generate_bindgen(stdout, cls.bindgen_default_bindgen_libclang_stderr, version_0_66_patched) @classmethod def generate_bindgen_libclang_failure(cls): @@ -231,6 +238,19 @@ else: result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen }) self.assertIn(f"Rust bindings generator '{bindgen}' is too old.", result.stderr) + def test_bindgen_bad_version_0_66_0_and_0_66_1(self): + for version in ("0.66.0", "0.66.1"): + with self.subTest(version=version): + bindgen = self.generate_bindgen_version(f"bindgen {version}") + result = self.run_script(self.Expected.SUCCESS_WITH_WARNINGS, { "BINDGEN": bindgen }) + self.assertIn(f"Rust bindings generator '{bindgen}' versions 0.66.0 and 0.66.1 may not", result.stderr) + + def test_bindgen_bad_version_0_66_0_and_0_66_1_patched(self): + for version in ("0.66.0", "0.66.1"): + with self.subTest(version=version): + bindgen = self.generate_bindgen_version(f"bindgen {version}", True) + result = self.run_script(self.Expected.SUCCESS, { "BINDGEN": bindgen }) + def test_bindgen_libclang_failure(self): bindgen = self.generate_bindgen_libclang_failure() result = self.run_script(self.Expected.FAILURE, { "BINDGEN": bindgen }) -- cgit