diff --git a/.clang-format b/.clang-format index 554a685..cede7d5 100644 --- a/.clang-format +++ b/.clang-format @@ -6,4 +6,5 @@ BreakBeforeBraces: Attach AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline -ColumnLimit: 0 +ColumnLimit: 120 +AlignEscapedNewlines: Left diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..8f5b23d --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,11 @@ +Checks: > + clang-analyzer-*, + bugprone-*, + misc-*, + readability-*, + -readability-magic-numbers, + -readability-identifier-length, + -misc-unused-parameters, + -misc-include-cleaner + +HeaderFilterRegex: 'src/.*' diff --git a/.gitignore b/.gitignore index 3412e37..5ad959c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,14 @@ .DS_Store -*.o -.so -*.dylib -build +*log*.txt +/tmp +/build + +# VSCode .vscode/* !.vscode/c_cpp_properties.json +!.vscode/extensions.json !.vscode/launch.json !.vscode/settings.json !.vscode/tasks.json + diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..cbd449f --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,12 @@ +{ + "configurations": [ + { + "name": "Mac", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "compilerPath": "/usr/bin/clang", + "cStandard": "c11", + "intelliSenseMode": "macos-clang-arm64" + } + ], + "version": 4 +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c03d5b9 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index b35902f..69aa14d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,18 +6,28 @@ "editor.formatOnSave": true, "editor.defaultFormatter": "ms-vscode.cpptools" }, + "[json]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, + "[markdown]": { + "editor.defaultFormatter": "denoland.vscode-deno", + "editor.formatOnSave": true + }, + "files.associations": { + "*.jsonl": "json", + ".clang-format": "yaml", + }, "cSpell.words": [ "armv", "CMSIS", + "cppcheck", "ctest", "Dryrun", "eabi", "libnewlib", "noninteractive", "tinyclib", - "trunc" + "trunc", + "xcrun" ], - "files.associations": { - ".clang-format": "yaml", - } } diff --git a/CHANGELOG.md b/CHANGELOG.md index f0fcfd7..ba8c8be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog -See https://common-changelog.org for commit guidelines. +See https://common-changelog.org for commit guidelines and https://semver.org +for versioning. ## v0.1.1 - 2025-04-27 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a603cf..8f4e4c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,19 +1,22 @@ cmake_minimum_required(VERSION 3.25) -project(tinyclib VERSION 0.1.0 LANGUAGES C) +project(tinyclib VERSION 0.1.1 LANGUAGES C) -# Set the output directories +# Settings +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) +# Dependencies +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 to C11 for now +# 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) @@ -81,22 +84,22 @@ if(BUILD_TESTS) # FetchContent for Unity testing framework FetchContent_Declare( - Unity - GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git - GIT_TAG v2.6.1 + Unity + GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git + GIT_TAG v2.6.1 ) FetchContent_MakeAvailable(Unity) FetchContent_GetProperties(Unity) if(NOT TARGET Unity) - add_library(Unity STATIC ${unity_SOURCE_DIR}/src/unity.c) + add_library(Unity STATIC ${unity_SOURCE_DIR}/src/unity.c) endif() target_include_directories(Unity PUBLIC ${unity_SOURCE_DIR}/src) enable_testing() foreach(t app config debug error test) add_executable(tl_${t}_test tests/arch/generic/tl_${t}_test.c) - target_link_libraries(tl_${t}_test Unity tinyclib) + target_link_libraries(tl_${t}_test unity tinyclib) add_test(NAME tl_${t}_test COMMAND tl_${t}_test) endforeach() endif() diff --git a/CMakePresets.json b/CMakePresets.json index e4b7105..183a981 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -11,6 +11,7 @@ "binaryDir": "${sourceDir}/build", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "BUILD_TESTS": "ON" } } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8c786dc --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +.PHONY: help install build clean test format lint check check-all fix + +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/arch/generic/*.c +ALL_FILES := $(SRC_FILES) $(TEST_FILES) + +help: ## Show available make targets + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf " %-15s %s\n", $$1, $$2}' + +configure: ## Configure cmake + cmake --preset default + +build: ## Build the project + cmake --build --preset default + +clean: ## Remove build directory + @test -n "$(CURDIR)" && [ "$(CURDIR)" != "/" ] + rm -rf "$(CURDIR)/$(BUILD_DIR)" + +test: ## Build and run tests + cmake --workflow --preset default + +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/arch/generic/*.c + +check: ## Static analysis + @test -n "$(CPPCHECK)" || { echo "error: cppcheck not found"; exit 1; } + $(CPPCHECK) --enable=warning,style,performance,portability --error-exitcode=1 \ + --project=$(BUILD_DIR)/compile_commands.json --suppress=missingIncludeSystem \ + -i$(BUILD_DIR) + +check-all: format lint check ## Run all checks + +fix: ## Fix code formatting and linting issues + @test -n "$(CLANG_FORMAT)" || { echo "error: clang-format not found"; exit 1; } + $(CLANG_FORMAT) -i $(ALL_FILES) diff --git a/README.md b/README.md index 2e2e9de..92460fb 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # tinyclib - A tiny C library -tinyclib is a tiny C library for building applications across various platforms, +tinyclib is a tiny C library for building applications across various platforms, from server-grade hardware to desktops, mobile, and embedded devices. ## Requirements - [Clang](https://clang.llvm.org/) or similar C compiler - [CMake](https://cmake.org/) +- [Ninja](https://github.com/ninja-build/ninja) + +See [mise.toml](mise.toml) for exact versions used. ### Recommended tools @@ -17,9 +20,8 @@ from server-grade hardware to desktops, mobile, and embedded devices. ## Installation ```shell -git clone https://github.com/devfacet/tinyclib.git -cd tinyclib/ -cmake --workflow --preset default +make configure +make build ``` ## Usage @@ -27,7 +29,7 @@ cmake --workflow --preset default ## Test ```shell -ctest --preset default +make test ``` ## Contributing @@ -36,5 +38,6 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) ## License -Licensed under The MIT License (MIT) -For the full copyright and license information, please view the LICENSE.txt file. +Licensed under The MIT License (MIT)\ +For the full copyright and license information, please view the LICENSE.txt +file. diff --git a/include/tl_config.h b/include/tl_config.h index 5a1edcf..4eaaf8f 100644 --- a/include/tl_config.h +++ b/include/tl_config.h @@ -22,7 +22,7 @@ * * @note Available for all ARM architectures. */ -#if defined(__ARM_ARCH) +#ifdef __ARM_ARCH #define TL_CMSIS_DSP_AVAILABLE 1 #else #define TL_CMSIS_DSP_AVAILABLE 0 diff --git a/include/tl_debug.h b/include/tl_debug.h index acbe839..e917b71 100644 --- a/include/tl_debug.h +++ b/include/tl_debug.h @@ -12,7 +12,7 @@ * @param level The debug level. * @param fmt The format string. * @param ... The arguments. - * + * * @return void */ #define TL_DEBUG_PRINT(level, fmt, ...) \ diff --git a/include/tl_error.h b/include/tl_error.h index cd54d10..c4bbdba 100644 --- a/include/tl_error.h +++ b/include/tl_error.h @@ -11,28 +11,28 @@ typedef enum { TL_ERROR_NONE = 0, // no error - TL_ERROR_INTERNAL = 10, // internal error - TL_ERROR_NOT_FOUND, // not found - TL_ERROR_NOT_READY, // not ready - TL_ERROR_NOT_IMPLEMENTED, // not implemented - TL_ERROR_NOT_SUPPORTED, // not supported - TL_ERROR_NOT_AVAILABLE, // not available - - TL_ERROR_TIMEOUT = 20, // timeout error - TL_ERROR_BUSY, // busy - TL_ERROR_IO, // I/O error - TL_ERROR_OUT_OF_RANGE, // out of range error - TL_ERROR_MEMORY_ALLOCATION, // memory allocation error - - TL_ERROR_INIT_FAILED = 30, // initialization error - TL_ERROR_ALREADY_INITIALIZED, // already initialized - TL_ERROR_NOT_INITIALIZED, // not initialized - TL_ERROR_INVALID_ARGUMENT, // invalid argument - TL_ERROR_INVALID_FUNCTION, // invalid function - TL_ERROR_INVALID_INSTANCE, // invalid instance - TL_ERROR_INVALID_SIZE, // invalid size - TL_ERROR_INVALID_TYPE, // invalid type - TL_ERROR_INVALID_VALUE, // invalid value + TL_ERROR_INTERNAL = 10, // internal error + TL_ERROR_NOT_FOUND = 11, // not found + TL_ERROR_NOT_READY = 12, // not ready + TL_ERROR_NOT_IMPLEMENTED = 13, // not implemented + TL_ERROR_NOT_SUPPORTED = 14, // not supported + TL_ERROR_NOT_AVAILABLE = 15, // not available + + TL_ERROR_TIMEOUT = 20, // timeout error + TL_ERROR_BUSY = 21, // busy + TL_ERROR_IO = 22, // I/O error + TL_ERROR_OUT_OF_RANGE = 23, // out of range error + TL_ERROR_MEMORY_ALLOCATION = 24, // memory allocation error + + TL_ERROR_INIT_FAILED = 30, // initialization error + TL_ERROR_ALREADY_INITIALIZED = 31, // already initialized + TL_ERROR_NOT_INITIALIZED = 32, // not initialized + TL_ERROR_INVALID_ARGUMENT = 33, // invalid argument + TL_ERROR_INVALID_FUNCTION = 34, // invalid function + TL_ERROR_INVALID_INSTANCE = 35, // invalid instance + TL_ERROR_INVALID_SIZE = 36, // invalid size + TL_ERROR_INVALID_TYPE = 37, // invalid type + TL_ERROR_INVALID_VALUE = 38, // invalid value } TLErrorCode; /** diff --git a/include/tl_test.h b/include/tl_test.h index 5d57a9f..93705f3 100644 --- a/include/tl_test.h +++ b/include/tl_test.h @@ -13,6 +13,6 @@ * * @return The difference between the two timespec structures in nanoseconds. */ -long long tl_timespec_diff_ns(struct timespec *start, struct timespec *end); +long long tl_timespec_diff_ns(const struct timespec *start, const struct timespec *end); #endif // TL_TEST_H diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..df13554 --- /dev/null +++ b/mise.toml @@ -0,0 +1,10 @@ +# mise settings set experimental true +# mise install + +[tools] +cmake = "4.3.0" +"conda:make" = "4.4.1" +ninja = "1.13.2" +"pipx:clang-format" = "latest" +"pipx:clang-tidy" = "latest" +"pipx:cppcheck" = "latest" diff --git a/src/tl_app.c b/src/tl_app.c index 8d46323..46fd3c0 100644 --- a/src/tl_app.c +++ b/src/tl_app.c @@ -13,7 +13,7 @@ static char **args = NULL; void tl_init_app(int argc, char *argv[]) { tl_parse_args(argc, argv); if (tl_get_flag("--debug-level")) { - tl_set_debug_level(atoi(tl_get_flag("--debug-level"))); + tl_set_debug_level((int)strtol(tl_get_flag("--debug-level"), NULL, 10)); } } @@ -25,7 +25,8 @@ void tl_parse_args(int argc, char *argv[]) { bool tl_lookup_flag(const char *flag) { for (int i = 1; i < arg_count; i++) { // If the argument starts with the flag and is followed by either '\0' or '=' then - if (strncmp(args[i], flag, strlen(flag)) == 0 && (args[i][strlen(flag)] == '\0' || args[i][strlen(flag)] == '=')) { + if (strncmp(args[i], flag, strlen(flag)) == 0 && + (args[i][strlen(flag)] == '\0' || args[i][strlen(flag)] == '=')) { return true; } } diff --git a/src/tl_error.c b/src/tl_error.c index f73c3d5..59fb3f1 100644 --- a/src/tl_error.c +++ b/src/tl_error.c @@ -7,14 +7,9 @@ #include void tl_error_set(TLError *error, TLErrorCode code, const char *message, ...) { - // Check and initialize error + // Ignore if error is NULL if (!error) { - error = malloc(sizeof(TLError)); - if (!error) { - return; - } - error->message = NULL; - error->message_size = 0; + return; } // Set error code and message diff --git a/src/tl_test.c b/src/tl_test.c index 41d96a6..dedad52 100644 --- a/src/tl_test.c +++ b/src/tl_test.c @@ -1,12 +1,10 @@ // See LICENSE.txt and CONTRIBUTING.md for details. -#define _POSIX_C_SOURCE 199309L - #include "tl_test.h" #include // TODO: Add tests -long long tl_timespec_diff_ns(struct timespec *start, struct timespec *end) { - return (end->tv_sec - start->tv_sec) * 1000000000LL + (end->tv_nsec - start->tv_nsec); +long long tl_timespec_diff_ns(const struct timespec *start, const struct timespec *end) { + return ((end->tv_sec - start->tv_sec) * 1000000000LL) + (end->tv_nsec - start->tv_nsec); } diff --git a/tests/arch/generic/tl_app_test.c b/tests/arch/generic/tl_app_test.c index 8c0c821..f9a5848 100644 --- a/tests/arch/generic/tl_app_test.c +++ b/tests/arch/generic/tl_app_test.c @@ -9,27 +9,27 @@ void tearDown(void) { // Teardown code if needed } -void test_tl_init_app(void) { +static void test_tl_init_app(void) { char *argv[] = {"program", "--debug-level=2"}; tl_init_app(2, argv); TEST_ASSERT_TRUE(tl_lookup_flag("--debug-level")); TEST_ASSERT_EQUAL_STRING("2", tl_get_flag("--debug-level")); } -void test_tl_parse_args(void) { +static void test_tl_parse_args(void) { char *argv[] = {"program", "--test-flag"}; tl_parse_args(2, argv); TEST_ASSERT_TRUE(tl_lookup_flag("--test-flag")); } -void test_tl_lookup_flag(void) { +static void test_tl_lookup_flag(void) { char *argv[] = {"program", "--test-flag"}; tl_init_app(2, argv); TEST_ASSERT_TRUE(tl_lookup_flag("--test-flag")); TEST_ASSERT_FALSE(tl_lookup_flag("--nonexistent-flag")); } -void test_tl_get_flag(void) { +static void test_tl_get_flag(void) { char *argv[] = {"program", "--key=value"}; tl_init_app(2, argv); TEST_ASSERT_EQUAL_STRING("value", tl_get_flag("--key")); diff --git a/tests/arch/generic/tl_config_test.c b/tests/arch/generic/tl_config_test.c index 43f4ae3..eca5664 100644 --- a/tests/arch/generic/tl_config_test.c +++ b/tests/arch/generic/tl_config_test.c @@ -9,17 +9,17 @@ void tearDown(void) { // Teardown code if needed } -void test_tl_get_debug_level(void) { +static void test_tl_get_debug_level(void) { tl_set_debug_level(3); TEST_ASSERT_EQUAL(3, tl_get_debug_level()); } -void test_tl_set_debug_level(void) { +static void test_tl_set_debug_level(void) { TEST_ASSERT_TRUE(tl_set_debug_level(5)); TEST_ASSERT_EQUAL(5, tl_get_debug_level()); } -void test_tl_neon_available(void) { +static void test_tl_neon_available(void) { #if TL_NEON_AVAILABLE TEST_ASSERT_TRUE(tl_neon_available()); #else @@ -27,7 +27,7 @@ void test_tl_neon_available(void) { #endif } -void test_tl_cmsis_dsp_available(void) { +static void test_tl_cmsis_dsp_available(void) { #if TL_CMSIS_DSP_AVAILABLE TEST_ASSERT_TRUE(tl_cmsis_dsp_available()); #else diff --git a/tests/arch/generic/tl_debug_test.c b/tests/arch/generic/tl_debug_test.c index db17ca1..c78132f 100644 --- a/tests/arch/generic/tl_debug_test.c +++ b/tests/arch/generic/tl_debug_test.c @@ -9,7 +9,7 @@ void tearDown(void) { // Teardown code if needed } -void test_tl_debug_functionality(void) { +static void test_tl_debug_functionality(void) { // Example test for tl_debug functionality TEST_ASSERT_TRUE(1); // Replace with actual tests } diff --git a/tests/arch/generic/tl_error_test.c b/tests/arch/generic/tl_error_test.c index e0d591f..13b8d71 100644 --- a/tests/arch/generic/tl_error_test.c +++ b/tests/arch/generic/tl_error_test.c @@ -11,33 +11,30 @@ void tearDown(void) { // Teardown code if needed } -void test_tl_error_set_with_fixed_message(void) { +static void test_tl_error_set_with_fixed_message(void) { TLError error = {0}; - tl_error_set(&error, 1, "fixed error message"); + tl_error_set(&error, TL_ERROR_INTERNAL, "fixed error message"); - TEST_ASSERT_EQUAL(1, error.code); + TEST_ASSERT_EQUAL(TL_ERROR_INTERNAL, error.code); TEST_ASSERT_NOT_NULL(error.message); TEST_ASSERT_EQUAL_STRING("fixed error message", error.message); free((void *)error.message); } -void test_tl_error_set_with_formatted_message(void) { +static void test_tl_error_set_with_formatted_message(void) { TLError error = {0}; - tl_error_set(&error, 2, "formatted error: %d", 42); + tl_error_set(&error, TL_ERROR_NOT_FOUND, "formatted error: %d", 42); - TEST_ASSERT_EQUAL(2, error.code); + TEST_ASSERT_EQUAL(TL_ERROR_NOT_FOUND, error.code); TEST_ASSERT_NOT_NULL(error.message); TEST_ASSERT_EQUAL_STRING("formatted error: 42", error.message); free((void *)error.message); } -void test_tl_error_set_with_null_error(void) { - tl_error_set(NULL, 3, "this should not crash"); - - TEST_ASSERT_EQUAL(3, 3); - TEST_ASSERT_EQUAL_STRING("this should not crash", "this should not crash"); +static void test_tl_error_set_with_null_error(void) { + tl_error_set(NULL, TL_ERROR_NOT_READY, "this should not crash"); } int main(void) { diff --git a/tests/arch/generic/tl_test_test.c b/tests/arch/generic/tl_test_test.c index b33625e..005493c 100644 --- a/tests/arch/generic/tl_test_test.c +++ b/tests/arch/generic/tl_test_test.c @@ -10,7 +10,7 @@ void tearDown(void) { // Teardown code if needed } -void test_tl_timespec_diff_ns(void) { +static void test_tl_timespec_diff_ns(void) { struct timespec start = {.tv_sec = 1, .tv_nsec = 500000000}; struct timespec end = {.tv_sec = 2, .tv_nsec = 200000000};