diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1cd0d8d..3096bd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,4 +24,7 @@ on: jobs: package_and_upload_all_check: uses: libhal/ci/.github/workflows/package_and_upload_all.yml@5.x.y + with: + modules_support_needed: true + coroutine_support_needed: true secrets: inherit diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9f7ae94..21db97c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -26,6 +26,8 @@ jobs: with: version: ${{ github.ref_name }} remote_url: https://libhal.jfrog.io/artifactory/api/conan/trunk-conan + modules_support_needed: true + coroutine_support_needed: true secrets: conan_remote_password: ${{ secrets.JFROG_LIBHAL_TRUNK_ID_TOKEN }} conan_remote_user: ${{ secrets.JFROG_LIBHAL_TRUNK_ID_TOKEN_USER }} diff --git a/CMakeLists.txt b/CMakeLists.txt index e63f440..1d5a738 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,10 +48,10 @@ install( install( EXPORT async_context_targets - FILE "libasync_context-config.cmake" - NAMESPACE libasync_context:: + FILE "async_context-config.cmake" DESTINATION "lib/cmake" CXX_MODULES_DIRECTORY "cxx-modules" + EXPORT_PACKAGE_DEPENDENCIES ) # ============================================================================== @@ -63,6 +63,7 @@ find_package(ut REQUIRED) if(CMAKE_CROSSCOMPILING) message(STATUS "Cross compiling, skipping unit test execution") else() + message(STATUS "Building unit tests!") add_executable(async_unit_test) # Add module files target_sources(async_unit_test @@ -75,7 +76,6 @@ else() target_compile_options(async_unit_test PRIVATE -g) target_link_libraries(async_unit_test PRIVATE async_context Boost::ut) - message(STATUS "Executing unit tests!") add_custom_target(run_tests ALL DEPENDS async_unit_test COMMAND async_unit_test) endif() diff --git a/conanfile.py b/conanfile.py index c8b909d..ec432ef 100644 --- a/conanfile.py +++ b/conanfile.py @@ -33,7 +33,7 @@ class async_context_conan(ConanFile): description = ("Implementation of C++20 coroutines targeting embedded system by eliminating the usage of the global heap and providing a 'context' which contains a coroutine stack frame and other useful utilities for scheduling.") topics = ("async", "coroutines", "stack", "scheduling", "scheduler") settings = "compiler", "build_type", "os", "arch" - exports_sources = "modules/*", "tests/*", "CMakeLists.txt", "LICENSE" + exports_sources = "modules/*", "tests/*", "CMakeLists.txt", "*.cmake.in", "LICENSE" package_type = "static-library" shared = False @@ -84,12 +84,12 @@ def validate(self): self._validate_compiler_version() def build_requirements(self): - # Provides CMake, Ninja, & toolchain scripts for enabling modules - self.tool_requires("cmake-modules-toolchain/1.0.2") + self.tool_requires("cmake/[^4.0.0]") + self.tool_requires("ninja/[^1.3.0]") self.test_requires("boost-ext-ut/2.3.1") def requirements(self): - self.requires("strong_ptr/0.0.0") + self.requires("strong_ptr/0.0.2") def layout(self): cmake_layout(self) diff --git a/modules/async_context.cppm b/modules/async_context.cppm index a51f032..dd75104 100644 --- a/modules/async_context.cppm +++ b/modules/async_context.cppm @@ -20,7 +20,6 @@ module; #include #include #include -#include #include #include #include @@ -32,14 +31,13 @@ export module async_context; export import strong_ptr; -export namespace async::inline v0 { +namespace async::inline v0 { -using u8 = std::uint8_t; -using byte = std::uint8_t; -using usize = std::size_t; +export using u8 = std::uint8_t; +export using byte = std::uint8_t; +export using usize = std::size_t; -enum class blocked_by : u8 -{ +export enum class blocked_by : u8 { /// Not blocked by anything, ready to run! nothing = 0, @@ -63,7 +61,7 @@ enum class blocked_by : u8 external = 4, }; -class context; +export class context; /** * @brief Thrown when an async::context runs out of stack memory @@ -72,7 +70,7 @@ class context; * cannot fit withint he context. * */ -struct bad_coroutine_alloc : std::bad_alloc +export struct bad_coroutine_alloc : std::bad_alloc { bad_coroutine_alloc(context const* p_violator) : violator(p_violator) @@ -117,7 +115,7 @@ using sleep_duration = std::chrono::nanoseconds; * @brief * */ -class scheduler +export class scheduler { public: /** @@ -156,7 +154,7 @@ private: std::variant p_block_info) = 0; }; -class context +export class context { public: static auto constexpr default_timeout = sleep_duration(0); @@ -415,10 +413,10 @@ protected: class context* m_context; }; -template +export template class future; -template +export template class future_promise_type : public promise_base { public: @@ -505,7 +503,7 @@ private: usize m_frame_size; }; -template<> +export template<> class future_promise_type : public promise_base { public: @@ -589,7 +587,7 @@ private: usize m_frame_size = 0; }; -template +export template class future { public: @@ -599,7 +597,7 @@ public: constexpr void resume() const { - auto active = handle().promise().context().active_handle(); + auto active = handle().promise().get_context().active_handle(); active.resume(); } diff --git a/test_package/.clangd b/test_package/.clangd new file mode 100644 index 0000000..f8ff8cd --- /dev/null +++ b/test_package/.clangd @@ -0,0 +1,3 @@ +CompileFlags: + CompilationDatabase: . + BuiltinHeaders: QueryDriver diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt new file mode 100644 index 0000000..d544a50 --- /dev/null +++ b/test_package/CMakeLists.txt @@ -0,0 +1,44 @@ +# 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. + +cmake_minimum_required(VERSION 4.0) + +# Generate compile commands for anyone using our libraries. +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_COLOR_DIAGNOSTICS ON) + +project(test_package LANGUAGES CXX) + +# Require Ninja or Visual Studio for modules +if(NOT CMAKE_GENERATOR MATCHES "Ninja|Visual Studio") + message(FATAL_ERROR "C++20 modules require Ninja or Visual Studio generator") +endif() + +find_package(async_context REQUIRED CONFIG) + +add_executable(${PROJECT_NAME}) +target_sources(${PROJECT_NAME} PUBLIC + FILE_SET CXX_MODULES + TYPE CXX_MODULES + PRIVATE main.cpp +) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_23) +target_link_libraries(${PROJECT_NAME} PRIVATE async_context) + +# 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) diff --git a/test_package/conanfile.py b/test_package/conanfile.py new file mode 100644 index 0000000..e323cf4 --- /dev/null +++ b/test_package/conanfile.py @@ -0,0 +1,52 @@ +#!/usr/bin/python +# +# 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. + +from conan import ConanFile +from conan.tools.build import cross_building +from conan.tools.cmake import CMake, cmake_layout, CMakeToolchain, CMakeDeps +from pathlib import Path + + +class TestPackageConan(ConanFile): + settings = "os", "arch", "compiler", "build_type" + generators = "VirtualRunEnv" + + def build_requirements(self): + self.tool_requires("cmake/[^4.0.0]") + self.tool_requires("ninja/[^1.3.0]") + + def requirements(self): + self.requires(self.tested_reference_str) + + def layout(self): + cmake_layout(self) + + def generate(self): + tc = CMakeToolchain(self) + tc.generate() + + deps = CMakeDeps(self) + deps.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def test(self): + if not cross_building(self): + bin_path = Path(self.cpp.build.bindirs[0]) / "test_package" + self.run(bin_path.absolute(), env="conanrun") diff --git a/test_package/main.cpp b/test_package/main.cpp new file mode 100644 index 0000000..92d7052 --- /dev/null +++ b/test_package/main.cpp @@ -0,0 +1,83 @@ +// 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. + +#include + +#include +#include +#include +#include +#include +#include +#include + +import async_context; + +struct my_scheduler : public async::scheduler +{ + int sleep_count = 0; + +private: + void do_schedule( + [[maybe_unused]] async::context& p_context, + [[maybe_unused]] async::blocked_by p_block_state, + [[maybe_unused]] std::variant + p_block_info) override + { + if (std::holds_alternative(p_block_info)) { + sleep_count++; + } + } +}; + +async::future coro_double_delay(async::context&) +{ + using namespace std::chrono_literals; + std::println("Delay for 500ms"); + co_await 500ms; + std::println("Delay for another 500ms"); + co_await 500ms; + std::println("Returning!"); + co_return; +} + +int main() +{ + auto scheduler = + mem::make_strong_ptr(std::pmr::new_delete_resource()); + auto buffer = mem::make_strong_ptr>( + std::pmr::new_delete_resource()); + auto buffer_span = mem::make_strong_ptr>( + std::pmr::new_delete_resource(), *buffer); + async::context my_context(scheduler, buffer_span); + + auto future_delay = coro_double_delay(my_context); + + assert(not future_delay.done()); + + future_delay.resume(); + + assert(scheduler->sleep_count == 1); + + future_delay.resume(); + + assert(scheduler->sleep_count == 2); + assert(not future_delay.done()); + + future_delay.resume(); + + assert(future_delay.done()); + + return 0; +} diff --git a/tests/async.test.cpp b/tests/async.test.cpp index a1def0e..4722b41 100644 --- a/tests/async.test.cpp +++ b/tests/async.test.cpp @@ -45,14 +45,16 @@ boost::ut::suite<"async::context"> async_context_suite = []() { int sleep_count = 0; private: - void do_schedule([[maybe_unused]] context& p_context, - [[maybe_unused]] blocked_by p_block_state, - [[maybe_unused]] std::variant - p_block_info) override + void do_schedule( + [[maybe_unused]] context& p_context, + [[maybe_unused]] blocked_by p_block_state, + [[maybe_unused]] std::variant + p_block_info) override { std::println("Scheduler called!", sleep_count); - if (std::holds_alternative(p_block_info)) { - std::println("sleep for: {}", std::get(p_block_info)); + if (std::holds_alternative(p_block_info)) { + std::println("sleep for: {}", + std::get(p_block_info)); sleep_count++; std::println("Sleep count = {}!", sleep_count); }