sapling/CMake/RustStaticLibrary.cmake
Wez Furlong b4f725330e eden: regenerate Cargo.lock on each cargo invocation
Summary:
When using our vendored set of crates, cmake doesn't
have any dependency information to use to invalidate the Cargo.lock
file when we update crate versions.  In addition, since we're
vendoring from a local directory, cargo itself doesn't seem to
want to re-assess the dependencies in that same situation, leading
to confusing error messages like this when we want to build rust
targets:

```
error: failed to select a version for the requirement `anyhow = "= 1.0.26"`
  candidate versions found which didn't match: 1.0.28
```

This commit addresses this issue by removing the `Cargo.lock` that
may be alongside the `Cargo.toml` prior to invoking `cargo`.
`cargo` is pretty quick at recomputing the deps so this has
neglible overhead.

Reviewed By: xavierd

Differential Revision: D21394363

fbshipit-source-id: 547db2e2395a47aed77d9597e659eb2d96e274dd
2020-05-04 18:37:36 -07:00

269 lines
7.9 KiB
CMake

include(FBCMakeParseArgs)
set(
USE_CARGO_VENDOR AUTO CACHE STRING
"Download Rust Crates from an internally vendored location"
)
set_property(CACHE USE_CARGO_VENDOR PROPERTY STRINGS AUTO ON OFF)
set(RUST_VENDORED_CRATES_DIR "$ENV{RUST_VENDORED_CRATES_DIR}")
if("${USE_CARGO_VENDOR}" STREQUAL "AUTO")
if(EXISTS "${RUST_VENDORED_CRATES_DIR}")
set(USE_CARGO_VENDOR ON)
else()
set(USE_CARGO_VENDOR OFF)
endif()
endif()
if(USE_CARGO_VENDOR)
if(NOT EXISTS "${RUST_VENDORED_CRATES_DIR}")
message(
FATAL "vendored rust crates not present: "
"${RUST_VENDORED_CRATES_DIR}"
)
endif()
set(RUST_CARGO_HOME "${CMAKE_BINARY_DIR}/_cargo_home")
file(MAKE_DIRECTORY "${RUST_CARGO_HOME}")
file(
TO_NATIVE_PATH "${RUST_VENDORED_CRATES_DIR}"
ESCAPED_RUST_VENDORED_CRATES_DIR
)
string(
REPLACE "\\" "\\\\"
ESCAPED_RUST_VENDORED_CRATES_DIR
"${ESCAPED_RUST_VENDORED_CRATES_DIR}"
)
file(
WRITE "${RUST_CARGO_HOME}/config"
"[source.crates-io]\n"
"replace-with = \"vendored-sources\"\n"
"\n"
"[source.vendored-sources]\n"
"directory = \"${ESCAPED_RUST_VENDORED_CRATES_DIR}\"\n"
)
endif()
# This function creates an interface library target based on the static library
# built by Cargo. It will call Cargo to build a staticlib and generate a CMake
# interface library with it.
#
# This function requires `find_package(Python COMPONENTS Interpreter)`.
#
# You need to set `lib:crate-type = ["staticlib"]` in your Cargo.toml to make
# Cargo build static library.
#
# ```cmake
# rust_static_library(<TARGET> [CRATE <CRATE_NAME>])
# ```
#
# Parameters:
# - TARGET:
# Name of the target name. This function will create an interface library
# target with this name.
# - CRATE_NAME:
# Name of the crate. This parameter is optional. If unspecified, it will
# fallback to `${TARGET}`.
#
# This function creates two targets:
# - "${TARGET}": an interface library target contains the static library built
# from Cargo.
# - "${TARGET}.cargo": an internal custom target that invokes Cargo.
#
# If you are going to use this static library from C/C++, you will need to
# write header files for the library (or generate with cbindgen) and bind these
# headers with the interface library.
#
function(rust_static_library TARGET)
fb_cmake_parse_args(ARG "" "CRATE" "" "${ARGN}")
if(DEFINED ARG_CRATE)
set(crate_name "${ARG_CRATE}")
else()
set(crate_name "${TARGET}")
endif()
set(cargo_target "${TARGET}.cargo")
set(target_dir $<IF:$<CONFIG:Debug>,debug,release>)
set(staticlib_name "${CMAKE_STATIC_LIBRARY_PREFIX}${crate_name}${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(rust_staticlib "${CMAKE_CURRENT_BINARY_DIR}/${target_dir}/${staticlib_name}")
set(cargo_cmd cargo)
if(WIN32)
set(cargo_cmd cargo.bat)
endif()
set(cargo_flags build $<IF:$<CONFIG:Debug>,,--release> -p ${crate_name})
if(USE_CARGO_VENDOR)
set(extra_cargo_env "CARGO_HOME=${RUST_CARGO_HOME}")
set(cargo_flags ${cargo_flags} --offline)
endif()
add_custom_target(
${cargo_target}
COMMAND
"${CMAKE_COMMAND}" -E remove -f "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.lock"
COMMAND
"${CMAKE_COMMAND}" -E env
"CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}"
${extra_cargo_env}
${cargo_cmd}
${cargo_flags}
COMMENT "Building Rust crate '${crate_name}'..."
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
BYPRODUCTS
"${CMAKE_CURRENT_BINARY_DIR}/debug/${staticlib_name}"
"${CMAKE_CURRENT_BINARY_DIR}/release/${staticlib_name}"
)
add_library(${TARGET} INTERFACE)
add_dependencies(${TARGET} ${cargo_target})
set_target_properties(
${TARGET}
PROPERTIES
INTERFACE_STATICLIB_OUTPUT_PATH "${rust_staticlib}"
INTERFACE_INSTALL_LIBNAME
"${CMAKE_STATIC_LIBRARY_PREFIX}${crate_name}_rs${CMAKE_STATIC_LIBRARY_SUFFIX}"
)
target_link_libraries(
${TARGET}
INTERFACE "$<BUILD_INTERFACE:${rust_staticlib}>"
)
endfunction()
# This function instructs cmake to define a target that will use `cargo build`
# to build a bin crate referenced by the Cargo.toml file in the current source
# directory.
# It accepts a single `TARGET` parameter which will be passed as the package
# name to `cargo build -p TARGET`.
# The cmake target will be registered to build by default as part of the
# ALL target.
function(rust_executable TARGET)
set(crate_name "${TARGET}")
set(cargo_target "${TARGET}.cargo")
set(target_dir $<IF:$<CONFIG:Debug>,debug,release>)
set(executable_name "${crate_name}${CMAKE_EXECUTABLE_SUFFIX}")
set(cargo_cmd cargo)
if(WIN32)
set(cargo_cmd cargo.bat)
endif()
set(cargo_flags build $<IF:$<CONFIG:Debug>,,--release> -p ${crate_name})
if(USE_CARGO_VENDOR)
set(extra_cargo_env "CARGO_HOME=${RUST_CARGO_HOME}")
set(cargo_flags ${cargo_flags} --offline)
endif()
add_custom_target(
${cargo_target}
ALL
COMMAND
"${CMAKE_COMMAND}" -E remove -f "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.lock"
COMMAND
"${CMAKE_COMMAND}" -E env
"CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}"
${extra_cargo_env}
${cargo_cmd}
${cargo_flags}
COMMENT "Building Rust executable '${crate_name}'..."
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
BYPRODUCTS
"${CMAKE_CURRENT_BINARY_DIR}/debug/${executable_name}"
"${CMAKE_CURRENT_BINARY_DIR}/release/${executable_name}"
)
set_property(TARGET "${cargo_target}"
PROPERTY EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/${target_dir}/${executable_name}")
endfunction()
# This function can be used to install the executable generated by a prior
# call to the `rust_executable` function.
# It requires a `TARGET` parameter to identify the target to be installed,
# and an optional `DESTINATION` parameter to specify the installation
# directory. If DESTINATION is not specified then the `bin` directory
# will be assumed.
function(install_rust_executable TARGET)
# Parse the arguments
set(one_value_args DESTINATION)
set(multi_value_args)
fb_cmake_parse_args(
ARG "" "${one_value_args}" "${multi_value_args}" "${ARGN}"
)
if(NOT DEFINED ARG_DESTINATION)
set(ARG_DESTINATION bin)
endif()
get_target_property(foo "${TARGET}.cargo" EXECUTABLE)
install(
PROGRAMS "${foo}"
DESTINATION "${ARG_DESTINATION}"
)
endfunction()
# This function installs the interface target generated from the function
# `rust_static_library`. Use this function if you want to export your Rust
# target to external CMake targets.
#
# ```cmake
# install_rust_static_library(
# <TARGET>
# INSTALL_DIR <INSTALL_DIR>
# [EXPORT <EXPORT_NAME>]
# )
# ```
#
# Parameters:
# - TARGET: Name of the Rust static library target.
# - EXPORT_NAME: Name of the exported target.
# - INSTALL_DIR: Path to the directory where this library will be installed.
#
function(install_rust_static_library TARGET)
fb_cmake_parse_args(ARG "" "EXPORT;INSTALL_DIR" "" "${ARGN}")
get_property(
staticlib_output_path
TARGET "${TARGET}"
PROPERTY INTERFACE_STATICLIB_OUTPUT_PATH
)
get_property(
staticlib_output_name
TARGET "${TARGET}"
PROPERTY INTERFACE_INSTALL_LIBNAME
)
if(NOT DEFINED staticlib_output_path)
message(FATAL_ERROR "Not a rust_static_library target.")
endif()
if(NOT DEFINED ARG_INSTALL_DIR)
message(FATAL_ERROR "Missing required argument.")
endif()
if(DEFINED ARG_EXPORT)
set(install_export_args EXPORT "${ARG_EXPORT}")
endif()
set(install_interface_dir "${ARG_INSTALL_DIR}")
if(NOT IS_ABSOLUTE "${install_interface_dir}")
set(install_interface_dir "\${_IMPORT_PREFIX}/${install_interface_dir}")
endif()
target_link_libraries(
${TARGET} INTERFACE
"$<INSTALL_INTERFACE:${install_interface_dir}/${staticlib_output_name}>"
)
install(
TARGETS ${TARGET}
${install_export_args}
LIBRARY DESTINATION ${ARG_INSTALL_DIR}
)
install(
FILES ${staticlib_output_path}
RENAME ${staticlib_output_name}
DESTINATION ${ARG_INSTALL_DIR}
)
endfunction()