From 2d13c085bfa52208a7dbe4c25e6d76631733524c Mon Sep 17 00:00:00 2001 From: Matthew McGowan Date: Mon, 23 Feb 2026 06:46:09 -0800 Subject: [PATCH 1/5] build: Add macOS build support for unit tests Add conditional CMake configuration for macOS (APPLE): - Skip -m32 and Linux-specific linker flags - Define HAVE_STRLCPY/HAVE_STRLCAT (macOS provides these natively) - Exclude n_str.c (strlcpy/strlcat bundled implementations) - Suppress AppleClang-specific warnings in upstream code - Use -flat_namespace for dylib and test executables to enable FFF test fake symbol interposition All changes are guarded by if(APPLE)/if(NOT APPLE) so Linux CI behavior is completely unchanged. --- CMakeLists.txt | 65 +++++++++++++++++++++++++++++++++------------ test/CMakeLists.txt | 6 ++++- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 01bd3fa6..68d6bc58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,28 +56,59 @@ target_compile_options( -Werror -Og -ggdb - PUBLIC - -m32 - -mfpmath=sse - -msse2 ) +if(NOT APPLE) + target_compile_options( + note_c + PUBLIC + -m32 + -mfpmath=sse + -msse2 + ) +endif() target_include_directories( note_c PUBLIC ${NOTE_C_SRC_DIR} ) -target_link_directories( - note_c - PUBLIC - /lib32 - /usr/lib32 - /usr/lib/gcc/x86_64-linux-gnu/12/32 -) -target_link_options( - note_c - PUBLIC - -m32 - -Wl,-melf_i386 -) +if(NOT APPLE) + target_link_directories( + note_c + PUBLIC + /lib32 + /usr/lib32 + /usr/lib/gcc/x86_64-linux-gnu/12/32 + ) + target_link_options( + note_c + PUBLIC + -m32 + -Wl,-melf_i386 + ) +endif() +if(APPLE) + target_compile_definitions( + note_c + PUBLIC + HAVE_STRLCPY + HAVE_STRLCAT + ) + # Suppress warnings in upstream code that AppleClang treats as errors + target_compile_options( + note_c + PRIVATE + -Wno-deprecated-non-prototype + -Wno-unused-but-set-variable + -Wno-strict-prototypes + ) + # macOS provides strlcpy/strlcat natively; exclude the bundled implementations + set_source_files_properties(${NOTE_C_SRC_DIR}/n_str.c PROPERTIES HEADER_FILE_ONLY TRUE) + # Use flat namespace so test fakes (FFF) can interpose library symbols + target_link_options( + note_c + PRIVATE + -Wl,-flat_namespace + ) +endif() if(NOTE_C_LOW_MEM) target_compile_definitions( diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f4a0c38a..4873ae5c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -17,7 +17,7 @@ FetchContent_Declare( FetchContent_MakeAvailable(Catch2) # Add specific build flags for Catch2 -if(TARGET Catch2) +if(TARGET Catch2 AND NOT APPLE) target_compile_options(Catch2 PUBLIC -m32 @@ -47,6 +47,10 @@ macro(add_test TEST_NAME) PRIVATE -Og PRIVATE -ggdb ) + if(APPLE) + # Allow test fakes (FFF) to interpose symbols from the note_c dylib + target_link_options(${TEST_NAME} PRIVATE -Wl,-flat_namespace) + endif() list(APPEND TEST_TARGETS ${TEST_NAME}) From 1cdf7a1a1417135c5859f363784c45e5ea734a1e Mon Sep 17 00:00:00 2001 From: Matthew McGowan Date: Mon, 9 Mar 2026 10:51:44 -0700 Subject: [PATCH 2/5] build: Add macOS build support for unit tests Use check_symbol_exists() to detect system-provided strlcpy/strlcat (macOS, *BSD) and conditionally exclude n_str.c and define HAVE_STRLCPY/ HAVE_STRLCAT guards. This avoids collisions with the platform's fortified string macros that prevented compilation on macOS. Guard the 32-bit x86 compile/link flags behind a Linux x86 check instead of if(NOT APPLE), making the build portable to any non-x86 platform. Suppress two AppleClang warnings-as-errors in upstream code (-Wstrict-prototypes, -Wunused-but-set-variable). Add -Wl,-flat_namespace on macOS for both the library and test executables so FFF test fakes can interpose dylib symbols. --- CMakeLists.txt | 99 ++++++++++++++++++++++++--------------------- test/CMakeLists.txt | 4 +- 2 files changed, 54 insertions(+), 49 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68d6bc58..ed36cf3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,26 +27,48 @@ option(NOTE_C_SINGLE_PRECISION "Use single precision for JSON floating point num option(NOTE_C_HEARTBEAT_CALLBACK "Enable heartbeat callback support." OFF) set(NOTE_C_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +# Detect whether the system already provides strlcpy/strlcat (macOS, *BSD). +# When present, we skip the bundled implementations in n_str.c and define +# guards so note.h doesn't redeclare them (which collides with the platform's +# fortified macros). +include(CheckSymbolExists) +check_symbol_exists(strlcpy "string.h" HAVE_STRLCPY) +check_symbol_exists(strlcat "string.h" HAVE_STRLCAT) + add_library(note_c SHARED) -target_sources( - note_c - PRIVATE - ${NOTE_C_SRC_DIR}/n_atof.c - ${NOTE_C_SRC_DIR}/n_b64.c - ${NOTE_C_SRC_DIR}/n_cjson.c - ${NOTE_C_SRC_DIR}/n_cjson_helpers.c - ${NOTE_C_SRC_DIR}/n_cobs.c - ${NOTE_C_SRC_DIR}/n_const.c - ${NOTE_C_SRC_DIR}/n_ftoa.c - ${NOTE_C_SRC_DIR}/n_helpers.c - ${NOTE_C_SRC_DIR}/n_hooks.c - ${NOTE_C_SRC_DIR}/n_i2c.c - ${NOTE_C_SRC_DIR}/n_md5.c - ${NOTE_C_SRC_DIR}/n_printf.c - ${NOTE_C_SRC_DIR}/n_request.c - ${NOTE_C_SRC_DIR}/n_serial.c - ${NOTE_C_SRC_DIR}/n_str.c + +set(NOTE_C_SOURCES + ${NOTE_C_SRC_DIR}/n_atof.c + ${NOTE_C_SRC_DIR}/n_b64.c + ${NOTE_C_SRC_DIR}/n_cjson.c + ${NOTE_C_SRC_DIR}/n_cjson_helpers.c + ${NOTE_C_SRC_DIR}/n_cobs.c + ${NOTE_C_SRC_DIR}/n_const.c + ${NOTE_C_SRC_DIR}/n_ftoa.c + ${NOTE_C_SRC_DIR}/n_helpers.c + ${NOTE_C_SRC_DIR}/n_hooks.c + ${NOTE_C_SRC_DIR}/n_i2c.c + ${NOTE_C_SRC_DIR}/n_md5.c + ${NOTE_C_SRC_DIR}/n_printf.c + ${NOTE_C_SRC_DIR}/n_request.c + ${NOTE_C_SRC_DIR}/n_serial.c ) +# n_str.c provides weak strlcpy/strlcat. On systems that already have them +# the file won't compile because the platform headers define these as +# fortified macros that conflict with the function definitions. +if(NOT HAVE_STRLCPY OR NOT HAVE_STRLCAT) + list(APPEND NOTE_C_SOURCES ${NOTE_C_SRC_DIR}/n_str.c) +endif() +target_sources(note_c PRIVATE ${NOTE_C_SOURCES}) + +if(HAVE_STRLCPY) + target_compile_definitions(note_c PUBLIC HAVE_STRLCPY) +endif() +if(HAVE_STRLCAT) + target_compile_definitions(note_c PUBLIC HAVE_STRLCAT) +endif() + target_compile_options( note_c PRIVATE @@ -57,20 +79,23 @@ target_compile_options( -Og -ggdb ) -if(NOT APPLE) - target_compile_options( - note_c - PUBLIC - -m32 - -mfpmath=sse - -msse2 +# Suppress warnings in upstream code that AppleClang treats as errors. +if(CMAKE_C_COMPILER_ID MATCHES "AppleClang") + target_compile_options(note_c PRIVATE + -Wno-strict-prototypes + -Wno-unused-but-set-variable ) endif() +# The Linux CI runs tests in 32-bit mode. These flags are x86-specific and +# unavailable on Apple toolchains or non-x86 architectures. +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i[3-6]86") + target_compile_options(note_c PUBLIC -m32 -mfpmath=sse -msse2) +endif() target_include_directories( note_c PUBLIC ${NOTE_C_SRC_DIR} ) -if(NOT APPLE) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i[3-6]86") target_link_directories( note_c PUBLIC @@ -86,28 +111,8 @@ if(NOT APPLE) ) endif() if(APPLE) - target_compile_definitions( - note_c - PUBLIC - HAVE_STRLCPY - HAVE_STRLCAT - ) - # Suppress warnings in upstream code that AppleClang treats as errors - target_compile_options( - note_c - PRIVATE - -Wno-deprecated-non-prototype - -Wno-unused-but-set-variable - -Wno-strict-prototypes - ) - # macOS provides strlcpy/strlcat natively; exclude the bundled implementations - set_source_files_properties(${NOTE_C_SRC_DIR}/n_str.c PROPERTIES HEADER_FILE_ONLY TRUE) # Use flat namespace so test fakes (FFF) can interpose library symbols - target_link_options( - note_c - PRIVATE - -Wl,-flat_namespace - ) + target_link_options(note_c PRIVATE -Wl,-flat_namespace) endif() if(NOTE_C_LOW_MEM) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4873ae5c..460bbda8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -16,8 +16,8 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(Catch2) -# Add specific build flags for Catch2 -if(TARGET Catch2 AND NOT APPLE) +# Add specific build flags for Catch2 (32-bit mode for Linux x86 CI only) +if(TARGET Catch2 AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i[3-6]86") target_compile_options(Catch2 PUBLIC -m32 From ca24782266ea0e9c67a0325e03b068aaf440f95e Mon Sep 17 00:00:00 2001 From: Matthew McGowan Date: Mon, 9 Mar 2026 10:56:40 -0700 Subject: [PATCH 3/5] fix: Resolve AppleClang warnings-as-errors - n_ua.c: add void to NoteUserAgent() prototype to fix -Wstrict-prototypes (empty parens means unspecified args in C) - n_helpers.c: remove unused variables j and lastLengthCount that triggered -Wunused-but-set-variable With these fixes the -Wno-* suppressions added in the previous commit are no longer needed and are removed from CMakeLists.txt. --- CMakeLists.txt | 7 ------- n_helpers.c | 10 +--------- n_ua.c | 4 ++-- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ed36cf3e..67d05e11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,13 +79,6 @@ target_compile_options( -Og -ggdb ) -# Suppress warnings in upstream code that AppleClang treats as errors. -if(CMAKE_C_COMPILER_ID MATCHES "AppleClang") - target_compile_options(note_c PRIVATE - -Wno-strict-prototypes - -Wno-unused-but-set-variable - ) -endif() # The Linux CI runs tests in 32-bit mode. These flags are x86-specific and # unavailable on Apple toolchains or non-x86 architectures. if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i[3-6]86") diff --git a/n_helpers.c b/n_helpers.c index 3c02cd61..3a46c2e3 100644 --- a/n_helpers.c +++ b/n_helpers.c @@ -2403,7 +2403,7 @@ uint32_t NoteMemAvailable(void) objHeader *lastObj = NULL; static long int maxsize = 35000; for (long int i=maxsize; i>=(long int)sizeof(objHeader); i=i-sizeof(objHeader)) { - for (long int j=0;; j++) { + for (;;) { objHeader *thisObj; thisObj = (objHeader *) _Malloc(i); if (thisObj == NULL) { @@ -2416,16 +2416,8 @@ uint32_t NoteMemAvailable(void) } // Free the objects backwards - long int lastLength = 0; - long int lastLengthCount = 0; uint32_t total = 0; while (lastObj != NULL) { - if (lastObj->length != lastLength) { - lastLength = lastObj->length; - lastLengthCount = 1; - } else { - lastLengthCount++; - } objHeader *thisObj = lastObj; lastObj = lastObj->prev; total += thisObj->length; diff --git a/n_ua.c b/n_ua.c index 91815291..c270373b 100644 --- a/n_ua.c +++ b/n_ua.c @@ -92,9 +92,9 @@ __attribute__((weak)) void NoteUserAgentUpdate(J *ua) */ /**************************************************************************/ #if defined(_MSC_VER) -J *NoteUserAgent() +J *NoteUserAgent(void) #else -__attribute__((weak)) J *NoteUserAgent() +__attribute__((weak)) J *NoteUserAgent(void) #endif { From 79246117ed29827ab2bb0a8a406c9b3975e59604 Mon Sep 17 00:00:00 2001 From: Matthew McGowan Date: Mon, 9 Mar 2026 12:15:00 -0700 Subject: [PATCH 4/5] ci: Add macOS build verification job to CI pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `run_macos_unit_tests` job that configures and builds note-c with tests on macos-latest. Tests are not run via ctest because FFF symbol interposition does not fully work on macOS — even with -flat_namespace, intra-library calls are resolved at link time and cannot be faked. --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 207dc3ef..c845f41e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -195,6 +195,30 @@ jobs: run: | docker run --rm --volume $(pwd):/note-c/ --workdir /note-c/ --entrypoint ./scripts/run_cppcheck.sh ghcr.io/blues/note_c_ci:latest + run_macos_unit_tests: + runs-on: macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Configure + run: cmake -B build -DNOTE_C_BUILD_TESTS=ON + + - name: Build + run: cmake --build build --parallel $(sysctl -n hw.ncpu) + + # NOTE: ctest is not run here. A subset of tests that rely on FFF + # symbol interposition fail on macOS due to two-level namespace + # linking. The build step above verifies that all sources and tests + # compile and link successfully. + # + # Even with -Wl,-flat_namespace, intra-library calls within the + # note_c dylib are resolved at link time as direct calls, so FFF + # fakes in the test executable cannot interpose them. Fully fixing + # this would require linking note-c as a static library for tests + # or using a different mocking approach on macOS. + publish_ci_image: runs-on: ubuntu-latest # Make sure unit tests unit tests passed before publishing. From 868e001793792b423bf3ccf02be92ed3661bc5e2 Mon Sep 17 00:00:00 2001 From: Matthew McGowan Date: Mon, 9 Mar 2026 12:25:35 -0700 Subject: [PATCH 5/5] fix: resolve CI failures for strlcpy/strlcat on newer glibc - Add `strlcpy` and `strlcat` to the libc dependency whitelist so the `check_libc_dependencies` job passes when `n_str.c` is excluded on systems that provide these functions natively (glibc 2.38+). - Only add `n_str.c` to the lcov coverage exclude list when the file is actually compiled, avoiding an lcov error on unused patterns. --- scripts/check_libc_dependencies.sh | 2 ++ test/CMakeLists.txt | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/check_libc_dependencies.sh b/scripts/check_libc_dependencies.sh index f3c7fadc..498f3686 100755 --- a/scripts/check_libc_dependencies.sh +++ b/scripts/check_libc_dependencies.sh @@ -30,6 +30,8 @@ LIBC_WHITELIST=( "strchr" "strcmp" "strlen" + "strlcpy" # bundled in n_str.c; also available in glibc 2.38+ + "strlcat" # bundled in n_str.c; also available in glibc 2.38+ "strncmp" "strstr" "strtol" # required by atoi in NoteGenEnvInt diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 460bbda8..a74e6d23 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -243,8 +243,12 @@ if(NOTE_C_COVERAGE) # ourselves, so we don't care about coverage for them. set( EXCLUDE_FROM_COVERAGE - "n_atof.c;n_b64.c;n_cjson.c;n_ftoa.c;n_md5.c;n_str.c" + "n_atof.c;n_b64.c;n_cjson.c;n_ftoa.c;n_md5.c" ) + # n_str.c is only compiled when the system lacks strlcpy/strlcat. + if(NOT HAVE_STRLCPY OR NOT HAVE_STRLCAT) + list(APPEND EXCLUDE_FROM_COVERAGE "n_str.c") + endif() foreach(EXCLUDE_FILE ${EXCLUDE_FROM_COVERAGE}) string(APPEND LCOV_EXCLUDE "--exclude '*/${EXCLUDE_FILE}' ") endforeach()