mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-04 12:16:41 +00:00
63b27f4a00
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 <me@kloenk.dev> Tested-by: Benno Lossin <benno.lossin@proton.me> Tested-by: Andreas Hindborg <a.hindborg@samsung.com> Link: https://lore.kernel.org/r/20240709160615.998336-7-ojeda@kernel.org Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
342 lines
17 KiB
Python
Executable File
342 lines
17 KiB
Python
Executable File
#!/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_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()
|