ladybird/Meta/CMake/rust_crate.cmake
sideshowbarker ad6f646d6d Meta: Rebuild Rust crates when the toolchain changes, not just sources
Problem: After a git pull of master, builds fail with an error message
about multiple/duplicate rust_eh_personality definitions/symbols.

Cause: LibWeb links two Rust staticlibs: libweb_content_blocker_rust and
libweb_rust. Both bundle Rust’s standard library (std). When we re-pin
rust-toolchain.toml, a git pull moves a rustup user’s compiler — but we
were only rebuilding the crate whose .rs also changed. So that left one
crate on the old std, and the other crate on the new std.

Fix: Make the cargo custom commands also depend on rust-toolchain.toml
and rustc — so a toolchain change re-triggers every Rust crate.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/9854
Fixes https://github.com/LadybirdBrowser/ladybird/issues/9643
2026-06-04 07:41:45 -04:00

209 lines
7.8 KiB
CMake

# import_rust_crate(MANIFEST_PATH path/to/Cargo.toml CRATE_NAME name)
#
# Builds a Rust static library crate using cargo and creates an IMPORTED target.
# MANIFEST_PATH is relative to CMAKE_CURRENT_SOURCE_DIR.
#
# When corrosion supports dependency tracking, we can use corrosion_import_crate() instead of this function. See:
# https://github.com/corrosion-rs/corrosion/issues/206
# https://github.com/corrosion-rs/corrosion/issues/624
function(import_rust_crate)
cmake_parse_arguments(PARSE_ARGV 0 ARG "" "MANIFEST_PATH;CRATE_NAME;FFI_OUTPUT_DIR;FFI_HEADER" "FEATURES")
if (NOT ARG_FFI_OUTPUT_DIR)
set(ARG_FFI_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}")
endif()
if (ARG_FFI_HEADER)
set(ffi_output "${ARG_FFI_OUTPUT_DIR}/${ARG_FFI_HEADER}")
endif()
_rust_crate_common_setup(
MANIFEST_PATH "${ARG_MANIFEST_PATH}"
CRATE_NAME ${ARG_CRATE_NAME}
FFI_OUTPUT_DIR "${ARG_FFI_OUTPUT_DIR}"
)
set(cargo_feature_flags "")
if (ARG_FEATURES)
list(JOIN ARG_FEATURES "," cargo_features)
list(APPEND cargo_feature_flags "--features=${cargo_features}")
endif()
if (WIN32)
set(output_lib "${cargo_output_dir}/${ARG_CRATE_NAME}.lib")
set(depfile "${cargo_output_dir}/${ARG_CRATE_NAME}.d")
else()
set(output_lib "${cargo_output_dir}/lib${ARG_CRATE_NAME}.a")
set(depfile "${cargo_output_dir}/lib${ARG_CRATE_NAME}.d")
endif()
add_custom_command(
OUTPUT "${output_lib}" ${ffi_output}
COMMAND
${CMAKE_COMMAND} -E env ${cargo_env}
"${RUST_CARGO}"
rustc
--lib
${cargo_feature_flags}
${cargo_common_flags}
COMMAND
${CMAKE_COMMAND}
-DCARGO_BUILD_SCRIPT_DIR=${cargo_output_dir}/build
-DCRATE_NAME=${ARG_CRATE_NAME}
-DFFI_HEADER=${ARG_FFI_HEADER}
-DFFI_OUTPUT_DIR=${ARG_FFI_OUTPUT_DIR}
-P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/sync_rust_ffi_header.cmake"
DEPENDS "${manifest_path}"
"${workspace_dir}/Cargo.lock" "${workspace_dir}/Cargo.toml"
"${RUST_RUSTC}" "${CMAKE_SOURCE_DIR}/rust-toolchain.toml"
DEPFILE "${depfile}"
COMMENT "Building Rust crate ${ARG_CRATE_NAME}"
USES_TERMINAL
COMMAND_EXPAND_LISTS
)
add_custom_target(${ARG_CRATE_NAME}-build DEPENDS "${output_lib}" ${ffi_output})
add_library(${ARG_CRATE_NAME} STATIC IMPORTED GLOBAL)
set_target_properties(${ARG_CRATE_NAME} PROPERTIES
IMPORTED_LOCATION "${output_lib}"
INTERFACE_INCLUDE_DIRECTORIES "${ARG_FFI_OUTPUT_DIR}"
)
add_dependencies(${ARG_CRATE_NAME} ${ARG_CRATE_NAME}-build)
# Rust staticlibs bundle the standard library, which on Windows depends on system libraries.
if (WIN32)
set_target_properties(${ARG_CRATE_NAME} PROPERTIES
INTERFACE_LINK_LIBRARIES "kernel32;ntdll;Ws2_32;userenv"
)
endif()
endfunction()
# build_rust_binary(MANIFEST_PATH path/to/Cargo.toml CRATE_NAME name BINARY_NAME name OUTPUT_PATH_VAR var)
#
# Builds a Rust binary crate target using cargo and exposes the copied binary path through OUTPUT_PATH_VAR.
function(build_rust_binary)
cmake_parse_arguments(PARSE_ARGV 0 ARG "" "MANIFEST_PATH;CRATE_NAME;BINARY_NAME;OUTPUT_NAME;OUTPUT_PATH_VAR;FFI_OUTPUT_DIR" "")
if (NOT ARG_OUTPUT_NAME)
set(ARG_OUTPUT_NAME "${ARG_BINARY_NAME}")
endif()
_rust_crate_common_setup(
MANIFEST_PATH "${ARG_MANIFEST_PATH}"
CRATE_NAME ${ARG_CRATE_NAME}
FFI_OUTPUT_DIR "${ARG_FFI_OUTPUT_DIR}"
)
set(cargo_binary "${cargo_output_dir}/${ARG_BINARY_NAME}${CMAKE_EXECUTABLE_SUFFIX}")
set(depfile "${cargo_output_dir}/${ARG_BINARY_NAME}.d")
set(output_binary "${CMAKE_BINARY_DIR}/bin/${ARG_OUTPUT_NAME}${CMAKE_EXECUTABLE_SUFFIX}")
add_custom_command(
OUTPUT "${output_binary}"
COMMAND
${CMAKE_COMMAND} -E env ${cargo_env}
"${RUST_CARGO}"
rustc
--bin ${ARG_BINARY_NAME}
${cargo_common_flags}
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${cargo_binary}" "${output_binary}"
DEPENDS "${manifest_path}"
"${workspace_dir}/Cargo.lock" "${workspace_dir}/Cargo.toml"
"${RUST_RUSTC}" "${CMAKE_SOURCE_DIR}/rust-toolchain.toml"
DEPFILE "${depfile}"
COMMENT "Building Rust binary ${ARG_BINARY_NAME}"
USES_TERMINAL
COMMAND_EXPAND_LISTS
)
add_custom_target(${ARG_BINARY_NAME}-build DEPENDS "${output_binary}")
if (ARG_OUTPUT_PATH_VAR)
set(${ARG_OUTPUT_PATH_VAR} "${output_binary}" PARENT_SCOPE)
endif()
endfunction()
# Shared cargo setup for import_rust_crate() and build_rust_binary().
function(_rust_crate_common_setup)
cmake_parse_arguments(PARSE_ARGV 0 ARG "" "MANIFEST_PATH;CRATE_NAME;FFI_OUTPUT_DIR" "")
set(manifest_path "${CMAKE_CURRENT_SOURCE_DIR}/${ARG_MANIFEST_PATH}")
# Find the workspace Cargo.lock to track as a dependency.
get_filename_component(workspace_dir "${manifest_path}" DIRECTORY)
while(NOT EXISTS "${workspace_dir}/Cargo.lock")
get_filename_component(workspace_dir "${workspace_dir}" DIRECTORY)
endwhile()
# Detect the Rust toolchain.
find_program(RUST_CARGO cargo REQUIRED)
find_program(RUST_RUSTC rustc REQUIRED)
if (NOT DEFINED CACHE{RUST_TARGET_TRIPLE})
execute_process(COMMAND "${RUST_RUSTC}" -vV OUTPUT_VARIABLE rustc_verbose)
string(REGEX MATCH "host: ([^\n]+)" _ "${rustc_verbose}")
string(STRIP "${CMAKE_MATCH_1}" host_triple)
set(RUST_TARGET_TRIPLE "${host_triple}" CACHE INTERNAL "Rust target triple")
endif()
# Build the uppercased and underscored variants of the target triple.
string(REPLACE "-" "_" target_underscore "${RUST_TARGET_TRIPLE}")
string(TOUPPER "${target_underscore}" target_upper)
# Determine the cargo profile and output directory name.
string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type_upper)
if (build_type_upper STREQUAL "DEBUG")
set(cargo_profile_flag "")
set(cargo_profile_dir "debug")
else()
set(cargo_profile_flag "--release")
set(cargo_profile_dir "release")
endif()
set(cargo_target_dir "${CMAKE_BINARY_DIR}/cargo/build")
set(cargo_output_dir "${cargo_target_dir}/${RUST_TARGET_TRIPLE}/${cargo_profile_dir}")
# Build environment variables for cargo.
set(cargo_env
"CC_${target_underscore}=${CMAKE_C_COMPILER}"
"CXX_${target_underscore}=${CMAKE_CXX_COMPILER}"
"CARGO_BUILD_RUSTC=${RUST_RUSTC}"
)
if (ARG_FFI_OUTPUT_DIR)
list(APPEND cargo_env "FFI_OUTPUT_DIR=${ARG_FFI_OUTPUT_DIR}")
endif()
# On Windows, rustc invokes the linker directly with MSVC-style flags, so we must not override it with a
# compiler driver like clang-cl.
if (NOT WIN32)
list(APPEND cargo_env
"CARGO_TARGET_${target_upper}_LINKER=${CMAKE_C_COMPILER}"
"AR_${target_underscore}=${CMAKE_AR}"
)
endif()
if (APPLE AND CMAKE_OSX_SYSROOT)
list(APPEND cargo_env "SDKROOT=${CMAKE_OSX_SYSROOT}")
endif()
set(cargo_common_flags
"--target=${RUST_TARGET_TRIPLE}"
--package ${ARG_CRATE_NAME}
--manifest-path "${manifest_path}"
--target-dir "${cargo_target_dir}"
${cargo_profile_flag}
--
-Cdefault-linker-libraries=yes
-D warnings
--emit=dep-info
)
# Populate the variable names used by the public helpers below.
set(RUST_CARGO "${RUST_CARGO}" PARENT_SCOPE)
set(cargo_common_flags "${cargo_common_flags}" PARENT_SCOPE)
set(cargo_env "${cargo_env}" PARENT_SCOPE)
set(cargo_output_dir "${cargo_output_dir}" PARENT_SCOPE)
set(manifest_path "${manifest_path}" PARENT_SCOPE)
set(workspace_dir "${workspace_dir}" PARENT_SCOPE)
endfunction()