Skip to content
Merged
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
28 changes: 12 additions & 16 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,35 +35,31 @@ jobs:
- name: Checkout code
uses: actions/checkout@v6

- name: Install mise
- name: Install Dependencies
run: |
curl https://mise.run | sh
mise settings experimental=true
mise trust
mise install

- name: Trust workspace
run: mise trust
- name: Configure
run: mise exec -- make configure

- name: Install dependencies
run: mise exec -- mise install
- name: Build
run: mise exec -- make build

- name: Configure project
run: mise exec -- make configure
- name: Test
run: mise exec -- make test

- name: Run formatting checks
- name: Formatting
run: mise exec -- make format

- name: Run lint
- name: Linting
run: mise exec -- make lint

- name: Run static analysis
- name: Static Analysis
run: mise exec -- make check

- name: Build project
run: mise exec -- make build

- name: Run tests
run: mise exec -- make test

test-amd64:
needs: test
runs-on: ubuntu-latest
Expand Down
6 changes: 5 additions & 1 deletion .vscode/c_cpp_properties.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
"configurations": [
{
"name": "Mac",
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
"includePath": ["${workspaceFolder}/**"],
"macFrameworkPath": [
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks"
],
"compilerPath": "/usr/bin/clang",
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
"cStandard": "c11",
"intelliSenseMode": "macos-clang-arm64"
}
Expand Down
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
},
"cSpell.words": [
"armv",
"BINDIR",
"CMSIS",
"cppcheck",
"ctest",
"Dryrun",
"eabi",
"endforeach",
"endfunction",
"INCLUDEDIR",
"libnewlib",
"noninteractive",
"tinyclib",
Expand Down
60 changes: 18 additions & 42 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,27 @@ cmake_minimum_required(VERSION 3.25)
project(tinyclib VERSION 0.3.1 LANGUAGES C)

# Settings and options
option(TL_BUILD_TESTS "Build tinyclib tests" ${PROJECT_IS_TOP_LEVEL})
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
option(TL_BUILD_TESTS "Build tinyclib tests" ${PROJECT_IS_TOP_LEVEL})

function(tl_configure_c_target target)
set_property(TARGET ${target} PROPERTY C_STANDARD 11)
set_property(TARGET ${target} PROPERTY C_STANDARD_REQUIRED ON)
set_property(TARGET ${target} PROPERTY C_EXTENSIONS OFF)
endfunction()

# Dependencies
include(CTest)
include(FetchContent)

# Sources
file(GLOB SOURCES CONFIGURE_DEPENDS src/*.c)

# Add the library (BUILD_SHARED_LIBS is handled by CMake)
add_library(tinyclib ${SOURCES})

# Set the C standard
set_property(TARGET tinyclib PROPERTY C_STANDARD 11)
set_property(TARGET tinyclib PROPERTY C_STANDARD_REQUIRED ON)
set_property(TARGET tinyclib PROPERTY C_EXTENSIONS OFF)
tl_configure_c_target(tinyclib)

# Set include directories for build and install
target_include_directories(
Expand All @@ -29,6 +32,14 @@ target_include_directories(
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
set_target_properties(tinyclib PROPERTIES POSITION_INDEPENDENT_CODE ON) # support shared libraries
add_library(tinyclib::tinyclib ALIAS tinyclib) # add a namespace alias for modern linking

# Tests
if(TL_BUILD_TESTS)
add_subdirectory(third_party/unity)
add_subdirectory(tests)
endif()

# Install targets and headers
include(GNUInstallDirs)
Expand Down Expand Up @@ -71,38 +82,3 @@ install(
NAMESPACE tinyclib::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/tinyclib
)

# Add a namespace alias for modern linking
add_library(tinyclib::tinyclib ALIAS tinyclib)

# Support shared libraries
set_target_properties(tinyclib PROPERTIES POSITION_INDEPENDENT_CODE ON)

# Build tests
if(TL_BUILD_TESTS)
include(CTest)
include(FetchContent)
set(TEST_TARGETS
tl_app_test
tl_config_test
tl_debug_test
tl_error_test
tl_flag_test
tl_test_test
)

# FetchContent for Unity testing framework
FetchContent_Declare(
Unity
GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git
GIT_TAG v2.6.1
)
FetchContent_MakeAvailable(Unity)

enable_testing()
foreach(t IN LISTS TEST_TARGETS)
add_executable(${t} tests/unit/${t}.c)
target_link_libraries(${t} unity tinyclib)
add_test(NAME ${t} COMMAND ${t})
endforeach()
endif()
51 changes: 31 additions & 20 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,59 +1,70 @@
.DEFAULT_GOAL := help

BUILD_DIR := build
CLANG_FORMAT := $(shell if command -v clang-format >/dev/null 2>&1; then echo clang-format; fi)
CLANG_TIDY := $(shell if command -v clang-tidy >/dev/null 2>&1; then echo clang-tidy; fi)
CPPCHECK := $(shell if command -v cppcheck >/dev/null 2>&1; then echo cppcheck; fi)
CLANG_TIDY_EXTRA_ARGS := $(shell if [ "$$(uname)" = "Darwin" ]; then echo "--extra-arg=--sysroot=$$(xcrun --show-sdk-path)"; fi)

SRC_FILES := src/*.c include/*.h
TEST_FILES := tests/unit/*.c
EXAMPLE_FILES := $(wildcard examples/*/*.c)
ALL_FILES := $(SRC_FILES) $(TEST_FILES) $(EXAMPLE_FILES)
CMD_FILES := $(wildcard cmd/*/*.c cmd/*/*.h)
ALL_FILES := $(SRC_FILES) $(TEST_FILES) $(CMD_FILES)

PRESET ?= default
JOBS ?= 4
PROJECT_DIR := $(CURDIR)
BUILD_ROOT := build
PRESET_BUILD_DIR := $(if $(filter default,$(PRESET)),$(BUILD_ROOT),$(BUILD_ROOT)/$(PRESET))

.PHONY: help install build clean clean-bin test format lint check check-all fix
.PHONY: help configure build clean clean-bin clean-cache test format lint check check-all fix

help: ## Show available make targets
@echo "Usage: make <target>"
@echo ""
@echo "Targets:"
@awk 'BEGIN {FS = ":.*## "} /^[a-zA-Z_-]+:.*## / {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST)

configure: ## Configure cmake
configure: ## Configure cmake (use PRESET=release for release mode)
cmake --preset $(PRESET)

build: ## Build the project
@test -d "$(BUILD_DIR)" || cmake --preset $(PRESET)
cmake --build --preset $(PRESET)
build: ## Build the project (use PRESET=release for release mode)
cmake --preset $(PRESET)
cmake --build --preset $(PRESET) -j $(JOBS)

clean: ## Remove build directories
@test -n "$(PROJECT_DIR)" && [ "$(PROJECT_DIR)" != "/" ]
rm -rf "$(PROJECT_DIR)/$(BUILD_ROOT)"

clean: ## Remove build directory
@test -n "$(CURDIR)" && [ "$(CURDIR)" != "/" ]
rm -rf "$(CURDIR)/$(BUILD_DIR)"
clean-bin: ## Remove built binaries for the selected preset
@test -n "$(PROJECT_DIR)" && [ "$(PROJECT_DIR)" != "/" ]
@test -d "$(PROJECT_DIR)/$(PRESET_BUILD_DIR)/cmd/bin" || exit 0
find "$(PROJECT_DIR)/$(PRESET_BUILD_DIR)/cmd/bin" -mindepth 1 -delete

clean-bin: ## Remove built binaries from the build directory
@test -n "$(CURDIR)" && [ "$(CURDIR)" != "/" ]
@test -d "$(CURDIR)/$(BUILD_DIR)/bin" || exit 0
find "$(CURDIR)/$(BUILD_DIR)/bin" -mindepth 1 -delete
clean-cache: ## Remove CMake cache for the selected preset
@test -n "$(PROJECT_DIR)" && [ "$(PROJECT_DIR)" != "/" ]
rm -f "$(PROJECT_DIR)/$(PRESET_BUILD_DIR)/CMakeCache.txt"

test: ## Run tests
ctest --preset default
@test -f "$(PROJECT_DIR)/$(PRESET_BUILD_DIR)/CMakeCache.txt" || cmake --preset $(PRESET)
cmake --build --preset $(PRESET) -j $(JOBS)
ctest --preset $(PRESET)

format: ## Check code formatting
@test -n "$(CLANG_FORMAT)" || { echo "error: clang-format not found"; exit 1; }
$(CLANG_FORMAT) --dry-run --Werror $(ALL_FILES) --verbose

lint: ## Check code linting
@test -n "$(CLANG_TIDY)" || { echo "error: clang-tidy not found"; exit 1; }
$(CLANG_TIDY) -p $(BUILD_DIR) $(CLANG_TIDY_EXTRA_ARGS) \
--header-filter="^$(CURDIR)/(src|include|tests)/" src/*.c tests/unit/*.c
@test -f "$(PRESET_BUILD_DIR)/compile_commands.json" || cmake --preset default
$(CLANG_TIDY) --config-file=$(PROJECT_DIR)/.clang-tidy -p $(PRESET_BUILD_DIR) $(CLANG_TIDY_EXTRA_ARGS) \
--header-filter="^$(PROJECT_DIR)/(src|include|tests)/" src/*.c tests/unit/*.c

check: ## Static analysis
@test -n "$(CPPCHECK)" || { echo "error: cppcheck not found"; exit 1; }
@test -f "$(PRESET_BUILD_DIR)/compile_commands.json" || cmake --preset default
$(CPPCHECK) --enable=warning,style,performance,portability --error-exitcode=1 \
--check-level=exhaustive --project=$(BUILD_DIR)/compile_commands.json \
--suppress=missingIncludeSystem -i$(BUILD_DIR)
--check-level=exhaustive --project=$(PRESET_BUILD_DIR)/compile_commands.json \
--suppress=missingIncludeSystem -i$(PRESET_BUILD_DIR)

check-all: test format lint check ## Run all checks

Expand Down
9 changes: 9 additions & 0 deletions include/tl_flag.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ size_t tl_count_flag(const char *flag);
*/
const char *tl_get_flag_at(const char *flag, size_t index);

/**
* @brief Looks up a specific positional argument by value.
*
* @param value The positional value to look up.
*
* @return true if the positional is found, false otherwise.
*/
bool tl_lookup_positional(const char *value);

/**
* @brief Returns the number of positional arguments.
*
Expand Down
12 changes: 12 additions & 0 deletions src/tl_flag.c
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,18 @@ const char *tl_get_flag_at(const char *flag, size_t index) {
return NULL;
}

bool tl_lookup_positional(const char *value) {
if (!value) {
return false;
}
for (size_t i = 0; i < positional_count; i++) {
if (strcmp(positionals[i], value) == 0) {
return true;
}
}
return false;
}

size_t tl_count_positional(void) {
return positional_count;
}
Expand Down
20 changes: 20 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
set(TEST_BIN_DIR "${PROJECT_BINARY_DIR}/tests/bin")

set(UNIT_TEST_TARGETS
app_test
config_test
debug_test
error_test
flag_test
test_test
)

foreach(test_target IN LISTS UNIT_TEST_TARGETS)
string(REGEX REPLACE "_test$" "" test_name "${test_target}")
set(test_binary "tl_${test_name}")
add_executable(${test_binary} unit/${test_target}.c)
target_link_libraries(${test_binary} PRIVATE tinyclib unity)
target_include_directories(${test_binary} PRIVATE ${PROJECT_SOURCE_DIR}/src)
set_target_properties(${test_binary} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TEST_BIN_DIR})
add_test(NAME ${test_binary} COMMAND ${test_binary})
endforeach()
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
41 changes: 41 additions & 0 deletions tests/unit/tl_flag_test.c → tests/unit/flag_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,42 @@ static void test_tl_parse_args_null_argv(void) {
TEST_ASSERT_FALSE(tl_lookup_flag("--anything"));
}

static void test_tl_lookup_positional(void) {
char *argv[] = {"program", "serve", "-f", "file"};
tl_parse_args(4, argv);
TEST_ASSERT_TRUE(tl_lookup_positional("serve"));
TEST_ASSERT_FALSE(tl_lookup_positional("missing"));
}

static void test_tl_lookup_positional_multiple(void) {
char *argv[] = {"program", "remote", "add", "origin", "--verbose"};
tl_parse_args(5, argv);
TEST_ASSERT_TRUE(tl_lookup_positional("remote"));
TEST_ASSERT_TRUE(tl_lookup_positional("add"));
TEST_ASSERT_TRUE(tl_lookup_positional("origin"));
TEST_ASSERT_FALSE(tl_lookup_positional("--verbose"));
}

static void test_tl_lookup_positional_after_terminator(void) {
char *argv[] = {"program", "cmd", "--", "--foo", "bar"};
tl_parse_args(5, argv);
TEST_ASSERT_TRUE(tl_lookup_positional("cmd"));
TEST_ASSERT_TRUE(tl_lookup_positional("--foo"));
TEST_ASSERT_TRUE(tl_lookup_positional("bar"));
}

static void test_tl_lookup_positional_none(void) {
char *argv[] = {"program", "--flag"};
tl_parse_args(2, argv);
TEST_ASSERT_FALSE(tl_lookup_positional("anything"));
}

static void test_tl_lookup_positional_null(void) {
char *argv[] = {"program", "cmd"};
tl_parse_args(2, argv);
TEST_ASSERT_FALSE(tl_lookup_positional(NULL));
}

int main(void) {
UNITY_BEGIN();

Expand Down Expand Up @@ -528,6 +564,11 @@ int main(void) {
RUN_TEST(test_tl_parse_line_double_backslash);
RUN_TEST(test_tl_negative_number_value);
RUN_TEST(test_tl_parse_args_null_argv);
RUN_TEST(test_tl_lookup_positional);
RUN_TEST(test_tl_lookup_positional_multiple);
RUN_TEST(test_tl_lookup_positional_after_terminator);
RUN_TEST(test_tl_lookup_positional_none);
RUN_TEST(test_tl_lookup_positional_null);

return UNITY_END();
}
File renamed without changes.
8 changes: 8 additions & 0 deletions third_party/unity/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
include(FetchContent)

FetchContent_Declare(
Unity
GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git
GIT_TAG cbcd08fa7de711053a3deec6339ee89cad5d2697 # v2.6.1
)
FetchContent_MakeAvailable(Unity)