Rust: Add support for compiling Rust with Makefile generator

This commit is contained in:
Baudouin Feildel
2026-05-24 13:48:48 +02:00
parent bda0b36155
commit 8154a4bc5c
13 changed files with 136 additions and 31 deletions

View File

@@ -3,7 +3,7 @@
include(${CMAKE_ROOT}/Modules/CMakeDetermineCompiler.cmake)
if(NOT "${CMAKE_GENERATOR}" MATCHES "^Ninja")
if(NOT "${CMAKE_GENERATOR}" MATCHES "^(Ninja)|(Unix Makefiles)")
message(FATAL_ERROR "Rust language not supported by \"${CMAKE_GENERATOR}\" generator")
endif()

View File

@@ -6,6 +6,7 @@
#include <sstream>
#include <utility>
#include <cm/filesystem>
#include <cm/string_view>
#include <cmext/string_view>
@@ -653,3 +654,34 @@ bool cmCommonTargetGenerator::HaveRequiredLanguages(
};
return std::all_of(languagesNeeded.cbegin(), languagesNeeded.cend(), unary);
}
void cmCommonTargetGenerator::ComputeRustFlagsForObjects(
std::string& linkCrates, std::string& nativeObjects,
std::vector<std::string> const& objects)
{
std::stringstream rlibsArgs;
std::stringstream objectsArgs;
auto const processObject = [&](std::string const& obj) {
cm::filesystem::path const objPath(obj);
if (objPath.extension() == ".rlib") {
// Drop the "lib..." prefix and the ".rs" suffix. The prefix is required
// by Rust on the crate rlib file, but is hidden from the user when using
// the crate from Rust source code, so we drop it to be consistent with
// common usage in Rust.
std::string objStem = objPath.stem().string();
objStem = objStem.substr(3, objStem.length() - 6);
rlibsArgs << " --extern=" << objStem << "="
<< this->LocalCommonGenerator->ConvertToOutputFormat(
obj, cmOutputConverter::SHELL);
} else {
objectsArgs << " -Clink-arg="
<< this->LocalCommonGenerator->ConvertToOutputFormat(
obj, cmOutputConverter::SHELL);
}
};
for (auto const& obj : objects) {
processObject(obj);
}
linkCrates += rlibsArgs.str();
nativeObjects += objectsArgs.str();
}

View File

@@ -90,6 +90,10 @@ protected:
bool HaveRequiredLanguages(std::vector<cmSourceFile const*> const& sources,
std::set<std::string>& languagesNeeded) const;
void ComputeRustFlagsForObjects(std::string& linkCrates,
std::string& nativeObjects,
std::vector<std::string> const& objects);
private:
using ByLanguageMap = std::map<std::string, std::string>;
struct ByConfig

View File

@@ -1013,6 +1013,18 @@ void cmGeneratorTarget::GetRustMainCrateRoot(
IMPLEMENT_VISIT(SourceKindRustMainCrateRoot);
}
cmSourceFile const* cmGeneratorTarget::GetRustMainCrateRoot(
std::string const& config) const
{
std::vector<cmSourceFile const*> files;
GetRustMainCrateRoot(files, config);
if (files.empty()) {
return nullptr;
}
assert(files.size() == 1);
return files[0];
}
std::set<cmLinkItem> const& cmGeneratorTarget::GetUtilityItems() const
{
if (!this->UtilityItemsDone) {

View File

@@ -228,6 +228,8 @@ public:
void GetRustMainCrateRoot(std::vector<cmSourceFile const*>&,
std::string const& config) const;
cmSourceFile const* GetRustMainCrateRoot(std::string const& config) const;
std::set<cmLinkItem> const& GetUtilityItems() const;
void ComputeObjectMapping();

View File

@@ -598,6 +598,16 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink)
vars.Launcher = linkerLauncher.c_str();
}
std::string rustMainCrateRootPath;
std::string rustLinkCrates;
std::string rustNativeObjects;
if (CreateRustLinkArguments(linkLanguage, rustMainCrateRootPath,
rustLinkCrates, rustNativeObjects)) {
vars.RustMainCrateRoot = rustMainCrateRootPath.c_str();
vars.RustLinkCrates = rustLinkCrates.c_str();
vars.RustNativeObjects = rustNativeObjects.c_str();
}
if (this->UseLWYU) {
cmValue lwyuCheck =
this->Makefile->GetDefinition("CMAKE_LINK_WHAT_YOU_USE_CHECK");

View File

@@ -837,6 +837,16 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules(
vars.Manifests = manifests.c_str();
vars.Config = this->GetConfigName().c_str();
std::string rustMainCrateRootPath;
std::string rustLinkCrates;
std::string rustNativeObjects;
if (CreateRustLinkArguments(linkLanguage, rustMainCrateRootPath,
rustLinkCrates, rustNativeObjects)) {
vars.RustMainCrateRoot = rustMainCrateRootPath.c_str();
vars.RustLinkCrates = rustLinkCrates.c_str();
vars.RustNativeObjects = rustNativeObjects.c_str();
}
// Compute the directory portion of the install_name setting.
std::string install_name_dir;
if (this->GeneratorTarget->GetType() == cmStateEnums::SHARED_LIBRARY) {

View File

@@ -976,6 +976,7 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
vars.Flags = flags.c_str();
vars.ISPCHeader = ispcHeaderForShell.c_str();
vars.Config = this->GetConfigName().c_str();
vars.RustEmit = source.GetRustEmitProperty()->c_str();
std::string definesString = cmStrCat("$(", lang, "_DEFINES)");
@@ -2053,6 +2054,12 @@ void cmMakefileTargetGenerator::AppendObjectDepends(
// Add dependencies on the external object files.
cm::append(depends, this->ExternalObjects);
// Add dependency on the Rust main crate root file.
if (cmSourceFile const* mainCrateRoot =
this->GeneratorTarget->GetRustMainCrateRoot(this->GetConfigName())) {
depends.push_back(mainCrateRoot->GetFullPath());
}
// Add a dependency on the rule file itself.
this->LocalGenerator->AppendRuleDepend(depends,
this->BuildFileNameFull.c_str());
@@ -2326,6 +2333,33 @@ void cmMakefileTargetGenerator::CreateObjectLists(
}
}
bool cmMakefileTargetGenerator::CreateRustLinkArguments(
std::string const& linkLanguage, std::string& rustMainCrateRootPath,
std::string& rustLinkCrates, std::string& rustNativeObjects)
{
if (linkLanguage == "Rust") {
this->ComputeRustFlagsForObjects(rustLinkCrates, rustNativeObjects,
this->Objects);
this->ComputeRustFlagsForObjects(rustLinkCrates, rustNativeObjects,
this->ExternalObjects);
cmSourceFile const* mainCrateRoot =
this->GeneratorTarget->GetRustMainCrateRoot(this->GetConfigName());
if (!mainCrateRoot) {
this->Makefile->IssueMessage(MessageType::FATAL_ERROR,
"Target " +
this->GeneratorTarget->GetName() +
" has no main crate root.");
return false;
}
rustMainCrateRootPath = mainCrateRoot->GetFullPath();
rustMainCrateRootPath = this->LocalGenerator->ConvertToOutputFormat(
rustMainCrateRootPath, cmOutputConverter::SHELL);
return true;
}
return false;
}
void cmMakefileTargetGenerator::AddIncludeFlags(std::string& flags,
std::string const& lang,
std::string const& /*config*/)

View File

@@ -193,6 +193,11 @@ protected:
bool useWatcomQuote, std::string const& linkLanguage,
ResponseFlagFor responseMode = ResponseFlagFor::Link);
bool CreateRustLinkArguments(std::string const& linkLanguage,
std::string& rustMainCrateRootPath,
std::string& rustLinkCrates,
std::string& rustNativeObjects);
/** Add commands for generate def files */
void GenDefFile(std::vector<std::string>& real_link_commands);

View File

@@ -10,7 +10,6 @@
#include <unordered_set>
#include <utility>
#include <cm/filesystem>
#include <cm/memory>
#include <cm/optional>
#include <cm/vector>
@@ -1299,40 +1298,21 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
linkBuild.ExplicitDeps = this->GetObjects(config);
// First we handle Rust rlib and normal native objects.
std::stringstream rlibs;
std::stringstream objects;
for (auto const& obj : linkBuild.ExplicitDeps) {
cm::filesystem::path const objPath(obj);
if (objPath.extension() == ".rlib") {
// Drop the "lib..." prefix and the ".rs" suffix. The prefix is
// required by Rust on the crate rlib file, but is hidden from the user
// when using the crate from Rust source code, so we drop it to be
// consistent with common usage in Rust.
std::string objStem = objPath.stem().string();
objStem = objStem.substr(3, objStem.length() - 6);
rlibs << " --extern=" << objStem << "="
<< lg->ConvertToOutputFormat(obj, cmOutputConverter::SHELL);
} else {
objects << " -Clink-arg="
<< lg->ConvertToOutputFormat(obj, cmOutputConverter::SHELL);
}
}
vars["RUST_LINK_CRATES"] = rlibs.str();
vars["RUST_NATIVE_OBJECTS"] = objects.str();
this->ComputeRustFlagsForObjects(vars["RUST_LINK_CRATES"],
vars["RUST_NATIVE_OBJECTS"],
linkBuild.ExplicitDeps);
// Then, we handle the main crate root that is build as part of the link
// step.
std::vector<cmSourceFile const*> mainCrateRoot;
gt->GetRustMainCrateRoot(mainCrateRoot, config);
if (mainCrateRoot.size() != 1) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
"Target " + gt->GetName() +
" has none or more than one main crate root.");
cmSourceFile const* mainCrateRoot = gt->GetRustMainCrateRoot(config);
if (!mainCrateRoot) {
this->Makefile->IssueMessage(MessageType::FATAL_ERROR,
"Target " + gt->GetName() +
" has no main crate root.");
return;
}
std::string mainCrateRootPath =
this->GetCompiledSourceNinjaPath(mainCrateRoot[0]);
this->GetCompiledSourceNinjaPath(mainCrateRoot);
linkBuild.ExplicitDeps.emplace_back(mainCrateRootPath);
mainCrateRootPath =
lg->ConvertToOutputFormat(mainCrateRootPath, cmOutputConverter::SHELL);

View File

@@ -0,0 +1,10 @@
\[ 20%\] Building Rust object CMakeFiles[\\/]Edition.20[0-9][0-9]\.dir[\\/]libedition_20[0-9][0-9]\.rs\.rlib
\[ 20%\] Built target Edition\.20[0-9][0-9]
\[ 40%\] Building Rust object CMakeFiles[\\/]Edition.20[0-9][0-9]\.dir[\\/]libedition_20[0-9][0-9]\.rs\.rlib
\[ 40%\] Built target Edition\.20[0-9][0-9]
\[ 60%\] Building Rust object CMakeFiles[\\/]Edition.20[0-9][0-9]\.dir[\\/]libedition_20[0-9][0-9]\.rs\.rlib
\[ 60%\] Built target Edition\.20[0-9][0-9]
\[ 80%\] Building Rust object CMakeFiles[\\/]Edition.20[0-9][0-9]\.dir[\\/]libedition_20[0-9][0-9]\.rs\.rlib
\[ 80%\] Built target Edition\.20[0-9][0-9]
\[100%\] Linking Rust executable Editions
\[100%\] Built target Editions

View File

@@ -7,5 +7,11 @@ block()
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/Editions-build)
run_cmake(Editions)
set(RunCMake_TEST_NO_CLEAN 1)
run_cmake_command(Editions-build ${CMAKE_COMMAND} --build . --config Debug)
if (RunCMake_GENERATOR MATCHES "Ninja")
run_cmake_command(Editions-build-Ninja ${CMAKE_COMMAND} --build . --config Debug)
elseif(RunCMake_GENERATOR MATCHES "Makefiles")
run_cmake_command(Editions-build-UnixMakefiles ${CMAKE_COMMAND} --build . --config Debug)
else()
message(FATAL_ERROR "Unsupported ${RunCMake_GENERATOR} generator!")
endif()
endblock()