cmake_minimum_required(VERSION 3.24) if(POLICY CMP0135) cmake_policy(SET CMP0135 NEW) endif() if(POLICY CMP0167) cmake_policy(SET CMP0167 NEW) endif() project(libloot VERSION "0.29.6") include(CMakeDependentOption) include(CMakePackageConfigHelpers) include(GNUInstallDirs) cmake_dependent_option(LIBLOOT_BUILD_SHARED "Build a shared library" ON "NOT BUILD_SHARED_LIBS" ON) option(RUN_CLANG_TIDY "Whether or not to run clang-tidy during build. Has no effect when using CMake's MSVC generator." OFF) option(LIBLOOT_BUILD_TESTS "Whether or not to build libloot's tests." ON) option(LIBLOOT_INSTALL_DOCS "Whether or not to install libloot's docs (which need to be built separately)." ON) ############################## # External Projects ############################## if(MSVC) set(LIBLOOT_CPP_FILENAME "libloot_cpp.lib") else() set(LIBLOOT_CPP_FILENAME "liblibloot_cpp.a") endif() if(CMAKE_CROSSCOMPILING AND NOT DEFINED RUST_TARGET) if(WIN32 AND MINGW AND CMAKE_SIZEOF_VOID_P EQUAL 8) set(RUST_TARGET "x86_64-pc-windows-gnu") elseif(WIN32 AND MINGW AND CMAKE_SIZEOF_VOID_P EQUAL 4) set(RUST_TARGET "i686-pc-windows-gnu") else() message(FATAL_ERROR "Cross-compiling without RUST_TARGET set, and an appropriate value could not be set automatically!") endif() endif() if(DEFINED RUST_TARGET) message(STATUS "Building for Rust target ${RUST_TARGET}") set(TARGET_PATH "${PROJECT_SOURCE_DIR}/../target/${RUST_TARGET}") else() set(TARGET_PATH "${PROJECT_SOURCE_DIR}/../target") endif() set(CARGO_COMMAND cargo build $<$>:--release> $<$,$>:--config> $<$,$>:${PROJECT_SOURCE_DIR}/.cargo/msvc-debug-config.toml> $<$:--target> $<$:${RUST_TARGET}>) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(CARGO_COMMAND ${CMAKE_COMMAND} -E env --modify "CXXFLAGS=string_append: -fno-lto" -- ${CARGO_COMMAND}) endif() add_custom_target(libloot_cargo # Force LTO to be disabled when building the first layer of the C++ wrapper, # as it may be enabled by default, and if so that leads to undefined symbols # in the C++ wrapper's shared library. It's not enough to just pass -fno-lto # when building libloot-cpp itself, it also needs to be passed when building # the cxx crate, as otherwise there will be missing cxxbridge symbols. COMMAND ${CARGO_COMMAND} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} BYPRODUCTS "${TARGET_PATH}/$,debug,release>/${LIBLOOT_CPP_FILENAME}" "${TARGET_PATH}/cxxbridge/libloot-cpp/src/lib.rs.cc" "${TARGET_PATH}/cxxbridge/libloot-cpp/src/lib.rs.h" "${TARGET_PATH}/cxxbridge/rust/cxx.h" ) add_library(libloot_cpp INTERFACE) add_dependencies(libloot_cpp libloot_cargo) target_link_libraries(libloot_cpp INTERFACE optimized "${TARGET_PATH}/release/${LIBLOOT_CPP_FILENAME}" debug "${TARGET_PATH}/debug/${LIBLOOT_CPP_FILENAME}") target_include_directories(libloot_cpp INTERFACE "${TARGET_PATH}/cxxbridge") if(WIN32) target_link_libraries(libloot_cpp INTERFACE ntdll ws2_32 bcrypt) endif() ############################## # General Settings ############################## set(LIBLOOT_SRC_API_CPP_FILES "${PROJECT_SOURCE_DIR}/src/api/api.cpp" "${PROJECT_SOURCE_DIR}/src/api/convert.cpp" "${PROJECT_SOURCE_DIR}/src/api/database.cpp" "${PROJECT_SOURCE_DIR}/src/api/exception/cyclic_interaction_error.cpp" "${PROJECT_SOURCE_DIR}/src/api/exception/exception.cpp" "${PROJECT_SOURCE_DIR}/src/api/exception/undefined_group_error.cpp" "${PROJECT_SOURCE_DIR}/src/api/metadata/file.cpp" "${PROJECT_SOURCE_DIR}/src/api/metadata/filename.cpp" "${PROJECT_SOURCE_DIR}/src/api/metadata/group.cpp" "${PROJECT_SOURCE_DIR}/src/api/metadata/location.cpp" "${PROJECT_SOURCE_DIR}/src/api/metadata/message.cpp" "${PROJECT_SOURCE_DIR}/src/api/metadata/message_content.cpp" "${PROJECT_SOURCE_DIR}/src/api/metadata/plugin_cleaning_data.cpp" "${PROJECT_SOURCE_DIR}/src/api/metadata/plugin_metadata.cpp" "${PROJECT_SOURCE_DIR}/src/api/metadata/tag.cpp" "${PROJECT_SOURCE_DIR}/src/api/game.cpp" "${PROJECT_SOURCE_DIR}/src/api/plugin.cpp" "${PROJECT_SOURCE_DIR}/src/api/vertex.cpp") set(LIBLOOT_INCLUDE_H_FILES "${PROJECT_SOURCE_DIR}/include/loot/api.h" "${PROJECT_SOURCE_DIR}/include/loot/api_decorator.h" "${PROJECT_SOURCE_DIR}/include/loot/database_interface.h" "${PROJECT_SOURCE_DIR}/include/loot/exception/cyclic_interaction_error.h" "${PROJECT_SOURCE_DIR}/include/loot/exception/plugin_not_loaded_error.h" "${PROJECT_SOURCE_DIR}/include/loot/exception/undefined_group_error.h" "${PROJECT_SOURCE_DIR}/include/loot/enum/edge_type.h" "${PROJECT_SOURCE_DIR}/include/loot/enum/game_type.h" "${PROJECT_SOURCE_DIR}/include/loot/enum/log_level.h" "${PROJECT_SOURCE_DIR}/include/loot/enum/message_type.h" "${PROJECT_SOURCE_DIR}/include/loot/game_interface.h" "${PROJECT_SOURCE_DIR}/include/loot/loot_version.h" "${PROJECT_SOURCE_DIR}/include/loot/metadata/file.h" "${PROJECT_SOURCE_DIR}/include/loot/metadata/filename.h" "${PROJECT_SOURCE_DIR}/include/loot/metadata/group.h" "${PROJECT_SOURCE_DIR}/include/loot/metadata/location.h" "${PROJECT_SOURCE_DIR}/include/loot/metadata/message.h" "${PROJECT_SOURCE_DIR}/include/loot/metadata/message_content.h" "${PROJECT_SOURCE_DIR}/include/loot/metadata/plugin_cleaning_data.h" "${PROJECT_SOURCE_DIR}/include/loot/metadata/plugin_metadata.h" "${PROJECT_SOURCE_DIR}/include/loot/metadata/tag.h" "${PROJECT_SOURCE_DIR}/include/loot/plugin_interface.h" "${PROJECT_SOURCE_DIR}/include/loot/vertex.h") set(LIBLOOT_SRC_API_H_FILES "${PROJECT_SOURCE_DIR}/src/api/convert.h" "${PROJECT_SOURCE_DIR}/src/api/database.h" "${PROJECT_SOURCE_DIR}/src/api/exception/exception.h" "${PROJECT_SOURCE_DIR}/src/api/game.h" "${PROJECT_SOURCE_DIR}/src/api/plugin.h") source_group(TREE "${PROJECT_SOURCE_DIR}/src/api" PREFIX "Source Files" FILES ${LIBLOOT_SRC_API_CPP_FILES}) source_group(TREE "${PROJECT_SOURCE_DIR}/include" PREFIX "Header Files" FILES ${LIBLOOT_INCLUDE_H_FILES}) source_group(TREE "${PROJECT_SOURCE_DIR}/src/api" PREFIX "Header Files" FILES ${LIBLOOT_SRC_API_H_FILES}) set(LIBLOOT_ALL_SOURCES ${LIBLOOT_SRC_API_CPP_FILES} ${LIBLOOT_INCLUDE_H_FILES} ${LIBLOOT_SRC_API_H_FILES} "${PROJECT_SOURCE_DIR}/src/api/resource.rc") ############################## # Define Targets ############################## if(LIBLOOT_BUILD_SHARED) set(LIBLOOT_LIBRARY_TYPE "SHARED") else() set(LIBLOOT_LIBRARY_TYPE "STATIC") endif() # Build API. add_library(libloot ${LIBLOOT_LIBRARY_TYPE} ${LIBLOOT_ALL_SOURCES}) target_link_libraries(libloot PRIVATE libloot_cpp) ############################## # Set Target-Specific Flags ############################## set(LIBLOOT_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/src" "${PROJECT_SOURCE_DIR}/include") set(LIBLOOT_COMMON_SYSTEM_INCLUDE_DIRS ${LIBLOADORDER_INCLUDE_DIRS} ${ESPLUGIN_INCLUDE_DIRS} ${LCI_INCLUDE_DIRS}) target_include_directories(libloot PUBLIC "$" "$") target_include_directories(libloot PRIVATE ${LIBLOOT_INCLUDE_DIRS}) target_include_directories(libloot SYSTEM PRIVATE ${LIBLOOT_COMMON_SYSTEM_INCLUDE_DIRS}) set_target_properties(libloot PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON # Stop the "lib" prefix being added on Linux, since it's already in the target name. PREFIX "") if(WIN32) target_compile_definitions(libloot PRIVATE UNICODE _UNICODE LOOT_EXPORT) endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") target_compile_options(libloot PRIVATE "-Wall" "-Wextra" "-Wpedantic" "-Wconversion" "-Wdeprecated" "-Wuninitialized" "-Winit-self" "-Wswitch-enum" "-Wstrict-overflow=5" "-Wstringop-overflow=4" "-Wduplicated-branches" "-Wduplicated-cond" "-Wzero-as-null-pointer-constant" "-Wfloat-equal" "-Wshadow" "-Wcast-qual" "-Wcast-align" "-Wsign-conversion" "-Wlogical-op" "-Wvla" "-Wno-xor-used-as-pow") endif() if(MSVC) # Turn off permissive mode to be more standards-compliant and avoid compiler errors. target_compile_options(libloot PRIVATE "/permissive-" "/W4" "/Wall" "/wd4514" "/wd4623" "/wd4625" "/wd4626" "/wd4710" "/wd4711" "/wd4738" "/wd4820" "/wd5026" "/wd5027" "/wd5045" "/external:anglebrackets" "/external:W0" "/Zc:__cplusplus" "/GL" "/MP" "/sdl" "$<$:/RTC1>") target_link_options(libloot PRIVATE "/INCREMENTAL:NO" "/LTCG") # Copy test code coverage config into build directory where MSVC expects to # find it. configure_file("${PROJECT_SOURCE_DIR}/.runsettings" "${PROJECT_BINARY_DIR}/.runsettings" COPYONLY) endif() ############################## # Configure clang-tidy ############################## if(RUN_CLANG_TIDY) set(CLANG_TIDY_COMMON_CHECKS "cppcoreguidelines-avoid-c-arrays" "cppcoreguidelines-c-copy-assignment-signature" "cppcoreguidelines-explicit-virtual-functions" "cppcoreguidelines-init-variables" "cppcoreguidelines-interfaces-global-init" "cppcoreguidelines-macro-usage" "cppcoreguidelines-narrowing-conventions" "cppcoreguidelines-no-malloc" "cppcoreguidelines-pro-bounds-array-to-pointer-decay" "cppcoreguidelines-pro-bounds-constant-array-index" "cppcoreguidelines-pro-bounds-pointer-arithmetic" "cppcoreguidelines-pro-type-const-cast" "cppcoreguidelines-pro-type-cstyle-cast" "cppcoreguidelines-pro-type-member-init" "cppcoreguidelines-pro-type-reinterpret-cast" "cppcoreguidelines-pro-type-static-cast-downcast" "cppcoreguidelines-pro-type-union-access" "cppcoreguidelines-pro-type-vararg" "cppcoreguidelines-pro-type-slicing") set(CLANG_TIDY_LIB_CHECKS ${CLANG_TIDY_COMMON_CHECKS} "cppcoreguidelines-avoid-goto" "cppcoreguidelines-avoid-magic-numbers" "cppcoreguidelines-non-private-member-variables-in-classes" "cppcoreguidelines-special-member-functions") list(JOIN CLANG_TIDY_LIB_CHECKS "," CLANG_TIDY_LIB_CHECKS_JOINED) set(CLANG_TIDY_LIB clang-tidy "-header-filter=.*" "-checks=${CLANG_TIDY_LIB_CHECKS_JOINED}") set_target_properties(libloot PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_LIB}") endif() ############################## # Tests ############################## if(LIBLOOT_BUILD_TESTS) include("cmake/tests.cmake") endif() ######################################## # Install ######################################## add_library(libloot::libloot ALIAS libloot) if(LIBLOOT_BUILD_SHARED) set_property(TARGET libloot PROPERTY VERSION ${libloot_VERSION}) set_property(TARGET libloot PROPERTY SOVERSION ${libloot_VERSION_MAJOR}) set_property(TARGET libloot PROPERTY INTERFACE_libloot_MAJOR_VERSION ${libloot_VERSION_MAJOR}) set_property(TARGET libloot APPEND PROPERTY COMPATIBLE_INTERFACE_STRING libloot_MAJOR_VERSION) configure_package_config_file( ${PROJECT_SOURCE_DIR}/cmake/Config.cmake.in "${PROJECT_BINARY_DIR}/liblootConfig.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libloot ) write_basic_package_version_file( "${PROJECT_BINARY_DIR}/liblootConfigVersion.cmake" VERSION "${libloot_VERSION}" COMPATIBILITY AnyNewerVersion ) install(TARGETS libloot EXPORT liblootTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(EXPORT liblootTargets NAMESPACE libloot:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libloot) export(TARGETS libloot NAMESPACE libloot:: FILE "${PROJECT_BINARY_DIR}/liblootTargets.cmake") if(MSVC) install(FILES $ DESTINATION ${CMAKE_INSTALL_LIBDIR} OPTIONAL CONFIGURATIONS Debug RelWithDebInfo) endif() install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) if(LIBLOOT_INSTALL_DOCS) install(DIRECTORY "${PROJECT_SOURCE_DIR}/../docs/build/html/" DESTINATION ${CMAKE_INSTALL_DOCDIR}) endif() install(FILES "${PROJECT_BINARY_DIR}/liblootConfig.cmake" "${PROJECT_BINARY_DIR}/liblootConfigVersion.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libloot) endif() ######################################## # CPack ######################################## if(NOT DEFINED CPACK_PACKAGE_VERSION) find_package(Git) if(GIT_FOUND) execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --long --always --abbrev=7 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} OUTPUT_VARIABLE GIT_DESCRIBE_STRING OUTPUT_STRIP_TRAILING_WHITESPACE) else() set(GIT_DESCRIBE_STRING "unknown-version") endif() set(CPACK_PACKAGE_VERSION ${GIT_DESCRIBE_STRING}) endif() if(WIN32) set(CPACK_GENERATOR "7Z") else() set(CPACK_GENERATOR "TXZ") endif() set(CPACK_PACKAGE_DIRECTORY "${PROJECT_BINARY_DIR}/package") include(CPack)