From 8154a4bc5c7ac611101af19361ce2eb5e05b7d5d Mon Sep 17 00:00:00 2001 From: Baudouin Feildel Date: Sun, 24 May 2026 13:48:48 +0200 Subject: [PATCH] Rust: Add support for compiling Rust with Makefile generator --- Modules/CMakeDetermineRustCompiler.cmake | 2 +- Source/cmCommonTargetGenerator.cxx | 32 ++++++++++++++++ Source/cmCommonTargetGenerator.h | 4 ++ Source/cmGeneratorTarget.cxx | 12 ++++++ Source/cmGeneratorTarget.h | 2 + .../cmMakefileExecutableTargetGenerator.cxx | 10 +++++ Source/cmMakefileLibraryTargetGenerator.cxx | 10 +++++ Source/cmMakefileTargetGenerator.cxx | 34 +++++++++++++++++ Source/cmMakefileTargetGenerator.h | 5 +++ Source/cmNinjaNormalTargetGenerator.cxx | 38 +++++-------------- ...ut.txt => Editions-build-Ninja-stdout.txt} | 0 .../Editions-build-UnixMakefiles-stdout.txt | 10 +++++ Tests/RunCMake/Rust/RunCMakeTest.cmake | 8 +++- 13 files changed, 136 insertions(+), 31 deletions(-) rename Tests/RunCMake/Rust/{Editions-build-stdout.txt => Editions-build-Ninja-stdout.txt} (100%) create mode 100644 Tests/RunCMake/Rust/Editions-build-UnixMakefiles-stdout.txt diff --git a/Modules/CMakeDetermineRustCompiler.cmake b/Modules/CMakeDetermineRustCompiler.cmake index 971354fabc..4272e1bf26 100644 --- a/Modules/CMakeDetermineRustCompiler.cmake +++ b/Modules/CMakeDetermineRustCompiler.cmake @@ -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() diff --git a/Source/cmCommonTargetGenerator.cxx b/Source/cmCommonTargetGenerator.cxx index 6db67678ca..f873769cc9 100644 --- a/Source/cmCommonTargetGenerator.cxx +++ b/Source/cmCommonTargetGenerator.cxx @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -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 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(); +} diff --git a/Source/cmCommonTargetGenerator.h b/Source/cmCommonTargetGenerator.h index b97b947c47..d8fd69499d 100644 --- a/Source/cmCommonTargetGenerator.h +++ b/Source/cmCommonTargetGenerator.h @@ -90,6 +90,10 @@ protected: bool HaveRequiredLanguages(std::vector const& sources, std::set& languagesNeeded) const; + void ComputeRustFlagsForObjects(std::string& linkCrates, + std::string& nativeObjects, + std::vector const& objects); + private: using ByLanguageMap = std::map; struct ByConfig diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index a31c1b2595..32df7f59ff 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -1013,6 +1013,18 @@ void cmGeneratorTarget::GetRustMainCrateRoot( IMPLEMENT_VISIT(SourceKindRustMainCrateRoot); } +cmSourceFile const* cmGeneratorTarget::GetRustMainCrateRoot( + std::string const& config) const +{ + std::vector files; + GetRustMainCrateRoot(files, config); + if (files.empty()) { + return nullptr; + } + assert(files.size() == 1); + return files[0]; +} + std::set const& cmGeneratorTarget::GetUtilityItems() const { if (!this->UtilityItemsDone) { diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h index 23f59bc015..2889b39c46 100644 --- a/Source/cmGeneratorTarget.h +++ b/Source/cmGeneratorTarget.h @@ -228,6 +228,8 @@ public: void GetRustMainCrateRoot(std::vector&, std::string const& config) const; + cmSourceFile const* GetRustMainCrateRoot(std::string const& config) const; + std::set const& GetUtilityItems() const; void ComputeObjectMapping(); diff --git a/Source/cmMakefileExecutableTargetGenerator.cxx b/Source/cmMakefileExecutableTargetGenerator.cxx index 453fbe2847..77c040fef5 100644 --- a/Source/cmMakefileExecutableTargetGenerator.cxx +++ b/Source/cmMakefileExecutableTargetGenerator.cxx @@ -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"); diff --git a/Source/cmMakefileLibraryTargetGenerator.cxx b/Source/cmMakefileLibraryTargetGenerator.cxx index c046e11381..d04051c632 100644 --- a/Source/cmMakefileLibraryTargetGenerator.cxx +++ b/Source/cmMakefileLibraryTargetGenerator.cxx @@ -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) { diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx index 59c0519201..22600419bb 100644 --- a/Source/cmMakefileTargetGenerator.cxx +++ b/Source/cmMakefileTargetGenerator.cxx @@ -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*/) diff --git a/Source/cmMakefileTargetGenerator.h b/Source/cmMakefileTargetGenerator.h index bcb5f26d8f..ce78230344 100644 --- a/Source/cmMakefileTargetGenerator.h +++ b/Source/cmMakefileTargetGenerator.h @@ -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& real_link_commands); diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx index 7ff9bb2304..3fbc1786c3 100644 --- a/Source/cmNinjaNormalTargetGenerator.cxx +++ b/Source/cmNinjaNormalTargetGenerator.cxx @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -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 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); diff --git a/Tests/RunCMake/Rust/Editions-build-stdout.txt b/Tests/RunCMake/Rust/Editions-build-Ninja-stdout.txt similarity index 100% rename from Tests/RunCMake/Rust/Editions-build-stdout.txt rename to Tests/RunCMake/Rust/Editions-build-Ninja-stdout.txt diff --git a/Tests/RunCMake/Rust/Editions-build-UnixMakefiles-stdout.txt b/Tests/RunCMake/Rust/Editions-build-UnixMakefiles-stdout.txt new file mode 100644 index 0000000000..a946b1e8c0 --- /dev/null +++ b/Tests/RunCMake/Rust/Editions-build-UnixMakefiles-stdout.txt @@ -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 diff --git a/Tests/RunCMake/Rust/RunCMakeTest.cmake b/Tests/RunCMake/Rust/RunCMakeTest.cmake index c3d0207b04..ae6f60206c 100644 --- a/Tests/RunCMake/Rust/RunCMakeTest.cmake +++ b/Tests/RunCMake/Rust/RunCMakeTest.cmake @@ -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()