Skip to content

Commit daeea98

Browse files
authored
➕ make strong_ptr transitive dependency + enhancements (#21)
- Make strong_ptr a transitive dependency - Implement C++20 modules support with proper export declarations - Update CMake configuration to use async_context-config.cmake and include EXPORT_PACKAGE_DEPENDENCIES - Refactor async_context module to use export keywords for all types, classes, and enums - Add test_package with CMakeLists.txt and conanfile.py for module testing - Add .clangd configuration for better module development support - Update conanfile.py to: - Include .cmake.in files in exports_sources - Use cmake/[^4.0.0] and ninja/[^1.3.0] as tool requirements - Update strong_ptr dependency to version 0.0.2 with transitive headers/libs enabled - Remove std::function callback usage and replace with strong_ptr for scheduler interface to enable entire class objects as schedulers rather than just function wrappers - Fix unit test execution by moving "Executing unit tests!" message to build phase and updating the test runner to use modern C++20 - Update async_context module to remove unused functional header and properly export all types and templates - Add comprehensive test for coroutine double delay functionality - Enhance scheduler test to properly handle chrono::nanoseconds type in variant
1 parent 630eb04 commit daeea98

File tree

10 files changed

+216
-29
lines changed

10 files changed

+216
-29
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ on:
2424
jobs:
2525
package_and_upload_all_check:
2626
uses: libhal/ci/.github/workflows/package_and_upload_all.yml@5.x.y
27+
with:
28+
modules_support_needed: true
29+
coroutine_support_needed: true
2730
secrets: inherit

.github/workflows/deploy.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ jobs:
2626
with:
2727
version: ${{ github.ref_name }}
2828
remote_url: https://libhal.jfrog.io/artifactory/api/conan/trunk-conan
29+
modules_support_needed: true
30+
coroutine_support_needed: true
2931
secrets:
3032
conan_remote_password: ${{ secrets.JFROG_LIBHAL_TRUNK_ID_TOKEN }}
3133
conan_remote_user: ${{ secrets.JFROG_LIBHAL_TRUNK_ID_TOKEN_USER }}

CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ install(
4848

4949
install(
5050
EXPORT async_context_targets
51-
FILE "libasync_context-config.cmake"
52-
NAMESPACE libasync_context::
51+
FILE "async_context-config.cmake"
5352
DESTINATION "lib/cmake"
5453
CXX_MODULES_DIRECTORY "cxx-modules"
54+
EXPORT_PACKAGE_DEPENDENCIES
5555
)
5656

5757
# ==============================================================================
@@ -63,6 +63,7 @@ find_package(ut REQUIRED)
6363
if(CMAKE_CROSSCOMPILING)
6464
message(STATUS "Cross compiling, skipping unit test execution")
6565
else()
66+
message(STATUS "Building unit tests!")
6667
add_executable(async_unit_test)
6768
# Add module files
6869
target_sources(async_unit_test
@@ -75,7 +76,6 @@ else()
7576
target_compile_options(async_unit_test PRIVATE -g)
7677
target_link_libraries(async_unit_test PRIVATE async_context Boost::ut)
7778

78-
message(STATUS "Executing unit tests!")
7979
add_custom_target(run_tests ALL DEPENDS async_unit_test COMMAND async_unit_test)
8080
endif()
8181

conanfile.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class async_context_conan(ConanFile):
3333
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.")
3434
topics = ("async", "coroutines", "stack", "scheduling", "scheduler")
3535
settings = "compiler", "build_type", "os", "arch"
36-
exports_sources = "modules/*", "tests/*", "CMakeLists.txt", "LICENSE"
36+
exports_sources = "modules/*", "tests/*", "CMakeLists.txt", "*.cmake.in", "LICENSE"
3737
package_type = "static-library"
3838
shared = False
3939

@@ -84,12 +84,12 @@ def validate(self):
8484
self._validate_compiler_version()
8585

8686
def build_requirements(self):
87-
# Provides CMake, Ninja, & toolchain scripts for enabling modules
88-
self.tool_requires("cmake-modules-toolchain/1.0.2")
87+
self.tool_requires("cmake/[^4.0.0]")
88+
self.tool_requires("ninja/[^1.3.0]")
8989
self.test_requires("boost-ext-ut/2.3.1")
9090

9191
def requirements(self):
92-
self.requires("strong_ptr/0.0.0")
92+
self.requires("strong_ptr/0.0.2")
9393

9494
def layout(self):
9595
cmake_layout(self)

modules/async_context.cppm

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ module;
2020
#include <chrono>
2121
#include <coroutine>
2222
#include <exception>
23-
#include <functional>
2423
#include <memory_resource>
2524
#include <new>
2625
#include <span>
@@ -32,14 +31,13 @@ export module async_context;
3231

3332
export import strong_ptr;
3433

35-
export namespace async::inline v0 {
34+
namespace async::inline v0 {
3635

37-
using u8 = std::uint8_t;
38-
using byte = std::uint8_t;
39-
using usize = std::size_t;
36+
export using u8 = std::uint8_t;
37+
export using byte = std::uint8_t;
38+
export using usize = std::size_t;
4039

41-
enum class blocked_by : u8
42-
{
40+
export enum class blocked_by : u8 {
4341
/// Not blocked by anything, ready to run!
4442
nothing = 0,
4543

@@ -63,7 +61,7 @@ enum class blocked_by : u8
6361
external = 4,
6462
};
6563

66-
class context;
64+
export class context;
6765

6866
/**
6967
* @brief Thrown when an async::context runs out of stack memory
@@ -72,7 +70,7 @@ class context;
7270
* cannot fit withint he context.
7371
*
7472
*/
75-
struct bad_coroutine_alloc : std::bad_alloc
73+
export struct bad_coroutine_alloc : std::bad_alloc
7674
{
7775
bad_coroutine_alloc(context const* p_violator)
7876
: violator(p_violator)
@@ -117,7 +115,7 @@ using sleep_duration = std::chrono::nanoseconds;
117115
* @brief
118116
*
119117
*/
120-
class scheduler
118+
export class scheduler
121119
{
122120
public:
123121
/**
@@ -156,7 +154,7 @@ private:
156154
std::variant<sleep_duration, context*> p_block_info) = 0;
157155
};
158156

159-
class context
157+
export class context
160158
{
161159
public:
162160
static auto constexpr default_timeout = sleep_duration(0);
@@ -415,10 +413,10 @@ protected:
415413
class context* m_context;
416414
};
417415

418-
template<typename T>
416+
export template<typename T>
419417
class future;
420418

421-
template<typename T>
419+
export template<typename T>
422420
class future_promise_type : public promise_base
423421
{
424422
public:
@@ -505,7 +503,7 @@ private:
505503
usize m_frame_size;
506504
};
507505

508-
template<>
506+
export template<>
509507
class future_promise_type<void> : public promise_base
510508
{
511509
public:
@@ -589,7 +587,7 @@ private:
589587
usize m_frame_size = 0;
590588
};
591589

592-
template<typename T>
590+
export template<typename T>
593591
class future
594592
{
595593
public:
@@ -599,7 +597,7 @@ public:
599597

600598
constexpr void resume() const
601599
{
602-
auto active = handle().promise().context().active_handle();
600+
auto active = handle().promise().get_context().active_handle();
603601
active.resume();
604602
}
605603

test_package/.clangd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CompileFlags:
2+
CompilationDatabase: .
3+
BuiltinHeaders: QueryDriver

test_package/CMakeLists.txt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright 2024 - 2025 Khalil Estell and the libhal contributors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
cmake_minimum_required(VERSION 4.0)
16+
17+
# Generate compile commands for anyone using our libraries.
18+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
19+
set(CMAKE_COLOR_DIAGNOSTICS ON)
20+
21+
project(test_package LANGUAGES CXX)
22+
23+
# Require Ninja or Visual Studio for modules
24+
if(NOT CMAKE_GENERATOR MATCHES "Ninja|Visual Studio")
25+
message(FATAL_ERROR "C++20 modules require Ninja or Visual Studio generator")
26+
endif()
27+
28+
find_package(async_context REQUIRED CONFIG)
29+
30+
add_executable(${PROJECT_NAME})
31+
target_sources(${PROJECT_NAME} PUBLIC
32+
FILE_SET CXX_MODULES
33+
TYPE CXX_MODULES
34+
PRIVATE main.cpp
35+
)
36+
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_23)
37+
target_link_libraries(${PROJECT_NAME} PRIVATE async_context)
38+
39+
# Always run this custom target by making it depend on ALL
40+
add_custom_target(copy_compile_commands ALL
41+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
42+
${CMAKE_BINARY_DIR}/compile_commands.json
43+
${CMAKE_SOURCE_DIR}/compile_commands.json
44+
DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json)

test_package/conanfile.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/python
2+
#
3+
# Copyright 2024 - 2025 Khalil Estell and the libhal contributors
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
from conan import ConanFile
18+
from conan.tools.build import cross_building
19+
from conan.tools.cmake import CMake, cmake_layout, CMakeToolchain, CMakeDeps
20+
from pathlib import Path
21+
22+
23+
class TestPackageConan(ConanFile):
24+
settings = "os", "arch", "compiler", "build_type"
25+
generators = "VirtualRunEnv"
26+
27+
def build_requirements(self):
28+
self.tool_requires("cmake/[^4.0.0]")
29+
self.tool_requires("ninja/[^1.3.0]")
30+
31+
def requirements(self):
32+
self.requires(self.tested_reference_str)
33+
34+
def layout(self):
35+
cmake_layout(self)
36+
37+
def generate(self):
38+
tc = CMakeToolchain(self)
39+
tc.generate()
40+
41+
deps = CMakeDeps(self)
42+
deps.generate()
43+
44+
def build(self):
45+
cmake = CMake(self)
46+
cmake.configure()
47+
cmake.build()
48+
49+
def test(self):
50+
if not cross_building(self):
51+
bin_path = Path(self.cpp.build.bindirs[0]) / "test_package"
52+
self.run(bin_path.absolute(), env="conanrun")

test_package/main.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2024 - 2025 Khalil Estell and the libhal contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <cassert>
16+
17+
#include <array>
18+
#include <chrono>
19+
#include <coroutine>
20+
#include <memory_resource>
21+
#include <print>
22+
#include <span>
23+
#include <variant>
24+
25+
import async_context;
26+
27+
struct my_scheduler : public async::scheduler
28+
{
29+
int sleep_count = 0;
30+
31+
private:
32+
void do_schedule(
33+
[[maybe_unused]] async::context& p_context,
34+
[[maybe_unused]] async::blocked_by p_block_state,
35+
[[maybe_unused]] std::variant<std::chrono::nanoseconds, async::context*>
36+
p_block_info) override
37+
{
38+
if (std::holds_alternative<std::chrono::nanoseconds>(p_block_info)) {
39+
sleep_count++;
40+
}
41+
}
42+
};
43+
44+
async::future<void> coro_double_delay(async::context&)
45+
{
46+
using namespace std::chrono_literals;
47+
std::println("Delay for 500ms");
48+
co_await 500ms;
49+
std::println("Delay for another 500ms");
50+
co_await 500ms;
51+
std::println("Returning!");
52+
co_return;
53+
}
54+
55+
int main()
56+
{
57+
auto scheduler =
58+
mem::make_strong_ptr<my_scheduler>(std::pmr::new_delete_resource());
59+
auto buffer = mem::make_strong_ptr<std::array<async::u8, 1024>>(
60+
std::pmr::new_delete_resource());
61+
auto buffer_span = mem::make_strong_ptr<std::span<async::u8>>(
62+
std::pmr::new_delete_resource(), *buffer);
63+
async::context my_context(scheduler, buffer_span);
64+
65+
auto future_delay = coro_double_delay(my_context);
66+
67+
assert(not future_delay.done());
68+
69+
future_delay.resume();
70+
71+
assert(scheduler->sleep_count == 1);
72+
73+
future_delay.resume();
74+
75+
assert(scheduler->sleep_count == 2);
76+
assert(not future_delay.done());
77+
78+
future_delay.resume();
79+
80+
assert(future_delay.done());
81+
82+
return 0;
83+
}

tests/async.test.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,16 @@ boost::ut::suite<"async::context"> async_context_suite = []() {
4545
int sleep_count = 0;
4646

4747
private:
48-
void do_schedule([[maybe_unused]] context& p_context,
49-
[[maybe_unused]] blocked_by p_block_state,
50-
[[maybe_unused]] std::variant<sleep_duration, context*>
51-
p_block_info) override
48+
void do_schedule(
49+
[[maybe_unused]] context& p_context,
50+
[[maybe_unused]] blocked_by p_block_state,
51+
[[maybe_unused]] std::variant<std::chrono::nanoseconds, context*>
52+
p_block_info) override
5253
{
5354
std::println("Scheduler called!", sleep_count);
54-
if (std::holds_alternative<sleep_duration>(p_block_info)) {
55-
std::println("sleep for: {}", std::get<sleep_duration>(p_block_info));
55+
if (std::holds_alternative<std::chrono::nanoseconds>(p_block_info)) {
56+
std::println("sleep for: {}",
57+
std::get<std::chrono::nanoseconds>(p_block_info));
5658
sleep_count++;
5759
std::println("Sleep count = {}!", sleep_count);
5860
}

0 commit comments

Comments
 (0)