Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 219 additions & 0 deletions extensions/kernel-rust.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Enable Rust support for Linux kernel compilation.
#
# Installs Rust toolchain via rustup into ${SRC}/cache/tools/rustup/ and
# configures the build environment so that CONFIG_RUST appears in kernel
# menuconfig and gets enabled automatically.
#
# The toolchain is cached by a hash of (RUST_VERSION, BINDGEN_VERSION, arch,
# RUST_EXTRA_COMPONENTS, RUST_EXTRA_CARGO_CRATES). Changing any of these
# triggers a full reinstall on the next build.
#
# Other extensions can request additional rustup components or cargo crates:
# RUST_EXTRA_COMPONENTS+=("clippy" "llvm-tools")
# RUST_EXTRA_CARGO_CRATES+=("mdbook" "cargo-deb@2.11.0")
#
# Usage: ./compile.sh kernel-config BOARD=... BRANCH=... ENABLE_EXTENSIONS="kernel-rust"
#
# References:
# https://docs.kernel.org/rust/quick-start.html
# https://docs.kernel.org/rust/general-information.html
# https://rust-for-linux.com/rust-version-policy
# https://rust-lang.github.io/rustup/installation/index.html

# Rust toolchain version installed via rustup.
# Kernel >= 6.12 requires rustc >= 1.78. See rust-version-policy above.
RUST_VERSION="${RUST_VERSION:-1.85.0}"

# bindgen-cli version installed via cargo.
# APT bindgen 0.66.1 panics on kernel >= 6.19 headers (FromBytesWithNulError
# in codegen/mod.rs). Fixed in >= 0.69.
BINDGEN_VERSION="${BINDGEN_VERSION:-0.71.1}"

# Enable Rust sample kernel modules for toolchain smoke testing.
# Set to "yes" to build rust_minimal, rust_print, rust_driver_faux as modules.
# Can also be set via command line: RUST_KERNEL_SAMPLES=yes
RUST_KERNEL_SAMPLES="${RUST_KERNEL_SAMPLES:-no}"

# Extra rustup components to install (e.g. clippy, llvm-tools).
# Other extensions can append: RUST_EXTRA_COMPONENTS+=("clippy")
declare -g -a RUST_EXTRA_COMPONENTS=()

# Extra cargo crates to install. Supports "name" or "name@version" syntax.
# Other extensions can append: RUST_EXTRA_CARGO_CRATES+=("mdbook" "cargo-deb@2.11.0")
declare -g -a RUST_EXTRA_CARGO_CRATES=()

# Resolved tool paths, set by host_dependencies_ready, used by custom_kernel_make_params.
declare -g RUST_TOOL_RUSTC=""
declare -g RUST_TOOL_RUSTFMT=""
declare -g RUST_TOOL_BINDGEN=""
declare -g RUST_TOOL_SYSROOT=""

function add_host_dependencies__add_rust_compiler() {
display_alert "Adding Rust kernel build dependencies" "${EXTENSION}" "info"
# bindgen needs libclang for dlopen; available on all target distros.
EXTRA_BUILD_DEPS+=" libclang-dev "
}

# Download rustup-init binary for the current architecture.
# Follows the project pattern: curl → .tmp → mv → chmod.
_download_rustup_init() {
local target_dir="$1"
local target_triple
case "${BASH_VERSINFO[5]}" in
*aarch64*) target_triple="aarch64-unknown-linux-gnu" ;;
*x86_64*) target_triple="x86_64-unknown-linux-gnu" ;;
*riscv64*) target_triple="riscv64gc-unknown-linux-gnu" ;;
*) exit_with_error "Unsupported architecture for rustup" "${BASH_VERSINFO[5]}" ;;
esac

local url="https://static.rust-lang.org/rustup/dist/${target_triple}/rustup-init"
local dest="${target_dir}/rustup-init"

display_alert "Downloading rustup-init" "${target_triple}" "info"
curl --proto '=https' --tlsv1.2 -sSf -o "${dest}.tmp" "${url}"
mv "${dest}.tmp" "${dest}"
chmod +x "${dest}"
}

# Install or reuse cached Rust toolchain in ${SRC}/cache/tools/rustup/.
_prepare_rust_toolchain() {
local rust_cache_dir="${SRC}/cache/tools/rustup"
mkdir -p "${rust_cache_dir}"

local rustup_home="${rust_cache_dir}/rustup-home"
local cargo_home="${rust_cache_dir}/cargo-home"

# Content-addressable cache: hash of version config + architecture + extras
local cache_key="${RUST_VERSION}|${BINDGEN_VERSION}|${BASH_VERSINFO[5]}"
cache_key+="|${RUST_EXTRA_COMPONENTS[*]}|${RUST_EXTRA_CARGO_CRATES[*]}"
local cache_hash
cache_hash="$(echo -n "${cache_key}" | sha256sum | cut -c1-16)"
local marker="${rust_cache_dir}/.marker-${cache_hash}"

if [[ -f "${marker}" ]]; then
display_alert "Rust toolchain cache hit" "${cache_hash}" "cachehit"
return 0
fi

# Remove stale markers from previous versions
rm -f "${rust_cache_dir}"/.marker-*

display_alert "Installing Rust toolchain" "rustc ${RUST_VERSION}, bindgen ${BINDGEN_VERSION}" "info"

# Download rustup-init
do_with_retries 3 _download_rustup_init "${rust_cache_dir}"

# Install minimal toolchain; SKIP_PATH_CHECK suppresses warnings about
# system rustc in /usr/bin (e.g. from mtkflash in Docker images).
RUSTUP_HOME="${rustup_home}" CARGO_HOME="${cargo_home}" \
RUSTUP_INIT_SKIP_PATH_CHECK=yes \
"${rust_cache_dir}/rustup-init" -y \
--profile minimal \
--default-toolchain "${RUST_VERSION}" \
--no-modify-path

# Components: rustfmt (not in minimal profile) + rust-src (kernel needs it) + extras
local -a components=(rustfmt rust-src "${RUST_EXTRA_COMPONENTS[@]}")
display_alert "Installing rustup components" "${components[*]}" "info"
RUSTUP_HOME="${rustup_home}" CARGO_HOME="${cargo_home}" \
"${cargo_home}/bin/rustup" component add "${components[@]}"

# Cargo crates: bindgen-cli (kernel needs it) + extras
# Supports "name" or "name@version" syntax.
local -a crates=("bindgen-cli@${BINDGEN_VERSION}" "${RUST_EXTRA_CARGO_CRATES[@]}")
local crate
for crate in "${crates[@]}"; do
display_alert "Installing cargo crate" "${crate}" "info"
RUSTUP_HOME="${rustup_home}" CARGO_HOME="${cargo_home}" \
"${cargo_home}/bin/cargo" install --locked "${crate}"
done

# Mark cache as valid only after everything succeeds
touch "${marker}"
display_alert "Rust toolchain installed" "${cache_hash}" "info"
}

# Resolve absolute paths to Rust tool binaries.
# Uses direct paths into the toolchain (not rustup proxies), so that
# env -i in run_kernel_make_internal() does not need RUSTUP_HOME set.
_resolve_rust_tool_paths() {
local rustup_home="${SRC}/cache/tools/rustup/rustup-home"
local cargo_home="${SRC}/cache/tools/rustup/cargo-home"

RUST_TOOL_SYSROOT="$(RUSTUP_HOME="${rustup_home}" CARGO_HOME="${cargo_home}" \
"${cargo_home}/bin/rustc" --print sysroot)"

# Direct binaries inside the toolchain, bypassing rustup proxy
RUST_TOOL_RUSTC="${RUST_TOOL_SYSROOT}/bin/rustc"
RUST_TOOL_RUSTFMT="${RUST_TOOL_SYSROOT}/bin/rustfmt"
RUST_TOOL_BINDGEN="${cargo_home}/bin/bindgen"
}

function host_dependencies_ready__add_rust_compiler() {
_prepare_rust_toolchain
_resolve_rust_tool_paths

# Verify all tools are executable
local tool_name tool_path
for tool_name in RUST_TOOL_RUSTC RUST_TOOL_RUSTFMT RUST_TOOL_BINDGEN; do
tool_path="${!tool_name}"
[[ -x "${tool_path}" ]] || exit_with_error "Required Rust tool '${tool_name}' not found at ${tool_path}" "${EXTENSION}"
done

display_alert "Rust toolchain ready" \
"rustc $(${RUST_TOOL_RUSTC} --version | awk '{print $2}'), bindgen $(${RUST_TOOL_BINDGEN} --version 2>&1 | awk '{print $2}')" "info"
}

function artifact_kernel_version_parts__add_rust_version() {
# Include Rust toolchain version in artifact hash so that changing
# RUST_VERSION or BINDGEN_VERSION triggers a kernel rebuild.
local cache_key="${RUST_VERSION}|${BINDGEN_VERSION}"
local short
short="$(echo -n "${cache_key}" | sha256sum | cut -c1-4)"

artifact_version_parts["_R"]="rust${short}"

# Add to order array if not already present
local found=0 entry
for entry in "${artifact_version_part_order[@]}"; do
[[ "${entry}" == *"-_R" ]] && found=1 && break
done
if [[ "${found}" -eq 0 ]]; then
artifact_version_part_order+=("0086-_R")
fi
}

function custom_kernel_config__add_rust_compiler() {
# https://docs.kernel.org/rust/quick-start.html
opts_y+=("RUST")

# Build sample Rust modules for toolchain smoke testing
if [[ "${RUST_KERNEL_SAMPLES}" == "yes" ]]; then
display_alert "Enabling Rust sample modules" "${EXTENSION}" "info"
opts_y+=("SAMPLES") # Parent menu for all kernel samples
opts_y+=("SAMPLES_RUST")
opts_m+=("SAMPLE_RUST_MINIMAL")
opts_m+=("SAMPLE_RUST_PRINT")
opts_m+=("SAMPLE_RUST_DRIVER_FAUX")
fi
}

function custom_kernel_make_params__add_rust_compiler() {
# run_kernel_make_internal uses "env -i" which clears all environment
# variables, so we pass Rust paths explicitly via make parameters.
# Using direct toolchain binaries (not rustup proxies) avoids needing
# RUSTUP_HOME in the env -i context.

common_make_params_quoted+=("RUSTC=${RUST_TOOL_RUSTC}")
common_make_params_quoted+=("RUSTFMT=${RUST_TOOL_RUSTFMT}")
common_make_params_quoted+=("BINDGEN=${RUST_TOOL_BINDGEN}")

# Rust standard library source path for kernel build
local rust_lib_src="${RUST_TOOL_SYSROOT}/lib/rustlib/src/rust/library"
if [[ -d "${rust_lib_src}" ]]; then
display_alert "Rust library source" "${rust_lib_src}" "info"
common_make_envs+=("RUST_LIB_SRC='${rust_lib_src}'")
else
display_alert "Rust library source not found" "CONFIG_RUST will not appear in menuconfig" "wrn"
fi
}