776 lines
34 KiB
CMake
776 lines
34 KiB
CMake
cmake_minimum_required(VERSION 3.21)
|
|
|
|
project(bravais
|
|
VERSION 0.1.0
|
|
DESCRIPTION "Sans-IO C library for the Bravais VPN protocol (post-quantum handshake, DPLPMTUD, transport shaping)"
|
|
HOMEPAGE_URL "https://bravais.org"
|
|
LANGUAGES C)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Platform guard: POSIX-only for now.
|
|
#
|
|
# libbravais itself is sans-IO and makes no syscalls, so the *sources* should
|
|
# port to Windows unchanged. Two things in this file do not:
|
|
#
|
|
# - the whole flag machinery below is GCC/Clang; MSVC needs its own branch
|
|
# (/W4, /analyze, /fsanitize=address, /GS, /guard:cf)
|
|
# - static and shared cannot share OUTPUT_NAME on Windows, because the DLL's
|
|
# import library is also bravais.lib and would collide with the static one
|
|
#
|
|
# Port bravaisd's platform backends first; this can wait.
|
|
# ---------------------------------------------------------------------------
|
|
if(WIN32)
|
|
message(FATAL_ERROR "libbravais has no MSVC/Windows build yet (the sources are portable; this file is not).")
|
|
endif()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Options
|
|
# ---------------------------------------------------------------------------
|
|
option(BRAVAIS_BUILD_STATIC "Build libbravais.a" ON)
|
|
option(BRAVAIS_BUILD_SHARED "Build libbravais.so" ON)
|
|
option(BRAVAIS_BUILD_TESTS "Build libbravais tests" OFF)
|
|
option(BRAVAIS_BUILD_FUZZERS "Build libbravais fuzz targets (needs Clang)" OFF)
|
|
option(BRAVAIS_BUILD_EXAMPLES "Build libbravais example programs" OFF)
|
|
|
|
if(NOT BRAVAIS_BUILD_STATIC AND NOT BRAVAIS_BUILD_SHARED)
|
|
message(FATAL_ERROR "Both BRAVAIS_BUILD_STATIC and BRAVAIS_BUILD_SHARED are OFF; nothing to build.")
|
|
endif()
|
|
|
|
# Crypto is a hard dependency of proto/ and crypto/, but not of util/ or wire/.
|
|
# Turn this off to bootstrap the cursor and the frame parser on a machine
|
|
# without liboqs installed; turn it back on before writing a line of crypto/.
|
|
option(BRAVAIS_ENABLE_CRYPTO "Link libsodium and liboqs (required for crypto/ and proto/)" ON)
|
|
|
|
# The warning set, the sanitizers and the custom configurations below are for
|
|
# working *on* bravais, not for working *with* it. A project that vendors us via
|
|
# add_subdirectory() wants a library, not our -Werror policy or an ASan runtime
|
|
# it never asked for, so the whole developer flag machinery defaults to
|
|
# top-level builds. Force it on with -DBRAVAIS_DEVELOPER_BUILD=ON if you are
|
|
# vendoring bravais and want it built the way we build it.
|
|
option(BRAVAIS_DEVELOPER_BUILD
|
|
"Build bravais with its own warning set, configurations and instrumentation"
|
|
${PROJECT_IS_TOP_LEVEL})
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Feature macros so POSIX/BSD calls resolve under -std=c23. On glibc,
|
|
# _POSIX_C_SOURCE + _DEFAULT_SOURCE expose them; on macOS, _POSIX_C_SOURCE
|
|
# alone *hides* BSD extensions, so _DARWIN_C_SOURCE is added to restore the
|
|
# full set.
|
|
#
|
|
# libbravais is sans-IO and should need none of this: no sockets, no clocks
|
|
# (the caller passes nowMs), no file descriptors. It is defined anyway so that
|
|
# a stray <endian.h> or a libsodium header expecting a POSIX environment does
|
|
# not fail oddly, and so tests/ and bravaisd can reuse the same list. If you
|
|
# find yourself *needing* one of these in src/, that is a sans-IO violation --
|
|
# fix the code, not this list.
|
|
# ---------------------------------------------------------------------------
|
|
set(BRAVAIS_FEATURE_DEFS _POSIX_C_SOURCE=200809L _DEFAULT_SOURCE)
|
|
if(APPLE)
|
|
list(APPEND BRAVAIS_FEATURE_DEFS _DARWIN_C_SOURCE)
|
|
endif()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Build configuration
|
|
#
|
|
# cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug # -Og -g3, ASan + UBSan
|
|
# cmake -S . -B build-strict -DCMAKE_BUILD_TYPE=Strict # Debug plus -Werror
|
|
# cmake -S . -B build-analyzer -DCMAKE_BUILD_TYPE=Analyzer # -Og -g3, GCC -fanalyzer
|
|
# cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release # -O3, _FORTIFY_SOURCE
|
|
#
|
|
# Debug finds memory bugs at run time, Analyzer finds them at compile time,
|
|
# Release survives them. Strict is Debug with warnings promoted to errors -- the
|
|
# configuration CI should gate on, kept separate so a new warning never blocks
|
|
# someone mid-debugging. The warning set is identical in all four; only the
|
|
# instrumentation and the error policy differ.
|
|
#
|
|
# Debug and Analyzer are separate configurations rather than one Debug build
|
|
# with everything switched on, because the two tools actively interfere: ASan's
|
|
# instrumentation inflates the CFG enough that -fanalyzer exhausts its
|
|
# exploration budget and silently stops reporting.
|
|
#
|
|
# ThreadSanitizer is deliberately absent: it cannot be combined with ASan, so it
|
|
# would need a third configuration of its own. libbravais is single-threaded by
|
|
# design -- a bravais_conn_t is owned by one thread and the library spawns
|
|
# none -- so TSan belongs in bravaisd's build, not here.
|
|
#
|
|
# MemorySanitizer is also absent, and is more tempting than TSan: a parser fed
|
|
# attacker-controlled bytes is exactly what MSan is for. It needs every
|
|
# dependency rebuilt instrumented (libsodium and liboqs included) or it drowns
|
|
# in false positives, so it is a separate build tree with its own toolchain
|
|
# file, not a configuration here.
|
|
# ---------------------------------------------------------------------------
|
|
set(BRAVAIS_CUSTOM_CONFIGS Strict Analyzer)
|
|
set(BRAVAIS_BUILD_TYPES Debug Strict Analyzer Release RelWithDebInfo MinSizeRel)
|
|
|
|
if(BRAVAIS_DEVELOPER_BUILD)
|
|
get_property(BRAVAIS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
|
if(BRAVAIS_MULTI_CONFIG)
|
|
foreach(_cfg IN LISTS BRAVAIS_CUSTOM_CONFIGS)
|
|
if(NOT "${_cfg}" IN_LIST CMAKE_CONFIGURATION_TYPES)
|
|
list(APPEND CMAKE_CONFIGURATION_TYPES ${_cfg})
|
|
endif()
|
|
endforeach()
|
|
elseif(NOT CMAKE_BUILD_TYPE)
|
|
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE)
|
|
message(STATUS "No CMAKE_BUILD_TYPE specified; defaulting to Debug")
|
|
endif()
|
|
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${BRAVAIS_BUILD_TYPES})
|
|
|
|
# Strict and Analyzer are custom configurations, so CMake has no built-in
|
|
# flags for them. Both inherit Debug's, and imported targets need a mapping
|
|
# because they only ship Debug/Release/NOCONFIG. liboqs and libsodium both
|
|
# arrive as imported targets, so this mapping is load-bearing here in a way
|
|
# it was not when the only dependency was Threads::Threads.
|
|
foreach(_cfg IN LISTS BRAVAIS_CUSTOM_CONFIGS)
|
|
string(TOUPPER ${_cfg} _cfg_upper)
|
|
set(CMAKE_C_FLAGS_${_cfg_upper} "${CMAKE_C_FLAGS_DEBUG}"
|
|
CACHE STRING "Flags used by the C compiler during ${_cfg} builds.")
|
|
mark_as_advanced(CMAKE_C_FLAGS_${_cfg_upper})
|
|
foreach(_linker EXE SHARED MODULE STATIC)
|
|
set(CMAKE_${_linker}_LINKER_FLAGS_${_cfg_upper} "${CMAKE_${_linker}_LINKER_FLAGS_DEBUG}"
|
|
CACHE STRING "Flags used by the linker during ${_cfg} builds.")
|
|
mark_as_advanced(CMAKE_${_linker}_LINKER_FLAGS_${_cfg_upper})
|
|
endforeach()
|
|
set(CMAKE_MAP_IMPORTED_CONFIG_${_cfg_upper} Debug "" Release RelWithDebInfo)
|
|
endforeach()
|
|
endif()
|
|
|
|
# Debug, Strict and Analyzer share the developer flag set (-Og -g3, warnings).
|
|
set(BRAVAIS_IS_DEBUGLIKE "$<OR:$<CONFIG:Debug>,$<CONFIG:Strict>,$<CONFIG:Analyzer>>")
|
|
# Debug and Strict are the runtime-instrumented pair; Analyzer must stay clean
|
|
# of sanitizers or -fanalyzer goes quiet (see above).
|
|
set(BRAVAIS_IS_SANITIZED "$<OR:$<CONFIG:Debug>,$<CONFIG:Strict>>")
|
|
|
|
# The Strict configuration always errors on warnings; this option additionally
|
|
# promotes them in Debug and Analyzer. OFF by default even though the tree is
|
|
# clean today, so a new compiler version's new warning never blocks someone
|
|
# mid-debugging -- build Strict when you want the gate.
|
|
option(BRAVAIS_WERROR "Debug/Analyzer: treat warnings as errors (always on in Strict)" OFF)
|
|
option(BRAVAIS_ENABLE_SANITIZERS "Debug/Strict configs: AddressSanitizer + UndefinedBehaviorSanitizer" ON)
|
|
option(BRAVAIS_ENABLE_ANALYZER "Analyzer config: the compiler's static analyzer (GCC -fanalyzer)" ON)
|
|
option(BRAVAIS_ENABLE_HARDENING "All configs: stack protector, _FORTIFY_SOURCE, RELRO/NOW, CFI" ON)
|
|
option(BRAVAIS_ENABLE_LTO "Optimized configs: link-time optimization" OFF)
|
|
|
|
# How much of the control-flow path -fanalyzer prints per report. 1 lists only
|
|
# the state transitions (opened here / first close here / leaks here), which is
|
|
# what you want while triaging; raise it when a report needs the branch-by-branch
|
|
# path that explains how it got there. GCC silently accepts out-of-range values,
|
|
# so validate here instead.
|
|
set(BRAVAIS_ANALYZER_VERBOSITY 1 CACHE STRING "Analyzer config: -fanalyzer path detail, 0 (terse) to 5 (full)")
|
|
set_property(CACHE BRAVAIS_ANALYZER_VERBOSITY PROPERTY STRINGS 0 1 2 3 4 5)
|
|
if(NOT BRAVAIS_ANALYZER_VERBOSITY MATCHES "^[0-5]$")
|
|
message(FATAL_ERROR
|
|
"BRAVAIS_ANALYZER_VERBOSITY must be an integer from 0 to 5, got '${BRAVAIS_ANALYZER_VERBOSITY}'")
|
|
endif()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Flag probing
|
|
#
|
|
# Most of the interesting memory diagnostics below only exist from a certain
|
|
# GCC/Clang version onwards, and several are architecture specific. Probe every
|
|
# flag instead of gating on compiler version, so an older or foreign toolchain
|
|
# silently gets the subset it understands rather than failing to configure.
|
|
#
|
|
# A flag the driver merely tolerates is not a flag that does anything (Clang
|
|
# accepts -fstack-clash-protection on arm64 and then ignores it), so the probe
|
|
# promotes "argument unused" to an error where the compiler supports that.
|
|
# ---------------------------------------------------------------------------
|
|
include(CheckCCompilerFlag)
|
|
include(CheckLinkerFlag)
|
|
|
|
check_c_compiler_flag(-Werror=unused-command-line-argument BRAVAIS_HAS_WERROR_UNUSED_ARG)
|
|
if(BRAVAIS_HAS_WERROR_UNUSED_ARG)
|
|
set(BRAVAIS_FLAG_PROBE_STRICT "-Werror=unused-command-line-argument")
|
|
else()
|
|
set(BRAVAIS_FLAG_PROBE_STRICT "")
|
|
endif()
|
|
|
|
function(bravais_append_supported_c_flags out_var)
|
|
set(_accepted ${${out_var}})
|
|
set(CMAKE_REQUIRED_FLAGS "${BRAVAIS_FLAG_PROBE_STRICT}")
|
|
foreach(_flag IN LISTS ARGN)
|
|
string(MAKE_C_IDENTIFIER "BRAVAIS_HAS_CFLAG_${_flag}" _cache_var)
|
|
check_c_compiler_flag("${_flag}" ${_cache_var})
|
|
if(${_cache_var})
|
|
list(APPEND _accepted "${_flag}")
|
|
endif()
|
|
endforeach()
|
|
set(${out_var} "${_accepted}" PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
# Instrumentation such as -fsanitize=address has to reach the linker as well,
|
|
# and is only usable if the matching runtime library actually exists, so the
|
|
# probe hands the flag to both halves of the try-compile.
|
|
#
|
|
# Flags are also probed cumulatively, in the order given: some are only legal in
|
|
# the presence of an earlier one (-fsanitize=pointer-compare is rejected unless
|
|
# -fsanitize=address is already on the command line), and probing it alone would
|
|
# quietly drop it.
|
|
function(bravais_append_supported_instrument_flags out_var)
|
|
set(_accepted ${${out_var}})
|
|
set(_context "")
|
|
foreach(_flag IN LISTS ARGN)
|
|
string(MAKE_C_IDENTIFIER "BRAVAIS_HAS_INSTRUMENT_${_flag}" _cache_var)
|
|
string(JOIN " " CMAKE_REQUIRED_FLAGS ${BRAVAIS_FLAG_PROBE_STRICT} ${_context})
|
|
set(CMAKE_REQUIRED_LINK_OPTIONS ${_context} "${_flag}")
|
|
check_c_compiler_flag("${_flag}" ${_cache_var})
|
|
if(${_cache_var})
|
|
list(APPEND _accepted "${_flag}")
|
|
list(APPEND _context "${_flag}")
|
|
endif()
|
|
endforeach()
|
|
set(${out_var} "${_accepted}" PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
function(bravais_append_supported_link_flags out_var)
|
|
set(_accepted ${${out_var}})
|
|
foreach(_flag IN LISTS ARGN)
|
|
string(MAKE_C_IDENTIFIER "BRAVAIS_HAS_LDFLAG_${_flag}" _cache_var)
|
|
check_linker_flag(C "${_flag}" ${_cache_var})
|
|
if(${_cache_var})
|
|
list(APPEND _accepted "${_flag}")
|
|
endif()
|
|
endforeach()
|
|
set(${out_var} "${_accepted}" PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Flag sets
|
|
#
|
|
# The per-config sets are applied through generator expressions so multi-config
|
|
# generators (Xcode, VS) work too.
|
|
# ---------------------------------------------------------------------------
|
|
set(BRAVAIS_C_WARNINGS "")
|
|
set(BRAVAIS_C_FLAGS_DEBUGLIKE "")
|
|
set(BRAVAIS_C_FLAGS_OPTIMIZED "")
|
|
# The two instrumentation sets, each owned by one configuration and never both.
|
|
set(BRAVAIS_SANITIZER_FLAGS "")
|
|
set(BRAVAIS_ANALYZER_FLAGS "")
|
|
# Instrumentation that must be handed to the linker as well.
|
|
set(BRAVAIS_INSTRUMENT_COMPILE "")
|
|
set(BRAVAIS_INSTRUMENT_LINK "")
|
|
set(BRAVAIS_WERROR_FLAG -Werror)
|
|
|
|
# No MSVC branch: the WIN32 guard above makes this file unreachable there.
|
|
if(BRAVAIS_DEVELOPER_BUILD)
|
|
# Portable warning set, memory-safety first.
|
|
#
|
|
# -Wconversion and -Wsign-conversion matter more here than in most C
|
|
# projects: this library spends its life converting between size_t lengths,
|
|
# uint64_t varints and the narrow fields of a wire format, and a silent
|
|
# truncation in that path is a parser bug by another name.
|
|
bravais_append_supported_c_flags(BRAVAIS_C_WARNINGS
|
|
-Wall
|
|
-Wextra
|
|
-Wpedantic
|
|
-Wshadow
|
|
-Wconversion
|
|
-Wsign-conversion
|
|
-Wcast-qual
|
|
-Wcast-align
|
|
-Wstrict-prototypes
|
|
-Wmissing-prototypes
|
|
-Wold-style-definition
|
|
-Wbad-function-cast
|
|
-Wwrite-strings
|
|
-Wformat=2
|
|
-Wnull-dereference
|
|
-Wdouble-promotion
|
|
-Wfloat-equal
|
|
-Wvla
|
|
-Wstack-protector
|
|
-Wpointer-arith
|
|
-Wundef
|
|
-Winit-self
|
|
-Wmissing-include-dirs
|
|
-Wswitch-enum
|
|
)
|
|
|
|
if(CMAKE_C_COMPILER_ID MATCHES "GNU")
|
|
# GCC-only diagnostics. The -W*=N forms ask for the strictest level:
|
|
# more false positives, but they catch out-of-bounds writes, dangling
|
|
# pointers and double frees that -Wall/-Wextra walk straight past.
|
|
bravais_append_supported_c_flags(BRAVAIS_C_WARNINGS
|
|
-Warray-bounds=2
|
|
-Wstringop-overflow=4
|
|
-Wstringop-truncation
|
|
-Wformat-overflow=2
|
|
-Wformat-truncation=2
|
|
-Wuse-after-free=3
|
|
-Wdangling-pointer=2
|
|
-Wfree-nonheap-object
|
|
-Walloc-zero
|
|
-Walloca
|
|
-Wduplicated-cond
|
|
-Wduplicated-branches
|
|
-Wlogical-op
|
|
-Wjump-misses-init
|
|
-Wtrampolines
|
|
-Wflex-array-member-not-at-end
|
|
)
|
|
# -fanalyzer is a whole-path symbolic execution pass (leaks, double
|
|
# free, use-after-free, NULL derefs across function boundaries). It is
|
|
# the only reason the Analyzer configuration exists.
|
|
if(BRAVAIS_ENABLE_ANALYZER)
|
|
bravais_append_supported_c_flags(BRAVAIS_ANALYZER_FLAGS
|
|
-fanalyzer-verbosity=${BRAVAIS_ANALYZER_VERBOSITY}
|
|
-fanalyzer
|
|
)
|
|
endif()
|
|
elseif(CMAKE_C_COMPILER_ID MATCHES "Clang")
|
|
# Clang-only diagnostics. Clang has no in-compiler equivalent of
|
|
# -fanalyzer, so the Analyzer config is simply an uninstrumented Debug
|
|
# build here -- which is exactly the base `scan-build cmake --build
|
|
# build-analyzer` wants.
|
|
bravais_append_supported_c_flags(BRAVAIS_C_WARNINGS
|
|
-Warray-bounds-pointer-arithmetic
|
|
-Wconditional-uninitialized
|
|
-Wshift-sign-overflow
|
|
-Wassign-enum
|
|
-Wcomma
|
|
-Wloop-analysis
|
|
-Wthread-safety
|
|
-Wover-aligned
|
|
)
|
|
endif()
|
|
|
|
# -Og keeps the code steppable while still running the optimizer passes
|
|
# that -Wmaybe-uninitialized / -Wstringop-* rely on; at -O0 those warnings
|
|
# go quiet. -fno-omit-frame-pointer buys readable sanitizer backtraces.
|
|
bravais_append_supported_c_flags(BRAVAIS_C_FLAGS_DEBUGLIKE
|
|
-Og
|
|
-g3
|
|
-fno-omit-frame-pointer
|
|
)
|
|
if(BRAVAIS_WERROR)
|
|
list(APPEND BRAVAIS_C_FLAGS_DEBUGLIKE ${BRAVAIS_WERROR_FLAG})
|
|
endif()
|
|
|
|
if(BRAVAIS_ENABLE_HARDENING)
|
|
bravais_append_supported_c_flags(BRAVAIS_C_FLAGS_DEBUGLIKE
|
|
-fstack-protector-strong
|
|
)
|
|
# _FORTIFY_SOURCE needs an optimized build to see through the buffer
|
|
# sizes, and it fights ASan's interceptors, so it is Release-only.
|
|
bravais_append_supported_c_flags(BRAVAIS_C_FLAGS_OPTIMIZED
|
|
-U_FORTIFY_SOURCE
|
|
-D_FORTIFY_SOURCE=3
|
|
-fstack-protector-strong
|
|
-fstack-clash-protection
|
|
)
|
|
bravais_append_supported_instrument_flags(BRAVAIS_INSTRUMENT_COMPILE
|
|
-fcf-protection=full # x86_64 CET
|
|
-mbranch-protection=standard # aarch64 BTI/PAC
|
|
)
|
|
bravais_append_supported_link_flags(BRAVAIS_INSTRUMENT_LINK
|
|
"LINKER:-z,relro"
|
|
"LINKER:-z,now"
|
|
"LINKER:-z,noexecstack"
|
|
)
|
|
endif()
|
|
|
|
if(BRAVAIS_ENABLE_SANITIZERS)
|
|
# Everything that composes with ASan. LeakSanitizer is not listed
|
|
# because ASan already includes it where it is supported, and
|
|
# -fsanitize=leak cannot be combined with -fsanitize=address.
|
|
# -fno-sanitize-recover makes UB abort instead of printing and
|
|
# continuing, so a bad shift or overflow cannot be ignored in CI.
|
|
#
|
|
# pointer-compare/pointer-subtract are inert unless
|
|
# ASAN_OPTIONS=detect_invalid_pointer_pairs=2 is set at run time. Do not
|
|
# set it on macOS/arm64: that runtime check misfires there and reports
|
|
# invalid-pointer-pair against 0xfffffffffffffff0 for code containing no
|
|
# pointer comparison at all. The compile flags stay because they cost
|
|
# nothing and the check is sound on Linux.
|
|
bravais_append_supported_instrument_flags(BRAVAIS_SANITIZER_FLAGS
|
|
-fsanitize=address
|
|
-fsanitize-address-use-after-scope
|
|
-fsanitize=pointer-compare
|
|
-fsanitize=pointer-subtract
|
|
-fsanitize=undefined
|
|
-fsanitize=bounds-strict
|
|
-fno-sanitize-recover=undefined
|
|
-fno-omit-frame-pointer
|
|
)
|
|
endif()
|
|
|
|
# Each instrumentation set belongs to exactly one configuration. Keeping
|
|
# them in separate configs is the point of the Analyzer build, so never
|
|
# emit both.
|
|
if(BRAVAIS_SANITIZER_FLAGS)
|
|
list(APPEND BRAVAIS_INSTRUMENT_COMPILE "$<${BRAVAIS_IS_SANITIZED}:${BRAVAIS_SANITIZER_FLAGS}>")
|
|
list(APPEND BRAVAIS_INSTRUMENT_LINK "$<${BRAVAIS_IS_SANITIZED}:${BRAVAIS_SANITIZER_FLAGS}>")
|
|
endif()
|
|
|
|
if(BRAVAIS_ENABLE_LTO)
|
|
include(CheckIPOSupported)
|
|
check_ipo_supported(RESULT BRAVAIS_IPO_SUPPORTED OUTPUT BRAVAIS_IPO_ERROR)
|
|
if(NOT BRAVAIS_IPO_SUPPORTED)
|
|
message(WARNING "LTO requested but unsupported by this toolchain: ${BRAVAIS_IPO_ERROR}")
|
|
endif()
|
|
endif()
|
|
endif()
|
|
|
|
# Applies the developer flag set to one of our targets. Deliberately a function
|
|
# rather than an INTERFACE library: the library targets go through
|
|
# install(EXPORT), and even a PRIVATE link to an INTERFACE target lands in
|
|
# INTERFACE_LINK_LIBRARIES as $<LINK_ONLY:...>, at which point CMake refuses to
|
|
# generate the export set unless that target is exported too. A function has no
|
|
# such footprint. Visible in tests/ and fuzz/ because add_subdirectory()
|
|
# inherits functions defined before the call.
|
|
function(bravais_apply_build_flags target)
|
|
# Before the gate: strict C23 is what bravais targets, not a developer
|
|
# nicety.
|
|
#
|
|
# target_compile_features(c_std_23) alone is not enough to pin the dialect.
|
|
# It only states a *lower bound*, so when the compiler's own default already
|
|
# meets it CMake emits no -std flag at all - and C_EXTENSIONS is only
|
|
# consulted when a flag is emitted. GCC 16 defaults to gnu23, so it would
|
|
# silently build with extensions on while AppleClang (default C17) got
|
|
# -std=c2x. Setting C_STANDARD explicitly makes the dialect identical on
|
|
# both. The compile feature stays as well: it is what propagates the C23
|
|
# requirement to consumers, which these properties do not.
|
|
set_target_properties(${target} PROPERTIES
|
|
C_STANDARD 23
|
|
C_STANDARD_REQUIRED ON
|
|
C_EXTENSIONS OFF
|
|
)
|
|
if(NOT BRAVAIS_DEVELOPER_BUILD)
|
|
return()
|
|
endif()
|
|
# Our warning policy and our dialect are nobody else's business, so these
|
|
# stay PRIVATE and never reach INTERFACE_COMPILE_OPTIONS.
|
|
target_compile_options(${target} PRIVATE
|
|
"${BRAVAIS_C_WARNINGS}"
|
|
"$<${BRAVAIS_IS_DEBUGLIKE}:${BRAVAIS_C_FLAGS_DEBUGLIKE}>"
|
|
"$<$<NOT:${BRAVAIS_IS_DEBUGLIKE}>:${BRAVAIS_C_FLAGS_OPTIMIZED}>"
|
|
# The static analyzer runs only in its own configuration, and never
|
|
# alongside the sanitizers.
|
|
"$<$<CONFIG:Analyzer>:${BRAVAIS_ANALYZER_FLAGS}>"
|
|
# Strict is Debug with the warning set turned into a build gate.
|
|
"$<$<CONFIG:Strict>:${BRAVAIS_WERROR_FLAG}>"
|
|
)
|
|
|
|
# Instrumentation is different: it is not a policy, it is an ABI. An
|
|
# ASan-instrumented libbravais.a emits calls to __asan_* that only resolve
|
|
# if the *final* link line also carries -fsanitize=address, so a consumer of
|
|
# a Debug/Strict build must inherit the flags or it fails to link with a
|
|
# wall of undefined ___asan_* symbols.
|
|
#
|
|
# libbravais.so does resolve its own __asan_* at link time, so it would
|
|
# build either way -- but an ASan DSO loaded by an uninstrumented executable
|
|
# needs the runtime initialised first, which means LD_PRELOAD and a bad
|
|
# afternoon. Propagating the flags to consumers of the shared library too
|
|
# makes the sane case automatic and the insane case someone else's explicit
|
|
# choice. Hence PUBLIC on both library kinds, PRIVATE on executables, which
|
|
# are link leaves.
|
|
get_target_property(_target_type ${target} TYPE)
|
|
if(_target_type STREQUAL "STATIC_LIBRARY" OR _target_type STREQUAL "SHARED_LIBRARY")
|
|
set(_instrument_scope PUBLIC)
|
|
else()
|
|
set(_instrument_scope PRIVATE)
|
|
endif()
|
|
target_compile_options(${target} ${_instrument_scope} "${BRAVAIS_INSTRUMENT_COMPILE}")
|
|
target_link_options(${target} ${_instrument_scope} "${BRAVAIS_INSTRUMENT_LINK}")
|
|
if(BRAVAIS_ENABLE_LTO AND BRAVAIS_IPO_SUPPORTED)
|
|
set_target_properties(${target} PROPERTIES
|
|
INTERPROCEDURAL_OPTIMIZATION_RELEASE ON
|
|
INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON
|
|
INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL ON
|
|
)
|
|
endif()
|
|
endfunction()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dependencies
|
|
#
|
|
# libsodium: X25519, ChaCha20-Poly1305, BLAKE2b, HKDF, constant-time compare,
|
|
# sodium_memzero. Found via pkg-config; it ships no CMake package.
|
|
# liboqs: ML-KEM-768. Ships a CMake config package.
|
|
#
|
|
# Both are PRIVATE link dependencies. The public header exposes neither, so a
|
|
# consumer of libbravais never needs their include directories -- and the
|
|
# static-library case still links them transitively because CMake records
|
|
# PRIVATE deps of a static library as $<LINK_ONLY:...> in the export.
|
|
# ---------------------------------------------------------------------------
|
|
if(BRAVAIS_ENABLE_CRYPTO)
|
|
find_package(PkgConfig REQUIRED)
|
|
pkg_check_modules(SODIUM IMPORTED_TARGET libsodium>=1.0.18)
|
|
find_package(liboqs CONFIG)
|
|
|
|
if(NOT SODIUM_FOUND OR NOT liboqs_FOUND)
|
|
message(FATAL_ERROR
|
|
"libbravais needs libsodium (found: ${SODIUM_FOUND}) and liboqs (found: ${liboqs_FOUND}).\n"
|
|
" Debian/Ubuntu: apt install libsodium-dev, then build liboqs from source\n"
|
|
" macOS: brew install libsodium liboqs\n"
|
|
"To bootstrap util/ and wire/ without them, reconfigure with "
|
|
"-DBRAVAIS_ENABLE_CRYPTO=OFF.")
|
|
endif()
|
|
endif()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Sources
|
|
#
|
|
# One list, two targets. Uncomment each group as it lands; CMake errors at
|
|
# configure time on a source file that does not exist yet, which is a feature
|
|
# (it keeps this list honest) and an annoyance (it means editing here as you
|
|
# go). Order mirrors the build order in docs/ROADMAP.md: wire before crypto,
|
|
# crypto before proto.
|
|
# ---------------------------------------------------------------------------
|
|
set(GLOB_RECURSE BRAVAIS_SOURCES
|
|
src/util/*.c
|
|
src/wire/*.c
|
|
)
|
|
|
|
if(BRAVAIS_ENABLE_CRYPTO)
|
|
set (GLOB_RECURSE BRAVAIS_SOURCES
|
|
${BRAVAIS_SOURCES}
|
|
src/crypto/*.c
|
|
src/proto/*.c
|
|
)
|
|
endif()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Library targets
|
|
#
|
|
# Static and shared are built as two targets from one source list, which
|
|
# compiles every translation unit twice. The obvious alternative -- one OBJECT
|
|
# library consumed by both -- saves that, but $<TARGET_OBJECTS:> does not carry
|
|
# usage requirements, so the include directories, compile features and link
|
|
# dependencies would have to be restated on all three targets, and the object
|
|
# library has to join the export set for install(EXPORT) to generate at all.
|
|
# For a library this size, compiling twice is cheaper than that bookkeeping.
|
|
#
|
|
# Both get POSITION_INDEPENDENT_CODE: the shared library needs it, and the
|
|
# static one gets linked into bravaisd, which is built PIE.
|
|
# ---------------------------------------------------------------------------
|
|
set(BRAVAIS_LIB_TARGETS "")
|
|
|
|
if(BRAVAIS_BUILD_STATIC)
|
|
add_library(bravais_static STATIC ${BRAVAIS_SOURCES})
|
|
add_library(bravais::static ALIAS bravais_static)
|
|
list(APPEND BRAVAIS_LIB_TARGETS bravais_static)
|
|
set_target_properties(bravais_static PROPERTIES OUTPUT_NAME bravais)
|
|
endif()
|
|
|
|
if(BRAVAIS_BUILD_SHARED)
|
|
add_library(bravais_shared SHARED ${BRAVAIS_SOURCES})
|
|
add_library(bravais::shared ALIAS bravais_shared)
|
|
list(APPEND BRAVAIS_LIB_TARGETS bravais_shared)
|
|
# SOVERSION tracks the major version, which is 0 -- and 0.x explicitly
|
|
# promises nothing about ABI. Bump the minor on every ABI break while the
|
|
# major is 0, and start taking SOVERSION seriously at 1.0.
|
|
set_target_properties(bravais_shared PROPERTIES
|
|
OUTPUT_NAME bravais
|
|
VERSION ${PROJECT_VERSION}
|
|
SOVERSION ${PROJECT_VERSION_MAJOR}
|
|
)
|
|
endif()
|
|
|
|
# `bravais::bravais` is what consumers should link. It follows the usual CMake
|
|
# convention and resolves to the shared library when both are built.
|
|
if(BRAVAIS_BUILD_SHARED)
|
|
add_library(bravais::bravais ALIAS bravais_shared)
|
|
else()
|
|
add_library(bravais::bravais ALIAS bravais_static)
|
|
endif()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Symbol visibility
|
|
#
|
|
# Everything is hidden by default and the public API is opted in one symbol at
|
|
# a time with BRAVAIS_API, generated below. This is the one place where the
|
|
# static and shared builds genuinely differ, and getting it wrong is how a
|
|
# library ends up exporting its own bravais_internal_* helpers forever.
|
|
#
|
|
# bravais.h: BRAVAIS_API bravais_result_t bravais_conn_new(...);
|
|
# static consumers: BRAVAIS_STATIC_DEFINE makes BRAVAIS_API expand to nothing
|
|
#
|
|
# The generated header lands in the build tree and is installed alongside the
|
|
# hand-written ones.
|
|
# ---------------------------------------------------------------------------
|
|
include(GenerateExportHeader)
|
|
|
|
if(BRAVAIS_BUILD_SHARED)
|
|
set(BRAVAIS_EXPORT_HEADER_TARGET bravais_shared)
|
|
else()
|
|
set(BRAVAIS_EXPORT_HEADER_TARGET bravais_static)
|
|
endif()
|
|
|
|
generate_export_header(${BRAVAIS_EXPORT_HEADER_TARGET}
|
|
BASE_NAME BRAVAIS
|
|
EXPORT_MACRO_NAME BRAVAIS_API
|
|
NO_EXPORT_MACRO_NAME BRAVAIS_LOCAL
|
|
DEPRECATED_MACRO_NAME BRAVAIS_DEPRECATED
|
|
STATIC_DEFINE BRAVAIS_STATIC_DEFINE
|
|
EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/export.h.raw
|
|
)
|
|
|
|
# generate_export_header() cannot put anything above its include guard
|
|
# (CUSTOM_CONTENT_FROM_VARIABLE lands at the bottom), so it writes to a scratch
|
|
# file and the SPDX line is stamped on here. configure_file() only rewrites the
|
|
# real header when its content changes, so reconfiguring does not force a
|
|
# rebuild of everything that includes it.
|
|
file(READ ${CMAKE_CURRENT_BINARY_DIR}/export.h.raw _export_h)
|
|
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/export.h.stamped
|
|
"// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR MIT\n${_export_h}")
|
|
configure_file(${CMAKE_CURRENT_BINARY_DIR}/export.h.stamped
|
|
${CMAKE_CURRENT_BINARY_DIR}/include/bravais/export.h COPYONLY)
|
|
|
|
foreach(_target IN LISTS BRAVAIS_LIB_TARGETS)
|
|
set_target_properties(${_target} PROPERTIES
|
|
C_VISIBILITY_PRESET hidden
|
|
VISIBILITY_INLINES_HIDDEN ON
|
|
POSITION_INDEPENDENT_CODE ON
|
|
)
|
|
|
|
target_include_directories(${_target}
|
|
PUBLIC
|
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>
|
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
|
PRIVATE
|
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
|
)
|
|
|
|
# PUBLIC, so consumers of bravais::bravais are compiled as C23 too. This is
|
|
# exported as INTERFACE_COMPILE_FEATURES, which means consumers need CMake
|
|
# 3.21 or newer to understand it. bravais_apply_build_flags() turns
|
|
# extensions off, asking for -std=c2x rather than -std=gnu2x; the feature
|
|
# macros above put back everything strict mode hides.
|
|
target_compile_features(${_target} PUBLIC c_std_23)
|
|
|
|
# PRIVATE: only the library translation units need these.
|
|
target_compile_definitions(${_target} PRIVATE ${BRAVAIS_FEATURE_DEFS})
|
|
|
|
if(BRAVAIS_ENABLE_CRYPTO)
|
|
target_link_libraries(${_target} PRIVATE PkgConfig::SODIUM OQS::oqs)
|
|
endif()
|
|
|
|
bravais_apply_build_flags(${_target})
|
|
endforeach()
|
|
|
|
# A consumer linking the static library must see BRAVAIS_API as empty, or the
|
|
# declarations carry a dllimport/visibility attribute that does not match what
|
|
# was compiled. PUBLIC so it reaches them through the export.
|
|
if(BRAVAIS_BUILD_STATIC)
|
|
target_compile_definitions(bravais_static PUBLIC BRAVAIS_STATIC_DEFINE)
|
|
endif()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Optional subdirectories
|
|
# ---------------------------------------------------------------------------
|
|
if(BRAVAIS_BUILD_EXAMPLES)
|
|
add_subdirectory(examples)
|
|
endif()
|
|
|
|
if(BRAVAIS_BUILD_TESTS)
|
|
enable_testing()
|
|
add_subdirectory(tests)
|
|
endif()
|
|
|
|
# Fuzzers link the static library so libFuzzer's -fsanitize=fuzzer sees every
|
|
# translation unit's coverage instrumentation. They need Clang; GCC has no
|
|
# libFuzzer.
|
|
if(BRAVAIS_BUILD_FUZZERS)
|
|
if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
|
|
message(FATAL_ERROR
|
|
"BRAVAIS_BUILD_FUZZERS needs Clang (libFuzzer); this is ${CMAKE_C_COMPILER_ID}. "
|
|
"Reconfigure with -DCMAKE_C_COMPILER=clang.")
|
|
endif()
|
|
if(NOT BRAVAIS_BUILD_STATIC)
|
|
message(FATAL_ERROR "BRAVAIS_BUILD_FUZZERS needs BRAVAIS_BUILD_STATIC=ON.")
|
|
endif()
|
|
add_subdirectory(fuzz)
|
|
endif()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Install / export (skipped when consumed via add_subdirectory()).
|
|
# ---------------------------------------------------------------------------
|
|
if(PROJECT_IS_TOP_LEVEL)
|
|
include(GNUInstallDirs)
|
|
include(CMakePackageConfigHelpers)
|
|
|
|
install(TARGETS ${BRAVAIS_LIB_TARGETS}
|
|
EXPORT bravaisTargets
|
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
|
)
|
|
|
|
# Hand-written public headers.
|
|
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
|
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
|
FILES_MATCHING PATTERN "*.h"
|
|
)
|
|
|
|
# ...and the generated one.
|
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/bravais/export.h
|
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/bravais
|
|
)
|
|
|
|
install(EXPORT bravaisTargets
|
|
FILE bravaisTargets.cmake
|
|
NAMESPACE bravais::
|
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/bravais
|
|
)
|
|
|
|
configure_package_config_file(
|
|
${CMAKE_CURRENT_SOURCE_DIR}/cmake/bravaisConfig.cmake.in
|
|
${CMAKE_CURRENT_BINARY_DIR}/bravaisConfig.cmake
|
|
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/bravais
|
|
)
|
|
|
|
write_basic_package_version_file(
|
|
${CMAKE_CURRENT_BINARY_DIR}/bravaisConfigVersion.cmake
|
|
VERSION ${PROJECT_VERSION}
|
|
COMPATIBILITY SameMajorVersion
|
|
)
|
|
|
|
install(FILES
|
|
${CMAKE_CURRENT_BINARY_DIR}/bravaisConfig.cmake
|
|
${CMAKE_CURRENT_BINARY_DIR}/bravaisConfigVersion.cmake
|
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/bravais
|
|
)
|
|
|
|
# pkg-config, for the consumers who are not using CMake. Cheap to ship and
|
|
# the thing a distro packager will ask for first.
|
|
configure_file(
|
|
${CMAKE_CURRENT_SOURCE_DIR}/cmake/bravais.pc.in
|
|
${CMAKE_CURRENT_BINARY_DIR}/bravais.pc
|
|
@ONLY
|
|
)
|
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/bravais.pc
|
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
|
|
)
|
|
endif()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Configuration summary
|
|
# ---------------------------------------------------------------------------
|
|
if(BRAVAIS_DEVELOPER_BUILD)
|
|
message(STATUS "bravais: version ${PROJECT_VERSION}")
|
|
message(STATUS "bravais: build type ${CMAKE_BUILD_TYPE}")
|
|
message(STATUS "bravais: compiler ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}")
|
|
message(STATUS "bravais: C standard C23 (extensions off)")
|
|
message(STATUS "bravais: libraries static=${BRAVAIS_BUILD_STATIC} shared=${BRAVAIS_BUILD_SHARED}")
|
|
message(STATUS "bravais: crypto ${BRAVAIS_ENABLE_CRYPTO}")
|
|
message(STATUS "bravais: warnings-as-err ${BRAVAIS_WERROR} (always on in Strict)")
|
|
message(STATUS "bravais: sanitizers [Debug/Strict] ${BRAVAIS_SANITIZER_FLAGS}")
|
|
message(STATUS "bravais: static analyzer [Analyzer] ${BRAVAIS_ANALYZER_FLAGS}")
|
|
message(STATUS "bravais: hardening ${BRAVAIS_ENABLE_HARDENING}")
|
|
message(STATUS "bravais: LTO ${BRAVAIS_ENABLE_LTO}")
|
|
if(NOT BRAVAIS_ENABLE_CRYPTO)
|
|
message(STATUS "bravais: NOTE - crypto is OFF; crypto/ and proto/ are not built. "
|
|
"This is a bootstrap configuration, not a usable library.")
|
|
endif()
|
|
if(CMAKE_BUILD_TYPE STREQUAL "Analyzer" AND NOT BRAVAIS_ANALYZER_FLAGS)
|
|
message(STATUS "bravais: NOTE - Analyzer config has no static analyzer on "
|
|
"${CMAKE_C_COMPILER_ID}; reconfigure with -DCMAKE_C_COMPILER=gcc "
|
|
"or run scan-build over this build tree")
|
|
endif()
|
|
else()
|
|
message(STATUS "bravais: consumed as a subproject; developer flags off "
|
|
"(-DBRAVAIS_DEVELOPER_BUILD=ON to enable)")
|
|
endif() |