diff --git a/Help/dev/experimental.rst b/Help/dev/experimental.rst index 3d528717bb..a17bdcdf39 100644 --- a/Help/dev/experimental.rst +++ b/Help/dev/experimental.rst @@ -127,7 +127,7 @@ Rust Support In order to activate support for Rust, set * variable ``CMAKE_EXPERIMENTAL_RUST`` to -* value ``3cc9b32c-47d3-4056-8953-d74e69fc0d6c``. +* value ``6b6e613b-f6cb-402d-8aea-59034fa8c65b``. 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 diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst index e1e7fa5a58..a530637c03 100644 --- a/Help/manual/cmake-properties.7.rst +++ b/Help/manual/cmake-properties.7.rst @@ -608,6 +608,7 @@ Properties on Source Files /prop_sf/OBJECT_DEPENDS /prop_sf/OBJECT_NAME /prop_sf/OBJECT_OUTPUTS + /prop_sf/Rust_EMIT /prop_sf/SKIP_AUTOGEN /prop_sf/SKIP_AUTOMOC /prop_sf/SKIP_AUTORCC diff --git a/Help/prop_sf/Rust_EMIT.rst b/Help/prop_sf/Rust_EMIT.rst new file mode 100644 index 0000000000..548a485e2c --- /dev/null +++ b/Help/prop_sf/Rust_EMIT.rst @@ -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. diff --git a/Modules/CMakeRustCompiler.cmake.in b/Modules/CMakeRustCompiler.cmake.in index deb19ba825..5da83e4ca0 100644 --- a/Modules/CMakeRustCompiler.cmake.in +++ b/Modules/CMakeRustCompiler.cmake.in @@ -3,3 +3,8 @@ 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@) + +# 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) diff --git a/Modules/CMakeRustInformation.cmake b/Modules/CMakeRustInformation.cmake index 2224d012e9..e7d41fd7a1 100644 --- a/Modules/CMakeRustInformation.cmake +++ b/Modules/CMakeRustInformation.cmake @@ -3,11 +3,20 @@ include(CMakeLanguageInformation) -if(UNIX) - set(CMAKE_Rust_OUTPUT_EXTENSION .o) -else() - set(CMAKE_Rust_OUTPUT_EXTENSION .obj) -endif() +set(CMAKE_Rust_OUTPUT_EXTENSION .rlib) + +# Other values are supported to generate various outputs (LLVM bitcode, or IR, +# crate metadata, Rust MIR). However, CMake cannot do anything with those +# 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_LINK_LIBRARY_FILE_FLAG "-C link-arg=") @@ -19,23 +28,62 @@ 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") +block( + PROPAGATE + CMAKE_Rust_LINK_PIE_SUPPORTED + CMAKE_Rust_LINK_NO_PIE_SUPPORTED + CMAKE_Rust_COMPILE_OPTIONS_PIE + CMAKE_Rust_COMPILE_OPTIONS_PIC + CMAKE_Rust_LINK_OPTIONS_PIE + CMAKE_Rust_LINK_OPTIONS_NO_PIE + ) + execute_process( + COMMAND "${CMAKE_Rust_COMPILER}" --print relocation-models + OUTPUT_VARIABLE RUSTC_OUTPUT + ERROR_VARIABLE RUSTC_ERROR + RESULT_VARIABLE RUSTC_EXITCODE + ) + if(RUSTC_EXITCODE EQUAL "0") + string(REPLACE "\n" ";" RUSTC_OUTPUT_LINES "${RUSTC_OUTPUT}") + list(TRANSFORM RUSTC_OUTPUT_LINES STRIP) + if("pic" IN_LIST RUSTC_OUTPUT_LINES) + set(CMAKE_Rust_COMPILE_OPTIONS_PIC -C relocation-model=pic) + endif() + if("pie" IN_LIST RUSTC_OUTPUT_LINES) + set(CMAKE_Rust_LINK_PIE_SUPPORTED TRUE) + set(CMAKE_Rust_COMPILE_OPTIONS_PIE -C relocation-model=pie) + set(CMAKE_Rust_LINK_OPTIONS_PIE -C relocation-model=pie) + else() + set(CMAKE_Rust_LINK_PIE_SUPPORTED FALSE) + endif() + if("static" IN_LIST RUSTC_OUTPUT_LINES) + set(CMAKE_Rust_LINK_NO_PIE_SUPPORTED TRUE) + set(CMAKE_Rust_LINK_OPTIONS_NO_PIE -C relocation-model=static) + else() + set(CMAKE_Rust_LINK_NO_PIE_SUPPORTED FALSE) + endif() + else() + string(REPLACE "\n" "\n " RUSTC_ERROR " ${RUSTC_ERROR}") + message(FATAL_ERROR "Failed to check PIC/PIE support in rustc:\n${RUSTC_ERROR}") + endif() +endblock() + 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 " --crate-type=rlib --emit=,dep-info= -o ") +endif() + if(NOT CMAKE_Rust_CREATE_STATIC_LIBRARY) - set(CMAKE_Rust_CREATE_STATIC_LIBRARY "${CMAKE_Rust_COMPILER} --crate-type=staticlib -o -C link-args=\"\"") + set(CMAKE_Rust_CREATE_STATIC_LIBRARY "${CMAKE_Rust_COMPILER} --crate-type=staticlib --emit=link,dep-info= -o ") endif() if(NOT CMAKE_Rust_CREATE_SHARED_LIBRARY) - set(CMAKE_Rust_CREATE_SHARED_LIBRARY "${CMAKE_Rust_COMPILER} --crate-type=cdylib -o -C link-args=\"\"") -endif() - -# Deadcode warnings are not useful when generating object files. -if(NOT CMAKE_Rust_COMPILE_OBJECT) - set(CMAKE_Rust_COMPILE_OBJECT "${CMAKE_Rust_COMPILER} -A dead_code --crate-type=lib --emit=obj=,dep-info= ") + set(CMAKE_Rust_CREATE_SHARED_LIBRARY "${CMAKE_Rust_COMPILER} --crate-type=cdylib --emit=link,dep-info= -o ") endif() if(NOT CMAKE_Rust_LINK_EXECUTABLE) - set(CMAKE_Rust_LINK_EXECUTABLE "${CMAKE_Rust_COMPILER} --crate-type=bin -o -C link-args=\"\"") + set(CMAKE_Rust_LINK_EXECUTABLE "${CMAKE_Rust_COMPILER} --crate-type=bin --emit=link,dep-info= -o ") endif() set(CMAKE_Rust_INFORMATION_LOADED 1) diff --git a/Source/cmExperimental.cxx b/Source/cmExperimental.cxx index acf8e5c276..03a08eb506 100644 --- a/Source/cmExperimental.cxx +++ b/Source/cmExperimental.cxx @@ -64,7 +64,7 @@ cmExperimental::FeatureData const LookupTable[] = { cmExperimental::TryCompileCondition::Never }, // Rust support { "Rust", - "3cc9b32c-47d3-4056-8953-d74e69fc0d6c", + "6b6e613b-f6cb-402d-8aea-59034fa8c65b", "CMAKE_EXPERIMENTAL_RUST", "CMake's support for the Rust programming language is experimental. " "It is meant only for experimentation and feedback to CMake developers.", diff --git a/Source/cmFileAPICodemodel.cxx b/Source/cmFileAPICodemodel.cxx index 38ca4dd442..3d398b2dab 100644 --- a/Source/cmFileAPICodemodel.cxx +++ b/Source/cmFileAPICodemodel.cxx @@ -1826,6 +1826,7 @@ Json::Value Target::DumpSource(cmGeneratorTarget::SourceAndKind const& sk, case cmGeneratorTarget::SourceKindResx: case cmGeneratorTarget::SourceKindXaml: case cmGeneratorTarget::SourceKindUnityBatched: + case cmGeneratorTarget::SourceKindRustMainCrateRoot: break; } diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index 9ead8abe7a..811d608b6f 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -1008,6 +1008,12 @@ void cmGeneratorTarget::GetManifests(std::vector& data, IMPLEMENT_VISIT(SourceKindManifest); } +void cmGeneratorTarget::GetRustMainCrateRoot( + std::vector& data, std::string const& config) const +{ + IMPLEMENT_VISIT(SourceKindRustMainCrateRoot); +} + std::set const& cmGeneratorTarget::GetUtilityItems() const { if (!this->UtilityItemsDone) { diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h index 497cde6682..388fa32ee2 100644 --- a/Source/cmGeneratorTarget.h +++ b/Source/cmGeneratorTarget.h @@ -145,6 +145,7 @@ public: SourceKindCustomCommand, SourceKindExternalObject, SourceKindCxxModuleSource, + SourceKindRustMainCrateRoot, SourceKindExtra, SourceKindHeader, SourceKindIDL, @@ -224,6 +225,9 @@ public: void GetManifests(std::vector&, std::string const& config) const; + void GetRustMainCrateRoot(std::vector&, + std::string const& config) const; + std::set const& GetUtilityItems() const; void ComputeObjectMapping(); diff --git a/Source/cmGeneratorTarget_Sources.cxx b/Source/cmGeneratorTarget_Sources.cxx index 9f31bd4231..3a7b53287b 100644 --- a/Source/cmGeneratorTarget_Sources.cxx +++ b/Source/cmGeneratorTarget_Sources.cxx @@ -347,6 +347,12 @@ void cmGeneratorTarget::ComputeKindedSources(KindedSources& files, cmsys::RegularExpression header_regex(CM_HEADER_REGEX); std::vector badObjLib; + cmValue const rustMainCrateRootProp = + this->GetProperty("Rust_MAIN_CRATE_ROOT"); + cmSourceFile const* rustMainCrateRootSf = rustMainCrateRootProp + ? this->Makefile->GetOrCreateSource(rustMainCrateRootProp) + : nullptr; + std::set emitted; for (BT const& s : srcs) { // Create each source at most once. @@ -379,7 +385,28 @@ void cmGeneratorTarget::ComputeKindedSources(KindedSources& files, } else if (sf->GetPropertyAsBool("EXTERNAL_OBJECT")) { kind = SourceKindExternalObject; } 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") { kind = SourceKindModuleDefinition; if (this->GetType() == cmStateEnums::OBJECT_LIBRARY) { diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 6821300ccb..0a36d67d90 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -1086,6 +1086,13 @@ std::string cmGlobalGenerator::GetLanguageOutputExtension( { std::string const& lang = source.GetLanguage(); 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); } // if no language is found then check to see if it is already an @@ -1110,6 +1117,16 @@ std::string cmGlobalGenerator::GetLanguageOutputExtension( 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 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 // since then the compiler- and platform-specific files have been // loaded which might have added more entries. diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index 7e6e4b435a..3c1169987b 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -369,6 +369,8 @@ public: std::string GetLanguageOutputExtension(cmSourceFile const&) const; //! What is the object file extension for a given language? 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? virtual char const* GetCMakeCFGIntDir() const { return "."; } @@ -875,6 +877,7 @@ private: std::set LanguagesInProgress; std::map OutputExtensions; std::map LanguageToOutputExtension; + std::map RustEmitToOutputExtension; #if __cplusplus >= 201402L || defined(_MSVC_LANG) && _MSVC_LANG >= 201402L std::map> ExtensionToLanguage; #else diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index 3c1982579d..55568773dd 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -3596,8 +3596,12 @@ void cmLocalGenerator::AppendPositionIndependentLinkerFlags( } char const* PICValue = target->GetLinkPIEProperty(config); - if (!PICValue) { - // POSITION_INDEPENDENT_CODE is not set + if (!PICValue && lang != "Rust") { + // POSITION_INDEPENDENT_CODE is not set, note that for Rust we do not + // return as the compiler tends to enable PIE all the time, which is the + // opposite of what C & C++ compilers do. So instead of letting the rust + // compiler decide on its own whether PIE should be enabled, we explicit + // set it. return; } @@ -4674,6 +4678,20 @@ std::string cmLocalGenerator::GetObjectFileNameWithoutTarget( *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. return this->CreateSafeUniqueObjectFileName(objectName, dir_max); } diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx index 99b0a1820c..e144e8d847 100644 --- a/Source/cmNinjaNormalTargetGenerator.cxx +++ b/Source/cmNinjaNormalTargetGenerator.cxx @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -479,8 +480,9 @@ void cmNinjaNormalTargetGenerator::WriteLinkRule( } if (this->TargetLinkLanguage(config) == "Rust") { - vars.RustSources = "$RUST_SOURCES"; - vars.RustObjectDeps = "$RUST_OBJECT_DEPS"; + vars.RustMainCrateRoot = "$RUST_MAIN_CRATE_ROOT"; + vars.RustLinkCrates = "$RUST_LINK_CRATES"; + vars.RustNativeObjects = "$RUST_NATIVE_OBJECTS"; } std::string responseFlag; @@ -1284,37 +1286,49 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement( } else if (this->TargetLinkLanguage(config) == "Rust") { // Use one-step build/link for Rust. // Compute specific libraries to link with. - std::vector 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. + // First we handle Rust rlib and normal native objects. + std::stringstream rlibs; + std::stringstream objects; for (auto const& obj : linkBuild.ExplicitDeps) { - if (obj != entry_obj) { - obj_deps << " " - << lg->ConvertToOutputFormat(obj, cmOutputConverter::SHELL); + 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(); - 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 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 { linkBuild.ExplicitDeps = this->GetObjects(config); } diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx index 7d4a27e7e3..6ed6d40c1c 100644 --- a/Source/cmNinjaTargetGenerator.cxx +++ b/Source/cmNinjaTargetGenerator.cxx @@ -674,6 +674,7 @@ void cmNinjaTargetGenerator::WriteCompileRule(std::string const& lang, vars.CudaCompileMode = "$CUDA_COMPILE_MODE"; vars.ISPCHeader = "$ISPC_HEADER_FILE"; vars.Config = "$CONFIG"; + vars.RustEmit = "$RUST_EMIT"; 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") { this->EmitSwiftDependencyInfo(source, config); } else { diff --git a/Source/cmRulePlaceholderExpander.cxx b/Source/cmRulePlaceholderExpander.cxx index 618e1b741e..7f9f0c3aac 100644 --- a/Source/cmRulePlaceholderExpander.cxx +++ b/Source/cmRulePlaceholderExpander.cxx @@ -156,14 +156,24 @@ std::string cmRulePlaceholderExpander::ExpandVariable( return this->ReplaceValues->SwiftSources; } } - if (this->ReplaceValues->RustSources) { - if (variable == "RUST_SOURCES") { - return this->ReplaceValues->RustSources; + if (this->ReplaceValues->RustEmit) { + if (variable == "RUST_EMIT") { + return this->ReplaceValues->RustEmit; } } - if (this->ReplaceValues->RustObjectDeps) { - if (variable == "RUST_OBJECT_DEPS") { - return this->ReplaceValues->RustObjectDeps; + if (this->ReplaceValues->RustMainCrateRoot) { + if (variable == "RUST_MAIN_CRATE_ROOT") { + 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) { diff --git a/Source/cmRulePlaceholderExpander.h b/Source/cmRulePlaceholderExpander.h index e80a463a7d..60fc8f8ae3 100644 --- a/Source/cmRulePlaceholderExpander.h +++ b/Source/cmRulePlaceholderExpander.h @@ -79,8 +79,10 @@ public: char const* SwiftModuleName = nullptr; char const* SwiftOutputFileMapOption = nullptr; char const* SwiftSources = nullptr; - char const* RustSources = nullptr; - char const* RustObjectDeps = nullptr; + char const* RustEmit = nullptr; + char const* RustMainCrateRoot = nullptr; + char const* RustLinkCrates = nullptr; + char const* RustNativeObjects = nullptr; char const* ISPCHeader = nullptr; char const* CudaCompileMode = nullptr; char const* Fatbinary = nullptr; diff --git a/Source/cmSourceFile.cxx b/Source/cmSourceFile.cxx index 5f2cf3bf3e..3325eee89d 100644 --- a/Source/cmSourceFile.cxx +++ b/Source/cmSourceFile.cxx @@ -503,3 +503,13 @@ void cmSourceFile::SetCustomCommand(std::unique_ptr 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; +} diff --git a/Source/cmSourceFile.h b/Source/cmSourceFile.h index d951793cdd..0d9bc2e1b6 100644 --- a/Source/cmSourceFile.h +++ b/Source/cmSourceFile.h @@ -184,6 +184,8 @@ public: void SetObjectLibrary(std::string const& objlib); std::string GetObjectLibrary() const; + cmValue GetRustEmitProperty() const; + private: cmSourceFileLocation Location; cmPropertyMap Properties; diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index aa9be72c72..3172093f6b 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -380,6 +380,8 @@ TargetProperty const StaticTargetProperties[] = { { "Swift_LANGUAGE_VERSION"_s, IC::CanCompileSources }, { "Swift_MODULE_DIRECTORY"_s, IC::CanCompileSources }, { "Swift_COMPILATION_MODE"_s, IC::CanCompileSources }, + // ---- Rust + { "Rust_MAIN_CRATE_ROOT"_s, IC::CanCompileSources }, // ---- moc { "AUTOMOC"_s, IC::CanCompileSources }, { "AUTOMOC_COMPILER_PREDEFINES"_s, IC::CanCompileSources }, diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx index d66f0c3a6f..c5992ee0f7 100644 --- a/Source/cmVisualStudio10TargetGenerator.cxx +++ b/Source/cmVisualStudio10TargetGenerator.cxx @@ -2629,6 +2629,9 @@ void cmVisualStudio10TargetGenerator::WriteAllSources(Elem& e0) case cmGeneratorTarget::SourceKindResx: this->ResxObjs.push_back(si.Source); break; + case cmGeneratorTarget::SourceKindRustMainCrateRoot: + tool = "None"; + break; case cmGeneratorTarget::SourceKindXaml: this->XamlObjs.push_back(si.Source); break; diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 6fc634597a..802998d702 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -468,6 +468,7 @@ if(BUILD_TESTING) if(CMake_TEST_Rust) ADD_TEST_MACRO(RustOnly RustOnly) ADD_TEST_MACRO(RustMix RustMix) + ADD_TEST_MACRO(RustPie RustPie) endif() if(CMAKE_Fortran_COMPILER) ADD_TEST_MACRO(FortranOnly FortranOnly) diff --git a/Tests/RunCMake/Rust/Enable.cmake b/Tests/RunCMake/Rust/Enable.cmake index 89d9c54871..14b641f477 100644 --- a/Tests/RunCMake/Rust/Enable.cmake +++ b/Tests/RunCMake/Rust/Enable.cmake @@ -1,3 +1,3 @@ -set(CMAKE_EXPERIMENTAL_RUST "3cc9b32c-47d3-4056-8953-d74e69fc0d6c") +set(CMAKE_EXPERIMENTAL_RUST "6b6e613b-f6cb-402d-8aea-59034fa8c65b") enable_language(Rust) message(STATUS "CMAKE_Rust_COMPILER='${CMAKE_Rust_COMPILER}'") diff --git a/Tests/RustMix/CMakeLists.txt b/Tests/RustMix/CMakeLists.txt index 8551afdd96..e3e821b899 100644 --- a/Tests/RustMix/CMakeLists.txt +++ b/Tests/RustMix/CMakeLists.txt @@ -1,14 +1,30 @@ cmake_minimum_required(VERSION 4.2) -set(CMAKE_EXPERIMENTAL_RUST "3cc9b32c-47d3-4056-8953-d74e69fc0d6c") +set(CMAKE_EXPERIMENTAL_RUST "6b6e613b-f6cb-402d-8aea-59034fa8c65b") -project(RustMix LANGUAGES C Rust) +project(RustMix LANGUAGES C CXX Rust) -add_library(liba STATIC liba.rs) -add_library(libb SHARED libb.rs) -add_library(libc OBJECT libc.rs) +add_library(rs_staticlib STATIC rs_staticlib.rs) +add_library(rs_cdylib SHARED rs_cdylib.rs) +add_library(rs_rlib OBJECT rs_rlib.rs) -add_executable(RustMix main.c) -target_link_libraries(RustMix liba) -target_link_libraries(RustMix libb) -target_link_libraries(RustMix libc) +add_library(c_static STATIC c_static.c) +add_library(c_shared SHARED c_shared.c) +add_library(c_obj OBJECT c_obj.c) + +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 +) diff --git a/Tests/RustMix/c_obj.c b/Tests/RustMix/c_obj.c new file mode 100644 index 0000000000..e269d41fef --- /dev/null +++ b/Tests/RustMix/c_obj.c @@ -0,0 +1,6 @@ +#include + +void c_obj_greet() +{ + printf("Hello from a C object file!"); +} diff --git a/Tests/RustMix/c_shared.c b/Tests/RustMix/c_shared.c new file mode 100644 index 0000000000..42255a3924 --- /dev/null +++ b/Tests/RustMix/c_shared.c @@ -0,0 +1,6 @@ +#include + +void c_shared_greet() +{ + printf("Hello from a C shared library!"); +} diff --git a/Tests/RustMix/c_static.c b/Tests/RustMix/c_static.c new file mode 100644 index 0000000000..6af1de2f79 --- /dev/null +++ b/Tests/RustMix/c_static.c @@ -0,0 +1,6 @@ +#include + +void c_static_greet() +{ + printf("Hello from a C static library!"); +} diff --git a/Tests/RustMix/cpp_shared.cpp b/Tests/RustMix/cpp_shared.cpp new file mode 100644 index 0000000000..bee6784fda --- /dev/null +++ b/Tests/RustMix/cpp_shared.cpp @@ -0,0 +1,6 @@ +#include + +extern "C" void cpp_shared_greet() +{ + std::cout << "Hello from a C++ shader library" << std::endl; +} diff --git a/Tests/RustMix/liba.rs b/Tests/RustMix/liba.rs deleted file mode 100644 index 2958db3758..0000000000 --- a/Tests/RustMix/liba.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[no_mangle] -pub extern "C" fn liba_greet() { - println!("Hello from Rust liba"); -} diff --git a/Tests/RustMix/libb.rs b/Tests/RustMix/libb.rs deleted file mode 100644 index e94f46fb88..0000000000 --- a/Tests/RustMix/libb.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[no_mangle] -pub extern "C" fn libb_greet() { - println!("Hello from Rust libb"); -} diff --git a/Tests/RustMix/libc.rs b/Tests/RustMix/libc.rs deleted file mode 100644 index 52bd67d5a2..0000000000 --- a/Tests/RustMix/libc.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[no_mangle] -pub extern "C" fn libc_greet() { - println!("Hello from Rust libc"); -} diff --git a/Tests/RustMix/main.c b/Tests/RustMix/main.c deleted file mode 100644 index 39ceba10e0..0000000000 --- a/Tests/RustMix/main.c +++ /dev/null @@ -1,15 +0,0 @@ -#include - -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; -} diff --git a/Tests/RustMix/main.rs b/Tests/RustMix/main.rs new file mode 100644 index 0000000000..fc153b0ab1 --- /dev/null +++ b/Tests/RustMix/main.rs @@ -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(); +} diff --git a/Tests/RustMix/rs_cdylib.rs b/Tests/RustMix/rs_cdylib.rs new file mode 100644 index 0000000000..ff8fd29adc --- /dev/null +++ b/Tests/RustMix/rs_cdylib.rs @@ -0,0 +1,4 @@ +#[no_mangle] +pub extern "C" fn rs_cdylib_greet() { + println!("Hello from a Rust cdylib!"); +} diff --git a/Tests/RustMix/rs_rlib.rs b/Tests/RustMix/rs_rlib.rs new file mode 100644 index 0000000000..6456453097 --- /dev/null +++ b/Tests/RustMix/rs_rlib.rs @@ -0,0 +1,3 @@ +pub fn rs_rlib_greet() { + println!("Hello from a Rust rlib!"); +} diff --git a/Tests/RustMix/rs_staticlib.rs b/Tests/RustMix/rs_staticlib.rs new file mode 100644 index 0000000000..c25b65f466 --- /dev/null +++ b/Tests/RustMix/rs_staticlib.rs @@ -0,0 +1,4 @@ +#[no_mangle] +pub extern "C" fn rs_staticlib_greet() { + println!("Hello from a Rust staticlib!"); +} diff --git a/Tests/RustOnly/CMakeLists.txt b/Tests/RustOnly/CMakeLists.txt index 8f889fbfb7..33e97422ac 100644 --- a/Tests/RustOnly/CMakeLists.txt +++ b/Tests/RustOnly/CMakeLists.txt @@ -1,14 +1,18 @@ cmake_minimum_required(VERSION 4.2) -set(CMAKE_EXPERIMENTAL_RUST "3cc9b32c-47d3-4056-8953-d74e69fc0d6c") +set(CMAKE_EXPERIMENTAL_RUST "6b6e613b-f6cb-402d-8aea-59034fa8c65b") project(RustOnly LANGUAGES Rust) -add_library(liba STATIC liba.rs) -add_library(libb SHARED libb.rs) -add_library(libc OBJECT libc.rs) +add_library(a STATIC a.rs) +add_library(b SHARED b.rs) +add_library(c OBJECT c.rs) -add_executable(RustOnly main.rs) -target_link_libraries(RustOnly liba) -target_link_libraries(RustOnly libb) -target_link_libraries(RustOnly libc) +add_executable(RustOnly d.rs main.rs e.rs) +# Ensure that Rust_MAIN_CRATE_ROOT property works. +set_target_properties(RustOnly PROPERTIES Rust_MAIN_CRATE_ROOT main.rs) +# 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) diff --git a/Tests/RustOnly/liba.rs b/Tests/RustOnly/a.rs similarity index 100% rename from Tests/RustOnly/liba.rs rename to Tests/RustOnly/a.rs diff --git a/Tests/RustOnly/libb.rs b/Tests/RustOnly/b.rs similarity index 100% rename from Tests/RustOnly/libb.rs rename to Tests/RustOnly/b.rs diff --git a/Tests/RustOnly/c.rs b/Tests/RustOnly/c.rs new file mode 100644 index 0000000000..6de15d74cd --- /dev/null +++ b/Tests/RustOnly/c.rs @@ -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"); +} diff --git a/Tests/RustOnly/d.rs b/Tests/RustOnly/d.rs new file mode 100644 index 0000000000..52de74c756 --- /dev/null +++ b/Tests/RustOnly/d.rs @@ -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"); +} diff --git a/Tests/RustOnly/e.rs b/Tests/RustOnly/e.rs new file mode 100644 index 0000000000..4b3bd7469a --- /dev/null +++ b/Tests/RustOnly/e.rs @@ -0,0 +1,4 @@ +#[no_mangle] +pub extern "C" fn libe_greet() { + println!("Hello from libe"); +} diff --git a/Tests/RustOnly/libc.rs b/Tests/RustOnly/libc.rs deleted file mode 100644 index dd46cc51a3..0000000000 --- a/Tests/RustOnly/libc.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[no_mangle] -pub extern "C" fn libc_greet() { - println!("Hello from libc"); -} diff --git a/Tests/RustOnly/main.rs b/Tests/RustOnly/main.rs index 067fb247e4..d177874365 100644 --- a/Tests/RustOnly/main.rs +++ b/Tests/RustOnly/main.rs @@ -3,13 +3,19 @@ mod moda; extern "C" { fn liba_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() { println!("Hello from main.rs"); moda::moda_greet(); unsafe { liba_greet() }; unsafe { libb_greet() }; - unsafe { libc_greet() }; + libc_greet(); + unsafe { libe_greet() }; } diff --git a/Tests/RustPie/CMakeLists.txt b/Tests/RustPie/CMakeLists.txt new file mode 100644 index 0000000000..b32b852fe6 --- /dev/null +++ b/Tests/RustPie/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 4.2) + +set(CMAKE_EXPERIMENTAL_RUST "6b6e613b-f6cb-402d-8aea-59034fa8c65b") + +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_$.rs + INPUT runner.rs +) + +add_executable(RustPie ${CMAKE_BINARY_DIR}/runner_$.rs) +add_dependencies(RustPie default_RustPie disabled_RustPie enabled_RustPie) diff --git a/Tests/RustPie/Default/CMakeLists.txt b/Tests/RustPie/Default/CMakeLists.txt new file mode 100644 index 0000000000..806bd62c31 --- /dev/null +++ b/Tests/RustPie/Default/CMakeLists.txt @@ -0,0 +1,3 @@ + +project(DefaultRustPie LANGUAGES Rust) +setup(default) diff --git a/Tests/RustPie/Disabled/CMakeLists.txt b/Tests/RustPie/Disabled/CMakeLists.txt new file mode 100644 index 0000000000..b5161be4cd --- /dev/null +++ b/Tests/RustPie/Disabled/CMakeLists.txt @@ -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 +) diff --git a/Tests/RustPie/Enabled/CMakeLists.txt b/Tests/RustPie/Enabled/CMakeLists.txt new file mode 100644 index 0000000000..c48a4e3c6e --- /dev/null +++ b/Tests/RustPie/Enabled/CMakeLists.txt @@ -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 +) diff --git a/Tests/RustPie/main.rs b/Tests/RustPie/main.rs new file mode 100644 index 0000000000..fb92a968ff --- /dev/null +++ b/Tests/RustPie/main.rs @@ -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(); +} diff --git a/Tests/RustPie/object.rs b/Tests/RustPie/object.rs new file mode 100644 index 0000000000..b189f46f42 --- /dev/null +++ b/Tests/RustPie/object.rs @@ -0,0 +1,3 @@ +pub fn object_foo() { + println!("object_foo"); +} diff --git a/Tests/RustPie/runner.rs b/Tests/RustPie/runner.rs new file mode 100644 index 0000000000..bc0a79854c --- /dev/null +++ b/Tests/RustPie/runner.rs @@ -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#"$"#, + r#"$"#, + r#"$"#, + ]; + 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." + ); +} diff --git a/Tests/RustPie/shared.rs b/Tests/RustPie/shared.rs new file mode 100644 index 0000000000..40f89e6e15 --- /dev/null +++ b/Tests/RustPie/shared.rs @@ -0,0 +1,4 @@ +#[no_mangle] +pub extern "C" fn shared_foo() { + println!("shared_foo"); +} diff --git a/Tests/RustPie/static.rs b/Tests/RustPie/static.rs new file mode 100644 index 0000000000..99b3116837 --- /dev/null +++ b/Tests/RustPie/static.rs @@ -0,0 +1,4 @@ +#[no_mangle] +pub extern "C" fn static_foo() { + println!("static_foo"); +}