diff --git a/.clang-tidy b/.clang-tidy index 2ac840f..8fc8a77 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,4 +1,5 @@ -Checks: "-header-filter=.*,readability-identifier-naming,modernize-*,performance-*,bugprone-*,-modernize-use-trailing-return-type,-checks=-*" +Checks: "readability-identifier-naming,modernize-*,performance-*,bugprone-*,-modernize-use-trailing-return-type,-bugprone-exception-escape" +HeaderFilterRegex: ".*" WarningsAsErrors: "*" UseColor: true CheckOptions: diff --git a/.cmake-format b/.cmake-format deleted file mode 100644 index 58d94b8..0000000 --- a/.cmake-format +++ /dev/null @@ -1,4 +0,0 @@ -format: - tab_size: 2 - line_width: 80 - dangle_parens: true diff --git a/.gitignore b/.gitignore index 1a6cb74..dbceadf 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,6 @@ *.app # IDEs -.vscode/ archives/ # GDB diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..67a1c18 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "files.associations": { + ".clangd": "text", + ".clang-tidy": "yaml", + ".clang-format": "yaml" + }, + "clangd.path": "${workspaceFolder}/compile_commands_clangd_resolver.py", + "clangd.arguments": ["--query-driver=**/g++,**/*-g++", "--clang-tidy"] +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e6356d..828b514 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,168 +12,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -cmake_minimum_required(VERSION 3.28) - -# Generate compile commands for anyone using our libraries. -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(CMAKE_CXX_SCAN_FOR_MODULES ON) -# The rest are self explanatory... -set(CMAKE_COLOR_DIAGNOSTICS ON) -set(BUILD_UNIT_TESTS ON) -set(RUN_UNIT_TESTS ON) -set(BUILD_BENCHMARKS ON) -set(RUN_BENCHMARKS OFF) -set(USE_CLANG_TIDY ON) +cmake_minimum_required(VERSION 4.0) project(async_context LANGUAGES CXX) -# ============================================================================== -# Find clang-tidy -# ============================================================================== - -if(CMAKE_CROSSCOMPILING) - message(STATUS "Cross compiling, skipping clang-tidy") -elseif(LIBHAL_ENABLE_CLANG_TIDY) - find_program(CLANG_TIDY_EXE NAMES clang-tidy) - if(CLANG_TIDY_EXE) - message(STATUS "Clang-tidy found: ${CLANG_TIDY_EXE}") - set(CLANG_TIDY_CMD "${CLANG_TIDY_EXE};--config-file=${CMAKE_CURRENT_SOURCE_DIR}/.clang-tidy") - if(LIBHAL_CLANG_TIDY_FIX) - list(APPEND CLANG_TIDY_CMD "--fix") - endif() - set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_CMD}) - else() - message(STATUS "Clang-tidy not found - continuing without clang-tidy") - endif() -else() - message(STATUS "Clang-tidy checks disabled (use -o enable_clang_tidy=True to enable)") -endif() - -# ============================================================================== -# Library -# ============================================================================== - -add_library(async_context STATIC) -target_compile_features(async_context PUBLIC cxx_std_23) -target_sources(async_context PUBLIC - FILE_SET CXX_MODULES - TYPE CXX_MODULES - - FILES - modules/async_context.cppm -) -target_compile_options(async_context PRIVATE - -g - -Werror - -Wno-unused-command-line-argument - -Wall - -Wextra - -Wshadow - -fexceptions - -fno-rtti) - -install( - TARGETS async_context - EXPORT async_context_targets - FILE_SET CXX_MODULES DESTINATION "." - LIBRARY DESTINATION "lib" - ARCHIVE DESTINATION "lib" - CXX_MODULES_BMI DESTINATION "bmi" -) - -install( - EXPORT async_context_targets - FILE "async_context-config.cmake" - DESTINATION "lib/cmake" - CXX_MODULES_DIRECTORY "cxx-modules" - EXPORT_PACKAGE_DEPENDENCIES -) - -# Always run this custom target by making it depend on ALL -add_custom_target(copy_compile_commands ALL - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_BINARY_DIR}/compile_commands.json - ${CMAKE_SOURCE_DIR}/compile_commands.json - DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json) - -# ============================================================================== -# Unit testing -# ============================================================================== - -if(NOT CMAKE_CROSSCOMPILING AND BUILD_UNIT_TESTS) - find_package(ut REQUIRED) - message(STATUS "Building unit tests!") - add_executable(async_unit_test) - # Add module files - target_sources(async_unit_test PUBLIC - FILE_SET CXX_MODULES - TYPE CXX_MODULES - FILES +# libhal cmake utilities from libhal-cmake-util +# Source: https://github.com/libhal/libhal-cmake-util +find_package(LibhalCMakeUtil REQUIRED) + +libhal_project_init() +libhal_add_library(async_context MODULES modules/async_context.cppm) +libhal_apply_compile_options(async_context) +libhal_install_library(async_context NAMESPACE libhal) +libhal_add_tests(async_context + TEST_NAMES + basic_context + basics + blocked_by + cancel + exclusive_access + proxy + + MODULES tests/util.cppm - PRIVATE - tests/main.test.cpp - tests/basics.test.cpp - tests/basic_context.test.cpp - tests/blocked_by.test.cpp - tests/cancel.test.cpp - tests/exclusive_access.test.cpp - tests/proxy.test.cpp - ) - - target_compile_features(async_unit_test PUBLIC cxx_std_23) - target_compile_options(async_unit_test PRIVATE - -g - -Werror - -Wno-unused-command-line-argument - -Wall - -Wextra - -Wshadow - -fexceptions - -fsanitize=address - -fno-rtti) - - target_link_options(async_unit_test PRIVATE - -g - -fsanitize=address) - target_link_libraries(async_unit_test PRIVATE async_context Boost::ut) - - if(RUN_UNIT_TESTS) - add_custom_target(run_tests ALL DEPENDS async_unit_test - COMMAND async_unit_test) - endif() # RUN_UNIT_TESTS -else() - message(STATUS "Cross compiling, skipping unit test execution") -endif() # NOT CMAKE_CROSSCOMPILING AND BUILD_UNIT_TESTS +) # ============================================================================== # Benchmarking # ============================================================================== -if(NOT CMAKE_CROSSCOMPILING AND BUILD_BENCHMARKS) - find_package(benchmark REQUIRED) +if(NOT CMAKE_CROSSCOMPILING) message(STATUS "Building benchmarks tests!") - add_executable(async_benchmark) - target_sources(async_benchmark PRIVATE benchmarks/benchmark.cpp) - target_compile_features(async_benchmark PUBLIC cxx_std_23) - target_compile_options(async_benchmark PRIVATE - -g - -Werror - -Wno-unused-command-line-argument - -Wall - -Wextra - -Wshadow - -fexceptions - -fno-rtti) - - target_link_libraries(async_benchmark PRIVATE - async_context - benchmark::benchmark) - - if(RUN_BENCHMARKS) - add_custom_target(run_benchmark ALL DEPENDS async_benchmark - COMMAND async_benchmark) - endif() # RUN_BENCHMARKS + find_package(benchmark REQUIRED) + libhal_add_executable(benchmark SOURCES benchmarks/benchmark.cpp) + target_link_libraries(benchmark PRIVATE async_context benchmark::benchmark) else() message(STATUS "Cross compiling, skipping benchmarks") -endif() # if(NOT CMAKE_CROSSCOMPILING AND BUILD_BENCHMARKS) +endif() # if(NOT CMAKE_CROSSCOMPILING) diff --git a/compile_commands_clangd_resolver.py b/compile_commands_clangd_resolver.py new file mode 100755 index 0000000..2647114 --- /dev/null +++ b/compile_commands_clangd_resolver.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +# Copyright 2024 - 2025 Khalil Estell and the libhal contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Search for clangd binary associated with LLVM toolchain. + +This script searches for the clangd binary that matches the LLVM toolchain +specified in compile_commands.json. It's designed to help locate the correct +clangd version for use with language servers. + +Usage: + python find_clangd.py + +Requirements: + - compile_commands.json must exist in the project directory + - LLVM toolchain must be properly installed + +Returns: + Path to the clangd binary or exits with error code. +""" + +import json +import os +import sys +from pathlib import Path + + +def main(): + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # Find compile_commands.json + compile_commands = os.path.join(script_dir, "compile_commands.json") + if not os.path.isfile(compile_commands): + print( + f"ERROR: Could not find compile_commands.json in {script_dir}", file=sys.stderr) + sys.exit(1) + + # Extract clangd path from compile_commands.json + with open(compile_commands, "r") as f: + data = json.load(f) + + if not data: + print( + f"ERROR: Could not extract clangd path from {compile_commands}", file=sys.stderr) + sys.exit(1) + + # Get compiler path from first entry + first_entry = data[0] + command = first_entry.get("command", "") + if not command: + print( + f"ERROR: Could not extract clangd path from {compile_commands}", file=sys.stderr) + sys.exit(1) + + compiler_path = Path(command.split(' ')[0]) + clangd_path = compiler_path.with_name("clangd") + + # Verify clangd exists and is executable + if not (clangd_path.exists() and os.access(clangd_path, os.X_OK)): + print( + f"ERROR: Could not find clangd at {clangd_path}", file=sys.stderr) + sys.exit(1) + + # Execute clangd with all passed arguments + os.execv(clangd_path, [str(clangd_path)] + sys.argv[1:]) + + +if __name__ == "__main__": + main() diff --git a/conanfile.py b/conanfile.py index 26deab5..6d5b412 100644 --- a/conanfile.py +++ b/conanfile.py @@ -99,6 +99,7 @@ def validate(self): def build_requirements(self): self.tool_requires("cmake/[^4.0.0]") self.tool_requires("ninja/[^1.3.0]") + self.requires("libhal-cmake-util/[^5.0.3]") if str(self.settings.os) != "baremetal": self.test_requires("boost-ext-ut/2.3.1") self.test_requires("benchmark/1.9.4") diff --git a/cspell.json b/cspell.json index dd6f45b..4368289 100644 --- a/cspell.json +++ b/cspell.json @@ -45,7 +45,12 @@ "EABI", "NDEBUG", "malloc", - "uptr" + "uptr", + "doxygenclass", + "doxygenstruct", + "doxygenfunction", + "doxygenenum", + "alignof", ], "ignorePaths": [ "build/", diff --git a/modules/async_context.cppm b/modules/async_context.cppm index bd8ba0d..962e369 100644 --- a/modules/async_context.cppm +++ b/modules/async_context.cppm @@ -1396,7 +1396,11 @@ public: constexpr auto await_transform( std::chrono::duration p_sleep_duration) noexcept { + // NOLINTBEGIN(clang-analyzer-core.CallAndMessage): False positive + // clang-tidy assuming that m_context is uninitialized even though its + // initialized at construction. return m_context->block_by_time(p_sleep_duration); + // NOLINTEND(clang-analyzer-core.CallAndMessage) } /** @@ -1408,8 +1412,7 @@ public: */ constexpr std::suspend_always await_transform() noexcept { - m_context->block_by_io(); - return {}; + return m_context->block_by_io(); } /** @@ -1980,7 +1983,7 @@ public: return handle; } - constexpr monostate_or& await_resume() const + [[nodiscard]] constexpr monostate_or& await_resume() const requires(not std::is_void_v) { if (std::holds_alternative(m_operation.m_state)) [[likely]] { diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt index b8f1524..304de31 100644 --- a/test_package/CMakeLists.txt +++ b/test_package/CMakeLists.txt @@ -35,7 +35,7 @@ target_sources(${PROJECT_NAME} PUBLIC PRIVATE main.cpp ) target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_23) -target_link_libraries(${PROJECT_NAME} PRIVATE async_context) +target_link_libraries(${PROJECT_NAME} PRIVATE libhal::async_context) target_compile_options(${PROJECT_NAME} PRIVATE -g diff --git a/tests/basic_context.test.cpp b/tests/basic_context.test.cpp index f70c6c7..8e59655 100644 --- a/tests/basic_context.test.cpp +++ b/tests/basic_context.test.cpp @@ -1,12 +1,13 @@ #include +#include #include -#include import async_context; import test_utils; -boost::ut::suite<"basic_context"> basic_context = []() { +void basic_context() +{ using namespace boost::ut; static constexpr auto stack_size = 1024; @@ -68,7 +69,7 @@ boost::ut::suite<"basic_context"> basic_context = []() { }; auto co = [&step, &co2](async::context& p_ctx) -> async::future { step = 1; // skipped as the co2 will immediately start - co_await co2(p_ctx); + [[maybe_unused]] auto val = co_await co2(p_ctx); step = 4; co_return expected_return_value; }; @@ -119,7 +120,7 @@ boost::ut::suite<"basic_context"> basic_context = []() { auto co = [&step, &co2](async::context& p_ctx) -> async::future { step = 1; // skipped as the co2 will immediately start - auto val = co_await co2(p_ctx); + [[maybe_unused]] auto val = co_await co2(p_ctx); step = 2; co_return val + return_value2; }; @@ -164,7 +165,7 @@ boost::ut::suite<"basic_context"> basic_context = []() { auto co = [&step, &co2](async::context& p_ctx) -> async::future { step = 1; // skipped as the co2 will immediately start co_await 44ms; - auto val = co_await co2(p_ctx); + [[maybe_unused]] auto val = co_await co2(p_ctx); co_await 50ms; step = 2; co_return val + return_value2; @@ -190,3 +191,8 @@ boost::ut::suite<"basic_context"> basic_context = []() { expect(that % stack_size == ctx->capacity()); }; }; + +int main() +{ + basic_context(); +} diff --git a/tests/basics.test.cpp b/tests/basics.test.cpp index 042ccda..2446441 100644 --- a/tests/basics.test.cpp +++ b/tests/basics.test.cpp @@ -5,7 +5,8 @@ import async_context; import test_utils; -boost::ut::suite<"basics"> basics = []() { +void basics() +{ using namespace boost::ut; "sync return type void"_test = []() { @@ -171,7 +172,7 @@ boost::ut::suite<"basics"> basics = []() { }; auto co = [&step, &co2](async::context& p_ctx) -> async::future { step = 1; // skipped as the co2 will immediately start - co_await co2(p_ctx); + [[maybe_unused]] auto val = co_await co2(p_ctx); step = 4; co_return expected_return_value; }; @@ -250,3 +251,8 @@ boost::ut::suite<"basics"> basics = []() { expect(that % 2 == step); }; }; + +int main() +{ + basics(); +} diff --git a/tests/blocked_by.test.cpp b/tests/blocked_by.test.cpp index c851c93..40a9dcd 100644 --- a/tests/blocked_by.test.cpp +++ b/tests/blocked_by.test.cpp @@ -6,7 +6,8 @@ import async_context; import test_utils; -boost::ut::suite<"blocking_states"> blocking_states = []() { +void blocking_states() +{ using namespace boost::ut; using namespace std::chrono_literals; @@ -208,3 +209,8 @@ boost::ut::suite<"blocking_states"> blocking_states = []() { expect(that % 4 == step); }; }; + +int main() +{ + blocking_states(); +} diff --git a/tests/cancel.test.cpp b/tests/cancel.test.cpp index 6803bd4..cd133e4 100644 --- a/tests/cancel.test.cpp +++ b/tests/cancel.test.cpp @@ -6,7 +6,8 @@ import async_context; import test_utils; -boost::ut::suite<"cancellation_tests"> cancellation_tests = []() { +void cancellation_tests() +{ using namespace boost::ut; using namespace std::chrono_literals; @@ -252,3 +253,8 @@ boost::ut::suite<"cancellation_tests"> cancellation_tests = []() { expect(that % 0 == ctx.memory_used()); }; }; + +int main() +{ + cancellation_tests(); +} diff --git a/tests/exclusive_access.test.cpp b/tests/exclusive_access.test.cpp index 2a3d360..819342d 100644 --- a/tests/exclusive_access.test.cpp +++ b/tests/exclusive_access.test.cpp @@ -6,7 +6,8 @@ import async_context; import test_utils; -boost::ut::suite<"guards_tests"> guards_tests = []() { +void guards_tests() +{ using namespace boost::ut; using namespace std::chrono_literals; "Context Token"_test = []() { @@ -128,3 +129,8 @@ boost::ut::suite<"guards_tests"> guards_tests = []() { expect(that % 0 == ctx2.memory_used()); }; }; + +int main() +{ + guards_tests(); +} diff --git a/tests/main.test.cpp b/tests/main.test.cpp deleted file mode 100644 index 40e2379..0000000 --- a/tests/main.test.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2024 - 2025 Khalil Estell and the libhal contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace async { -} // namespace async - -int main() -{ -} diff --git a/tests/proxy.test.cpp b/tests/proxy.test.cpp index 7837415..fca99ae 100644 --- a/tests/proxy.test.cpp +++ b/tests/proxy.test.cpp @@ -6,7 +6,8 @@ import async_context; import test_utils; -boost::ut::suite<"proxy_tests"> proxy_tests = []() { +void proxy_tests() +{ using namespace boost::ut; using namespace std::chrono_literals; @@ -158,3 +159,8 @@ boost::ut::suite<"proxy_tests"> proxy_tests = []() { expect(that % 0 == ctx.memory_used()); }; }; + +int main() +{ + proxy_tests(); +}