about summary refs log tree commit diff homepage
diff options
context:
space:
mode:
authorJulian Büning <julian.buening@rwth-aachen.de>2018-10-22 15:42:49 +0200
committerMartinNowack <martin.nowack@gmail.com>2019-07-30 14:49:24 +0100
commit2d6a5dd67f2fdc346e7ccba55643db30ecfd3a82 (patch)
tree0d2cf9668d84b080030e828bdd9fefad90490249
parenteef4fc08530f357cc49109dc738f1ab4c519a42c (diff)
downloadklee-2d6a5dd67f2fdc346e7ccba55643db30ecfd3a82.tar.gz
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.
-rw-r--r--README-CMake.md12
-rw-r--r--cmake/find_llvm.cmake16
-rw-r--r--unittests/CMakeLists.txt162
-rw-r--r--unittests/TestMain.cpp6
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=<path_to_gtest_source> to cmake where "
-    "<path_to_gtest_source> 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=<path_to_gtest_source> to CMake where "
+      "<path_to_gtest_source> 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();