Rust: Add experimental support with Ninja

The TU for Rust is a crate, not per-source.  `rustc` expects entry file
to TU as input, and cannot work with object files.  So we need to have
one-step build/link in most cases, similar to what old Swift was doing.

Issue: #25492
Signed-off-by: Ayush Singh <ayush@beagleboard.org>
This commit is contained in:
Ayush Singh
2026-02-06 11:32:56 -05:00
committed by Brad King
parent 3fe1e45584
commit 5c1b0a6b7e
34 changed files with 303 additions and 0 deletions
@@ -5,6 +5,7 @@ if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
set(CMake_TEST_ISPC "ON" CACHE STRING "")
endif()
set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database" CACHE STRING "")
set(CMake_TEST_Rust "ON" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERIFY_URL_BAD "https://badtls-expired.kitware.com" CACHE STRING "")
set(CMake_TEST_TLS_VERSION "1.3" CACHE STRING "")
@@ -2,5 +2,6 @@ if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
set(CMake_TEST_ISPC "ON" CACHE STRING "")
endif()
set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database" CACHE STRING "")
set(CMake_TEST_Rust "ON" CACHE STRING "")
include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")
+12
View File
@@ -158,3 +158,15 @@ When activated, this experimental feature provides the following:
* The experimental ``export(SBOM)`` and ``install(SBOM)`` commands are
available to generate a Software Bill of Materials or "SBOM" for the current
project. See :command:`install(SBOM)` for a complete overview of the command.
Rust Support
============
In order to activate support for Rust, set
* variable ``CMAKE_EXPERIMENTAL_RUST`` to
* value ``3cc9b32c-47d3-4056-8953-d74e69fc0d6c``.
This UUID may change in future versions of CMake. Be sure to use the value
documented here by the source tree of the version of CMake with which you are
experimenting.
+49
View File
@@ -0,0 +1,49 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file LICENSE.rst or https://cmake.org/licensing for details.
include(${CMAKE_ROOT}/Modules/CMakeDetermineCompiler.cmake)
if(NOT "${CMAKE_GENERATOR}" MATCHES "^Ninja")
message(FATAL_ERROR "Rust language not supported by \"${CMAKE_GENERATOR}\" generator")
endif()
set(CMAKE_Rust_COMPILER_INIT "rustc")
set(CMAKE_Rust_COMPILER_HINTS "$ENV{HOME}/.cargo/bin")
_cmake_find_compiler(Rust)
get_filename_component(RUSTC_REAL "${CMAKE_Rust_COMPILER}" REALPATH)
get_filename_component(RUSTC_FILENAME "${RUSTC_REAL}" NAME)
# When rustup is used for installing rust, rustc will just be a symlink to rustup. In such cases,
# we need to query rustup for underlying rustc path.
if(RUSTC_FILENAME STREQUAL "rustup")
get_filename_component(RUSTC_DIR "${CMAKE_Rust_COMPILER}" DIRECTORY)
set(RUSTUP_PATH "${RUSTC_DIR}/rustup")
# Fix RUSTUP_HOME in ctest.
if(RUSTC_FILENAME STREQUAL "rustup" AND NOT "$ENV{CTEST_REAL_HOME}" STREQUAL "" AND "$ENV{RUSTUP_HOME}" STREQUAL "")
set(ENV{RUSTUP_HOME} "$ENV{CTEST_REAL_HOME}/.rustup")
endif()
execute_process(
COMMAND ${RUSTUP_PATH} which rustc
OUTPUT_VARIABLE REAL_RUSTC
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if("${REAL_RUSTC}" STREQUAL "")
message(FATAL_ERROR "Failed to find path to real rustc")
endif()
set_property(CACHE CMAKE_Rust_COMPILER PROPERTY VALUE "${REAL_RUSTC}")
endif()
if(CMAKE_Rust_COMPILER)
set(CMAKE_Rust_COMPILER_WORKS TRUE)
endif()
configure_file(
"${CMAKE_ROOT}/Modules/CMakeRustCompiler.cmake.in"
"${CMAKE_PLATFORM_INFO_DIR}/CMakeRustCompiler.cmake"
@ONLY)
+5
View File
@@ -0,0 +1,5 @@
set(CMAKE_Rust_COMPILER "@CMAKE_Rust_COMPILER@")
set(CMAKE_Rust_COMPILER_ENV_VAR "RUSTC")
set(CMAKE_Rust_SOURCE_FILE_EXTENSIONS rs)
set(CMAKE_Rust_COMPILER_LOADED 1)
set(CMAKE_Rust_COMPILER_WORKS @CMAKE_Rust_COMPILER_WORKS@)
+41
View File
@@ -0,0 +1,41 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file LICENSE.rst or https://cmake.org/licensing for details.
include(CMakeLanguageInformation)
if(UNIX)
set(CMAKE_Rust_OUTPUT_EXTENSION .o)
else()
set(CMAKE_Rust_OUTPUT_EXTENSION .obj)
endif()
set(CMAKE_Rust_LIBRARY_PATH_FLAG "-L ")
set(CMAKE_Rust_LINK_LIBRARY_FILE_FLAG "-C link-arg=")
set(CMAKE_EXECUTABLE_RUNTIME_Rust_FLAG "-C link-arg=-Wl,-rpath,")
set(CMAKE_EXECUTABLE_RUNTIME_Rust_FLAG_SEP ",")
set(CMAKE_Rust_FLAGS_DEBUG_INIT "-C opt-level=0 -g")
set(CMAKE_Rust_FLAGS_RELEASE_INIT "-O")
set(CMAKE_Rust_FLAGS_RELWITHDEBINFO_INIT "-O -g")
set(CMAKE_Rust_FLAGS_MINSIZEREL_INIT "-C opt-level=z")
cmake_initialize_per_config_variable(CMAKE_Rust_FLAGS "Flags used by the Rust compiler")
if(NOT CMAKE_Rust_CREATE_STATIC_LIBRARY)
set(CMAKE_Rust_CREATE_STATIC_LIBRARY "${CMAKE_Rust_COMPILER} <LANGUAGE_COMPILE_FLAGS> --crate-type=staticlib <RUST_SOURCES> -o <TARGET> -C link-args=\"<RUST_OBJECT_DEPS>\"")
endif()
if(NOT CMAKE_Rust_CREATE_SHARED_LIBRARY)
set(CMAKE_Rust_CREATE_SHARED_LIBRARY "${CMAKE_Rust_COMPILER} <LANGUAGE_COMPILE_FLAGS> --crate-type=cdylib <RUST_SOURCES> -o <TARGET> <LINK_FLAGS> <LINK_LIBRARIES> -C link-args=\"<RUST_OBJECT_DEPS>\"")
endif()
# Deadcode warnings are not useful when generating object files.
if(NOT CMAKE_Rust_COMPILE_OBJECT)
set(CMAKE_Rust_COMPILE_OBJECT "${CMAKE_Rust_COMPILER} <FLAGS> -A dead_code --crate-type=lib --emit=obj=<OBJECT>,dep-info=<DEP_FILE> <SOURCE>")
endif()
if(NOT CMAKE_Rust_LINK_EXECUTABLE)
set(CMAKE_Rust_LINK_EXECUTABLE "${CMAKE_Rust_COMPILER} <FLAGS> --crate-type=bin <RUST_SOURCES> -o <TARGET> <LINK_FLAGS> <LINK_LIBRARIES> -C link-args=\"<RUST_OBJECT_DEPS>\"")
endif()
set(CMAKE_Rust_INFORMATION_LOADED 1)
+9
View File
@@ -0,0 +1,9 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file LICENSE.rst or https://cmake.org/licensing for details.
configure_file(
"${CMAKE_ROOT}/Modules/CMakeRustCompiler.cmake.in"
"${CMAKE_PLATFORM_INFO_DIR}/CMakeRustCompiler.cmake"
@ONLY)
include(${CMAKE_PLATFORM_INFO_DIR}/CMakeRustCompiler.cmake)
+8
View File
@@ -80,6 +80,14 @@ cmExperimental::FeatureData const LookupTable[] = {
"experimentation and feedback to CMake developers.",
{},
cmExperimental::TryCompileCondition::Never },
// Rust support
{ "Rust",
"3cc9b32c-47d3-4056-8953-d74e69fc0d6c",
"CMAKE_EXPERIMENTAL_RUST",
"CMake's support for the Rust programming language is experimental. "
"It is meant only for experimentation and feedback to CMake developers.",
{},
cmExperimental::TryCompileCondition::Never },
};
static_assert(sizeof(LookupTable) / sizeof(LookupTable[0]) ==
static_cast<size_t>(cmExperimental::Feature::Sentinel),
+1
View File
@@ -24,6 +24,7 @@ public:
MappedPackageInfo,
ExportBuildDatabase,
GenerateSbom,
Rust,
Sentinel,
};
+10
View File
@@ -733,6 +733,16 @@ void cmGlobalGenerator::EnableLanguage(
for (std::string const& lang : languages) {
needSetLanguageEnabledMaps[lang] = false;
if (lang == "Rust" &&
!cmExperimental::HasSupportEnabled(*this->Makefiles[0].get(),
cmExperimental::Feature::Rust)) {
mf->IssueMessage(MessageType::FATAL_ERROR,
"Experimental Rust support is not enabled.");
cmSystemTools::SetFatalErrorOccurred();
return;
}
if (lang == "NONE") {
this->SetLanguageEnabled("NONE", mf);
continue;
+39
View File
@@ -478,6 +478,11 @@ void cmNinjaNormalTargetGenerator::WriteLinkRule(
vars.Includes = "$INCLUDES";
}
if (this->TargetLinkLanguage(config) == "Rust") {
vars.RustSources = "$RUST_SOURCES";
vars.RustObjectDeps = "$RUST_OBJECT_DEPS";
}
std::string responseFlag;
std::string cmakeVarLang =
@@ -1276,6 +1281,40 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
gt->IsExecutableWithExports()) {
linkBuild.Outputs.push_back(vars["SWIFT_MODULE"]);
}
} else if (this->TargetLinkLanguage(config) == "Rust") {
// Use one-step build/link for Rust.
// Compute specific libraries to link with.
std::vector<cmSourceFile const*> sources;
gt->GetObjectSources(sources, config);
cmLocalGenerator const* lg = this->GetLocalGenerator();
std::string entry_obj;
for (auto const& source : sources) {
if (source->GetLanguage() == "Rust") {
if (vars.count("RUST_SOURCES") == 0) {
std::string const sourcePath =
this->GetCompiledSourceNinjaPath(source);
vars["RUST_SOURCES"] =
lg->ConvertToOutputFormat(sourcePath, cmOutputConverter::SHELL);
entry_obj = this->GetObjectFilePath(source, config);
} else {
assert(false && "Rust crate can only have 1 entry");
}
}
}
linkBuild.ExplicitDeps = this->GetObjects(config);
std::stringstream obj_deps;
// Do not try linking to object file created from the crate entry.
for (auto const& obj : linkBuild.ExplicitDeps) {
if (obj != entry_obj) {
obj_deps << " "
<< lg->ConvertToOutputFormat(obj, cmOutputConverter::SHELL);
}
}
vars["RUST_OBJECT_DEPS"] = obj_deps.str();
} else {
linkBuild.ExplicitDeps = this->GetObjects(config);
}
+10
View File
@@ -143,6 +143,16 @@ std::string cmRulePlaceholderExpander::ExpandVariable(
return this->ReplaceValues->SwiftSources;
}
}
if (this->ReplaceValues->RustSources) {
if (variable == "RUST_SOURCES") {
return this->ReplaceValues->RustSources;
}
}
if (this->ReplaceValues->RustObjectDeps) {
if (variable == "RUST_OBJECT_DEPS") {
return this->ReplaceValues->RustObjectDeps;
}
}
if (this->ReplaceValues->TargetPDB) {
if (variable == "TARGET_PDB") {
return this->ReplaceValues->TargetPDB;
+2
View File
@@ -69,6 +69,8 @@ public:
char const* SwiftModuleName = nullptr;
char const* SwiftOutputFileMapOption = nullptr;
char const* SwiftSources = nullptr;
char const* RustSources = nullptr;
char const* RustObjectDeps = nullptr;
char const* ISPCHeader = nullptr;
char const* CudaCompileMode = nullptr;
char const* Fatbinary = nullptr;
+4
View File
@@ -461,6 +461,10 @@ if(BUILD_TESTING)
ADD_TEST_MACRO(SwiftMixLib Swifty)
endif()
endif()
if(CMake_TEST_Rust)
ADD_TEST_MACRO(RustOnly RustOnly)
ADD_TEST_MACRO(RustMix RustMix)
endif()
if(CMAKE_Fortran_COMPILER)
ADD_TEST_MACRO(FortranOnly FortranOnly)
set_property(TEST FortranOnly APPEND PROPERTY LABELS "Fortran")
+3
View File
@@ -600,6 +600,9 @@ if(UNIX AND CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG)
-DCMAKE_EXECUTABLE_FORMAT=${CMAKE_EXECUTABLE_FORMAT}
)
endif()
if(CMake_TEST_Rust)
add_RunCMake_test(Rust)
endif()
add_RunCMake_test(ScriptMode)
add_RunCMake_test(StandardLinkDirectories)
add_RunCMake_test(Swift -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
+3
View File
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 4.2)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)
+6
View File
@@ -0,0 +1,6 @@
^CMake Warning \(dev\) at Enable\.cmake:[0-9]+ \(enable_language\):
CMake's support for the Rust programming language is experimental\. It is
meant only for experimentation and feedback to CMake developers\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
This warning is for project developers\. Use -Wno-dev to suppress it\.$
+1
View File
@@ -0,0 +1 @@
-- CMAKE_Rust_COMPILER='[^']*/rustc'
+3
View File
@@ -0,0 +1,3 @@
set(CMAKE_EXPERIMENTAL_RUST "3cc9b32c-47d3-4056-8953-d74e69fc0d6c")
enable_language(Rust)
message(STATUS "CMAKE_Rust_COMPILER='${CMAKE_Rust_COMPILER}'")
@@ -0,0 +1 @@
1
@@ -0,0 +1,4 @@
^CMake Error at EnableExperimental\.cmake:[0-9]+ \(enable_language\):
Experimental Rust support is not enabled\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
@@ -0,0 +1 @@
enable_language(Rust)
+4
View File
@@ -0,0 +1,4 @@
include(RunCMake)
run_cmake(Enable)
run_cmake(EnableExperimental)
+14
View File
@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 4.2)
set(CMAKE_EXPERIMENTAL_RUST "3cc9b32c-47d3-4056-8953-d74e69fc0d6c")
project(RustMix LANGUAGES C Rust)
add_library(liba STATIC liba.rs)
add_library(libb SHARED libb.rs)
add_library(libc OBJECT libc.rs)
add_executable(RustMix main.c)
target_link_libraries(RustMix liba)
target_link_libraries(RustMix libb)
target_link_libraries(RustMix libc)
+4
View File
@@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn liba_greet() {
println!("Hello from Rust liba");
}
+4
View File
@@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn libb_greet() {
println!("Hello from Rust libb");
}
+4
View File
@@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn libc_greet() {
println!("Hello from Rust libc");
}
+15
View File
@@ -0,0 +1,15 @@
#include <stdio.h>
extern void liba_greet();
extern void libb_greet();
extern void libc_greet();
int main()
{
printf("Hello from C main\n");
liba_greet();
libb_greet();
libc_greet();
return 0;
}
+14
View File
@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 4.2)
set(CMAKE_EXPERIMENTAL_RUST "3cc9b32c-47d3-4056-8953-d74e69fc0d6c")
project(RustOnly LANGUAGES Rust)
add_library(liba STATIC liba.rs)
add_library(libb SHARED libb.rs)
add_library(libc OBJECT libc.rs)
add_executable(RustOnly main.rs)
target_link_libraries(RustOnly liba)
target_link_libraries(RustOnly libb)
target_link_libraries(RustOnly libc)
+4
View File
@@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn liba_greet() {
println!("Hello from liba");
}
+4
View File
@@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn libb_greet() {
println!("Hello from libb");
}
+4
View File
@@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn libc_greet() {
println!("Hello from libc");
}
+15
View File
@@ -0,0 +1,15 @@
mod moda;
extern "C" {
fn liba_greet();
fn libb_greet();
fn libc_greet();
}
fn main() {
println!("Hello from main.rs");
moda::moda_greet();
unsafe { liba_greet() };
unsafe { libb_greet() };
unsafe { libc_greet() };
}
+3
View File
@@ -0,0 +1,3 @@
pub fn moda_greet() {
println!("Hello from moda");
}