From 2d6a5dd67f2fdc346e7ccba55643db30ecfd3a82 Mon Sep 17 00:00:00 2001 From: Julian Büning Date: Mon, 22 Oct 2018 15:42:49 +0200 Subject: CMake: enable reuse of LLVM's googletest src and targets Fixes an issue that occurs with USE_CMAKE_FIND_PACKAGE_LLVM=ON and LLVM compiled from sources, which then exports gtest and gtest_main targets. In case gtest and gtest_main targets are not imported from LLVM and GTEST_SRC_DIR is not set, CMake can now reuse the googletest sources from LLVM_BUILD_MAIN_SRC_DIR (if available) with USE_CMAKE_FIND_PACKAGE_LLVM=ON. This last limitation is due to LLVM making modifications to the CMakeLists.txt of googletest that requires add_llvm_library() from AddLLVM.cmake. --- README-CMake.md | 12 +++- cmake/find_llvm.cmake | 16 ++++- unittests/CMakeLists.txt | 162 +++++++++++++++++++++++++++++++++++------------ unittests/TestMain.cpp | 6 ++ 4 files changed, 150 insertions(+), 46 deletions(-) diff --git a/README-CMake.md b/README-CMake.md index 4deb8860..2687c86b 100644 --- a/README-CMake.md +++ b/README-CMake.md @@ -61,9 +61,11 @@ cmake -DCMAKE_BUILD_TYPE=Release /path/to/klee/src * `ENABLE_ZLIB` (BOOLEAN) - Enable zlib support. -* `GTEST_SRC_DIR` (STRING) - Path to GTest source tree. +* `GTEST_SRC_DIR` (STRING) - Path to Google Test source tree. If it is not + specified and `USE_CMAKE_FIND_PACKAGE_LLVM` is used, CMake will try to reuse + the version included within the LLVM source tree. -* `GTEST_INCLUDE_DIR` (STRING) - Path to GTest include directory, +* `GTEST_INCLUDE_DIR` (STRING) - Path to Google Test include directory, if it is not under `GTEST_SRC_DIR`. * `KLEE_ENABLE_TIMESTAMP` (BOOLEAN) - Enable timestamps in KLEE sources. @@ -81,6 +83,10 @@ cmake -DCMAKE_BUILD_TYPE=Release /path/to/klee/src only relevant if `USE_CMAKE_FIND_PACKAGE_LLVM` is `FALSE`. This is used to detect the LLVM version and find libraries. +* `LLVM_DIR` (STRING) - Path to `LLVMConfig.cmake`. This is only relevant if + `USE_CMAKE_FIND_PACKAGE_LLVM` is `TRUE`. This can be used to tell CMake where + it can find LLVM outside of standard directories. + * `MAKE_BINARY` (STRING) - Path to `make` binary used to build KLEE's runtime. * `metaSMT_DIR` (STRING) - Provides a hint to CMake, where the metaSMT constraint @@ -94,6 +100,6 @@ cmake -DCMAKE_BUILD_TYPE=Release /path/to/klee/src against STP in a build directory or an installed copy. * `USE_CMAKE_FIND_PACKAGE_LLVM` (BOOLEAN) - Use `find_package(LLVM CONFIG)` - to find LLVM. + to find LLVM (instead of using `llvm-config` with `LLVM_CONFIG_BINARY`). * `WARNINGS_AS_ERRORS` (BOOLEAN) - Treat warnings as errors when building KLEE. diff --git a/cmake/find_llvm.cmake b/cmake/find_llvm.cmake index 02c99960..874bd1ae 100644 --- a/cmake/find_llvm.cmake +++ b/cmake/find_llvm.cmake @@ -20,8 +20,14 @@ option(USE_CMAKE_FIND_PACKAGE_LLVM "Use find_package(LLVM CONFIG) to find LLVM" OFF) if (USE_CMAKE_FIND_PACKAGE_LLVM) + # Use find_package() to detect LLVM in the user's environment. + # The user can force a particular copy by passing + # `-DLLVM_DIR=/path/to/LLVMConfig.cmake` to CMake. find_package(LLVM CONFIG REQUIRED) + list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") + include(AddLLVM) + # Provide function to map LLVM components to libraries. function(klee_get_llvm_libs output_var) llvm_map_components_to_libnames(${output_var} ${ARGN}) @@ -31,9 +37,8 @@ if (USE_CMAKE_FIND_PACKAGE_LLVM) set(LLVM_ENABLE_VISIBILITY_INLINES_HIDDEN OFF) else() # Use the llvm-config binary to get the information needed. - # Try to detect it in the user's environment. The user can - # force a particular binary by passing `-DLLVM_CONFIG_BINARY=/path/to/llvm-config` - # to CMake. + # Try to detect it in the user's environment. The user can force a particular + # binary by passing `-DLLVM_CONFIG_BINARY=/path/to/llvm-config` to CMake. find_program(LLVM_CONFIG_BINARY NAMES llvm-config) message(STATUS "LLVM_CONFIG_BINARY: ${LLVM_CONFIG_BINARY}") @@ -142,6 +147,11 @@ else() _run_llvm_config(LLVM_TOOLS_BINARY_DIR "--bindir") _run_llvm_config(TARGET_TRIPLE "--host-target") + _run_llvm_config(LLVM_BUILD_MAIN_SRC_DIR "--src-root") + if (NOT EXISTS "${LLVM_BUILD_MAIN_SRC_DIR}") + set(LLVM_BUILD_MAIN_SRC_DIR "") + endif() + # Provide function to map LLVM components to libraries. function(klee_get_llvm_libs OUTPUT_VAR) _run_llvm_config(_llvm_libs "--libfiles" ${ARGN}) diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index d6e58183..ef052f9b 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -7,41 +7,128 @@ # #===------------------------------------------------------------------------===# -# Build GTest. We don't use a pre-built version due to -# https://github.com/google/googletest/blob/master/googletest/docs/FAQ.md#why-is-it-not-recommended-to-install-a-pre-compiled-copy-of-google-test-for-example-into-usrlocal -set(GTEST_SRC_DIR - "/usr/src/gtest" - CACHE - PATH - "Path to GTest source directory" -) +if (TARGET gtest AND TARGET gtest_main) + # try to reuse LLVM's targets + + message(WARNING "LLVM exports 'gtest' and 'gtest_main' targets (for Google " + "Test), so KLEE cannot create them. By default, KLEE will reuse " + "LLVM's 'gtest' and 'gtest_main' targets if they are available. This is, " + "however, only recommended if LLVM and KLEE were build with the same " + "compiler and linker flags to prevent any compatibility issues.\n" + "To prevent CMake from reusing the targets or to use a different version " + "of Google Test, try either of the following:\n" + "- Point LLVM_DIR to the directory containing the `LLVMConfig.cmake` file " + "of an installed copy of LLVM instead of a build tree.\n" + "- Pass -DLLVM_INCLUDE_TESTS=OFF to CMake when building LLVM. This " + "prevents building unit tests in LLVM (but not in KLEE) and exporting the " + "target to the build tree.") + + if (GTEST_SRC_DIR) + message(FATAL_ERROR "Cannot use GTEST_SRC_DIR when targets 'gtest' and " + "'gtest_main' are already defined.\n" + "Either reuse LLVM's Google Test setup by not setting GTEST_SRC_DIR or " + "choose one of the options to prevent LLVM from exporting these targets.") + endif() + + # check if it's really LLVM that exports them + list(FIND LLVM_EXPORTED_TARGETS "gtest" _GTEST_INDEX) + list(FIND LLVM_EXPORTED_TARGETS "test_main" _GTEST_MAIN_INDEX) + if (${_GTEST_INDEX} GREATER -1 AND ${_GTEST_MAIN_INDEX}) + message(STATUS "Google Test: Reusing LLVM's 'gtest' and 'gtest_main' targets.") + # in this case, only include directory has to be set + if (LLVM_BUILD_MAIN_SRC_DIR) + set(GTEST_INCLUDE_DIR + "${LLVM_BUILD_MAIN_SRC_DIR}/utils/unittest/googletest/include" + CACHE + PATH + "Path to Google Test include directory" + ) + endif() + else() + message(FATAL_ERROR "Reusing Google Test targets from LLVM failed:" + "LLVM_EXPORTED_TARGETS does not contain 'gtest' or 'gtest_main'.") + endif() +else() + # LLVM's targets are not reused + + if (NOT GTEST_SRC_DIR) + if (USE_CMAKE_FIND_PACKAGE_LLVM AND LLVM_BUILD_MAIN_SRC_DIR) + # build from LLVM's utils directory + # NOTE: This can only be done using USE_CMAKE_FIND_PACKAGE_LLVM as + # LLVM replaced Google Test's CMakeLists.txt with its own, + # requiring add_llvm_library() from AddLLVM.cmake. + message(STATUS "Google Test: Building from LLVM's source tree.") + + set(GTEST_INCLUDE_DIR + "${LLVM_BUILD_MAIN_SRC_DIR}/utils/unittest/googletest/include" + CACHE + PATH + "Path to Google Test include directory" + ) + + add_subdirectory("${LLVM_BUILD_MAIN_SRC_DIR}/utils/unittest/" + "${CMAKE_CURRENT_BINARY_DIR}/gtest_build") + + # add includes for LLVM's modifications + target_include_directories(gtest BEFORE PRIVATE ${LLVM_INCLUDE_DIRS}) + target_include_directories(gtest_main BEFORE PRIVATE ${LLVM_INCLUDE_DIRS}) + else() + # try to find Google Test, as GTEST_SRC_DIR is not manually specified + find_path(GTEST_SRC_DIR + "src/gtest.cc" + + HINTS + "/usr/src/gtest" + + # prevent CMake from finding gtest.cc in LLVM's utils directory + NO_DEFAULT_PATH -if (NOT EXISTS "${GTEST_SRC_DIR}") - message(FATAL_ERROR "GTest source directory \"${GTEST_SRC_DIR}\" cannot be found.\n" - "Try passing -DGTEST_SRC_DIR= to cmake where " - " is the path to the GoogleTest source tree.\n" - "Alternatively you can disable unit tests by passing " - "-DENABLE_UNIT_TESTS=OFF to cmake.") + DOC + "Path to Google Test source directory" + ) + endif() + endif() + + if (NOT (TARGET gtest AND TARGET gtest_main)) + # building from GTEST_SRC_DIR, not from LLVM's utils directory + find_path(GTEST_INCLUDE_DIR + "gtest/gtest.h" + + HINTS + "${GTEST_SRC_DIR}/include" + + NO_DEFAULT_PATH + + DOC + "Path to Google Test include directory" + ) + + if (NOT EXISTS "${GTEST_SRC_DIR}") + message(FATAL_ERROR "Google Test source directory \"${GTEST_SRC_DIR}\" " + "cannot be found.\n" + "Try passing -DGTEST_SRC_DIR= to CMake where " + " is the path to the Google Test source tree.\n" + "Alternatively, you can disable unit tests by passing " + "-DENABLE_UNIT_TESTS=OFF to CMake.") + endif() + message(STATUS "Google Test: Building from source.") + message(STATUS "GTEST_SRC_DIR: ${GTEST_SRC_DIR}") + + # Prevent Google Test from adding to our install target. + # Required for >= 1.8.0, but can only be disabled starting with 1.8.1 + set(GTEST_INSTALL OFF CACHE BOOL "disable installing Google Test" FORCE) + + # Build Google Test as part of our project + add_subdirectory(${GTEST_SRC_DIR} "${CMAKE_CURRENT_BINARY_DIR}/gtest_build") + endif() + + # build Google Test with KLEE's defines and compile flags + target_compile_definitions(gtest PRIVATE ${KLEE_COMPONENT_CXX_DEFINES}) + target_compile_definitions(gtest_main PRIVATE ${KLEE_COMPONENT_CXX_DEFINES}) + target_compile_options(gtest PRIVATE ${KLEE_COMPONENT_CXX_FLAGS}) + target_compile_options(gtest_main PRIVATE ${KLEE_COMPONENT_CXX_FLAGS}) endif() -# It's important that GTest is built with KLEE's compile flags -# so set them here. -set(_OLD_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") -foreach (f ${KLEE_COMPONENT_CXX_FLAGS}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${f}") -endforeach() -foreach (f ${KLEE_COMPONENT_CXX_DEFINES}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${f}") -endforeach() - -# Build GTest as part of our project -# FIXME: Prevent GTest from adding to our install target. -# This is a problem for GTest >= 1.8. I've filled a PR to fix the issue -# ( https://github.com/google/googletest/pull/921 ). If it gets accepted -# we can do `set(gtest_enable_install FALSE)` to fix this. -add_subdirectory(${GTEST_SRC_DIR} "${CMAKE_CURRENT_BINARY_DIR}/gtest_build") - -set(CMAKE_CXX_FLAGS "${_OLD_CMAKE_CXX_FLAGS}") # Restore the flags # This keeps track of all the unit test # targets so we can ensure they are built @@ -52,22 +139,17 @@ define_property(GLOBAL FULL_DOCS "KLEE unit tests" ) -set(GTEST_INCLUDE_DIR - "${GTEST_SRC_DIR}/include" - CACHE - PATH - "Path to GTest include directory" -) - if (NOT IS_DIRECTORY "${GTEST_INCLUDE_DIR}") message(FATAL_ERROR - "Cannot find GTest include directory \"${GTEST_INCLUDE_DIR}\"") + "Cannot find Google Test include directory \"${GTEST_INCLUDE_DIR}\"") endif() +message(STATUS "GTEST_INCLUDE_DIR: ${GTEST_INCLUDE_DIR}") function(add_klee_unit_test target_name) add_executable(${target_name} ${ARGN}) target_link_libraries(${target_name} PRIVATE gtest_main) target_include_directories(${target_name} BEFORE PRIVATE "${GTEST_INCLUDE_DIR}") + target_include_directories(${target_name} BEFORE PRIVATE ${KLEE_COMPONENT_EXTRA_INCLUDE_DIRS}) set_target_properties(${target_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/unittests/" diff --git a/unittests/TestMain.cpp b/unittests/TestMain.cpp index 095076b2..9a64a4af 100644 --- a/unittests/TestMain.cpp +++ b/unittests/TestMain.cpp @@ -9,6 +9,12 @@ #include "gtest/gtest.h" +// WARNING: If LLVM's gtest_main target is reused +// or is built from LLVM's source tree, +// this file is ignored. Instead, LLVM's +// utils/unittest/UnitTestMain/TestMain.cpp +// is used. + int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); -- cgit 1.4.1