diff options
Diffstat (limited to 'cmake')
48 files changed, 1816 insertions, 60 deletions
diff --git a/cmake/AddCompileFlags.cmake b/cmake/AddCompileFlags.cmake deleted file mode 100644 index 8f3877d9..00000000 --- a/cmake/AddCompileFlags.cmake +++ /dev/null @@ -1,17 +0,0 @@ -# - MACRO_ADD_COMPILE_FLAGS(<_target> "flags...") - -# Copyright (c) 2006, Oswald Buddenhagen, <ossi@kde.org> -# -# Redistribution and use is allowed according to the terms of the BSD license. - -macro(add_compile_flags _target _flg) - - get_target_property(_flags ${_target} COMPILE_FLAGS) - if (_flags) - set(_flags "${_flags} ${_flg}") - else (_flags) - set(_flags "${_flg}") - endif (_flags) - set_target_properties(${_target} PROPERTIES COMPILE_FLAGS "${_flags}") - -endmacro(add_compile_flags) diff --git a/cmake/CmakeUninstall.cmake.in b/cmake/CmakeUninstall.cmake.in deleted file mode 100644 index 4c07dc7b..00000000 --- a/cmake/CmakeUninstall.cmake.in +++ /dev/null @@ -1,21 +0,0 @@ -if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") - message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") -endif(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") - -file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) -string(REGEX REPLACE "\n" ";" files "${files}") -foreach(file ${files}) - message(STATUS "Uninstalling $ENV{DESTDIR}${file}") - if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") - exec_program( - "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" - OUTPUT_VARIABLE rm_out - RETURN_VALUE rm_retval - ) - if(NOT "${rm_retval}" STREQUAL 0) - message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") - endif(NOT "${rm_retval}" STREQUAL 0) - else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") - message(STATUS "File $ENV{DESTDIR}${file} does not exist.") - endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") -endforeach(file) diff --git a/cmake/OuroborosConfig.cmake.in b/cmake/OuroborosConfig.cmake.in new file mode 100644 index 00000000..41ff7fdd --- /dev/null +++ b/cmake/OuroborosConfig.cmake.in @@ -0,0 +1,23 @@ +# Ouroboros CMake Package Configuration +# +# This file allows other CMake projects to find and use Ouroboros libraries +# via find_package(Ouroboros). +# +# Exported targets: +# Ouroboros::dev - Development library (libouroboros-dev) +# Ouroboros::irm - IRM management library (libouroboros-irm) +# +# Example usage: +# find_package(Ouroboros REQUIRED) +# target_link_libraries(myapp Ouroboros::dev) + +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# Required dependencies for Ouroboros libraries +find_dependency(Threads) + +include("${CMAKE_CURRENT_LIST_DIR}/OuroborosTargets.cmake") + +check_required_components(Ouroboros) diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake new file mode 100644 index 00000000..5d452e02 --- /dev/null +++ b/cmake/compiler.cmake @@ -0,0 +1,64 @@ +include(utils/CompilerUtils) + +test_and_set_c_compiler_flag_global(-std=c89) +test_and_set_c_compiler_flag_global(-Wall) +# -Wextra may fail on clobbered warning due to pthread_cleanup +test_and_set_c_compiler_flag_global(-Wno-clobbered) +test_and_set_c_compiler_flag_global(-Wextra) +# explicitly add other flags in -Wextra +test_and_set_c_compiler_flag_global(-Wempty-body) +test_and_set_c_compiler_flag_global(-Wignored-qualifiers) +test_and_set_c_compiler_flag_global(-Wimplicit-fallthrough=4) +test_and_set_c_compiler_flag_global(-Wmissing-field-initializers) +test_and_set_c_compiler_flag_global(-Wmissing-parameter-type) +test_and_set_c_compiler_flag_global(-Wold-style-declaration) +test_and_set_c_compiler_flag_global(-Woverride-init) +test_and_set_c_compiler_flag_global(-Wsign-compare) +test_and_set_c_compiler_flag_global(-Wtype-limits) +test_and_set_c_compiler_flag_global(-Wuninitialized) +test_and_set_c_compiler_flag_global(-Wshift-negative-value) +test_and_set_c_compiler_flag_global(-Wunused-parameter) +test_and_set_c_compiler_flag_global(-Wunused-but-set-parameter) +test_and_set_c_compiler_flag_global(-Werror) +test_and_set_c_compiler_flag_global(-Wundef) +test_and_set_c_compiler_flag_global(-Wpointer-arith) +test_and_set_c_compiler_flag_global(-Wstrict-prototypes) +test_and_set_c_compiler_flag_global(-Wvla) +test_and_set_c_compiler_flag_global(-Wswitch-default) +test_and_set_c_compiler_flag_global(-Wreturn-type) +test_and_set_c_compiler_flag_global(-Wunreachable-code) +test_and_set_c_compiler_flag_global(-Wdeclaration-after-statement) +test_and_set_c_compiler_flag_global(-Winfinite-recursion) +test_and_set_c_compiler_flag_global(-fmax-errors=5) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING + "Build type (Release, Debug, DebugASan, DebugTSan, DebugLSan, DebugUSan, DebugAnalyzer)" FORCE) +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + test_and_set_c_compiler_flag_global(-O3) +elseif(CMAKE_BUILD_TYPE STREQUAL "Debug") + test_and_set_c_compiler_flag_global(-g) +elseif(CMAKE_BUILD_TYPE STREQUAL "DebugASan") + test_and_set_c_compiler_flag_global(-g) + test_and_set_c_compiler_flag_global(-fsanitize=address) + add_link_options(-fsanitize=address) +elseif(CMAKE_BUILD_TYPE STREQUAL "DebugTSan") + test_and_set_c_compiler_flag_global(-g) + test_and_set_c_compiler_flag_global(-fsanitize=thread) + add_link_options(-fsanitize=thread) +elseif(CMAKE_BUILD_TYPE STREQUAL "DebugLSan") + test_and_set_c_compiler_flag_global(-g) + test_and_set_c_compiler_flag_global(-fsanitize=leak) + add_link_options(-fsanitize=leak) +elseif(CMAKE_BUILD_TYPE STREQUAL "DebugUSan") + test_and_set_c_compiler_flag_global(-g) + test_and_set_c_compiler_flag_global(-fsanitize=undefined) + add_link_options(-fsanitize=undefined) +elseif(CMAKE_BUILD_TYPE STREQUAL "DebugAnalyzer") + test_and_set_c_compiler_flag_global(-g) + test_and_set_c_compiler_flag_global(-fanalyzer) +else() + message(FATAL_ERROR "Unknown build type ${CMAKE_BUILD_TYPE}") +endif() diff --git a/cmake/config/global.cmake b/cmake/config/global.cmake new file mode 100644 index 00000000..243e1ba0 --- /dev/null +++ b/cmake/config/global.cmake @@ -0,0 +1,45 @@ +# Global configuration options for Ouroboros +# These options affect the entire framework + +# Installation directories +set(OUROBOROS_CONFIG_DIR "${CMAKE_INSTALL_FULL_SYSCONFDIR}/ouroboros" CACHE PATH + "Configuration directory") + +# Security directories +set(OUROBOROS_SECURITY_DIR "${OUROBOROS_CONFIG_DIR}/security" CACHE PATH + "Security directory holding authentication information") +set(OUROBOROS_CA_CRT_DIR "${OUROBOROS_SECURITY_DIR}/cacert" CACHE PATH + "Directory holding trusted CA certificates") +set(OUROBOROS_SRV_CRT_DIR "${OUROBOROS_SECURITY_DIR}/server" CACHE PATH + "Directory holding server certificates") +set(OUROBOROS_CLI_CRT_DIR "${OUROBOROS_SECURITY_DIR}/client" CACHE PATH + "Directory holding client certificates") +set(OUROBOROS_UNTRUSTED_DIR "${OUROBOROS_SECURITY_DIR}/untrusted" CACHE PATH + "Directory holding untrusted intermediate certificates") + +# Shared memory naming +set(SHM_PREFIX "ouroboros" CACHE STRING + "String to prepend to POSIX shared memory filenames") +set(SHM_LOCKFILE_NAME "/${SHM_PREFIX}.lockfile" CACHE INTERNAL + "Filename for the POSIX shared memory lockfile") + +# FUSE configuration +if(HAVE_FUSE) + set(FUSE_PREFIX "/tmp/ouroboros" CACHE STRING + "Mountpoint for RIB filesystem") +endif() + +# Secure memory configuration +set(IRMD_SECMEM_MAX 1048576 CACHE STRING "IRMd secure heap size") +set(PROC_SECMEM_MAX 1048576 CACHE STRING "Process secure heap size") +set(SECMEM_GUARD 32 CACHE STRING "Secure heap min size") + +# Container/deployment options +set(BUILD_CONTAINER FALSE CACHE BOOL + "Disable thread priority setting for container compatibility") +set(DISABLE_CORE_LOCK TRUE CACHE BOOL + "Disable locking performance threads to a core") + +# IPC socket configuration +set(SOCK_BUF_SIZE 10240 CACHE STRING + "Size of the buffer used by the UNIX sockets for local IPC") diff --git a/cmake/config/ipcp/broadcast.cmake b/cmake/config/ipcp/broadcast.cmake new file mode 100644 index 00000000..3c4d98da --- /dev/null +++ b/cmake/config/ipcp/broadcast.cmake @@ -0,0 +1,4 @@ +# Broadcast IPCP configuration options for Ouroboros + +set(IPCP_BROADCAST_MPL 100 CACHE STRING + "Default maximum packet lifetime for the Broadcast IPCP, in ms") diff --git a/cmake/config/ipcp/common.cmake b/cmake/config/ipcp/common.cmake new file mode 100644 index 00000000..ffd5dc32 --- /dev/null +++ b/cmake/config/ipcp/common.cmake @@ -0,0 +1,43 @@ +# Common IPCP configuration options for Ouroboros +# Options affecting all IPC Process types + +# Connection manager +set(CONNMGR_RCV_TIMEOUT 1000 CACHE STRING + "Timeout for the connection manager to wait for OCEP info (ms).") + +# Debugging +set(IPCP_DEBUG_LOCAL FALSE CACHE BOOL + "Use PID as address for local debugging") + +# QoS cube priorities (0-99, higher = more priority) +set(IPCP_QOS_CUBE_BE_PRIO 50 CACHE STRING + "Priority for best effort QoS cube (0-99)") +set(IPCP_QOS_CUBE_VIDEO_PRIO 90 CACHE STRING + "Priority for video QoS cube (0-99)") +set(IPCP_QOS_CUBE_VOICE_PRIO 99 CACHE STRING + "Priority for voice QoS cube (0-99)") + +# Validate QoS cube priorities +if((IPCP_QOS_CUBE_BE_PRIO LESS 0) OR (IPCP_QOS_CUBE_BE_PRIO GREATER 99)) + message(FATAL_ERROR "Invalid priority for best effort QoS cube (must be 0-99)") +endif() +if((IPCP_QOS_CUBE_VIDEO_PRIO LESS 0) OR (IPCP_QOS_CUBE_VIDEO_PRIO GREATER 99)) + message(FATAL_ERROR "Invalid priority for video QoS cube (must be 0-99)") +endif() +if((IPCP_QOS_CUBE_VOICE_PRIO LESS 0) OR (IPCP_QOS_CUBE_VOICE_PRIO GREATER 99)) + message(FATAL_ERROR "Invalid priority for voice QoS cube (must be 0-99)") +endif() + +# Threading +set(IPCP_MIN_THREADS 4 CACHE STRING + "Minimum number of worker threads in the IPCP") +set(IPCP_ADD_THREADS 4 CACHE STRING + "Number of extra threads to start when an IPCP faces thread starvation") +set(IPCP_SCHED_THR_MUL 2 CACHE STRING + "Number of scheduler threads per QoS cube") + +# Linux-specific +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(IPCP_LINUX_TIMERSLACK_NS 100 CACHE STRING + "Slack value for high resolution timers on Linux systems.") +endif() diff --git a/cmake/config/ipcp/eth.cmake b/cmake/config/ipcp/eth.cmake new file mode 100644 index 00000000..2f50d24d --- /dev/null +++ b/cmake/config/ipcp/eth.cmake @@ -0,0 +1,15 @@ +# Ethernet IPCP configuration options for Ouroboros +# Options for eth-llc and eth-dix IPCPs + +set(IPCP_ETH_RD_THR 1 CACHE STRING + "Number of reader threads in Ethernet IPCP") +set(IPCP_ETH_WR_THR 1 CACHE STRING + "Number of writer threads in Ethernet IPCP") +set(IPCP_ETH_QDISC_BYPASS false CACHE BOOL + "Bypass the Qdisc in the kernel when using raw sockets") +set(IPCP_ETH_LO_MTU 9000 CACHE STRING + "Restrict Ethernet MTU over loopback interfaces") +set(IPCP_ETH_MGMT_FRAME_SIZE 9000 CACHE STRING + "Management frame buffer size for Ethernet IPCPs") +set(IPCP_ETH_MPL 100 CACHE STRING + "Default maximum packet lifetime for the Ethernet IPCPs, in ms") diff --git a/cmake/config/ipcp/local.cmake b/cmake/config/ipcp/local.cmake new file mode 100644 index 00000000..df47c45b --- /dev/null +++ b/cmake/config/ipcp/local.cmake @@ -0,0 +1,7 @@ +# Local IPCP configuration options for Ouroboros + +set(IPCP_LOCAL_MPL 100 CACHE STRING + "Default maximum packet lifetime for the Local IPCP, in ms") + +set(IPCP_LOCAL_POLLING FALSE CACHE BOOL + "Enable active polling in the Local IPCP for low-latency mode") diff --git a/cmake/config/ipcp/udp.cmake b/cmake/config/ipcp/udp.cmake new file mode 100644 index 00000000..691948ab --- /dev/null +++ b/cmake/config/ipcp/udp.cmake @@ -0,0 +1,9 @@ +# UDP IPCP configuration options for Ouroboros +# Options for udp4 and udp6 IPCPs + +set(IPCP_UDP_RD_THR 3 CACHE STRING + "Number of reader threads in UDP IPCPs") +set(IPCP_UDP_WR_THR 3 CACHE STRING + "Number of writer threads in UDP IPCPs") +set(IPCP_UDP_MPL 5000 CACHE STRING + "Default maximum packet lifetime for the UDP IPCPs, in ms") diff --git a/cmake/config/ipcp/unicast.cmake b/cmake/config/ipcp/unicast.cmake new file mode 100644 index 00000000..905c661a --- /dev/null +++ b/cmake/config/ipcp/unicast.cmake @@ -0,0 +1,12 @@ +# Unicast IPCP configuration options for Ouroboros + +set(IPCP_UNICAST_MPL 100 CACHE STRING + "Default maximum packet lifetime for the Unicast IPCP, in ms") +set(PFT_SIZE 256 CACHE STRING + "Prefix forwarding table size for the Unicast IPCP") + +# Protocol debugging +set(DEBUG_PROTO_DHT FALSE CACHE BOOL + "Add DHT protocol debug logging") +set(DEBUG_PROTO_LS FALSE CACHE BOOL + "Add link state protocol debug logging") diff --git a/cmake/config/irmd.cmake b/cmake/config/irmd.cmake new file mode 100644 index 00000000..9795e4a4 --- /dev/null +++ b/cmake/config/irmd.cmake @@ -0,0 +1,36 @@ +# IRMd configuration options for Ouroboros +# Options affecting the IPC Resource Manager daemon + +# Timeouts (all in milliseconds unless noted) +set(IRMD_REQ_ARR_TIMEOUT 1000 CACHE STRING + "Timeout for an application to respond to a new flow (ms)") +set(BOOTSTRAP_TIMEOUT 5000 CACHE STRING + "Timeout for an IPCP to bootstrap (ms)") +set(ENROLL_TIMEOUT 20000 CACHE STRING + "Timeout for an IPCP to enroll (ms)") +set(REG_TIMEOUT 20000 CACHE STRING + "Timeout for registering a name (ms)") +set(QUERY_TIMEOUT 200 CACHE STRING + "Timeout to query a name with an IPCP (ms)") +set(CONNECT_TIMEOUT 20000 CACHE STRING + "Timeout to connect an IPCP to another IPCP (ms)") +set(FLOW_ALLOC_TIMEOUT 20000 CACHE STRING + "Timeout for a flow allocation response (ms)") + +# OAP (Ouroboros Authentication Protocol) +set(OAP_REPLAY_TIMER 20 CACHE STRING + "OAP replay protection window (s)") +set(DEBUG_PROTO_OAP FALSE CACHE BOOL + "Add Flow allocation protocol message output to IRMd debug logging") + +# Threading +set(IRMD_MIN_THREADS 8 CACHE STRING + "Minimum number of worker threads in the IRMd") +set(IRMD_ADD_THREADS 8 CACHE STRING + "Number of extra threads to start when the IRMD faces thread starvation") + +# Process management +set(IRMD_PKILL_TIMEOUT 30 CACHE STRING + "Number of seconds to wait before sending SIGKILL to subprocesses on exit") +set(IRMD_KILL_ALL_PROCESSES TRUE CACHE BOOL + "Kill all processes on exit") diff --git a/cmake/config/lib.cmake b/cmake/config/lib.cmake new file mode 100644 index 00000000..287f30dc --- /dev/null +++ b/cmake/config/lib.cmake @@ -0,0 +1,94 @@ +# Library configuration options for Ouroboros +# Options affecting libouroboros-common, libouroboros-dev, libouroboros-irm + +# Flow limits +set(SYS_MAX_FLOWS 10240 CACHE STRING + "Maximum number of total flows for this system") +set(PROG_MAX_FLOWS 4096 CACHE STRING + "Maximum number of flows in an application") +set(PROG_RES_FDS 64 CACHE STRING + "Number of reserved flow descriptors per application") +set(PROG_MAX_FQUEUES 32 CACHE STRING + "Maximum number of flow sets per application") + +# Threading +if(NOT APPLE) + set(PTHREAD_COND_CLOCK "CLOCK_MONOTONIC" CACHE STRING + "Clock to use for condition variable timing") +else() + set(PTHREAD_COND_CLOCK "CLOCK_REALTIME" CACHE INTERNAL + "Clock to use for condition variable timing") +endif() + +# Timeouts +set(SOCKET_TIMEOUT 500 CACHE STRING + "Default timeout for responses from IPCPs (ms)") + +# QoS settings +set(QOS_DISABLE_CRC TRUE CACHE BOOL + "Ignores ber setting on all QoS cubes") + +# Delta-t protocol timers +set(DELTA_T_MPL 60 CACHE STRING + "Maximum packet lifetime (s)") +set(DELTA_T_ACK 10 CACHE STRING + "Maximum time to acknowledge a packet (s)") +set(DELTA_T_RTX 120 CACHE STRING + "Maximum time to retransmit a packet (s)") + +# FRCT configuration +set(FRCT_REORDER_QUEUE_SIZE 256 CACHE STRING + "Size of the reordering queue, must be a power of 2") +set(FRCT_START_WINDOW 64 CACHE STRING + "Start window, must be a power of 2") +set(FRCT_LINUX_RTT_ESTIMATOR TRUE CACHE BOOL + "Use Linux RTT estimator formula instead of the TCP RFC formula") +set(FRCT_RTO_MDEV_MULTIPLIER 2 CACHE STRING + "Multiplier for deviation term in the RTO: RTO = sRTT + (mdev << X)") +set(FRCT_RTO_INC_FACTOR 0 CACHE STRING + "Divisor for RTO increase after timeout: RTO += RTX >> X, 0: Karn/Partridge") +set(FRCT_RTO_MIN 250 CACHE STRING + "Minimum Retransmission Timeout (RTO) for FRCT (us)") +set(FRCT_TICK_TIME 5000 CACHE STRING + "Tick time for FRCT activity (retransmission, acknowledgments) (us)") + +# Retransmission (RXM) configuration +set(RXM_BUFFER_ON_HEAP FALSE CACHE BOOL + "Store packets for retransmission on the heap instead of in packet buffer") +set(RXM_BLOCKING TRUE CACHE BOOL + "Use blocking writes for retransmission") +set(RXM_MIN_RESOLUTION 20 CACHE STRING + "Minimum retransmission delay (ns), as a power to 2") +set(RXM_WHEEL_MULTIPLIER 4 CACHE STRING + "Factor for retransmission wheel levels as a power to 2") +set(RXM_WHEEL_LEVELS 3 CACHE STRING + "Number of levels in the retransmission wheel") +set(RXM_WHEEL_SLOTS_PER_LEVEL 256 CACHE STRING + "Number of slots per level in the retransmission wheel, must be a power of 2") + +# Acknowledgment wheel configuration +set(ACK_WHEEL_SLOTS 256 CACHE STRING + "Number of slots in the acknowledgment wheel, must be a power of 2") +set(ACK_WHEEL_RESOLUTION 18 CACHE STRING + "Minimum acknowledgment delay (ns), as a power to 2") + +# Thread pool manager (TPM) debugging +set(TPM_DEBUG_REPORT_INTERVAL 0 CACHE STRING + "Interval at wich the TPM will report long running threads (s), 0 disables") +set(TPM_DEBUG_ABORT_TIMEOUT 0 CACHE STRING + "TPM abort process after a thread reaches this timeout (s), 0 disables") + +# Encryption +set(KEY_ROTATION_BIT 20 CACHE STRING + "Bit position in packet counter that triggers key rotation (default 20 = every 2^20 packets)") + +# Flow statistics (requires FUSE) +if(HAVE_FUSE) + set(PROC_FLOW_STATS TRUE CACHE BOOL + "Enable flow statistics tracking for application flows") + if(PROC_FLOW_STATS) + message(STATUS "Application flow statistics enabled") + else() + message(STATUS "Application flow statistics disabled") + endif() +endif() diff --git a/cmake/config/ssm.cmake b/cmake/config/ssm.cmake new file mode 100644 index 00000000..c1f34655 --- /dev/null +++ b/cmake/config/ssm.cmake @@ -0,0 +1,131 @@ +# Secure Shared Memory (SSM) pool configuration for Ouroboros +# This file defines the allocation parameters for the secure shared memory +# pool allocator + +# Shared memory pool naming configuration +set(SSM_PREFIX "ouroboros" CACHE STRING + "Prefix for secure shared memory pools") + +# Pool naming (internal) +set(SSM_GSPP_NAME "/${SSM_PREFIX}.gspp" CACHE INTERNAL + "Name for the Global Shared Packet Pool") +set(SSM_PUP_NAME_FMT "/${SSM_PREFIX}.pup.%d" CACHE INTERNAL + "Format string for Per-User Pool names (uid as argument)") + +# Packet buffer configuration +set(SSM_POOL_NAME "/${SHM_PREFIX}.pool" CACHE INTERNAL + "Name for the main POSIX shared memory pool") +set(SSM_POOL_BLOCKS 16384 CACHE STRING + "Number of blocks in SSM packet pool, must be a power of 2") +set(SSM_PK_BUFF_HEADSPACE 256 CACHE STRING + "Bytes of headspace to reserve for future headers") +set(SSM_PK_BUFF_TAILSPACE 32 CACHE STRING + "Bytes of tailspace to reserve for future tails") +set(SSM_RBUFF_SIZE 1024 CACHE STRING + "Number of blocks in rbuff buffer, must be a power of 2") +set(SSM_RBUFF_PREFIX "/${SHM_PREFIX}.rbuff." CACHE INTERNAL + "Prefix for rbuff POSIX shared memory filenames") +set(SSM_FLOW_SET_PREFIX "/${SHM_PREFIX}.set." CACHE INTERNAL + "Prefix for the POSIX shared memory flow set") + +# Number of shards per size class for reducing contention +set(SSM_POOL_SHARDS 4 CACHE STRING + "Number of allocator shards per size class") + +# Global Shared Packet Pool (GSPP) - for privileged processes +# Shared by all processes in 'ouroboros' group (~60 MB total) +set(SSM_GSPP_256_BLOCKS 1024 CACHE STRING + "GSPP: Number of 256B blocks") +set(SSM_GSPP_512_BLOCKS 768 CACHE STRING + "GSPP: Number of 512B blocks") +set(SSM_GSPP_1K_BLOCKS 512 CACHE STRING + "GSPP: Number of 1KB blocks") +set(SSM_GSPP_2K_BLOCKS 384 CACHE STRING + "GSPP: Number of 2KB blocks") +set(SSM_GSPP_4K_BLOCKS 256 CACHE STRING + "GSPP: Number of 4KB blocks") +set(SSM_GSPP_16K_BLOCKS 128 CACHE STRING + "GSPP: Number of 16KB blocks") +set(SSM_GSPP_64K_BLOCKS 64 CACHE STRING + "GSPP: Number of 64KB blocks") +set(SSM_GSPP_256K_BLOCKS 32 CACHE STRING + "GSPP: Number of 256KB blocks") +set(SSM_GSPP_1M_BLOCKS 16 CACHE STRING + "GSPP: Number of 1MB blocks") + +# Per-User Pool (PUP) - for unprivileged applications +# Each unprivileged app gets its own smaller pool (~7.5 MB total) +set(SSM_PUP_256_BLOCKS 128 CACHE STRING + "PUP: Number of 256B blocks") +set(SSM_PUP_512_BLOCKS 96 CACHE STRING + "PUP: Number of 512B blocks") +set(SSM_PUP_1K_BLOCKS 64 CACHE STRING + "PUP: Number of 1KB blocks") +set(SSM_PUP_2K_BLOCKS 48 CACHE STRING + "PUP: Number of 2KB blocks") +set(SSM_PUP_4K_BLOCKS 32 CACHE STRING + "PUP: Number of 4KB blocks") +set(SSM_PUP_16K_BLOCKS 16 CACHE STRING + "PUP: Number of 16KB blocks") +set(SSM_PUP_64K_BLOCKS 8 CACHE STRING + "PUP: Number of 64KB blocks") +set(SSM_PUP_256K_BLOCKS 2 CACHE STRING + "PUP: Number of 256KB blocks") +set(SSM_PUP_1M_BLOCKS 0 CACHE STRING + "PUP: Number of 1MB blocks") + +# SSM pool size calculations +include(utils/HumanReadable) + +math(EXPR SSM_GSPP_TOTAL_SIZE + "(1 << 8) * ${SSM_GSPP_256_BLOCKS} + \ + (1 << 9) * ${SSM_GSPP_512_BLOCKS} + \ + (1 << 10) * ${SSM_GSPP_1K_BLOCKS} + \ + (1 << 11) * ${SSM_GSPP_2K_BLOCKS} + \ + (1 << 12) * ${SSM_GSPP_4K_BLOCKS} + \ + (1 << 14) * ${SSM_GSPP_16K_BLOCKS} + \ + (1 << 16) * ${SSM_GSPP_64K_BLOCKS} + \ + (1 << 18) * ${SSM_GSPP_256K_BLOCKS} + \ + (1 << 20) * ${SSM_GSPP_1M_BLOCKS}") + +set(SSM_GSPP_TOTAL_SIZE ${SSM_GSPP_TOTAL_SIZE} CACHE INTERNAL + "GSPP total size in bytes") + +math(EXPR SSM_PUP_TOTAL_SIZE + "(1 << 8) * ${SSM_PUP_256_BLOCKS} + \ + (1 << 9) * ${SSM_PUP_512_BLOCKS} + \ + (1 << 10) * ${SSM_PUP_1K_BLOCKS} + \ + (1 << 11) * ${SSM_PUP_2K_BLOCKS} + \ + (1 << 12) * ${SSM_PUP_4K_BLOCKS} + \ + (1 << 14) * ${SSM_PUP_16K_BLOCKS} + \ + (1 << 16) * ${SSM_PUP_64K_BLOCKS} + \ + (1 << 18) * ${SSM_PUP_256K_BLOCKS} + \ + (1 << 20) * ${SSM_PUP_1M_BLOCKS}") + +set(SSM_PUP_TOTAL_SIZE ${SSM_PUP_TOTAL_SIZE} CACHE INTERNAL + "PUP total size in bytes") + +set(SSM_POOL_TOTAL_SIZE ${SSM_GSPP_TOTAL_SIZE} CACHE INTERNAL + "Total shared memory pool size in bytes") + +format_bytes_human_readable(${SSM_GSPP_TOTAL_SIZE} SSM_GSPP_SIZE_DISPLAY) +format_bytes_human_readable(${SSM_PUP_TOTAL_SIZE} SSM_PUP_SIZE_DISPLAY) + +message(STATUS "Secure Shared Memory Pool Configuration:") +message(STATUS " Pool prefix: ${SSM_PREFIX}") +message(STATUS " Size classes: " + "256B, 512B, 1KiB, 2KiB, 4KiB, 16KiB, 64KiB, 256KiB, 1MiB") +message(STATUS " Max allocation: 1 MB") +message(STATUS " Shards per class: ${SSM_POOL_SHARDS}") +message(STATUS " GSPP (privileged): ${SSM_GSPP_SIZE_DISPLAY} " + "(${SSM_GSPP_TOTAL_SIZE} bytes)") +message(STATUS " Blocks: ${SSM_GSPP_256_BLOCKS}, ${SSM_GSPP_512_BLOCKS}, " + "${SSM_GSPP_1K_BLOCKS}, ${SSM_GSPP_2K_BLOCKS}, ${SSM_GSPP_4K_BLOCKS}, " + "${SSM_GSPP_16K_BLOCKS}, ${SSM_GSPP_64K_BLOCKS}, ${SSM_GSPP_256K_BLOCKS}, " + "${SSM_GSPP_1M_BLOCKS}") +message(STATUS " PUP (unprivileged): ${SSM_PUP_SIZE_DISPLAY} " + "(${SSM_PUP_TOTAL_SIZE} bytes)") +message(STATUS " Blocks: ${SSM_PUP_256_BLOCKS}, ${SSM_PUP_512_BLOCKS}, " + "${SSM_PUP_1K_BLOCKS}, ${SSM_PUP_2K_BLOCKS}, ${SSM_PUP_4K_BLOCKS}, " + "${SSM_PUP_16K_BLOCKS}, ${SSM_PUP_64K_BLOCKS}, ${SSM_PUP_256K_BLOCKS}, " + "${SSM_PUP_1M_BLOCKS}") diff --git a/cmake/config/tests.cmake b/cmake/config/tests.cmake new file mode 100644 index 00000000..24730989 --- /dev/null +++ b/cmake/config/tests.cmake @@ -0,0 +1,17 @@ +# Test configuration options + +set(DISABLE_TESTS_LOGGING TRUE CACHE BOOL + "Disable Ouroboros log output in tests") +if(DISABLE_TESTS_LOGGING) + message(STATUS "Ouroboros logging in test output disabled") +else() + message(STATUS "Ouroboros logging in test output enabled") +endif() + +set(DISABLE_TESTS_CORE_DUMPS TRUE CACHE BOOL + "Enable core dumps for tests (useful for debugging)") +if(DISABLE_TESTS_CORE_DUMPS) + message(STATUS "Core dumps in tests enabled") +else() + message(STATUS "Core dumps in tests disabled") +endif() diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake new file mode 100644 index 00000000..109fe1d6 --- /dev/null +++ b/cmake/dependencies.cmake @@ -0,0 +1,43 @@ +find_package(PkgConfig QUIET) +include(CheckSymbolExists) + +# System libraries and features +include(dependencies/system/protobufc) +include(dependencies/system/libraries) +include(dependencies/system/explicit_bzero) +include(dependencies/system/robustmutex) +include(dependencies/system/fuse) +include(dependencies/system/sysrandom) + +# Cryptography +include(dependencies/crypt/openssl) +include(dependencies/crypt/libgcrypt) + +# IRMd +include(dependencies/irmd/libtoml) + +# Ethernet IPCP backends +include(dependencies/eth/rawsockets) +include(dependencies/eth/bpf) +include(dependencies/eth/netmap) +if(HAVE_RAW_SOCKETS OR HAVE_BPF OR HAVE_NETMAP) + set(HAVE_ETH TRUE CACHE INTERNAL "Ethernet IPCP support available") +else() + unset(HAVE_ETH CACHE) +endif() + +# UDP IPCP +include(dependencies/udp/ddns) + +# Coverage tools +include(dependencies/coverage/gcov) +include(dependencies/coverage/lcov) + +# Validate that at least one secure random generator is available +if(NOT ((CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") OR APPLE OR + HAVE_SYS_RANDOM OR HAVE_OPENSSL_RNG OR HAVE_LIBGCRYPT)) + message(FATAL_ERROR "No secure random generator found, " + "please install libgcrypt (> 1.7.0) or OpenSSL") +endif() + + diff --git a/cmake/dependencies/coverage/gcov.cmake b/cmake/dependencies/coverage/gcov.cmake new file mode 100644 index 00000000..1b593155 --- /dev/null +++ b/cmake/dependencies/coverage/gcov.cmake @@ -0,0 +1,21 @@ +include(utils/CompilerUtils) + +find_program(GCOV_PATH gcov) + +if(GCOV_PATH) + set(HAVE_GCOV TRUE CACHE INTERNAL "gcov coverage tool available") + set(DISABLE_COVERAGE ON CACHE BOOL "Disable code coverage analysis") + if(DISABLE_COVERAGE) + message(STATUS "gcov found - coverage analysis available (disabled by user)") + else() + message(STATUS "Code coverage analysis enabled") + test_and_set_c_compiler_flag_global(-g) + test_and_set_c_compiler_flag_global(--coverage) + add_link_options(--coverage) + endif() +else() + set(HAVE_GCOV FALSE CACHE INTERNAL "gcov coverage tool available") + message(STATUS "gcov not found - coverage analysis not available") +endif() + +mark_as_advanced(GCOV_PATH) diff --git a/cmake/dependencies/coverage/lcov.cmake b/cmake/dependencies/coverage/lcov.cmake new file mode 100644 index 00000000..65ed316e --- /dev/null +++ b/cmake/dependencies/coverage/lcov.cmake @@ -0,0 +1,17 @@ +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) + +if(LCOV_PATH AND GENHTML_PATH) + set(HAVE_LCOV TRUE CACHE INTERNAL "lcov HTML coverage reports available") + message(STATUS "lcov and genhtml found - HTML coverage reports available") +else() + set(HAVE_LCOV FALSE CACHE INTERNAL "lcov HTML coverage reports available") + if(NOT LCOV_PATH) + message(STATUS "lcov not found - HTML coverage reports not available") + endif() + if(NOT GENHTML_PATH) + message(STATUS "genhtml not found - HTML coverage reports not available") + endif() +endif() + +mark_as_advanced(LCOV_PATH GENHTML_PATH) diff --git a/cmake/dependencies/crypt/libgcrypt.cmake b/cmake/dependencies/crypt/libgcrypt.cmake new file mode 100644 index 00000000..4f8a4cfe --- /dev/null +++ b/cmake/dependencies/crypt/libgcrypt.cmake @@ -0,0 +1,55 @@ +# Try pkg-config first, fall back to find_library +if(PkgConfig_FOUND) + pkg_check_modules(LIBGCRYPT QUIET IMPORTED_TARGET libgcrypt>=1.7.0) + if(LIBGCRYPT_FOUND AND NOT TARGET Gcrypt::Gcrypt) + add_library(Gcrypt::Gcrypt ALIAS PkgConfig::LIBGCRYPT) + endif() +endif() + +if(NOT LIBGCRYPT_FOUND) + find_library(LIBGCRYPT_LIBRARIES gcrypt QUIET) + if(LIBGCRYPT_LIBRARIES) + find_path(LIBGCRYPT_INCLUDE_DIR gcrypt.h + HINTS /usr/include /usr/local/include) + if(LIBGCRYPT_INCLUDE_DIR) + file(STRINGS ${LIBGCRYPT_INCLUDE_DIR}/gcrypt.h GCSTR + REGEX "^#define GCRYPT_VERSION ") + string(REGEX REPLACE "^#define GCRYPT_VERSION \"(.*)\".*$" "\\1" + LIBGCRYPT_VERSION "${GCSTR}") + if(NOT LIBGCRYPT_VERSION VERSION_LESS "1.7.0") + set(LIBGCRYPT_FOUND TRUE) + if(NOT TARGET Gcrypt::Gcrypt) + add_library(Gcrypt::Gcrypt UNKNOWN IMPORTED) + set_target_properties(Gcrypt::Gcrypt PROPERTIES + IMPORTED_LOCATION "${LIBGCRYPT_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBGCRYPT_INCLUDE_DIR}") + endif() + endif() + endif() + endif() +endif() + +if(LIBGCRYPT_FOUND) + set(DISABLE_LIBGCRYPT FALSE CACHE BOOL "Disable libgcrypt support") + if(NOT DISABLE_LIBGCRYPT) + if(LIBGCRYPT_VERSION) + message(STATUS "libgcrypt support enabled (version ${LIBGCRYPT_VERSION})") + else() + message(STATUS "libgcrypt support enabled") + endif() + set(HAVE_LIBGCRYPT TRUE CACHE INTERNAL "libgcrypt cryptography support available") + else() + message(STATUS "libgcrypt support disabled by user") + unset(HAVE_LIBGCRYPT CACHE) + endif() +else() + message(STATUS "Install libgcrypt >= 1.7.0 to enable libgcrypt support") + unset(HAVE_LIBGCRYPT CACHE) +endif() + +if(NOT HAVE_LIBGCRYPT) + set(LIBGCRYPT_LIBRARIES "") + set(LIBGCRYPT_INCLUDE_DIR "") +endif() + +mark_as_advanced(LIBGCRYPT_LIBRARIES LIBGCRYPT_INCLUDE_DIR) diff --git a/cmake/dependencies/crypt/openssl.cmake b/cmake/dependencies/crypt/openssl.cmake new file mode 100644 index 00000000..ed07cc9d --- /dev/null +++ b/cmake/dependencies/crypt/openssl.cmake @@ -0,0 +1,35 @@ +find_package(OpenSSL QUIET) +if(OPENSSL_FOUND) + set(HAVE_OPENSSL_RNG TRUE) + if(OPENSSL_VERSION VERSION_LESS "3.0.0") + message(STATUS "Install version >= 3.0.0 to enable OpenSSL support " + "(found version \"${OPENSSL_VERSION}\")") + else() + set(DISABLE_OPENSSL FALSE CACHE BOOL "Disable OpenSSL support") + if(NOT DISABLE_OPENSSL) + message(STATUS "OpenSSL support enabled, found version ${OPENSSL_VERSION}") + set(HAVE_OPENSSL TRUE CACHE INTERNAL "OpenSSL cryptography support available") + set(DISABLE_PQC FALSE CACHE BOOL "Disable post-quantum cryptography support") + if(OPENSSL_VERSION VERSION_GREATER_EQUAL "3.4.0") + if(NOT DISABLE_PQC) + set(HAVE_OPENSSL_PQC TRUE CACHE INTERNAL "OpenSSL post-quantum cryptography available") + message(STATUS "OpenSSL PQC support enabled") + else() + message(STATUS "OpenSSL PQC support disabled by user") + unset(HAVE_OPENSSL_PQC CACHE) + endif() + else() + message(STATUS "Install OpenSSL >= 3.4.0 for PQC support") + endif() + else() + message(STATUS "OpenSSL support disabled") + unset(HAVE_OPENSSL CACHE) + endif() + endif() +else() + message(STATUS "Install OpenSSL version >= 3.0.0 to enable OpenSSL support") + unset(HAVE_OPENSSL_RNG) + unset(HAVE_OPENSSL CACHE) +endif() + +# Secure memory options are in cmake/config/global.cmake diff --git a/cmake/dependencies/eth/bpf.cmake b/cmake/dependencies/eth/bpf.cmake new file mode 100644 index 00000000..c2f69e79 --- /dev/null +++ b/cmake/dependencies/eth/bpf.cmake @@ -0,0 +1,20 @@ +# Berkeley Packet Filter support (BSD/macOS only) +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_path(BPF_C_INCLUDE_DIR + net/bpf.h + HINTS /usr/include /usr/local/include) + + mark_as_advanced(BPF_C_INCLUDE_DIR) + + if(BPF_C_INCLUDE_DIR) + set(DISABLE_BPF FALSE CACHE BOOL + "Disable Berkeley Packet Filter support for Ethernet IPCPs") + if(NOT DISABLE_BPF) + message(STATUS "Berkeley Packet Filter support for Ethernet IPCPs enabled") + set(HAVE_BPF TRUE) + else() + message(STATUS "Berkeley Packet Filter support for Ethernet IPCPs disabled by user") + unset(HAVE_BPF) + endif() + endif() +endif() diff --git a/cmake/dependencies/eth/netmap.cmake b/cmake/dependencies/eth/netmap.cmake new file mode 100644 index 00000000..94ecd634 --- /dev/null +++ b/cmake/dependencies/eth/netmap.cmake @@ -0,0 +1,18 @@ +# netmap support (optional acceleration) +find_path(NETMAP_C_INCLUDE_DIR + net/netmap_user.h + HINTS /usr/include /usr/local/include) + +mark_as_advanced(NETMAP_C_INCLUDE_DIR) + +if(NOT HAVE_RAW_SOCKETS AND NOT HAVE_BPF AND NETMAP_C_INCLUDE_DIR) + set(DISABLE_NETMAP FALSE CACHE BOOL + "Disable netmap support for ETH IPCPs") + if(NOT DISABLE_NETMAP) + message(STATUS "Netmap support for Ethernet IPCPs enabled") + set(HAVE_NETMAP TRUE) + else() + message(STATUS "Netmap support for Ethernet IPCPs disabled by user") + unset(HAVE_NETMAP) + endif() +endif() diff --git a/cmake/dependencies/eth/rawsockets.cmake b/cmake/dependencies/eth/rawsockets.cmake new file mode 100644 index 00000000..395d9efb --- /dev/null +++ b/cmake/dependencies/eth/rawsockets.cmake @@ -0,0 +1,12 @@ +# Raw sockets support (Linux only) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(DISABLE_RAW_SOCKETS FALSE CACHE BOOL + "Disable raw socket support for Ethernet IPCPs") + if(NOT DISABLE_RAW_SOCKETS) + message(STATUS "Raw socket support for Ethernet IPCPs enabled") + set(HAVE_RAW_SOCKETS TRUE) + else() + message(STATUS "Raw socket support for Ethernet IPCPs disabled by user") + unset(HAVE_RAW_SOCKETS) + endif() +endif() diff --git a/cmake/dependencies/irmd/libtoml.cmake b/cmake/dependencies/irmd/libtoml.cmake new file mode 100644 index 00000000..dcbc17e4 --- /dev/null +++ b/cmake/dependencies/irmd/libtoml.cmake @@ -0,0 +1,29 @@ +find_library(LIBTOML_LIBRARY toml QUIET) +if(LIBTOML_LIBRARY) + find_path(LIBTOML_INCLUDE_DIR toml.h) + set(DISABLE_CONFIGFILE FALSE CACHE BOOL + "Disable configuration file support") + if(NOT DISABLE_CONFIGFILE) + set(OUROBOROS_CONFIG_FILE irmd.conf CACHE STRING + "Name of the IRMd configuration file") + set(HAVE_TOML TRUE CACHE INTERNAL "TOML configuration file support available") + message(STATUS "Configuration file support enabled") + message(STATUS "Configuration directory: ${OUROBOROS_CONFIG_DIR}") + # Create imported target for consistency with other dependencies + if(NOT TARGET toml::toml) + add_library(toml::toml UNKNOWN IMPORTED) + set_target_properties(toml::toml PROPERTIES + IMPORTED_LOCATION "${LIBTOML_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBTOML_INCLUDE_DIR}") + endif() + else() + message(STATUS "Configuration file support disabled by user") + unset(OUROBOROS_CONFIG_FILE CACHE) + unset(HAVE_TOML CACHE) + endif() + mark_as_advanced(LIBTOML_LIBRARY LIBTOML_INCLUDE_DIR) +else() + message(STATUS "Install tomlc99 for config file support") + message(STATUS " https://github.com/cktan/tomlc99") + unset(HAVE_TOML CACHE) +endif() diff --git a/cmake/dependencies/system/explicit_bzero.cmake b/cmake/dependencies/system/explicit_bzero.cmake new file mode 100644 index 00000000..89ab3abc --- /dev/null +++ b/cmake/dependencies/system/explicit_bzero.cmake @@ -0,0 +1,4 @@ +# Check for explicit_bzero in string.h +# glibc requires _DEFAULT_SOURCE to expose it; harmless on other platforms +list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_DEFAULT_SOURCE) +check_symbol_exists(explicit_bzero "string.h" HAVE_EXPLICIT_BZERO) diff --git a/cmake/dependencies/system/fuse.cmake b/cmake/dependencies/system/fuse.cmake new file mode 100644 index 00000000..7de12b31 --- /dev/null +++ b/cmake/dependencies/system/fuse.cmake @@ -0,0 +1,44 @@ +# Try pkg-config first, fall back to find_library +if(PkgConfig_FOUND) + pkg_check_modules(FUSE QUIET IMPORTED_TARGET fuse>=2.6) + if(FUSE_FOUND AND NOT TARGET Fuse::Fuse) + add_library(Fuse::Fuse ALIAS PkgConfig::FUSE) + endif() +endif() + +if(NOT FUSE_FOUND) + find_library(FUSE_LIBRARIES fuse QUIET) + if(FUSE_LIBRARIES) + set(FUSE_FOUND TRUE) + if(NOT TARGET Fuse::Fuse) + add_library(Fuse::Fuse UNKNOWN IMPORTED) + set_target_properties(Fuse::Fuse PROPERTIES + IMPORTED_LOCATION "${FUSE_LIBRARIES}") + endif() + endif() +endif() + +if(FUSE_FOUND) + set(DISABLE_FUSE FALSE CACHE BOOL "Disable FUSE support") + if(NOT DISABLE_FUSE) + if(FUSE_VERSION) + message(STATUS "FUSE support enabled (version ${FUSE_VERSION})") + else() + message(STATUS "FUSE support enabled") + endif() + # FUSE_PREFIX is set in cmake/config/global.cmake + set(HAVE_FUSE TRUE CACHE INTERNAL "FUSE filesystem support available") + else() + message(STATUS "FUSE support disabled by user") + unset(HAVE_FUSE CACHE) + endif() +else() + message(STATUS "Install FUSE version >= 2.6 to enable RIB access") + unset(HAVE_FUSE CACHE) +endif() + +if(NOT HAVE_FUSE) + set(FUSE_LIBRARIES "") +endif() + +mark_as_advanced(FUSE_LIBRARIES) diff --git a/cmake/dependencies/system/libraries.cmake b/cmake/dependencies/system/libraries.cmake new file mode 100644 index 00000000..55c22d6a --- /dev/null +++ b/cmake/dependencies/system/libraries.cmake @@ -0,0 +1,17 @@ +if(NOT APPLE) + find_library(LIBRT_LIBRARIES rt) + mark_as_advanced(LIBRT_LIBRARIES) + if(NOT LIBRT_LIBRARIES) + message(FATAL_ERROR "Could not find librt") + endif() +else() + set(LIBRT_LIBRARIES "") +endif() + +find_package(Threads REQUIRED) + +find_library(LIBM_LIBRARIES m) +mark_as_advanced(LIBM_LIBRARIES) +if(NOT LIBM_LIBRARIES) + message(FATAL_ERROR "Could not find libm") +endif() diff --git a/cmake/dependencies/system/protobufc.cmake b/cmake/dependencies/system/protobufc.cmake new file mode 100644 index 00000000..b7e0062a --- /dev/null +++ b/cmake/dependencies/system/protobufc.cmake @@ -0,0 +1,13 @@ +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/utils") + +find_package(ProtobufC QUIET) +if(NOT (PROTOBUF_C_INCLUDE_DIRS AND PROTOBUF_C_LIBRARY + AND PROTOBUF_PROTOC_C_EXECUTABLE)) + message(FATAL_ERROR "Protobuf C compiler required but not found. " + "Please install Google Protocol Buffers.") +else() + message(STATUS "Found protobuf C compiler in ${PROTOBUF_PROTOC_C_EXECUTABLE}") +endif() + +# Note: Include dirs are added per-target via target_include_directories +# using ${PROTOBUF_C_INCLUDE_DIRS} diff --git a/cmake/dependencies/system/robustmutex.cmake b/cmake/dependencies/system/robustmutex.cmake new file mode 100644 index 00000000..89b7325b --- /dev/null +++ b/cmake/dependencies/system/robustmutex.cmake @@ -0,0 +1,18 @@ +list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_POSIX_C_SOURCE=200809L) +list(APPEND CMAKE_REQUIRED_DEFINITIONS -D__XSI_VISIBLE=500) +list(APPEND CMAKE_REQUIRED_LIBRARIES pthread) +check_symbol_exists(pthread_mutexattr_setrobust pthread.h HAVE_ROBUST_MUTEX) + +if(HAVE_ROBUST_MUTEX) + set(DISABLE_ROBUST_MUTEXES FALSE CACHE BOOL "Disable robust mutex support") + if(NOT DISABLE_ROBUST_MUTEXES) + message(STATUS "Robust mutex support enabled") + set(HAVE_ROBUST_MUTEX TRUE) + else() + message(STATUS "Robust mutex support disabled by user") + unset(HAVE_ROBUST_MUTEX) + endif() +else() + message(STATUS "Robust mutex support not available") + unset(HAVE_ROBUST_MUTEX) +endif() diff --git a/cmake/dependencies/system/sysrandom.cmake b/cmake/dependencies/system/sysrandom.cmake new file mode 100644 index 00000000..dc8443a1 --- /dev/null +++ b/cmake/dependencies/system/sysrandom.cmake @@ -0,0 +1,14 @@ +if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(SYS_RND_HDR "") +else() + find_path(SYS_RND_HDR sys/random.h PATH /usr/include/ /usr/local/include/) + if(SYS_RND_HDR) + message(STATUS "Found sys/random.h in ${SYS_RND_HDR}") + set(HAVE_SYS_RANDOM TRUE) + else() + set(SYS_RND_HDR "") + unset(HAVE_SYS_RANDOM) + endif() +endif() + +mark_as_advanced(SYS_RND_HDR) diff --git a/cmake/dependencies/udp/ddns.cmake b/cmake/dependencies/udp/ddns.cmake new file mode 100644 index 00000000..e8208e47 --- /dev/null +++ b/cmake/dependencies/udp/ddns.cmake @@ -0,0 +1,31 @@ +# DDNS (Dynamic DNS) support detection +# Requires nsupdate and nslookup tools + +find_program(NSUPDATE_EXECUTABLE + NAMES nsupdate + DOC "The nsupdate tool that enables DDNS") + +find_program(NSLOOKUP_EXECUTABLE + NAMES nslookup + DOC "The nslookup tool that resolves DNS names") + +mark_as_advanced(NSLOOKUP_EXECUTABLE NSUPDATE_EXECUTABLE) + +if(NSLOOKUP_EXECUTABLE AND NSUPDATE_EXECUTABLE) + set(DISABLE_DDNS FALSE CACHE BOOL "Disable DDNS support") + if(NOT DISABLE_DDNS) + message(STATUS "DDNS support enabled") + set(HAVE_DDNS TRUE CACHE INTERNAL "Dynamic DNS support available") + else() + message(STATUS "DDNS support disabled by user") + unset(HAVE_DDNS CACHE) + endif() +else() + if(NSLOOKUP_EXECUTABLE) + message(STATUS "Install nsupdate to enable DDNS support") + elseif(NSUPDATE_EXECUTABLE) + message(STATUS "Install nslookup to enable DDNS support") + else() + message(STATUS "Install nslookup and nsupdate to enable DDNS support") + endif() +endif() diff --git a/cmake/doc.cmake b/cmake/doc.cmake new file mode 100644 index 00000000..f75cde5b --- /dev/null +++ b/cmake/doc.cmake @@ -0,0 +1,54 @@ +set(DOC_SOURCE_DIR "${CMAKE_SOURCE_DIR}/doc") +set(DOC_BINARY_DIR "${CMAKE_BINARY_DIR}/doc") + +set(MAN_NAMES + # Add man page sources here + flow_accept.3 + flow_alloc.3 + flow_dealloc.3 + flow_read.3 + flow_write.3 + fccntl.3 + fqueue.3 + fqueue_create.3 + fqueue_destroy.3 + fqueue_next.3 + fevent.3 + fset.3 + fset_create.3 + fset_destroy.3 + fset_zero.3 + fset_add.3 + fset_del.3 + fset_has.3 + ouroboros-glossary.7 + ouroboros-tutorial.7 + ouroboros.8 + irmd.8 + irm.8 + ) + +find_program(GZIP_EXECUTABLE + NAMES gzip + DOC "Will gzip the man pages") +mark_as_advanced(GZIP_EXECUTABLE) + +if(GZIP_EXECUTABLE) + # Create the doc output directory + file(MAKE_DIRECTORY ${DOC_BINARY_DIR}) + + foreach (m ${MAN_NAMES}) + set(md ${DOC_BINARY_DIR}/${m}.gz) + + add_custom_command( + OUTPUT ${md} + COMMAND ${GZIP_EXECUTABLE} + ARGS -c ${DOC_SOURCE_DIR}/man/${m} > ${md} + COMMENT "Compressing manpage ${m}" + VERBATIM) + + set(MAN_FILES ${MAN_FILES} ${md}) + endforeach () + + add_custom_target(man ALL DEPENDS ${MAN_FILES}) +endif() diff --git a/cmake/include.cmake b/cmake/include.cmake new file mode 100644 index 00000000..ace51d0f --- /dev/null +++ b/cmake/include.cmake @@ -0,0 +1,24 @@ +set(HEADERS_SOURCE_DIR "${CMAKE_SOURCE_DIR}/include/ouroboros") + +# SOCK_BUF_SIZE is in cmake/config/global.cmake + +configure_file("${CMAKE_SOURCE_DIR}/include/ouroboros/version.h.in" + "${CMAKE_BINARY_DIR}/include/ouroboros/version.h" @ONLY) + +configure_file("${CMAKE_SOURCE_DIR}/include/ouroboros/sockets.h.in" + "${CMAKE_BINARY_DIR}/include/ouroboros/sockets.h" @ONLY) + +set(PUBLIC_HEADER_FILES + ${HEADERS_SOURCE_DIR}/cep.h + ${HEADERS_SOURCE_DIR}/cdefs.h + ${HEADERS_SOURCE_DIR}/dev.h + ${HEADERS_SOURCE_DIR}/errno.h + ${HEADERS_SOURCE_DIR}/fccntl.h + ${HEADERS_SOURCE_DIR}/fqueue.h + ${HEADERS_SOURCE_DIR}/ipcp.h + ${HEADERS_SOURCE_DIR}/irm.h + ${HEADERS_SOURCE_DIR}/name.h + ${HEADERS_SOURCE_DIR}/proto.h + ${HEADERS_SOURCE_DIR}/qos.h + ${CMAKE_BINARY_DIR}/include/ouroboros/version.h +) diff --git a/cmake/install.cmake b/cmake/install.cmake new file mode 100644 index 00000000..79714df2 --- /dev/null +++ b/cmake/install.cmake @@ -0,0 +1,101 @@ +# Installation configuration + +include(CMakePackageConfigHelpers) + +# Public headers +install(FILES ${PUBLIC_HEADER_FILES} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ouroboros) + +# Man pages +if(GZIP_EXECUTABLE) + foreach(_man ${MAN_FILES}) + string(REGEX REPLACE "^.+[.]([1-9]).gz" "\\1" _mansect ${_man}) + install(FILES ${_man} DESTINATION "${CMAKE_INSTALL_MANDIR}/man${_mansect}") + endforeach() +endif() + +# pkg-config files +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ouroboros-dev.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ouroboros-irm.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + +set(OUROBOROS_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/Ouroboros") + +install(EXPORT OuroborosTargets + FILE OuroborosTargets.cmake + NAMESPACE Ouroboros:: + DESTINATION ${OUROBOROS_CMAKE_DIR}) + +configure_package_config_file( + "${CMAKE_SOURCE_DIR}/cmake/OuroborosConfig.cmake.in" + "${CMAKE_BINARY_DIR}/OuroborosConfig.cmake" + INSTALL_DESTINATION ${OUROBOROS_CMAKE_DIR}) + +write_basic_package_version_file( + "${CMAKE_BINARY_DIR}/OuroborosConfigVersion.cmake" + VERSION ${PACKAGE_VERSION} + COMPATIBILITY SameMajorVersion) + +install(FILES + "${CMAKE_BINARY_DIR}/OuroborosConfig.cmake" + "${CMAKE_BINARY_DIR}/OuroborosConfigVersion.cmake" + DESTINATION ${OUROBOROS_CMAKE_DIR}) + +# Systemd service file installation +set(SYSTEMD_INSTALL_FILES "DETECT" CACHE STRING + "Install systemd .service files (NO (never), DETECT (use pkg-config - default),\ + FORCE (always - see SYSTEMD_UNITDIR_OVERRIDE))") +set(SYSTEMD_UNITDIR_OVERRIDE "" CACHE PATH + "Path to install systemd files. When SYSTEMD_INSTALL_FILES == DETECT, this\ + can be empty to automatically determine the path. Cannot be empty when FORCE.") + +if(SYSTEMD_INSTALL_FILES STREQUAL "DETECT" OR SYSTEMD_INSTALL_FILES STREQUAL "FORCE") + if(SYSTEMD_INSTALL_FILES STREQUAL "DETECT") + if(PkgConfig_FOUND) + pkg_check_modules(SYSTEMD "systemd") + endif() + if(SYSTEMD_FOUND) + if(SYSTEMD_UNITDIR_OVERRIDE STREQUAL "") + execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} + --variable=systemdsystemunitdir systemd + OUTPUT_VARIABLE SYSTEMD_UNITDIR_INTERNAL) + string(REGEX REPLACE "[ \t\n]+" "" SYSTEMD_UNITDIR_INTERNAL + "${SYSTEMD_UNITDIR_INTERNAL}" + ) + else() + set(SYSTEMD_UNITDIR_INTERNAL "${SYSTEMD_UNITDIR_OVERRIDE}") + endif() + else() + set(SYSTEMD_UNITDIR_INTERNAL "") + endif() + elseif(SYSTEMD_INSTALL_FILES STREQUAL "FORCE") + if(SYSTEMD_UNITDIR_OVERRIDE STREQUAL "") + message(FATAL_ERROR "Systemd installation required by user, but no path\ + provided with SYSTEMD_UNITDIR_OVERRIDE.") + else() + set(SYSTEMD_UNITDIR_INTERNAL "${SYSTEMD_UNITDIR_OVERRIDE}") + endif() + endif() + if(NOT SYSTEMD_UNITDIR_INTERNAL STREQUAL "") + message(STATUS "Systemd service installation enabled to: ${SYSTEMD_UNITDIR_INTERNAL}") + if(LIBTOML_LIBRARIES AND NOT DISABLE_CONFIGFILE) + set (CONFIGURE_STRING "--config ${OUROBOROS_CONFIG_DIR}/${OUROBOROS_CONFIG_FILE}") + else() + set (CONFIGURE_STRING "") + endif() + configure_file("${CMAKE_SOURCE_DIR}/ouroboros.service.in" + "${CMAKE_BINARY_DIR}/ouroboros.service" @ONLY) + install(FILES "${CMAKE_BINARY_DIR}/ouroboros.service" + DESTINATION "${SYSTEMD_UNITDIR_INTERNAL}") + endif() +else() + message(STATUS "Systemd service installation disabled by user") +endif() + +configure_file("${CMAKE_SOURCE_DIR}/cmake/utils/CMakeUninstall.cmake.in" + "${CMAKE_BINARY_DIR}/cmake/cmakeuninstall.cmake" @ONLY) + +add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/cmake/cmakeuninstall.cmake) diff --git a/cmake/package.cmake b/cmake/package.cmake new file mode 100644 index 00000000..71105ee8 --- /dev/null +++ b/cmake/package.cmake @@ -0,0 +1,29 @@ +set(PACKAGE_NAME "${CMAKE_PROJECT_NAME}") +set(PACKAGE_DESCRIPTION "The Ouroboros prototype") +set(PACKAGE_URL "http://ouroboros.rocks") +set(PACKAGE_BUGREPORT "http://ouroboros.rocks/bugzilla/") + +message(STATUS "Package name is: ${PACKAGE_NAME}") +message(STATUS "Package description is: ${PACKAGE_DESCRIPTION}") +message(STATUS "Package version is: ${PACKAGE_VERSION}") +message(STATUS "Package URL is: ${PACKAGE_URL}") +message(STATUS "Package bug-report address: ${PACKAGE_BUGREPORT}") +message(STATUS "Package install prefix: ${CMAKE_INSTALL_PREFIX}") + +configure_file("${CMAKE_SOURCE_DIR}/ouroboros-dev.pc.in" + "${CMAKE_BINARY_DIR}/ouroboros-dev.pc" @ONLY) + +configure_file("${CMAKE_SOURCE_DIR}/ouroboros-irm.pc.in" + "${CMAKE_BINARY_DIR}/ouroboros-irm.pc" @ONLY) + +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PACKAGE_DESCRIPTION}") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README") +set(CPACK_PACKAGE_VERSION_MAJOR "${PACKAGE_VERSION_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${PACKAGE_VERSION_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${PACKAGE_VERSION_PATCH}") +set(CPACK_PACKAGE_INSTALL_DIRECTORY + "CMake ${CMake_VERSION_MAJOR}.${CMake_VERSION_MINOR}") +set(CPACK_GENERATOR "TGZ") +set(CPACK_SOURCE_GENERATOR "TGZ") + +include(CPack) diff --git a/cmake/tests.cmake b/cmake/tests.cmake new file mode 100644 index 00000000..edc2987c --- /dev/null +++ b/cmake/tests.cmake @@ -0,0 +1,30 @@ +include(CTest) # Sets BUILD_TESTING by default to on. +include(utils/TestUtils) + +# Test configuration options +include(config/tests) +include(utils/DisableTestLogging) + +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING) + set(BUILD_TESTS ON) +else() + set(BUILD_TESTS OFF) +endif() + +add_custom_target(build_tests) + +if(BUILD_TESTS) + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}) + add_dependencies(check build_tests) +endif() + +# Test subdirectories are added from their parent CMakeLists.txt files +# via add_subdirectory(tests) - keeping tests with their source code + +# Coverage target setup (called after all targets are defined) +function(setup_coverage_target) + if(BUILD_TESTS) + include(utils/GenCoverage) + create_coverage_target() + endif() +endfunction() diff --git a/cmake/utils/AddCompileFlags.cmake b/cmake/utils/AddCompileFlags.cmake new file mode 100644 index 00000000..b81ccc67 --- /dev/null +++ b/cmake/utils/AddCompileFlags.cmake @@ -0,0 +1,6 @@ +# - add_compile_flags(<target> <flags>...) +# Add compile flags to a target using modern CMake + +macro(add_compile_flags _target) + target_compile_options(${_target} PRIVATE ${ARGN}) +endmacro() diff --git a/cmake/utils/CMakeUninstall.cmake.in b/cmake/utils/CMakeUninstall.cmake.in new file mode 100644 index 00000000..b8bba5fd --- /dev/null +++ b/cmake/utils/CMakeUninstall.cmake.in @@ -0,0 +1,21 @@ +if (NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") +endif() + +file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach (file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if (IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + execute_process( + COMMAND @CMAKE_COMMAND@ -E remove "$ENV{DESTDIR}${file}" + RESULT_VARIABLE rm_out + ERROR_VARIABLE rm_retval + ) + if (NOT "${rm_retval}" STREQUAL "" AND NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif() + else() + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif() +endforeach () diff --git a/cmake/CompilerUtils.cmake b/cmake/utils/CompilerUtils.cmake index 7c8b022f..35548f4f 100644 --- a/cmake/CompilerUtils.cmake +++ b/cmake/utils/CompilerUtils.cmake @@ -1,13 +1,13 @@ include(CheckCCompilerFlag) function(test_and_set_c_compiler_flag_global _flag) - string(REGEX REPLACE "-" "_" _sflag ${_flag}) - set(CMAKE_REQUIRED_FLAGS ${_flag}) + string(REGEX REPLACE "=" "_" _sflag ${_sflag}) + # Use -Werror during test so clang rejects unknown flags + set(CMAKE_REQUIRED_FLAGS "-Werror ${_flag}") check_c_compiler_flag(${_flag} COMPILER_SUPPORTS_FLAG_${_sflag}) if(COMPILER_SUPPORTS_FLAG_${_sflag}) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_flag}" PARENT_SCOPE) + add_compile_options(${_flag}) endif() - -endfunction(test_and_set_c_compiler_flag_global) +endfunction() diff --git a/cmake/utils/DebugTargets.cmake b/cmake/utils/DebugTargets.cmake new file mode 100644 index 00000000..78899213 --- /dev/null +++ b/cmake/utils/DebugTargets.cmake @@ -0,0 +1,16 @@ +# Utility functions for Ouroboros target configuration +set(OUROBOROS_DEBUG_CONFIGS + Debug + DebugASan + DebugTSan + DebugLSan + DebugUSan + DebugAnalyzer +) + +# Add CONFIG_OUROBOROS_DEBUG definition for debug build types +function(ouroboros_target_debug_definitions target) + list(JOIN OUROBOROS_DEBUG_CONFIGS "," _configs) + target_compile_definitions(${target} PRIVATE + "$<$<CONFIG:${_configs}>:CONFIG_OUROBOROS_DEBUG>") +endfunction() diff --git a/cmake/utils/DisableTestLogging.cmake b/cmake/utils/DisableTestLogging.cmake new file mode 100644 index 00000000..40c16668 --- /dev/null +++ b/cmake/utils/DisableTestLogging.cmake @@ -0,0 +1,11 @@ +# Macro to apply test logging settings to a target +# Configuration options are in cmake/config/tests.cmake + +macro(disable_test_logging_for_target target) + if(DISABLE_TESTS_LOGGING) + target_compile_definitions(${target} PRIVATE OUROBOROS_DISABLE_LOGGING) + endif() + if(DISABLE_TESTS_CORE_DUMPS) + target_compile_definitions(${target} PRIVATE DISABLE_TESTS_CORE_DUMPS) + endif() +endmacro() diff --git a/cmake/FindProtobufC.cmake b/cmake/utils/FindProtobufC.cmake index ff532a46..4632dfcb 100644 --- a/cmake/FindProtobufC.cmake +++ b/cmake/utils/FindProtobufC.cmake @@ -1,22 +1,22 @@ -function(PROTOBUF_GENERATE_C SRCS HDRS) - if (NOT ARGN) - message(SEND_ERROR "Error: PROTOBUF_GENERATE_C() called without any proto files") +function(protobuf_generate_c SRCS HDRS) + if(NOT ARGN) + message(SEND_ERROR "Error: protobuf_generate_c() called without any proto files") return() - endif () + endif() - if (PROTOBUF_GENERATE_C_APPEND_PATH) + if(PROTOBUF_GENERATE_C_APPEND_PATH) # Create an include path for each file specified foreach (FIL ${ARGN}) get_filename_component(ABS_FIL ${FIL} ABSOLUTE) get_filename_component(ABS_PATH ${ABS_FIL} PATH) list(FIND _protobuf_include_path ${ABS_PATH} _contains_already) - if (${_contains_already} EQUAL -1) + if(${_contains_already} EQUAL -1) list(APPEND _protobuf_include_path -I ${ABS_PATH}) - endif () + endif() endforeach () - else () + else() set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR}) - endif () + endif() set(${SRCS}) set(${HDRS}) @@ -42,33 +42,37 @@ function(PROTOBUF_GENERATE_C SRCS HDRS) set(${HDRS} ${${HDRS}} PARENT_SCOPE) endfunction() -# By default have PROTOBUF_GENERATE_C macro pass -I to protoc +# By default have protobuf_generate_c function pass -I to protoc # for each directory where a proto file is referenced. -if (NOT DEFINED PROTOBUF_GENERATE_C_APPEND_PATH) +if(NOT DEFINED PROTOBUF_GENERATE_C_APPEND_PATH) set(PROTOBUF_GENERATE_C_APPEND_PATH TRUE) -endif () +endif() -# Find library find_library(PROTOBUF_C_LIBRARY NAMES libprotobuf-c.so libprotobuf-c libprotobuf-c.dylib ) mark_as_advanced(PROTOBUF_C_LIBRARY) -# Find the include directory find_path(PROTOBUF_C_INCLUDE_DIR google/protobuf-c/protobuf-c.h ) mark_as_advanced(PROTOBUF_C_INCLUDE_DIR) -# Find the protoc-c Executable find_program(PROTOBUF_PROTOC_C_EXECUTABLE - NAMES protoc-c + NAMES protoc protoc-c DOC "The Google Protocol Buffers C Compiler" ) mark_as_advanced(PROTOBUF_PROTOC_C_EXECUTABLE) -find_package(PackageHandleStandardArgs) +include(FindPackageHandleStandardArgs) find_package_handle_standard_args(ProtobufC DEFAULT_MSG PROTOBUF_C_LIBRARY PROTOBUF_C_INCLUDE_DIR PROTOBUF_PROTOC_C_EXECUTABLE) set(PROTOBUF_C_INCLUDE_DIRS ${PROTOBUF_C_INCLUDE_DIR}) + +if(ProtobufC_FOUND AND NOT TARGET ProtobufC::ProtobufC) + add_library(ProtobufC::ProtobufC UNKNOWN IMPORTED) + set_target_properties(ProtobufC::ProtobufC PROPERTIES + IMPORTED_LOCATION "${PROTOBUF_C_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${PROTOBUF_C_INCLUDE_DIR}") +endif() diff --git a/cmake/utils/GenCoverage.cmake b/cmake/utils/GenCoverage.cmake new file mode 100644 index 00000000..0ae11ee8 --- /dev/null +++ b/cmake/utils/GenCoverage.cmake @@ -0,0 +1,94 @@ +# Creates the coverage target for test coverage analysis +# Requires HAVE_GCOV to be set (from dependencies/gcov.cmake) +# Uses HAVE_LCOV for optional HTML generation (from dependencies/lcov.cmake) + +# Filter patterns for lcov --remove +set(LCOV_FILTERS '*_test.c' '*.h') + +# Ignore inconsistent coverage: legitimate gaps in error paths and +# edge cases that are difficult to exercise in unit tests. +function(get_html_coverage_commands OUTPUT_VAR) + if(HAVE_LCOV) + set(COMMANDS + COMMAND ${LCOV_PATH} + --capture --directory . + --output-file coverage.info + > /dev/null 2>&1 + COMMAND ${LCOV_PATH} + --remove coverage.info ${LCOV_FILTERS} + --output-file coverage_filtered.info + --ignore-errors inconsistent + > /dev/null 2>&1 + COMMAND ${GENHTML_PATH} + coverage_filtered.info + --output-directory coverage_html + > /dev/null 2>&1 + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "HTML report: ${CMAKE_BINARY_DIR}/coverage_html/index.html" + COMMAND ${CMAKE_COMMAND} -E echo "" + ) + set(${OUTPUT_VAR} "${COMMANDS}" PARENT_SCOPE) + else() + set(${OUTPUT_VAR} "" PARENT_SCOPE) + endif() +endfunction() + +function(create_informational_target) + # MESSAGE lines are passed as ARGV, last arg is COMMENT + list(LENGTH ARGV NUM_ARGS) + math(EXPR COMMENT_IDX "${NUM_ARGS} - 1") + list(GET ARGV ${COMMENT_IDX} COMMENT_TEXT) + + # Build command list + set(COMMANDS + COMMAND ${CMAKE_COMMAND} -E echo "" + ) + foreach(i RANGE 0 ${COMMENT_IDX}) + if(NOT i EQUAL ${COMMENT_IDX}) + list(GET ARGV ${i} LINE) + list(APPEND COMMANDS COMMAND ${CMAKE_COMMAND} -E echo "${LINE}") + endif() + endforeach() + list(APPEND COMMANDS COMMAND ${CMAKE_COMMAND} -E echo "") + + add_custom_target(coverage + ${COMMANDS} + COMMENT "${COMMENT_TEXT}" + ) +endfunction() + +function(create_coverage_target) + if(HAVE_GCOV AND NOT DISABLE_COVERAGE) + get_html_coverage_commands(HTML_COVERAGE_COMMANDS) + + add_custom_target(coverage + COMMAND ${CMAKE_CTEST_COMMAND} -D ExperimentalStart > /dev/null 2>&1 + COMMAND ${CMAKE_CTEST_COMMAND} -D ExperimentalConfigure > /dev/null 2>&1 + COMMAND ${CMAKE_CTEST_COMMAND} -D ExperimentalBuild > /dev/null 2>&1 + COMMAND ${CMAKE_CTEST_COMMAND} -D ExperimentalTest > /dev/null 2>&1 + COMMAND ${CMAKE_CTEST_COMMAND} -D ExperimentalCoverage > /dev/null 2>&1 + COMMAND ${CMAKE_COMMAND} + -DPROJECT_SOURCE_DIR=${CMAKE_SOURCE_DIR} + -DPROJECT_BINARY_DIR=${CMAKE_BINARY_DIR} + -P ${CMAKE_SOURCE_DIR}/cmake/utils/PrintCoverage.cmake + ${HTML_COVERAGE_COMMANDS} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + DEPENDS build_tests + COMMENT "Running tests with coverage analysis using CTest" + ) + elseif(HAVE_GCOV) + create_informational_target( + "Coverage is currently disabled" + "To enable coverage analysis, reconfigure with" + " cmake -DDISABLE_COVERAGE=OFF .." + "Coverage disabled" + ) + message(STATUS "Coverage disabled. Use 'make coverage' for instructions to enable.") + else() + create_informational_target( + "Coverage analysis is not available" + "Install gcov to enable coverage support" + "Coverage not available" + ) + endif() +endfunction() diff --git a/cmake/utils/HumanReadable.cmake b/cmake/utils/HumanReadable.cmake new file mode 100644 index 00000000..8bc1722f --- /dev/null +++ b/cmake/utils/HumanReadable.cmake @@ -0,0 +1,17 @@ +# Human-readable size conversion utilities + +# Convert bytes to human-readable format (GB, MB, KB) +# Usage: format_bytes_human_readable(<bytes> <output_var>) +function(format_bytes_human_readable bytes output_var) + math(EXPR size_gb "${bytes} / 1073741824") + math(EXPR size_mb "${bytes} / 1048576") + math(EXPR size_kb "${bytes} / 1024") + + if(size_gb GREATER 0) + set(${output_var} "${size_gb} GB" PARENT_SCOPE) + elseif(size_mb GREATER 0) + set(${output_var} "${size_mb} MB" PARENT_SCOPE) + else() + set(${output_var} "${size_kb} KB" PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/utils/ParseCoverage.cmake b/cmake/utils/ParseCoverage.cmake new file mode 100644 index 00000000..0bbed0de --- /dev/null +++ b/cmake/utils/ParseCoverage.cmake @@ -0,0 +1,120 @@ +# Parse CTest Coverage.xml file and extract structured coverage data. +# +# Sets in PARENT_SCOPE: +# LOC_TESTED, LOC_UNTESTED - Overall metrics +# FILE_COVERAGE_LIST - "path|name|tested|untested|total|percent" entries +# COVERED_FILES - Absolute paths of covered files + +# Regex building blocks +set(R_NUMBER "[0-9]+") +set(R_NON_TAG "[^<]*") +set(R_NON_BRACKET "[^>]*") +set(R_NON_QUOTE "[^\"]+") + +# XML element patterns +set(R_LOC_TESTED_ELEM "<LOCTested>(${R_NUMBER})</LOCTested>") +set(R_LOC_TESTED_SKIP "<LOCTested>${R_NUMBER}</LOCTested>") +set(R_LOC_UNTESTED_ELEM "<LOCUntested>(${R_NUMBER})</LOCUntested>") +set(R_LOC_UNTESTED_CAP "<LOCUnTested>(${R_NUMBER})</LOCUnTested>") + +# Regex patterns for XML parsing +set(REGEX_LOC_TESTED "</File>${R_NON_TAG}${R_LOC_TESTED_ELEM}") +set(REGEX_LOC_UNTESTED "</File>${R_NON_TAG}${R_LOC_TESTED_SKIP}${R_NON_TAG}${R_LOC_UNTESTED_ELEM}") +set(REGEX_FILE_ENTRY "<File${R_NON_BRACKET}Name=\"(${R_NON_QUOTE})\"${R_NON_BRACKET}>${R_NON_TAG}${R_LOC_TESTED_ELEM}${R_NON_TAG}${R_LOC_UNTESTED_CAP}") + +# Regex patterns for filtering +set(REGEX_TEST_FILE "test") +set(REGEX_TEST_DIR "/tests/") +set(REGEX_PROTOBUF_C "\\.pb-c\\.c$") +set(REGEX_PROTOBUF_ALT "\\.pb\\.c$") +set(REGEX_DOTSLASH_SRC "^\\.\\/src\\/") + +function(should_skip_file FILE_PATH OUTPUT_VAR) + if(FILE_PATH MATCHES "${REGEX_TEST_FILE}" OR FILE_PATH MATCHES "${REGEX_TEST_DIR}" OR + FILE_PATH MATCHES "${REGEX_PROTOBUF_C}" OR FILE_PATH MATCHES "${REGEX_PROTOBUF_ALT}") + set(${OUTPUT_VAR} TRUE PARENT_SCOPE) + else() + set(${OUTPUT_VAR} FALSE PARENT_SCOPE) + endif() +endfunction() + +function(normalize_coverage_path FILE_PATH PROJECT_SOURCE_DIR OUTPUT_VAR) + if(NOT IS_ABSOLUTE "${FILE_PATH}") + string(REGEX REPLACE "${REGEX_DOTSLASH_SRC}" "src/" FILE_PATH "${FILE_PATH}") + get_filename_component(FILE_PATH "${PROJECT_SOURCE_DIR}/${FILE_PATH}" ABSOLUTE) + endif() + set(${OUTPUT_VAR} "${FILE_PATH}" PARENT_SCOPE) +endfunction() + +function(extract_xml_attribute XML_STRING ATTRIBUTE OUTPUT_VAR) + string(REGEX MATCH "${ATTRIBUTE}=\"([^\"]+)\"" _ "${XML_STRING}") + set(${OUTPUT_VAR} "${CMAKE_MATCH_1}" PARENT_SCOPE) +endfunction() + +function(extract_xml_element XML_STRING ELEMENT OUTPUT_VAR) + string(REGEX MATCH "<${ELEMENT}>([^<]+)</${ELEMENT}>" _ "${XML_STRING}") + set(${OUTPUT_VAR} "${CMAKE_MATCH_1}" PARENT_SCOPE) +endfunction() + +function(build_coverage_entry PATH NAME TESTED UNTESTED OUTPUT_VAR) + math(EXPR TOTAL "${TESTED} + ${UNTESTED}") + if(NOT TOTAL GREATER 0) + set(${OUTPUT_VAR} "" PARENT_SCOPE) + return() + endif() + math(EXPR PERCENT "(${TESTED} * 100) / ${TOTAL}") + set(${OUTPUT_VAR} "${PATH}|${NAME}|${TESTED}|${UNTESTED}|${TOTAL}|${PERCENT}" PARENT_SCOPE) +endfunction() + +function(parse_coverage_xml COVERAGE_FILE PROJECT_SOURCE_DIR) + if(NOT EXISTS "${COVERAGE_FILE}") + return() + endif() + + file(READ "${COVERAGE_FILE}" COVERAGE_XML) + + string(REGEX MATCH "${REGEX_LOC_TESTED}" _ "${COVERAGE_XML}") + set(TESTED "${CMAKE_MATCH_1}") + + string(REGEX MATCH "${REGEX_LOC_UNTESTED}" _ "${COVERAGE_XML}") + set(UNTESTED "${CMAKE_MATCH_1}") + + if(NOT TESTED OR NOT UNTESTED) + return() + endif() + + string(REGEX MATCHALL "${REGEX_FILE_ENTRY}" FILE_MATCHES "${COVERAGE_XML}") + + set(COVERED_LIST "") + set(COVERAGE_DATA "") + + foreach(MATCH ${FILE_MATCHES}) + extract_xml_attribute("${MATCH}" "FullPath" PATH) + extract_xml_attribute("${MATCH}" "Name" NAME) + + should_skip_file("${PATH}" SKIP) + if(SKIP) + continue() + endif() + + normalize_coverage_path("${PATH}" "${PROJECT_SOURCE_DIR}" ABS_PATH) + list(APPEND COVERED_LIST "${ABS_PATH}") + + extract_xml_element("${MATCH}" "LOCTested" TESTED_LINES) + extract_xml_element("${MATCH}" "LOCUnTested" UNTESTED_LINES) + + if(NOT TESTED_LINES OR NOT UNTESTED_LINES) + continue() + endif() + + build_coverage_entry("${PATH}" "${NAME}" "${TESTED_LINES}" "${UNTESTED_LINES}" ENTRY) + if(ENTRY) + list(APPEND COVERAGE_DATA "${ENTRY}") + endif() + endforeach() + + set(LOC_TESTED "${TESTED}" PARENT_SCOPE) + set(LOC_UNTESTED "${UNTESTED}" PARENT_SCOPE) + set(FILE_COVERAGE_LIST "${COVERAGE_DATA}" PARENT_SCOPE) + set(COVERED_FILES "${COVERED_LIST}" PARENT_SCOPE) +endfunction() diff --git a/cmake/utils/PrintCoverage.cmake b/cmake/utils/PrintCoverage.cmake new file mode 100644 index 00000000..4b75fa09 --- /dev/null +++ b/cmake/utils/PrintCoverage.cmake @@ -0,0 +1,333 @@ +# Script to parse and display coverage results from CTest +# +# This script is invoked by the 'make coverage' target and parses the CTest +# Coverage.xml file to generate a formatted coverage report grouped by component. +# +# This script is run with cmake -P, so CMAKE_SOURCE_DIR won't be set correctly. +# Use PROJECT_SOURCE_DIR and PROJECT_BINARY_DIR passed as -D arguments. + +if(NOT DEFINED PROJECT_SOURCE_DIR) + message(FATAL_ERROR "PROJECT_SOURCE_DIR must be defined") +endif() +if(NOT DEFINED PROJECT_BINARY_DIR) + message(FATAL_ERROR "PROJECT_BINARY_DIR must be defined") +endif() + +# Include coverage parsing functions +include(${CMAKE_CURRENT_LIST_DIR}/ParseCoverage.cmake) + +# Create padding strings (CMake 2.8 compatible) +function(make_padding LENGTH OUTPUT_VAR) + set(RESULT "") + if(LENGTH GREATER 0) + foreach(i RANGE 1 ${LENGTH}) + set(RESULT "${RESULT} ") + endforeach() + endif() + set(${OUTPUT_VAR} "${RESULT}" PARENT_SCOPE) +endfunction() + +# Format a number with padding for right alignment +function(format_number VALUE WIDTH OUTPUT_VAR) + string(LENGTH "${VALUE}" VALUE_LEN) + math(EXPR PAD_LEN "${WIDTH} - ${VALUE_LEN}") + make_padding(${PAD_LEN} PADDING) + set(${OUTPUT_VAR} "${PADDING}${VALUE}" PARENT_SCOPE) +endfunction() + +# Format a complete coverage row with consistent alignment +function(format_coverage_row LABEL TESTED TESTED_FC UNTESTED UNTESTED_FC TOTAL PERCENT OUTPUT_VAR) + string(LENGTH "${LABEL}" LABEL_LEN) + math(EXPR LABEL_PAD "28 - ${LABEL_LEN}") + make_padding(${LABEL_PAD} LP) + + format_number(${TESTED} 6 TS) + format_number(${TESTED_FC} 3 TFC) + format_number(${UNTESTED} 8 US) + format_number(${UNTESTED_FC} 3 UFC) + format_number(${TOTAL} 5 TT) + format_number(${PERCENT} 3 PC) + set(${OUTPUT_VAR} " ${LABEL}${LP}${TS}[${TFC}] ${US}[${UFC}] ${TT} ${PC}%" PARENT_SCOPE) +endfunction() + +# Format the header row to align with data columns +function(format_coverage_header OUTPUT_VAR) + set(HEADER " Component Tested Untested Total %") + set(${OUTPUT_VAR} "${HEADER}" PARENT_SCOPE) +endfunction() + +# Calculate metrics from entry list (pipe-delimited: path|name|tested|untested|total|percent) +function(calculate_metrics ENTRIES OUT_TESTED OUT_UNTESTED OUT_TESTED_FC OUT_UNTESTED_FC) + set(TESTED 0) + set(UNTESTED 0) + set(TESTED_FC 0) + set(UNTESTED_FC 0) + + foreach(ENTRY ${ENTRIES}) + string(REPLACE "|" ";" PARTS "${ENTRY}") + list(GET PARTS 2 ENTRY_TESTED) + list(GET PARTS 3 ENTRY_UNTESTED) + + math(EXPR TESTED "${TESTED} + ${ENTRY_TESTED}") + math(EXPR UNTESTED "${UNTESTED} + ${ENTRY_UNTESTED}") + + if(ENTRY_TESTED EQUAL 0) + math(EXPR UNTESTED_FC "${UNTESTED_FC} + 1") + else() + math(EXPR TESTED_FC "${TESTED_FC} + 1") + endif() + endforeach() + + set(${OUT_TESTED} "${TESTED}" PARENT_SCOPE) + set(${OUT_UNTESTED} "${UNTESTED}" PARENT_SCOPE) + set(${OUT_TESTED_FC} "${TESTED_FC}" PARENT_SCOPE) + set(${OUT_UNTESTED_FC} "${UNTESTED_FC}" PARENT_SCOPE) +endfunction() + +# Discover components and sub-components from source tree +function(discover_components PROJECT_SOURCE_DIR OUT_COMPONENTS OUT_COMP_SUBCOMPS) + file(GLOB COMPONENT_DIRS "${PROJECT_SOURCE_DIR}/src/*") + set(COMPONENTS "") + set(SKIP_DIRS "include;doc;tests") + + foreach(DIR ${COMPONENT_DIRS}) + if(IS_DIRECTORY ${DIR}) + get_filename_component(COMP_NAME ${DIR} NAME) + list(FIND SKIP_DIRS ${COMP_NAME} SKIP_IDX) + if(SKIP_IDX EQUAL -1) + list(APPEND COMPONENTS ${COMP_NAME}) + + file(GLOB SUBCOMP_DIRS "${DIR}/*") + set(COMP_${COMP_NAME}_SUBCOMPS "") + foreach(SUBDIR ${SUBCOMP_DIRS}) + if(IS_DIRECTORY ${SUBDIR}) + get_filename_component(SUBCOMP_NAME ${SUBDIR} NAME) + list(FIND SKIP_DIRS ${SUBCOMP_NAME} SKIP_IDX2) + if(SKIP_IDX2 EQUAL -1) + list(APPEND COMP_${COMP_NAME}_SUBCOMPS ${SUBCOMP_NAME}) + set(COMP_${COMP_NAME}_${SUBCOMP_NAME} "") + endif() + endif() + endforeach() + endif() + endif() + endforeach() + + set(${OUT_COMPONENTS} "${COMPONENTS}" PARENT_SCOPE) + foreach(COMP ${COMPONENTS}) + set(COMP_${COMP}_SUBCOMPS "${COMP_${COMP}_SUBCOMPS}" PARENT_SCOPE) + endforeach() +endfunction() + +# Print a coverage line +function(print_coverage_line LABEL TESTED TESTED_FC UNTESTED UNTESTED_FC TOTAL PERCENT) + format_coverage_row("${LABEL}" ${TESTED} ${TESTED_FC} ${UNTESTED} ${UNTESTED_FC} ${TOTAL} ${PERCENT} ROW) + execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${ROW}") +endfunction() + +# Locate the coverage file +if(NOT EXISTS "${CMAKE_BINARY_DIR}/Testing/TAG") + message(WARNING "Testing/TAG file not found. Coverage may not have run successfully.") + return() +endif() + +file(STRINGS "${CMAKE_BINARY_DIR}/Testing/TAG" TAG_CONTENTS LIMIT_COUNT 1) +string(STRIP "${TAG_CONTENTS}" TEST_DIR) +set(COVERAGE_FILE "${CMAKE_BINARY_DIR}/Testing/${TEST_DIR}/Coverage.xml") + +if(NOT EXISTS "${COVERAGE_FILE}") + message(WARNING "Coverage file not found: ${COVERAGE_FILE}") + execute_process(COMMAND ${CMAKE_COMMAND} -E echo "Note: Coverage results should be in Testing/${TEST_DIR}/") + return() +endif() + +# Parse the coverage XML file +parse_coverage_xml("${COVERAGE_FILE}" "${PROJECT_SOURCE_DIR}") + +if(NOT LOC_TESTED OR NOT LOC_UNTESTED) + message(WARNING "Could not parse coverage metrics") + return() +endif() + +math(EXPR TOTAL_LOC "${LOC_TESTED} + ${LOC_UNTESTED}") +if(NOT TOTAL_LOC GREATER 0) + message(WARNING "No coverage data found") + return() +endif() + +math(EXPR COVERAGE_PERCENT "(${LOC_TESTED} * 100) / ${TOTAL_LOC}") + +# Discover and group files by component +if(NOT FILE_COVERAGE_LIST) + execute_process(COMMAND ${CMAKE_COMMAND} -E echo "No coverage data for source files (excluding tests)") + return() +endif() + +# Discover components from source tree (only src/ directory) +discover_components("${PROJECT_SOURCE_DIR}" COMPONENTS COMP_SUBCOMPS) + +# Find all source files and identify completely untested ones +file(GLOB_RECURSE ALL_SOURCE_FILES "${PROJECT_SOURCE_DIR}/src/*.c") +set(UNCOVERED_FILES "") +foreach(SRC_FILE ${ALL_SOURCE_FILES}) + if(SRC_FILE MATCHES "test" OR SRC_FILE MATCHES "/tests/") + continue() + endif() + + list(FIND COVERED_FILES "${SRC_FILE}" FILE_IDX) + if(FILE_IDX EQUAL -1) + file(STRINGS "${SRC_FILE}" FILE_LINES) + list(LENGTH FILE_LINES LINE_COUNT) + if(LINE_COUNT GREATER 0) + get_filename_component(FILE_NAME "${SRC_FILE}" NAME) + list(APPEND UNCOVERED_FILES "${SRC_FILE}|${FILE_NAME}|0|${LINE_COUNT}|${LINE_COUNT}|0") + endif() + endif() +endforeach() + +# Combine covered and uncovered files +set(ALL_FILES ${FILE_COVERAGE_LIST}) +if(UNCOVERED_FILES) + list(APPEND ALL_FILES ${UNCOVERED_FILES}) +endif() + +# Group files into components and sub-components +foreach(ENTRY ${ALL_FILES}) + string(REPLACE "|" ";" ENTRY_PARTS "${ENTRY}") + list(GET ENTRY_PARTS 0 F_PATH) + + # Normalize path and extract relative path from src/ + # Remove leading ./ if present, then ensure path starts with src/ + string(REGEX REPLACE "^\\./" "" REL_PATH "${F_PATH}") + string(REGEX REPLACE "^.*/src/" "src/" REL_PATH "${REL_PATH}") + + # Tokenize on /: ["src", "component", "subcomponent", "file.c"] + string(REPLACE "/" ";" PATH_TOKENS "${REL_PATH}") + list(LENGTH PATH_TOKENS TOKEN_COUNT) + + if(TOKEN_COUNT EQUAL 3) + # src/component/file.c - add to component main + list(GET PATH_TOKENS 1 FILE_COMP) + list(FIND COMPONENTS ${FILE_COMP} COMP_IDX) + if(COMP_IDX GREATER -1) + list(APPEND COMP_${FILE_COMP} "${ENTRY}") + list(APPEND COMP_${FILE_COMP}_main "${ENTRY}") + endif() + elseif(TOKEN_COUNT GREATER 3) + # src/component/subdir/.../file.c - check if subdir is a known subcomponent + list(GET PATH_TOKENS 1 FILE_COMP) + list(GET PATH_TOKENS 2 FILE_SUBCOMP) + + list(FIND COMPONENTS ${FILE_COMP} COMP_IDX) + if(COMP_IDX GREATER -1) + list(APPEND COMP_${FILE_COMP} "${ENTRY}") + + # Check if subdir is a recognized subcomponent + list(FIND COMP_${FILE_COMP}_SUBCOMPS ${FILE_SUBCOMP} SUBCOMP_IDX) + if(SUBCOMP_IDX GREATER -1) + list(APPEND COMP_${FILE_COMP}_${FILE_SUBCOMP} "${ENTRY}") + else() + # Subdir not recognized, treat as main + list(APPEND COMP_${FILE_COMP}_main "${ENTRY}") + endif() + endif() + endif() +endforeach() + +execute_process(COMMAND ${CMAKE_COMMAND} -E echo "") +execute_process(COMMAND ${CMAKE_COMMAND} -E echo "=============================================================================") +format_coverage_header(HEADER) +execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${HEADER}") +execute_process(COMMAND ${CMAKE_COMMAND} -E echo "=============================================================================") + +# Process and display each component +foreach(COMP_NAME ${COMPONENTS}) + set(COMP_LIST ${COMP_${COMP_NAME}}) + + if(NOT COMP_LIST) + continue() + endif() + + calculate_metrics("${COMP_LIST}" COMP_TESTED COMP_UNTESTED COMP_TESTED_FILE_COUNT COMP_UNTESTED_FILE_COUNT) + math(EXPR COMP_TOTAL "${COMP_TESTED} + ${COMP_UNTESTED}") + if(COMP_TOTAL GREATER 0) + math(EXPR COMP_PERCENT "(${COMP_TESTED} * 100) / ${COMP_TOTAL}") + else() + set(COMP_PERCENT 0) + endif() + + print_coverage_line("${COMP_NAME}" ${COMP_TESTED} ${COMP_TESTED_FILE_COUNT} ${COMP_UNTESTED} ${COMP_UNTESTED_FILE_COUNT} ${COMP_TOTAL} ${COMP_PERCENT}) + + # Display "main" sub-component first if it has files + set(MAIN_LIST ${COMP_${COMP_NAME}_main}) + if(MAIN_LIST) + calculate_metrics("${MAIN_LIST}" MAIN_TESTED MAIN_UNTESTED MAIN_TESTED_FILE_COUNT MAIN_UNTESTED_FILE_COUNT) + math(EXPR MAIN_TOTAL "${MAIN_TESTED} + ${MAIN_UNTESTED}") + if(MAIN_TOTAL GREATER 0) + math(EXPR MAIN_PERCENT "(${MAIN_TESTED} * 100) / ${MAIN_TOTAL}") + else() + set(MAIN_PERCENT 0) + endif() + + print_coverage_line(" ." ${MAIN_TESTED} ${MAIN_TESTED_FILE_COUNT} ${MAIN_UNTESTED} ${MAIN_UNTESTED_FILE_COUNT} ${MAIN_TOTAL} ${MAIN_PERCENT}) + endif() + + # Display sub-components + foreach(SUBCOMP ${COMP_${COMP_NAME}_SUBCOMPS}) + set(SUBCOMP_LIST ${COMP_${COMP_NAME}_${SUBCOMP}}) + if(NOT SUBCOMP_LIST) + continue() + endif() + + calculate_metrics("${SUBCOMP_LIST}" SUBCOMP_TESTED SUBCOMP_UNTESTED SUBCOMP_TESTED_FILE_COUNT SUBCOMP_UNTESTED_FILE_COUNT) + math(EXPR SUBCOMP_TOTAL "${SUBCOMP_TESTED} + ${SUBCOMP_UNTESTED}") + if(SUBCOMP_TOTAL GREATER 0) + math(EXPR SUBCOMP_PERCENT "(${SUBCOMP_TESTED} * 100) / ${SUBCOMP_TOTAL}") + else() + set(SUBCOMP_PERCENT 0) + endif() + + print_coverage_line(" ${SUBCOMP}" ${SUBCOMP_TESTED} ${SUBCOMP_TESTED_FILE_COUNT} ${SUBCOMP_UNTESTED} ${SUBCOMP_UNTESTED_FILE_COUNT} ${SUBCOMP_TOTAL} ${SUBCOMP_PERCENT}) + endforeach() + + execute_process(COMMAND ${CMAKE_COMMAND} -E echo "") +endforeach() + +# Calculate overall coverage +set(TOTAL_TESTED ${LOC_TESTED}) +set(TOTAL_UNTESTED ${LOC_UNTESTED}) + +# Count file coverage for totals +set(TOTAL_TESTED_FILE_COUNT 0) +set(TOTAL_UNTESTED_FILE_COUNT 0) + +foreach(ENTRY ${FILE_COVERAGE_LIST}) + string(REPLACE "|" ";" PARTS "${ENTRY}") + list(GET PARTS 2 ENTRY_TESTED) + if(ENTRY_TESTED EQUAL 0) + math(EXPR TOTAL_UNTESTED_FILE_COUNT "${TOTAL_UNTESTED_FILE_COUNT} + 1") + else() + math(EXPR TOTAL_TESTED_FILE_COUNT "${TOTAL_TESTED_FILE_COUNT} + 1") + endif() +endforeach() + +# Add untested file counts +foreach(ENTRY ${UNCOVERED_FILES}) + string(REPLACE "|" ";" PARTS "${ENTRY}") + list(GET PARTS 3 ENTRY_UNTESTED) + math(EXPR TOTAL_UNTESTED "${TOTAL_UNTESTED} + ${ENTRY_UNTESTED}") + math(EXPR TOTAL_UNTESTED_FILE_COUNT "${TOTAL_UNTESTED_FILE_COUNT} + 1") +endforeach() + +math(EXPR TOTAL_ALL_LOC "${TOTAL_TESTED} + ${TOTAL_UNTESTED}") +if(TOTAL_ALL_LOC GREATER 0) + math(EXPR OVERALL_COVERAGE_PERCENT "(${TOTAL_TESTED} * 100) / ${TOTAL_ALL_LOC}") +else() + set(OVERALL_COVERAGE_PERCENT 0) +endif() + +execute_process(COMMAND ${CMAKE_COMMAND} -E echo "=============================================================================") +print_coverage_line("Total" ${TOTAL_TESTED} ${TOTAL_TESTED_FILE_COUNT} ${TOTAL_UNTESTED} ${TOTAL_UNTESTED_FILE_COUNT} ${TOTAL_ALL_LOC} ${OVERALL_COVERAGE_PERCENT}) +execute_process(COMMAND ${CMAKE_COMMAND} -E echo "=============================================================================") +execute_process(COMMAND ${CMAKE_COMMAND} -E echo "") +execute_process(COMMAND ${CMAKE_COMMAND} -E echo "Detailed XML report: ${COVERAGE_FILE}") diff --git a/cmake/utils/TestUtils.cmake b/cmake/utils/TestUtils.cmake new file mode 100644 index 00000000..e602690c --- /dev/null +++ b/cmake/utils/TestUtils.cmake @@ -0,0 +1,38 @@ +# Compute test name prefix from directory structure +function(compute_test_prefix) + file(RELATIVE_PATH _prefix "${CMAKE_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}") + string(REGEX REPLACE "/tests$" "" _prefix "${_prefix}") + set(TEST_PREFIX "${_prefix}" PARENT_SCOPE) +endfunction() + +# Register tests from a test executable with the test framework +# Usage: ouroboros_register_tests(TARGET <target> TESTS <test_list> [ENVIRONMENT <env>]) +# The TESTS argument should be the test list variable created by create_test_sourcelist +function(ouroboros_register_tests) + cmake_parse_arguments(PARSE_ARGV 0 ARG "" "TARGET;ENVIRONMENT" "TESTS") + + if(NOT ARG_TARGET) + message(FATAL_ERROR "ouroboros_register_tests: TARGET required") + endif() + + if(NOT ARG_TESTS) + message(FATAL_ERROR "ouroboros_register_tests: TESTS required") + endif() + + # First entry is the test driver, skip it + set(_tests ${ARG_TESTS}) + list(POP_FRONT _tests) + + foreach (test_src ${_tests}) + get_filename_component(test_name ${test_src} NAME_WE) + add_test(${TEST_PREFIX}/${test_name} + ${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET} ${test_name}) + # All Ouroboros tests support skip return code + set_property(TEST ${TEST_PREFIX}/${test_name} PROPERTY SKIP_RETURN_CODE 1) + # Optional environment variables + if(ARG_ENVIRONMENT) + set_property(TEST ${TEST_PREFIX}/${test_name} + PROPERTY ENVIRONMENT "${ARG_ENVIRONMENT}") + endif() + endforeach () +endfunction() diff --git a/cmake/version.cmake b/cmake/version.cmake new file mode 100644 index 00000000..00183d34 --- /dev/null +++ b/cmake/version.cmake @@ -0,0 +1,5 @@ +set(PACKAGE_VERSION_MAJOR 0) +set(PACKAGE_VERSION_MINOR 22) +set(PACKAGE_VERSION_PATCH 0) +set(PACKAGE_VERSION + "${PACKAGE_VERSION_MAJOR}.${PACKAGE_VERSION_MINOR}.${PACKAGE_VERSION_PATCH}") |
