# Copyright (c) 2020-now by the Zeek Project. See LICENSE for details. cmake_minimum_required(VERSION 3.15.0) if (APPLE AND CMAKE_VERSION VERSION_GREATER_EQUAL 4.0.0 AND NOT CMAKE_OSX_SYSROOT) # We are going to need having CMAKE_OSX_SYSROOT point to the macOS SDK # path. However, starting with CMake 4.0, CMAKE_OSX_SYSROOT is not set # automatically anymore. So we follow the guidance from the CMake 4.0 # release notes here: # # Builds targeting macOS no longer choose any SDK or pass an "-isysroot" # flag to the compiler by default. Instead, compilers are expected to # choose a default macOS SDK on their own. In order to use a compiler # that does not do this, users must now specify # "-DCMAKE_OSX_SYSROOT=macosx" when configuring their build. # # The described situation ("let the compiler choose") doesn't quite match # ours, but this does lead to CMAKE_OSX_SYSROOT pointing to the macOS SDK # path when we need it. set(CMAKE_OSX_SYSROOT "macosx") endif () project(spicy LANGUAGES C CXX) set(flex_minimum_version "2.5.37") set(bison_minimum_version "3.0") set(macos_minimum_version "19.0.0") # macOS 10.15.0 (Catalina) ## Initialize defaults & global options if (NOT CMAKE_BUILD_TYPE) # CMake doesn't set build type by default. set(CMAKE_BUILD_TYPE "Debug") endif () set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) include(Util) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # CMake uses -O2 by default with RelWithDebInfo. string(REPLACE "-O2" "-O3" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") include(CheckCompiler) include(GNUInstallDirs) if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) endif () if (NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) endif () if (NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) endif () if (BINARY_PACKAGING_MODE) # Binary packaging mode uses a static link which causes issues with # unresolved symbols for a number of tests. if (USE_SANITIZERS) message(FATAL_ERROR "Sanitizers are unsupported in binary packaging mode") endif () set(BUILD_SHARED_LIBS OFF) set(CMAKE_SKIP_RPATH ON) set(CMAKE_INSTALL_RPATH_USE_LINK_PATH OFF) set(CMAKE_MACOSX_RPATH OFF) else () make_install_rpath(rpath ${CMAKE_INSTALL_FULL_BINDIR} ${CMAKE_INSTALL_FULL_LIBDIR}) set(CMAKE_INSTALL_RPATH "${rpath}") endif () if (USE_CCACHE) find_program(CCACHE_PROGRAM ccache) if (CCACHE_PROGRAM) set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM}) set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM}) else () set(USE_CCACHE "no (error: could not find ccache)") endif () else () set(USE_CCACHE "no") endif () if (USE_SANITIZERS) # Recommended flags per https://github.com/google/sanitizers/wiki/AddressSanitizer set(sanitizer_cxx_flags "-fsanitize=${USE_SANITIZERS} -fno-omit-frame-pointer -fno-optimize-sibling-calls -O1") set(sanitizer_ld_flags "-fsanitize=${USE_SANITIZERS}") if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(sanitizer_cxx_flags "${sanitizer_cxx_flags} -shared-libasan") set(sanitizer_ld_flags "${sanitizer_ld_flags} -frtlib-add-rpath -shared-libasan") endif () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${sanitizer_cxx_flags}") set(EXTRA_CXX_FLAGS "${EXTRA_CXX_FLAGS} ${sanitizer_cxx_flags}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${sanitizer_ld_flags}") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${sanitizer_ld_flags}") set(EXTRA_LD_FLAGS "${EXTRA_LD_FLAGS} ${sanitizer_ld_flags}") set(HILTI_DEV_PRECOMPILE_HEADERS "no") endif () if (USE_WERROR) set(werror_flags "-Werror") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${werror_flags}") set(EXTRA_CXX_FLAGS "${EXTRA_CXX_FLAGS} ${werror_flags}") endif () ## Load modules # If the user specified dedicated prefixes for Flex or Bison, look in these # prefixes first. As the upstream modules do not support specifying these we # inject them here by hand. # # The implementation relies on the fact that the `find_*` commands do not search # again should the output variable already be set successfully. We first search # for the artifacts with `NO_DEFAULT_PATH` and then later trigger the upstream # `find_package` logic. With that any user-specified prefix takes precedence # over what could be found in the default search locations. if (FLEX_ROOT) find_program( FLEX_EXECUTABLE NAMES flex win_flex PATHS ${FLEX_ROOT} PATH_SUFFIXES bin NO_DEFAULT_PATH) find_library( FL_LIBRARY NAMES fl PATHS ${FLEX_ROOT} PATH_SUFFIXES lib NO_DEFAULT_PATH) find_path( FLEX_INCLUDE_DIR FlexLexer.h PATHS ${FLEX_ROOT} PATH_SUFFIXES include NO_DEFAULT_PATH) endif () if (BISON_ROOT) find_program( BISON_EXECUTABLE NAMES bison win_bison PATHS ${BISON_ROOT} PATH_SUFFIXES bin NO_DEFAULT_PATH) endif () find_package(FLEX REQUIRED) find_package(BISON REQUIRED) find_package(ZLIB REQUIRED) find_package(Backtrace) if (Backtrace_FOUND AND NOT APPLE) # On systems other than MacOS there's a libexecinfo that's not working for us: # it seems to break when compiling without frame pointers so we disable it. if ("${Backtrace_LIBRARY}" MATCHES "libexecinfo") message(STATUS "Disabling backtrace because we found libexecinfo") set(Backtrace_FOUND "no") endif () endif () # Prettify output if (Backtrace_FOUND) set(HILTI_HAVE_BACKTRACE "yes") else () set(HILTI_HAVE_BACKTRACE "no") endif () if (APPLE) set(MACOS_FOUND "yes") require_version("macOS" MACOS_FOUND ${CMAKE_SYSTEM_VERSION} "${macos_minimum_version}" true) endif () require_version("Flex" FLEX_FOUND FLEX_VERSION "${flex_minimum_version}" true) require_version("Bison" BISON_FOUND BISON_VERSION "${bison_minimum_version}" true) find_package(GoldLinker) find_package(Threads) option(BUILD_TOOLCHAIN "Build the Spicy compiler toolchain" ON) if (BUILD_TOOLCHAIN) set(HAVE_TOOLCHAIN yes) else () set(HAVE_TOOLCHAIN no) endif () set(HILTI_COMPILER_LAUNCHER "" CACHE STRING "C++ compiler launcher to use by default during JIT") # Set up testing infrastructure. enable_testing() # Get project version. file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/VERSION SPICY_VERSION LIMIT_COUNT 1) set(CMAKE_PROJECT_VERSION ${SPICY_VERSION}) # Get Spicy commit. If we cannot get the current commit (e.g., no .git # directory present for release tarballs), this will leave SPICY_COMMIT unset. execute_process( COMMAND ${CMAKE_COMMAND} -E env GIT_DIR=${CMAKE_CURRENT_SOURCE_DIR}/.git git rev-parse --short HEAD OUTPUT_VARIABLE SPICY_COMMIT OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_VARIABLE ignored) string(REGEX MATCH "([0-9]*)\.([0-9]*)\.([0-9]*).*" _ ${CMAKE_PROJECT_VERSION}) math(EXPR SPICY_VERSION_NUMBER "${CMAKE_MATCH_1} * 10000 + ${CMAKE_MATCH_2} * 100 + ${CMAKE_MATCH_3}") if (NOT "${SPICY_COMMIT}" STREQUAL "") set(SPICY_VERSION_LONG "${SPICY_VERSION} (${SPICY_COMMIT})") else () set(SPICY_VERSION_LONG ${SPICY_VERSION}) endif () # Add subdirectories. add_subdirectory(hilti) add_subdirectory(spicy) add_subdirectory(scripts) add_subdirectory(3rdparty) ## Print build summary string(TOUPPER ${CMAKE_BUILD_TYPE} BuildType) string(STRIP "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${BuildType}}" cflags) string(STRIP "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BuildType}}" cxxflags) # Precompiled headers. option("HILTI_DEV_PRECOMPILE_HEADERS" "Precompile headers for developer tests" ON) if (${HILTI_DEV_PRECOMPILE_HEADERS} AND TARGET hilti-config AND TARGET spicy-config) # Precompile libhilti for use in JIT during development. # # We only use precompiled headers during JIT, but e.g., not to during # compilation of Spicy itself. This gives us the benefits of JIT without # e.g., making it harder for ccache to work during development. It also # allows us to punt on some trickier cleanups of header files. add_custom_command( OUTPUT ${PROJECT_BINARY_DIR}/cache/spicy/precompiled_libhilti.h ${PROJECT_BINARY_DIR}/cache/spicy/precompiled_libhilti.h.gch ${PROJECT_BINARY_DIR}/cache/spicy/precompiled_libhilti_debug.h ${PROJECT_BINARY_DIR}/cache/spicy/precompiled_libhilti_debug.h.gch ${PROJECT_BINARY_DIR}/cache/spicy/precompiled_libspicy.h ${PROJECT_BINARY_DIR}/cache/spicy/precompiled_libspicy.h.gch ${PROJECT_BINARY_DIR}/cache/spicy/precompiled_libspicy_debug.h ${PROJECT_BINARY_DIR}/cache/spicy/precompiled_libspicy_debug.h.gch COMMENT "Generating precompiled headers" COMMAND ${CMAKE_COMMAND} -E env SPICY_CACHE=${PROJECT_BINARY_DIR}/cache/spicy ${PROJECT_SOURCE_DIR}/scripts/precompile-headers.sh --bindir ${CMAKE_BINARY_DIR}/bin DEPENDS ${PROJECT_SOURCE_DIR}/scripts/precompile-headers.sh ${CMAKE_CURRENT_SOURCE_DIR}/hilti/runtime/include/libhilti.h ${CMAKE_CURRENT_SOURCE_DIR}/spicy/runtime/include/libspicy.h hilti-config spicy-config) add_custom_target(precompiled-headers ALL COMMENT "Generating precompiled headers" DEPENDS ${PROJECT_BINARY_DIR}/cache/spicy/precompiled_libhilti.h) endif () # Global test target add_custom_target( check COMMAND ctest --output-on-failure -C $ DEPENDS tests COMMENT "Running unit tests") add_custom_target(tests DEPENDS hilti-tests spicy-tests COMMENT "Running unit tests") # Packaging. # Check tags the HEAD commit corresponds to. If we cannot get the current # commit (e.g., no .git directory present for release tarballs), this will # leave SPICY_TAGS unset which is fine for users working from tarballs. execute_process(COMMAND git tag --points-at HEAD v* OUTPUT_VARIABLE SPICY_TAGS ERROR_VARIABLE ignored) if ("${SPICY_TAGS}" STREQUAL "") # If the HEAD commit does not correspond to a tag it is not a release. Hide # the version number in packaging artifacts so we can e.g., provide stable # links to the latest version. set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-dev") else () # If the HEAD commit corresponds to a tag it is a release and we expect a # version number in packaging artifacts. set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CMAKE_PROJECT_VERSION}") endif () set(CPACK_PACKAGE_CONTACT "info@zeek.org") set(CPACK_STRIP_FILES ON) set(CPACK_SOURCE_STRIP_FILES ON) set(CPACK_BINARY_DEB OFF) set(CPACK_BINARY_RPM OFF) set(CPACK_BINARY_STGZ OFF) set(CPACK_BINARY_TZ OFF) set(CPACK_BINARY_TGZ ON) find_program(RPMBUILD rpmbuild) if (RPMBUILD) set(CPACK_BINARY_RPM ON) endif () find_program(DPKG_DEB dpkg-deb) if (DPKG_DEB) set(CPACK_BINARY_DEB ON) endif () # While this should be sufficient to set a prefix for installation, we still # bake in other absolute paths by using `CMAKE_INSTALL_FULL_*`-style variables, # e.g., when baking details about the installation into binaries. set(CPACK_SET_DESTDIR ON) set(CPACK_INSTALL_PREFIX "/opt/spicy") set(CPACK_PACKAGE_RELOCATABLE OFF) include(CPack) # Emit configuration summary. set(SPICY_HOST_SYSTEM "${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION} (${CMAKE_SYSTEM_PROCESSOR})") set(SPICY_C_COMPILER "${CMAKE_C_COMPILER} (${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION})") set(SPICY_CXX_COMPILER "${CMAKE_CXX_COMPILER} (${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION})") message( "\n====================| Spicy Build Summary |====================" "\n" "\nVersion: ${SPICY_VERSION_LONG}" "\n" "\nBuild type: ${CMAKE_BUILD_TYPE}" "\nBuild directory: ${PROJECT_BINARY_DIR}" "\nInstall prefix: ${CMAKE_INSTALL_PREFIX}" "\nBuild shared libs: ${BUILD_SHARED_LIBS}" "\n" "\nHost system: ${SPICY_HOST_SYSTEM}" "\nC compiler: ${SPICY_C_COMPILER}" "\nC++ compiler: ${SPICY_CXX_COMPILER}" "\n" "\nBuilding toolchain: ${HAVE_TOOLCHAIN}" "\n" "\nUse ccache: ${USE_CCACHE}" "\nUse gold linker: ${GOLD_FOUND}" "\nUse sanitizers: ${USE_SANITIZERS}" "\nUse backtrace: ${HILTI_HAVE_BACKTRACE}" "\n" "\nWarnings are errors: ${USE_WERROR}" "\nPrecompile headers: ${HILTI_DEV_PRECOMPILE_HEADERS}" "\n" "\nBison version: ${BISON_VERSION}" "\nCMake version: ${CMAKE_VERSION}" "\nFlex version: ${FLEX_VERSION}" "\nzlib version: ${ZLIB_VERSION_STRING}" "\n" "\n================================================================\n")