diff --git a/CMakeLists.txt b/CMakeLists.txt index 59866fcc9..3163a3314 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,23 +78,12 @@ endif() # Enable use of pkg-config to find depenencies. find_package(PkgConfig QUIET) -# -# Find Build Tools -# - -if(MAINTAINER_MODE) - # Bison, Flex required to build Yara module for libclamav. - find_package(BISON REQUIRED) - find_package(FLEX REQUIRED) - # TODO: Gperf required to generate JS-normalization code. - # find_package(GPERF REQUIRED) -endif() - # # Load Build Options # # CMake Option default values: +set(MAINTAINER_MODE_DEFAULT OFF) set(ENABLE_APP_DEFAULT ON) if(WIN32 OR APPLE) set(ENABLE_MILTER_DEFAULT OFF) @@ -120,6 +109,25 @@ set(ENABLE_SYSTEMD_DEFAULT ON) # See CMakeOptions.cmake for additional options. include(CMakeOptions.cmake) +# +# Find Build Tools +# +if(MAINTAINER_MODE) + # cbindgen required to update C headers for Rust libraries. + set(cbindgen_REQUIRED 1) + set(CBINDGEN ON) + + # Bison, Flex required to build Yara module for libclamav. + find_package(BISON REQUIRED) + find_package(FLEX REQUIRED) + + # TODO: Gperf required to generate JS-normalization code. + # find_package(GPERF REQUIRED) +else() + set(CBINDGEN OFF) +endif() +find_package(Rust REQUIRED) + if(ENABLE_FUZZ) # We'd like the fuzz targets to be statically linked set(ENABLE_STATIC_LIB ON) @@ -1059,6 +1067,7 @@ ${c} Compiler: ${e} ${b} Build type: ${e}${CMAKE_BUILD_TYPE} ${b} C compiler: ${e}${CMAKE_C_COMPILER} ${b} C++ compiler: ${e}${CMAKE_CXX_COMPILER} +${b} Rust toolchain: ${e}${cargo_EXECUTABLE} (${cargo_VERSION}) ${b} CFLAGS: ${e}${CMAKE_C_FLAGS_${_build_type}} ${CMAKE_C_FLAGS} ${b} CXXFLAGS: ${e}${CMAKE_CXX_FLAGS_${_build_type}} ${CMAKE_CXX_FLAGS} ${b} WARNCFLAGS: ${e}${WARNCFLAGS}") @@ -1079,7 +1088,15 @@ ${b} Enable UnRAR: ${e}${ENABLE_UNRAR} ${b} Examples: ${e}${ENABLE_EXAMPLES} ${b} Tests: ${e}${ENABLE_TESTS} ${b} Build man pages: ${e}${ENABLE_MAN_PAGES} -${b} Build doxygen HTML: ${e}${ENABLE_DOXYGEN}") +${b} Build doxygen HTML: ${e}${ENABLE_DOXYGEN} +${b} Maintainer Mode: ${e}${MAINTAINER_MODE}") +if(MAINTAINER_MODE) +message("\ +${R} Maintainer Mode Tools: ${e} +${y} cbindgen: ${e}${cbindgen_EXECUTABLE} (${cbindgen_VERSION}) +${y} bison: ${e}${BISON_EXECUTABLE} (${BISON_VERSION}) +${y} flex: ${e}${FLEX_EXECUTABLE} (${FLEX_VERSION})") +endif() if(NOT WIN32) message("\ ${c} Build Extras: ${e}") diff --git a/CMakeOptions.cmake b/CMakeOptions.cmake index 96b4a430d..a740a6ac3 100644 --- a/CMakeOptions.cmake +++ b/CMakeOptions.cmake @@ -34,6 +34,10 @@ option(OPTIMIZE "Allow compiler optimizations. Set to OFF to disable (i.e. to set -O0)." ON) +option(MAINTAINER_MODE + "Update generated sources. Requires flex, bison, cbindgen." + ${MAINTAINER_MODE_DEFAULT}) + option(ENABLE_WERROR "Compile time warnings will cause build failures.") diff --git a/cmake/FindRust.cmake b/cmake/FindRust.cmake new file mode 100644 index 000000000..a6070c217 --- /dev/null +++ b/cmake/FindRust.cmake @@ -0,0 +1,255 @@ +# Find the Rust toolchain and add the `add_rust_library()` API to build Rust +# libraries. +# +# Copyright (C) 2021 Cisco Systems, Inc. and/or its affiliates. All rights reserved. +# +# Author: Micah Snyder +# To see this in a sample project, visit: https://github.com/micahsnyder/cmake-rust-demo +# +# Code to set the Cargo arguments was lifted from: +# https://github.com/Devolutions/CMakeRust +# +# This Module defines the following variables: +# - _FOUND - True if the program was found +# - _EXECUTABLE - path of the program +# - _VERSION - version number of the program +# +# ... for the following Rust toolchain programs: +# - cargo +# - rustc +# - rustup +# - rust-gdb +# - rust-lldb +# - rustdoc +# - rustfmt +# - bindgen +# - cbindgen +# +# Note that `cbindgen` is presently 3rd-party, and is not included with the +# standard Rust installation. `bindgen` is a part of the rust toolchain, but +# might need to be installed separately. +# +# Callers can make any program mandatory by setting `_REQUIRED` before +# the call to `find_package(Rust)` +# +# Eg: +# +# if(MAINTAINER_MODE) +# set(cbindgen_REQUIRED 1) +# set(bindgen_REQUIRED 1) +# endif() +# find_package(Rust REQUIRED) +# +# This module also provides an `add_rust_library()` function which allows a +# caller to create a Rust static library target which you can link to with +# `target_link_libraries()`. +# +# Your Rust static library target will itself depend on the native static libs +# you get from `rustc --crate-type staticlib --print=native-static-libs /dev/null` +# +# Example `add_rust_library()` usage: +# +# add_rust_library(TARGET yourlib WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") +# add_library(YourProject::yourlib ALIAS yourlib) +# +# add_executable(yourexe) +# target_link_libraries(yourexe YourProject::yourlib) +# + +if(NOT DEFINED CARGO_HOME) + if(WIN32) + set(CARGO_HOME "$ENV{USERPROFILE}/.cargo") + else() + set(CARGO_HOME "$ENV{HOME}/.cargo") + endif() +endif() + +include(FindPackageHandleStandardArgs) + +function(find_rust_program RUST_PROGRAM) + find_program(${RUST_PROGRAM}_EXECUTABLE ${RUST_PROGRAM} + HINTS "${CARGO_HOME}" + PATH_SUFFIXES "bin" + ) + + if(${RUST_PROGRAM}_EXECUTABLE) + execute_process(COMMAND "${${RUST_PROGRAM}_EXECUTABLE}" --version + OUTPUT_VARIABLE ${RUST_PROGRAM}_VERSION_OUTPUT + ERROR_VARIABLE ${RUST_PROGRAM}_VERSION_ERROR + RESULT_VARIABLE ${RUST_PROGRAM}_VERSION_RESULT + ) + if(NOT ${${RUST_PROGRAM}_VERSION_RESULT} EQUAL 0) + message(STATUS "Rust tool `${RUST_PROGRAM}` not found: Failed to determine version.") + unset(${RUST_PROGRAM}_EXECUTABLE) + else() + string(REGEX + MATCH "[0-9]+\\.[0-9]+(\\.[0-9]+)?(-nightly)?" + ${RUST_PROGRAM}_VERSION "${${RUST_PROGRAM}_VERSION_OUTPUT}" + ) + set(${RUST_PROGRAM}_VERSION "${${RUST_PROGRAM}_VERSION}" PARENT_SCOPE) + message(STATUS "Rust tool `${RUST_PROGRAM}` found: ${${RUST_PROGRAM}_EXECUTABLE}, ${${RUST_PROGRAM}_VERSION}") + endif() + + mark_as_advanced(${RUST_PROGRAM}_EXECUTABLE ${RUST_PROGRAM}_VERSION) + else() + if(${${RUST_PROGRAM}_REQUIRED}) + message(FATAL_ERROR "Rust tool `${RUST_PROGRAM}` not found.") + else() + message(STATUS "Rust tool `${RUST_PROGRAM}` not found.") + endif() + endif() +endfunction() + +function(add_rust_library) + set(options) + set(oneValueArgs TARGET WORKING_DIRECTORY) + cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(WIN32) + set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${LIB_TARGET}/${LIB_BUILD_TYPE}/${ARGS_TARGET}.lib") + else() + set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${LIB_TARGET}/${LIB_BUILD_TYPE}/lib${ARGS_TARGET}.a") + endif() + + file(GLOB_RECURSE LIB_SOURCES "${ARGS_WORKING_DIRECTORY}/*.rs") + + set(MY_CARGO_ARGS ${CARGO_ARGS}) + list(APPEND MY_CARGO_ARGS "--target-dir" ${CMAKE_CURRENT_BINARY_DIR}) + list(JOIN MY_CARGO_ARGS " " MY_CARGO_ARGS_STRING) + + # Build the library and generate the c-binding, if `cbindgen` is required. + if(${cbindgen_REQUIRED}) + add_custom_command( + OUTPUT "${OUTPUT}" + COMMAND ${CMAKE_COMMAND} -E env "CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}" ${cargo_EXECUTABLE} ARGS ${MY_CARGO_ARGS} + COMMAND ${cbindgen_EXECUTABLE} --lang c -o ${ARGS_WORKING_DIRECTORY}/${ARGS_TARGET}.h ${ARGS_WORKING_DIRECTORY} + WORKING_DIRECTORY "${ARGS_WORKING_DIRECTORY}" + DEPENDS ${LIB_SOURCES} + COMMENT "Building ${ARGS_TARGET} in ${ARGS_WORKING_DIRECTORY} with:\n\t ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}") + else() + add_custom_command( + OUTPUT "${OUTPUT}" + COMMAND ${CMAKE_COMMAND} -E env "CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}" ${cargo_EXECUTABLE} ARGS ${MY_CARGO_ARGS} + WORKING_DIRECTORY "${ARGS_WORKING_DIRECTORY}" + DEPENDS ${LIB_SOURCES} + COMMENT "Building ${ARGS_TARGET} in ${ARGS_WORKING_DIRECTORY} with:\n\t ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}") + endif() + + # Create a target from the build output + add_custom_target(${ARGS_TARGET}_target + DEPENDS ${OUTPUT}) + + # Create a static imported library target from library target + add_library(${ARGS_TARGET} STATIC IMPORTED GLOBAL) + add_dependencies(${ARGS_TARGET} ${ARGS_TARGET}_target) + target_link_libraries(${ARGS_TARGET} INTERFACE ${RUST_NATIVE_STATIC_LIBS}) + + # Specify where the library is and where to find the headers + set_target_properties(${ARGS_TARGET} + PROPERTIES + IMPORTED_LOCATION "${OUTPUT}" + INTERFACE_INCLUDE_DIRECTORIES "${ARGS_WORKING_DIRECTORY}" + ) +endfunction() + +function(add_rust_test) + set(options) + set(oneValueArgs NAME WORKING_DIRECTORY) + cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + add_test( + NAME test-${ARGS_NAME} + COMMAND ${CMAKE_COMMAND} -E env "CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}" ${cargo_EXECUTABLE} test -vv --color always + WORKING_DIRECTORY ${ARGS_WORKING_DIRECTORY} + ) +endfunction() + +# +# Cargo is the primary tool for using the Rust Toolchain to to build static +# libs that can include other crate dependencies. +# +find_rust_program(cargo) + +# These other programs may also be useful... +find_rust_program(rustc) +find_rust_program(rustup) +find_rust_program(rust-gdb) +find_rust_program(rust-lldb) +find_rust_program(rustdoc) +find_rust_program(rustfmt) +find_rust_program(bindgen) +find_rust_program(cbindgen) + +# Determine the native libs required to link w/ rust static libs +# message(STATUS "Detecting native static libs for rust: ${rustc_EXECUTABLE} --crate-type staticlib --print=native-static-libs /dev/null") +execute_process( + COMMAND ${CMAKE_COMMAND} -E env "CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}" ${rustc_EXECUTABLE} --crate-type staticlib --print=native-static-libs /dev/null + OUTPUT_VARIABLE RUST_NATIVE_STATIC_LIBS_OUTPUT + ERROR_VARIABLE RUST_NATIVE_STATIC_LIBS_ERROR + RESULT_VARIABLE RUST_NATIVE_STATIC_LIBS_RESULT +) +string(REGEX REPLACE "\r?\n" ";" LINE_LIST "${RUST_NATIVE_STATIC_LIBS_ERROR}") +foreach(LINE ${LINE_LIST}) + # do the match on each line + string(REGEX MATCH "native-static-libs: .*" LINE "${LINE}") + if(NOT LINE) + continue() + endif() + string(REPLACE "native-static-libs: " "" LINE "${LINE}") + string(REGEX REPLACE " " "" LINE "${LINE}") + string(REGEX REPLACE " " ";" LINE "${LINE}") + if(LINE) + message(STATUS "Rust's native static libs: ${LINE}") + set(RUST_NATIVE_STATIC_LIBS "${LINE}") + break() + endif() +endforeach() + +if(WIN32) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(LIB_TARGET "x86_64-pc-windows-msvc") + else() + set(LIB_TARGET "i686-pc-windows-msvc") + endif() +elseif(ANDROID) + if(ANDROID_SYSROOT_ABI STREQUAL "x86") + set(LIB_TARGET "i686-linux-android") + elseif(ANDROID_SYSROOT_ABI STREQUAL "x86_64") + set(LIB_TARGET "x86_64-linux-android") + elseif(ANDROID_SYSROOT_ABI STREQUAL "arm") + set(LIB_TARGET "arm-linux-androideabi") + elseif(ANDROID_SYSROOT_ABI STREQUAL "arm64") + set(LIB_TARGET "aarch64-linux-android") + endif() +elseif(IOS) + set(LIB_TARGET "universal") +elseif(CMAKE_SYSTEM_NAME STREQUAL Darwin) + set(LIB_TARGET "x86_64-apple-darwin") +else() + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(LIB_TARGET "x86_64-unknown-linux-gnu") + else() + set(LIB_TARGET "i686-unknown-linux-gnu") + endif() +endif() + +if(IOS) + set(CARGO_ARGS "lipo") +else() + set(CARGO_ARGS "build") + list(APPEND CARGO_ARGS "--target" ${LIB_TARGET}) +endif() + +if(NOT CMAKE_BUILD_TYPE) + set(LIB_BUILD_TYPE "debug") +elseif(${CMAKE_BUILD_TYPE} STREQUAL "Release") + set(LIB_BUILD_TYPE "release") + list(APPEND CARGO_ARGS "--release") +else() + set(LIB_BUILD_TYPE "debug") +endif() + +find_package_handle_standard_args( Rust + REQUIRED_VARS cargo_EXECUTABLE + VERSION_VAR cargo_VERSION +)