From f8edb391ab6f7a95b1d30a9dd5f6197721c7d050 Mon Sep 17 00:00:00 2001 From: John Elliott Date: Fri, 10 Nov 2023 15:18:39 -0800 Subject: [PATCH] Add rust_cxx_bridge CMake function (#473) Summary: X-link: https://github.com/facebookincubator/zstrong/pull/617 X-link: https://github.com/facebookincubator/katran/pull/208 X-link: https://github.com/facebookincubator/fizz/pull/102 X-link: https://github.com/facebookexternal/traffixr/pull/4 X-link: https://github.com/facebook/watchman/pull/1173 X-link: https://github.com/facebook/hhvm/pull/9411 X-link: https://github.com/facebook/fbthrift/pull/587 X-link: https://github.com/facebookincubator/velox/pull/7518 We need a better way to create cxxbridges - something that uses the recommended method of cxxbridge-cmd. This function creates C++ bindings using the [cxx] crate. Original function found here: https://github.com/corrosion-rs/corrosion/blob/master/cmake/Corrosion.cmake#L1390 Differential Revision: D51160627 --- .../CMake/RustStaticLibrary.cmake | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/build/fbcode_builder/CMake/RustStaticLibrary.cmake b/build/fbcode_builder/CMake/RustStaticLibrary.cmake index bd1b5f96ac..7a5fc9f23d 100644 --- a/build/fbcode_builder/CMake/RustStaticLibrary.cmake +++ b/build/fbcode_builder/CMake/RustStaticLibrary.cmake @@ -324,3 +324,191 @@ function(install_rust_static_library TARGET) DESTINATION ${ARG_INSTALL_DIR} ) endfunction() + +# This function creates C++ bindings using the [cxx] crate. +# +# Original function found here: https://github.com/corrosion-rs/corrosion/blob/master/cmake/Corrosion.cmake#L1390 +# Simplified for use as part of RustStaticLibrary module. License below. +# +# MIT License +# +# Copyright (c) 2018 Andrew Gaspar +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# The rules approximately do the following: +# - Check which version of `cxx` the Rust crate depends on. +# - Check if the exact same version of `cxxbridge-cmd` is installed +# - If not, create a rule to build the exact same version of `cxxbridge-cmd`. +# - Create rules to run `cxxbridge` and generate +# - The `rust/cxx.h` header +# - A header and source file for the specified CXX_BRIDGE_FILE. +# - The generated sources (and header include directories) are added to the +# `${TARGET}` CMake library target. +# +# ```cmake +# rust_cxx_bridge( [CRATE ]) +# ``` +# +# Parameters: +# - TARGET: +# Name of the target name. The target that the bridge will be included with. +# - CXX_BRIDGE_FILE: +# Name of the file that include the cxxbridge (e.g., "src/ffi.rs"). +# - CRATE_NAME: +# Name of the crate. This parameter is optional. If unspecified, it will +# fallback to `${TARGET}`. +# +function(rust_cxx_bridge TARGET CXX_BRIDGE_FILE) + fb_cmake_parse_args(ARG "" "CRATE" "" "${ARGN}") + + if(DEFINED ARG_CRATE) + set(crate_name "${ARG_CRATE}") + else() + set(crate_name "${TARGET}") + endif() + + execute_process(COMMAND + ${CARGO_COMMAND} tree -i cxx --depth=0 + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE cxx_version_result + OUTPUT_VARIABLE cxx_version_output + ) + + if(NOT "${cxx_version_result}" EQUAL "0") + message(FATAL_ERROR "Crate ${crate_name} does not depend on cxx.") + endif() + if(cxx_version_output MATCHES "cxx v([0-9]+.[0-9]+.[0-9]+)") + set(cxx_required_version "${CMAKE_MATCH_1}") + else() + message( + FATAL_ERROR + "Failed to parse cxx version from cargo tree output: `cxx_version_output`") + endif() + + # First check if a suitable version of cxxbridge is installed + find_program(INSTALLED_CXXBRIDGE cxxbridge PATHS "$ENV{HOME}/.cargo/bin/") + mark_as_advanced(INSTALLED_CXXBRIDGE) + if(INSTALLED_CXXBRIDGE) + execute_process( + COMMAND ${INSTALLED_CXXBRIDGE} + --version OUTPUT_VARIABLE cxxbridge_version_output) + if(cxxbridge_version_output MATCHES "cxxbridge ([0-9]+.[0-9]+.[0-9]+)") + set(cxxbridge_version "${CMAKE_MATCH_1}") + else() + set(cxxbridge_version "") + endif() + endif() + + set(cxxbridge "") + if(cxxbridge_version) + if(cxxbridge_version VERSION_EQUAL cxx_required_version) + set(cxxbridge "${INSTALLED_CXXBRIDGE}") + if(NOT TARGET "cxxbridge_v${cxx_required_version}") + # Add an empty target. + add_custom_target("cxxbridge_v${cxx_required_version}") + endif() + endif() + endif() + + # No suitable version of cxxbridge was installed, + # so use custom target to install correct version. + if(NOT cxxbridge) + if(NOT TARGET "cxxbridge_v${cxx_required_version}") + add_custom_command( + OUTPUT + "${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}/bin/cxxbridge" + COMMAND + ${CMAKE_COMMAND} -E make_directory + "${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}" + COMMAND + ${CARGO_COMMAND} install cxxbridge-cmd + --version "${cxx_required_version}" + --root "${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}" + --quiet + # todo: use --target-dir to potentially reuse artifacts + COMMENT "Installing cxxbridge (version ${cxx_required_version})" + ) + add_custom_target( + "cxxbridge_v${cxx_required_version}" + DEPENDS + "${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}/bin/cxxbridge" + ) + endif() + set( + cxxbridge + "${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}/bin/cxxbridge") + endif() + + add_library(${crate_name} STATIC) + target_include_directories( + ${crate_name} + PUBLIC + $ + $ + ) + + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/rust") + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rust/cxx.h" + COMMAND + ${cxxbridge} + --header --output "${CMAKE_CURRENT_BINARY_DIR}/rust/cxx.h" + DEPENDS "cxxbridge_v${cxx_required_version}" + COMMENT "Generating rust/cxx.h header" + ) + + get_filename_component(filename_component ${CXX_BRIDGE_FILE} NAME) + get_filename_component(directory_component ${CXX_BRIDGE_FILE} DIRECTORY) + set(directory "") + if(directory_component) + set(directory "${directory_component}") + endif() + + set(cxx_header ${directory}/${filename_component}.h) + set(cxx_source ${directory}/${filename_component}.cc) + set(rust_source_path "${CMAKE_CURRENT_SOURCE_DIR}/${CXX_BRIDGE_FILE}") + + file( + MAKE_DIRECTORY + "${CMAKE_CURRENT_BINARY_DIR}/${directory_component}" + ) + + add_custom_command( + OUTPUT + "${CMAKE_CURRENT_BINARY_DIR}/${cxx_header}" + "${CMAKE_CURRENT_BINARY_DIR}/${cxx_source}" + COMMAND + ${cxxbridge} ${rust_source_path} + --header --output "${CMAKE_CURRENT_BINARY_DIR}/${cxx_header}" + COMMAND + ${cxxbridge} ${rust_source_path} + --output "${CMAKE_CURRENT_BINARY_DIR}/${cxx_source}" + --include "${cxx_header}" + DEPENDS "cxxbridge_v${cxx_required_version}" "${rust_source_path}" + COMMENT "Generating cxx bindings for crate ${crate_name}" + ) + + target_sources(${crate_name} + PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}/${cxx_header}" + "${CMAKE_CURRENT_BINARY_DIR}/rust/cxx.h" + "${CMAKE_CURRENT_BINARY_DIR}/${cxx_source}" + ) +endfunction()