Rust: Switch to building .rs files to .rlib instead of native object files

Each Rust source file given to a target is considered a separate crate
root. There's a concept of a "main crate root", which is the `.rs` file
that is given to the final linker phase in CMake. Other Rust source file
in a target are built as rlib separately.

The project can still build `.rs` files into object files by setting the
Rust_EMIT property on the desired source file.
This commit is contained in:
Baudouin Feildel 2026-02-12 22:00:02 +01:00 committed by Brad King
parent 942ddcdd36
commit 881faf67e5
50 changed files with 508 additions and 95 deletions

View File

@ -608,6 +608,7 @@ Properties on Source Files
/prop_sf/OBJECT_DEPENDS /prop_sf/OBJECT_DEPENDS
/prop_sf/OBJECT_NAME /prop_sf/OBJECT_NAME
/prop_sf/OBJECT_OUTPUTS /prop_sf/OBJECT_OUTPUTS
/prop_sf/Rust_EMIT
/prop_sf/SKIP_AUTOGEN /prop_sf/SKIP_AUTOGEN
/prop_sf/SKIP_AUTOMOC /prop_sf/SKIP_AUTOMOC
/prop_sf/SKIP_AUTORCC /prop_sf/SKIP_AUTORCC

View File

@ -0,0 +1,39 @@
Rust_EMIT
---------
.. versionadded:: 4.4
.. note::
Experimental. Gated by ``CMAKE_EXPERIMENTAL_RUST``.
This property controls the type of output generated by the Rust compiler. Can
be one of several values:
``link``
This is the default if the property is not set. The crate will be compiled
into an `rlib` file, e.g.: ``libfile.rs.rlib``
.. note::
CMake will automatically prefix the generated ``rlib`` file
with ``lib`` as the Rust compiler always requires such a
prefix for external crates.
``obj``
Generate a native object file, e.g.: ``file.rs.o``
``asm``
Generate an assembly file, e.g.: ``file.rs.s``
.. note::
The ``obj`` and ``asm`` output types are known to have the following
limitations:
* When enabled, the Rust compiler disables multiple codegen units and
ThinLTO. Depending on the situation, this can affect the generated code,
optimizations. It also reduce the internal parallelism inside and the
compiler and can reduce the effectiveness of incremental rebuilds.
* The generated ``obj`` files cannot be used as external crates, so other
Rust code can only use C-style API from them.
* The generated ``obj`` files cannot be linked as-is with native linkers,
additional crates from the Rust toolchain needs to linked too. The actual
crates to link depend on the code and compiler options.

View File

@ -3,3 +3,8 @@ set(CMAKE_Rust_COMPILER_ENV_VAR "RUSTC")
set(CMAKE_Rust_SOURCE_FILE_EXTENSIONS rs) set(CMAKE_Rust_SOURCE_FILE_EXTENSIONS rs)
set(CMAKE_Rust_COMPILER_LOADED 1) set(CMAKE_Rust_COMPILER_LOADED 1)
set(CMAKE_Rust_COMPILER_WORKS @CMAKE_Rust_COMPILER_WORKS@) set(CMAKE_Rust_COMPILER_WORKS @CMAKE_Rust_COMPILER_WORKS@)
# Make sure CMake prefers to link with Rust when Rust source files are present
set(CMAKE_Rust_LINKER_PREFERENCE 60)
# Once compiled by Rust, static libraries can be linked by anyone.
set(CMAKE_Rust_LINKER_PREFERENCE_PROPAGATES 0)

View File

@ -3,11 +3,20 @@
include(CMakeLanguageInformation) include(CMakeLanguageInformation)
if(UNIX) set(CMAKE_Rust_OUTPUT_EXTENSION .rlib)
set(CMAKE_Rust_OUTPUT_EXTENSION .o)
else() # Other values are supported to generate various outputs (LLVM bitcode, or IR,
set(CMAKE_Rust_OUTPUT_EXTENSION .obj) # crate metadata, Rust MIR). However, CMake cannot do anything with those
endif() # outputs, so we list output which can be reused in later stages of the build.
# See: https://doc.rust-lang.org/rustc/command-line-arguments.html#--emit-specifies-the-types-of-output-files-to-generate
set(CMAKE_Rust_EMIT_VALUES link obj asm)
# The output extension for each supported emit value.
set(CMAKE_Rust_EMIT_link_OUTPUT_EXTENSION .rlib)
set(CMAKE_Rust_EMIT_asm_OUTPUT_EXTENSION .s)
# Might be switched to .obj on Windows when using MSVC target triple, see:
# https://github.com/rust-lang/rust/issues/37207
set(CMAKE_Rust_EMIT_obj_OUTPUT_EXTENSION .o)
set(CMAKE_Rust_LIBRARY_PATH_FLAG "-L ") set(CMAKE_Rust_LIBRARY_PATH_FLAG "-L ")
set(CMAKE_Rust_LINK_LIBRARY_FILE_FLAG "-C link-arg=") set(CMAKE_Rust_LINK_LIBRARY_FILE_FLAG "-C link-arg=")
@ -61,21 +70,20 @@ endblock()
cmake_initialize_per_config_variable(CMAKE_Rust_FLAGS "Flags used by the Rust compiler") cmake_initialize_per_config_variable(CMAKE_Rust_FLAGS "Flags used by the Rust compiler")
if(NOT CMAKE_Rust_COMPILE_OBJECT)
set(CMAKE_Rust_COMPILE_OBJECT "<CMAKE_Rust_COMPILER> --crate-type=rlib <FLAGS> --emit=<RUST_EMIT>,dep-info=<DEP_FILE> -o <OBJECT> <SOURCE>")
endif()
if(NOT CMAKE_Rust_CREATE_STATIC_LIBRARY) 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>\"") set(CMAKE_Rust_CREATE_STATIC_LIBRARY "${CMAKE_Rust_COMPILER} <LANGUAGE_COMPILE_FLAGS> --crate-type=staticlib --emit=link,dep-info=<DEP_FILE> <RUST_MAIN_CRATE_ROOT> -o <TARGET> <RUST_LINK_CRATES> <RUST_NATIVE_OBJECTS>")
endif() endif()
if(NOT CMAKE_Rust_CREATE_SHARED_LIBRARY) 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>\"") set(CMAKE_Rust_CREATE_SHARED_LIBRARY "${CMAKE_Rust_COMPILER} <LANGUAGE_COMPILE_FLAGS> --crate-type=cdylib --emit=link,dep-info=<DEP_FILE> <RUST_MAIN_CRATE_ROOT> -o <TARGET> <RUST_LINK_CRATES> <RUST_NATIVE_OBJECTS> <LINK_FLAGS> <LINK_LIBRARIES>")
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() endif()
if(NOT CMAKE_Rust_LINK_EXECUTABLE) 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>\"") set(CMAKE_Rust_LINK_EXECUTABLE "${CMAKE_Rust_COMPILER} <FLAGS> --crate-type=bin --emit=link,dep-info=<DEP_FILE> <RUST_MAIN_CRATE_ROOT> -o <TARGET> <RUST_LINK_CRATES> <RUST_NATIVE_OBJECTS> <LINK_FLAGS> <LINK_LIBRARIES>")
endif() endif()
set(CMAKE_Rust_INFORMATION_LOADED 1) set(CMAKE_Rust_INFORMATION_LOADED 1)

View File

@ -1826,6 +1826,7 @@ Json::Value Target::DumpSource(cmGeneratorTarget::SourceAndKind const& sk,
case cmGeneratorTarget::SourceKindResx: case cmGeneratorTarget::SourceKindResx:
case cmGeneratorTarget::SourceKindXaml: case cmGeneratorTarget::SourceKindXaml:
case cmGeneratorTarget::SourceKindUnityBatched: case cmGeneratorTarget::SourceKindUnityBatched:
case cmGeneratorTarget::SourceKindRustMainCrateRoot:
break; break;
} }

View File

@ -1009,6 +1009,12 @@ void cmGeneratorTarget::GetManifests(std::vector<cmSourceFile const*>& data,
IMPLEMENT_VISIT(SourceKindManifest); IMPLEMENT_VISIT(SourceKindManifest);
} }
void cmGeneratorTarget::GetRustMainCrateRoot(
std::vector<cmSourceFile const*>& data, std::string const& config) const
{
IMPLEMENT_VISIT(SourceKindRustMainCrateRoot);
}
std::set<cmLinkItem> const& cmGeneratorTarget::GetUtilityItems() const std::set<cmLinkItem> const& cmGeneratorTarget::GetUtilityItems() const
{ {
if (!this->UtilityItemsDone) { if (!this->UtilityItemsDone) {

View File

@ -145,6 +145,7 @@ public:
SourceKindCustomCommand, SourceKindCustomCommand,
SourceKindExternalObject, SourceKindExternalObject,
SourceKindCxxModuleSource, SourceKindCxxModuleSource,
SourceKindRustMainCrateRoot,
SourceKindExtra, SourceKindExtra,
SourceKindHeader, SourceKindHeader,
SourceKindIDL, SourceKindIDL,
@ -224,6 +225,9 @@ public:
void GetManifests(std::vector<cmSourceFile const*>&, void GetManifests(std::vector<cmSourceFile const*>&,
std::string const& config) const; std::string const& config) const;
void GetRustMainCrateRoot(std::vector<cmSourceFile const*>&,
std::string const& config) const;
std::set<cmLinkItem> const& GetUtilityItems() const; std::set<cmLinkItem> const& GetUtilityItems() const;
void ComputeObjectMapping(); void ComputeObjectMapping();

View File

@ -347,6 +347,12 @@ void cmGeneratorTarget::ComputeKindedSources(KindedSources& files,
cmsys::RegularExpression header_regex(CM_HEADER_REGEX); cmsys::RegularExpression header_regex(CM_HEADER_REGEX);
std::vector<cmSourceFile*> badObjLib; std::vector<cmSourceFile*> badObjLib;
cmValue const rustMainCrateRootProp =
this->GetProperty("Rust_MAIN_CRATE_ROOT");
cmSourceFile const* rustMainCrateRootSf = rustMainCrateRootProp
? this->Makefile->GetOrCreateSource(rustMainCrateRootProp)
: nullptr;
std::set<cmSourceFile*> emitted; std::set<cmSourceFile*> emitted;
for (BT<std::string> const& s : srcs) { for (BT<std::string> const& s : srcs) {
// Create each source at most once. // Create each source at most once.
@ -379,7 +385,28 @@ void cmGeneratorTarget::ComputeKindedSources(KindedSources& files,
} else if (sf->GetPropertyAsBool("EXTERNAL_OBJECT")) { } else if (sf->GetPropertyAsBool("EXTERNAL_OBJECT")) {
kind = SourceKindExternalObject; kind = SourceKindExternalObject;
} else if (!sf->GetOrDetermineLanguage().empty()) { } else if (!sf->GetOrDetermineLanguage().empty()) {
kind = SourceKindObjectSource; if (sf->GetOrDetermineLanguage() == "Rust") {
// NOLINTNEXTLINE(bugprone-branch-clone)
if (this->Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
// There is no main crate root for object libraries.
kind = SourceKindObjectSource;
} else if (!rustMainCrateRootSf) {
// We do not have a main crate root source file, we use the first
// Rust source file for it.
rustMainCrateRootSf = sf;
kind = SourceKindRustMainCrateRoot;
} else if (rustMainCrateRootSf == sf) {
// Current source file is the main crate root defined in the target
// Rust_MAIN_CRATE_ROOT property.
kind = SourceKindRustMainCrateRoot;
} else {
// Any other Rust source file is treated as a normal object, but will
// be built into a .rlib. Maybe in the future this could be changed?
kind = SourceKindObjectSource;
}
} else {
kind = SourceKindObjectSource;
}
} else if (ext == "def") { } else if (ext == "def") {
kind = SourceKindModuleDefinition; kind = SourceKindModuleDefinition;
if (this->GetType() == cmStateEnums::OBJECT_LIBRARY) { if (this->GetType() == cmStateEnums::OBJECT_LIBRARY) {

View File

@ -1086,6 +1086,13 @@ std::string cmGlobalGenerator::GetLanguageOutputExtension(
{ {
std::string const& lang = source.GetLanguage(); std::string const& lang = source.GetLanguage();
if (!lang.empty()) { if (!lang.empty()) {
if (lang == "Rust") {
// Rust source file can be compiled into different type of outputs. So
// we need to change the extension based on the Rust_EMIT property.
if (cmValue const rustEmit = source.GetRustEmitProperty()) {
return this->GetRustEmitOutputExtension(rustEmit);
}
}
return this->GetLanguageOutputExtension(lang); return this->GetLanguageOutputExtension(lang);
} }
// if no language is found then check to see if it is already an // if no language is found then check to see if it is already an
@ -1110,6 +1117,16 @@ std::string cmGlobalGenerator::GetLanguageOutputExtension(
return ""; return "";
} }
std::string cmGlobalGenerator::GetRustEmitOutputExtension(
std::string const& emitValue) const
{
auto const it = this->RustEmitToOutputExtension.find(emitValue);
if (it != this->RustEmitToOutputExtension.end()) {
return it->second;
}
return "";
}
cm::string_view cmGlobalGenerator::GetLanguageFromExtension( cm::string_view cmGlobalGenerator::GetLanguageFromExtension(
cm::string_view ext) const cm::string_view ext) const
{ {
@ -1210,6 +1227,19 @@ void cmGlobalGenerator::SetLanguageEnabledMaps(std::string const& l,
} }
} }
if (l == "Rust") {
std::string const emitValues =
mf->GetSafeDefinition("CMAKE_Rust_EMIT_VALUES");
cmList emitList{ emitValues };
for (std::string const& v : emitList) {
std::string emitOutputExtension =
cmStrCat("CMAKE_Rust_EMIT_", v, "_OUTPUT_EXTENSION");
if (cmValue outputExtension = mf->GetDefinition(emitOutputExtension)) {
this->RustEmitToOutputExtension[v] = outputExtension;
}
}
}
// The map was originally filled by SetLanguageEnabledFlag, but // The map was originally filled by SetLanguageEnabledFlag, but
// since then the compiler- and platform-specific files have been // since then the compiler- and platform-specific files have been
// loaded which might have added more entries. // loaded which might have added more entries.

View File

@ -369,6 +369,8 @@ public:
std::string GetLanguageOutputExtension(cmSourceFile const&) const; std::string GetLanguageOutputExtension(cmSourceFile const&) const;
//! What is the object file extension for a given language? //! What is the object file extension for a given language?
std::string GetLanguageOutputExtension(std::string const& lang) const; std::string GetLanguageOutputExtension(std::string const& lang) const;
//! What is the object file extension for a given --emit option in Rust?
std::string GetRustEmitOutputExtension(std::string const& emitValue) const;
//! What is the configurations directory variable called? //! What is the configurations directory variable called?
virtual char const* GetCMakeCFGIntDir() const { return "."; } virtual char const* GetCMakeCFGIntDir() const { return "."; }
@ -875,6 +877,7 @@ private:
std::set<std::string> LanguagesInProgress; std::set<std::string> LanguagesInProgress;
std::map<std::string, std::string> OutputExtensions; std::map<std::string, std::string> OutputExtensions;
std::map<std::string, std::string> LanguageToOutputExtension; std::map<std::string, std::string> LanguageToOutputExtension;
std::map<std::string, std::string> RustEmitToOutputExtension;
#if __cplusplus >= 201402L || defined(_MSVC_LANG) && _MSVC_LANG >= 201402L #if __cplusplus >= 201402L || defined(_MSVC_LANG) && _MSVC_LANG >= 201402L
std::map<std::string, std::string, std::less<void>> ExtensionToLanguage; std::map<std::string, std::string, std::less<void>> ExtensionToLanguage;
#else #else

View File

@ -4678,6 +4678,20 @@ std::string cmLocalGenerator::GetObjectFileNameWithoutTarget(
*hasSourceExtension = keptSourceExtension; *hasSourceExtension = keptSourceExtension;
} }
if (source.GetLanguage() == "Rust") {
cmValue const rustEmit = source.GetRustEmitProperty();
// Rust requires any rlib to start with lib prefix on all platforms to
// allow linking to them as crate. So we enforce having lib prefix for rust
// "object" files.
if (rustEmit == "link") {
cmCMakePath objectPath(objectName);
std::string const objectFileName =
"lib" + objectPath.GetFileName().String();
objectPath.ReplaceFileName(objectFileName);
objectName = objectPath.String();
}
}
// Convert to a safe name. // Convert to a safe name.
return this->CreateSafeUniqueObjectFileName(objectName, dir_max); return this->CreateSafeUniqueObjectFileName(objectName, dir_max);
} }

View File

@ -10,6 +10,7 @@
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
#include <cm/filesystem>
#include <cm/memory> #include <cm/memory>
#include <cm/optional> #include <cm/optional>
#include <cm/vector> #include <cm/vector>
@ -479,8 +480,9 @@ void cmNinjaNormalTargetGenerator::WriteLinkRule(
} }
if (this->TargetLinkLanguage(config) == "Rust") { if (this->TargetLinkLanguage(config) == "Rust") {
vars.RustSources = "$RUST_SOURCES"; vars.RustMainCrateRoot = "$RUST_MAIN_CRATE_ROOT";
vars.RustObjectDeps = "$RUST_OBJECT_DEPS"; vars.RustLinkCrates = "$RUST_LINK_CRATES";
vars.RustNativeObjects = "$RUST_NATIVE_OBJECTS";
} }
std::string responseFlag; std::string responseFlag;
@ -1284,37 +1286,49 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
} else if (this->TargetLinkLanguage(config) == "Rust") { } else if (this->TargetLinkLanguage(config) == "Rust") {
// Use one-step build/link for Rust. // Use one-step build/link for Rust.
// Compute specific libraries to link with. // Compute specific libraries to link with.
std::vector<cmSourceFile const*> sources;
gt->GetObjectSources(sources, config);
cmLocalGenerator const* lg = this->GetLocalGenerator(); 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); linkBuild.ExplicitDeps = this->GetObjects(config);
std::stringstream obj_deps;
// Do not try linking to object file created from the crate entry. // First we handle Rust rlib and normal native objects.
std::stringstream rlibs;
std::stringstream objects;
for (auto const& obj : linkBuild.ExplicitDeps) { for (auto const& obj : linkBuild.ExplicitDeps) {
if (obj != entry_obj) { cm::filesystem::path const objPath(obj);
obj_deps << " " if (objPath.extension() == ".rlib") {
<< lg->ConvertToOutputFormat(obj, cmOutputConverter::SHELL); // 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();
vars["RUST_OBJECT_DEPS"] = obj_deps.str(); // 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.");
return;
}
std::string mainCrateRootPath =
this->GetCompiledSourceNinjaPath(mainCrateRoot[0]);
linkBuild.ExplicitDeps.emplace_back(mainCrateRootPath);
mainCrateRootPath =
lg->ConvertToOutputFormat(mainCrateRootPath, cmOutputConverter::SHELL);
vars["RUST_MAIN_CRATE_ROOT"] = mainCrateRootPath;
} else { } else {
linkBuild.ExplicitDeps = this->GetObjects(config); linkBuild.ExplicitDeps = this->GetObjects(config);
} }

View File

@ -674,6 +674,7 @@ void cmNinjaTargetGenerator::WriteCompileRule(std::string const& lang,
vars.CudaCompileMode = "$CUDA_COMPILE_MODE"; vars.CudaCompileMode = "$CUDA_COMPILE_MODE";
vars.ISPCHeader = "$ISPC_HEADER_FILE"; vars.ISPCHeader = "$ISPC_HEADER_FILE";
vars.Config = "$CONFIG"; vars.Config = "$CONFIG";
vars.RustEmit = "$RUST_EMIT";
cmMakefile* mf = this->GetMakefile(); cmMakefile* mf = this->GetMakefile();
@ -1738,6 +1739,11 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
} }
} }
if (language == "Rust") {
cmValue const rustEmit = source->GetRustEmitProperty();
vars["RUST_EMIT"] = rustEmit;
}
if (language == "Swift") { if (language == "Swift") {
this->EmitSwiftDependencyInfo(source, config); this->EmitSwiftDependencyInfo(source, config);
} else { } else {

View File

@ -156,14 +156,24 @@ std::string cmRulePlaceholderExpander::ExpandVariable(
return this->ReplaceValues->SwiftSources; return this->ReplaceValues->SwiftSources;
} }
} }
if (this->ReplaceValues->RustSources) { if (this->ReplaceValues->RustEmit) {
if (variable == "RUST_SOURCES") { if (variable == "RUST_EMIT") {
return this->ReplaceValues->RustSources; return this->ReplaceValues->RustEmit;
} }
} }
if (this->ReplaceValues->RustObjectDeps) { if (this->ReplaceValues->RustMainCrateRoot) {
if (variable == "RUST_OBJECT_DEPS") { if (variable == "RUST_MAIN_CRATE_ROOT") {
return this->ReplaceValues->RustObjectDeps; return this->ReplaceValues->RustMainCrateRoot;
}
}
if (this->ReplaceValues->RustLinkCrates) {
if (variable == "RUST_LINK_CRATES") {
return this->ReplaceValues->RustLinkCrates;
}
}
if (this->ReplaceValues->RustNativeObjects) {
if (variable == "RUST_NATIVE_OBJECTS") {
return this->ReplaceValues->RustNativeObjects;
} }
} }
if (this->ReplaceValues->TargetPDB) { if (this->ReplaceValues->TargetPDB) {

View File

@ -79,8 +79,10 @@ public:
char const* SwiftModuleName = nullptr; char const* SwiftModuleName = nullptr;
char const* SwiftOutputFileMapOption = nullptr; char const* SwiftOutputFileMapOption = nullptr;
char const* SwiftSources = nullptr; char const* SwiftSources = nullptr;
char const* RustSources = nullptr; char const* RustEmit = nullptr;
char const* RustObjectDeps = nullptr; char const* RustMainCrateRoot = nullptr;
char const* RustLinkCrates = nullptr;
char const* RustNativeObjects = nullptr;
char const* ISPCHeader = nullptr; char const* ISPCHeader = nullptr;
char const* CudaCompileMode = nullptr; char const* CudaCompileMode = nullptr;
char const* Fatbinary = nullptr; char const* Fatbinary = nullptr;

View File

@ -503,3 +503,13 @@ void cmSourceFile::SetCustomCommand(std::unique_ptr<cmCustomCommand> cc)
{ {
this->CustomCommand = std::move(cc); this->CustomCommand = std::move(cc);
} }
cmValue cmSourceFile::GetRustEmitProperty() const
{
static std::string const s_default = "link";
cmValue const value = this->GetProperty("Rust_EMIT");
if (!value || value->empty()) {
return cmValue(s_default);
}
return value;
}

View File

@ -184,6 +184,8 @@ public:
void SetObjectLibrary(std::string const& objlib); void SetObjectLibrary(std::string const& objlib);
std::string GetObjectLibrary() const; std::string GetObjectLibrary() const;
cmValue GetRustEmitProperty() const;
private: private:
cmSourceFileLocation Location; cmSourceFileLocation Location;
cmPropertyMap Properties; cmPropertyMap Properties;

View File

@ -380,6 +380,8 @@ TargetProperty const StaticTargetProperties[] = {
{ "Swift_LANGUAGE_VERSION"_s, IC::CanCompileSources }, { "Swift_LANGUAGE_VERSION"_s, IC::CanCompileSources },
{ "Swift_MODULE_DIRECTORY"_s, IC::CanCompileSources }, { "Swift_MODULE_DIRECTORY"_s, IC::CanCompileSources },
{ "Swift_COMPILATION_MODE"_s, IC::CanCompileSources }, { "Swift_COMPILATION_MODE"_s, IC::CanCompileSources },
// ---- Rust
{ "Rust_MAIN_CRATE_ROOT"_s, IC::CanCompileSources },
// ---- moc // ---- moc
{ "AUTOMOC"_s, IC::CanCompileSources }, { "AUTOMOC"_s, IC::CanCompileSources },
{ "AUTOMOC_COMPILER_PREDEFINES"_s, IC::CanCompileSources }, { "AUTOMOC_COMPILER_PREDEFINES"_s, IC::CanCompileSources },

View File

@ -2629,6 +2629,9 @@ void cmVisualStudio10TargetGenerator::WriteAllSources(Elem& e0)
case cmGeneratorTarget::SourceKindResx: case cmGeneratorTarget::SourceKindResx:
this->ResxObjs.push_back(si.Source); this->ResxObjs.push_back(si.Source);
break; break;
case cmGeneratorTarget::SourceKindRustMainCrateRoot:
tool = "None";
break;
case cmGeneratorTarget::SourceKindXaml: case cmGeneratorTarget::SourceKindXaml:
this->XamlObjs.push_back(si.Source); this->XamlObjs.push_back(si.Source);
break; break;

View File

@ -464,6 +464,7 @@ if(BUILD_TESTING)
if(CMake_TEST_Rust) if(CMake_TEST_Rust)
ADD_TEST_MACRO(RustOnly RustOnly) ADD_TEST_MACRO(RustOnly RustOnly)
ADD_TEST_MACRO(RustMix RustMix) ADD_TEST_MACRO(RustMix RustMix)
ADD_TEST_MACRO(RustPie RustPie)
endif() endif()
if(CMAKE_Fortran_COMPILER) if(CMAKE_Fortran_COMPILER)
ADD_TEST_MACRO(FortranOnly FortranOnly) ADD_TEST_MACRO(FortranOnly FortranOnly)

View File

@ -2,13 +2,29 @@ cmake_minimum_required(VERSION 4.2)
set(CMAKE_EXPERIMENTAL_RUST "3cc9b32c-47d3-4056-8953-d74e69fc0d6c") set(CMAKE_EXPERIMENTAL_RUST "3cc9b32c-47d3-4056-8953-d74e69fc0d6c")
project(RustMix LANGUAGES C Rust) project(RustMix LANGUAGES C CXX Rust)
add_library(liba STATIC liba.rs) add_library(rs_staticlib STATIC rs_staticlib.rs)
add_library(libb SHARED libb.rs) add_library(rs_cdylib SHARED rs_cdylib.rs)
add_library(libc OBJECT libc.rs) add_library(rs_rlib OBJECT rs_rlib.rs)
add_executable(RustMix main.c) add_library(c_static STATIC c_static.c)
target_link_libraries(RustMix liba) add_library(c_shared SHARED c_shared.c)
target_link_libraries(RustMix libb) add_library(c_obj OBJECT c_obj.c)
target_link_libraries(RustMix libc)
add_library(cpp_shared SHARED cpp_shared.cpp)
# Note: trying to link a C++ object file or static library into the Rust
# executable currently fails with errors related to missing symbols from
# libstdc++, while libstdc++ is present on the linker command line.
add_executable(RustMix main.rs)
target_link_libraries(
RustMix
rs_staticlib
rs_cdylib
rs_rlib
c_static
c_shared
c_obj
cpp_shared
)

6
Tests/RustMix/c_obj.c Normal file
View File

@ -0,0 +1,6 @@
#include <stdio.h>
void c_obj_greet()
{
printf("Hello from a C object file!");
}

6
Tests/RustMix/c_shared.c Normal file
View File

@ -0,0 +1,6 @@
#include <stdio.h>
void c_shared_greet()
{
printf("Hello from a C shared library!");
}

6
Tests/RustMix/c_static.c Normal file
View File

@ -0,0 +1,6 @@
#include <stdio.h>
void c_static_greet()
{
printf("Hello from a C static library!");
}

View File

@ -0,0 +1,6 @@
#include <iostream>
extern "C" void cpp_shared_greet()
{
std::cout << "Hello from a C++ shader library" << std::endl;
}

View File

@ -1,4 +0,0 @@
#[no_mangle]
pub extern "C" fn liba_greet() {
println!("Hello from Rust liba");
}

View File

@ -1,4 +0,0 @@
#[no_mangle]
pub extern "C" fn libb_greet() {
println!("Hello from Rust libb");
}

View File

@ -1,4 +0,0 @@
#[no_mangle]
pub extern "C" fn libc_greet() {
println!("Hello from Rust libc");
}

View File

@ -1,15 +0,0 @@
#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;
}

30
Tests/RustMix/main.rs Normal file
View File

@ -0,0 +1,30 @@
extern "C" {
// Functions defined in C
fn c_obj_greet();
fn c_static_greet();
fn c_shared_greet();
// Functions defined in C++
fn cpp_shared_greet();
// Functions defined in Rust through a C-style ABI
fn rs_staticlib_greet();
fn rs_cdylib_greet();
}
// Functions defined in Rust, using native Rust ABI
extern crate rs_rlib;
use rs_rlib::rs_rlib_greet;
fn main() {
println!("Hello from main.rs");
unsafe {
c_obj_greet();
c_static_greet();
c_shared_greet();
cpp_shared_greet();
rs_staticlib_greet();
rs_cdylib_greet();
}
rs_rlib_greet();
}

View File

@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn rs_cdylib_greet() {
println!("Hello from a Rust cdylib!");
}

3
Tests/RustMix/rs_rlib.rs Normal file
View File

@ -0,0 +1,3 @@
pub fn rs_rlib_greet() {
println!("Hello from a Rust rlib!");
}

View File

@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn rs_staticlib_greet() {
println!("Hello from a Rust staticlib!");
}

View File

@ -4,11 +4,15 @@ set(CMAKE_EXPERIMENTAL_RUST "3cc9b32c-47d3-4056-8953-d74e69fc0d6c")
project(RustOnly LANGUAGES Rust) project(RustOnly LANGUAGES Rust)
add_library(liba STATIC liba.rs) add_library(a STATIC a.rs)
add_library(libb SHARED libb.rs) add_library(b SHARED b.rs)
add_library(libc OBJECT libc.rs) add_library(c OBJECT c.rs)
add_executable(RustOnly main.rs) add_executable(RustOnly d.rs main.rs e.rs)
target_link_libraries(RustOnly liba) # Ensure that Rust_MAIN_CRATE_ROOT property works.
target_link_libraries(RustOnly libb) set_target_properties(RustOnly PROPERTIES Rust_MAIN_CRATE_ROOT main.rs)
target_link_libraries(RustOnly libc) # Emit an object file instead of an rlib.
set_source_files_properties(e.rs PROPERTIES Rust_EMIT "obj")
target_link_libraries(RustOnly a)
target_link_libraries(RustOnly b)
target_link_libraries(RustOnly c)

4
Tests/RustOnly/c.rs Normal file
View File

@ -0,0 +1,4 @@
// We can expose a Rust API, as this is compiled into a rlib.
pub fn libc_greet() {
println!("Hello from libc");
}

4
Tests/RustOnly/d.rs Normal file
View File

@ -0,0 +1,4 @@
// We can expose a Rust API, as this is compiled into a rlib.
pub fn libd_greet() {
println!("Hello from libd");
}

4
Tests/RustOnly/e.rs Normal file
View File

@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn libe_greet() {
println!("Hello from libe");
}

View File

@ -1,4 +0,0 @@
#[no_mangle]
pub extern "C" fn libc_greet() {
println!("Hello from libc");
}

View File

@ -3,13 +3,19 @@ mod moda;
extern "C" { extern "C" {
fn liba_greet(); fn liba_greet();
fn libb_greet(); fn libb_greet();
fn libc_greet(); fn libe_greet();
} }
extern crate c;
extern crate d;
use c::libc_greet;
use d::libd_greet;
fn main() { fn main() {
println!("Hello from main.rs"); println!("Hello from main.rs");
moda::moda_greet(); moda::moda_greet();
unsafe { liba_greet() }; unsafe { liba_greet() };
unsafe { libb_greet() }; unsafe { libb_greet() };
unsafe { libc_greet() }; libc_greet();
unsafe { libe_greet() };
} }

View File

@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 4.2)
set(CMAKE_EXPERIMENTAL_RUST "3cc9b32c-47d3-4056-8953-d74e69fc0d6c")
function(setup PREFIX)
add_library(${PREFIX}_static STATIC ../static.rs)
add_library(${PREFIX}_shared SHARED ../shared.rs)
add_library(${PREFIX}_object OBJECT ../object.rs)
add_executable(${PREFIX}_RustPie ../main.rs)
target_link_libraries(
${PREFIX}_RustPie PRIVATE
${PREFIX}_static
${PREFIX}_shared
${PREFIX}_object
)
endfunction()
project(RustPie LANGUAGES Rust)
add_subdirectory(Default)
add_subdirectory(Disabled)
add_subdirectory(Enabled)
file(
GENERATE OUTPUT ${CMAKE_BINARY_DIR}/runner_$<CONFIG>.rs
INPUT runner.rs
)
add_executable(RustPie ${CMAKE_BINARY_DIR}/runner_$<CONFIG>.rs)
add_dependencies(RustPie default_RustPie disabled_RustPie enabled_RustPie)

View File

@ -0,0 +1,3 @@
project(DefaultRustPie LANGUAGES Rust)
setup(default)

View File

@ -0,0 +1,12 @@
project(DisabledRustPie LANGUAGES Rust)
setup(disabled)
set_property(
TARGET
disabled_static
disabled_shared
disabled_object
disabled_RustPie
PROPERTY POSITION_INDEPENDENT_CODE OFF
)

View File

@ -0,0 +1,12 @@
project(EnabledRustPie LANGUAGES Rust)
setup(enabled)
set_property(
TARGET
enabled_static
enabled_shared
enabled_object
enabled_RustPie
PROPERTY POSITION_INDEPENDENT_CODE TRUE
)

18
Tests/RustPie/main.rs Normal file
View File

@ -0,0 +1,18 @@
extern "C" {
fn static_foo();
fn shared_foo();
}
extern crate object;
use object::object_foo;
fn main() {
unsafe {
static_foo();
shared_foo();
}
object_foo();
}

3
Tests/RustPie/object.rs Normal file
View File

@ -0,0 +1,3 @@
pub fn object_foo() {
println!("object_foo");
}

66
Tests/RustPie/runner.rs Normal file
View File

@ -0,0 +1,66 @@
use std::process::Command;
#[cfg(target_os="macos")]
fn is_pie_executable(executable: &str) -> bool {
let output = Command::new("otool")
.args(["-hv", executable])
.output()
.expect("Failed to check executable");
if !output.status.success() {
panic!("otool exited with non-zero exit code");
}
output.stdout.windows(5).any(|w| {
(w[0] == b' ' || w[0] == b'\t')
&& (w[1..=3] == b"PIE")
&& (w[4] == b' ' || w[4] == b'\n')
})
}
#[cfg(all(target_family="unix", not(target_os="macos")))]
fn is_pie_executable(executable: &str) -> bool {
let output = Command::new("readelf")
.args(["-lW", executable])
.env("LANG", "C")
.env("LC_ALL", "C")
.output()
.expect("Failed to check executable");
if !output.status.success() {
panic!("readelf exited with non-zero exit code");
}
let is_pie = output.stdout.windows(20).any(|w| w == b"Elf file type is DYN");
let is_not_pie = output.stdout.windows(21).any(|w| w == b"Elf file type is EXEC");
assert!(is_pie ^ is_not_pie, "Cannot determine type of ELF file");
is_pie
}
fn main() {
let commands = [
// The actual path to the commands will be filled in by CMake.
r#"$<TARGET_FILE:default_RustPie>"#,
r#"$<TARGET_FILE:disabled_RustPie>"#,
r#"$<TARGET_FILE:enabled_RustPie>"#,
];
for command in commands {
println!("Start command: {command:?}");
let result = Command::new(command)
.spawn()
.expect("Failed to launch command")
.wait()
.expect("Failure while waiting for process to finish")
.success();
if !result {
panic!("Process exited with non-zero exit code.");
}
}
assert!(
!is_pie_executable(commands[1]),
"ERROR: disabled_RustPie must not be a PIE executable, but it is not."
);
assert!(
is_pie_executable(commands[2]),
"ERROR: enabled_RustPie must be a PIE executable, but it is not."
);
}

4
Tests/RustPie/shared.rs Normal file
View File

@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn shared_foo() {
println!("shared_foo");
}

4
Tests/RustPie/static.rs Normal file
View File

@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn static_foo() {
println!("static_foo");
}