#[=======================================================================[.rst: FindRust -------- Find Rust This module finds an installed rustc compiler and the cargo build tool. If Rust is managed by rustup it determines the available toolchains and returns a concrete Rust version, not a rustup proxy. #]=======================================================================] cmake_minimum_required(VERSION 3.12) option( Rust_RUSTUP_INSTALL_MISSING_TARGET "Use Rustup to automatically install missing targets instead of giving up" OFF ) # search for Cargo here and set up a bunch of cool flags and stuff include(FindPackageHandleStandardArgs) list(APPEND CMAKE_MESSAGE_CONTEXT "FindRust") # Print error message and return. Should not be used from inside functions macro(_findrust_failed) if("${Rust_FIND_REQUIRED}") message(FATAL_ERROR ${ARGN}) elseif(NOT "${Rust_FIND_QUIETLY}") message(WARNING ${ARGN}) endif() set(Rust_FOUND "") return() endmacro() # Checks if the actual version of a Rust toolchain matches the VERSION requirements specified in find_package. function(_findrust_version_ok ACTUAL_VERSION OUT_IS_OK) if(DEFINED Rust_FIND_VERSION_RANGE) if(Rust_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE") set(COMPARSION_OPERATOR "VERSION_LESS_EQUAL") elseif(Rust_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE") set(COMPARSION_OPERATOR "VERSION_LESS") else() message(FATAL_ERROR "Unexpected value in `_FIND_VERSION_RANGE_MAX`: " "`${Rust_FIND_VERSION_RANGE_MAX}`.") endif() if(("${ACTUAL_VERSION}" VERSION_GREATER_EQUAL "${Rust_FIND_VERSION_RANGE_MIN}") AND ( "${ACTUAL_VERSION}" ${COMPARSION_OPERATOR} "${Rust_FIND_VERSION_RANGE_MAX}" ) ) set("${OUT_IS_OK}" TRUE PARENT_SCOPE) else() set("${OUT_IS_OK}" FALSE PARENT_SCOPE) endif() elseif(DEFINED Rust_FIND_VERSION) if(Rust_VERSION_EXACT) set(COMPARISON_OPERATOR VERSION_EQUAL) else() set(COMPARISON_OPERATOR VERSION_GREATER_EQUAL) endif() if(_TOOLCHAIN_${_TOOLCHAIN_SELECTED}_VERSION "${COMPARISON_OPERATOR}" Rust_FIND_VERSION) set("${OUT_IS_OK}" TRUE PARENT_SCOPE) else() set("${OUT_IS_OK}" FALSE PARENT_SCOPE) endif() else() # if no VERSION requirement was specified, the version is always okay. set("${OUT_IS_OK}" TRUE PARENT_SCOPE) endif() endfunction() function(_corrosion_strip_target_triple input_triple_or_path output_triple) # If the target_triple is a path to a custom target specification file, then strip everything # except the filename from `target_triple`. get_filename_component(target_triple_ext "${input_triple_or_path}" EXT) set(target_triple "${input_triple_or_path}") if(target_triple_ext) if(target_triple_ext STREQUAL ".json") get_filename_component(target_triple "${input_triple_or_path}" NAME_WE) endif() endif() set(${output_triple} "${target_triple}" PARENT_SCOPE) endfunction() function(_corrosion_parse_target_triple target_triple out_arch out_vendor out_os out_env) _corrosion_strip_target_triple(${target_triple} target_triple) # The vendor part may be left out from the target triple, and since `env` is also optional, # we determine if vendor is present by matching against a list of known vendors. set(known_vendors "apple" "esp[a-z0-9]*" # espressif, e.g. riscv32imc-esp-espidf or xtensa-esp32s3-none-elf "fortanix" "kmc" "pc" "nintendo" "nvidia" "openwrt" "alpine" "chimera" "unikraft" "unknown" "uwp" # aarch64-uwp-windows-msvc "wrs" # e.g. aarch64-wrs-vxworks "sony" "sun" ) # todo: allow users to add additional vendors to the list via a cmake variable. list(JOIN known_vendors "|" known_vendors_joined) # vendor is optional - We detect if vendor is present by matching against a known list of # vendors. The next field is the OS, which we assume to always be present, while the last field # is again optional and contains the environment. string(REGEX MATCH "^([a-z0-9_\.]+)-((${known_vendors_joined})-)?([a-z0-9_]+)(-([a-z0-9_]+))?$" whole_match "${target_triple}" ) if((NOT whole_match) AND (NOT CORROSION_NO_WARN_PARSE_TARGET_TRIPLE_FAILED)) message(WARNING "Failed to parse target-triple `${target_triple}`." "Corrosion determines some information about the output artifacts based on OS " "specified in the Rust target-triple.\n" "Currently this is relevant for windows and darwin (mac) targets, since file " "extensions differ.\n" "Note: If you are targeting a different OS you can suppress this warning by" " setting the CMake cache variable " "`CORROSION_NO_WARN_PARSE_TARGET_TRIPLE_FAILED`." "Please consider opening an issue on github if you you need to add a new vendor to the list." ) endif() message(DEBUG "Parsed Target triple: arch: ${CMAKE_MATCH_1}, vendor: ${CMAKE_MATCH_3}, " "OS: ${CMAKE_MATCH_4}, env: ${CMAKE_MATCH_6}") set("${out_arch}" "${CMAKE_MATCH_1}" PARENT_SCOPE) set("${out_vendor}" "${CMAKE_MATCH_3}" PARENT_SCOPE) set("${out_os}" "${CMAKE_MATCH_4}" PARENT_SCOPE) set("${out_env}" "${CMAKE_MATCH_6}" PARENT_SCOPE) endfunction() function(_corrosion_determine_libs_new target_triple out_libs out_flags) set(package_dir "${CMAKE_BINARY_DIR}/corrosion/required_libs") # Cleanup on reconfigure to get a cleans state (in case we change something in the future) file(REMOVE_RECURSE "${package_dir}") file(MAKE_DIRECTORY "${package_dir}") set(manifest "[package]\nname = \"required_libs\"\nedition = \"2018\"\nversion = \"0.1.0\"\n") string(APPEND manifest "\n[lib]\ncrate-type=[\"staticlib\"]\npath = \"lib.rs\"\n") string(APPEND manifest "\n[workspace]\n") file(WRITE "${package_dir}/Cargo.toml" "${manifest}") file(WRITE "${package_dir}/lib.rs" "pub fn add(left: usize, right: usize) -> usize {left + right}\n") execute_process( COMMAND ${CMAKE_COMMAND} -E env "CARGO_BUILD_RUSTC=${Rust_COMPILER_CACHED}" ${Rust_CARGO_CACHED} rustc --verbose --color never --target=${target_triple} -- --print=native-static-libs WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/corrosion/required_libs" RESULT_VARIABLE cargo_build_result ERROR_VARIABLE cargo_build_error_message ) if(cargo_build_result) message(DEBUG "Determining required native libraries - failed: ${cargo_build_result}.") message(TRACE "The cargo build error was: ${cargo_build_error_message}") message(DEBUG "Note: This is expected for Rust targets without std support") return() else() # The pattern starts with `native-static-libs:` and goes to the end of the line. if(cargo_build_error_message MATCHES "native-static-libs: ([^\r\n]+)\r?\n") string(REPLACE " " ";" "libs_list" "${CMAKE_MATCH_1}") set(stripped_lib_list "") set(flag_list "") set(was_last_framework OFF) foreach(lib ${libs_list}) # merge -framework;lib -> "-framework lib" as CMake does de-duplication of link libraries, and -framework prefix is required if (lib STREQUAL "-framework") set(was_last_framework ON) continue() endif() if (was_last_framework) list(APPEND stripped_lib_list "-framework ${lib}") set(was_last_framework OFF) continue() endif() # Flags start with / for MSVC if (lib MATCHES "^/" AND ${target_triple} MATCHES "msvc$") # Windows GNU uses the compiler to invoke the linker, so -Wl, prefix is needed # https://gitlab.kitware.com/cmake/cmake/-/blob/9bed4f4d817f139f0c2e050d7420e1e247949fe4/Modules/Platform/Windows-GNU.cmake#L156 if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") list(APPEND flag_list "-Wl,${lib}") else() list(APPEND flag_list "${lib}") endif() else() # Strip leading `-l` (unix) and potential .lib suffix (windows) string(REGEX REPLACE "^-l" "" "stripped_lib" "${lib}") string(REGEX REPLACE "\.lib$" "" "stripped_lib" "${stripped_lib}") list(APPEND stripped_lib_list "${stripped_lib}") endif() endforeach() set(libs_list "${stripped_lib_list}") # We leave it up to the C/C++ executable that links in the Rust static-library # to determine which version of the msvc runtime library it should select. list(FILTER libs_list EXCLUDE REGEX "^msvcrtd?") list(FILTER flag_list EXCLUDE REGEX "^(-Wl,)?/defaultlib:msvcrtd?") else() message(DEBUG "Determining required native libraries - failed: Regex match failure.") message(DEBUG "`native-static-libs` not found in: `${cargo_build_error_message}`") return() endif() endif() set("${out_libs}" "${libs_list}" PARENT_SCOPE) set("${out_flags}" "${flag_list}" PARENT_SCOPE) endfunction() if (NOT "${Rust_TOOLCHAIN}" STREQUAL "$CACHE{Rust_TOOLCHAIN}") # Promote Rust_TOOLCHAIN to a cache variable if it is not already a cache variable set(Rust_TOOLCHAIN ${Rust_TOOLCHAIN} CACHE STRING "Requested rustup toolchain" FORCE) endif() set(_RESOLVE_RUSTUP_TOOLCHAINS_DESC "Indicates whether to descend into the toolchain pointed to by rustup") set(Rust_RESOLVE_RUSTUP_TOOLCHAINS ON CACHE BOOL ${_RESOLVE_RUSTUP_TOOLCHAINS_DESC}) # This block checks to see if we're prioritizing a rustup-managed toolchain. if (DEFINED Rust_TOOLCHAIN) # If the user specifies `Rust_TOOLCHAIN`, then look for `rustup` first, rather than `rustc`. find_program(Rust_RUSTUP rustup PATHS "$ENV{HOME}/.cargo/bin") if(NOT Rust_RUSTUP) if(NOT "${Rust_FIND_QUIETLY}") message( WARNING "CMake variable `Rust_TOOLCHAIN` specified, but `rustup` was not found. " "Ignoring toolchain and looking for a Rust toolchain not managed by rustup.") endif() endif() else() # If we aren't definitely using a rustup toolchain, look for rustc first - the user may have # a toolchain installed via a method other than rustup higher in the PATH, which should be # preferred. However, if the first-found rustc is a rustup proxy, then we'll revert to # finding the preferred toolchain via rustup. # Uses `Rust_COMPILER` to let user-specified `rustc` win. But we will still "override" the # user's setting if it is pointing to `rustup`. Default rustup install path is provided as a # backup if a toolchain cannot be found in the user's PATH. if (DEFINED Rust_COMPILER) set(_Rust_COMPILER_TEST "${Rust_COMPILER}") set(_USER_SPECIFIED_RUSTC ON) if(NOT (EXISTS "${_Rust_COMPILER_TEST}" AND NOT IS_DIRECTORY "${_Rust_COMPILER_TEST}")) set(_ERROR_MESSAGE "Rust_COMPILER was set to `${Rust_COMPILER}`, but this file does " "not exist." ) _findrust_failed(${_ERROR_MESSAGE}) return() endif() else() find_program(_Rust_COMPILER_TEST rustc PATHS "$ENV{HOME}/.cargo/bin") if(NOT EXISTS "${_Rust_COMPILER_TEST}") cmake_path(CONVERT "$ENV{HOME}/.cargo/bin" TO_CMAKE_PATH_LIST _cargo_bin_dir) set(_ERROR_MESSAGE "`rustc` not found in PATH or `${_cargo_bin_dir}`.\n" "Hint: Check if `rustc` is in PATH or manually specify the location " "by setting `Rust_COMPILER` to the path to `rustc`.") _findrust_failed(${_ERROR_MESSAGE}) endif() endif() # Check if the discovered rustc is actually a "rustup" proxy. execute_process( COMMAND ${CMAKE_COMMAND} -E env RUSTUP_FORCE_ARG0=rustup "${_Rust_COMPILER_TEST}" --version OUTPUT_VARIABLE _RUSTC_VERSION_RAW ERROR_VARIABLE _RUSTC_VERSION_STDERR RESULT_VARIABLE _RUSTC_VERSION_RESULT ) if(NOT (_RUSTC_VERSION_RESULT EQUAL "0")) _findrust_failed("`${_Rust_COMPILER_TEST} --version` failed with ${_RUSTC_VERSION_RESULT}\n" "rustc stderr:\n${_RUSTC_VERSION_STDERR}" ) endif() if (_RUSTC_VERSION_RAW MATCHES "rustup [0-9\\.]+") if (_USER_SPECIFIED_RUSTC) message( WARNING "User-specified Rust_COMPILER pointed to rustup's rustc proxy. Corrosion's " "FindRust will always try to evaluate to an actual Rust toolchain, and so the " "user-specified Rust_COMPILER will be discarded in favor of the default " "rustup-managed toolchain." ) unset(Rust_COMPILER) unset(Rust_COMPILER CACHE) endif() # Get `rustup` next to the `rustc` proxy get_filename_component(_RUST_PROXIES_PATH "${_Rust_COMPILER_TEST}" DIRECTORY) find_program(Rust_RUSTUP rustup HINTS "${_RUST_PROXIES_PATH}" NO_DEFAULT_PATH) endif() unset(_Rust_COMPILER_TEST CACHE) endif() # At this point, the only thing we should have evaluated is a path to `rustup` _if that's what the # best source for a Rust toolchain was determined to be_. if (NOT Rust_RUSTUP) set(Rust_RESOLVE_RUSTUP_TOOLCHAINS OFF CACHE BOOL ${_RESOLVE_RUSTUP_TOOLCHAINS_DESC} FORCE) endif() # List of user variables that will override any toolchain-provided setting set(_Rust_USER_VARS Rust_COMPILER Rust_CARGO Rust_CARGO_TARGET Rust_CARGO_HOST_TARGET) foreach(_VAR ${_Rust_USER_VARS}) if (DEFINED "${_VAR}") set(${_VAR}_CACHED "${${_VAR}}" CACHE INTERNAL "Internal cache of ${_VAR}") else() unset(${_VAR}_CACHED CACHE) endif() endforeach() # Discover what toolchains are installed by rustup, if the discovered `rustc` is a proxy from # `rustup` and the user hasn't explicitly requested to override this behavior, then select either # the default toolchain, or the requested toolchain Rust_TOOLCHAIN if (Rust_RESOLVE_RUSTUP_TOOLCHAINS) execute_process( COMMAND "${Rust_RUSTUP}" toolchain list --verbose OUTPUT_VARIABLE _TOOLCHAINS_RAW ) string(REPLACE "\n" ";" _TOOLCHAINS_RAW "${_TOOLCHAINS_RAW}") set(_DISCOVERED_TOOLCHAINS "") set(_DISCOVERED_TOOLCHAINS_RUSTC_PATH "") set(_DISCOVERED_TOOLCHAINS_CARGO_PATH "") set(_DISCOVERED_TOOLCHAINS_VERSION "") foreach(_TOOLCHAIN_RAW ${_TOOLCHAINS_RAW}) if (_TOOLCHAIN_RAW MATCHES "([a-zA-Z0-9\\._\\-]+)[ \t\r\n]?(\\(default\\) \\(override\\)|\\(default\\)|\\(override\\))?[ \t\r\n]+(.+)") set(_TOOLCHAIN "${CMAKE_MATCH_1}") set(_TOOLCHAIN_TYPE "${CMAKE_MATCH_2}") set(_TOOLCHAIN_PATH "${CMAKE_MATCH_3}") set(_TOOLCHAIN_${_TOOLCHAIN}_PATH "${CMAKE_MATCH_3}") if (_TOOLCHAIN_TYPE MATCHES ".*\\(default\\).*") set(_TOOLCHAIN_DEFAULT "${_TOOLCHAIN}") endif() if (_TOOLCHAIN_TYPE MATCHES ".*\\(override\\).*") set(_TOOLCHAIN_OVERRIDE "${_TOOLCHAIN}") endif() execute_process( COMMAND "${_TOOLCHAIN_PATH}/bin/rustc" --version OUTPUT_VARIABLE _TOOLCHAIN_RAW_VERSION ) if (_TOOLCHAIN_RAW_VERSION MATCHES "rustc ([0-9]+)\\.([0-9]+)\\.([0-9]+)(-nightly)?") list(APPEND _DISCOVERED_TOOLCHAINS "${_TOOLCHAIN}") list(APPEND _DISCOVERED_TOOLCHAINS_RUSTC_PATH "${_TOOLCHAIN_PATH}/bin/rustc") list(APPEND _DISCOVERED_TOOLCHAINS_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}") # We need this variable to determine the default toolchain, since `foreach(... IN ZIP_LISTS ...)` # requires CMake 3.17. As a workaround we define this variable to lookup the version when iterating # through the `_DISCOVERED_TOOLCHAINS` lists. set(_TOOLCHAIN_${_TOOLCHAIN}_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}") if(CMAKE_MATCH_4) set(_TOOLCHAIN_${_TOOLCHAIN}_IS_NIGHTLY "TRUE") else() set(_TOOLCHAIN_${_TOOLCHAIN}_IS_NIGHTLY "FALSE") endif() if(EXISTS "${_TOOLCHAIN_PATH}/bin/cargo") list(APPEND _DISCOVERED_TOOLCHAINS_CARGO_PATH "${_TOOLCHAIN_PATH}/bin/cargo") else() list(APPEND _DISCOVERED_TOOLCHAINS_CARGO_PATH "NOTFOUND") endif() else() message(AUTHOR_WARNING "Unexpected output from `rustc --version` for Toolchain `${_TOOLCHAIN}`: " "`${_TOOLCHAIN_RAW_VERSION}`.\n" "Ignoring this toolchain." ) endif() else() message(AUTHOR_WARNING "Didn't recognize toolchain: ${_TOOLCHAIN_RAW}. Ignoring this toolchain.\n" "Rustup toolchain list output( `${Rust_RUSTUP} toolchain list --verbose`):\n" "${_TOOLCHAINS_RAW}" ) endif() endforeach() # Expose a list of available rustup toolchains. list(LENGTH _DISCOVERED_TOOLCHAINS _toolchain_len) list(LENGTH _DISCOVERED_TOOLCHAINS_RUSTC_PATH _toolchain_rustc_len) list(LENGTH _DISCOVERED_TOOLCHAINS_CARGO_PATH _toolchain_cargo_len) list(LENGTH _DISCOVERED_TOOLCHAINS_VERSION _toolchain_version_len) if(NOT (_toolchain_len EQUAL _toolchain_rustc_len AND _toolchain_cargo_len EQUAL _toolchain_version_len AND _toolchain_len EQUAL _toolchain_cargo_len) ) message(FATAL_ERROR "Internal error - list length mismatch." "List lengths: ${_toolchain_len} toolchains, ${_toolchain_rustc_len} rustc, ${_toolchain_cargo_len} cargo," " ${_toolchain_version_len} version. The lengths should be the same." ) endif() set(Rust_RUSTUP_TOOLCHAINS "${_DISCOVERED_TOOLCHAINS}" CACHE INTERNAL "List of available Rustup toolchains") set(Rust_RUSTUP_TOOLCHAINS_RUSTC_PATH "${_DISCOVERED_TOOLCHAINS_RUSTC_PATH}" CACHE INTERNAL "List of the rustc paths corresponding to the toolchain at the same index in `Rust_RUSTUP_TOOLCHAINS`." ) set(Rust_RUSTUP_TOOLCHAINS_CARGO_PATH "${_DISCOVERED_TOOLCHAINS_CARGO_PATH}" CACHE INTERNAL "List of the cargo paths corresponding to the toolchain at the same index in `Rust_RUSTUP_TOOLCHAINS`. \ May also be `NOTFOUND` if the toolchain does not have a cargo executable." ) set(Rust_RUSTUP_TOOLCHAINS_VERSION "${_DISCOVERED_TOOLCHAINS_VERSION}" CACHE INTERNAL "List of the rust toolchain version corresponding to the toolchain at the same index in \ `Rust_RUSTUP_TOOLCHAINS`." ) # Rust_TOOLCHAIN is preferred over a requested version if it is set. if (NOT DEFINED Rust_TOOLCHAIN) if (NOT DEFINED _TOOLCHAIN_OVERRIDE) set(_TOOLCHAIN_SELECTED "${_TOOLCHAIN_DEFAULT}") else() set(_TOOLCHAIN_SELECTED "${_TOOLCHAIN_OVERRIDE}") endif() # Check default toolchain first. _findrust_version_ok("_TOOLCHAIN_${_TOOLCHAIN_SELECTED}_VERSION" _VERSION_OK) if(NOT "${_VERSION_OK}") foreach(_TOOLCHAIN "${_DISCOVERED_TOOLCHAINS}") _findrust_version_ok("_TOOLCHAIN_${_TOOLCHAIN}_VERSION" _VERSION_OK) if("${_VERSION_OK}") set(_TOOLCHAIN_SELECTED "${_TOOLCHAIN}") break() endif() endforeach() # Check if we found a suitable version in the for loop. if(NOT "${_VERSION_OK}") string(REPLACE ";" "\n" _DISCOVERED_TOOLCHAINS "${_DISCOVERED_TOOLCHAINS}") _findrust_failed("Failed to find a Rust toolchain matching the version requirements of " "${Rust_FIND_VERSION}. Available toolchains: ${_DISCOVERED_TOOLCHAINS}") endif() endif() endif() set(Rust_TOOLCHAIN "${_TOOLCHAIN_SELECTED}" CACHE STRING "The rustup toolchain to use") set_property(CACHE Rust_TOOLCHAIN PROPERTY STRINGS "${_DISCOVERED_TOOLCHAINS}") if(NOT Rust_FIND_QUIETLY) message(STATUS "Rust Toolchain: ${Rust_TOOLCHAIN}") endif() if (NOT Rust_TOOLCHAIN IN_LIST _DISCOVERED_TOOLCHAINS) # If the precise toolchain wasn't found, try appending the default host execute_process( COMMAND "${Rust_RUSTUP}" show RESULT_VARIABLE _SHOW_RESULT OUTPUT_VARIABLE _SHOW_RAW ) if(NOT "${_SHOW_RESULT}" EQUAL "0") _findrust_failed("Command `${Rust_RUSTUP} show` failed") endif() if (_SHOW_RAW MATCHES "Default host: ([a-zA-Z0-9_\\-]*)\n") set(_DEFAULT_HOST "${CMAKE_MATCH_1}") else() _findrust_failed("Failed to parse \"Default host\" from `${Rust_RUSTUP} show`. Got: ${_SHOW_RAW}") endif() if (NOT "${Rust_TOOLCHAIN}-${_DEFAULT_HOST}" IN_LIST _DISCOVERED_TOOLCHAINS) set(_NOT_FOUND_MESSAGE "Could not find toolchain '${Rust_TOOLCHAIN}'\n" "Available toolchains:\n" ) foreach(_TOOLCHAIN ${_DISCOVERED_TOOLCHAINS}) list(APPEND _NOT_FOUND_MESSAGE " `${_TOOLCHAIN}`\n") endforeach() _findrust_failed(${_NOT_FOUND_MESSAGE}) endif() set(_RUSTUP_TOOLCHAIN_FULL "${Rust_TOOLCHAIN}-${_DEFAULT_HOST}") else() set(_RUSTUP_TOOLCHAIN_FULL "${Rust_TOOLCHAIN}") endif() set(_RUST_TOOLCHAIN_PATH "${_TOOLCHAIN_${_RUSTUP_TOOLCHAIN_FULL}_PATH}") if(NOT "${Rust_FIND_QUIETLY}") message(VERBOSE "Rust toolchain ${_RUSTUP_TOOLCHAIN_FULL}") message(VERBOSE "Rust toolchain path ${_RUST_TOOLCHAIN_PATH}") endif() # Is overridden if the user specifies `Rust_COMPILER` explicitly. find_program( Rust_COMPILER_CACHED rustc HINTS "${_RUST_TOOLCHAIN_PATH}/bin" NO_DEFAULT_PATH) else() message(DEBUG "Rust_RESOLVE_RUSTUP_TOOLCHAINS=OFF and Rust_RUSTUP=${Rust_RUSTUP}") if(Rust_RUSTUP) get_filename_component(_RUSTUP_DIR "${Rust_RUSTUP}" DIRECTORY) find_program(Rust_COMPILER_CACHED rustc HINTS "${_RUSTUP_DIR}") else() find_program(Rust_COMPILER_CACHED rustc) endif() message(DEBUG "find_program rustc: ${Rust_COMPILER_CACHED}") if (EXISTS "${Rust_COMPILER_CACHED}") # rustc is expected to be at `/bin/rustc`. get_filename_component(_RUST_TOOLCHAIN_PATH "${Rust_COMPILER_CACHED}" DIRECTORY) get_filename_component(_RUST_TOOLCHAIN_PATH "${_RUST_TOOLCHAIN_PATH}" DIRECTORY) endif() endif() if (NOT EXISTS "${Rust_COMPILER_CACHED}") set(_NOT_FOUND_MESSAGE "The rustc executable was not found. " "Rust not installed or ~/.cargo/bin not added to path?\n" "Hint: Consider setting `Rust_COMPILER` to the absolute path of `rustc`." ) _findrust_failed(${_NOT_FOUND_MESSAGE}) endif() if (Rust_RESOLVE_RUSTUP_TOOLCHAINS) set(_NOT_FOUND_MESSAGE "Rust was detected to be managed by rustup, but failed to find `cargo` " "next to `rustc` in `${_RUST_TOOLCHAIN_PATH}/bin`. This can happen for custom toolchains, " "if cargo was not built. " "Please manually specify the path to a compatible `cargo` by setting `Rust_CARGO`." ) find_program( Rust_CARGO_CACHED cargo HINTS "${_RUST_TOOLCHAIN_PATH}/bin" NO_DEFAULT_PATH ) # note: maybe can use find_package_handle_standard_args here, if we remove the _CACHED postfix. # not sure why that is here... if(NOT EXISTS "${Rust_CARGO_CACHED}") _findrust_failed(${_NOT_FOUND_MESSAGE}) endif() set(Rust_TOOLCHAIN_IS_RUSTUP_MANAGED TRUE CACHE INTERNAL "" FORCE) else() set(_NOT_FOUND_MESSAGE "Failed to find `cargo` in PATH and `${_RUST_TOOLCHAIN_PATH}/bin`.\n" "Please ensure cargo is in PATH or manually specify the path to a compatible `cargo` by " "setting `Rust_CARGO`." ) # On some systems (e.g. NixOS) cargo is not managed by rustup and also not next to rustc. find_program( Rust_CARGO_CACHED cargo HINTS "${_RUST_TOOLCHAIN_PATH}/bin" ) # note: maybe can use find_package_handle_standard_args here, if we remove the _CACHED postfix. # not sure why that is here... if(NOT EXISTS "${Rust_CARGO_CACHED}") _findrust_failed(${_NOT_FOUND_MESSAGE}) endif() endif() execute_process( COMMAND "${Rust_CARGO_CACHED}" --version --verbose OUTPUT_VARIABLE _CARGO_VERSION_RAW RESULT_VARIABLE _CARGO_VERSION_RESULT ) # todo: check if cargo is a required component! if(NOT ( "${_CARGO_VERSION_RESULT}" EQUAL "0" )) _findrust_failed("Failed to get cargo version.\n" "`${Rust_CARGO_CACHED} --version` failed with error: `${_CARGO_VERSION_RESULT}" ) endif() # todo: don't set cache variables here, but let find_package_handle_standard_args do the promotion # later. if (_CARGO_VERSION_RAW MATCHES "cargo ([0-9]+)\\.([0-9]+)\\.([0-9]+)") set(Rust_CARGO_VERSION_MAJOR "${CMAKE_MATCH_1}" CACHE INTERNAL "" FORCE) set(Rust_CARGO_VERSION_MINOR "${CMAKE_MATCH_2}" CACHE INTERNAL "" FORCE) set(Rust_CARGO_VERSION_PATCH "${CMAKE_MATCH_3}" CACHE INTERNAL "" FORCE) set(Rust_CARGO_VERSION "${Rust_CARGO_VERSION_MAJOR}.${Rust_CARGO_VERSION_MINOR}.${Rust_CARGO_VERSION_PATCH}" CACHE INTERNAL "" FORCE) # Workaround for the version strings where the `cargo ` prefix is missing. elseif(_CARGO_VERSION_RAW MATCHES "([0-9]+)\\.([0-9]+)\\.([0-9]+)") set(Rust_CARGO_VERSION_MAJOR "${CMAKE_MATCH_1}" CACHE INTERNAL "" FORCE) set(Rust_CARGO_VERSION_MINOR "${CMAKE_MATCH_2}" CACHE INTERNAL "" FORCE) set(Rust_CARGO_VERSION_PATCH "${CMAKE_MATCH_3}" CACHE INTERNAL "" FORCE) set(Rust_CARGO_VERSION "${Rust_CARGO_VERSION_MAJOR}.${Rust_CARGO_VERSION_MINOR}.${Rust_CARGO_VERSION_PATCH}" CACHE INTERNAL "" FORCE) else() _findrust_failed( "Failed to parse cargo version. `cargo --version` evaluated to (${_CARGO_VERSION_RAW}). " "Expected a .. version triple." ) endif() execute_process( COMMAND "${Rust_COMPILER_CACHED}" --version --verbose OUTPUT_VARIABLE _RUSTC_VERSION_RAW RESULT_VARIABLE _RUSTC_VERSION_RESULT ) if(NOT ( "${_RUSTC_VERSION_RESULT}" EQUAL "0" )) _findrust_failed("Failed to get rustc version.\n" "${Rust_COMPILER_CACHED} --version failed with error: `${_RUSTC_VERSION_RESULT}`") endif() if (_RUSTC_VERSION_RAW MATCHES "rustc ([0-9]+)\\.([0-9]+)\\.([0-9]+)(-nightly)?") set(Rust_VERSION_MAJOR "${CMAKE_MATCH_1}" CACHE INTERNAL "" FORCE) set(Rust_VERSION_MINOR "${CMAKE_MATCH_2}" CACHE INTERNAL "" FORCE) set(Rust_VERSION_PATCH "${CMAKE_MATCH_3}" CACHE INTERNAL "" FORCE) set(Rust_VERSION "${Rust_VERSION_MAJOR}.${Rust_VERSION_MINOR}.${Rust_VERSION_PATCH}" CACHE INTERNAL "" FORCE) if(CMAKE_MATCH_4) set(Rust_IS_NIGHTLY 1 CACHE INTERNAL "" FORCE) else() set(Rust_IS_NIGHTLY 0 CACHE INTERNAL "" FORCE) endif() else() _findrust_failed("Failed to parse rustc version. `${Rust_COMPILER_CACHED} --version --verbose` " "evaluated to:\n`${_RUSTC_VERSION_RAW}`" ) endif() if (_RUSTC_VERSION_RAW MATCHES "host: ([a-zA-Z0-9_\\-]*)\n") set(Rust_DEFAULT_HOST_TARGET "${CMAKE_MATCH_1}") set(Rust_CARGO_HOST_TARGET_CACHED "${Rust_DEFAULT_HOST_TARGET}" CACHE STRING "Host triple") else() _findrust_failed( "Failed to parse rustc host target. `rustc --version --verbose` evaluated to:\n${_RUSTC_VERSION_RAW}" ) endif() if (_RUSTC_VERSION_RAW MATCHES "LLVM version: ([0-9]+)\\.([0-9]+)(\\.([0-9]+))?") set(Rust_LLVM_VERSION_MAJOR "${CMAKE_MATCH_1}" CACHE INTERNAL "" FORCE) set(Rust_LLVM_VERSION_MINOR "${CMAKE_MATCH_2}" CACHE INTERNAL "" FORCE) # With the Rust toolchain 1.44.1 the reported LLVM version is 9.0, i.e. without a patch version. # Since cmake regex does not support non-capturing groups, just ignore Match 3. set(Rust_LLVM_VERSION_PATCH "${CMAKE_MATCH_4}" CACHE INTERNAL "" FORCE) set(Rust_LLVM_VERSION "${Rust_LLVM_VERSION_MAJOR}.${Rust_LLVM_VERSION_MINOR}.${Rust_LLVM_VERSION_PATCH}" CACHE INTERNAL "" FORCE) elseif(NOT Rust_FIND_QUIETLY) message( WARNING "Failed to parse rustc LLVM version. `rustc --version --verbose` evaluated to:\n${_RUSTC_VERSION_RAW}" ) endif() if (NOT Rust_CARGO_TARGET_CACHED) unset(_CARGO_ARCH) unset(_CARGO_ABI) if (WIN32) if (CMAKE_VS_PLATFORM_NAME) string(TOLOWER "${CMAKE_VS_PLATFORM_NAME}" LOWER_VS_PLATFORM_NAME) if ("${LOWER_VS_PLATFORM_NAME}" STREQUAL "win32") set(_CARGO_ARCH i686) elseif("${LOWER_VS_PLATFORM_NAME}" STREQUAL "x64") set(_CARGO_ARCH x86_64) elseif("${LOWER_VS_PLATFORM_NAME}" STREQUAL "arm64") set(_CARGO_ARCH aarch64) else() message(WARNING "VS Platform '${CMAKE_VS_PLATFORM_NAME}' not recognized") endif() endif() # Fallback path if(NOT DEFINED _CARGO_ARCH) # Possible values for windows when not cross-compiling taken from here: # https://learn.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details # When cross-compiling the user is expected to supply the value, so we match more variants. if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64)$") set(_CARGO_ARCH x86_64) elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64)$") set(_CARGO_ARCH aarch64) elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(X86|x86|i686)$") set(_CARGO_ARCH i686) elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "i586") set(_CARGO_ARCH i586) elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "IA64") message(FATAL_ERROR "No rust target for Intel Itanium.") elseif(NOT "${CMAKE_SYSTEM_PROCESSOR}") message(WARNING "Failed to detect target architecture. Please set `CMAKE_SYSTEM_PROCESSOR`" " to your target architecture or set `Rust_CARGO_TARGET` to your cargo target triple." ) else() message(WARNING "Failed to detect target architecture. Please set " "`Rust_CARGO_TARGET` to your cargo target triple." ) endif() endif() set(_CARGO_VENDOR "pc-windows") # The MSVC Generators will always target the msvc ABI. # For other generators we check the compiler ID and compiler target (if present) # If no compiler is set and we are not cross-compiling then we just choose the # default rust host target. if(DEFINED MSVC OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC" OR "${CMAKE_CXX_COMPILER_TARGET}" MATCHES "-msvc$" OR "${CMAKE_C_COMPILER_TARGET}" MATCHES "-msvc$" ) set(_CARGO_ABI msvc) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR (NOT CMAKE_CROSSCOMPILING AND NOT DEFINED CMAKE_CXX_COMPILER_ID AND NOT DEFINED CMAKE_C_COMPILER_ID AND "${Rust_DEFAULT_HOST_TARGET}" MATCHES "-gnu$" ) ) set(_CARGO_ABI gnu) elseif(("${CMAKE_C_COMPILER_ID}" MATCHES "Clang$" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang$") AND ("${CMAKE_CXX_COMPILER_TARGET}" MATCHES "-gnu(llvm)?$" OR "${CMAKE_C_COMPILER_TARGET}" MATCHES "-gnu(llvm)?$") ) if("${Rust_VERSION}" VERSION_GREATER_EQUAL "1.79") set(_CARGO_ABI gnullvm) else() message(WARNING "Your selected C/C++ compilers suggest you want to use the -gnullvm" " rust targets, however your Rust compiler version is ${Rust_VERSION}, which is" " before the promotion of the gnullvm target to tier2." " Please either use a more recent rust compiler or manually choose a target " " triple by specifying `Rust_CARGO_TARGET` manually." ) endif() elseif(NOT "${CMAKE_CROSSCOMPILING}" AND "${Rust_DEFAULT_HOST_TARGET}" MATCHES "-msvc$") # We first check if the gnu branches match to ensure this fallback is only used # if no compiler is enabled. set(_CARGO_ABI msvc) else() message(WARNING "Could not determine the target ABI. Please specify `Rust_CARGO_TARGET` manually.") endif() if(DEFINED _CARGO_ARCH AND DEFINED _CARGO_VENDOR AND DEFINED _CARGO_ABI) set(Rust_CARGO_TARGET_CACHED "${_CARGO_ARCH}-${_CARGO_VENDOR}-${_CARGO_ABI}" CACHE STRING "Target triple") endif() elseif (ANDROID) if (CMAKE_ANDROID_ARCH_ABI STREQUAL armeabi-v7a) if (CMAKE_ANDROID_ARM_MODE) set(_Rust_ANDROID_TARGET armv7-linux-androideabi) else () set(_Rust_ANDROID_TARGET thumbv7neon-linux-androideabi) endif() elseif (CMAKE_ANDROID_ARCH_ABI STREQUAL arm64-v8a) set(_Rust_ANDROID_TARGET aarch64-linux-android) elseif (CMAKE_ANDROID_ARCH_ABI STREQUAL x86) set(_Rust_ANDROID_TARGET i686-linux-android) elseif (CMAKE_ANDROID_ARCH_ABI STREQUAL x86_64) set(_Rust_ANDROID_TARGET x86_64-linux-android) endif() if (_Rust_ANDROID_TARGET) set(Rust_CARGO_TARGET_CACHED "${_Rust_ANDROID_TARGET}" CACHE STRING "Target triple") endif() elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "OHOS") if(CMAKE_OHOS_ARCH_ABI STREQUAL arm64-v8a) set(_RUST_OHOS_TARGET aarch64-unknown-linux-ohos) elseif(CMAKE_OHOS_ARCH_ABI STREQUAL armeabi-v7a) set(_RUST_OHOS_TARGET armv7-unknown-linux-ohos) elseif(CMAKE_OHOS_ARCH_ABI STREQUAL x86_64) set(_RUST_OHOS_TARGET x86_64-unknown-linux-ohos) else() message(WARNING "unrecognized OHOS architecture: ${OHOS_ARCH}") endif() if(_RUST_OHOS_TARGET) set(Rust_CARGO_TARGET_CACHED "${_RUST_OHOS_TARGET}" CACHE STRING "Target triple") endif() endif() # Fallback to the default host target if(NOT Rust_CARGO_TARGET_CACHED) if(CMAKE_CROSSCOMPILING) message(WARNING "CMake is in cross-compiling mode, but the cargo target-triple could not be inferred." "Falling back to the default host target. Please consider manually setting `Rust_CARGO_TARGET`." ) endif() set(Rust_CARGO_TARGET_CACHED "${Rust_DEFAULT_HOST_TARGET}" CACHE STRING "Target triple") endif() message(STATUS "Rust Target: ${Rust_CARGO_TARGET_CACHED}") endif() if(Rust_TOOLCHAIN_IS_RUSTUP_MANAGED) execute_process(COMMAND rustup target list --toolchain "${Rust_TOOLCHAIN}" OUTPUT_VARIABLE AVAILABLE_TARGETS_RAW ) string(REPLACE "\n" ";" AVAILABLE_TARGETS_RAW "${AVAILABLE_TARGETS_RAW}") string(REPLACE " (installed)" "" "AVAILABLE_TARGETS" "${AVAILABLE_TARGETS_RAW}") set(INSTALLED_TARGETS_RAW "${AVAILABLE_TARGETS_RAW}") list(FILTER INSTALLED_TARGETS_RAW INCLUDE REGEX " \\(installed\\)") string(REPLACE " (installed)" "" "INSTALLED_TARGETS" "${INSTALLED_TARGETS_RAW}") list(TRANSFORM INSTALLED_TARGETS STRIP) if("${Rust_CARGO_TARGET_CACHED}" IN_LIST AVAILABLE_TARGETS) message(DEBUG "Cargo target ${Rust_CARGO_TARGET} is an official target-triple") message(DEBUG "Installed targets: ${INSTALLED_TARGETS}") if(NOT ("${Rust_CARGO_TARGET_CACHED}" IN_LIST INSTALLED_TARGETS)) if(Rust_RUSTUP_INSTALL_MISSING_TARGET) message(STATUS "Cargo target ${Rust_CARGO_TARGET_CACHED} is not installed. Installing via rustup.") execute_process(COMMAND "${Rust_RUSTUP}" target add --toolchain ${Rust_TOOLCHAIN} ${Rust_CARGO_TARGET_CACHED} RESULT_VARIABLE target_add_result ) if(NOT "${target_add_result}" EQUAL "0") message(FATAL_ERROR "Target ${Rust_CARGO_TARGET_CACHED} is not installed for toolchain " "${Rust_TOOLCHAIN} and automatically installing failed with ${target_add_result}.\n" "You can try to manually install by running\n" "`rustup target add --toolchain ${Rust_TOOLCHAIN} ${Rust_CARGO_TARGET}`." ) endif() message(STATUS "Installed target ${Rust_CARGO_TARGET_CACHED} successfully.") else() message(FATAL_ERROR "Target ${Rust_CARGO_TARGET_CACHED} is not installed for toolchain ${Rust_TOOLCHAIN}.\n" "Help: Run `rustup target add --toolchain ${Rust_TOOLCHAIN} ${Rust_CARGO_TARGET_CACHED}` to install " "the missing target or configure corrosion with `Rust_RUSTUP_INSTALL_MISSING_TARGET=ON`." ) endif() endif() endif() endif() if(Rust_CARGO_TARGET_CACHED STREQUAL Rust_DEFAULT_HOST_TARGET) set(Rust_CROSSCOMPILING FALSE CACHE INTERNAL "Rust is configured for cross-compiling") else() set(Rust_CROSSCOMPILING TRUE CACHE INTERNAL "Rust is configured for cross-compiling") endif() _corrosion_parse_target_triple("${Rust_CARGO_TARGET_CACHED}" rust_arch rust_vendor rust_os rust_env) _corrosion_parse_target_triple("${Rust_CARGO_HOST_TARGET_CACHED}" rust_host_arch rust_host_vendor rust_host_os rust_host_env) set(Rust_CARGO_TARGET_ARCH "${rust_arch}" CACHE INTERNAL "Target architecture") set(Rust_CARGO_TARGET_VENDOR "${rust_vendor}" CACHE INTERNAL "Target vendor") set(Rust_CARGO_TARGET_OS "${rust_os}" CACHE INTERNAL "Target Operating System") set(Rust_CARGO_TARGET_ENV "${rust_env}" CACHE INTERNAL "Target environment") set(Rust_CARGO_HOST_ARCH "${rust_host_arch}" CACHE INTERNAL "Host architecture") set(Rust_CARGO_HOST_VENDOR "${rust_host_vendor}" CACHE INTERNAL "Host vendor") set(Rust_CARGO_HOST_OS "${rust_host_os}" CACHE INTERNAL "Host Operating System") set(Rust_CARGO_HOST_ENV "${rust_host_env}" CACHE INTERNAL "Host environment") if(NOT DEFINED CACHE{Rust_CARGO_TARGET_LINK_NATIVE_LIBS}) message(STATUS "Determining required link libraries for target ${Rust_CARGO_TARGET_CACHED}") unset(required_native_libs) _corrosion_determine_libs_new("${Rust_CARGO_TARGET_CACHED}" required_native_libs required_link_flags) if(DEFINED required_native_libs) message(STATUS "Required static libs for target ${Rust_CARGO_TARGET_CACHED}: ${required_native_libs}" ) endif() if(DEFINED required_link_flags) message(STATUS "Required link flags for target ${Rust_CARGO_TARGET_CACHED}: ${required_link_flags}" ) endif() # In very recent corrosion versions it is possible to override the rust compiler version # per target, so to be totally correct we would need to determine the libraries for # every installed Rust version, that the user could choose from. # In practice there aren't likely going to be any major differences, so we just do it once # for the target and once for the host target (if cross-compiling). set(Rust_CARGO_TARGET_LINK_NATIVE_LIBS "${required_native_libs}" CACHE INTERNAL "Required native libraries when linking Rust static libraries") set(Rust_CARGO_TARGET_LINK_OPTIONS "${required_link_flags}" CACHE INTERNAL "Required link flags when linking Rust static libraries") endif() if(Rust_CROSSCOMPILING AND NOT DEFINED CACHE{Rust_CARGO_HOST_TARGET_LINK_NATIVE_LIBS}) message(STATUS "Determining required link libraries for target ${Rust_CARGO_HOST_TARGET_CACHED}") unset(host_libs) _corrosion_determine_libs_new("${Rust_CARGO_HOST_TARGET_CACHED}" host_libs host_flags) if(DEFINED host_libs) message(STATUS "Required static libs for host target ${Rust_CARGO_HOST_TARGET_CACHED}: ${host_libs}" ) endif() set(Rust_CARGO_HOST_TARGET_LINK_NATIVE_LIBS "${host_libs}" CACHE INTERNAL "Required native libraries when linking Rust static libraries for the host target") set(Rust_CARGO_HOST_TARGET_LINK_OPTIONS "${host_flags}" CACHE INTERNAL "Required linker flags when linking Rust static libraries for the host target") endif() # Set the input variables as non-cache variables so that the variables are available after # `find_package`, even if the values were evaluated to defaults. foreach(_VAR ${_Rust_USER_VARS}) set(${_VAR} "${${_VAR}_CACHED}") # Ensure cached variables have type INTERNAL set(${_VAR}_CACHED "${${_VAR}_CACHED}" CACHE INTERNAL "Internal cache of ${_VAR}") endforeach() find_package_handle_standard_args( Rust REQUIRED_VARS Rust_COMPILER Rust_VERSION Rust_CARGO Rust_CARGO_VERSION Rust_CARGO_TARGET Rust_CARGO_HOST_TARGET VERSION_VAR Rust_VERSION ) if(NOT TARGET Rust::Rustc) add_executable(Rust::Rustc IMPORTED GLOBAL) set_property( TARGET Rust::Rustc PROPERTY IMPORTED_LOCATION "${Rust_COMPILER_CACHED}" ) add_executable(Rust::Cargo IMPORTED GLOBAL) set_property( TARGET Rust::Cargo PROPERTY IMPORTED_LOCATION "${Rust_CARGO_CACHED}" ) set(Rust_FOUND true) endif() list(POP_BACK CMAKE_MESSAGE_CONTEXT)