cmake_minimum_required(VERSION 3.20) # Option to use Conan for dependency management # Check if we're running with a Conan preset (the toolchain file will be in generators/) if(CMAKE_TOOLCHAIN_FILE MATCHES "conan_toolchain\\.cmake$") set(YAMS_USE_CONAN ON CACHE BOOL "Using Conan package manager" FORCE) else() option(YAMS_USE_CONAN "Use Conan package manager for dependencies" OFF) endif() # Derive version from git tags or allow override via -DYAMS_VERSION=MAJOR.MINOR.PATCH set(YAMS_VERSION "" CACHE STRING "Override Yams version (semver: MAJOR.MINOR.PATCH)") if(NOT YAMS_VERSION) find_package(Git QUIET) if(Git_FOUND) execute_process( COMMAND "${GIT_EXECUTABLE}" describe --tags --match "v[0-9]*" --dirty --always WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_DESCRIBE OUTPUT_STRIP_TRAILING_WHITESPACE ) # Strip leading 'v' and any suffix like -123-gabcdef string(REGEX REPLACE "^v" "" YAMS_VERSION_CANDIDATE "${GIT_DESCRIBE}") string(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" YAMS_SEMVER "${YAMS_VERSION_CANDIDATE}") if(NOT YAMS_SEMVER) set(YAMS_SEMVER "0.0.0") endif() else() set(YAMS_SEMVER "0.0.0") endif() else() set(YAMS_SEMVER "${YAMS_VERSION}") endif() project(YAMS VERSION ${YAMS_SEMVER} DESCRIPTION "Yet Another Memory System - High-performance content-addressed storage" LANGUAGES C CXX ) # Generate version header from project version file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/generated/yams") # Write template for version header and configure it set(_YAMS_VERSION_TEMPLATE "${CMAKE_CURRENT_BINARY_DIR}/yams_version.hpp.in") file(WRITE "${_YAMS_VERSION_TEMPLATE}" "/* Auto-generated by CMake - Do not edit */ #pragma once #define YAMS_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ #define YAMS_VERSION_MINOR @PROJECT_VERSION_MINOR@ #define YAMS_VERSION_PATCH @PROJECT_VERSION_PATCH@ #define YAMS_VERSION_STRING \"@PROJECT_VERSION@\" ") configure_file( "${_YAMS_VERSION_TEMPLATE}" "${CMAKE_CURRENT_BINARY_DIR}/generated/yams/version.hpp" @ONLY ) # Ensure default CMake package dir uses 'Yams' casing for consumers include(GNUInstallDirs) # Install generated version header install(FILES "${CMAKE_CURRENT_BINARY_DIR}/generated/yams/version.hpp" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/yams" COMPONENT development) set(YAMS_CMAKE_PACKAGE_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/Yams" CACHE PATH "Install dir for YAMS CMake package files") # Modern CMake policies (set only if available) if(POLICY CMP0077) # option() honors normal variables cmake_policy(SET CMP0077 NEW) endif() if(POLICY CMP0126) # file(GENERATE) supports generator expressions in OUTPUT cmake_policy(SET CMP0126 NEW) endif() if(POLICY CMP0135) # IN_LIST behavior and other modern improvements cmake_policy(SET CMP0135 NEW) endif() # C++ standard configuration (C++20-only) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) message(STATUS "Using C++20 mode") # Compiler version checks for C++20 mode if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 11) message(FATAL_ERROR "GCC ${CMAKE_CXX_COMPILER_VERSION} detected. GCC 11+ is required for C++20 support.") endif() message(STATUS "Using GCC ${CMAKE_CXX_COMPILER_VERSION} in C++20 mode") elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12) message(FATAL_ERROR "Clang ${CMAKE_CXX_COMPILER_VERSION} detected. Clang 12+ is required for C++20 support.") endif() message(STATUS "Using Clang ${CMAKE_CXX_COMPILER_VERSION} in C++20 mode") endif() ## Kept above in unified C++20 configuration # Tracy profiler integration for Debug builds if(CMAKE_BUILD_TYPE STREQUAL "Debug") find_package(tracy QUIET) if(tracy_FOUND) message(STATUS "Tracy profiler enabled for Debug build") set(YAMS_ENABLE_PROFILING ON) add_compile_definitions(TRACY_ENABLE) # Add required flags for better profiling on macOS/Linux if(UNIX) add_compile_options(-g -fno-omit-frame-pointer) if(APPLE) add_link_options(-rdynamic) endif() endif() # Tracy requires compiling TracyClient.cpp into the project # The Tracy::TracyClient target should handle this, but we may need to add it explicitly # This is handled by linking to Tracy::TracyClient in each library else() message(STATUS "Tracy profiler not found - profiling disabled") set(YAMS_ENABLE_PROFILING OFF) endif() else() set(YAMS_ENABLE_PROFILING OFF) endif() # Check for std::format availability (C++20 project; allow fmt fallback) include(CheckCXXSourceCompiles) check_cxx_source_compiles(" #include #ifdef __cpp_lib_format #include int main() { auto s = std::format(\"{}\", 42); (void)s; return 0; } #else #error No format support #endif " YAMS_HAS_STD_FORMAT) if(YAMS_HAS_STD_FORMAT) add_compile_definitions(YAMS_HAS_STD_FORMAT=1) message(STATUS "std::format is available") else() add_compile_definitions(YAMS_HAS_STD_FORMAT=0) message(STATUS "std::format is NOT available - using fmt fallback") endif() # Export compile commands for tooling set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Build profile options set(YAMS_BUILD_PROFILE "custom" CACHE STRING "Build profile: release, dev, or custom") set_property(CACHE YAMS_BUILD_PROFILE PROPERTY STRINGS release dev custom) # Individual component options option(YAMS_BUILD_CLI "Build CLI tool" ON) option(YAMS_ENABLE_TUI "Enable TUI features in CLI" OFF) option(YAMS_BUILD_MCP_SERVER "Build MCP server" ON) option(YAMS_BUILD_MAINTENANCE_TOOLS "Build maintenance tools (gc, stats)" OFF) option(YAMS_BUILD_TESTS "Build unit tests" OFF) option(YAMS_BUILD_STRESS_TESTS "Build stress tests" OFF) option(YAMS_BUILD_BENCHMARKS "Build benchmarks" OFF) option(YAMS_ENABLE_SANITIZERS "Enable sanitizers in Debug" ON) option(YAMS_ENABLE_COVERAGE "Enable code coverage" OFF) option(YAMS_USE_VCPKG "Use vcpkg for dependencies" OFF) option(YAMS_ENABLE_NATIVE_OPTIMIZATIONS "Enable -march=native on supported platforms" OFF) option(YAMS_ENABLE_APPLE_SILICON_OPTIMIZATIONS "Enable Apple Silicon optimizations (auto-enabled on ARM64 Macs)" ON) option(YAMS_CLANG_USE_LIBCXX "Use libc++ with Clang (Linux requires libc++ dev packages)" OFF) option(YAMS_BUILD_API_DOCS "Build Doxygen API documentation" OFF) # ------------------------------------------------------------------------------ # Docs build (Pandoc) and embedded CLI help assets # ------------------------------------------------------------------------------ option(YAMS_BUILD_DOCS "Build CLI docs, generate manpages, and embed verbose help" OFF) find_program(PANDOC_EXECUTABLE pandoc) set(CLI_MD "${CMAKE_SOURCE_DIR}/docs/user_guide/cli.md") set(GEN_DIR "${CMAKE_BINARY_DIR}/generated/cli") set(MAN_DIR "${CMAKE_BINARY_DIR}/man") if(YAMS_BUILD_DOCS AND PANDOC_EXECUTABLE AND EXISTS "${CLI_MD}") # Ensure output directories file(MAKE_DIRECTORY "${GEN_DIR}") file(MAKE_DIRECTORY "${MAN_DIR}") file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/generated") file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/cmake") # 1) Generate full plain-text help from Markdown add_custom_command( OUTPUT "${GEN_DIR}/cli_help.txt" COMMAND "${PANDOC_EXECUTABLE}" -f markdown -t plain "${CLI_MD}" -o "${GEN_DIR}/cli_help.txt" DEPENDS "${CLI_MD}" COMMENT "Generating plain-text CLI help from Markdown" VERBATIM ) # 2) Generate yams(1) manpage from Markdown add_custom_command( OUTPUT "${MAN_DIR}/yams.1" COMMAND "${PANDOC_EXECUTABLE}" -s -f markdown -t man "${CLI_MD}" -o "${MAN_DIR}/yams.1" DEPENDS "${CLI_MD}" COMMENT "Generating yams(1) manpage from Markdown" VERBATIM ) # 3) Embed script (converts a text file to a C++ header with a constexpr string) set(EMBED_SCRIPT "${CMAKE_BINARY_DIR}/cmake/embed_text.cmake") file(WRITE "${EMBED_SCRIPT}" "if(NOT DEFINED INPUT) message(FATAL_ERROR \"INPUT not set\") endif() if(NOT DEFINED OUTPUT) message(FATAL_ERROR \"OUTPUT not set\") endif() # Optional: caller can pass -DSYMBOL=. Defaults to VERBOSE. if(NOT DEFINED SYMBOL) set(SYMBOL \"VERBOSE\") endif() set(_SYM \"\${SYMBOL}\") string(TOUPPER \"\${_SYM}\" _SYM) string(REGEX REPLACE \"[^A-Za-z0-9_]\" \"_\" _SYM \"\${_SYM}\") # Prefix command-specific symbols for clarity (keep full doc as VERBOSE) if(NOT _SYM STREQUAL \"VERBOSE\") set(_SYM \"CMD_\${_SYM}\") endif() # Strip quotes from INPUT and OUTPUT if present string(REGEX REPLACE \"^\\\"(.*)\\\"$\" \"\\\\1\" INPUT_CLEAN \"\${INPUT}\") string(REGEX REPLACE \"^\\\"(.*)\\\"$\" \"\\\\1\" OUTPUT_CLEAN \"\${OUTPUT}\") # Check if input file exists if(NOT EXISTS \"\${INPUT_CLEAN}\") message(FATAL_ERROR \"Input file does not exist: '\${INPUT_CLEAN}'\") endif() # Check if output directory exists get_filename_component(OUTPUT_DIR \"\${OUTPUT_CLEAN}\" DIRECTORY) if(NOT EXISTS \"\${OUTPUT_DIR}\") file(MAKE_DIRECTORY \"\${OUTPUT_DIR}\") endif() file(READ \"\${INPUT_CLEAN}\" _c) string(REPLACE \"\\\\\" \"\\\\\\\\\" _c \"\${_c}\") string(REPLACE \"\\\"\" \"\\\\\\\"\" _c \"\${_c}\") string(REPLACE \"\\n\" \"\\\\n\\\"\\n\\\"\" _c \"\${_c}\") file(WRITE \"\${OUTPUT_CLEAN}\" \"// Generated by embed_text.cmake - do not edit\\n#pragma once\\nnamespace yams { namespace cli_help {\\nconstexpr const char \${_SYM}[] = \\\"\${_c}\\\";\\n} } // namespace yams::cli_help\\n\") ") # 4) Embed full verbose help into a header add_custom_command( OUTPUT "${CMAKE_BINARY_DIR}/generated/cli_help.hpp" COMMAND "${CMAKE_COMMAND}" -DINPUT="${GEN_DIR}/cli_help.txt" -DOUTPUT="${CMAKE_BINARY_DIR}/generated/cli_help.hpp" -DSYMBOL=VERBOSE -P "${EMBED_SCRIPT}" DEPENDS "${GEN_DIR}/cli_help.txt" "${EMBED_SCRIPT}" COMMENT "Embedding full CLI help into header" VERBATIM ) # 5) Split per-command sections from the Markdown for detailed command help # The splitter scans lines and collects sections for known commands. set(SPLIT_SCRIPT "${CMAKE_BINARY_DIR}/cmake/split_cli_sections.cmake") file(WRITE "${SPLIT_SCRIPT}" "set(CLI_MD \"${CLI_MD}\") set(OUT_DIR \"${GEN_DIR}\") # Commands to extract (must match headings like '## ' or '## {#cmd-name}') set(COMMANDS init add get delete list search config auth stats uninstall migrate browse serve) file(STRINGS \"${CLI_MD}\" LINES) set(current \"\") set(in_section FALSE) set(content \"\") function(_flush_section name) if(NOT \"\${name}\" STREQUAL \"\") set(path \"\${OUT_DIR}/cli_help_\${name}.txt\") file(WRITE \"\${OUT_DIR}/tmp_\${name}.txt\" \"\${content}\") file(RENAME \"\${OUT_DIR}/tmp_\${name}.txt\" \"\${path}\") endif() endfunction() foreach(line IN LISTS LINES) if(line MATCHES \"^##[ ]+([A-Za-z0-9_-]+)[ ]*(\\{#cmd-.*\\})?\") string(REGEX REPLACE \"^##[ ]+([A-Za-z0-9_-]+).*$\" \"\\\\1\" heading \"\${line}\") # Close previous section if any if(in_section) _flush_section(\"\${current}\") set(content \"\") endif() # Start a new section only if the heading is one of our commands list(FIND COMMANDS \"\${heading}\" idx) if(NOT idx EQUAL -1) set(current \"\${heading}\") set(in_section TRUE) set(content \"\${line}\\n\") else() set(current \"\") set(in_section FALSE) set(content \"\") endif() else() if(in_section) set(content \"\${content}\${line}\\n\") endif() endif() endforeach() # Flush the last open section if(in_section) _flush_section(\"\${current}\") endif() ") add_custom_command( OUTPUT "${GEN_DIR}/cli_help_init.txt" "${GEN_DIR}/cli_help_add.txt" "${GEN_DIR}/cli_help_get.txt" "${GEN_DIR}/cli_help_delete.txt" "${GEN_DIR}/cli_help_list.txt" "${GEN_DIR}/cli_help_search.txt" "${GEN_DIR}/cli_help_config.txt" "${GEN_DIR}/cli_help_auth.txt" "${GEN_DIR}/cli_help_stats.txt" "${GEN_DIR}/cli_help_uninstall.txt" "${GEN_DIR}/cli_help_migrate.txt" "${GEN_DIR}/cli_help_browse.txt" "${GEN_DIR}/cli_help_serve.txt" COMMAND "${CMAKE_COMMAND}" -P "${SPLIT_SCRIPT}" DEPENDS "${CLI_MD}" "${SPLIT_SCRIPT}" COMMENT "Splitting per-command CLI sections from Markdown" VERBATIM ) # 6) Embed per-command verbose help headers set(EMBED_HEADERS) foreach(cmd init add get delete list search config auth stats uninstall migrate browse serve) set(infile "${GEN_DIR}/cli_help_${cmd}.txt") set(outfile "${CMAKE_BINARY_DIR}/generated/cli_help_${cmd}.hpp") add_custom_command( OUTPUT "${outfile}" COMMAND "${CMAKE_COMMAND}" -DINPUT="${infile}" -DOUTPUT="${outfile}" -DSYMBOL=${cmd} -P "${EMBED_SCRIPT}" DEPENDS "${infile}" "${EMBED_SCRIPT}" COMMENT "Embedding ${cmd} CLI help into header" VERBATIM ) list(APPEND EMBED_HEADERS "${outfile}") endforeach() # 7) Aggregate docs target add_custom_target(docs DEPENDS "${CMAKE_BINARY_DIR}/generated/cli_help.hpp" ${EMBED_HEADERS} "${MAN_DIR}/yams.1" ) # 8) Install generated manpage include(GNUInstallDirs) install(FILES "${MAN_DIR}/yams.1" DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" COMPONENT runtime) # 9) Install data files (magic_numbers.json) install(FILES "${CMAKE_SOURCE_DIR}/data/magic_numbers.json" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/yams/data" COMPONENT runtime) # 10) Global compile definition to indicate embedded help is available add_compile_definitions(YAMS_EMBEDDED_VERBOSE_HELP=1) else() if(YAMS_BUILD_DOCS AND NOT PANDOC_EXECUTABLE) message(WARNING "YAMS_BUILD_DOCS=ON but Pandoc was not found. Install Pandoc and re-configure CMake. - macOS: brew install pandoc - Ubuntu/Debian: sudo apt-get update && sudo apt-get install -y pandoc - Fedora: sudo dnf install -y pandoc - Arch: sudo pacman -S pandoc - Windows (PowerShell): choco install pandoc or scoop install pandoc") elseif(YAMS_BUILD_DOCS AND NOT EXISTS "${CLI_MD}") message(WARNING "YAMS_BUILD_DOCS=ON but CLI markdown not found at ${CLI_MD}; skipping docs build.") else() message(STATUS "Docs build disabled; verbose help embedding and manpage generation are skipped.") endif() endif() # Doxygen-based API docs if(YAMS_BUILD_API_DOCS) find_package(Doxygen QUIET) if(DOXYGEN_FOUND) set(DOXYGEN_OUTPUT_DIR "${CMAKE_BINARY_DIR}/docs/api") file(MAKE_DIRECTORY "${DOXYGEN_OUTPUT_DIR}") set(DOXYFILE_IN "${CMAKE_BINARY_DIR}/cmake/Doxyfile.in") set(DOXYFILE_OUT "${CMAKE_BINARY_DIR}/Doxyfile") file(WRITE "${DOXYFILE_IN}" "PROJECT_NAME = \"YAMS\" OUTPUT_DIRECTORY = \"${DOXYGEN_OUTPUT_DIR}\" GENERATE_HTML = YES GENERATE_LATEX = NO QUIET = YES WARN_AS_ERROR = NO INPUT = \"${CMAKE_SOURCE_DIR}/include\" RECURSIVE = YES EXTRACT_PRIVATE = NO ") configure_file("${DOXYFILE_IN}" "${DOXYFILE_OUT}" @ONLY) add_custom_target(docs-api COMMAND "${DOXYGEN_EXECUTABLE}" "${DOXYFILE_OUT}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" COMMENT "Generating API documentation with Doxygen" VERBATIM ) else() message(WARNING "YAMS_BUILD_API_DOCS=ON but Doxygen not found. Install doxygen to enable API docs.") endif() endif() # Configure based on build profile if(YAMS_BUILD_PROFILE STREQUAL "release") message(STATUS "Using RELEASE build profile - CLI and MCP server only") set(YAMS_BUILD_CLI ON CACHE BOOL "" FORCE) set(YAMS_BUILD_MCP_SERVER ON CACHE BOOL "" FORCE) set(YAMS_BUILD_MAINTENANCE_TOOLS OFF CACHE BOOL "" FORCE) set(YAMS_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(YAMS_BUILD_BENCHMARKS OFF CACHE BOOL "" FORCE) set(YAMS_BUILD_STRESS_TESTS OFF CACHE BOOL "" FORCE) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE) endif() elseif(YAMS_BUILD_PROFILE STREQUAL "dev") message(STATUS "Using DEVELOPMENT build profile - all components") set(YAMS_BUILD_CLI ON CACHE BOOL "" FORCE) set(YAMS_BUILD_MCP_SERVER ON CACHE BOOL "" FORCE) set(YAMS_BUILD_MAINTENANCE_TOOLS ON CACHE BOOL "" FORCE) set(YAMS_BUILD_TESTS ON CACHE BOOL "" FORCE) set(YAMS_BUILD_BENCHMARKS ON CACHE BOOL "" FORCE) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) endif() else() message(STATUS "Using CUSTOM build profile - individual options apply") # Set a sensible default build type for single-config generators when unset if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel) endif() endif() # Legacy compatibility set(YAMS_BUILD_TOOLS ${YAMS_BUILD_CLI}) # C++20 feature detection include(CheckCXXSourceCompiles) check_cxx_source_compiles(" #include template concept Integral = std::integral; int main() { return 0; } " YAMS_HAS_CXX20_CONCEPTS) if(NOT YAMS_HAS_CXX20_CONCEPTS) message(WARNING "concepts appear unavailable; features using may be limited") add_compile_definitions(YAMS_HAS_CXX20_CONCEPTS=0) else() add_compile_definitions(YAMS_HAS_CXX20_CONCEPTS=1) message(STATUS "concepts available") endif() # Compiler warnings if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") add_compile_options( -Wall -Wextra -Wpedantic -Wcast-align -Wcast-qual -Wdouble-promotion -Wformat=2 -Wshadow $<$:-Wnon-virtual-dtor> $<$:-Og> $<$:-g3> $<$:-O3> ) # Architecture-specific optimizations if(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64") # Apple Silicon (M1/M2/M3) optimizations if(YAMS_ENABLE_APPLE_SILICON_OPTIMIZATIONS) add_compile_options($<$:-mcpu=apple-m1>) add_compile_options($<$:-fvectorize>) message(STATUS "Apple Silicon optimizations enabled (-mcpu=apple-m1)") endif() elseif(YAMS_ENABLE_NATIVE_OPTIMIZATIONS) # x86_64 and other architectures add_compile_options($<$:-march=native>) if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") add_compile_options($<$:-mtune=native>) endif() message(STATUS "Native optimizations enabled (-march=native)") endif() # Coroutine support if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") add_compile_options(-fcoroutines) elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") if(APPLE OR YAMS_CLANG_USE_LIBCXX) add_compile_options(-stdlib=libc++) add_link_options(-stdlib=libc++) endif() # Apple Clang has coroutines enabled by default in C++20 if(NOT APPLE) add_compile_options(-fcoroutines-ts) endif() endif() # Optional coroutine capability probe (warn-only, runs after flags above) check_cxx_source_compiles(" #include struct task { struct promise_type { task get_return_object() { return {}; } std::suspend_never initial_suspend() noexcept { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() noexcept {} void unhandled_exception() {} }; }; task foo() { co_return; } int main() { foo(); return 0; } " HAVE_CXX20_COROUTINES) if(NOT HAVE_CXX20_COROUTINES) message(WARNING "coroutines appear unsupported with current flags; features requiring may fail to build.") endif() elseif(MSVC) add_compile_options( /W4 /permissive- /Zc:__cplusplus /Zc:preprocessor # For proper __VA_OPT__ support $<$:/Od /RTC1> $<$:/O2 /GL> ) add_link_options($<$:/LTCG>) endif() # Sanitizers if(YAMS_ENABLE_SANITIZERS AND CMAKE_BUILD_TYPE STREQUAL "Debug") if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") add_compile_options(-fsanitize=address,undefined -fno-omit-frame-pointer) add_link_options(-fsanitize=address,undefined) endif() endif() # Code Coverage if(YAMS_ENABLE_COVERAGE) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") add_compile_options(-fprofile-arcs -ftest-coverage) add_link_options(-fprofile-arcs -ftest-coverage) set(COVERAGE_LIBRARIES gcov) elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-fprofile-instr-generate -fcoverage-mapping) add_link_options(-fprofile-instr-generate -fcoverage-mapping) endif() # Find coverage tools find_program(GCOVR_PATH gcovr) find_program(LCOV_PATH lcov) find_program(GENHTML_PATH genhtml) if(GCOVR_PATH) message(STATUS "Found gcovr: ${GCOVR_PATH}") elseif(LCOV_PATH AND GENHTML_PATH) message(STATUS "Found lcov: ${LCOV_PATH}") message(STATUS "Found genhtml: ${GENHTML_PATH}") else() message(WARNING "No coverage report generator found. Install gcovr or lcov for coverage reports.") endif() message(STATUS "Code coverage enabled") endif() # Dependencies include(FetchContent) set(FETCHCONTENT_QUIET FALSE) # OpenSSL if(APPLE) # Help CMake find Homebrew's OpenSSL set(OPENSSL_ROOT_DIR ${OPENSSL_ROOT_DIR} /opt/homebrew/opt/openssl@3 /usr/local/opt/openssl@3) endif() find_package(OpenSSL REQUIRED) message(STATUS "Found OpenSSL: ${OPENSSL_VERSION}") message(STATUS "OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}") message(STATUS "OpenSSL crypto library: ${OPENSSL_CRYPTO_LIBRARY}") # ZLIB (for CRC32 and compression) find_package(ZLIB REQUIRED) message(STATUS "Found ZLIB: ${ZLIB_VERSION_STRING}") # libcurl (HTTP client) find_package(CURL REQUIRED) message(STATUS "Found libcurl: ${CURL_VERSION_STRING}") if(NOT TARGET CURL::libcurl) message(FATAL_ERROR "CURL::libcurl target not found after find_package(CURL)") endif() # Provide a stable interface target for downloader code add_library(yams_curl INTERFACE) target_link_libraries(yams_curl INTERFACE CURL::libcurl) add_library(yams::curl ALIAS yams_curl) install(TARGETS yams_curl EXPORT YamsTargets) # Threads (required for websocketpp) find_package(Threads REQUIRED) # Ncurses detection for Linux builds if(YAMS_BUILD_CLI AND NOT YAMS_USE_CONAN) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") # Look for ncurses with wide character support first find_package(Curses) if(NOT CURSES_FOUND) # Try pkg-config as fallback find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(NCURSES ncursesw) if(NOT NCURSES_FOUND) pkg_check_modules(NCURSES ncurses) endif() endif() if(NOT NCURSES_FOUND) message(FATAL_ERROR "ncurses library not found. Please install it:\n" " Ubuntu/Debian: sudo apt-get install libncurses-dev\n" " RHEL/CentOS: sudo yum install ncurses-devel\n" " Fedora: sudo dnf install ncurses-devel\n" " Arch: sudo pacman -S ncurses") endif() else() message(STATUS "Found Curses: ${CURSES_LIBRARIES}") endif() endif() endif() # Google Test - only use FetchContent when not using Conan if(YAMS_BUILD_TESTS AND NOT YAMS_USE_CONAN) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.14.0 ) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) endif() # Google Benchmark # Handle benchmarks - only use FetchContent when not using Conan if(YAMS_BUILD_BENCHMARKS AND NOT YAMS_USE_CONAN) FetchContent_Declare( benchmark GIT_REPOSITORY https://github.com/google/benchmark.git GIT_TAG v1.8.3 ) set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) # Disable werror for benchmark library to avoid strict warning issues set(BENCHMARK_ENABLE_WERROR OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(benchmark) # Apply warning suppressions to benchmark target after it's created if(TARGET benchmark) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(benchmark PRIVATE -Wno-sign-conversion -Wno-format-nonliteral -Wno-double-promotion -Wno-conversion -Wno-old-style-cast) endif() endif() if(TARGET benchmark_main) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(benchmark_main PRIVATE -Wno-sign-conversion -Wno-format-nonliteral -Wno-double-promotion -Wno-conversion -Wno-old-style-cast) endif() endif() endif() # Dependency management if(YAMS_USE_CONAN) # When using Conan, dependencies are provided via find_package find_package(spdlog REQUIRED) find_package(nlohmann_json REQUIRED) find_package(CLI11 REQUIRED) find_package(ZLIB REQUIRED) find_package(zstd REQUIRED) find_package(SQLite3 REQUIRED) find_package(Protobuf REQUIRED) find_package(OpenSSL REQUIRED) find_package(onnxruntime REQUIRED) # fmt for std::format fallback if(NOT YAMS_HAS_STD_FORMAT) find_package(fmt REQUIRED) endif() if(YAMS_BUILD_CLI) find_package(Curses REQUIRED) endif() # MCP server no longer requires Drogon or Boost if(YAMS_BUILD_TESTS) # Try to find GTest - Conan generates both gtest and GTest configs # But we should use the one that doesn't cause ALIAS target errors if(EXISTS "${CMAKE_BINARY_DIR}/generators/gtestConfig.cmake") # Use lowercase gtest from Conan find_package(gtest REQUIRED) message(STATUS "Conan build: Found gtest package") else() # Try uppercase GTest find_package(GTest REQUIRED) message(STATUS "Conan build: Found GTest package") endif() # Check which targets are available if(TARGET gtest::gtest) message(STATUS "Conan build: gtest::gtest target is available") endif() if(TARGET GTest::gtest) message(STATUS "Conan build: GTest::gtest target is available") endif() if(TARGET GTest::GTest) message(STATUS "Conan build: GTest::GTest target is available") endif() endif() if(YAMS_BUILD_BENCHMARKS) find_package(benchmark REQUIRED) endif() endif() # When not using Conan, use FetchContent for dependencies message(STATUS "YAMS_USE_CONAN is: ${YAMS_USE_CONAN}") if(NOT YAMS_USE_CONAN) message(STATUS "Using FetchContent for dependencies (non-Conan mode)") # fmt for std::format fallback when not available if(NOT YAMS_HAS_STD_FORMAT) FetchContent_Declare( fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 10.2.1 ) set(FMT_INSTALL OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(fmt) message(STATUS "Using fmt library as std::format fallback") endif() # spdlog for logging FetchContent_Declare( spdlog GIT_REPOSITORY https://github.com/gabime/spdlog.git GIT_TAG v1.12.0 ) # Configure spdlog for proper installation # Enable installation so spdlog can be exported with YAMS targets set(SPDLOG_INSTALL ON CACHE BOOL "" FORCE) set(SPDLOG_BUILD_SHARED OFF CACHE BOOL "" FORCE) set(SPDLOG_FMT_EXTERNAL OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(spdlog) # nlohmann_json for JSON parsing find_package(nlohmann_json 3.11 QUIET) if(NOT nlohmann_json_FOUND) FetchContent_Declare( nlohmann_json GIT_REPOSITORY https://github.com/nlohmann/json.git GIT_TAG v3.11.3 ) FetchContent_MakeAvailable(nlohmann_json) # Export nlohmann_json with YamsTargets for downstream consumers install(TARGETS nlohmann_json EXPORT YamsTargets ) endif() # Install nlohmann_json headers (it's header-only) install(DIRECTORY ${nlohmann_json_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) # Boost for MCP server (Beast/ASIO for WebSocket support) if(YAMS_BUILD_MCP_SERVER) message(STATUS "Fetching Boost headers for MCP server") FetchContent_Declare( Boost URL https://github.com/boostorg/boost/releases/download/boost-1.84.0/boost-1.84.0.tar.xz URL_HASH SHA256=2e64e5d79a738d0fa6fb546c6e5c2bd28f88d268a2a080546f74e5ff98f29d0e DOWNLOAD_EXTRACT_TIMESTAMP ON ) FetchContent_MakeAvailable(Boost) # Boost automatically creates boost_headers target, just need to make it available # The Boost::headers alias should already exist from the Boost CMake configuration if(NOT TARGET Boost::headers) # Only create alias if it doesn't exist if(TARGET boost_headers) add_library(Boost::headers ALIAS boost_headers) else() # Fallback: create our own interface library add_library(yams_boost_headers INTERFACE) target_include_directories(yams_boost_headers SYSTEM INTERFACE ${boost_SOURCE_DIR}) add_library(Boost::headers ALIAS yams_boost_headers) endif() endif() endif() endif() # CLI11 for command line parsing if(YAMS_BUILD_TOOLS) if(NOT YAMS_USE_CONAN) FetchContent_Declare( CLI11 GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git GIT_TAG v2.4.1 ) FetchContent_MakeAvailable(CLI11) endif() # ImTUI for Terminal User Interface (Immediate Mode TUI) if(YAMS_BUILD_CLI AND YAMS_ENABLE_TUI) if(YAMS_USE_CONAN) # Use FetchContent for ImTUI with Conan as well, and provide a stable wrapper target find_package(Curses REQUIRED) # Workaround: imtui's CMake checks tinfo when CURSES_NCURSES_HAS_NODELAY is false, # which can fail with CURSES_EXTRA_LIBRARY=NOTFOUND on some Conan/system combos. # Seed the result to skip the tinfo probe; we still link Curses::Curses explicitly. set(CURSES_NCURSES_HAS_NODELAY TRUE CACHE BOOL "Assume ncurses provides nodelay; avoid tinfo probe" FORCE) FetchContent_Declare( imtui GIT_REPOSITORY https://github.com/ggerganov/imtui.git GIT_TAG master GIT_SHALLOW TRUE GIT_SUBMODULES_RECURSE TRUE ) set(IMTUI_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(imtui) # Ensure consistent target name and link against Curses if(TARGET imtui-ncurses) target_link_libraries(imtui-ncurses INTERFACE Curses::Curses) elseif(TARGET imtui::imtui-ncurses) add_library(imtui-ncurses INTERFACE) target_link_libraries(imtui-ncurses INTERFACE imtui::imtui-ncurses Curses::Curses) else() message(FATAL_ERROR "ImTUI target 'imtui-ncurses' not found after FetchContent.") endif() # Platform-specific link adjustments for ImTUI if(APPLE) target_link_libraries(imtui-ncurses INTERFACE "-framework System") endif() if(UNIX AND NOT APPLE) # Link to tinfo if it exists as a separate library (common on Linux) find_library(TINFO_LIBRARY NAMES tinfo tinfow) if(TINFO_LIBRARY) target_link_libraries(imtui-ncurses INTERFACE "${TINFO_LIBRARY}") endif() endif() else() # Use FetchContent for non-Conan builds FetchContent_Declare( imtui GIT_REPOSITORY https://github.com/ggerganov/imtui.git GIT_TAG master GIT_SHALLOW TRUE GIT_SUBMODULES_RECURSE TRUE ) set(IMTUI_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(imtui) # Ensure the wrapper target exists and link ncurses and tinfo as needed if(TARGET imtui-ncurses) if(TARGET Curses::Curses) target_link_libraries(imtui-ncurses INTERFACE Curses::Curses) elseif(DEFINED CURSES_LIBRARIES) target_link_libraries(imtui-ncurses INTERFACE ${CURSES_LIBRARIES}) endif() if(UNIX AND NOT APPLE) find_library(TINFO_LIBRARY NAMES tinfo tinfow) if(TINFO_LIBRARY) target_link_libraries(imtui-ncurses INTERFACE "${TINFO_LIBRARY}") endif() endif() endif() endif() endif() endif() # Boost.Beast is part of Boost and will be available automatically # No separate dependency needed for WebSocket transport # Zstandard for compression - only if not using Conan if(NOT YAMS_USE_CONAN) FetchContent_Declare( zstd URL https://github.com/facebook/zstd/releases/download/v1.5.6/zstd-1.5.6.tar.gz URL_HASH SHA256=8c29e06cf42aacc1eafc4077ae2ec6c6fcb96a626157e0593d5e82a34fd403c1 SOURCE_SUBDIR build/cmake ) set(ZSTD_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE) set(ZSTD_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(ZSTD_BUILD_SHARED OFF CACHE BOOL "" FORCE) set(ZSTD_BUILD_STATIC ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(zstd) endif() # LZMA SDK for compression - always needed since no good Conan package exists # if(NOT YAMS_USE_CONAN) FetchContent_Declare( lzma URL https://github.com/ip7z/7zip/releases/download/24.08/7z2408-src.tar.xz URL_HASH SHA256=aa04aac906a04df59e7301f4c69e9f48808e6c8ecae4eb697703a47bfb0ac042 DOWNLOAD_EXTRACT_TIMESTAMP TRUE ) FetchContent_GetProperties(lzma) if(NOT lzma_POPULATED) FetchContent_Populate(lzma) # Create LZMA library from SDK sources add_library(lzma_sdk STATIC ${lzma_SOURCE_DIR}/C/LzmaEnc.c ${lzma_SOURCE_DIR}/C/LzmaDec.c ${lzma_SOURCE_DIR}/C/LzFind.c ${lzma_SOURCE_DIR}/C/Lzma2Enc.c ${lzma_SOURCE_DIR}/C/Lzma2Dec.c ${lzma_SOURCE_DIR}/C/Lzma86Enc.c ${lzma_SOURCE_DIR}/C/Lzma86Dec.c ${lzma_SOURCE_DIR}/C/CpuArch.c ${lzma_SOURCE_DIR}/C/Alloc.c ${lzma_SOURCE_DIR}/C/LzFindMt.c ${lzma_SOURCE_DIR}/C/Threads.c ${lzma_SOURCE_DIR}/C/MtCoder.c ${lzma_SOURCE_DIR}/C/MtDec.c ${lzma_SOURCE_DIR}/C/Lzma2DecMt.c ${lzma_SOURCE_DIR}/C/LzFindOpt.c ${lzma_SOURCE_DIR}/C/7zStream.c ) target_include_directories(lzma_sdk PUBLIC $ $ ) # Enable multi-threaded support for LZMA SDK # Disable warnings for third-party code if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(lzma_sdk PRIVATE -w) elseif(MSVC) target_compile_options(lzma_sdk PRIVATE /w) endif() endif() # endif() # SQLite3 with FTS5 - only if not using Conan if(NOT YAMS_USE_CONAN) FetchContent_Declare( sqlite3 URL https://www.sqlite.org/2024/sqlite-amalgamation-3460100.zip URL_HASH SHA256=77823cb110929c2bcb0f5d48e4833b5c59a8a6e40cdea3936b99e199dbbe5784 DOWNLOAD_EXTRACT_TIMESTAMP TRUE ) FetchContent_GetProperties(sqlite3) if(NOT sqlite3_POPULATED) FetchContent_Populate(sqlite3) # Create SQLite library with FTS5 enabled add_library(sqlite3 STATIC ${sqlite3_SOURCE_DIR}/sqlite3.c ) target_include_directories(sqlite3 PUBLIC $ $ ) # Enable FTS5 and other useful extensions target_compile_definitions(sqlite3 PRIVATE SQLITE_ENABLE_FTS5=1 SQLITE_ENABLE_RTREE=1 SQLITE_ENABLE_JSON1=1 SQLITE_ENABLE_STAT4=1 SQLITE_ENABLE_UPDATE_DELETE_LIMIT=1 SQLITE_ENABLE_COLUMN_METADATA=1 SQLITE_DEFAULT_MEMSTATUS=0 SQLITE_DEFAULT_WAL_SYNCHRONOUS=1 SQLITE_LIKE_DOESNT_MATCH_BLOBS=1 SQLITE_MAX_EXPR_DEPTH=0 SQLITE_OMIT_DEPRECATED=1 SQLITE_OMIT_SHARED_CACHE=1 SQLITE_USE_ALLOCA=1 SQLITE_THREADSAFE=2 ) # Disable warnings for third-party code if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(sqlite3 PRIVATE -w) elseif(MSVC) target_compile_options(sqlite3 PRIVATE /w) endif() endif() endif() # PDFium for PDF text extraction option(YAMS_ENABLE_PDF "Enable PDF text extraction support" ON) # macOS runtime search path defaults for installed binaries if(APPLE) set(CMAKE_MACOSX_RPATH ON) # Ensure installed executables/DYLIBs can find dependencies in lib/ set(CMAKE_INSTALL_RPATH "@loader_path/../lib") endif() # sqlite-vec extension for vector similarity search # This provides fast KNN search for embeddings with minimal dependencies message(STATUS "Fetching sqlite-vec for vector search support") FetchContent_Declare( sqlite_vec GIT_REPOSITORY https://github.com/asg017/sqlite-vec.git GIT_TAG v0.1.3 # Latest stable version as of Aug 2024 GIT_SHALLOW TRUE ) FetchContent_GetProperties(sqlite_vec) if(NOT sqlite_vec_POPULATED) FetchContent_Populate(sqlite_vec) # Export the source directory for use by yams_vector set(sqlite_vec_SOURCE_DIR ${sqlite_vec_SOURCE_DIR} CACHE INTERNAL "sqlite-vec source directory") message(STATUS "sqlite-vec source fetched successfully to ${sqlite_vec_SOURCE_DIR}") endif() if(YAMS_ENABLE_PDF) message(STATUS "Configuring PDFium for PDF support") # Try to find PDFium from Conan first if using Conan set(PDFium_FOUND FALSE) if(YAMS_USE_CONAN) message(STATUS "Attempting to find PDFium via Conan...") find_package(PDFium QUIET) if(PDFium_FOUND) message(STATUS "PDFium found via Conan") add_library(pdfium::pdfium ALIAS PDFium::PDFium) else() message(STATUS "PDFium not available via Conan, falling back to FetchContent") endif() endif() # If PDFium not found via Conan, use FetchContent (both Conan and non-Conan builds) if(NOT PDFium_FOUND) # Determine platform and architecture if(APPLE) # More robust architecture detection for macOS if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64" OR CMAKE_OSX_ARCHITECTURES MATCHES "arm64") set(PDFIUM_PLATFORM "mac-arm64") message(STATUS "Detected macOS ARM64 architecture for PDFium") else() set(PDFIUM_PLATFORM "mac-x64") message(STATUS "Detected macOS x64 architecture for PDFium") endif() elseif(UNIX) if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") set(PDFIUM_PLATFORM "linux-arm64") else() set(PDFIUM_PLATFORM "linux-x64") endif() elseif(WIN32) if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(PDFIUM_PLATFORM "win-x64") else() set(PDFIUM_PLATFORM "win-x86") endif() endif() # Use prebuilt binaries from pdfium-binaries project # Latest release: PDFium 141.0.7350.0 (chromium/7350) FetchContent_Declare( pdfium URL https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F7350/pdfium-${PDFIUM_PLATFORM}.tgz DOWNLOAD_EXTRACT_TIMESTAMP TRUE ) FetchContent_GetProperties(pdfium) if(NOT pdfium_POPULATED) message(STATUS "Downloading PDFium binaries for ${PDFIUM_PLATFORM}...") FetchContent_Populate(pdfium) # Verify PDFium was downloaded correctly if(NOT EXISTS "${pdfium_SOURCE_DIR}/include/fpdfview.h") message(WARNING "PDFium download failed or incomplete. PDF support will be disabled.") set(YAMS_ENABLE_PDF OFF) return() endif() # Additional verification for library file if(APPLE) if(NOT EXISTS "${pdfium_SOURCE_DIR}/lib/libpdfium.dylib") message(WARNING "PDFium library not found at ${pdfium_SOURCE_DIR}/lib/libpdfium.dylib") message(WARNING "PDF support will be disabled.") set(YAMS_ENABLE_PDF OFF CACHE BOOL "Enable PDF text extraction support" FORCE) set(YAMS_PDFIUM_FOUND FALSE CACHE BOOL "PDFium library found and configured" FORCE) else() # Verify it's the correct architecture execute_process( COMMAND file "${pdfium_SOURCE_DIR}/lib/libpdfium.dylib" OUTPUT_VARIABLE DYLIB_INFO OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET ) message(STATUS "PDFium library info: ${DYLIB_INFO}") # Check if the architecture matches what we expect if(CMAKE_OSX_ARCHITECTURES MATCHES "arm64" AND NOT DYLIB_INFO MATCHES "arm64") message(WARNING "PDFium library architecture mismatch - expected arm64") message(WARNING "PDF support will be disabled for this build") set(YAMS_ENABLE_PDF OFF CACHE BOOL "Enable PDF text extraction support" FORCE) set(YAMS_PDFIUM_FOUND FALSE CACHE BOOL "PDFium library found and configured" FORCE) elseif(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" AND NOT DYLIB_INFO MATCHES "x86_64") message(WARNING "PDFium library architecture mismatch - expected x86_64") message(WARNING "PDF support will be disabled for this build") set(YAMS_ENABLE_PDF OFF CACHE BOOL "Enable PDF text extraction support" FORCE) set(YAMS_PDFIUM_FOUND FALSE CACHE BOOL "PDFium library found and configured" FORCE) endif() endif() endif() # Only create imported target if PDF is still enabled if(NOT YAMS_ENABLE_PDF) message(STATUS "Skipping PDFium target creation due to disabled PDF support") return() endif() # Create imported target for PDFium add_library(pdfium SHARED IMPORTED GLOBAL) # Set include directories set_target_properties(pdfium PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${pdfium_SOURCE_DIR}/include" ) # Set library location based on platform if(APPLE) set_target_properties(pdfium PROPERTIES IMPORTED_LOCATION "${pdfium_SOURCE_DIR}/lib/libpdfium.dylib" ) # Ensure @rpath install_name on the fetched dylib for proper link-time embedding execute_process(COMMAND install_name_tool -id @rpath/libpdfium.dylib "${pdfium_SOURCE_DIR}/lib/libpdfium.dylib") elseif(UNIX) set_target_properties(pdfium PROPERTIES IMPORTED_LOCATION "${pdfium_SOURCE_DIR}/lib/libpdfium.so" ) elseif(WIN32) set_target_properties(pdfium PROPERTIES IMPORTED_LOCATION "${pdfium_SOURCE_DIR}/bin/pdfium.dll" IMPORTED_IMPLIB "${pdfium_SOURCE_DIR}/lib/pdfium.lib" ) endif() # Create alias for consistent naming add_library(pdfium::pdfium ALIAS pdfium) # Install the prebuilt PDFium library based on platform if(APPLE) install(FILES "${pdfium_SOURCE_DIR}/lib/libpdfium.dylib" DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT runtime) elseif(UNIX) install(FILES "${pdfium_SOURCE_DIR}/lib/libpdfium.so" DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT runtime) elseif(WIN32) install(FILES "${pdfium_SOURCE_DIR}/bin/pdfium.dll" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime) install(FILES "${pdfium_SOURCE_DIR}/lib/pdfium.lib" DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT development) endif() if(APPLE) # Ensure consumers load PDFium via @rpath; installed binaries use @loader_path/../lib install(CODE "execute_process(COMMAND install_name_tool -id @rpath/libpdfium.dylib \"${CMAKE_INSTALL_FULL_LIBDIR}/libpdfium.dylib\")") # Patch installed executables to reference @rpath/libpdfium.dylib and ensure rpath includes ../lib install(CODE " set(_bins yams yams-mcp-server) foreach(_bin IN LISTS _bins) set(_path \"${CMAKE_INSTALL_FULL_BINDIR}/${_bin}\") if(EXISTS \"${_path}\") execute_process(COMMAND install_name_tool -change libpdfium.dylib @rpath/libpdfium.dylib \"${_path}\") execute_process(COMMAND install_name_tool -change ./libpdfium.dylib @rpath/libpdfium.dylib \"${_path}\") execute_process(COMMAND install_name_tool -add_rpath \"@loader_path/../lib\" \"${_path}\") endif() endforeach() ") endif() message(STATUS "PDFium ${PDFIUM_PLATFORM} successfully configured") set(YAMS_PDFIUM_FOUND TRUE CACHE BOOL "PDFium library found and configured") endif() # end if(NOT pdfium_POPULATED) endif() # end if(NOT PDFium_FOUND) else() message(STATUS "PDFium support disabled (YAMS_ENABLE_PDF=OFF)") set(YAMS_PDFIUM_FOUND FALSE CACHE BOOL "PDFium library found and configured") endif() # Protocol Buffers (optional, for manifest serialization) find_package(Protobuf QUIET) if(Protobuf_FOUND) message(STATUS "Found Protobuf: ${Protobuf_VERSION}") # Find Abseil (required by modern Protocol Buffers) find_package(absl QUIET) if(absl_FOUND) message(STATUS "Found Abseil for Protocol Buffers") else() # Try to find Abseil through pkg-config find_package(PkgConfig QUIET) if(PkgConfig_FOUND) pkg_check_modules(ABSL absl_base absl_strings absl_log absl_log_internal) if(ABSL_FOUND) message(STATUS "Found Abseil via pkg-config") endif() endif() endif() else() message(STATUS "Protobuf not found, using custom serialization") endif() # Create core library add_library(yams_core INTERFACE) target_include_directories(yams_core INTERFACE $ $ ) target_compile_features(yams_core INTERFACE cxx_std_20) add_library(yams::core ALIAS yams_core) # Add subdirectories add_subdirectory(src) if(YAMS_BUILD_TESTS) enable_testing() message(STATUS "Building tests: YAMS_USE_CONAN=${YAMS_USE_CONAN}") # Use FetchContent for GTest only when not using Conan if(NOT YAMS_USE_CONAN) message(STATUS "Non-Conan build: Using FetchContent for GoogleTest") include(FetchContent) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.14.0 GIT_SHALLOW TRUE ) # Prevent overriding the parent project's compiler/linker settings on Windows set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) # Create alias targets for compatibility with find_package(GTest) if(TARGET gtest AND NOT TARGET GTest::gtest) add_library(GTest::gtest ALIAS gtest) endif() if(TARGET gtest_main AND NOT TARGET GTest::gtest_main) add_library(GTest::gtest_main ALIAS gtest_main) endif() if(TARGET gmock AND NOT TARGET GTest::gmock) add_library(GTest::gmock ALIAS gmock) endif() if(TARGET gmock_main AND NOT TARGET GTest::gmock_main) add_library(GTest::gmock_main ALIAS gmock_main) endif() endif() add_subdirectory(tests) endif() # CTest is included for test discovery include(CTest) # Build tools based on options if(YAMS_BUILD_CLI OR YAMS_BUILD_MCP_SERVER OR YAMS_BUILD_MAINTENANCE_TOOLS) add_subdirectory(tools) endif() # Installation include(GNUInstallDirs) include(CMakePackageConfigHelpers) # --------------------------------------------------------------------------- # RPATH best practices (for installed targets on macOS/Linux) # --------------------------------------------------------------------------- # Use build RPATH so binaries run from the build tree set(CMAKE_SKIP_BUILD_RPATH FALSE) set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) # Add install directories to the install RPATH set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) # macOS specific if(APPLE) set(CMAKE_MACOSX_RPATH ON) endif() # If the install libdir isn't a system dir, add it to the install RPATH list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_LIBDIR}" _isSys) if(_isSys EQUAL -1) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") endif() # --------------------------------------------------------------------------- # Uninstall target (uses install_manifest.txt generated by "make install") # --------------------------------------------------------------------------- set(UNINSTALL_SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") file(WRITE "${UNINSTALL_SCRIPT}" "if(NOT EXISTS \"${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\")\n message(FATAL_ERROR \"Cannot find install manifest: ${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\")\nendif()\nfile(READ \"${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt\" _files)\nstring(REPLACE \"\\n\" \";\" _files \"${_files}\")\nforeach(_file IN LISTS _files)\n if(_file STREQUAL \"\")\n continue()\n endif()\n if(EXISTS \"${_file}\" OR IS_SYMLINK \"${_file}\")\n file(REMOVE \"${_file}\")\n endif()\nendforeach()\n") if(NOT TARGET uninstall) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P "${UNINSTALL_SCRIPT}" COMMENT "Removing installed files listed in install_manifest.txt" ) endif() # Package config (install location configurable) # Export targets set(YAMS_EXPORT_TARGETS yams_core yams_indexing) # Always export lzma_sdk since we always build it if(TARGET lzma_sdk) list(APPEND YAMS_EXPORT_TARGETS lzma_sdk) endif() # Only export sqlite3 if we built it (non-Conan) if(NOT YAMS_USE_CONAN) if(TARGET sqlite3) list(APPEND YAMS_EXPORT_TARGETS sqlite3) endif() # Note: sqlite_vec is not exported as it's an implementation detail endif() install(TARGETS ${YAMS_EXPORT_TARGETS} EXPORT YamsTargets ) install(EXPORT YamsTargets FILE YamsTargets.cmake NAMESPACE yams:: DESTINATION ${YAMS_CMAKE_PACKAGE_DIR} ) # Install headers install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT development ) # Install data files install(FILES data/magic_numbers.json DESTINATION ${CMAKE_INSTALL_DATADIR}/yams/data COMPONENT runtime ) install(FILES sql/reference_schema.sql DESTINATION ${CMAKE_INSTALL_DATADIR}/yams/sql COMPONENT runtime ) # CPack configuration for creating packages set(CPACK_PACKAGE_NAME "YAMS") set(CPACK_PACKAGE_VENDOR "YAMS Project") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Yet Another Memory System - High-performance content-addressed storage") set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") # Components set(CPACK_COMPONENTS_ALL runtime maintenance development) set(CPACK_COMPONENT_RUNTIME_DISPLAY_NAME "Runtime") set(CPACK_COMPONENT_RUNTIME_DESCRIPTION "YAMS CLI and MCP server") set(CPACK_COMPONENT_MAINTENANCE_DISPLAY_NAME "Maintenance Tools") set(CPACK_COMPONENT_MAINTENANCE_DESCRIPTION "Garbage collection and statistics tools") set(CPACK_COMPONENT_DEVELOPMENT_DISPLAY_NAME "Development") set(CPACK_COMPONENT_DEVELOPMENT_DESCRIPTION "Headers and libraries for development") # Package generation if(YAMS_BUILD_PROFILE STREQUAL "release") set(CPACK_COMPONENTS_ALL runtime) endif() # Platform-specific packaging if(UNIX AND NOT APPLE) # Determine Linux distribution find_program(LSB_RELEASE_EXEC lsb_release) if(LSB_RELEASE_EXEC) execute_process(COMMAND ${LSB_RELEASE_EXEC} -is OUTPUT_VARIABLE LSB_RELEASE_ID OUTPUT_STRIP_TRAILING_WHITESPACE ) endif() # DEB packaging for Debian/Ubuntu if(LSB_RELEASE_ID MATCHES "Debian|Ubuntu") set(CPACK_GENERATOR "DEB;TGZ") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "YAMS Team") set(CPACK_DEBIAN_PACKAGE_SECTION "utils") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl3, libstdc++6, libgcc-s1, zlib1g, libzstd1, liblzma5, libsqlite3-0") set(CPACK_DEBIAN_PACKAGE_SUGGESTS "libmagic1") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/trvon/yams") # RPM packaging for RedHat/Fedora/CentOS elseif(LSB_RELEASE_ID MATCHES "RedHat|Fedora|CentOS|Rocky|AlmaLinux") set(CPACK_GENERATOR "RPM;TGZ") set(CPACK_RPM_PACKAGE_GROUP "Applications/System") set(CPACK_RPM_PACKAGE_LICENSE "MIT") set(CPACK_RPM_PACKAGE_URL "https://github.com/trvon/yams") set(CPACK_RPM_PACKAGE_REQUIRES "openssl, libstdc++, libgcc, zlib, zstd, xz-libs, sqlite") set(CPACK_RPM_PACKAGE_SUGGESTS "file-libs") else() # Generic Linux packaging set(CPACK_GENERATOR "TGZ;STGZ") endif() # AppImage support option(YAMS_BUILD_APPIMAGE "Build AppImage package" OFF) if(YAMS_BUILD_APPIMAGE) list(APPEND CPACK_GENERATOR "External") set(CPACK_EXTERNAL_PACKAGE_SCRIPT "${CMAKE_SOURCE_DIR}/packaging/appimage/build_appimage.cmake") endif() elseif(APPLE) set(CPACK_GENERATOR "DragNDrop;TGZ") set(CPACK_DMG_VOLUME_NAME "YAMS") set(CPACK_DMG_FORMAT "UDZO") elseif(WIN32) set(CPACK_GENERATOR "NSIS;ZIP") set(CPACK_NSIS_DISPLAY_NAME "YAMS") set(CPACK_NSIS_PACKAGE_NAME "YAMS") set(CPACK_NSIS_URL_INFO_ABOUT "https://github.com/trvon/yams") endif() include(CPack) # Create package config configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/YamsConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/YamsConfig.cmake" INSTALL_DESTINATION ${YAMS_CMAKE_PACKAGE_DIR} ) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/YamsConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/YamsConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/YamsConfigVersion.cmake" DESTINATION ${YAMS_CMAKE_PACKAGE_DIR} ) # Coverage targets if(YAMS_ENABLE_COVERAGE) # Create coverage directory file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/coverage) # Coverage target using gcovr (preferred) if(GCOVR_PATH) add_custom_target(coverage-html COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/coverage/html COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/coverage/html COMMAND ${GCOVR_PATH} --root ${CMAKE_SOURCE_DIR} --exclude ${CMAKE_SOURCE_DIR}/tests/ --exclude ${CMAKE_SOURCE_DIR}/_deps/ --exclude ${CMAKE_SOURCE_DIR}/build/ --html --html-details --output ${CMAKE_BINARY_DIR}/coverage/html/index.html WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Generating coverage HTML report with gcovr" ) add_custom_target(coverage-xml COMMAND ${GCOVR_PATH} --root ${CMAKE_SOURCE_DIR} --exclude ${CMAKE_SOURCE_DIR}/tests/ --exclude ${CMAKE_SOURCE_DIR}/_deps/ --exclude ${CMAKE_SOURCE_DIR}/build/ --xml --output ${CMAKE_BINARY_DIR}/coverage/coverage.xml WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Generating coverage XML report with gcovr" ) add_custom_target(coverage-summary COMMAND ${GCOVR_PATH} --root ${CMAKE_SOURCE_DIR} --exclude ${CMAKE_SOURCE_DIR}/tests/ --exclude ${CMAKE_SOURCE_DIR}/_deps/ --exclude ${CMAKE_SOURCE_DIR}/build/ WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Displaying coverage summary" ) # Coverage target using lcov (alternative) elseif(LCOV_PATH AND GENHTML_PATH) add_custom_target(coverage-html COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/coverage/html COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/coverage/html COMMAND ${LCOV_PATH} --capture --directory ${CMAKE_BINARY_DIR} --output-file ${CMAKE_BINARY_DIR}/coverage/coverage.info COMMAND ${LCOV_PATH} --remove ${CMAKE_BINARY_DIR}/coverage/coverage.info '${CMAKE_SOURCE_DIR}/tests/*' '${CMAKE_SOURCE_DIR}/_deps/*' '${CMAKE_SOURCE_DIR}/build/*' '/usr/*' '/opt/*' --output-file ${CMAKE_BINARY_DIR}/coverage/coverage.info COMMAND ${GENHTML_PATH} ${CMAKE_BINARY_DIR}/coverage/coverage.info --output-directory ${CMAKE_BINARY_DIR}/coverage/html WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Generating coverage HTML report with lcov" ) add_custom_target(coverage-summary COMMAND ${LCOV_PATH} --summary ${CMAKE_BINARY_DIR}/coverage/coverage.info WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Displaying coverage summary" ) endif() # Combined coverage target that runs tests first if(TARGET coverage-html) add_custom_target(coverage COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target all COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target coverage-html WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Running tests and generating coverage report" ) endif() endif() # ============================================================================= # Code Quality Targets # ============================================================================= # Find cppcheck for static analysis find_program(CPPCHECK_PATH cppcheck) if(CPPCHECK_PATH) message(STATUS "Found cppcheck: ${CPPCHECK_PATH}") # Quality check script path set(QUALITY_SCRIPT "${CMAKE_SOURCE_DIR}/scripts/check-quality.sh") if(EXISTS ${QUALITY_SCRIPT}) # Quick quality check (essential checks only) add_custom_target(quality-quick COMMAND ${QUALITY_SCRIPT} --profile quick --build-dir ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Running quick code quality analysis" ) # Normal quality check (standard checks) add_custom_target(quality COMMAND ${QUALITY_SCRIPT} --profile normal --build-dir ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Running standard code quality analysis" ) # Strict quality check (all checks, warnings as errors) add_custom_target(quality-strict COMMAND ${QUALITY_SCRIPT} --profile strict --build-dir ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Running strict code quality analysis" ) # CI quality check (optimized for continuous integration) add_custom_target(quality-ci COMMAND ${QUALITY_SCRIPT} --profile ci --format xml --output ${CMAKE_BINARY_DIR}/quality-reports/ --build-dir ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Running CI code quality analysis with XML output" ) # Quality check with automatic formatting add_custom_target(quality-format COMMAND ${QUALITY_SCRIPT} --profile normal --fix-format --build-dir ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Running code quality analysis with automatic formatting" ) # Git-aware quality check (only modified files) add_custom_target(quality-git COMMAND ${QUALITY_SCRIPT} --profile normal --git --build-dir ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Running code quality analysis on git-modified files" ) # Generate quality baseline add_custom_target(quality-baseline COMMAND ${QUALITY_SCRIPT} --profile normal --generate-baseline --output ${CMAKE_BINARY_DIR}/quality-reports/ --build-dir ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Generating code quality baseline" ) # Create quality reports directory file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/quality-reports) message(STATUS "Code quality targets enabled:") message(STATUS " quality-quick - Quick essential checks") message(STATUS " quality - Standard quality analysis") message(STATUS " quality-strict - Strict analysis (warnings as errors)") message(STATUS " quality-ci - CI-optimized with XML output") message(STATUS " quality-format - Analysis with auto-formatting") message(STATUS " quality-git - Analysis of git-modified files only") message(STATUS " quality-baseline - Generate quality baseline") else() message(WARNING "Quality script not found: ${QUALITY_SCRIPT}") endif() # Add quality checks to development workflow targets if(TARGET format-code) add_dependencies(quality format-code) endif() else() message(STATUS "cppcheck not found - quality targets disabled") message(STATUS "Install with: brew install cppcheck (macOS) or apt-get install cppcheck (Linux)") endif() # Find clang-format for code formatting find_program(CLANG_FORMAT_PATH clang-format) if(CLANG_FORMAT_PATH AND EXISTS "${CMAKE_SOURCE_DIR}/scripts/format-code.sh") add_custom_target(format-code COMMAND ${CMAKE_SOURCE_DIR}/scripts/format-code.sh WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Formatting code with clang-format" ) add_custom_target(format-check COMMAND ${CMAKE_SOURCE_DIR}/scripts/format-code.sh --check WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Checking code formatting" ) message(STATUS "Code formatting targets enabled:") message(STATUS " format-code - Format all C++ files") message(STATUS " format-check - Check if formatting is needed") endif() # Combined development targets if(CPPCHECK_PATH AND EXISTS ${QUALITY_SCRIPT}) # Full development check: format + quality + tests add_custom_target(dev-check COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target all COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --parallel WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Running full development checks (build + test + quality)" ) if(TARGET format-code) add_dependencies(dev-check format-code) endif() if(TARGET quality) add_dependencies(dev-check quality) endif() # Pre-commit hook target add_custom_target(pre-commit COMMAND ${QUALITY_SCRIPT} --profile normal --git --fix-format --build-dir ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Running pre-commit quality checks and formatting" ) message(STATUS "Development workflow targets:") message(STATUS " dev-check - Full development check (build + test + quality)") message(STATUS " pre-commit - Pre-commit checks (git files only, with formatting)") endif() # CPack configuration set(CPACK_PACKAGE_VENDOR "YAMS Project") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_DESCRIPTION}) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) # CPack already included above at line 1306 # Generate and install CMake package config for find_package(Yams) include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/YamsConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/YamsConfig.cmake" INSTALL_DESTINATION ${YAMS_CMAKE_PACKAGE_DIR} ) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/YamsConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/YamsConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/YamsConfigVersion.cmake" DESTINATION ${YAMS_CMAKE_PACKAGE_DIR} )