# vim: set ts=4:sw=4:expandtab: cmake_minimum_required(VERSION 3.14) if (POLICY CMP0048) cmake_policy(SET CMP0048 NEW) # Allow project(xxx VERSION a.b.c) endif() if (POLICY CMP0074) cmake_policy(SET CMP0074 NEW) # find_package uses _ROOT variables endif() if (POLICY CMP0110) cmake_policy(SET CMP0110 NEW) # add_test() supports arbitrary characters in test names endif() # Get version from git-version-gen if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.tarball-version") file(READ "${CMAKE_CURRENT_SOURCE_DIR}/.tarball-version" PROJECT_VERSION) string(STRIP "${PROJECT_VERSION}" PROJECT_VERSION) else() execute_process( COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/build-aux/git-version-gen" "${CMAKE_CURRENT_SOURCE_DIR}/.tarball-version" OUTPUT_VARIABLE PROJECT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) endif() # Parse version components string(REGEX MATCH "^([0-9]+)(\\.([0-9]+)(\\.([0-9]+))?)?(-(.*))?$" _ "${PROJECT_VERSION}") set(VERSION_MAJOR "${CMAKE_MATCH_1}") if(CMAKE_MATCH_3) set(VERSION_MINOR "${CMAKE_MATCH_3}") else() set(VERSION_MINOR "0") endif() if(CMAKE_MATCH_5) set(VERSION_PATCH "${CMAKE_MATCH_5}") else() set(VERSION_PATCH "0") endif() if(CMAKE_MATCH_7) set(VERSION_EXTRA "${CMAKE_MATCH_7}") else() set(VERSION_EXTRA "") endif() # Optional MSVC cross-compilation support via msvc-wine # Usage: cmake -DMSVC_WINE_DIR=/path/to/msvc [-DMSVC_WINE_COMPILER=cl|clang-cl|clang] .. include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/MSVCCrossCompile.cmake OPTIONAL) project(libfyaml LANGUAGES C ASM VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) # Export compile commands by default for clangd LSP support set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Must use GNUInstallDirs to install libraries into correct locations on all platforms include(GNUInstallDirs) include(CheckIncludeFile) include(CheckFunctionExists) include(CheckCSourceCompiles) include(CheckCCompilerFlag) include(CheckLibraryExists) include(CheckSymbolExists) include(CMakeDependentOption) include(CTest) include(TestBigEndian) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CheckLibClang.cmake) # does this CMake version understand generator expressions? if(${CMAKE_VERSION} VERSION_LESS "3.27") set(HAVE_CMAKE_GEXPR 0) else() set(HAVE_CMAKE_GEXPR 1) endif() # Macro to add libclang support to a target macro(add_libclang_to_target target_name link_scope) if(HAVE_LIBCLANG) # Split LIBCLANG_CFLAGS into list and add as compile options string(REPLACE " " ";" LIBCLANG_CFLAGS_LIST "${LIBCLANG_CFLAGS}") target_compile_options(${target_name} PRIVATE ${LIBCLANG_CFLAGS_LIST}) # Split LIBCLANG_LDFLAGS into list and add as link directories/options # LIBCLANG_LDFLAGS typically contains -L/path/to/lib and -Wl,flags # Extract only the -L paths string(REGEX MATCHALL "-L([^ ]+)" LIBCLANG_L_FLAGS "${LIBCLANG_LDFLAGS}") set(LIBCLANG_LINK_DIRS_LOCAL "") foreach(flag ${LIBCLANG_L_FLAGS}) string(REGEX REPLACE "^-L" "" dir "${flag}") list(APPEND LIBCLANG_LINK_DIRS_LOCAL "${dir}") endforeach() if(LIBCLANG_LINK_DIRS_LOCAL) target_link_directories(${target_name} ${link_scope} ${LIBCLANG_LINK_DIRS_LOCAL}) endif() # Add include directories (LIBCLANG_INCLUDES covers cases where the # path is not embedded as -I flags in LIBCLANG_CFLAGS, e.g. direct path) if(LIBCLANG_INCLUDES) target_include_directories(${target_name} PRIVATE ${LIBCLANG_INCLUDES}) endif() # Add libclang libraries target_link_libraries(${target_name} ${link_scope} ${LIBCLANG_LIBS}) endif() endmacro() function(link_whole_archive_static target_name static_target) if(MSVC) target_link_libraries(${target_name} PRIVATE ${static_target}) elseif(APPLE) target_link_libraries(${target_name} PRIVATE -Wl,-force_load,$ ) elseif(WIN32) target_link_libraries(${target_name} PRIVATE ${static_target}) target_link_options(${target_name} PRIVATE "LINKER:/WHOLEARCHIVE:$" ) else() target_link_libraries(${target_name} PRIVATE -Wl,--whole-archive ${static_target} -Wl,--no-whole-archive ) endif() endfunction() # Options option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(ENABLE_PORTABLE_TARGET "Enable portable mode (disable per-target optimizations)" OFF) option(ENABLE_STATIC_TOOLS "Tools will be compiled as static executables" OFF) option(ENABLE_ASAN "Enable ASAN support" OFF) option(ENABLE_NETWORK "Enable tests requiring network access" ON) option(ENABLE_DEVMODE "Enable development mode only debugging" OFF) option(BUILD_TESTING "Build tests" ON) option(ENABLE_GENERIC "Enable generic subsystem (requires statement expressions)" ON) option(ENABLE_REFLECTION "Enable reflection subsystem (requires statement expressions)" ON) option(ENABLE_LIBCLANG "Enable libclang support for reflection" ON) # Path to LLVM installation (optional, overrides automatic detection) set(LLVM_ROOT "" CACHE PATH "Path to LLVM installation directory (e.g., /usr/lib/llvm-15)") # Read libtool version file(READ "${CMAKE_CURRENT_SOURCE_DIR}/.libtool-version" LIBTOOL_VERSION) string(STRIP "${LIBTOOL_VERSION}" LIBTOOL_VERSION) # Parse libtool version components (format is current:revision:age) string(REGEX REPLACE "^([0-9]+):.*" "\\1" LIBTOOL_CURRENT "${LIBTOOL_VERSION}") string(REGEX REPLACE "^[0-9]+:([0-9]+):.*" "\\1" LIBTOOL_REVISION "${LIBTOOL_VERSION}") string(REGEX REPLACE "^[0-9]+:[0-9]+:([0-9]+)" "\\1" LIBTOOL_AGE "${LIBTOOL_VERSION}") # Calculate SO version (current - age) math(EXPR SO_VERSION "${LIBTOOL_CURRENT} - ${LIBTOOL_AGE}") set(SO_VERSION_FULL "${SO_VERSION}.${LIBTOOL_AGE}.${LIBTOOL_REVISION}") # Platform detection if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") set(TARGET_CPU_X86_64 TRUE) set(TARGET_CPU_ANY_X86 TRUE) elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i.86|x86") set(TARGET_CPU_X86 TRUE) set(TARGET_CPU_ANY_X86 TRUE) elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64") set(TARGET_CPU_ARM64 TRUE) set(TARGET_CPU_ANY_ARM TRUE) elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm") set(TARGET_CPU_ARM TRUE) set(TARGET_CPU_ANY_ARM TRUE) endif() if(CMAKE_CROSSCOMPILING) set(CROSS_COMPILING 1) message(STATUS "Cross-compiling detected:") message(STATUS " Build system: ${CMAKE_HOST_SYSTEM_NAME} ${CMAKE_HOST_SYSTEM_PROCESSOR}") message(STATUS " Target system: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_PROCESSOR}") else() set(CROSS_COMPILING 0) endif() # When cross-compiling for Windows, look for wine to run target executables set(HAVE_WINE 0) if(CROSS_COMPILING AND CMAKE_SYSTEM_NAME STREQUAL "Windows") find_program(WINE_EXECUTABLE NAMES wine64 wine) if(WINE_EXECUTABLE) set(HAVE_WINE 1) set(CMAKE_CROSSCOMPILING_EMULATOR "${WINE_EXECUTABLE}" CACHE STRING "Wine emulator for Windows cross-compilation tests" FORCE) message(STATUS "Found wine: ${WINE_EXECUTABLE} (tests will run under wine)") else() message(STATUS "wine not found; tests disabled for Windows cross-compilation") endif() endif() # Check for required dependencies if(NOT WIN32) set(CMAKE_THREAD_PREFER_PTHREAD TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads REQUIRED) if(NOT CMAKE_USE_PTHREADS_INIT) message(FATAL_ERROR "Missing required pthread support") endif() else() # Windows uses native threading APIs set(CMAKE_USE_WIN32_THREADS_INIT TRUE) endif() # detect if we're on clang-cl if(WIN32 AND MSVC AND CMAKE_C_COMPILER_ID STREQUAL "Clang") set(CLANG_CL TRUE) endif() # Check for headers and functions check_include_file("alloca.h" HAVE_ALLOCA_H) check_include_file("byteswap.h" HAVE_BYTESWAP_H) check_function_exists(__builtin_bswap16 HAVE___BUILTIN_BSWAP16) check_function_exists(__builtin_bswap32 HAVE___BUILTIN_BSWAP32) check_function_exists(__builtin_bswap64 HAVE___BUILTIN_BSWAP64) check_function_exists(qsort_r HAVE_QSORT_R) check_function_exists(mremap HAVE_MREMAP) # Check for environment variables check_c_source_compiles(" extern char **environ; int main() { return 0; } " HAVE_DECL_ENVIRON) # find PkgConfig if(NOT CMAKE_CROSSCOMPILING) find_package(PkgConfig) endif() # Check for optional dependencies # libyaml detection - try CMake config first, then pkg-config set(HAVE_LIBYAML 0) find_package(yaml QUIET CONFIG) if(yaml_FOUND) set(HAVE_LIBYAML 1) # CMake config found, use imported target set(LIBYAML_LIBRARIES yaml) message(STATUS "Found libyaml via CMake config") else() if(PKG_CONFIG_FOUND) pkg_check_modules(LIBYAML yaml-0.1) if(LIBYAML_FOUND) set(HAVE_LIBYAML 1) message(STATUS "Found libyaml via pkg-config") else() message(WARNING "failed to find libyaml; compatibility disabled") endif() endif() endif() # check framework detection - try CMake config first, then pkg-config, then FetchContent # When cross-compiling with wine we skip find_package/pkg-config (those would # find host libraries) and go straight to FetchContent so check is built for # the target platform. function(fy_target_compile_definition_if_exists _target_name) if(NOT TARGET ${_target_name}) return() endif() get_target_property(_aliased_target ${_target_name} ALIASED_TARGET) if(_aliased_target) set(_target_name ${_aliased_target}) endif() target_compile_definitions(${_target_name} PRIVATE _CRT_SECURE_NO_WARNINGS) endfunction() set(HAVE_CHECK 0) set(HAVE_COMPATIBLE_CHECK 0) if(BUILD_TESTING AND (NOT CMAKE_CROSSCOMPILING OR HAVE_WINE)) if(NOT CMAKE_CROSSCOMPILING) find_package(check QUIET CONFIG) if(check_FOUND) set(HAVE_CHECK 1) set(CHECK_LIBRARIES check) if(TARGET Check::check) set(CHECK_TARGET Check::check) else() set(CHECK_TARGET check) endif() get_target_property(CHECK_INCLUDE_DIRS ${CHECK_TARGET} INTERFACE_INCLUDE_DIRECTORIES) # Get library location to extract directory get_target_property(CHECK_LIB_LOCATION ${CHECK_TARGET} IMPORTED_LOCATION) if(NOT CHECK_LIB_LOCATION) get_target_property(CHECK_LIB_LOCATION ${CHECK_TARGET} LOCATION) endif() if(CHECK_LIB_LOCATION) get_filename_component(CHECK_LIBRARY_DIRS "${CHECK_LIB_LOCATION}" DIRECTORY) endif() message(STATUS "Found check framework via CMake config") elseif(PKG_CONFIG_FOUND) # Fall back to pkg-config pkg_check_modules(CHECK check) if(CHECK_FOUND) set(HAVE_CHECK 1) message(STATUS "Found check framework via pkg-config") endif() endif() endif() # Fall back to FetchContent if check not found. # Always used when cross-compiling (skips host detection above) so that # check is compiled for the target platform and can run under wine. if(NOT HAVE_CHECK) include(FetchContent) message(STATUS "check framework not found, fetching via FetchContent...") FetchContent_Declare( check GIT_REPOSITORY https://github.com/libcheck/check.git GIT_TAG 0.15.2 ) # Disable check's own tests and docs set(CHECK_ENABLE_TESTS OFF CACHE BOOL "" FORCE) set(CHECK_ENABLE_GCOV OFF CACHE BOOL "" FORCE) if(WIN32) # check's itimerspec/clock_gettime detection is unreliable on # Windows (both MinGW and MSVC/Clang): probe them explicitly. include(CheckTypeSize) check_type_size("struct itimerspec" ITIMERSPEC LANGUAGE C) if(NOT HAVE_ITIMERSPEC) set(STRUCT_ITIMERSPEC_DEFINITION_MISSING 1 CACHE INTERNAL "" FORCE) add_compile_definitions(STRUCT_ITIMERSPEC_DEFINITION_MISSING=1) endif() # Windows always provides clock_gettime (MinGW via winpthread, # MSVC via Windows SDK), but check_function_exists may fail to # detect it. Force it so check's configure_file emits # #define HAVE_CLOCK_GETTIME 1 and skips its own stub. set(HAVE_CLOCK_GETTIME 1 CACHE INTERNAL "" FORCE) endif() FetchContent_MakeAvailable(check) if(WIN32) fy_target_compile_definition_if_exists(check) fy_target_compile_definition_if_exists(Check::check) fy_target_compile_definition_if_exists(checkShared) fy_target_compile_definition_if_exists(Check::checkShared) endif() if(TARGET checkShared) # check unconditionally creates a checkShared SHARED target alongside # the static check target. We only need the static library; exclude the # shared target and give it a distinct output name so that Ninja never # sees two rules generating the same check.lib import library. set_target_properties(checkShared PROPERTIES EXCLUDE_FROM_ALL TRUE OUTPUT_NAME checkDynamic) endif() set(HAVE_CHECK 1) set(CHECK_LIBRARIES check) set(CHECK_INCLUDE_DIRS "${check_SOURCE_DIR}/src" "${check_BINARY_DIR}" "${check_BINARY_DIR}/src") set(HAVE_SRUNNER_SET_TAP 1) # 0.15.2 has srunner_set_tap set(HAVE_COMPATIBLE_CHECK 1) message(STATUS "Found check framework via FetchContent") endif() # Check if libcheck has srunner_set_tap (jessie has outdated libcheck). # FetchContent always has it (set above); only probe when using a system library. if(HAVE_CHECK AND NOT HAVE_COMPATIBLE_CHECK) set(_SAVED_CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES}") set(_SAVED_CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES}") set(_SAVED_CMAKE_REQUIRED_LINK_OPTIONS "${CMAKE_REQUIRED_LINK_OPTIONS}") set(CMAKE_REQUIRED_LIBRARIES ${CHECK_LIBRARIES}) set(CMAKE_REQUIRED_INCLUDES ${CHECK_INCLUDE_DIRS}) foreach(libdir ${CHECK_LIBRARY_DIRS}) list(APPEND CMAKE_REQUIRED_LINK_OPTIONS "-L${libdir}") endforeach() check_symbol_exists(srunner_set_tap "check.h" HAVE_SRUNNER_SET_TAP) set(CMAKE_REQUIRED_LIBRARIES "${_SAVED_CMAKE_REQUIRED_LIBRARIES}") set(CMAKE_REQUIRED_INCLUDES "${_SAVED_CMAKE_REQUIRED_INCLUDES}") set(CMAKE_REQUIRED_LINK_OPTIONS "${_SAVED_CMAKE_REQUIRED_LINK_OPTIONS}") unset(_SAVED_CMAKE_REQUIRED_LIBRARIES) unset(_SAVED_CMAKE_REQUIRED_INCLUDES) unset(_SAVED_CMAKE_REQUIRED_LINK_OPTIONS) if(HAVE_SRUNNER_SET_TAP) set(HAVE_COMPATIBLE_CHECK 1) endif() endif() endif() # Check for libclang (for reflection support) set(HAVE_LIBCLANG 0) set(LIBCLANG_CFLAGS "") set(LIBCLANG_LDFLAGS "") set(LIBCLANG_LIBS "") set(LIBCLANG_FOUND FALSE) set(LIBCLANG_DETECTION_METHOD "") if(NOT ENABLE_REFLECTION) message(STATUS "libclang support disabled (ENABLE_REFLECTION=OFF)") set(ENABLE_LIBCLANG OFF CACHE BOOL "Disable libclang" FORCE) endif() if(NOT HAVE_CMAKE_GEXPR) message(STATUS "libclang support disabled (CMake version is too old (<=3.27)") set(ENABLE_LIBCLANG OFF CACHE BOOL "Disable libclang" FORCE) endif() if(NOT ENABLE_LIBCLANG) message(STATUS "libclang support disabled (ENABLE_LIBCLANG=OFF)") else() # If user specified LLVM_ROOT, add it to CMAKE_PREFIX_PATH for this search if(LLVM_ROOT) message(STATUS "Using user-specified LLVM_ROOT: ${LLVM_ROOT}") list(PREPEND CMAKE_PREFIX_PATH "${LLVM_ROOT}") endif() # Try CMake config mode first find_package(LLVM QUIET CONFIG) # If LLVM was found, help CMake find Clang in the same installation if(LLVM_FOUND) # Clang's config is typically in ${LLVM_INSTALL_PREFIX}/lib/cmake/clang set(Clang_DIR "${LLVM_INSTALL_PREFIX}/lib/cmake/clang") endif() # Avoid find_package(Clang CONFIG): some distros ship LLVM packages where # ClangTargets.cmake references missing static libs (e.g. libclangBasic.a) # and emits a CMake FATAL_ERROR that QUIET cannot suppress. Find libclang # directly instead — we only need the shared lib and the include path. if(LLVM_FOUND) find_library(_LIBCLANG_LIB NAMES clang libclang PATHS "${LLVM_LIBRARY_DIRS}" NO_DEFAULT_PATH) if(_LIBCLANG_LIB) set(Clang_FOUND TRUE) set(CLANG_INCLUDE_DIRS "${LLVM_INCLUDE_DIRS}") set(LIBCLANG_FULL_PATH "${_LIBCLANG_LIB}") else() message(STATUS "LLVM ${LLVM_PACKAGE_VERSION}: libclang not found in ${LLVM_LIBRARY_DIRS}, skipping") set(LLVM_FOUND FALSE) endif() unset(_LIBCLANG_LIB CACHE) endif() if(LLVM_FOUND AND Clang_FOUND) message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION} via CMake config") message(STATUS " LLVM_INSTALL_PREFIX: ${LLVM_INSTALL_PREFIX}") message(STATUS " LLVM_INCLUDE_DIRS: ${LLVM_INCLUDE_DIRS}") # Use LLVM's CMake variables directly set(LIBCLANG_CFLAGS_LIST ${LLVM_DEFINITIONS}) list(APPEND LIBCLANG_CFLAGS_LIST "-I${LLVM_INCLUDE_DIRS}") list(APPEND LIBCLANG_CFLAGS_LIST "-I${CLANG_INCLUDE_DIRS}") # Set up library directories set(LIBCLANG_LINK_DIRS ${LLVM_LIBRARY_DIRS}) # Link against libclang (C API) and LLVM core libs. # Use the full path found by find_library to avoid -lclang resolution # issues when the lib dir isn't in the default search path. if(TARGET libclang) set(LIBCLANG_LIBS_LIST libclang LLVM) else() set(LIBCLANG_LIBS_LIST "${LIBCLANG_FULL_PATH}" LLVM) endif() set(LIBCLANG_INCLUDES "${LLVM_INCLUDE_DIRS};${CLANG_INCLUDE_DIRS}") set(LIBCLANG_FOUND TRUE) set(LIBCLANG_DETECTION_METHOD "CMake config") elseif(NOT CROSS_COMPILING) # Fallback to llvm-config method (only when not cross-compiling) message(STATUS "LLVM CMake config not found, trying llvm-config") # Try to find llvm-config (versions 20 down to 10) set(LLVM_CONFIG_NAMES llvm-config) foreach(ver RANGE 20 10 -1) list(APPEND LLVM_CONFIG_NAMES llvm-config-${ver}) endforeach() find_program(LLVM_CONFIG NAMES ${LLVM_CONFIG_NAMES}) if(LLVM_CONFIG) execute_process(COMMAND ${LLVM_CONFIG} --cflags OUTPUT_VARIABLE LIBCLANG_CFLAGS_TEMP OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${LLVM_CONFIG} --ldflags OUTPUT_VARIABLE LIBCLANG_LDFLAGS_TEMP OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${LLVM_CONFIG} --libs OUTPUT_VARIABLE LLVM_LIBS_TEMP OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${LLVM_CONFIG} --system-libs OUTPUT_VARIABLE LLVM_SYSLIBS_TEMP OUTPUT_STRIP_TRAILING_WHITESPACE) set(LIBCLANG_CFLAGS_LIST "${LIBCLANG_CFLAGS_TEMP} ${LIBCLANG_LDFLAGS_TEMP}") set(LIBCLANG_LIBS_LIST "-lclang;${LLVM_LIBS_TEMP};${LLVM_SYSLIBS_TEMP}") set(LIBCLANG_CFLAGS "${LIBCLANG_CFLAGS_TEMP}") set(LIBCLANG_LDFLAGS "${LIBCLANG_LDFLAGS_TEMP}") set(LIBCLANG_FOUND TRUE) set(LIBCLANG_DETECTION_METHOD "llvm-config: ${LLVM_CONFIG}") else() # Last resort: no LLVMConfig.cmake and no llvm-config. # Try to find libclang directly under LLVM_ROOT (if set) or under # well-known platform-specific default install locations. set(_LLVM_SEARCH_ROOTS) if(LLVM_ROOT) list(APPEND _LLVM_SEARCH_ROOTS "${LLVM_ROOT}") endif() if(WIN32) # Chocolatey installs to Program Files/LLVM by default list(APPEND _LLVM_SEARCH_ROOTS "C:/Program Files/LLVM" "C:/LLVM") else() # Homebrew (macOS) and common Linux package manager paths list(APPEND _LLVM_SEARCH_ROOTS "/opt/homebrew/opt/llvm" "/usr/local/opt/llvm") foreach(ver RANGE 20 10 -1) list(APPEND _LLVM_SEARCH_ROOTS "/usr/lib/llvm-${ver}" "/usr/local/lib/llvm-${ver}") endforeach() endif() foreach(_root IN LISTS _LLVM_SEARCH_ROOTS) if(NOT LIBCLANG_FOUND) find_library(_LIBCLANG_LIB_DIRECT NAMES clang libclang PATHS "${_root}/lib" NO_DEFAULT_PATH) if(_LIBCLANG_LIB_DIRECT AND EXISTS "${_root}/include/clang-c/Index.h") set(LIBCLANG_CFLAGS_LIST "") set(LIBCLANG_LINK_DIRS "${_root}/lib") set(LIBCLANG_LIBS_LIST "${_LIBCLANG_LIB_DIRECT}") set(LIBCLANG_INCLUDES "${_root}/include") set(LIBCLANG_FOUND TRUE) set(LIBCLANG_DETECTION_METHOD "direct path: ${_root}") message(STATUS "Found libclang via direct path: ${_LIBCLANG_LIB_DIRECT}") endif() unset(_LIBCLANG_LIB_DIRECT CACHE) endif() endforeach() endif() else() message(STATUS "Cross-compiling: LLVM CMake config not found") message(STATUS " Set LLVM_ROOT to LLVM install directory (e.g., /usr/lib/llvm-18)") message(STATUS " Or set CMAKE_PREFIX_PATH or LLVM_DIR to the directory containing LLVMConfig.cmake") endif() # Test if libclang actually works (single test for all detection methods) if(LIBCLANG_FOUND) if(NOT CROSS_COMPILING) check_libclang_works(LIBCLANG_WORKS CFLAGS "${LIBCLANG_CFLAGS_LIST}" INCLUDES "${LIBCLANG_INCLUDES}" LINK_DIRS "${LIBCLANG_LINK_DIRS}" LIBRARIES "${LIBCLANG_LIBS_LIST}" ) else() # When cross-compiling, disable libclang by default to avoid linking host libraries # User can set LLVM_ROOT, CMAKE_PREFIX_PATH, or LLVM_DIR to point to target-specific LLVM installation message(STATUS "Cross-compiling: disabling libclang") message(STATUS " To enable, set LLVM_ROOT or CMAKE_PREFIX_PATH to target LLVM installation") set(LIBCLANG_WORKS FALSE) endif() if(LIBCLANG_WORKS) set(HAVE_LIBCLANG 1) # Convert list to string for LIBCLANG_CFLAGS if needed (CMake config path) if(LIBCLANG_DETECTION_METHOD STREQUAL "CMake config") string(REPLACE ";" " " LIBCLANG_CFLAGS "${LIBCLANG_CFLAGS_LIST}") endif() set(LIBCLANG_LIBS "${LIBCLANG_LIBS_LIST}") message(STATUS "Found libclang via ${LIBCLANG_DETECTION_METHOD}") # Check for llvm-c/Core.h (absent in stripped installs e.g. Chocolatey) set(_SAVED_CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES}") set(CMAKE_REQUIRED_INCLUDES "${LIBCLANG_INCLUDES}") include(CheckIncludeFile) check_include_file("llvm-c/Core.h" HAVE_LLVM_C_CORE_H) set(CMAKE_REQUIRED_INCLUDES "${_SAVED_CMAKE_REQUIRED_INCLUDES}") if(HAVE_LLVM_C_CORE_H) list(APPEND COMMON_C_DEFINITIONS HAVE_LLVM_C_CORE_H) endif() else() message(STATUS "libclang not found or not working") message(STATUS " Reflection support will be disabled") message(STATUS " To specify libclang location: cmake -DLLVM_ROOT=/path/to/llvm") message(STATUS " To disable this check: cmake -DENABLE_LIBCLANG=OFF") endif() else() if(NOT LIBCLANG_FOUND) message(STATUS "libclang not found") message(STATUS " Reflection support will be disabled") message(STATUS " To specify libclang location: cmake -DLLVM_ROOT=/path/to/llvm") message(STATUS " To disable this check: cmake -DENABLE_LIBCLANG=OFF") endif() endif() endif() # ENABLE_LIBCLANG # check for bash find_program(BASH_EXECUTABLE bash) if(BASH_EXECUTABLE) set(HAVE_BASH 1) else() set(HAVE_BASH 0) endif() # Check for tools find_program(GIT_EXECUTABLE git) if(GIT_EXECUTABLE) set(HAVE_GIT 1) else() set(HAVE_GIT 0) endif() find_program(JQ_EXECUTABLE jq) if(JQ_EXECUTABLE) set(HAVE_JQ 1) else() set(HAVE_JQ 0) endif() find_program(DOCKER_EXECUTABLE docker) if(DOCKER_EXECUTABLE) set(HAVE_DOCKER 1) else() set(HAVE_DOCKER 0) endif() # Test suite URLs and commits if(NOT TESTSUITEURL) set(TESTSUITEURL "https://github.com/yaml/yaml-test-suite") endif() if(NOT TESTSUITECHECKOUT) set(TESTSUITECHECKOUT "6e6c296ae9c9d2d5c4134b4b64d01b29ac19ff6f") endif() if(NOT JSONTESTSUITEURL) set(JSONTESTSUITEURL "https://github.com/nst/JSONTestSuite") endif() if(NOT JSONTESTSUITECHECKOUT) set(JSONTESTSUITECHECKOUT "d64aefb55228d9584d3e5b2433f720ea8fd00c82") endif() # FetchContent for test suites (downloaded at configure time when network is available) include(FetchContent) if(ENABLE_NETWORK) FetchContent_Declare( yaml_test_suite GIT_REPOSITORY ${TESTSUITEURL} GIT_TAG ${TESTSUITECHECKOUT} GIT_SHALLOW TRUE ) FetchContent_Declare( json_test_suite GIT_REPOSITORY ${JSONTESTSUITEURL} GIT_TAG ${JSONTESTSUITECHECKOUT} GIT_SHALLOW FALSE ) endif() # SIMD capability detection set(TARGET_HAS_SSE2 FALSE) set(TARGET_HAS_SSE41 FALSE) set(TARGET_HAS_AVX2 FALSE) set(TARGET_HAS_AVX512 FALSE) set(TARGET_HAS_NEON FALSE) # Disable SIMD assembly on Windows (uses Unix assembly syntax) # Use portable implementations instead if(WIN32) message(STATUS "Windows detected: using portable SIMD implementations") elseif(TARGET_CPU_X86_64 AND NOT ENABLE_PORTABLE_TARGET) check_c_source_compiles(" #include int main() { __m128i a = _mm_setzero_si128(); return 0; } " COMPILER_SUPPORTS_SSE2) if(COMPILER_SUPPORTS_SSE2) set(TARGET_HAS_SSE2 TRUE) endif() check_c_source_compiles(" #include int main() { __m128i a = _mm_setzero_si128(); return 0; } " COMPILER_SUPPORTS_SSE41) if(COMPILER_SUPPORTS_SSE41) set(TARGET_HAS_SSE41 TRUE) endif() set(CMAKE_REQUIRED_FLAGS "-mavx2") check_c_source_compiles(" #include int main() { __m256i a = _mm256_setzero_si256(); return 0; } " COMPILER_SUPPORTS_AVX2) set(CMAKE_REQUIRED_FLAGS "") if(COMPILER_SUPPORTS_AVX2) set(TARGET_HAS_AVX2 TRUE) endif() set(CMAKE_REQUIRED_FLAGS "-mavx512f -mavx512vl") check_c_source_compiles(" #include int main() { __m512i a = _mm512_setzero_si512(); return 0; } " COMPILER_SUPPORTS_AVX512) set(CMAKE_REQUIRED_FLAGS "") if(COMPILER_SUPPORTS_AVX512) set(TARGET_HAS_AVX512 TRUE) endif() endif() if(TARGET_CPU_ANY_ARM AND NOT ENABLE_PORTABLE_TARGET) if(TARGET_CPU_ARM64) set(TARGET_HAS_NEON TRUE) else() check_c_source_compiles(" #include int main() { uint8x16_t a = vdupq_n_u8(0); return 0; } " COMPILER_SUPPORTS_NEON) if(COMPILER_SUPPORTS_NEON) set(TARGET_HAS_NEON TRUE) endif() endif() endif() # ASAN support set(HAVE_ASAN 0) if(ENABLE_ASAN) check_c_source_compiles(" int main() { return 0; } " ASAN_WORKS) if(ASAN_WORKS) set(HAVE_ASAN 1) endif() endif() set(ASAN_C_FLAGS) if(HAVE_ASAN) set(ASAN_C_FLAGS "-fsanitize=address,signed-integer-overflow,undefined" "-fno-omit-frame-pointer") endif() # Build common compiler flags for all targets set(COMMON_C_FLAGS) if(WIN32 AND CMAKE_C_COMPILER_ID STREQUAL "Clang") # MSVC compat mode (enabled by default when clang targets x86_64-windows-msvc) # breaks __VA_OPT__ preprocessing — empty __VA_ARGS__ causes completely wrong # macro expansion. Apply only to libfyaml targets (not FetchContent deps). list(APPEND COMMON_C_FLAGS -fno-ms-compatibility) endif() if(WIN32) set(COMMON_C_DEFINITIONS WIN32_LEAN_AND_MEAN _CRT_SECURE_NO_WARNINGS) else() set(COMMON_C_DEFINITIONS _GNU_SOURCE) endif() # MSVC requires C11 for stdatomic.h if(MSVC) # Use /std:c17 for better C11/C17 support in MSVC set(CMAKE_C_STANDARD 17) set(CMAKE_C_STANDARD_REQUIRED ON) # Enable experimental C11 atomics in MSVC (only) if(NOT CLANG_CL) add_compile_options(/experimental:c11atomics) endif() endif() if(NOT MSVC) list(APPEND COMMON_C_FLAGS -Wall -Wsign-compare) if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") list(APPEND COMMON_C_FLAGS -Wextra) endif() check_c_compiler_flag(-Wno-unused-function COMPILER_SUPPORTS_WNO_UNUSED_FUNCTION) if(COMPILER_SUPPORTS_WNO_UNUSED_FUNCTION) list(APPEND COMMON_C_FLAGS -Wno-unused-function) endif() check_c_compiler_flag(-Wno-stringop-overflow COMPILER_SUPPORTS_WNO_STRINGOP_OVERFLOW) if(COMPILER_SUPPORTS_WNO_STRINGOP_OVERFLOW) list(APPEND COMMON_C_FLAGS -Wno-stringop-overflow) endif() if(CMAKE_C_COMPILER_ID MATCHES "Clang") check_c_compiler_flag(-Wno-tautological-constant-out-of-range-compare COMPILER_SUPPORTS_WNO_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) if(COMPILER_SUPPORTS_WNO_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) list(APPEND COMMON_C_FLAGS -Wno-tautological-constant-out-of-range-compare) endif() endif() set(USE_STD 0) if(NOT USE_STD) check_c_compiler_flag(-std=c2x COMPILER_SUPPORTS_GNU2X) if(COMPILER_SUPPORTS_GNU2X) list(APPEND COMMON_C_FLAGS -std=gnu2x) set(USE_STD 1) endif() endif() if(NOT USE_STD) check_c_compiler_flag(-std=c2x COMPILER_SUPPORTS_C2X) if(COMPILER_SUPPORTS_C2X) list(APPEND COMMON_C_FLAGS -std=c2x) set(USE_STD 1) endif() endif() # GCC heap trampolines support (for nested functions) # Heap trampolines are more secure than executable stack trampolines # Available in GCC 14+ with -ftrampoline-impl=heap # Supported targets: x86_64/i386/aarch64 Linux, x86_64/i386 Darwin # Note: we link-test a nested function to catch platforms (e.g. Cygwin) # where the compiler accepts the flag but the runtime support is missing. set(HAVE_HEAP_TRAMPOLINES 0) if(CMAKE_C_COMPILER_ID STREQUAL "GNU") set(CMAKE_REQUIRED_FLAGS_SAVE "${CMAKE_REQUIRED_FLAGS}") set(CMAKE_REQUIRED_FLAGS "-ftrampoline-impl=heap") check_c_source_compiles(" typedef void (*fn_t)(void); fn_t make_trampoline(int x) { void nested(void) { (void)x; } return nested; } int main(void) { fn_t f = make_trampoline(42); f(); return 0; } " COMPILER_SUPPORTS_HEAP_TRAMPOLINES) set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS_SAVE}") if(COMPILER_SUPPORTS_HEAP_TRAMPOLINES) set(HAVE_HEAP_TRAMPOLINES 1) list(APPEND COMMON_C_FLAGS -ftrampoline-impl=heap) message(STATUS "GCC heap trampolines enabled (-ftrampoline-impl=heap)") else() message(STATUS "GCC heap trampolines not available or runtime missing, using stack trampolines") endif() endif() endif() # Check for GCC statement expressions ({ ... }) support # MSVC does not support these; GCC and Clang do. check_c_source_compiles(" int main(void) { int x = ({ int y = 1; y + 1; }); return x - 2; } " HAVE_STATEMENT_EXPRESSIONS) test_big_endian(IS_BIG_ENDIAN) if(NOT IS_BIG_ENDIAN) set(HAVE_GENERIC_PLATFORM 1) else() set(HAVE_GENERIC_PLATFORM 0) endif() if(HAVE_STATEMENT_EXPRESSIONS AND HAVE_GENERIC_PLATFORM) list(APPEND COMMON_C_DEFINITIONS HAVE_STATEMENT_EXPRESSIONS) if(ENABLE_GENERIC) set(HAVE_GENERIC 1) list(APPEND COMMON_C_DEFINITIONS HAVE_GENERIC) else() set(HAVE_GENERIC 0) endif() if(ENABLE_REFLECTION) set(HAVE_REFLECTION 1) list(APPEND COMMON_C_DEFINITIONS HAVE_REFLECTION) else() set(HAVE_REFLECTION 0) endif() elseif(HAVE_STATEMENT_EXPRESSIONS) list(APPEND COMMON_C_DEFINITIONS HAVE_STATEMENT_EXPRESSIONS) set(HAVE_GENERIC 0) if(ENABLE_GENERIC) message(STATUS "Generic subsystem disabled (requires little-endian targets)") endif() if(ENABLE_REFLECTION) set(HAVE_REFLECTION 1) list(APPEND COMMON_C_DEFINITIONS HAVE_REFLECTION) else() set(HAVE_REFLECTION 0) endif() else() set(HAVE_GENERIC 0) set(HAVE_REFLECTION 0) if(ENABLE_GENERIC OR ENABLE_REFLECTION) message(STATUS "Statement expressions not supported; generic and reflection disabled") endif() endif() # the options for the libs set(LIB_OPTS "") if(NOT MSVC AND NOT WIN32) if(BUILD_SHARED_LIBS) list(APPEND LIB_OPTS "-fPIC") endif() endif() # Network tests if(ENABLE_NETWORK) set(HAVE_NETWORK 1) else() set(HAVE_NETWORK 0) endif() # Dev mode if(ENABLE_DEVMODE) set(HAVE_DEVMODE 1) else() set(HAVE_DEVMODE 0) endif() # Static support if(NOT BUILD_SHARED_LIBS) set(HAVE_STATIC 1) else() set(HAVE_STATIC 0) endif() # Library sources set(LIBHDRS src/lib/fy-accel.h src/lib/fy-atom.h src/lib/fy-composer.h src/lib/fy-diag.h src/lib/fy-doc.h src/lib/fy-docbuilder.h src/lib/fy-docstate.h src/lib/fy-dump.h src/lib/fy-emit.h src/lib/fy-emit-accum.h src/lib/fy-event.h src/lib/fy-input.h src/lib/fy-parse.h src/lib/fy-path.h src/lib/fy-token.h src/lib/fy-types.h src/lib/fy-walk.h ) set(UTILHDRS src/util/fy-blob.h src/util/fy-ctype.h src/util/fy-endian.h src/util/fy-id.h src/util/fy-list.h src/util/fy-typelist.h src/util/fy-utf8.h src/util/fy-utils.h src/util/fy-align.h src/util/fy-bit64.h src/util/fy-vlsize.h src/util/fy-win32.h ) set(LIBSRCS src/lib/fy-accel.c src/lib/fy-atom.c src/lib/fy-composer.c src/lib/fy-diag.c src/lib/fy-doc.c src/lib/fy-docbuilder.c src/lib/fy-docstate.c src/lib/fy-dump.c src/lib/fy-emit.c src/lib/fy-event.c src/lib/fy-input.c src/lib/fy-parse.c src/lib/fy-path.c src/lib/fy-token.c src/lib/fy-types.c src/lib/fy-walk.c src/util/fy-blob.c src/util/fy-ctype.c src/util/fy-utf8.c src/util/fy-utils.c src/xxhash/xxhash.c src/thread/fy-thread.c src/allocator/fy-allocator.c src/allocator/fy-allocator-linear.c src/allocator/fy-allocator-malloc.c src/allocator/fy-allocator-mremap.c src/allocator/fy-allocator-dedup.c src/allocator/fy-allocator-auto.c src/blake3/blake3_host_state.c src/blake3/blake3_backend.c src/blake3/blake3_be_cpusimd.c src/blake3/fy-blake3.c src/lib/fy-composer-diag.c src/lib/fy-doc-diag.c src/lib/fy-docbuilder-diag.c src/lib/fy-input-diag.c src/lib/fy-parse-diag.c ) if(HAVE_GENERIC) list(APPEND LIBSRCS src/generic/fy-generic.c src/generic/fy-generic-docbuilder.c src/generic/fy-generic-decoder.c src/generic/fy-generic-encoder.c src/generic/fy-generic-op.c src/generic/fy-generic-iter.c ) endif() if(HAVE_REFLECTION) list(APPEND LIBSRCS src/reflection/fy-reflection.c src/reflection/fy-packed-backend.c src/reflection/fy-null-backend.c src/reflection/fy-registry.c src/reflection/fy-type-meta.c src/reflection/fy-meta-type-system.c src/reflection/fy-type-context.c src/reflection/fy-meta-serdes.c src/reflection/fy-reflection-util.c ) # Conditionally add clang backend if libclang is available if(HAVE_LIBCLANG) list(APPEND LIBSRCS src/reflection/fy-clang-backend.c) endif() endif() set(LIBHDRSPUB include/libfyaml/libfyaml-util.h include/libfyaml/libfyaml-core.h include/libfyaml/libfyaml-path-exec.h include/libfyaml/libfyaml-dociter.h include/libfyaml/libfyaml-docbuild.h include/libfyaml/libfyaml-composer.h include/libfyaml/libfyaml-allocator.h include/libfyaml/libfyaml-thread.h include/libfyaml/libfyaml-blake3.h include/libfyaml/libfyaml-align.h include/libfyaml/libfyaml-endian.h include/libfyaml/libfyaml-vlsize.h include/libfyaml/libfyaml-atomics.h ) if(HAVE_REFLECTION) list(APPEND LIBHDRSPUB include/libfyaml/libfyaml-reflection.h) endif() if(HAVE_GENERIC) list(APPEND LIBHDRSPUB include/libfyaml/libfyaml-generic.h) endif() # BLAKE3 portable library add_library(b3portable OBJECT src/blake3/blake3_portable.c src/blake3/blake3.c ) target_include_directories(b3portable PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src/util ${CMAKE_CURRENT_SOURCE_DIR}/src/thread ${CMAKE_CURRENT_SOURCE_DIR}/src/blake3 ) target_compile_definitions(b3portable PRIVATE ${COMMON_C_DEFINITIONS} HASHER_SUFFIX=portable SIMD_DEGREE=1 ) target_compile_options(b3portable PRIVATE ${COMMON_C_FLAGS} ${LIB_OPTS}) # BLAKE3 SIMD libraries set(BLAKE3_TARGETS b3portable) if(TARGET_HAS_SSE2) add_library(b3sse2 OBJECT src/blake3/blake3_sse2.c src/blake3/blake3_sse2_x86-64_unix.S src/blake3/blake3.c ) target_include_directories(b3sse2 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src/util ${CMAKE_CURRENT_SOURCE_DIR}/src/thread ${CMAKE_CURRENT_SOURCE_DIR}/src/blake3 ) target_compile_definitions(b3sse2 PRIVATE ${COMMON_C_DEFINITIONS} HASHER_SUFFIX=sse2 SIMD_DEGREE=4 ) target_compile_options(b3sse2 PRIVATE ${COMMON_C_FLAGS} -msse2 ${LIB_OPTS}) list(APPEND BLAKE3_TARGETS b3sse2) endif() if(TARGET_HAS_SSE41) add_library(b3sse41 OBJECT src/blake3/blake3_sse41.c src/blake3/blake3_sse41_x86-64_unix.S src/blake3/blake3.c ) target_include_directories(b3sse41 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src/util ${CMAKE_CURRENT_SOURCE_DIR}/src/thread ${CMAKE_CURRENT_SOURCE_DIR}/src/blake3 ) target_compile_definitions(b3sse41 PRIVATE ${COMMON_C_DEFINITIONS} HASHER_SUFFIX=sse41 SIMD_DEGREE=4 ) target_compile_options(b3sse41 PRIVATE ${COMMON_C_FLAGS} -msse4.1 ${LIB_OPTS}) list(APPEND BLAKE3_TARGETS b3sse41) endif() if(TARGET_HAS_AVX2) add_library(b3avx2 OBJECT src/blake3/blake3_avx2.c src/blake3/blake3_avx2_x86-64_unix.S src/blake3/blake3.c ) target_include_directories(b3avx2 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src/util ${CMAKE_CURRENT_SOURCE_DIR}/src/thread ${CMAKE_CURRENT_SOURCE_DIR}/src/blake3 ) target_compile_definitions(b3avx2 PRIVATE ${COMMON_C_DEFINITIONS} HASHER_SUFFIX=avx2 SIMD_DEGREE=8 ) target_compile_options(b3avx2 PRIVATE ${COMMON_C_FLAGS} -mavx2 ${LIB_OPTS}) list(APPEND BLAKE3_TARGETS b3avx2) endif() if(TARGET_HAS_AVX512) add_library(b3avx512 OBJECT src/blake3/blake3_avx512.c src/blake3/blake3_avx512_x86-64_unix.S src/blake3/blake3.c ) target_include_directories(b3avx512 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src/util ${CMAKE_CURRENT_SOURCE_DIR}/src/thread ${CMAKE_CURRENT_SOURCE_DIR}/src/blake3 ) target_compile_definitions(b3avx512 PRIVATE ${COMMON_C_DEFINITIONS} HASHER_SUFFIX=avx512 SIMD_DEGREE=16 ) target_compile_options(b3avx512 PRIVATE ${COMMON_C_FLAGS} -mavx512f -mavx512vl ${LIB_OPTS}) list(APPEND BLAKE3_TARGETS b3avx512) endif() if(TARGET_HAS_NEON) add_library(b3neon OBJECT src/blake3/blake3_neon.c src/blake3/blake3.c ) target_include_directories(b3neon PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src/util ${CMAKE_CURRENT_SOURCE_DIR}/src/thread ${CMAKE_CURRENT_SOURCE_DIR}/src/blake3 ) target_compile_definitions(b3neon PRIVATE ${COMMON_C_DEFINITIONS} HASHER_SUFFIX=neon SIMD_DEGREE=4 ) target_compile_options(b3neon PRIVATE ${COMMON_C_FLAGS} ${LIB_OPTS}) if(TARGET_CPU_ARM) target_compile_options(b3neon PRIVATE -mfpu=neon) endif() list(APPEND BLAKE3_TARGETS b3neon) endif() # Main library add_library(fyaml ${LIBHDRS} ${UTILHDRS} ${LIBSRCS} ${LIBHDRSPUB} ) # Link BLAKE3 object libraries foreach(blake3_target ${BLAKE3_TARGETS}) target_sources(fyaml PRIVATE $) endforeach() # On Windows, use a .def file to export only the public API symbols # This is needed because libfyaml uses GCC-style suffix attributes for FY_EXPORT # which are incompatible with MSVC's __declspec prefix requirement if(WIN32 AND BUILD_SHARED_LIBS) # Generate the .def file from all public headers during build set(GENERATED_DEF_FILE "${CMAKE_CURRENT_BINARY_DIR}/fyaml.def") # Build a list of absolute paths for all public headers set(ABS_LIBHDRSPUB) foreach(HDR ${LIBHDRSPUB}) list(APPEND ABS_LIBHDRSPUB "${CMAKE_CURRENT_SOURCE_DIR}/${HDR}") endforeach() # Write header list to a response file (one path per line) to avoid # issues with passing semicolon-separated lists via -D on native Windows. # On cmd.exe, backslash-escaped semicolons (\;) are passed literally, # corrupting the paths and causing "Invalid argument" from file(READ). set(HEADER_LIST_FILE "${CMAKE_CURRENT_BINARY_DIR}/def-header-list.txt") string(REPLACE ";" "\n" HEADER_LIST_CONTENT "${ABS_LIBHDRSPUB}") file(WRITE "${HEADER_LIST_FILE}" "${HEADER_LIST_CONTENT}\n") add_custom_command( OUTPUT ${GENERATED_DEF_FILE} COMMAND ${CMAKE_COMMAND} -DHEADER_LIST_FILE=${HEADER_LIST_FILE} -DOUTPUT_DEF=${GENERATED_DEF_FILE} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/generate-def.cmake DEPENDS ${HEADER_LIST_FILE} ${ABS_LIBHDRSPUB} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/generate-def.cmake COMMENT "Generating fyaml.def from public headers" ) target_sources(fyaml PRIVATE "${GENERATED_DEF_FILE}") endif() add_library(libfyaml::libfyaml ALIAS fyaml) # Always build a static library for internal tools (they need access to internal APIs) # This is separate from BUILD_SHARED_LIBS and matches autotools behavior add_library(fyaml_static STATIC ${LIBHDRS} ${UTILHDRS} ${LIBSRCS} ${LIBHDRSPUB} ) # Link BLAKE3 object libraries to static version foreach(blake3_target ${BLAKE3_TARGETS}) target_sources(fyaml_static PRIVATE $) endforeach() set_target_properties(fyaml_static PROPERTIES OUTPUT_NAME fyaml_static POSITION_INDEPENDENT_CODE ON FOLDER "Libraries" ) # Configure static library with same settings as main library target_include_directories(fyaml_static PUBLIC $ $ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/lib ${CMAKE_CURRENT_SOURCE_DIR}/src/util ${CMAKE_CURRENT_SOURCE_DIR}/src/xxhash ${CMAKE_CURRENT_SOURCE_DIR}/src/thread ${CMAKE_CURRENT_SOURCE_DIR}/src/allocator ${CMAKE_CURRENT_SOURCE_DIR}/src/blake3 ${CMAKE_CURRENT_SOURCE_DIR}/src/generic ${CMAKE_CURRENT_SOURCE_DIR}/src/reflection ${CMAKE_CURRENT_BINARY_DIR} ) if(NOT WIN32) target_link_libraries(fyaml_static PUBLIC Threads::Threads ) endif() # Add libclang support if available add_libclang_to_target(fyaml_static INTERFACE) # check if blocks are available, and if so link them include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CheckClangBlocks.cmake) # Apply flags if available if (HAVE_CLANG_BLOCKS) add_compile_options(-fblocks) list(APPEND COMMON_C_FLAGS -fblocks) if (CLANG_BLOCKS_LIB) link_libraries(${CLANG_BLOCKS_LIB}) endif() endif() target_compile_definitions(fyaml_static PRIVATE HAVE_CONFIG_H VERSION="${PROJECT_VERSION}" $<$>:NDEBUG> ) target_compile_definitions(fyaml_static PRIVATE ${COMMON_C_DEFINITIONS}) target_compile_options(fyaml_static PRIVATE ${COMMON_C_FLAGS}) if(NOT MSVC) if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") target_compile_options(fyaml_static PRIVATE -O2) endif() if(HAVE_ASAN) target_compile_options(fyaml_static PRIVATE ${ASAN_C_FLAGS}) # Don't add link options here - executables will link dynamically against libasan endif() endif() set_target_properties(fyaml PROPERTIES OUTPUT_NAME fyaml # Keep package/project versioning on PROJECT_VERSION, but derive the # shared-library ABI version from .libtool-version for CMake builds too. VERSION ${SO_VERSION_FULL} SOVERSION ${SO_VERSION} FOLDER "Libraries" PUBLIC_HEADER "include/libfyaml.h" ) target_include_directories(fyaml PUBLIC $ $ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/lib ${CMAKE_CURRENT_SOURCE_DIR}/src/util ${CMAKE_CURRENT_SOURCE_DIR}/src/xxhash ${CMAKE_CURRENT_SOURCE_DIR}/src/thread ${CMAKE_CURRENT_SOURCE_DIR}/src/allocator ${CMAKE_CURRENT_SOURCE_DIR}/src/blake3 ${CMAKE_CURRENT_SOURCE_DIR}/src/generic ${CMAKE_CURRENT_SOURCE_DIR}/src/reflection ) if(NOT WIN32) target_link_libraries(fyaml PUBLIC Threads::Threads ) else() # Windows needs no extra libraries for threading (uses kernel32) endif() # Link libm where it exists as a separate library (Linux, FreeBSD, etc.). # macOS includes math in libSystem; Windows has no libm. find_library(LIBM m) if(LIBM) target_link_libraries(fyaml PUBLIC ${LIBM}) target_link_libraries(fyaml_static PUBLIC ${LIBM}) endif() # Add libclang support if available add_libclang_to_target(fyaml PRIVATE) # Generate config.h configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h" ) target_include_directories(fyaml PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_compile_definitions(fyaml PRIVATE HAVE_CONFIG_H VERSION="${PROJECT_VERSION}" $<$>:NDEBUG> ) target_compile_definitions(fyaml PRIVATE ${COMMON_C_DEFINITIONS}) target_compile_options(fyaml PRIVATE ${COMMON_C_FLAGS}) # Shared options between GCC and CLANG (skip for Windows cross-compilation) if(NOT MSVC AND NOT WIN32) target_compile_options(fyaml PRIVATE -fvisibility=hidden) if(BUILD_SHARED_LIBS) target_compile_options(fyaml PRIVATE -fPIC) endif() if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") target_compile_options(fyaml PRIVATE -O2) endif() if(HAVE_ASAN) target_compile_options(fyaml PRIVATE ${ASAN_C_FLAGS} -fno-omit-frame-pointer) # Use INTERFACE to propagate link flags to executables linking against this library target_link_options(fyaml PRIVATE ${ASAN_C_FLAGS} INTERFACE ${ASAN_C_FLAGS}) endif() endif() # clang-cl: disable MSVC-compatible preprocessor so that statement expressions # in the generic API headers work correctly for both the library build and for # consumers that include libfyaml headers via find_package(libfyaml). # CMAKE_C_SIMULATE_ID is "MSVC" only for clang-cl, not for regular Clang. if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_SIMULATE_ID STREQUAL "MSVC") foreach(_tgt fyaml fyaml_static) target_compile_options(${_tgt} PUBLIC -fno-ms-compatibility) endforeach() message(STATUS "clang-cl detected: adding -fno-ms-compatibility to fyaml targets") endif() # on windows we need a fall-back getopt if(WIN32) set(GETOPT_SOURCES src/getopt/getopt.c) set(GETOPT_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/src/getopt") else() set(GETOPT_SOURCES) set(GETOPT_INCLUDES) endif() # Build fy-tool set(FY_TOOL_SOURCES src/tool/fy-tool.c src/tool/fy-tool-dump.c ${GETOPT_SOURCES} ) set(FY_TOOL_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/src/valgrind ${CMAKE_CURRENT_SOURCE_DIR}/src/reflection ${CMAKE_CURRENT_SOURCE_DIR}/src/lib ${CMAKE_CURRENT_SOURCE_DIR}/src/util ${CMAKE_CURRENT_SOURCE_DIR}/src/xxhash ${CMAKE_CURRENT_SOURCE_DIR}/src/thread ${CMAKE_CURRENT_SOURCE_DIR}/src/allocator ${CMAKE_CURRENT_SOURCE_DIR}/src/blake3 ${CMAKE_CURRENT_SOURCE_DIR}/src/generic ${CMAKE_CURRENT_BINARY_DIR} ${GETOPT_INCLUDES} ) if(WIN32) list(APPEND FY_TOOL_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/src/util") endif() add_executable(fy-tool ${FY_TOOL_SOURCES}) target_include_directories(fy-tool PRIVATE ${FY_TOOL_INCLUDE_DIRS}) target_compile_definitions(fy-tool PRIVATE ${COMMON_C_DEFINITIONS}) target_link_libraries(fy-tool PRIVATE fyaml) if(ENABLE_STATIC_TOOLS AND NOT MSVC) target_link_options(fy-tool PRIVATE -static) endif() # Build internal tools - these are never installed, built for internal testing # These tools use private/internal APIs and embed libfyaml code via fyaml_static # System libraries are dynamically linked (works with or without ASAN) # Skip when cross-compiling since these tools cannot be executed on build host set(INTERNAL_TOOL_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src/valgrind ${CMAKE_CURRENT_SOURCE_DIR}/src/lib ${CMAKE_CURRENT_SOURCE_DIR}/src/util ${CMAKE_CURRENT_SOURCE_DIR}/src/xxhash ${CMAKE_CURRENT_SOURCE_DIR}/src/thread ${CMAKE_CURRENT_SOURCE_DIR}/src/allocator ${CMAKE_CURRENT_SOURCE_DIR}/src/generic ${CMAKE_CURRENT_SOURCE_DIR}/src/reflection ${CMAKE_CURRENT_BINARY_DIR} ${LIBYAML_INCLUDE_DIRS} ${GETOPT_INCLUDES} ) # libfyaml-parser requires libyaml if(HAVE_LIBYAML) add_executable(libfyaml-parser src/internal/libfyaml-parser.c ${GETOPT_SOURCES} ) target_compile_definitions(libfyaml-parser PRIVATE ${COMMON_C_DEFINITIONS}) target_compile_options(libfyaml-parser PRIVATE ${COMMON_C_FLAGS}) target_include_directories(libfyaml-parser PRIVATE ${INTERNAL_TOOL_INCLUDES}) target_link_directories(libfyaml-parser PRIVATE ${LIBYAML_LIBRARY_DIRS}) link_whole_archive_static(libfyaml-parser fyaml_static) target_link_libraries(libfyaml-parser PRIVATE ${LIBYAML_LIBRARIES} $<$:${LIBCLANG_LIBS}> ) if(NOT MSVC) if(HAVE_ASAN) target_link_options(libfyaml-parser PRIVATE ${ASAN_C_FLAGS}) endif() endif() if(HAVE_LIBCLANG) target_link_directories(libfyaml-parser PRIVATE ${LIBCLANG_LINK_DIRS}) endif() add_dependencies(libfyaml-parser fyaml_static) endif() add_executable(fy-thread src/internal/fy-thread.c ${GETOPT_SOURCES} ) target_include_directories(fy-thread PRIVATE ${INTERNAL_TOOL_INCLUDES}) target_compile_definitions(fy-thread PRIVATE ${COMMON_C_DEFINITIONS} FY_STATIC) link_whole_archive_static(fy-thread fyaml_static) target_link_libraries(fy-thread PRIVATE $<$:${LIBCLANG_LIBS}> ) if(NOT MSVC) if(HAVE_ASAN) target_link_options(fy-thread PRIVATE ${ASAN_C_FLAGS}) endif() endif() if(HAVE_LIBCLANG) target_link_directories(fy-thread PRIVATE ${LIBCLANG_LINK_DIRS}) endif() add_dependencies(fy-thread fyaml_static) add_executable(fy-b3sum src/internal/fy-b3sum.c ${GETOPT_SOURCES} ) target_include_directories(fy-b3sum PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/blake3 ${INTERNAL_TOOL_INCLUDES} ) target_compile_definitions(fy-b3sum PRIVATE ${COMMON_C_DEFINITIONS} FY_STATIC) link_whole_archive_static(fy-b3sum fyaml_static) target_link_libraries(fy-b3sum PRIVATE $<$:${LIBCLANG_LIBS}> ) if(NOT MSVC) if(HAVE_ASAN) target_link_options(fy-b3sum PRIVATE ${ASAN_C_FLAGS}) endif() endif() if(HAVE_LIBCLANG) target_link_directories(fy-b3sum PRIVATE ${LIBCLANG_LINK_DIRS}) endif() add_dependencies(fy-b3sum fyaml_static) add_executable(fy-allocators src/internal/fy-allocators.c ${GETOPT_SOURCES} ) target_include_directories(fy-allocators PRIVATE ${INTERNAL_TOOL_INCLUDES}) target_compile_definitions(fy-allocators PRIVATE ${COMMON_C_DEFINITIONS} FY_STATIC) link_whole_archive_static(fy-allocators fyaml_static) target_link_libraries(fy-allocators PRIVATE $<$:${LIBCLANG_LIBS}> ) if(NOT MSVC) if(HAVE_ASAN) target_link_options(fy-allocators PRIVATE ${ASAN_C_FLAGS}) endif() endif() if(HAVE_LIBCLANG) target_link_directories(fy-allocators PRIVATE ${LIBCLANG_LINK_DIRS}) endif() add_dependencies(fy-allocators fyaml_static) if(BUILD_TESTING AND HAVE_BASH AND HAVE_CHECK AND (NOT CROSS_COMPILING OR HAVE_WINE)) set(CAN_TEST 1) else() set(CAN_TEST 0) endif() # Testing if(CAN_TEST) # Include TAP subtest registration functions include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/add-tap-subtests.cmake) # Create test directory file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test) # Create src directory for compatibility with test scripts # Tests expect tools at build/src/ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/src) # Build libfyaml-test if check is available if(HAVE_COMPATIBLE_CHECK) add_executable(libfyaml-test test/libfyaml-test.c test/libfyaml-test-core.c test/libfyaml-test-meta.c test/libfyaml-test-emit.c test/libfyaml-test-atom-analysis.c test/libfyaml-test-emit-bugs.c test/libfyaml-test-parse-bugs.c test/libfyaml-test-allocator.c test/libfyaml-test-fuzzing.c test/libfyaml-test-private.c test/libfyaml-test-private-id.c test/libfyaml-test-parser.c test/libfyaml-test-thread.c $<$:test/libfyaml-test-generic.c> $<$:test/libfyaml-test-generic-scalars.c> $<$:test/libfyaml-test-reflection.c> ${GETOPT_SOURCES} ) target_compile_options(libfyaml-test PRIVATE ${COMMON_C_FLAGS}) target_compile_definitions(libfyaml-test PRIVATE ${COMMON_C_DEFINITIONS}) target_include_directories(libfyaml-test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src/check ${CMAKE_CURRENT_SOURCE_DIR}/src/valgrind ${CMAKE_CURRENT_SOURCE_DIR}/src/lib ${CMAKE_CURRENT_SOURCE_DIR}/src/util ${CMAKE_CURRENT_SOURCE_DIR}/src/allocator ${CMAKE_CURRENT_SOURCE_DIR}/src/generic ${CMAKE_CURRENT_SOURCE_DIR}/src/reflection ${CMAKE_CURRENT_BINARY_DIR} ${CHECK_INCLUDE_DIRS} ${GETOPT_INCLUDES} ) # Link against static library to access private symbols # Use same pattern as internal tools link_whole_archive_static(libfyaml-test fyaml_static) target_link_libraries(libfyaml-test PRIVATE ${CHECK_LIBRARIES} $<$:${LIBCLANG_LIBS}> ) if(NOT MSVC) if(HAVE_ASAN) target_link_options(libfyaml-test PRIVATE ${ASAN_C_FLAGS}) endif() endif() if(CHECK_LIBRARY_DIRS) target_link_directories(libfyaml-test PRIVATE ${CHECK_LIBRARY_DIRS}) endif() if(HAVE_LIBCLANG) target_link_directories(libfyaml-test PRIVATE ${LIBCLANG_LINK_DIRS}) endif() if (HAVE_CLANG_BLOCKS) target_compile_options(libfyaml-test PRIVATE -fblocks) if (CLANG_BLOCKS_LIB) target_link_libraries(libfyaml-test PRIVATE ${CLANG_BLOCKS_LIB}) endif() endif() add_dependencies(libfyaml-test fyaml_static) # Set output directory to match autoconf behavior set_target_properties(libfyaml-test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test ) add_libfyaml_tests() endif() # Error tests - register individual subtests add_testerrors_tests() # Emitter tests - register individual subtests add_testemitter_tests(testemitter "") add_testemitter_tests(testemitter-streaming "--streaming") add_testemitter_tests(testemitter-restreaming "--streaming --recreating") if(HAVE_GENERIC) add_testemitter_tests(testemitter-generic-failsafe "" SUITE_NAME testemitter EXTRA_ENV "EMITTER_DUMP_ARGS=--generic --keep-style --schema yaml1.2-failsafe" ) endif() # Network-dependent tests using FetchContent if(HAVE_NETWORK) # Download test suites at configure time via FetchContent message(STATUS "Fetching YAML test suite...") FetchContent_MakeAvailable(yaml_test_suite) message(STATUS "Fetching JSON test suite...") FetchContent_MakeAvailable(json_test_suite) # YAML test suite tests - register individual subtests # Two modes (handled automatically by add_testsuite_tests): # 1. If test-suite-data exists (via FetchContent symlink): scan filesystem # 2. If cmake/testsuite-tests.cmake exists: use pre-generated list (offline builds) add_testsuite_tests(testsuite testsuite.test) add_testsuite_tests(testsuite-evstream testsuite-evstream.test) add_testsuite_tests(testsuite-resolution testsuite-resolution.test) if(HAVE_GENERIC) add_testsuite_tests(testsuite-generic testsuite-generic.test) endif() if(HAVE_JQ) add_testsuite_tests(testsuite-json testsuite-json.test) endif() # JSON test suite - register individual subtests add_jsontestsuite_tests() endif() # Reflection tests - only if libclang is available and reflection is enabled if(HAVE_REFLECTION AND HAVE_LIBCLANG) add_testreflection_tests() add_testreflection_packed_tests() endif() # Build all test dependencies as part of default 'all' target # Note: CMake's 'make test' target doesn't trigger builds by design. # Use 'make all test' or 'make check-am' or 'make build_and_test' for complete workflow. set(TEST_DEPENDENCIES fy-tool fy-thread fy-b3sum fy-allocators) if(HAVE_COMPATIBLE_CHECK) list(APPEND TEST_DEPENDENCIES libfyaml-test) endif() if(HAVE_LIBYAML) list(APPEND TEST_DEPENDENCIES libfyaml-parser) endif() set(TEST_SUITES testsuite.test jsontestsuite.test testsuite-evstream.test testsuite-resolution.test testerrors.test testemitter.test testemitter-streaming.test testemitter-restreaming.test ) if(HAVE_GENERIC) list(APPEND TEST_SUITES testsuite-generic.test testemitter-generic-failsafe.test ) endif() if(HAVE_JQ) list(APPEND TEST_SUITES testsuite-json.test) endif() endif() # Documentation find_program(SPHINX_EXECUTABLE NAMES sphinx-build) find_program(LATEXMK_EXECUTABLE NAMES latexmk) find_program(KPSEWHICH_EXECUTABLE NAMES kpsewhich) set(HAVE_XCOLOR_STY FALSE) if(KPSEWHICH_EXECUTABLE) execute_process( COMMAND ${KPSEWHICH_EXECUTABLE} xcolor.sty RESULT_VARIABLE KPSEWHICH_XCOLOR_RESULT OUTPUT_QUIET ERROR_QUIET ) if(KPSEWHICH_XCOLOR_RESULT EQUAL 0) set(HAVE_XCOLOR_STY TRUE) endif() endif() # all of these to be able to build doc-latexpdf set(HAVE_DOC_LATEXPDF (SPHINX_EXECUTABLE AND LATEXMK_EXECUTABLE AND HAVE_XCOLOR_STY)) if(SPHINX_EXECUTABLE) set(HAVE_SPHINX TRUE) set(SPHINX_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/doc") set(SPHINX_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/doc/_build") # Add custom targets for documentation add_custom_target(doc-html COMMAND ${SPHINX_EXECUTABLE} -M html "${SPHINX_SOURCE_DIR}" "${SPHINX_BUILD_DIR}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Building HTML documentation with Sphinx" VERBATIM ) add_custom_target(doc-man COMMAND ${SPHINX_EXECUTABLE} -M man "${SPHINX_SOURCE_DIR}" "${SPHINX_BUILD_DIR}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Building man pages with Sphinx" VERBATIM ) add_custom_target(doc-text COMMAND ${SPHINX_EXECUTABLE} -M text "${SPHINX_SOURCE_DIR}" "${SPHINX_BUILD_DIR}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Building plain-text documentation with Sphinx" VERBATIM ) if(HAVE_DOC_LATEXPDF) add_custom_target(doc-latexpdf COMMAND ${SPHINX_EXECUTABLE} -M latexpdf "${SPHINX_SOURCE_DIR}" "${SPHINX_BUILD_DIR}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Building PDF documentation with Sphinx" VERBATIM ) endif() add_custom_target(doc-clean COMMAND ${CMAKE_COMMAND} -E remove_directory "${SPHINX_BUILD_DIR}" COMMENT "Cleaning documentation build directory" ) add_custom_target(doc-canned-man DEPENDS doc-man COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SPHINX_BUILD_DIR}/man/fy-tool.1" "${CMAKE_CURRENT_SOURCE_DIR}/doc/canned-man/fy-tool.1" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SPHINX_BUILD_DIR}/man/libfyaml.3" "${CMAKE_CURRENT_SOURCE_DIR}/doc/canned-man/libfyaml.3" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SPHINX_BUILD_DIR}/man/libfyaml-core.3" "${CMAKE_CURRENT_SOURCE_DIR}/doc/canned-man/libfyaml-core.3" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SPHINX_BUILD_DIR}/man/libfyaml-misc.3" "${CMAKE_CURRENT_SOURCE_DIR}/doc/canned-man/libfyaml-misc.3" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SPHINX_BUILD_DIR}/man/libfyaml-generics.3" "${CMAKE_CURRENT_SOURCE_DIR}/doc/canned-man/libfyaml-generics.3" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SPHINX_BUILD_DIR}/man/libfyaml-reflection.3" "${CMAKE_CURRENT_SOURCE_DIR}/doc/canned-man/libfyaml-reflection.3" COMMENT "Updating canned man pages from the built Sphinx man pages" VERBATIM ) message(STATUS "Sphinx found: ${SPHINX_EXECUTABLE}") message(STATUS " make doc-html - Build HTML documentation") message(STATUS " make doc-man - Build man pages") message(STATUS " make doc-canned-man - Refresh canned man pages from doc-man") message(STATUS " make doc-text - Build plain-text documentation") if(HAVE_DOC_LATEXPDF) message(STATUS " make doc-latexpdf - Build PDF documentation") endif() else() set(HAVE_SPHINX FALSE) message(STATUS "Sphinx not found - documentation targets disabled") message(STATUS " Install: pip3 install sphinx sphinx_rtd_theme sphinx-markdown-builder linuxdoc") endif() install(FILES doc/canned-man/fy-tool.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) install(FILES doc/canned-man/libfyaml.3 doc/canned-man/libfyaml-core.3 doc/canned-man/libfyaml-misc.3 doc/canned-man/libfyaml-generics.3 doc/canned-man/libfyaml-reflection.3 DESTINATION ${CMAKE_INSTALL_MANDIR}/man3 ) if(NOT WIN32) install(CODE " execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink fy-tool.1 fy-dump.1 WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1) execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink fy-tool.1 fy-filter.1 WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1) execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink fy-tool.1 fy-testsuite.1 WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1) execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink fy-tool.1 fy-join.1 WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1) execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink fy-tool.1 fy-ypath.1 WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1) execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink fy-tool.1 fy-compose.1 WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1) ") endif() # Install targets install(TARGETS fyaml fy-tool EXPORT libfyaml-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) # Install sub-headers into /include/libfyaml/ install(FILES ${LIBHDRSPUB} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/libfyaml ) # Create symlinks for fy-tool (not on Windows - symlinks require special privileges) if(NOT WIN32) install(CODE " execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink fy-tool fy-dump WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink fy-tool fy-filter WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink fy-tool fy-testsuite WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink fy-tool fy-join WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink fy-tool fy-ypath WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink fy-tool fy-compose WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) ") endif() install(EXPORT libfyaml-targets FILE libfyaml-targets.cmake NAMESPACE libfyaml:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libfyaml ) export( TARGETS fyaml FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/libfyaml-targets.cmake" NAMESPACE libfyaml:: ) # Package config include(CMakePackageConfigHelpers) configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/libfyaml-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/cmake/libfyaml-config.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libfyaml ) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/cmake/libfyaml-config-version.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/libfyaml-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake/libfyaml-config-version.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libfyaml ) # pkg-config file configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/libfyaml.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libfyaml.pc @ONLY ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/libfyaml.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig ) # tags and cscope support include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/tags.cmake) # Python core bindings if(NOT CMAKE_CROSSCOMPILING) find_package(Python3 COMPONENTS Interpreter Development.Module) endif() if(CMAKE_CROSSCOMPILING) set(_ENABLE_PYTHON_BINDINGS_DEFAULT OFF) elseif(Python3_FOUND) set(_ENABLE_PYTHON_BINDINGS_DEFAULT ON) else() set(_ENABLE_PYTHON_BINDINGS_DEFAULT OFF) endif() option(ENABLE_PYTHON_BINDINGS "Build Python core bindings (_libfyaml extension)" ${_ENABLE_PYTHON_BINDINGS_DEFAULT}) if(CMAKE_CROSSCOMPILING AND ENABLE_PYTHON_BINDINGS) message(STATUS "Python bindings disabled (cross-compiling requires a target Python SDK, which is not auto-detected)") set(HAVE_PYTHON_BINDINGS 0) elseif(WIN32 AND ENABLE_PYTHON_BINDINGS AND NOT CMAKE_C_COMPILER_ID STREQUAL "Clang") message(STATUS "Python bindings disabled (windows requires clang/clang-cl)") set(HAVE_PYTHON_BINDINGS 0) elseif(ENABLE_PYTHON_BINDINGS AND NOT HAVE_GENERIC) message(STATUS "Python bindings disabled (requires statement expressions / generic support)") set(HAVE_PYTHON_BINDINGS 0) elseif(ENABLE_PYTHON_BINDINGS) if(NOT Python3_FOUND) message(FATAL_ERROR "Python3 development headers not found (required for ENABLE_PYTHON_BINDINGS)") endif() if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug" AND (NOT DEFINED Python3_LIBRARY_DEBUG OR Python3_LIBRARY_DEBUG MATCHES "-NOTFOUND$")) message(STATUS "Python bindings disabled (windows Debug build requires matching Python debug import library)") set(HAVE_PYTHON_BINDINGS 0) else() Python3_add_library(_libfyaml MODULE WITH_SOABI "${CMAKE_CURRENT_SOURCE_DIR}/python-libfyaml/libfyaml/_libfyaml.c" ) target_include_directories(_libfyaml PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}" ) target_link_libraries(_libfyaml PRIVATE fyaml_static) target_compile_options(_libfyaml PRIVATE ${COMMON_C_FLAGS}) if(HAVE_ASAN) target_compile_options(_libfyaml PRIVATE ${ASAN_C_FLAGS}) target_link_options(_libfyaml PRIVATE ${ASAN_C_FLAGS}) endif() # Place the .so in the build tree, not the source tree set(_PY_PKG_SRC "${CMAKE_CURRENT_SOURCE_DIR}/python-libfyaml/libfyaml") set(_PY_PKG_DST "${CMAKE_CURRENT_BINARY_DIR}/python-libfyaml/libfyaml") set_target_properties(_libfyaml PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${_PY_PKG_DST}" RUNTIME_OUTPUT_DIRECTORY "${_PY_PKG_DST}" PREFIX "" ) # Link/copy source files into the build staging dir so the complete # package is importable directly from the build tree. # COPY_ON_ERROR makes this work on Windows where symlinks may be # unavailable (requires Developer Mode or admin rights). file(MAKE_DIRECTORY "${_PY_PKG_DST}") file(GLOB _py_srcs "${_PY_PKG_SRC}/*.py" "${_PY_PKG_SRC}/*.pyi") foreach(_src ${_py_srcs}) get_filename_component(_name "${_src}" NAME) file(CREATE_LINK "${_src}" "${_PY_PKG_DST}/${_name}" SYMBOLIC COPY_ON_ERROR) endforeach() set(HAVE_PYTHON_BINDINGS 1) endif() else() set(HAVE_PYTHON_BINDINGS 0) endif() # Python binding tests (pytest) - one CTest entry per test function/method # ASan + dlopen of an ASan-instrumented extension crashes on macOS and Windows, # so skip Python tests in those configurations. if(HAVE_PYTHON_BINDINGS AND BUILD_TESTING AND NOT CMAKE_CROSSCOMPILING AND NOT (HAVE_ASAN AND (APPLE OR WIN32))) add_python_tests() endif() # Print configuration summary message(STATUS "") message(STATUS "---{ ${PROJECT_NAME} ${PROJECT_VERSION} }---") message(STATUS "") message(STATUS "VERSION: ${PROJECT_VERSION}") message(STATUS "MAJOR.MINOR: ${VERSION_MAJOR}.${VERSION_MINOR}") message(STATUS "PATCH: ${VERSION_PATCH}") message(STATUS "EXTRA: ${VERSION_EXTRA}") message(STATUS "LIBTOOL_VERSION: ${LIBTOOL_VERSION}") message(STATUS "prefix: ${CMAKE_INSTALL_PREFIX}") message(STATUS "Build system: ${CMAKE_HOST_SYSTEM_NAME} ${CMAKE_HOST_SYSTEM_PROCESSOR}") message(STATUS "Target system: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_PROCESSOR}") message(STATUS "Cross compiling: ${CROSS_COMPILING}") message(STATUS "C compiler: ${CMAKE_C_COMPILER}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") message(STATUS "HAVE_BASH: ${HAVE_BASH}") message(STATUS "HAVE_CHECK: ${HAVE_CHECK}") message(STATUS "HAVE_COMPATIBLE_CHECK: ${HAVE_COMPATIBLE_CHECK}") message(STATUS "HAVE_LIBYAML: ${HAVE_LIBYAML}") message(STATUS "HAVE_CMAKE_GEXPR: ${HAVE_CMAKE_GEXPR}") message(STATUS "HAVE_LIBCLANG: ${HAVE_LIBCLANG}") message(STATUS "HAVE_GENERIC: ${HAVE_GENERIC}") message(STATUS "HAVE_REFLECTION: ${HAVE_REFLECTION}") message(STATUS "HAVE_PYTHON_BINDINGS: ${HAVE_PYTHON_BINDINGS}") message(STATUS "HAVE_CLANG_BLOCKS: ${HAVE_CLANG_BLOCKS}") message(STATUS "HAVE_NETWORK: ${HAVE_NETWORK}") message(STATUS "HAVE_DEVMODE: ${HAVE_DEVMODE}") message(STATUS "HAVE_GIT: ${HAVE_GIT}") message(STATUS "HAVE_JQ: ${HAVE_JQ}") message(STATUS "HAVE_DOCKER: ${HAVE_DOCKER}") message(STATUS "HAVE_SPHINX: ${HAVE_SPHINX}") message(STATUS "HAVE_ASAN: ${HAVE_ASAN}") message(STATUS "TARGET_HAS_SSE2: ${TARGET_HAS_SSE2}") message(STATUS "TARGET_HAS_SSE41: ${TARGET_HAS_SSE41}") message(STATUS "TARGET_HAS_AVX2: ${TARGET_HAS_AVX2}") message(STATUS "TARGET_HAS_AVX512: ${TARGET_HAS_AVX512}") message(STATUS "TARGET_HAS_NEON: ${TARGET_HAS_NEON}") message(STATUS "HAVE_HEAP_TRAMPOLINES: ${HAVE_HEAP_TRAMPOLINES}") message(STATUS "TESTSUITEURL: ${TESTSUITEURL}") message(STATUS "TESTSUITECHECKOUT: ${TESTSUITECHECKOUT}") message(STATUS "JSONTESTSUITEURL: ${JSONTESTSUITEURL}") message(STATUS "JSONTESTSUITECHECKOUT: ${JSONTESTSUITECHECKOUT}") message(STATUS "CAN_TEST: ${CAN_TEST}") message(STATUS "") if(CROSS_COMPILING AND NOT HAVE_WINE) message(STATUS "Tests disabled when cross-compiling (target binaries cannot execute on build host)") message(STATUS "") elseif(CROSS_COMPILING AND HAVE_WINE) message(STATUS "Tests enabled via wine: ${WINE_EXECUTABLE}") message(STATUS "") endif()