create initial structure + cmakelists (thanks claude)
This commit is contained in:
+765
@@ -0,0 +1,765 @@
|
||||
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}/include/bravais/export.h
|
||||
)
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user