Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,22 @@ jobs:
run: |
docker run --rm --volume $(pwd):/note-c/ --workdir /note-c/ --entrypoint ./scripts/check_libc_dependencies.sh ghcr.io/blues/note_c_ci:latest

run_integration_tests:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Configure add_subdirectory integration test
run: cmake -S test/integration -B build-integration

- name: Build add_subdirectory integration test
run: cmake --build build-integration --parallel $(nproc)

- name: Run add_subdirectory integration test
run: ctest --test-dir build-integration --output-on-failure

run_unit_tests:
runs-on: ubuntu-latest
if: ${{ always() }}
Expand Down Expand Up @@ -195,6 +211,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.
Expand Down
258 changes: 153 additions & 105 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.21)
cmake_policy(SET CMP0095 NEW)

if ("${CMAKE_BINARY_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}")
Expand All @@ -16,129 +16,177 @@ if(NOT EXISTS ${PROJECT_BINARY_DIR}/.gitignore)
endif()

option(NOTE_C_BUILD_DOCS "Build docs." OFF)
option(NOTE_C_BUILD_TESTS "Build tests." ON)
option(NOTE_C_BUILD_TESTS "Build tests." ${PROJECT_IS_TOP_LEVEL})
option(NOTE_C_COVERAGE "Compile for test NOTE_C_COVERAGE reporting." OFF)
option(NOTE_C_LOW_MEM "Build the library tailored for low memory usage." OFF)
option(NOTE_C_MEM_CHECK "Run tests with Valgrind." OFF)
option(NOTE_NODEBUG "Build the library without debug information." OFF)
option(NOTE_C_NO_LIBC "Build the library without linking against libc, generating errors for any undefined symbols." OFF)
option(NOTE_C_NO_LIBC "Audit libc dependencies by linking the note_c test/audit library without libc, generating errors for any undefined symbols." OFF)
option(NOTE_C_SHOW_MALLOC "Build the library with flags required to log memory usage." OFF)
option(NOTE_C_SINGLE_PRECISION "Use single precision for JSON floating point numbers." OFF)
option(NOTE_C_HEARTBEAT_CALLBACK "Enable heartbeat callback support." OFF)

# NOTE_C_NO_LIBC is a link-time undefined-symbol audit (see
# scripts/check_libc_dependencies.sh). It only has any effect on the shared
# `note_c` test/audit target, which is built when NOTE_C_BUILD_TESTS is ON. The
# static `note_c_lib` is archived with `ar` (no link step), so the flag would
# silently do nothing there. Fail loudly rather than appear to audit when it
# can't.
if(NOTE_C_NO_LIBC AND NOT NOTE_C_BUILD_TESTS)
message(FATAL_ERROR
"NOTE_C_NO_LIBC is only meaningful for the note_c shared test/audit "
"target. Enable NOTE_C_BUILD_TESTS, or leave NOTE_C_NO_LIBC disabled "
"when consuming note_c_lib.")
endif()

set(NOTE_C_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR})
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
)
target_compile_options(
note_c
PRIVATE
-Wall
-Wextra
-Wpedantic
-Werror
-Og
-ggdb
PUBLIC
-m32
-mfpmath=sse
-msse2
)
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(NOTE_C_LOW_MEM)
target_compile_definitions(
note_c
PUBLIC
NOTE_C_LOW_MEM
)
else()
# This file is empty if NOTE_C_LOW_MEM is defined, which leads to a warning
# about an empty translation unit, so we only add it to the build if
# NOTE_C_LOW_MEM is false.
target_sources(
note_c
PRIVATE
${NOTE_C_SRC_DIR}/n_ua.c
)
endif()
# 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)

if(NOTE_C_NO_LIBC)
target_link_options(
note_c
PRIVATE
-nostdlib
-nodefaultlibs
LINKER:--no-undefined
)
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()

if(NOTE_NODEBUG)
target_compile_definitions(
note_c
PUBLIC
NOTE_NODEBUG
)
endif()
# Apply the include path and compile-definition configuration shared by every
# note-c library target. Build- and test-specific settings (warnings, 32-bit
# cross-compile, FFF interposition, NOTE_C_TEST, NOTE_C_NO_LIBC) are applied at
# each target's own definition site, not here.
function(note_c_configure_common target)
target_include_directories(${target} PUBLIC ${NOTE_C_SRC_DIR})

if(NOTE_C_SHOW_MALLOC)
target_compile_definitions(
note_c
PUBLIC
NOTE_C_SHOW_MALLOC
)
endif()
if(HAVE_STRLCPY)
target_compile_definitions(${target} PUBLIC HAVE_STRLCPY)
endif()
if(HAVE_STRLCAT)
target_compile_definitions(${target} PUBLIC HAVE_STRLCAT)
endif()

if(NOTE_C_SINGLE_PRECISION)
target_compile_definitions(
note_c
PUBLIC
NOTE_C_SINGLE_PRECISION
)
endif()
if(NOTE_C_LOW_MEM)
target_compile_definitions(${target} PUBLIC NOTE_C_LOW_MEM)
else()
# n_ua.c is empty when NOTE_C_LOW_MEM is defined, which leads to an
# empty-translation-unit warning, so only compile it otherwise.
target_sources(${target} PRIVATE ${NOTE_C_SRC_DIR}/n_ua.c)
endif()

if(NOTE_C_HEARTBEAT_CALLBACK)
target_compile_definitions(
if(NOTE_NODEBUG)
target_compile_definitions(${target} PUBLIC NOTE_NODEBUG)
endif()
if(NOTE_C_SHOW_MALLOC)
target_compile_definitions(${target} PUBLIC NOTE_C_SHOW_MALLOC)
endif()
if(NOTE_C_SINGLE_PRECISION)
target_compile_definitions(${target} PUBLIC NOTE_C_SINGLE_PRECISION)
endif()
if(NOTE_C_HEARTBEAT_CALLBACK)
target_compile_definitions(${target} PUBLIC NOTE_C_HEARTBEAT_CALLBACK)
endif()
endfunction()

# ---------------------------------------------------------------------------
# note_c_lib — static library for downstream consumers.
#
# Projects that include note-c as a submodule can simply do:
#
# add_subdirectory(note-c)
# target_link_libraries(my_app PRIVATE note_c_lib)
#
# This target carries the include path and platform compile definitions
# (HAVE_STRLCPY, etc.) but no test flags, 32-bit cross-compile options,
# or other CI-specific settings.
# ---------------------------------------------------------------------------
add_library(note_c_lib STATIC ${NOTE_C_SOURCES})
note_c_configure_common(note_c_lib)

# NOTE_C_NO_LIBC is intentionally NOT applied to note_c_lib: it is a static
# archive, so target_link_options has no effect (no link step), and the audit
# is performed against the shared note_c target below.

# ---------------------------------------------------------------------------
# note_c — shared library used by the unit test harness.
#
# This target adds -Werror, 32-bit cross-compile flags (Linux CI), FFF
# interposition support (macOS), and the NOTE_C_TEST define. It is NOT
# intended for downstream consumption.
# ---------------------------------------------------------------------------
if(NOTE_C_BUILD_TESTS)
add_library(note_c SHARED ${NOTE_C_SOURCES})
note_c_configure_common(note_c)

target_compile_options(
note_c
PUBLIC
NOTE_C_HEARTBEAT_CALLBACK
PRIVATE
-Wall
-Wextra
-Wpedantic
-Werror
-Og
-ggdb
)
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()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i[3-6]86")
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)
# Use flat namespace so test fakes (FFF) can interpose library symbols
target_link_options(note_c PRIVATE -Wl,-flat_namespace)
endif()

# The libc dependency audit (scripts/check_libc_dependencies.sh) links this
# shared target without libc so the linker reports undefined symbols. This
# only works on a target that is actually linked, hence note_c, not the
# static note_c_lib.
if(NOTE_C_NO_LIBC)
target_link_options(
note_c
PRIVATE
-nostdlib
-nodefaultlibs
LINKER:--no-undefined
)
endif()

if(NOTE_C_BUILD_TESTS)
# Including CTest here rather than in test/CMakeLists.txt allows us to run
# ctest from the root build directory (e.g. build/ instead of build/test/).
# We also need to set MEMORYCHECK_COMMAND_OPTIONS before including this.
Expand Down
10 changes: 1 addition & 9 deletions n_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions n_ua.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
{

Expand Down
2 changes: 2 additions & 0 deletions scripts/check_libc_dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading