From 459fbdcf4862fd29dd111860bbf64fb9551a8504 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Sat, 10 Jan 2026 18:50:18 +0100 Subject: [PATCH 01/22] Add Windows CI build too --- .github/workflows/cmake.yml | 68 +++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 4 ++- 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/cmake.yml diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 0000000..ef7638f --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,68 @@ +--- +name: CMake + +on: + push: + branches: ["develop"] + pull_request: + branches: ["develop"] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + schedule: + # run at 15:30 on day-of-month 7. + - cron: '30 15 7 * *' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: release + CTEST_OUTPUT_ON_FAILURE: 1 + +jobs: + build: + strategy: + fail-fast: false + + matrix: + os: [windows] + + runs-on: ${{ matrix.os }}-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup build environment + uses: lukka/get-cmake@latest + with: + cmakeVersion: "~4.2.1" + ninjaVersion: "^1.13.0" + + - name: Setup MSVC + if: startsWith(matrix.os, 'windows') + uses: TheMrMilchmann/setup-msvc-dev@v3 + with: + arch: x64 + + - name: Setup Cpp + if: matrix.os != 'windows' + uses: aminya/setup-cpp@v1 + with: + compiler: llvm + + - name: Configure CMake + run: cmake --preset msvc-${{env.BUILD_TYPE}} --log-level=VERBOSE + + - name: Build + # Build your program with the given configuration + run: cmake --build --preset msvc-${{env.BUILD_TYPE}} + + # - name: Install + # # Install the project artefacts to CMAKE_INSTALL_PREFIX + # run: cmake --install ${{github.workspace}}/build/${{env.BUILD_TYPE}} --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build/${{env.BUILD_TYPE}} + # Execute tests defined by the CMake configuration, but needs to find_package(Boost)! + run: ctest -C msvc-${{env.BUILD_TYPE}} diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b36f29..ce92578 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,11 +11,13 @@ project( # gersemi: off -# Modules opt in only on compilers that support g++-15 and clang-20+ +# Modules opt in only on compilers that support it: msvc, g++-15 and clang-20+ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 20) set(CMAKE_CXX_SCAN_FOR_MODULES 1) elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15) set(CMAKE_CXX_SCAN_FOR_MODULES 1) +elseif(MSVC) + set(CMAKE_CXX_SCAN_FOR_MODULES 1) else() set(CMAKE_CXX_SCAN_FOR_MODULES 0) endif() From f3c5c4af29d897375b1d00c7c60fee19ce05d66f Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Sat, 10 Jan 2026 19:19:15 +0100 Subject: [PATCH 02/22] Use portable C++20 check and portable C++ feature checks --- include/beman/scope/scope.hpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/include/beman/scope/scope.hpp b/include/beman/scope/scope.hpp index 7d33b2c..e768259 100644 --- a/include/beman/scope/scope.hpp +++ b/include/beman/scope/scope.hpp @@ -10,12 +10,30 @@ #include // clang-format off -#if __cplusplus < 202002L +#include + +#if defined(__cpp_concepts) && __cpp_concepts >= 201907L + // C++20 concepts supported +#elif __cplusplus < 202002L #error "C++20 or later is required" #endif -// clang-format on -#include //todo unconditional for unique_resource +// detect standard header first, then experimental, otherwise use local implementation +#if defined(__has_include) +# if __has_include() +# include +# define BEMAN_SCOPE_USE_STD +# elif __has_include() +# include +# define BEMAN_SCOPE_USE_STD_EXPERIMENTAL +# else +// no std scope header — fall through to local implementation below +# endif +#elif defined(__cpp_lib_scope) && __cpp_lib_scope >= 2023xxxxL +# include +# define BEMAN_SCOPE_USE_STD +#endif +// clang-format on #ifdef BEMAN_SCOPE_USE_STD_EXPERIMENTAL From 9ad29b70fba901b194f1760ff91d51b01c7cd7f0 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Sat, 10 Jan 2026 19:35:01 +0100 Subject: [PATCH 03/22] Use C++23 on Windows --- CMakePresets.json | 2 ++ include/beman/scope/scope.hpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakePresets.json b/CMakePresets.json index 483e1a3..655058f 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -101,6 +101,7 @@ "_debug-base" ], "cacheVariables": { + "CMAKE_CXX_STANDARD": "23", "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" } }, @@ -112,6 +113,7 @@ "_release-base" ], "cacheVariables": { + "CMAKE_CXX_STANDARD": "23", "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" } } diff --git a/include/beman/scope/scope.hpp b/include/beman/scope/scope.hpp index e768259..d18f154 100644 --- a/include/beman/scope/scope.hpp +++ b/include/beman/scope/scope.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -67,7 +68,7 @@ namespace beman::scope { // todo temporary template -using unique_resource = std::experimental::unique_resource; +using unique_resource = std::unique_resource; // todo temporary template > From 01a4dcf6d65803bb3da33a6718da441bfdeaf3d8 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Sat, 10 Jan 2026 20:23:31 +0100 Subject: [PATCH 04/22] Quickfix to compile with C++23 --- include/beman/scope/scope.hpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/include/beman/scope/scope.hpp b/include/beman/scope/scope.hpp index d18f154..df45bb9 100644 --- a/include/beman/scope/scope.hpp +++ b/include/beman/scope/scope.hpp @@ -50,15 +50,15 @@ template using scope_success = std::experimental::scope_success; // todo temporary -// template -// using unique_resource = std::experimental::unique_resource; +template +using unique_resource = std::experimental::fundamentals_v3::unique_resource; -// template > -// unique_resource, std::decay_t> -// make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept(noexcept( -// std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)))) { -// return std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)); -//} +template > +unique_resource, std::decay_t> +make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept(noexcept( + std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)))) { + return std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)); +} } // namespace beman::scope @@ -71,11 +71,11 @@ template using unique_resource = std::unique_resource; // todo temporary -template > -unique_resource, std::decay_t > -make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept(noexcept( - std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)))) { - return std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)); +template > +unique_resource, std::decay_t> +make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept( + noexcept(std::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)))) { + return std::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)); } //================================================================================================== From 6f7560373938ac2dbe303c48a1cce99287e75dd9 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Sun, 11 Jan 2026 21:59:08 +0100 Subject: [PATCH 05/22] Add simple BEMAN_SCOPE_USE_FALLBACK code This compiles with clang++-21 on OSX and should build on Windows too. --- include/beman/scope/scope.hpp | 17 +- include/beman/scope/scope_impl.hpp | 281 +++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+), 6 deletions(-) create mode 100644 include/beman/scope/scope_impl.hpp diff --git a/include/beman/scope/scope.hpp b/include/beman/scope/scope.hpp index df45bb9..9b45233 100644 --- a/include/beman/scope/scope.hpp +++ b/include/beman/scope/scope.hpp @@ -33,6 +33,8 @@ #elif defined(__cpp_lib_scope) && __cpp_lib_scope >= 2023xxxxL # include # define BEMAN_SCOPE_USE_STD +#else +# warning "Missing feature __cpp_lib_scope" #endif // clang-format on @@ -62,20 +64,20 @@ make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept(noexcept( } // namespace beman::scope -#else // ! BEMAN_SCOPE__USE_STD_EXPERIMENTAL +#elif defined(BEMAN_SCOPE_USE_STD) namespace beman::scope { // todo temporary template -using unique_resource = std::unique_resource; +using unique_resource = std::experimental::unique_resource; // todo temporary template > unique_resource, std::decay_t> -make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept( - noexcept(std::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)))) { - return std::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)); +make_unique_resource_checked(R&& r, const S& invalid, D&& d) noexcept(noexcept( + std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)))) { + return std::experimental::make_unique_resource_checked(std::forward(r), std::forward(invalid), std::forward(d)); } //================================================================================================== @@ -440,6 +442,9 @@ using scope_fail = scope_guard + +#if defined(__cpp_concepts) && __cpp_concepts >= 201907L + // C++20 concepts supported +#elif __cplusplus < 202002L +# error "C++20 or later is required" +#endif + +// detect standard header first, then experimental, otherwise use local implementation +#ifdef __has_include +# if __has_include() +# include +# define BEMAN_SCOPE_USE_STD +# elif __has_include() +# include +# define BEMAN_SCOPE_USE_STD_EXPERIMENTAL +# else +# define BEMAN_SCOPE_USE_FALLBACK +# endif +#else +# define BEMAN_SCOPE_USE_FALLBACK +#endif + +#ifdef BEMAN_SCOPE_USE_STD +# if !defined(__cpp_lib_scope_exit) +# error "Standard present but __cpp_lib_scope_exit not defined" +# endif +#endif +// clang-format on + +#ifdef BEMAN_SCOPE_USE_FALLBACK +#include +#include +#include + +namespace beman::scope { + +// TODO(CK): make a std::experimental::scope_exit::scope_exit conform +// implementation +template +class [[nodiscard]] scope_exit { + F f; + bool active = true; + + public: + constexpr explicit scope_exit(F func) noexcept(std::is_nothrow_move_constructible_v) : f(std::move(func)) {} + + // Move constructor + constexpr scope_exit(scope_exit&& other) noexcept(std::is_nothrow_move_constructible_v) + : f(std::move(other.f)), active(other.active) { + other.active = false; + } + + // Deleted copy + auto operator=(const scope_exit&) -> scope_exit& = delete; + scope_exit(const scope_exit&) = delete; + + // Move assignment + constexpr auto operator=(scope_exit&& other) noexcept(std::is_nothrow_move_assignable_v) -> scope_exit& { + if (this != &other) { + f = std::move(other.f); + active = other.active; + other.active = false; + } + return *this; + } + + // Destructor: call only if scope is exiting normally + ~scope_exit() noexcept(noexcept(f())) { + if (active) { + f(); + } + } + + // Release to prevent execution + constexpr auto release() -> void { active = false; } +}; + +// Factory helper +template +static auto make_scope_exit(F f) -> scope_exit { + return scope_exit(std::move(f)); +} + +// TODO(CK): make a std::experimental::scope_fail::scope_fail conform +// implementation +template +class [[nodiscard]] scope_fail { + F f; + bool active = true; + int exception_count{}; + + public: + // Constructor: capture current uncaught exceptions + constexpr explicit scope_fail(F func) noexcept(std::is_nothrow_move_constructible_v) + : f(std::move(func)), exception_count(std::uncaught_exceptions()) {} + + // Move constructor + constexpr scope_fail(scope_fail&& other) noexcept(std::is_nothrow_move_constructible_v) + : f(std::move(other.f)), active(other.active), exception_count(other.exception_count) { + other.active = false; + } + + // Deleted copy + scope_fail(const scope_fail&) = delete; + auto operator=(const scope_fail&) -> scope_fail& = delete; + + // Move assignment + constexpr auto operator=(scope_fail&& other) noexcept(std::is_nothrow_move_assignable_v) -> scope_fail& { + if (this != &other) { + f = std::move(other.f); + active = other.active; + exception_count = other.exception_count; + other.active = false; + } + return *this; + } + + // Destructor: call if scope is exiting due to an exception + ~scope_fail() noexcept(noexcept(f())) { + if (active && std::uncaught_exceptions() > exception_count) { + f(); + } + } + + // Release to prevent execution + constexpr auto release() -> void { active = false; } +}; + +// Factory helper +template +constexpr auto make_scope_fail(F&& f) -> scope_fail> { + return scope_fail>(std::forward(f)); +} + +// TODO(CK): make a std::experimental::scope_success::scope_success conform +// implementation +template +class [[nodiscard]] scope_success { + F f; + bool active = true; + int exception_count{}; + + public: + // Constructor: capture current uncaught exceptions + constexpr explicit scope_success(F func) noexcept(std::is_nothrow_move_constructible_v) + : f(std::move(func)), exception_count(std::uncaught_exceptions()) {} + + // Move constructor + constexpr scope_success(scope_success&& other) noexcept(std::is_nothrow_move_constructible_v) + : f(std::move(other.f)), active(other.active), exception_count(other.exception_count) { + other.active = false; + } + + // Deleted copy + scope_success(const scope_success&) = delete; + auto operator=(const scope_success&) -> scope_success& = delete; + + // Move assignment + constexpr auto operator=(scope_success&& other) noexcept(std::is_nothrow_move_assignable_v) -> scope_success& { + if (this != &other) { + f = std::move(other.f); + active = other.active; + exception_count = other.exception_count; + other.active = false; + } + return *this; + } + + // Destructor: call only if scope is exiting normally + ~scope_success() noexcept(noexcept(f())) { + if (active && std::uncaught_exceptions() == exception_count) { + f(); + } + } + + // Release to prevent execution + constexpr auto release() -> void { active = false; } +}; + +// Factory helper +template +constexpr auto make_scope_success(F&& f) -> scope_success> { + return scope_success>(std::forward(f)); +} + +template +class [[nodiscard]] unique_resource { + Resource resource; + Deleter deleter; + bool active = true; + + public: + // Constructor + constexpr unique_resource(Resource r, Deleter d) noexcept(std::is_nothrow_move_constructible_v) + : resource(std::move(r)), deleter(std::move(d)) {} + + // Move constructor + constexpr unique_resource(unique_resource&& other) noexcept(std::is_nothrow_move_constructible_v) + : resource(std::move(other.resource)), deleter(std::move(other.deleter)), active(other.active) { + other.active = false; + } + + // Move assignment + constexpr auto operator=(unique_resource&& other) noexcept(std::is_nothrow_move_assignable_v) + -> unique_resource& { + if (this != &other) { + reset(std::move(other.resource)); + deleter = std::move(other.deleter); + active = other.active; + other.active = false; + } + return *this; + } + + // Deleted copy operations + unique_resource(const unique_resource&) = delete; + auto operator=(const unique_resource&) -> unique_resource& = delete; + + // Destructor + ~unique_resource() noexcept(noexcept(deleter(resource))) { + if (active) { + deleter(resource); + } + } + + // Release ownership + constexpr void release() noexcept(noexcept(deleter(resource))) { active = false; } + + // Reset resource + constexpr void reset() noexcept(noexcept(deleter(resource))) { + if (active) { + deleter(resource); + } + active = false; + } + constexpr void reset(Resource new_resource) noexcept(noexcept(deleter(resource))) { + if (active) { + deleter(resource); + } + resource = std::move(new_resource); + active = true; + } + + // Accessors + constexpr auto get() const -> const Resource& { return resource; } + constexpr auto get() -> Resource& { return resource; } + + // operator* — only for non-void pointer resources + constexpr auto operator*() const noexcept -> std::add_lvalue_reference_t> + requires(std::is_pointer_v && !std::is_void_v>) + { + return *resource; + } + + // operator-> — only for pointer resources + constexpr auto operator->() const noexcept -> Resource + requires std::is_pointer_v + { + return resource; + } + + // Check if active + constexpr explicit operator bool() const noexcept { return active; } +}; + +// Deduction guide +template +unique_resource(Resource&&, Deleter&&) -> unique_resource, std::decay_t>; + +} // namespace beman::scope + +#endif // BEMAN_SCOPE_USE_FALLBACK + +#endif // SCOPE_IMPL_HPP From d83e3f443e82980021bfd336f7e606dca61baa06 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Sun, 11 Jan 2026 22:23:05 +0100 Subject: [PATCH 06/22] Fix Windows CI test directory --- .github/workflows/cmake.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index ef7638f..09db9ed 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -58,11 +58,10 @@ jobs: # Build your program with the given configuration run: cmake --build --preset msvc-${{env.BUILD_TYPE}} + - name: Test + # Execute tests defined by the CMake configuration + run: ctest --preset msvc-${{env.BUILD_TYPE}} + # - name: Install # # Install the project artefacts to CMAKE_INSTALL_PREFIX - # run: cmake --install ${{github.workspace}}/build/${{env.BUILD_TYPE}} --config ${{env.BUILD_TYPE}} - - - name: Test - working-directory: ${{github.workspace}}/build/${{env.BUILD_TYPE}} - # Execute tests defined by the CMake configuration, but needs to find_package(Boost)! - run: ctest -C msvc-${{env.BUILD_TYPE}} + # run: cmake --build --preset msvc-${{env.BUILD_TYPE}} --target install From 2f5ab941ea93d03326d85a6461a7fd205bfccc74 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Tue, 13 Jan 2026 21:05:04 +0100 Subject: [PATCH 07/22] Refactory unique_resource code to be portable Add more test for unique_resource --- CMakePresets.json | 2 + examples/CMakeLists.txt | 1 + gcovr.cfg | 21 +++ include/beman/scope/scope_impl.hpp | 61 +++++-- makefile | 65 ++++++++ tests/CMakeLists.txt | 8 +- tests/unique_resource_2.test.cpp | 254 +++++++++++++++++++++++++++++ 7 files changed, 396 insertions(+), 16 deletions(-) create mode 100644 gcovr.cfg create mode 100644 makefile create mode 100644 tests/unique_resource_2.test.cpp diff --git a/CMakePresets.json b/CMakePresets.json index 655058f..91bb5e6 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -79,6 +79,7 @@ "_debug-base" ], "cacheVariables": { + "CMAKE_CXX_STANDARD": "23", "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" } }, @@ -90,6 +91,7 @@ "_release-base" ], "cacheVariables": { + "CMAKE_CXX_STANDARD": "23", "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" } }, diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 23ffaf7..0f1d6d3 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -13,4 +13,5 @@ foreach(example ${ALL_EXAMPLES}) add_executable(${example}) target_sources(${example} PRIVATE ${example}.cpp) target_link_libraries(${example} PRIVATE beman::scope) + add_test(NAME ${example} COMMAND ${example}) endforeach() diff --git a/gcovr.cfg b/gcovr.cfg new file mode 100644 index 0000000..52add0f --- /dev/null +++ b/gcovr.cfg @@ -0,0 +1,21 @@ +root = . +search-path = build + +filter = examples/* +# filter = src/* +filter = include/* + +exclude-directories = stagedir +exclude-directories = build/*/*/_deps +exclude-directories = tests +exclude-directories = conan + +gcov-ignore-parse-errors = all +print-summary = yes + +html-details = build/coverage/index.html + +cobertura-pretty = yes +cobertura = build/cobertura.xml + +#TBD delete-gcov-files = yes diff --git a/include/beman/scope/scope_impl.hpp b/include/beman/scope/scope_impl.hpp index c03286a..e0cb393 100644 --- a/include/beman/scope/scope_impl.hpp +++ b/include/beman/scope/scope_impl.hpp @@ -17,9 +17,11 @@ # if __has_include() # include # define BEMAN_SCOPE_USE_STD +// XXX #warning "Set BEMAN_SCOPE_USE_STD" # elif __has_include() # include # define BEMAN_SCOPE_USE_STD_EXPERIMENTAL +// XXX #warning "Set BEMAN_SCOPE_USE_STD_EXPERIMENTAL" # else # define BEMAN_SCOPE_USE_FALLBACK # endif @@ -83,8 +85,9 @@ class [[nodiscard]] scope_exit { }; // Factory helper +// NOLINTNEXTLINE(misc-use-anonymous-namespace) template -static auto make_scope_exit(F f) -> scope_exit { +auto make_scope_exit(F f) -> scope_exit { return scope_exit(std::move(f)); } @@ -134,6 +137,7 @@ class [[nodiscard]] scope_fail { }; // Factory helper +// NOLINTNEXTLINE(misc-use-anonymous-namespace) template constexpr auto make_scope_fail(F&& f) -> scope_fail> { return scope_fail>(std::forward(f)); @@ -185,6 +189,7 @@ class [[nodiscard]] scope_success { }; // Factory helper +// NOLINTNEXTLINE(misc-use-anonymous-namespace) template constexpr auto make_scope_success(F&& f) -> scope_success> { return scope_success>(std::forward(f)); @@ -203,8 +208,8 @@ class [[nodiscard]] unique_resource { // Move constructor constexpr unique_resource(unique_resource&& other) noexcept(std::is_nothrow_move_constructible_v) - : resource(std::move(other.resource)), deleter(std::move(other.deleter)), active(other.active) { - other.active = false; + : resource(std::move(other.resource)), deleter(std::move(other.deleter)) { + active = std::exchange(other.active, false); } // Move assignment @@ -212,9 +217,8 @@ class [[nodiscard]] unique_resource { -> unique_resource& { if (this != &other) { reset(std::move(other.resource)); - deleter = std::move(other.deleter); - active = other.active; - other.active = false; + deleter = std::move(other.deleter); + active = std::exchange(other.active, false); } return *this; } @@ -224,22 +228,20 @@ class [[nodiscard]] unique_resource { auto operator=(const unique_resource&) -> unique_resource& = delete; // Destructor - ~unique_resource() noexcept(noexcept(deleter(resource))) { - if (active) { - deleter(resource); - } - } + ~unique_resource() noexcept(noexcept(deleter(resource))) { reset(); } // Release ownership - constexpr void release() noexcept(noexcept(deleter(resource))) { active = false; } + constexpr void release() noexcept { active = false; } // Reset resource constexpr void reset() noexcept(noexcept(deleter(resource))) { if (active) { + active = false; deleter(resource); } - active = false; } + + // Reset the resource and call deleter if engaged constexpr void reset(Resource new_resource) noexcept(noexcept(deleter(resource))) { if (active) { deleter(resource); @@ -259,14 +261,17 @@ class [[nodiscard]] unique_resource { return *resource; } - // operator-> — only for pointer resources + // Optional pointer convenience constexpr auto operator->() const noexcept -> Resource requires std::is_pointer_v { return resource; } - // Check if active + // TODO(CK): missing usecase? + constexpr auto get_deleter() const noexcept -> Deleter; + + // NOTE: check if active; not required from LWG? constexpr explicit operator bool() const noexcept { return active; } }; @@ -274,8 +279,34 @@ class [[nodiscard]] unique_resource { template unique_resource(Resource&&, Deleter&&) -> unique_resource, std::decay_t>; +// Factory: conditionally engaged +// NOLINTNEXTLINE(misc-use-anonymous-namespace) +template +constexpr auto make_unique_resource_checked(R&& r, const Invalid& invalid, D&& d) { + using resource_type = std::decay_t; + using deleter_type = std::decay_t; + + if (r == invalid) { + // Disengaged resource + unique_resource ur(resource_type{}, std::forward(d)); + ur.release(); // disengage immediately + return ur; + } + + return unique_resource(std::forward(r), std::forward(d)); +} + } // namespace beman::scope +#elifdef BEMAN_SCOPE_USE_STD_EXPERIMENTAL + +namespace beman::scope { +using std::experimental::scope_exit; +using std::experimental::scope_fail; +using std::experimental::scope_success; +using std::experimental::unique_resource; +} // namespace beman::scope + // #endif // BEMAN_SCOPE_USE_FALLBACK #endif // SCOPE_IMPL_HPP diff --git a/makefile b/makefile new file mode 100644 index 0000000..22b4fc4 --- /dev/null +++ b/makefile @@ -0,0 +1,65 @@ +# Standard stuff + +.SUFFIXES: + +MAKEFLAGS+= --no-builtin-rules # Disable the built-in implicit rules. +# MAKEFLAGS+= --warn-undefined-variables # Warn when an undefined variable is referenced. +# MAKEFLAGS+= --include-dir=$(CURDIR)/conan # Search DIRECTORY for included makefiles (*.mk). + +export hostSystemName=$(shell uname) + +ifeq (${hostSystemName},Darwin) + export LLVM_PREFIX:=$(shell brew --prefix llvm) + export LLVM_DIR:=$(shell realpath ${LLVM_PREFIX}) + export PATH:=${LLVM_DIR}/bin:${PATH} + + export CMAKE_CXX_STDLIB_MODULES_JSON=${LLVM_DIR}/lib/c++/libc++.modules.json + export CXX=clang++ + export LDFLAGS=-L$(LLVM_DIR)/lib/c++ -lc++abi # XXX -lc++ -lc++experimental + # FIXME: export GCOV="llvm-cov gcov" + + ### TODO: to test g++-15: + export GCC_PREFIX:=$(shell brew --prefix gcc) + export GCC_DIR:=$(shell realpath ${GCC_PREFIX}) + + # export CMAKE_CXX_STDLIB_MODULES_JSON=${GCC_DIR}/lib/gcc/current/libstdc++.modules.json + # export CXX:=g++-15 + # export CXXFLAGS:=-stdlib=libstdc++ + # export GCOV="gcov" +else ifeq (${hostSystemName},Linux) + export LLVM_DIR=/usr/lib/llvm-20 + export PATH:=${LLVM_DIR}/bin:${PATH} + export CXX=clang++-20 +endif + +.PHONY: all install coverage clean distclean + +all: build/compile_commands.json + ln -sf $< . + ninja -C build + +build/compile_commands.json: CMakeLists.txt makefile + cmake -S . -B build -G Ninja --log-level=DEBUG -D CMAKE_BUILD_TYPE=Release \ + -D CMAKE_EXPERIMENTAL_CXX_IMPORT_STD="d0edc3af-4c50-42ea-a356-e2862fe7a444" \ + -D CMAKE_CXX_STDLIB_MODULES_JSON=${CMAKE_CXX_STDLIB_MODULES_JSON} \ + -D CMAKE_CXX_STANDARD=23 -D CMAKE_CXX_EXTENSIONS=YES -D CMAKE_CXX_STANDARD_REQUIRED=YES \ + -D CMAKE_CXX_FLAGS='-fno-inline --coverage' \ + -D CMAKE_CXX_MODULE_STD=NO \ + -D CMAKE_INSTALL_MESSAGE=LAZY # XXX -D CMAKE_SKIP_INSTALL_RULES=YES # --fresh + +install: build/cmake_install.cmake + cmake --install build + +distclean: clean + rm -rf build + find . -name '*~' -delete + +build/coverage: test + mkdir -p $@ + +coverage: build/coverage + gcovr # XXX -v + +# Anything we don't know how to build will use this rule. +% :: + ninja -C build $(@) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a7cbcfc..6acb735 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,7 +11,13 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(Catch2) -set(ALL_TESTNAMES scope_success scope_exit scope_fail unique_resource) +set(ALL_TESTNAMES + scope_success + scope_exit + scope_fail + unique_resource + unique_resource_2 +) # module tests will only compile with gcc15 or clang20 and above if(CMAKE_CXX_SCAN_FOR_MODULES) diff --git a/tests/unique_resource_2.test.cpp b/tests/unique_resource_2.test.cpp new file mode 100644 index 0000000..a564286 --- /dev/null +++ b/tests/unique_resource_2.test.cpp @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "beman/scope/scope.hpp" + +#include +#include + +namespace { + +struct Counter { + int value = 0; +}; + +struct CountingDeleter { + // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) + Counter* counter{nullptr}; + + void operator()(int& /*unused*/) const noexcept { ++counter->value; } +}; + +} // namespace + +TEST_CASE("Construct file unique_resource", "[unique_resource]") { + bool open_file_good = false; + bool close_file_good = false; + + { + auto file = beman::scope::unique_resource(fopen("example.txt", "w"), // Acquire the FILE* + [&close_file_good](FILE* f) -> void { + if (f) { + (void)fclose(f); // Release (cleanup) the resource + close_file_good = true; + } + }); + + if (!file) { + throw std::runtime_error("file didn't open"); + } + open_file_good = true; + } + + REQUIRE(open_file_good == true); + REQUIRE(close_file_good == true); +} + +TEST_CASE("unique_resource basic construction and engagement", "[unique_resource]") { + Counter c{}; // NOLINT(misc-const-correctness) + { + beman::scope::unique_resource r(42, CountingDeleter{&c}); + + REQUIRE(static_cast(r)); + REQUIRE(r.get() == 42); + REQUIRE(c.value == 0); + } + + REQUIRE(c.value == 1); +} + +TEST_CASE("unique_resource release disengages without deleting", "[unique_resource]") { + Counter c{}; // NOLINT(misc-const-correctness) + { + beman::scope::unique_resource r(7, CountingDeleter{&c}); + + r.release(); + + REQUIRE_FALSE(r); + } + + REQUIRE(c.value == 0); +} + +TEST_CASE("unique_resource reset() destroys current resource", "[unique_resource]") { + Counter c{}; // NOLINT(misc-const-correctness) + + { + beman::scope::unique_resource r(1, CountingDeleter{&c}); + + r.reset(); + REQUIRE_FALSE(r); + REQUIRE(c.value == 1); + } + + REQUIRE(c.value == 1); +} + +TEST_CASE("unique_resource reset(new_resource) replaces resource", "[unique_resource]") { + Counter c{}; // NOLINT(misc-const-correctness) + + { + beman::scope::unique_resource r(1, CountingDeleter{&c}); + + r.reset(2); + + REQUIRE(r); + REQUIRE(r.get() == 2); + REQUIRE(c.value == 1); + } + + REQUIRE(c.value == 2); +} + +TEST_CASE("unique_resource move constructor transfers ownership", "[unique_resource]") { + Counter c{}; // NOLINT(misc-const-correctness) + + beman::scope::unique_resource r1(10, CountingDeleter{&c}); + beman::scope::unique_resource r2(std::move(r1)); + + REQUIRE_FALSE(r1); + REQUIRE(r2); + REQUIRE(r2.get() == 10); + + r2.reset(); + REQUIRE(c.value == 1); +} + +TEST_CASE("unique_resource move assignment destroys target before transfer", "[unique_resource]") { + Counter c1{}; // NOLINT(misc-const-correctness) + Counter c2{}; // NOLINT(misc-const-correctness) + + beman::scope::unique_resource r1(1, CountingDeleter{&c1}); + beman::scope::unique_resource r2(2, CountingDeleter{&c2}); + + r2 = std::move(r1); + + REQUIRE_FALSE(r1); + REQUIRE(r2); + REQUIRE(r2.get() == 1); + + REQUIRE(c2.value == 1); // old r2 destroyed + REQUIRE(c1.value == 0); + + r2.reset(); + REQUIRE(c1.value == 1); +} + +TEST_CASE("unique_resource destructor is idempotent after release", "[unique_resource]") { + Counter c{}; // NOLINT(misc-const-correctness) + + { + beman::scope::unique_resource r(99, CountingDeleter{&c}); + + r.release(); + } + + REQUIRE(c.value == 0); +} + +TEST_CASE("make_unique_resource_checked disengages on invalid", "[unique_resource]") { + Counter c{}; // NOLINT(misc-const-correctness) + + { + auto r = beman::scope::make_unique_resource_checked(-1, -1, CountingDeleter{&c}); + + REQUIRE_FALSE(r); + } + + REQUIRE(c.value == 0); +} + +TEST_CASE("make_unique_resource_checked engages on valid", "[unique_resource]") { + Counter c{}; // NOLINT(misc-const-correctness) + + { + auto r = beman::scope::make_unique_resource_checked(3, -1, CountingDeleter{&c}); + + REQUIRE(r); + } + + REQUIRE(c.value == 1); +} + +TEST_CASE("Open a nonexisting file with make_unique_resource_checked", "[unique_resource]") { + bool open_file_good = false; + bool close_file_good = false; + + { + auto file = + beman::scope::make_unique_resource_checked(fopen("nonexisting.txt", "r"), // Acquire the FILE* + nullptr, + [&close_file_good](FILE* f) -> void { + if (f) { + (void)fclose(f); // Release (cleanup) the resource + close_file_good = true; + } + }); + + if (file.get() != nullptr) { + open_file_good = true; + } + } + + REQUIRE(open_file_good == false); + REQUIRE(close_file_good == false); +} + +TEST_CASE("unique_resource supports deduction guide", "[unique_resource]") { + Counter c{}; // NOLINT(misc-const-correctness) + + beman::scope::unique_resource r(123, CountingDeleter{&c}); + + static_assert(std::is_same_v >); + + r.reset(); + REQUIRE(c.value == 1); +} + +TEST_CASE("unique_resource does not double-delete after move", "[unique_resource]") { + Counter c{}; // NOLINT(misc-const-correctness) + + { + beman::scope::unique_resource r1(1, CountingDeleter{&c}); + + { + auto r2 = std::move(r1); + } + + REQUIRE(c.value == 1); + } + + REQUIRE(c.value == 1); +} + +TEST_CASE("unique_resource operator* returns reference to resource", "[unique_resource]") { + int value = 42; + + // Define the deleter type explicitly (function pointer) + using DeleterType = void (*)(int*); + + // Empty deleter + auto empty_deleter = [](int*) {}; + + // Create unique_resource instance (modifiable) + beman::scope::unique_resource r(&value, empty_deleter); + + // operator* should return a reference + int& ref = *r; + + // Check that the reference refers to the original value + REQUIRE(&ref == &value); + REQUIRE(ref == 42); + + // Modify the value through the reference + ref = 100; + REQUIRE(value == 100); + + // Create a const unique_resource instance + const beman::scope::unique_resource r2(&value, empty_deleter); + + // operator* should return const reference + const int& cref = *r2; + REQUIRE(cref == 100); + + // Modifying through cref would fail to compile (correct) +} From 4a9894da3f221e5bfd0b661c4eff1ac77de02bb2 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Tue, 13 Jan 2026 21:36:32 +0100 Subject: [PATCH 08/22] Prevent use of explicit operator bool() const noexcept --- tests/unique_resource_2.test.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/unique_resource_2.test.cpp b/tests/unique_resource_2.test.cpp index a564286..da1d136 100644 --- a/tests/unique_resource_2.test.cpp +++ b/tests/unique_resource_2.test.cpp @@ -33,7 +33,7 @@ TEST_CASE("Construct file unique_resource", "[unique_resource]") { } }); - if (!file) { + if (file.get() == nullptr) { throw std::runtime_error("file didn't open"); } open_file_good = true; @@ -48,7 +48,7 @@ TEST_CASE("unique_resource basic construction and engagement", "[unique_resource { beman::scope::unique_resource r(42, CountingDeleter{&c}); - REQUIRE(static_cast(r)); + // XXX REQUIRE(static_cast(r)); REQUIRE(r.get() == 42); REQUIRE(c.value == 0); } @@ -63,7 +63,7 @@ TEST_CASE("unique_resource release disengages without deleting", "[unique_resour r.release(); - REQUIRE_FALSE(r); + // XXX REQUIRE_FALSE(r); } REQUIRE(c.value == 0); @@ -76,7 +76,7 @@ TEST_CASE("unique_resource reset() destroys current resource", "[unique_resource beman::scope::unique_resource r(1, CountingDeleter{&c}); r.reset(); - REQUIRE_FALSE(r); + // XXX REQUIRE_FALSE(r); REQUIRE(c.value == 1); } @@ -91,7 +91,7 @@ TEST_CASE("unique_resource reset(new_resource) replaces resource", "[unique_reso r.reset(2); - REQUIRE(r); + // XXX REQUIRE(r); REQUIRE(r.get() == 2); REQUIRE(c.value == 1); } @@ -105,8 +105,8 @@ TEST_CASE("unique_resource move constructor transfers ownership", "[unique_resou beman::scope::unique_resource r1(10, CountingDeleter{&c}); beman::scope::unique_resource r2(std::move(r1)); - REQUIRE_FALSE(r1); - REQUIRE(r2); + // XXX REQUIRE_FALSE(r1); + // XXX REQUIRE(r2); REQUIRE(r2.get() == 10); r2.reset(); @@ -122,8 +122,8 @@ TEST_CASE("unique_resource move assignment destroys target before transfer", "[u r2 = std::move(r1); - REQUIRE_FALSE(r1); - REQUIRE(r2); + // XXX REQUIRE_FALSE(r1); + // XXX REQUIRE(r2); REQUIRE(r2.get() == 1); REQUIRE(c2.value == 1); // old r2 destroyed @@ -144,14 +144,14 @@ TEST_CASE("unique_resource destructor is idempotent after release", "[unique_res REQUIRE(c.value == 0); } - +#ifdef BEMAN_SCOPE_USE_FALLBACK TEST_CASE("make_unique_resource_checked disengages on invalid", "[unique_resource]") { Counter c{}; // NOLINT(misc-const-correctness) { auto r = beman::scope::make_unique_resource_checked(-1, -1, CountingDeleter{&c}); - REQUIRE_FALSE(r); + // XXX REQUIRE_FALSE(r); } REQUIRE(c.value == 0); @@ -163,7 +163,7 @@ TEST_CASE("make_unique_resource_checked engages on valid", "[unique_resource]") { auto r = beman::scope::make_unique_resource_checked(3, -1, CountingDeleter{&c}); - REQUIRE(r); + // XXX REQUIRE(r); } REQUIRE(c.value == 1); @@ -192,6 +192,7 @@ TEST_CASE("Open a nonexisting file with make_unique_resource_checked", "[unique_ REQUIRE(open_file_good == false); REQUIRE(close_file_good == false); } +#endif TEST_CASE("unique_resource supports deduction guide", "[unique_resource]") { Counter c{}; // NOLINT(misc-const-correctness) From 7d92b09e87061f169b51a4ab21699c4d43398d50 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Wed, 14 Jan 2026 06:37:23 +0100 Subject: [PATCH 09/22] Test appleclang on CI too --- .github/workflows/cmake.yml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 09db9ed..de7a8ab 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -27,7 +27,18 @@ jobs: fail-fast: false matrix: - os: [windows] + os: [windows, macos] + include: + - { os: macos, uname: appleclang } + # - { os: ubuntu, uname: gcc } + - { os: ubuntu, uname: llvm } + - { os: windows, uname: msvc } + + # TODO(CK): + # type: [shared, static] + # include: + # - { type: shared, shared: YES } + # - { type: static, shared: NO } runs-on: ${{ matrix.os }}-latest @@ -49,19 +60,20 @@ jobs: if: matrix.os != 'windows' uses: aminya/setup-cpp@v1 with: + # compiler: ${{matrix.uname}} compiler: llvm - name: Configure CMake - run: cmake --preset msvc-${{env.BUILD_TYPE}} --log-level=VERBOSE + run: cmake --preset ${{matrix.uname}}-${{env.BUILD_TYPE}} --log-level=VERBOSE - name: Build # Build your program with the given configuration - run: cmake --build --preset msvc-${{env.BUILD_TYPE}} + run: cmake --build --preset ${{matrix.uname}}-${{env.BUILD_TYPE}} - name: Test # Execute tests defined by the CMake configuration - run: ctest --preset msvc-${{env.BUILD_TYPE}} + run: ctest --preset ${{matrix.uname}}-${{env.BUILD_TYPE}} # - name: Install # # Install the project artefacts to CMAKE_INSTALL_PREFIX - # run: cmake --build --preset msvc-${{env.BUILD_TYPE}} --target install + # run: cmake --build --preset ${{matrix.uname}}-${{env.BUILD_TYPE}} --target install From 58335ffadc8fc99e83c29566bf3ab4cf8199de85 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Thu, 15 Jan 2026 21:35:16 +0100 Subject: [PATCH 10/22] Deleted move assignment operators scope_exit does not need to be move-assignable! --- .github/workflows/cmake.yml | 8 +++---- include/beman/scope/scope_impl.hpp | 37 +++++++++++++++++++++--------- makefile | 5 +++- tests/scope_exit.test.cpp | 19 +++++++++++++++ tests/unique_resource_2.test.cpp | 24 ++++++++++++++++++- 5 files changed, 76 insertions(+), 17 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index de7a8ab..2f52cbf 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -27,10 +27,10 @@ jobs: fail-fast: false matrix: - os: [windows, macos] + os: [windows] include: - { os: macos, uname: appleclang } - # - { os: ubuntu, uname: gcc } + # XXX - { os: ubuntu, uname: gcc } - { os: ubuntu, uname: llvm } - { os: windows, uname: msvc } @@ -60,11 +60,11 @@ jobs: if: matrix.os != 'windows' uses: aminya/setup-cpp@v1 with: - # compiler: ${{matrix.uname}} + # XXX compiler: ${{matrix.uname}} compiler: llvm - name: Configure CMake - run: cmake --preset ${{matrix.uname}}-${{env.BUILD_TYPE}} --log-level=VERBOSE + run: cmake --preset ${{matrix.uname}}-${{env.BUILD_TYPE}} --log-level=VERBOSE # XXX -Wdev - name: Build # Build your program with the given configuration diff --git a/include/beman/scope/scope_impl.hpp b/include/beman/scope/scope_impl.hpp index e0cb393..31f0c10 100644 --- a/include/beman/scope/scope_impl.hpp +++ b/include/beman/scope/scope_impl.hpp @@ -63,15 +63,10 @@ class [[nodiscard]] scope_exit { auto operator=(const scope_exit&) -> scope_exit& = delete; scope_exit(const scope_exit&) = delete; - // Move assignment - constexpr auto operator=(scope_exit&& other) noexcept(std::is_nothrow_move_assignable_v) -> scope_exit& { - if (this != &other) { - f = std::move(other.f); - active = other.active; - other.active = false; - } - return *this; - } + // Deleted move assignment + // Does scope_exit need to be move-assignable? LEWG: NO! + constexpr auto operator=(scope_exit&& other) noexcept(std::is_nothrow_move_assignable_v) + -> scope_exit& = delete; // Destructor: call only if scope is exiting normally ~scope_exit() noexcept(noexcept(f())) { @@ -82,6 +77,9 @@ class [[nodiscard]] scope_exit { // Release to prevent execution constexpr auto release() -> void { active = false; } + + // Helper to tests if active + constexpr auto is_active() -> bool { return active; } }; // Factory helper @@ -114,8 +112,12 @@ class [[nodiscard]] scope_fail { scope_fail(const scope_fail&) = delete; auto operator=(const scope_fail&) -> scope_fail& = delete; + // Deleted move assignment // Move assignment - constexpr auto operator=(scope_fail&& other) noexcept(std::is_nothrow_move_assignable_v) -> scope_fail& { + constexpr auto operator=(scope_fail&& other) noexcept(std::is_nothrow_move_assignable_v) + -> scope_fail& = delete; +#if MOVE_ASSIGNMENT_NEEDED + G { if (this != &other) { f = std::move(other.f); active = other.active; @@ -124,6 +126,7 @@ class [[nodiscard]] scope_fail { } return *this; } +#endif // Destructor: call if scope is exiting due to an exception ~scope_fail() noexcept(noexcept(f())) { @@ -134,6 +137,9 @@ class [[nodiscard]] scope_fail { // Release to prevent execution constexpr auto release() -> void { active = false; } + + // Helper to tests if active + constexpr auto is_active() -> bool { return active; } }; // Factory helper @@ -166,8 +172,12 @@ class [[nodiscard]] scope_success { scope_success(const scope_success&) = delete; auto operator=(const scope_success&) -> scope_success& = delete; + // Deleted move assignment // Move assignment - constexpr auto operator=(scope_success&& other) noexcept(std::is_nothrow_move_assignable_v) -> scope_success& { + constexpr auto operator=(scope_success&& other) noexcept(std::is_nothrow_move_assignable_v) + -> scope_success& = delete; +#if MOVE_ASSIGNMENT_NEEDED + { if (this != &other) { f = std::move(other.f); active = other.active; @@ -176,6 +186,7 @@ class [[nodiscard]] scope_success { } return *this; } +#endif // Destructor: call only if scope is exiting normally ~scope_success() noexcept(noexcept(f())) { @@ -186,6 +197,9 @@ class [[nodiscard]] scope_success { // Release to prevent execution constexpr auto release() -> void { active = false; } + + // Helper to tests if active + constexpr auto is_active() -> bool { return active; } }; // Factory helper @@ -271,6 +285,7 @@ class [[nodiscard]] unique_resource { // TODO(CK): missing usecase? constexpr auto get_deleter() const noexcept -> Deleter; + // Helper to tests is_active() // NOTE: check if active; not required from LWG? constexpr explicit operator bool() const noexcept { return active; } }; diff --git a/makefile b/makefile index 22b4fc4..81041fd 100644 --- a/makefile +++ b/makefile @@ -54,11 +54,14 @@ distclean: clean rm -rf build find . -name '*~' -delete +gclean: clean + find build -name '*.gc..' -delete + build/coverage: test mkdir -p $@ coverage: build/coverage - gcovr # XXX -v + gcovr --merge-mode-functions separate # Anything we don't know how to build will use this rule. % :: diff --git a/tests/scope_exit.test.cpp b/tests/scope_exit.test.cpp index 65acf7a..51a4477 100644 --- a/tests/scope_exit.test.cpp +++ b/tests/scope_exit.test.cpp @@ -125,6 +125,25 @@ TEST_CASE("scope_exit handles nested guards in correct order", "[scope_exit][adv REQUIRE(trace == "second first "); } +// fails +// #include +// +// TEST_CASE("scope_exit move assignment transfers ownership", "[scope_exit][advanced]") { +// bool cleanup_ran = false; +// +// { +// scope_exit> guard1([&]{ cleanup_ran = true; }); +// scope_exit> guard2([&]{}); +// +// guard2 = std::move(guard1); +// +// REQUIRE_FALSE(guard1.is_active()); +// REQUIRE(guard2.is_active()); +// } +// +// REQUIRE(cleanup_ran == false); +// } + // fails // TEST_CASE("scope_exit cleanup handles custom object with side effects", "[scope_exit][advanced]") { // struct Tracer { diff --git a/tests/unique_resource_2.test.cpp b/tests/unique_resource_2.test.cpp index da1d136..b613d5c 100644 --- a/tests/unique_resource_2.test.cpp +++ b/tests/unique_resource_2.test.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace { @@ -199,7 +200,7 @@ TEST_CASE("unique_resource supports deduction guide", "[unique_resource]") { beman::scope::unique_resource r(123, CountingDeleter{&c}); - static_assert(std::is_same_v >); + static_assert(std::is_same_v>); r.reset(); REQUIRE(c.value == 1); @@ -253,3 +254,24 @@ TEST_CASE("unique_resource operator* returns reference to resource", "[unique_re // Modifying through cref would fail to compile (correct) } + +struct Foo { + int value = 0; +}; + +TEST_CASE("unique_resource operator-> works", "[unique_resource]") { + bool deleted = false; + Foo* raw = new Foo{42}; + + // Use std::function for the deleter + beman::scope::unique_resource> r(raw, [&](Foo* p) { + deleted = true; + delete p; + }); + + REQUIRE(r->value == 42); + r->value = 100; + REQUIRE(r->value == 100); + + REQUIRE_FALSE(deleted); // deleter not called yet +} From 1a86a992dfa6dbe3e8ca246edb5dd21474e5a454 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Thu, 15 Jan 2026 22:04:00 +0100 Subject: [PATCH 11/22] Try to BUILD_SHARED_LIBS on windows add get_version() to CXX_MODULE to export some code; --- CMakePresets.json | 1 + include/beman/scope/beman.scope.cppm | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CMakePresets.json b/CMakePresets.json index 91bb5e6..54e21c1 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -116,6 +116,7 @@ ], "cacheVariables": { "CMAKE_CXX_STANDARD": "23", + "BUILD_SHARED_LIBS": true, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" } } diff --git a/include/beman/scope/beman.scope.cppm b/include/beman/scope/beman.scope.cppm index 59ce381..a2de359 100644 --- a/include/beman/scope/beman.scope.cppm +++ b/include/beman/scope/beman.scope.cppm @@ -8,6 +8,9 @@ module; export module beman.scope; export namespace beman::scope { + +constexpr int get_version() { return 0; } + using ::beman::scope::scope_exit; using ::beman::scope::scope_fail; using ::beman::scope::scope_success; From 8c636279a9aec608ea0938b8faafc02389864c8f Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Thu, 15 Jan 2026 23:38:26 +0100 Subject: [PATCH 12/22] Use GenerateExportHeader cmake module --- CMakeLists.txt | 23 ++++++++++++++++++++--- include/beman/scope/beman.scope.cppm | 3 --- include/beman/scope/scope_impl.hpp | 10 ++++++---- makefile | 2 +- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce92578..c4d14b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,8 @@ else() endif() set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN TRUE) # [CMAKE.SKIP_TESTS] option( @@ -53,25 +55,40 @@ message( if(CMAKE_CXX_SCAN_FOR_MODULES) add_library(beman.scope) +else() + add_library(beman.scope INTERFACE) +endif() + +include(GenerateExportHeader) + +generate_export_header( + beman.scope + BASE_NAME beman.scope + EXPORT_FILE_NAME beman/scope/modules_export.hpp +) + +if(CMAKE_CXX_SCAN_FOR_MODULES) target_sources( beman.scope PUBLIC FILE_SET HEADERS - BASE_DIRS include + BASE_DIRS include ${CMAKE_CURRENT_BINARY_DIR} FILES include/beman/scope/scope.hpp + ${CMAKE_CURRENT_BINARY_DIR}/beman/scope/modules_export.hpp + PUBLIC FILE_SET CXX_MODULES BASE_DIRS include FILES include/beman/scope/beman.scope.cppm ) else() - add_library(beman.scope INTERFACE) target_sources( beman.scope INTERFACE FILE_SET HEADERS - BASE_DIRS include + BASE_DIRS include ${CMAKE_CURRENT_BINARY_DIR} FILES include/beman/scope/scope.hpp + ${CMAKE_CURRENT_BINARY_DIR}/beman/scope/modules_export.hpp ) endif() diff --git a/include/beman/scope/beman.scope.cppm b/include/beman/scope/beman.scope.cppm index a2de359..59ce381 100644 --- a/include/beman/scope/beman.scope.cppm +++ b/include/beman/scope/beman.scope.cppm @@ -8,9 +8,6 @@ module; export module beman.scope; export namespace beman::scope { - -constexpr int get_version() { return 0; } - using ::beman::scope::scope_exit; using ::beman::scope::scope_fail; using ::beman::scope::scope_success; diff --git a/include/beman/scope/scope_impl.hpp b/include/beman/scope/scope_impl.hpp index 31f0c10..fbb691d 100644 --- a/include/beman/scope/scope_impl.hpp +++ b/include/beman/scope/scope_impl.hpp @@ -37,6 +37,8 @@ // clang-format on #ifdef BEMAN_SCOPE_USE_FALLBACK +#include "beman/scope/modules_export.hpp" + #include #include #include @@ -46,7 +48,7 @@ namespace beman::scope { // TODO(CK): make a std::experimental::scope_exit::scope_exit conform // implementation template -class [[nodiscard]] scope_exit { +class [[nodiscard]] BEMAN_SCOPE_EXPORT scope_exit { F f; bool active = true; @@ -92,7 +94,7 @@ auto make_scope_exit(F f) -> scope_exit { // TODO(CK): make a std::experimental::scope_fail::scope_fail conform // implementation template -class [[nodiscard]] scope_fail { +class [[nodiscard]] BEMAN_SCOPE_EXPORT scope_fail { F f; bool active = true; int exception_count{}; @@ -152,7 +154,7 @@ constexpr auto make_scope_fail(F&& f) -> scope_fail> { // TODO(CK): make a std::experimental::scope_success::scope_success conform // implementation template -class [[nodiscard]] scope_success { +class [[nodiscard]] BEMAN_SCOPE_EXPORT scope_success { F f; bool active = true; int exception_count{}; @@ -210,7 +212,7 @@ constexpr auto make_scope_success(F&& f) -> scope_success> { } template -class [[nodiscard]] unique_resource { +class [[nodiscard]] BEMAN_SCOPE_EXPORT unique_resource { Resource resource; Deleter deleter; bool active = true; diff --git a/makefile b/makefile index 81041fd..3ff2d1a 100644 --- a/makefile +++ b/makefile @@ -32,7 +32,7 @@ else ifeq (${hostSystemName},Linux) export CXX=clang++-20 endif -.PHONY: all install coverage clean distclean +.PHONY: all install coverage gclean distclean all: build/compile_commands.json ln -sf $< . From f51bfa4b0a85363a3461bebafff2d1abce6fc8d6 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Sat, 17 Jan 2026 00:07:13 +0100 Subject: [PATCH 13/22] Quickfix for missing export header This is needed if only an interface library is build --- CMakeLists.txt | 18 +++++++++--------- include/beman/scope/scope_impl.hpp | 8 ++++++-- makefile | 4 ++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c4d14b8..4dc32b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,18 +55,18 @@ message( if(CMAKE_CXX_SCAN_FOR_MODULES) add_library(beman.scope) + + include(GenerateExportHeader) + + generate_export_header( + beman.scope + BASE_NAME beman.scope + EXPORT_FILE_NAME beman/scope/modules_export.hpp + ) else() add_library(beman.scope INTERFACE) endif() -include(GenerateExportHeader) - -generate_export_header( - beman.scope - BASE_NAME beman.scope - EXPORT_FILE_NAME beman/scope/modules_export.hpp -) - if(CMAKE_CXX_SCAN_FOR_MODULES) target_sources( beman.scope @@ -88,7 +88,7 @@ else() FILE_SET HEADERS BASE_DIRS include ${CMAKE_CURRENT_BINARY_DIR} FILES include/beman/scope/scope.hpp - ${CMAKE_CURRENT_BINARY_DIR}/beman/scope/modules_export.hpp + # NO! ${CMAKE_CURRENT_BINARY_DIR}/beman/scope/modules_export.hpp ) endif() diff --git a/include/beman/scope/scope_impl.hpp b/include/beman/scope/scope_impl.hpp index fbb691d..b05159c 100644 --- a/include/beman/scope/scope_impl.hpp +++ b/include/beman/scope/scope_impl.hpp @@ -34,10 +34,14 @@ # error "Standard present but __cpp_lib_scope_exit not defined" # endif #endif -// clang-format on #ifdef BEMAN_SCOPE_USE_FALLBACK -#include "beman/scope/modules_export.hpp" +# if __has_include("beman/scope/modules_export.hpp") +# include "beman/scope/modules_export.hpp" +# else +# define BEMAN_SCOPE_EXPORT +# endif +// clang-format on #include #include diff --git a/makefile b/makefile index 3ff2d1a..bf4e84e 100644 --- a/makefile +++ b/makefile @@ -50,8 +50,8 @@ build/compile_commands.json: CMakeLists.txt makefile install: build/cmake_install.cmake cmake --install build -distclean: clean - rm -rf build +distclean: # XXX clean + rm -rf build compile_commands.json find . -name '*~' -delete gclean: clean From c9197e604851cc852543dba10ee562d2ce4c1a07 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Sat, 17 Jan 2026 16:23:47 +0100 Subject: [PATCH 14/22] Use ctest --build-and-test to check the installed version --- CMakeLists.txt | 49 +++++++++++++++------------- CMakePresets.json | 3 +- examples/CMakeLists.txt | 20 +++++++++++- examples/scope-module.cpp | 10 +++--- include/beman/scope/beman.scope.cppm | 2 +- include/beman/scope/scope.hpp | 2 +- include/beman/scope/scope_impl.hpp | 7 ++-- tests/CMakeLists.txt | 28 +++++++++++++++- tests/module.test.cpp | 18 +++++----- tests/unique_resource.test.cpp | 2 +- 10 files changed, 94 insertions(+), 47 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dc32b7..d0a72d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,12 +47,14 @@ option( ) message( - "Compiler is: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}" + "Compiler is: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}" ) message( - "cmake is: ${CMAKE_VERSION} modules scan : ${CMAKE_CXX_SCAN_FOR_MODULES}" + "cmake is: ${CMAKE_VERSION} modules scan: ${CMAKE_CXX_SCAN_FOR_MODULES}" ) +# gersemi: on + if(CMAKE_CXX_SCAN_FOR_MODULES) add_library(beman.scope) @@ -72,51 +74,51 @@ if(CMAKE_CXX_SCAN_FOR_MODULES) beman.scope PUBLIC FILE_SET HEADERS - BASE_DIRS include ${CMAKE_CURRENT_BINARY_DIR} - FILES include/beman/scope/scope.hpp - ${CMAKE_CURRENT_BINARY_DIR}/beman/scope/modules_export.hpp - + BASE_DIRS include ${CMAKE_CURRENT_BINARY_DIR} + FILES + include/beman/scope/scope.hpp + include/beman/scope/scope_impl.hpp + ${CMAKE_CURRENT_BINARY_DIR}/beman/scope/modules_export.hpp PUBLIC FILE_SET CXX_MODULES - BASE_DIRS include - FILES include/beman/scope/beman.scope.cppm + BASE_DIRS include + FILES include/beman/scope/beman.scope.cppm ) else() target_sources( beman.scope INTERFACE FILE_SET HEADERS - BASE_DIRS include ${CMAKE_CURRENT_BINARY_DIR} - FILES include/beman/scope/scope.hpp - # NO! ${CMAKE_CURRENT_BINARY_DIR}/beman/scope/modules_export.hpp + BASE_DIRS include ${CMAKE_CURRENT_BINARY_DIR} + FILES include/beman/scope/scope.hpp + # NO! ${CMAKE_CURRENT_BINARY_DIR}/beman/scope/modules_export.hpp ) endif() +target_compile_features(beman.scope PUBLIC cxx_std_20) add_library(beman::scope ALIAS beman.scope) set_target_properties( beman.scope - PROPERTIES - VERIFY_INTERFACE_HEADER_SETS ON - EXPORT_NAME scope + PROPERTIES VERIFY_INTERFACE_HEADER_SETS ON EXPORT_NAME scope ) include(GNUInstallDirs) +set(package_install_dir ${CMAKE_INSTALL_LIBDIR}/cmake/beman.scope) + +# TBD: always? CK install( TARGETS beman.scope COMPONENT beman.scope EXPORT beman.scope-targets - - FILE_SET CXX_MODULES - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILE_SET CXX_MODULES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} CXX_MODULES_BMI - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/beman.scope/bmi-${CMAKE_CXX_COMPILER_ID}_$ + DESTINATION + ${package_install_dir}/bmi-${CMAKE_CXX_COMPILER_ID}_$ FILE_SET HEADERS ) -# gersemi: on - if(BEMAN_SCOPE_INSTALL_CONFIG_FILE_PACKAGE) include(CMakePackageConfigHelpers) @@ -129,13 +131,13 @@ if(BEMAN_SCOPE_INSTALL_CONFIG_FILE_PACKAGE) FILES cmake/beman.scope-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/beman.scope-config-version.cmake - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/beman.scope + DESTINATION ${package_install_dir} COMPONENT beman.scope ) install( EXPORT beman.scope-targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/beman.scope + DESTINATION ${package_install_dir} NAMESPACE beman:: CXX_MODULES_DIRECTORY cxx-modules @@ -143,8 +145,9 @@ if(BEMAN_SCOPE_INSTALL_CONFIG_FILE_PACKAGE) ) endif() +enable_testing() + if(BEMAN_SCOPE_BUILD_TESTS) - enable_testing() add_subdirectory(tests) endif() diff --git a/CMakePresets.json b/CMakePresets.json index 54e21c1..659825c 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -104,6 +104,7 @@ ], "cacheVariables": { "CMAKE_CXX_STANDARD": "23", + "BUILD_SHARED_LIBS": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" } }, @@ -116,7 +117,7 @@ ], "cacheVariables": { "CMAKE_CXX_STANDARD": "23", - "BUILD_SHARED_LIBS": true, + "BUILD_SHARED_LIBS": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" } } diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 0f1d6d3..60bcdea 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,9 +1,27 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +cmake_minimum_required(VERSION 3.28...4.2) + +project(beman.scope.example LANGUAGES CXX) + +if(PROJECT_IS_TOP_LEVEL) + if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 23) + set(CMAKE_CXX_EXTENSIONS YES) + set(CMAKE_CXX_STANDARD_REQUIRED YES) + endif() + set(CMAKE_CXX_SCAN_FOR_MODULES OFF) + + find_package(beman.scope REQUIRED) + + enable_testing() +endif() + set(ALL_EXAMPLES scope_example unique_resource unique_resource-file) # module tests will only compile with gcc15 or clang20 and above -if(CMAKE_CXX_SCAN_FOR_MODULES AND CMAKE_CXX_MODULE_STD) +# NOTE: needs C++23 or newer! CK +if(CMAKE_CXX_SCAN_FOR_MODULES) list(APPEND ALL_EXAMPLES scope-module) endif() diff --git a/examples/scope-module.cpp b/examples/scope-module.cpp index 22dd6d3..01d7cc7 100644 --- a/examples/scope-module.cpp +++ b/examples/scope-module.cpp @@ -15,12 +15,12 @@ // destroy noisy // scope exit: true success: true fail: false -// #ifdef HAS_MODULE_STD +// NOTE: this needs C++23! CK +#ifdef HAS_MODULE_STD import std; -// #else -// NOTE: this needs C++23! -// #include -// #endif +#else +#include +#endif import beman.scope; diff --git a/include/beman/scope/beman.scope.cppm b/include/beman/scope/beman.scope.cppm index 59ce381..f862dae 100644 --- a/include/beman/scope/beman.scope.cppm +++ b/include/beman/scope/beman.scope.cppm @@ -3,7 +3,7 @@ // g++-15 -std=c++26 -O2 -fmodules -fmodule-only -c ${scopetop}/include/beman/scope/beman.scope.cppm module; -#include "scope.hpp" +#include "beman/scope/scope.hpp" export module beman.scope; diff --git a/include/beman/scope/scope.hpp b/include/beman/scope/scope.hpp index 9b45233..5631e1b 100644 --- a/include/beman/scope/scope.hpp +++ b/include/beman/scope/scope.hpp @@ -443,7 +443,7 @@ using scope_fail = scope_guard; using deleter_type = std::decay_t; + unique_resource ur(resource_type{}, std::forward(d)); if (r == invalid) { - // Disengaged resource - unique_resource ur(resource_type{}, std::forward(d)); ur.release(); // disengage immediately - return ur; } - - return unique_resource(std::forward(r), std::forward(d)); + return ur; } } // namespace beman::scope diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6acb735..c30035b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,7 +7,7 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/catchorg/Catch2.git GIT_TAG v3.11.0 EXCLUDE_FROM_ALL - # FIND_PACKAGE_ARGS 3.11 + # NO! FIND_PACKAGE_ARGS 3.11 ) FetchContent_MakeAvailable(Catch2) @@ -37,3 +37,29 @@ foreach(testname ${ALL_TESTNAMES}) ) catch_discover_tests(test.${testname}) endforeach() + +if(BEMAN_SCOPE_INSTALL_CONFIG_FILE_PACKAGE) + # test if the targets are usable from the install directory + add_test( + NAME install-to-stagedir + COMMAND + ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR} --prefix + ${CMAKE_BINARY_DIR}/stagedir --config $ + ) + add_test( + NAME find-package-test + COMMAND + ${CMAKE_CTEST_COMMAND} # --verbose + --output-on-failure -C $ --build-and-test + "${CMAKE_SOURCE_DIR}/examples" + "${CMAKE_CURRENT_BINARY_DIR}/find-package-test" --build-generator + ${CMAKE_GENERATOR} --build-makeprogram ${CMAKE_MAKE_PROGRAM} + --build-options "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}" + "-DCMAKE_CXX_MODULE_STD=${CMAKE_CXX_MODULE_STD}" + "-DCMAKE_CXX_SCAN_FOR_MODULES=${CMAKE_CXX_SCAN_FOR_MODULES}" + "-DCMAKE_BUILD_TYPE=$" + "-DCMAKE_PREFIX_PATH=${CMAKE_BINARY_DIR}/stagedir" + ) +endif() diff --git a/tests/module.test.cpp b/tests/module.test.cpp index a4b22fc..ce898ea 100644 --- a/tests/module.test.cpp +++ b/tests/module.test.cpp @@ -11,27 +11,29 @@ struct DummyResource { DummyResource(bool& flag) : cleaned(flag) { cleaned = false; } - bool is_clean() const { return cleaned; } + [[nodiscard]] bool is_clean() const { return cleaned; } }; TEST_CASE("module-test", "[scope_module_test]") { - bool exit_ran, success_ran, fail_ran = false; - bool cleaned = true; + bool exit_ran{}; + bool success_ran{}; + bool fail_ran{}; + bool cleaned{true}; { // clang-format off beman::scope::scope_exit _se([&exit_ran] { exit_ran = true; }); beman::scope::scope_success _ss([&success_ran] { success_ran = true; }); beman::scope::scope_fail _sf([&fail_ran] { fail_ran = true; }); - auto resource_ptr = beman::scope::unique_resource(new DummyResource(cleaned), - [](DummyResource* ptr) { ptr->cleaned =true; delete ptr; }); - REQUIRE(cleaned == false); - REQUIRE(resource_ptr->is_clean() == false); + auto resource_ptr = beman::scope::unique_resource(new DummyResource(cleaned), + [](DummyResource* ptr) { ptr->cleaned = true; delete ptr; }); // clang-format on + + REQUIRE(cleaned == false); + REQUIRE(resource_ptr->is_clean() == false); } // Normal scope exit REQUIRE(exit_ran == true); REQUIRE(success_ran == true); REQUIRE(fail_ran == false); REQUIRE(cleaned == true); - } diff --git a/tests/unique_resource.test.cpp b/tests/unique_resource.test.cpp index 48f611d..9edd61e 100644 --- a/tests/unique_resource.test.cpp +++ b/tests/unique_resource.test.cpp @@ -68,7 +68,7 @@ TEST_CASE("unique_resource does not clean up after release", "[unique_resource]" [](DummyResource r) { *(r.cleanedUp) = true; } ); - res.release(); //no cleanup run + res.release(); // no cleanup run } REQUIRE(cleaned == false); From 7af734d63cd96cf64ecbdef83f49f10a905ad108 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Sat, 17 Jan 2026 19:51:45 +0100 Subject: [PATCH 15/22] The final cut Prevent warning: placeholder variables are a C++2c extension --- CMakeLists.txt | 6 ++++-- CMakePresets.json | 14 ++++++-------- examples/scope-module.cpp | 11 +++++++---- include/beman/scope/beman.scope.cppm | 2 +- include/beman/scope/scope.hpp | 2 +- include/beman/scope/scope_impl.hpp | 8 ++++---- 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0a72d5..ffb5c17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,18 +84,20 @@ if(CMAKE_CXX_SCAN_FOR_MODULES) BASE_DIRS include FILES include/beman/scope/beman.scope.cppm ) + target_compile_features(beman.scope PUBLIC cxx_std_20) else() target_sources( beman.scope INTERFACE FILE_SET HEADERS BASE_DIRS include ${CMAKE_CURRENT_BINARY_DIR} - FILES include/beman/scope/scope.hpp + FILES + include/beman/scope/scope.hpp + include/beman/scope/scope_impl.hpp # NO! ${CMAKE_CURRENT_BINARY_DIR}/beman/scope/modules_export.hpp ) endif() -target_compile_features(beman.scope PUBLIC cxx_std_20) add_library(beman::scope ALIAS beman.scope) set_target_properties( diff --git a/CMakePresets.json b/CMakePresets.json index 659825c..b48e93b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -7,9 +7,11 @@ "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { - "CMAKE_CXX_STANDARD": "20", - "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", - "CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "./infra/cmake/use-fetch-content.cmake" + "CMAKE_CXX_EXTENSIONS": true, + "CMAKE_CXX_STANDARD": "23", + "CMAKE_CXX_STANDARD_REQUIRED": true, + "CMAKE_EXPORT_COMPILE_COMMANDS": true, + "CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "infra/cmake/use-fetch-content.cmake" } }, { @@ -68,7 +70,7 @@ "_release-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-libc++-toolchain.cmake" } }, { @@ -79,7 +81,6 @@ "_debug-base" ], "cacheVariables": { - "CMAKE_CXX_STANDARD": "23", "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" } }, @@ -91,7 +92,6 @@ "_release-base" ], "cacheVariables": { - "CMAKE_CXX_STANDARD": "23", "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" } }, @@ -103,7 +103,6 @@ "_debug-base" ], "cacheVariables": { - "CMAKE_CXX_STANDARD": "23", "BUILD_SHARED_LIBS": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" } @@ -116,7 +115,6 @@ "_release-base" ], "cacheVariables": { - "CMAKE_CXX_STANDARD": "23", "BUILD_SHARED_LIBS": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" } diff --git a/examples/scope-module.cpp b/examples/scope-module.cpp index 01d7cc7..da0427d 100644 --- a/examples/scope-module.cpp +++ b/examples/scope-module.cpp @@ -32,12 +32,14 @@ struct noisy_resource { int main() { - bool exit_ran, success_ran, fail_ran = false; + bool exit_ran{}; + bool success_ran{}; + bool fail_ran{}; { std::print("--> scope start\n"); - beman::scope::scope_exit _([&exit_ran] { exit_ran = true; }); - beman::scope::scope_success _([&success_ran] { success_ran = true; }); - beman::scope::scope_fail _([&fail_ran] { fail_ran = true; }); + beman::scope::scope_exit _se([&exit_ran] { exit_ran = true; }); + beman::scope::scope_success _ss([&success_ran] { success_ran = true; }); + beman::scope::scope_fail _sf([&fail_ran] { fail_ran = true; }); auto resource_ptr = beman::scope::unique_resource(new noisy_resource(), // Cleanup function [](noisy_resource* ptr) { delete ptr; }); @@ -46,3 +48,4 @@ int main() { std::print("scope exit: {} success: {} fail: {} \n", exit_ran, success_ran, fail_ran); } +// clang-format on diff --git a/include/beman/scope/beman.scope.cppm b/include/beman/scope/beman.scope.cppm index f862dae..e9bfaec 100644 --- a/include/beman/scope/beman.scope.cppm +++ b/include/beman/scope/beman.scope.cppm @@ -3,7 +3,7 @@ // g++-15 -std=c++26 -O2 -fmodules -fmodule-only -c ${scopetop}/include/beman/scope/beman.scope.cppm module; -#include "beman/scope/scope.hpp" +#include export module beman.scope; diff --git a/include/beman/scope/scope.hpp b/include/beman/scope/scope.hpp index 5631e1b..0222c5c 100644 --- a/include/beman/scope/scope.hpp +++ b/include/beman/scope/scope.hpp @@ -443,7 +443,7 @@ using scope_fail = scope_guard #endif // BEMAN_SCOPE_USE_STD_EXPERIMENTAL diff --git a/include/beman/scope/scope_impl.hpp b/include/beman/scope/scope_impl.hpp index 45e607c..161d397 100644 --- a/include/beman/scope/scope_impl.hpp +++ b/include/beman/scope/scope_impl.hpp @@ -319,10 +319,10 @@ constexpr auto make_unique_resource_checked(R&& r, const Invalid& invalid, D&& d #elifdef BEMAN_SCOPE_USE_STD_EXPERIMENTAL namespace beman::scope { -using std::experimental::scope_exit; -using std::experimental::scope_fail; -using std::experimental::scope_success; -using std::experimental::unique_resource; +using ::std::experimental::scope_exit; +using ::std::experimental::scope_fail; +using ::std::experimental::scope_success; +using ::std::experimental::unique_resource; } // namespace beman::scope // #endif // BEMAN_SCOPE_USE_FALLBACK From 3c99179bef3095d01c11b945fdce87fbc459a86b Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Sat, 17 Jan 2026 20:32:30 +0100 Subject: [PATCH 16/22] Prevent use of std::print() for now --- examples/scope-module.cpp | 37 +++++++++++++++++++++++++------------ tests/module.test.cpp | 6 +++++- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/examples/scope-module.cpp b/examples/scope-module.cpp index da0427d..2bb67a8 100644 --- a/examples/scope-module.cpp +++ b/examples/scope-module.cpp @@ -15,37 +15,50 @@ // destroy noisy // scope exit: true success: true fail: false +#include + // NOTE: this needs C++23! CK #ifdef HAS_MODULE_STD import std; -#else -#include #endif +// for g++-15 the order is important -- import after #includes import beman.scope; -// clang-format off -struct noisy_resource { - noisy_resource() { std::print( "construct noisy\n" ); } - ~noisy_resource() { std::print( "destroy noisy\n" ); } +namespace { + +struct DummyResource { + bool& cleaned; + + DummyResource(bool& flag) : cleaned(flag) { cleaned = false; } + + [[nodiscard]] bool is_clean() const { return cleaned; } }; +} // namespace + int main() { bool exit_ran{}; bool success_ran{}; bool fail_ran{}; + bool cleaned{true}; { - std::print("--> scope start\n"); + // clang-format off beman::scope::scope_exit _se([&exit_ran] { exit_ran = true; }); beman::scope::scope_success _ss([&success_ran] { success_ran = true; }); beman::scope::scope_fail _sf([&fail_ran] { fail_ran = true; }); - auto resource_ptr = beman::scope::unique_resource(new noisy_resource(), - // Cleanup function - [](noisy_resource* ptr) { delete ptr; }); - std::print("--> scope end\n"); + auto resource_ptr = beman::scope::unique_resource(new DummyResource(cleaned), + [](DummyResource* ptr) { ptr->cleaned = true; delete ptr; }); + // clang-format on + + assert(cleaned == false); + assert(resource_ptr->is_clean() == false); } // Normal scope exit - std::print("scope exit: {} success: {} fail: {} \n", exit_ran, success_ran, fail_ran); + assert(exit_ran == true); + assert(success_ran == true); + assert(fail_ran == false); + assert(cleaned == true); } // clang-format on diff --git a/tests/module.test.cpp b/tests/module.test.cpp index ce898ea..d5f7ec6 100644 --- a/tests/module.test.cpp +++ b/tests/module.test.cpp @@ -3,9 +3,11 @@ #define CATCH_CONFIG_MAIN #include -// for g++-15 the order is important -- import after includes +// for g++-15 the order is important -- import after #includes import beman.scope; +namespace { + struct DummyResource { bool& cleaned; @@ -14,6 +16,8 @@ struct DummyResource { [[nodiscard]] bool is_clean() const { return cleaned; } }; +} // namespace + TEST_CASE("module-test", "[scope_module_test]") { bool exit_ran{}; bool success_ran{}; From 9b632076ce670935935a7824c098f61cb430cb49 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Tue, 27 Jan 2026 11:32:06 +0100 Subject: [PATCH 17/22] Add infra submodule and use it --- .github/workflows/ci_tests.yml | 53 +++++++++++++------ .github/workflows/pre-commit-check.yml | 13 +++++ .github/workflows/pre-commit-update.yml | 15 ++++++ .gitignore | 8 ++- .pre-commit-config.yaml | 4 +- CMakeLists.txt | 4 +- CMakePresets.json | 30 ++++++++++- include/beman/scope/beman.scope.cppm | 2 +- infra/.beman_submodule | 4 ++ infra/.pre-commit-config.yaml | 3 ++ .../cmake/beman-install-library-config.cmake | 6 +-- infra/cmake/use-fetch-content.cmake | 5 +- makefile | 6 ++- tests/CMakeLists.txt | 8 ++- 14 files changed, 130 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/pre-commit-check.yml create mode 100644 .github/workflows/pre-commit-update.yml diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 9a463d5..f2475d5 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -10,14 +10,13 @@ on: workflow_dispatch: schedule: - cron: '30 15 * * *' - - cron: "0 0 * * 0" jobs: beman-submodule-check: - uses: ./.github/workflows/reusable-beman-submodule-check.yml + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.2.1 preset-test: - uses: ./.github/workflows/reusable-beman-preset-test.yml + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-preset-test.yml@1.2.1 with: matrix_config: > [ @@ -25,10 +24,14 @@ jobs: {"preset": "gcc-release", "image": "ghcr.io/bemanproject/infra-containers-gcc:latest"}, {"preset": "llvm-debug", "image": "ghcr.io/bemanproject/infra-containers-clang:latest"}, {"preset": "llvm-release", "image": "ghcr.io/bemanproject/infra-containers-clang:latest"}, + {"preset": "appleclang-debug", "runner": "macos-latest"}, + {"preset": "appleclang-release", "runner": "macos-latest"}, + {"preset": "msvc-debug", "runner": "windows-latest"}, + {"preset": "msvc-release", "runner": "windows-latest"} ] build-and-test: - uses: ./.github/workflows/reusable-beman-build-and-test.yml + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-build-and-test.yml@1.2.1 with: matrix_config: > { @@ -51,7 +54,7 @@ jobs: } ] }, - { "versions": ["14", "13"], + { "versions": ["14"], "tests": [ { "cxxversions": ["c++26", "c++23", "c++20"], "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] @@ -60,11 +63,11 @@ jobs: } ], "clang": [ - { "versions": ["20"], + { "versions": ["21"], "tests": [ {"cxxversions": ["c++26"], "tests": [ - { "stdlibs": ["libstdc++"], + { "stdlibs": ["libc++"], "tests": [ "Debug.Default", "Release.Default", "Release.TSan", "Release.MaxSan", "Debug.Werror", "Debug.Dynamic" @@ -74,16 +77,38 @@ jobs: }, { "cxxversions": ["c++23", "c++20"], "tests": [ - {"stdlibs": ["libstdc++"], "tests": ["Release.Default"]} + {"stdlibs": ["libc++"], "tests": ["Release.Default"]} ] } ] }, - { "versions": ["19"], + { "versions": ["20", "19"], "tests": [ { "cxxversions": ["c++26", "c++23", "c++20"], "tests": [ - {"stdlibs": ["libstdc++"], "tests": ["Release.Default"]} + {"stdlibs": ["libc++"], "tests": ["Release.Default"]} + ] + } + ] + } + ], + "appleclang": [ + { "versions": ["latest"], + "tests": [ + { "cxxversions": ["c++23", "c++20"], + "tests": [{ "stdlibs": ["libc++"], "tests": ["Release.Default"]}] + } + ] + } + ], + "msvc": [ + { "versions": ["latest"], + "tests": [ + { "cxxversions": ["c++23"], + "tests": [ + { "stdlibs": ["stl"], + "tests": ["Debug.Default", "Release.Default", "Release.MaxSan"] + } ] } ] @@ -93,9 +118,5 @@ jobs: create-issue-when-fault: needs: [preset-test, build-and-test] - if: failure() && github.event.schedule == '30 15 * * *' - uses: ./.github/workflows/reusable-beman-create-issue-when-fault.yml - - auto-update-pre-commit: - if: github.event.schedule == '00 16 * * 0' - uses: ./.github/workflows/reusable-beman-update-pre-commit.yml + if: failure() && github.event_name == 'schedule' + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-create-issue-when-fault.yml@1.2.1 diff --git a/.github/workflows/pre-commit-check.yml b/.github/workflows/pre-commit-check.yml new file mode 100644 index 0000000..5749343 --- /dev/null +++ b/.github/workflows/pre-commit-check.yml @@ -0,0 +1,13 @@ +name: Lint Check (pre-commit) + +on: + # We have to use pull_request_target here as pull_request does not grant + # enough permission for reviewdog + pull_request_target: + push: + branches: + - main + +jobs: + pre-commit: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-pre-commit.yml@1.2.1 diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml new file mode 100644 index 0000000..9261dbf --- /dev/null +++ b/.github/workflows/pre-commit-update.yml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +name: Weekly pre-commit autoupdate + +on: + workflow_dispatch: + schedule: + - cron: "0 16 * * 0" + +jobs: + auto-update-pre-commit: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-update-pre-commit.yml@1.2.1 + secrets: + APP_ID: ${{ secrets.AUTO_PR_BOT_APP_ID }} + PRIVATE_KEY: ${{ secrets.AUTO_PR_BOT_PRIVATE_KEY }} diff --git a/.gitignore b/.gitignore index baadefc..0e4b215 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ -.cache +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + /compile_commands.json +/CMakeUserPresets.json /build +**/_deps/ +**/CMakeFiles/ +/cmake/presets # ignore emacs temp files *~ @@ -8,3 +13,4 @@ # ignore vscode settings .vscode +.cache diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a4daacd..cecf800 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,14 +14,14 @@ repos: # This brings in a portable version of clang-format. # See also: https://github.com/ssciwr/clang-format-wheel - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v21.1.6 + rev: v21.1.8 hooks: - id: clang-format types_or: [c++, c, json] # CMake linting and formatting - repo: https://github.com/BlankSpruce/gersemi - rev: 0.23.1 + rev: 0.25.2 hooks: - id: gersemi name: CMake linting diff --git a/CMakeLists.txt b/CMakeLists.txt index ffb5c17..a434572 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,9 +12,9 @@ project( # gersemi: off # Modules opt in only on compilers that support it: msvc, g++-15 and clang-20+ -if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 20) +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 20) set(CMAKE_CXX_SCAN_FOR_MODULES 1) -elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15) +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15) set(CMAKE_CXX_SCAN_FOR_MODULES 1) elseif(MSVC) set(CMAKE_CXX_SCAN_FOR_MODULES 1) diff --git a/CMakePresets.json b/CMakePresets.json index b48e93b..194a873 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -7,16 +7,30 @@ "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { + "BEMAN_USE_STD_MODULE": true, "CMAKE_CXX_EXTENSIONS": true, + "CMAKE_CXX_SCAN_FOR_MODULES": true, "CMAKE_CXX_STANDARD": "23", "CMAKE_CXX_STANDARD_REQUIRED": true, "CMAKE_EXPORT_COMPILE_COMMANDS": true, + "CMAKE_SKIP_TEST_ALL_DEPENDENCY": false, "CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "infra/cmake/use-fetch-content.cmake" } }, { "name": "_debug-base", "hidden": true, + "warnings": { + "dev": true, + "deprecated": true, + "uninitialized": true, + "unusedCli": true, + "systemVars": false + }, + "errors": { + "dev": false, + "deprecated": false + }, "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "BEMAN_BUILDSYS_SANITIZER": "MaxSan" @@ -59,7 +73,12 @@ "_debug-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-toolchain.cmake" + "BEMAN_USE_STD_MODULE": false, + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-libc++-toolchain.cmake" + }, + "environment": { + "CXX": "clang++", + "CMAKE_CXX_FLAGS": "-stdlib=libc++" } }, { @@ -70,7 +89,12 @@ "_release-base" ], "cacheVariables": { + "BEMAN_USE_STD_MODULE": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-libc++-toolchain.cmake" + }, + "environment": { + "CXX": "clang++", + "CMAKE_CXX_FLAGS": "-stdlib=libc++" } }, { @@ -81,6 +105,8 @@ "_debug-base" ], "cacheVariables": { + "BEMAN_USE_STD_MODULE": false, + "CMAKE_CXX_SCAN_FOR_MODULES": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" } }, @@ -92,6 +118,8 @@ "_release-base" ], "cacheVariables": { + "BEMAN_USE_STD_MODULE": false, + "CMAKE_CXX_SCAN_FOR_MODULES": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" } }, diff --git a/include/beman/scope/beman.scope.cppm b/include/beman/scope/beman.scope.cppm index e9bfaec..39f6a91 100644 --- a/include/beman/scope/beman.scope.cppm +++ b/include/beman/scope/beman.scope.cppm @@ -3,7 +3,7 @@ // g++-15 -std=c++26 -O2 -fmodules -fmodule-only -c ${scopetop}/include/beman/scope/beman.scope.cppm module; -#include +#include export module beman.scope; diff --git a/infra/.beman_submodule b/infra/.beman_submodule index bfed167..2d3e3b4 100644 --- a/infra/.beman_submodule +++ b/infra/.beman_submodule @@ -1,3 +1,7 @@ [beman_submodule] remote=https://github.com/bemanproject/infra.git +<<<<<<< Updated upstream commit_hash=bb58b2a1cc894d58a55bf745be78f5d27029e245 +======= +commit_hash=b3545a45640abd1fedc01441ca3f220d9ac5a8e3 +>>>>>>> Stashed changes diff --git a/infra/.pre-commit-config.yaml b/infra/.pre-commit-config.yaml index e806e59..db8f459 100644 --- a/infra/.pre-commit-config.yaml +++ b/infra/.pre-commit-config.yaml @@ -19,6 +19,7 @@ repos: - id: gersemi name: CMake linting exclude: ^.*/tests/.*/data/ # Exclude test data directories +<<<<<<< Updated upstream # Python linting and formatting # config file: ruff.toml (not currently present but add if needed) @@ -30,3 +31,5 @@ repos: files: ^tools/beman-tidy/ - id: ruff-format files: ^tools/beman-tidy/ +======= +>>>>>>> Stashed changes diff --git a/infra/cmake/beman-install-library-config.cmake b/infra/cmake/beman-install-library-config.cmake index e7fd0ad..c40959d 100644 --- a/infra/cmake/beman-install-library-config.cmake +++ b/infra/cmake/beman-install-library-config.cmake @@ -84,8 +84,8 @@ function(beman_install_library name) option( ${project_prefix}_INSTALL_CONFIG_FILE_PACKAGE - "Enable building examples. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }." - ${PROJECT_IS_TOP_LEVEL} + "Enable creating and installing a CMake config-file package. Default: ON. Values: { ON, OFF }." + ON ) # By default, install the config package @@ -121,7 +121,7 @@ function(beman_install_library name) find_file( config_file_template NAMES "${package_name}-config.cmake.in" - PATHS "${CMAKE_CURRENT_SOURCE_DIR}" + PATHS "${PROJECT_SOURCE_DIR}/cmake" NO_DEFAULT_PATH NO_CACHE REQUIRED diff --git a/infra/cmake/use-fetch-content.cmake b/infra/cmake/use-fetch-content.cmake index 4ed4839..d78669b 100644 --- a/infra/cmake/use-fetch-content.cmake +++ b/infra/cmake/use-fetch-content.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.24) +cmake_minimum_required(VERSION 3.30) include(FetchContent) @@ -165,9 +165,8 @@ function(BemanExemplar_provideDependency method package_name) "${BemanExemplar_name}" GIT_REPOSITORY "${BemanExemplar_repo}" GIT_TAG "${BemanExemplar_tag}" - EXCLUDE_FROM_ALL + # NO! EXCLUDE_FROM_ALL ) - set(INSTALL_GTEST OFF) # Disable GoogleTest installation FetchContent_MakeAvailable("${BemanExemplar_name}") # Important! _FOUND tells CMake that `find_package` is diff --git a/makefile b/makefile index bf4e84e..6c4875a 100644 --- a/makefile +++ b/makefile @@ -32,7 +32,7 @@ else ifeq (${hostSystemName},Linux) export CXX=clang++-20 endif -.PHONY: all install coverage gclean distclean +.PHONY: all install coverage gclean distclean format all: build/compile_commands.json ln -sf $< . @@ -63,6 +63,10 @@ build/coverage: test coverage: build/coverage gcovr --merge-mode-functions separate +format: distclean + pre-commit autoupdate + pre-commit run --all + # Anything we don't know how to build will use this rule. % :: ninja -C build $(@) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c30035b..8e89080 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,12 +2,18 @@ include(FetchContent) +if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(_FIND_PACKAGE_ARGS) +else() + set(_FIND_PACKAGE_ARGS FIND_PACKAGE_ARGS 3.11) +endif() + FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git GIT_TAG v3.11.0 EXCLUDE_FROM_ALL - # NO! FIND_PACKAGE_ARGS 3.11 + ${_FIND_PACKAGE_ARGS} ) FetchContent_MakeAvailable(Catch2) From 38f5da32113380b16c2ec828a69386267ea60a4b Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Tue, 27 Jan 2026 18:55:56 +0100 Subject: [PATCH 18/22] infra/tools/beman-submodule/beman-submodule update --- infra/.beman_submodule | 4 ---- infra/.pre-commit-config.yaml | 14 -------------- infra/.pre-commit-hooks.yaml | 7 ------- infra/cmake/use-fetch-content.cmake | 5 +++-- 4 files changed, 3 insertions(+), 27 deletions(-) delete mode 100644 infra/.pre-commit-hooks.yaml diff --git a/infra/.beman_submodule b/infra/.beman_submodule index 2d3e3b4..10ea6a3 100644 --- a/infra/.beman_submodule +++ b/infra/.beman_submodule @@ -1,7 +1,3 @@ [beman_submodule] remote=https://github.com/bemanproject/infra.git -<<<<<<< Updated upstream -commit_hash=bb58b2a1cc894d58a55bf745be78f5d27029e245 -======= commit_hash=b3545a45640abd1fedc01441ca3f220d9ac5a8e3 ->>>>>>> Stashed changes diff --git a/infra/.pre-commit-config.yaml b/infra/.pre-commit-config.yaml index db8f459..bc4dd84 100644 --- a/infra/.pre-commit-config.yaml +++ b/infra/.pre-commit-config.yaml @@ -19,17 +19,3 @@ repos: - id: gersemi name: CMake linting exclude: ^.*/tests/.*/data/ # Exclude test data directories -<<<<<<< Updated upstream - - # Python linting and formatting - # config file: ruff.toml (not currently present but add if needed) - # https://docs.astral.sh/ruff/configuration/ - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.2 - hooks: - - id: ruff-check - files: ^tools/beman-tidy/ - - id: ruff-format - files: ^tools/beman-tidy/ -======= ->>>>>>> Stashed changes diff --git a/infra/.pre-commit-hooks.yaml b/infra/.pre-commit-hooks.yaml deleted file mode 100644 index d327587..0000000 --- a/infra/.pre-commit-hooks.yaml +++ /dev/null @@ -1,7 +0,0 @@ -- id: beman-tidy - name: "beman-tidy: bemanification your repo" - entry: ./tools/beman-tidy/beman-tidy - language: script - pass_filenames: false - always_run: true - args: [".", "--verbose"] diff --git a/infra/cmake/use-fetch-content.cmake b/infra/cmake/use-fetch-content.cmake index d78669b..4ed4839 100644 --- a/infra/cmake/use-fetch-content.cmake +++ b/infra/cmake/use-fetch-content.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.30) +cmake_minimum_required(VERSION 3.24) include(FetchContent) @@ -165,8 +165,9 @@ function(BemanExemplar_provideDependency method package_name) "${BemanExemplar_name}" GIT_REPOSITORY "${BemanExemplar_repo}" GIT_TAG "${BemanExemplar_tag}" - # NO! EXCLUDE_FROM_ALL + EXCLUDE_FROM_ALL ) + set(INSTALL_GTEST OFF) # Disable GoogleTest installation FetchContent_MakeAvailable("${BemanExemplar_name}") # Important! _FOUND tells CMake that `find_package` is From 3d810f119cc259860061b7082aab0c71e7db8a67 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Fri, 27 Feb 2026 21:32:35 +0100 Subject: [PATCH 19/22] Refactory CXX_MODULE according to Daniela advice Quickfix: do not use the old implementation Autoupdate pre-commit hooks --- .clang-format | 2 +- .pre-commit-config.yaml | 4 +- CMakeLists.txt | 15 ++++- examples/CMakeLists.txt | 2 +- examples/scope-module.cpp | 2 +- include/beman/scope/beman.scope.cppm | 65 +++++++++++++++++++- include/beman/scope/scope.hpp | 2 +- include/beman/scope/scope_impl.hpp | 90 +++++++++++++++------------- makefile | 22 +++---- tests/CMakeLists.txt | 19 +++--- 10 files changed, 151 insertions(+), 72 deletions(-) diff --git a/.clang-format b/.clang-format index d256b10..838c1aa 100644 --- a/.clang-format +++ b/.clang-format @@ -128,7 +128,7 @@ IndentCaseBlocks: false IndentCaseLabels: false IndentExternBlock: AfterExternBlock IndentGotoLabels: true -IndentPPDirectives: None +IndentPPDirectives: BeforeHash IndentRequiresClause: true IndentWidth: 4 IndentWrappedFunctionNames: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cecf800..623334d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,14 +14,14 @@ repos: # This brings in a portable version of clang-format. # See also: https://github.com/ssciwr/clang-format-wheel - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v21.1.8 + rev: v22.1.0 hooks: - id: clang-format types_or: [c++, c, json] # CMake linting and formatting - repo: https://github.com/BlankSpruce/gersemi - rev: 0.25.2 + rev: 0.26.0 hooks: - id: gersemi name: CMake linting diff --git a/CMakeLists.txt b/CMakeLists.txt index a434572..bf16ee4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -cmake_minimum_required(VERSION 3.28...4.2) +cmake_minimum_required(VERSION 3.30...4.3) project( beman.scope @@ -56,6 +56,13 @@ message( # gersemi: on if(CMAKE_CXX_SCAN_FOR_MODULES) + if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 23) + endif() + + # TODO(CK): check if possible to enable + option(BEMAN_SCOPE_IMPORT_STD "use import std; if possible" OFF) + add_library(beman.scope) include(GenerateExportHeader) @@ -65,6 +72,10 @@ if(CMAKE_CXX_SCAN_FOR_MODULES) BASE_NAME beman.scope EXPORT_FILE_NAME beman/scope/modules_export.hpp ) + if(BEMAN_SCOPE_IMPORT_STD) + set_target_properties(beman.scope PROPERTIES CXX_MODULE_STD ON) + target_compile_definitions(beman.scope PUBLIC BEMAN_SCOPE_IMPORT_STD) + endif() else() add_library(beman.scope INTERFACE) endif() @@ -84,7 +95,7 @@ if(CMAKE_CXX_SCAN_FOR_MODULES) BASE_DIRS include FILES include/beman/scope/beman.scope.cppm ) - target_compile_features(beman.scope PUBLIC cxx_std_20) + target_compile_features(beman.scope PUBLIC cxx_std_${CMAKE_CXX_STANDARD}) else() target_sources( beman.scope diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 60bcdea..080bb33 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -cmake_minimum_required(VERSION 3.28...4.2) +cmake_minimum_required(VERSION 3.30...4.3) project(beman.scope.example LANGUAGES CXX) diff --git a/examples/scope-module.cpp b/examples/scope-module.cpp index 2bb67a8..b866f54 100644 --- a/examples/scope-module.cpp +++ b/examples/scope-module.cpp @@ -18,7 +18,7 @@ #include // NOTE: this needs C++23! CK -#ifdef HAS_MODULE_STD +#ifdef BEMAN_SCOPE_IMPORT_STD import std; #endif diff --git a/include/beman/scope/beman.scope.cppm b/include/beman/scope/beman.scope.cppm index 39f6a91..945df0f 100644 --- a/include/beman/scope/beman.scope.cppm +++ b/include/beman/scope/beman.scope.cppm @@ -3,13 +3,76 @@ // g++-15 -std=c++26 -O2 -fmodules -fmodule-only -c ${scopetop}/include/beman/scope/beman.scope.cppm module; -#include +// Put all implementation-provided headers into the global module fragment +// to prevent attachment to this module. +#ifdef BEMAN_SCOPE_IMPORT_STD +import std; +#else + #include + #include + #include + +// warning: '#include ' attaches the declarations to the named +// module 'beman.scope', which is not usually intended; consider moving that +// directive before the module declaration +// [-Winclude-angled-in-module-purview] +// TODO(CK): #include +#endif + +#define BEMAN_SCOPE_USE_DANIELA_ADVICE +#ifndef BEMAN_SCOPE_USE_DANIELA_ADVICE + #include +#endif export module beman.scope; +#ifdef BEMAN_SCOPE_USE_DANIELA_ADVICE + #define BEMAN_SCOPE_MODULE_EXPORT export + #define BEMAN_SCOPE_MODULE_BEGIN_EXPORT export { + #define BEMAN_SCOPE_MODULE_END_EXPORT } + + // If you define BEMAN_SCOPE_ATTACH_TO_GLOBAL_MODULE + // - all declarations are detached from module 'beman.scope' + // - the module behaves like a traditional static library, too + // - all library symbols are mangled traditionally + // - you can mix TUs with either importing or #including the {beman.scope} API + #ifdef BEMAN_SCOPE_ATTACH_TO_GLOBAL_MODULE +extern "C++" { + #endif + +// NOTE: this export all implementation details too! CK +export { + + // FIXME: warning: '#include ' attaches the declarations to the named + // module 'beman.scope', which is not usually intended; consider moving that + // directive before the module declaration [-Winclude-angled-in-module-purview] + + #include + +} // end of export + + #ifdef BEMAN_SCOPE_ATTACH_TO_GLOBAL_MODULE +} + #endif + +#else +// ========================================================== +// Note: not needed because of using `module attachment`! +// ========================================================== + export namespace beman::scope { using ::beman::scope::scope_exit; using ::beman::scope::scope_fail; using ::beman::scope::scope_success; using ::beman::scope::unique_resource; } // namespace beman::scope + +// ========================================================== +#endif + +// TODO(CK): g++-15: sorry, unimplemented: private module fragment +// XXX module :private; + +// FIXME: /usr/local/Cellar/gcc/15.2.0_1/include/c++/15/bits/refwrap.h:310:11: +// error: conflicting declaration of 'class std::reference_wrapper<_Tp>' in module 'beman.scope' +// XXX /usr/local/Cellar/gcc/15.2.0_1/include/c++/15/type_traits:75:11: note: previously declared in global module diff --git a/include/beman/scope/scope.hpp b/include/beman/scope/scope.hpp index 0222c5c..e1ab6bb 100644 --- a/include/beman/scope/scope.hpp +++ b/include/beman/scope/scope.hpp @@ -443,7 +443,7 @@ using scope_fail = scope_guard + #include #endif // BEMAN_SCOPE_USE_STD_EXPERIMENTAL diff --git a/include/beman/scope/scope_impl.hpp b/include/beman/scope/scope_impl.hpp index 161d397..9181b52 100644 --- a/include/beman/scope/scope_impl.hpp +++ b/include/beman/scope/scope_impl.hpp @@ -3,49 +3,53 @@ #ifndef SCOPE_IMPL_HPP #define SCOPE_IMPL_HPP -// clang-format off -#include - -#if defined(__cpp_concepts) && __cpp_concepts >= 201907L - // C++20 concepts supported -#elif __cplusplus < 202002L -# error "C++20 or later is required" -#endif - -// detect standard header first, then experimental, otherwise use local implementation -#ifdef __has_include -# if __has_include() -# include -# define BEMAN_SCOPE_USE_STD -// XXX #warning "Set BEMAN_SCOPE_USE_STD" -# elif __has_include() -# include -# define BEMAN_SCOPE_USE_STD_EXPERIMENTAL -// XXX #warning "Set BEMAN_SCOPE_USE_STD_EXPERIMENTAL" -# else -# define BEMAN_SCOPE_USE_FALLBACK -# endif -#else -# define BEMAN_SCOPE_USE_FALLBACK -#endif - -#ifdef BEMAN_SCOPE_USE_STD -# if !defined(__cpp_lib_scope_exit) -# error "Standard present but __cpp_lib_scope_exit not defined" -# endif +#define BEMAN_SCOPE_USE_FALLBACK + +#ifdef BEMAN_SCOPE_IMPORT_STD + #include + + #if defined(__cpp_concepts) && __cpp_concepts >= 201907L + // C++20 concepts supported + #elif __cplusplus < 202002L + #error "C++20 or later is required" + #endif + + // detect standard header first, then experimental, otherwise use local implementation + #ifndef BEMAN_SCOPE_USE_FALLBACK + #ifdef __has_include + #if __has_include() + #include + #define BEMAN_SCOPE_USE_STD + // XXX #warning "Set BEMAN_SCOPE_USE_STD" + #elif __has_include() + #include + #define BEMAN_SCOPE_USE_STD_EXPERIMENTAL + // XXX #warning "Set BEMAN_SCOPE_USE_STD_EXPERIMENTAL" + #else + #define BEMAN_SCOPE_USE_FALLBACK + #endif + #else + #define BEMAN_SCOPE_USE_FALLBACK + #endif + #endif + + #ifdef BEMAN_SCOPE_USE_STD + #if !defined(__cpp_lib_scope_exit) + #error "Standard present but __cpp_lib_scope_exit not defined" + #endif + #endif #endif #ifdef BEMAN_SCOPE_USE_FALLBACK -# if __has_include("beman/scope/modules_export.hpp") -# include "beman/scope/modules_export.hpp" -# else -# define BEMAN_SCOPE_EXPORT -# endif -// clang-format on + #if __has_include("beman/scope/modules_export.hpp") + #include "beman/scope/modules_export.hpp" + #else + #define BEMAN_SCOPE_EXPORT + #endif -#include -#include -#include + #include + #include + #include namespace beman::scope { @@ -122,7 +126,7 @@ class [[nodiscard]] BEMAN_SCOPE_EXPORT scope_fail { // Move assignment constexpr auto operator=(scope_fail&& other) noexcept(std::is_nothrow_move_assignable_v) -> scope_fail& = delete; -#if MOVE_ASSIGNMENT_NEEDED + #if MOVE_ASSIGNMENT_NEEDED G { if (this != &other) { f = std::move(other.f); @@ -132,7 +136,7 @@ class [[nodiscard]] BEMAN_SCOPE_EXPORT scope_fail { } return *this; } -#endif + #endif // Destructor: call if scope is exiting due to an exception ~scope_fail() noexcept(noexcept(f())) { @@ -182,7 +186,7 @@ class [[nodiscard]] BEMAN_SCOPE_EXPORT scope_success { // Move assignment constexpr auto operator=(scope_success&& other) noexcept(std::is_nothrow_move_assignable_v) -> scope_success& = delete; -#if MOVE_ASSIGNMENT_NEEDED + #if MOVE_ASSIGNMENT_NEEDED { if (this != &other) { f = std::move(other.f); @@ -192,7 +196,7 @@ class [[nodiscard]] BEMAN_SCOPE_EXPORT scope_success { } return *this; } -#endif + #endif // Destructor: call only if scope is exiting normally ~scope_success() noexcept(noexcept(f())) { diff --git a/makefile b/makefile index 6c4875a..2014fa3 100644 --- a/makefile +++ b/makefile @@ -4,7 +4,6 @@ MAKEFLAGS+= --no-builtin-rules # Disable the built-in implicit rules. # MAKEFLAGS+= --warn-undefined-variables # Warn when an undefined variable is referenced. -# MAKEFLAGS+= --include-dir=$(CURDIR)/conan # Search DIRECTORY for included makefiles (*.mk). export hostSystemName=$(shell uname) @@ -13,23 +12,23 @@ ifeq (${hostSystemName},Darwin) export LLVM_DIR:=$(shell realpath ${LLVM_PREFIX}) export PATH:=${LLVM_DIR}/bin:${PATH} - export CMAKE_CXX_STDLIB_MODULES_JSON=${LLVM_DIR}/lib/c++/libc++.modules.json + export CMAKE_CXX_STDLIB_MODULES_JSON:=${LLVM_DIR}/lib/c++/libc++.modules.json export CXX=clang++ - export LDFLAGS=-L$(LLVM_DIR)/lib/c++ -lc++abi # XXX -lc++ -lc++experimental - # FIXME: export GCOV="llvm-cov gcov" + export LDFLAGS=-L$(LLVM_DIR)/lib/c++ -lc++abi # XXX -lc++ + # FIXME: export GCOV:="llvm-cov gcov" ### TODO: to test g++-15: export GCC_PREFIX:=$(shell brew --prefix gcc) export GCC_DIR:=$(shell realpath ${GCC_PREFIX}) - # export CMAKE_CXX_STDLIB_MODULES_JSON=${GCC_DIR}/lib/gcc/current/libstdc++.modules.json + # export CMAKE_CXX_STDLIB_MODULES_JSON:=${GCC_DIR}/lib/gcc/current/libstdc++.modules.json # export CXX:=g++-15 # export CXXFLAGS:=-stdlib=libstdc++ - # export GCOV="gcov" + # export GCOV:="gcov" else ifeq (${hostSystemName},Linux) - export LLVM_DIR=/usr/lib/llvm-20 + export LLVM_DIR:=/usr/lib/llvm-20 export PATH:=${LLVM_DIR}/bin:${PATH} - export CXX=clang++-20 + export CXX:=clang++-20 endif .PHONY: all install coverage gclean distclean format @@ -43,9 +42,10 @@ build/compile_commands.json: CMakeLists.txt makefile -D CMAKE_EXPERIMENTAL_CXX_IMPORT_STD="d0edc3af-4c50-42ea-a356-e2862fe7a444" \ -D CMAKE_CXX_STDLIB_MODULES_JSON=${CMAKE_CXX_STDLIB_MODULES_JSON} \ -D CMAKE_CXX_STANDARD=23 -D CMAKE_CXX_EXTENSIONS=YES -D CMAKE_CXX_STANDARD_REQUIRED=YES \ - -D CMAKE_CXX_FLAGS='-fno-inline --coverage' \ - -D CMAKE_CXX_MODULE_STD=NO \ - -D CMAKE_INSTALL_MESSAGE=LAZY # XXX -D CMAKE_SKIP_INSTALL_RULES=YES # --fresh + -D BEMAN_SCOPE_IMPORT_STD=NO \ + -D CMAKE_INSTALL_MESSAGE=LAZY \ + # XXX -D CMAKE_CXX_FLAGS='-fno-inline --coverage' \ + # XXX -D CMAKE_SKIP_INSTALL_RULES=YES # --fresh install: build/cmake_install.cmake cmake --install build diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8e89080..88b2711 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,13 +5,13 @@ include(FetchContent) if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(_FIND_PACKAGE_ARGS) else() - set(_FIND_PACKAGE_ARGS FIND_PACKAGE_ARGS 3.11) + set(_FIND_PACKAGE_ARGS FIND_PACKAGE_ARGS 3.13) endif() FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.11.0 + GIT_TAG v3.13.0 EXCLUDE_FROM_ALL ${_FIND_PACKAGE_ARGS} ) @@ -60,12 +60,13 @@ if(BEMAN_SCOPE_INSTALL_CONFIG_FILE_PACKAGE) "${CMAKE_SOURCE_DIR}/examples" "${CMAKE_CURRENT_BINARY_DIR}/find-package-test" --build-generator ${CMAKE_GENERATOR} --build-makeprogram ${CMAKE_MAKE_PROGRAM} - --build-options "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" - "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" - "-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}" - "-DCMAKE_CXX_MODULE_STD=${CMAKE_CXX_MODULE_STD}" - "-DCMAKE_CXX_SCAN_FOR_MODULES=${CMAKE_CXX_SCAN_FOR_MODULES}" - "-DCMAKE_BUILD_TYPE=$" - "-DCMAKE_PREFIX_PATH=${CMAKE_BINARY_DIR}/stagedir" + --build-options # + "-D BEMAN_SCOPE_IMPORT_STD=${BEMAN_SCOPE_IMPORT_STD}" + "-D CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-D CMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-D CMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}" + "-D CMAKE_CXX_SCAN_FOR_MODULES=${CMAKE_CXX_SCAN_FOR_MODULES}" + "-D CMAKE_BUILD_TYPE=$" + "-D CMAKE_PREFIX_PATH=${CMAKE_BINARY_DIR}/stagedir" ) endif() From 4a35cc4fb06b85491acbe4ab81f69a0afb239b4c Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Tue, 3 Mar 2026 11:29:58 +0100 Subject: [PATCH 20/22] Next steps with import std; CMAKE_EXPERIMENTAL_CXX_IMPORT_STD is still needed Build and install always a header only lib Fix some clang-tidy warnings pre-commit autoupdate --- .github/workflows/ci_tests.yml | 22 ++-- .github/workflows/pre-commit-check.yml | 2 +- .github/workflows/pre-commit-update.yml | 2 +- .pre-commit-config.yaml | 24 ++-- CMakeLists.txt | 100 ++++++++------- CMakePresets.json | 24 ++-- README.md | 38 +++--- cmake/cxx-modules-rules.cmake | 155 ++++++++++++++++++++++++ cmake/prelude.cmake | 122 +++++++++++++++++++ examples/CMakeLists.txt | 32 ++--- examples/scope-module.cpp | 6 +- examples/scope_example.cpp | 6 +- examples/unique_resource-file.cpp | 2 +- examples/unique_resource.cpp | 2 +- include/beman/scope/beman.scope.cppm | 1 - include/beman/scope/scope_impl.hpp | 14 ++- makefile | 28 +++-- tests/CMakeLists.txt | 33 +++-- 18 files changed, 467 insertions(+), 146 deletions(-) create mode 100644 cmake/cxx-modules-rules.cmake create mode 100644 cmake/prelude.cmake diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index f2475d5..cc3e30b 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -13,10 +13,10 @@ on: jobs: beman-submodule-check: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.2.1 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.3.0 preset-test: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-preset-test.yml@1.2.1 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-preset-test.yml@1.3.0 with: matrix_config: > [ @@ -31,7 +31,7 @@ jobs: ] build-and-test: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-build-and-test.yml@1.2.1 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-build-and-test.yml@1.3.0 with: matrix_config: > { @@ -42,9 +42,9 @@ jobs: "tests": [ { "stdlibs": ["libstdc++"], "tests": [ - "Debug.Default", "Release.Default", "Release.TSan", - "Release.MaxSan", "Debug.Werror", "Debug.Dynamic", - "Debug.Coverage" + "Debug.Default", "Release.Default", "Release.MaxSan", + "Debug.Coverage", "Debug.Werror", + "Debug.Dynamic", "Release.Dynamic" ] } ] @@ -69,8 +69,8 @@ jobs: "tests": [ { "stdlibs": ["libc++"], "tests": [ - "Debug.Default", "Release.Default", "Release.TSan", - "Release.MaxSan", "Debug.Werror", "Debug.Dynamic" + "Debug.Default", "Release.Default", "Release.MaxSan", + "Debug.Dynamic", "Release.Dynamic" ] } ] @@ -107,7 +107,9 @@ jobs: { "cxxversions": ["c++23"], "tests": [ { "stdlibs": ["stl"], - "tests": ["Debug.Default", "Release.Default", "Release.MaxSan"] + "tests": ["Debug.Default", "Release.Default", "Release.MaxSan", + "Debug.Dynamic", "Release.Dynamic" + ] } ] } @@ -119,4 +121,4 @@ jobs: create-issue-when-fault: needs: [preset-test, build-and-test] if: failure() && github.event_name == 'schedule' - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-create-issue-when-fault.yml@1.2.1 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-create-issue-when-fault.yml@1.3.0 diff --git a/.github/workflows/pre-commit-check.yml b/.github/workflows/pre-commit-check.yml index 5749343..2f91103 100644 --- a/.github/workflows/pre-commit-check.yml +++ b/.github/workflows/pre-commit-check.yml @@ -10,4 +10,4 @@ on: jobs: pre-commit: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-pre-commit.yml@1.2.1 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-pre-commit.yml@1.3.0 diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml index 9261dbf..930b750 100644 --- a/.github/workflows/pre-commit-update.yml +++ b/.github/workflows/pre-commit-update.yml @@ -9,7 +9,7 @@ on: jobs: auto-update-pre-commit: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-update-pre-commit.yml@1.2.1 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-update-pre-commit.yml@1.3.0 secrets: APP_ID: ${{ secrets.AUTO_PR_BOT_APP_ID }} PRIVATE_KEY: ${{ secrets.AUTO_PR_BOT_PRIVATE_KEY }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 623334d..c4395de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,11 +4,11 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml exclude: ^\.clang-(format|tidy)$ - - id: check-added-large-files + - id: check-added-large-files # Clang-format for C++ # This brings in a portable version of clang-format. @@ -16,24 +16,24 @@ repos: - repo: https://github.com/pre-commit/mirrors-clang-format rev: v22.1.0 hooks: - - id: clang-format - types_or: [c++, c, json] + - id: clang-format + types_or: [c++, c, json] # CMake linting and formatting - repo: https://github.com/BlankSpruce/gersemi rev: 0.26.0 hooks: - - id: gersemi - name: CMake linting - exclude: ^.*/tests/.*/data/ # Exclude test data directories + - id: gersemi + name: CMake linting + exclude: ^.*/tests/.*/data/ # Exclude test data directories # Markdown linting # Config file: .markdownlint.yaml - # Commented out to disable this by default. Uncomment to enable markdown linting. + # Commented out to disable this by default. Uncomment to enable markdown linting. # - repo: https://github.com/igorshubovych/markdownlint-cli - # rev: v0.46.0 + # rev: v0.47.0 # hooks: - # - id: markdownlint + # - id: markdownlint - repo: https://github.com/codespell-project/codespell rev: v2.4.1 diff --git a/CMakeLists.txt b/CMakeLists.txt index bf16ee4..de8c06d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,29 +2,28 @@ cmake_minimum_required(VERSION 3.30...4.3) +include(./cmake/prelude.cmake) + project( beman.scope DESCRIPTION "Generic Scope Guard" LANGUAGES CXX - VERSION 0.0.1 + VERSION 0.1.0 ) -# gersemi: off - # Modules opt in only on compilers that support it: msvc, g++-15 and clang-20+ -if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 20) - set(CMAKE_CXX_SCAN_FOR_MODULES 1) -elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15) - set(CMAKE_CXX_SCAN_FOR_MODULES 1) -elseif(MSVC) - set(CMAKE_CXX_SCAN_FOR_MODULES 1) -else() - set(CMAKE_CXX_SCAN_FOR_MODULES 0) +include(./cmake/cxx-modules-rules.cmake) + +#=============================================================================== +if(BEMAN_USE_MODULES) + option(BUILD_SHARED_LIBS "Build using shared libraries" OFF) + + set(CMAKE_CXX_SCAN_FOR_MODULES ON) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) + set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) endif() -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(CMAKE_CXX_VISIBILITY_PRESET hidden) -set(CMAKE_VISIBILITY_INLINES_HIDDEN TRUE) +# gersemi: off # [CMAKE.SKIP_TESTS] option( @@ -46,22 +45,15 @@ option( ${PROJECT_IS_TOP_LEVEL} ) -message( - "Compiler is: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}" -) -message( - "cmake is: ${CMAKE_VERSION} modules scan: ${CMAKE_CXX_SCAN_FOR_MODULES}" -) +message(STATUS "Compiler is: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}") +message(STATUS "Cmake is: ${CMAKE_VERSION} modules scan: ${CMAKE_CXX_SCAN_FOR_MODULES}") # gersemi: on -if(CMAKE_CXX_SCAN_FOR_MODULES) - if(NOT DEFINED CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 23) - endif() +set(BEMAN_SCOPE_TARGETS beman.scope_headers) - # TODO(CK): check if possible to enable - option(BEMAN_SCOPE_IMPORT_STD "use import std; if possible" OFF) +if(BEMAN_USE_MODULES) + option(BEMAN_SCOPE_IMPORT_STD "Use import std;" ${BEMAN_HAS_IMPORT_STD}) add_library(beman.scope) @@ -76,11 +68,14 @@ if(CMAKE_CXX_SCAN_FOR_MODULES) set_target_properties(beman.scope PROPERTIES CXX_MODULE_STD ON) target_compile_definitions(beman.scope PUBLIC BEMAN_SCOPE_IMPORT_STD) endif() -else() - add_library(beman.scope INTERFACE) -endif() + if(BEMAN_SCOPE_USE_DANIELA_ADVICE) + target_compile_definitions( + beman.scope + PUBLIC BEMAN_SCOPE_USE_DANIELA_ADVICE + ) + endif() + add_library(beman::scope ALIAS beman.scope) -if(CMAKE_CXX_SCAN_FOR_MODULES) target_sources( beman.scope PUBLIC @@ -95,25 +90,40 @@ if(CMAKE_CXX_SCAN_FOR_MODULES) BASE_DIRS include FILES include/beman/scope/beman.scope.cppm ) + + # CMake requires the language standard to be specified as compile feature + # when a target provides C++23 modules and the target will be installed target_compile_features(beman.scope PUBLIC cxx_std_${CMAKE_CXX_STANDARD}) -else() - target_sources( - beman.scope - INTERFACE - FILE_SET HEADERS - BASE_DIRS include ${CMAKE_CURRENT_BINARY_DIR} - FILES - include/beman/scope/scope.hpp - include/beman/scope/scope_impl.hpp - # NO! ${CMAKE_CURRENT_BINARY_DIR}/beman/scope/modules_export.hpp - ) + + # FIXME: Quickfix only to prevent linker problems on windows dll? CK + if(WIN32 AND BUILD_SHARED_LIBS) + set_target_properties( + beman.scope + PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON + ) + endif() + set_target_properties(beman.scope PROPERTIES EXPORT_NAME scope) + list(APPEND BEMAN_SCOPE_TARGETS beman.scope) endif() -add_library(beman::scope ALIAS beman.scope) +add_library(beman.scope_headers INTERFACE) +target_sources( + beman.scope_headers + INTERFACE + FILE_SET HEADERS + BASE_DIRS include ${CMAKE_CURRENT_BINARY_DIR} + FILES + include/beman/scope/scope.hpp + include/beman/scope/scope_impl.hpp +) + +add_library(beman::scope_headers ALIAS beman.scope_headers) set_target_properties( - beman.scope - PROPERTIES VERIFY_INTERFACE_HEADER_SETS ON EXPORT_NAME scope + beman.scope_headers + PROPERTIES + VERIFY_INTERFACE_HEADER_SETS ${PROJECT_IS_TOP_LEVEL} + EXPORT_NAME scope_headers ) include(GNUInstallDirs) @@ -122,7 +132,7 @@ set(package_install_dir ${CMAKE_INSTALL_LIBDIR}/cmake/beman.scope) # TBD: always? CK install( - TARGETS beman.scope + TARGETS ${BEMAN_SCOPE_TARGETS} COMPONENT beman.scope EXPORT beman.scope-targets FILE_SET CXX_MODULES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} diff --git a/CMakePresets.json b/CMakePresets.json index 194a873..649e838 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -7,12 +7,14 @@ "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { + "BEMAN_SCOPE_USE_DANIELA_ADVICE": true, + "BEMAN_USE_MODULES": true, "BEMAN_USE_STD_MODULE": true, - "CMAKE_CXX_EXTENSIONS": true, - "CMAKE_CXX_SCAN_FOR_MODULES": true, "CMAKE_CXX_STANDARD": "23", + "CMAKE_CXX_EXTENSIONS": true, "CMAKE_CXX_STANDARD_REQUIRED": true, "CMAKE_EXPORT_COMPILE_COMMANDS": true, + "CMAKE_INSTALL_MESSAGE": "LAZY", "CMAKE_SKIP_TEST_ALL_DEPENDENCY": false, "CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "infra/cmake/use-fetch-content.cmake" } @@ -73,7 +75,6 @@ "_debug-base" ], "cacheVariables": { - "BEMAN_USE_STD_MODULE": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-libc++-toolchain.cmake" }, "environment": { @@ -89,7 +90,6 @@ "_release-base" ], "cacheVariables": { - "BEMAN_USE_STD_MODULE": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-libc++-toolchain.cmake" }, "environment": { @@ -106,7 +106,7 @@ ], "cacheVariables": { "BEMAN_USE_STD_MODULE": false, - "CMAKE_CXX_SCAN_FOR_MODULES": false, + "BEMAN_USE_MODULES": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" } }, @@ -119,7 +119,7 @@ ], "cacheVariables": { "BEMAN_USE_STD_MODULE": false, - "CMAKE_CXX_SCAN_FOR_MODULES": false, + "BEMAN_USE_MODULES": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" } }, @@ -131,8 +131,12 @@ "_debug-base" ], "cacheVariables": { - "BUILD_SHARED_LIBS": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" } }, { @@ -143,8 +147,12 @@ "_release-base" ], "cacheVariables": { - "BUILD_SHARED_LIBS": false, "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" } } ], diff --git a/README.md b/README.md index 4e9ff82..90f76d5 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ SPDX-License-Identifier: CC0-1.0 **Status**: [Under development and not yet ready for production use.](https://github.com/bemanproject/beman/blob/main/docs/beman_library_maturity_model.md#under-development-and-not-yet-ready-for-production-use) -# Overview +## Overview During the C++20 cycle [P0052 Generic Scope Guard and RAII Wrapper for the Standard Library](https://wg21.link/P0052) added 4 types: `scope_exit`, `scope_fail`, `scope_success` @@ -80,22 +80,21 @@ but with different checked conditions on exiting the scope. Full runnable examples can be found in `examples/`. - ## Integrate beman.scope into your project -Beman.scope is a header-only library that currently relies on TS implementations -for `unique_resource` and is thus currently available only on g++-13 and up, or -clang 19 and up -- in C++20 mode. +`Beman.scope_headers` is a header-only library that builds with modern compilers +in C++20 mode. -| C++ Version | Compilers | Note | -|-------------|------------------|-------------------| -| 20 | gcc13+, clang19+ | No modules | -| 23-26 | gcc15+, clang19+ | modules supported | +| C++ Version | Compilers | Note | +|-------------|------------------------------------|-------------------| +| 20 | gcc13+, clang19+, msvc, appleclang | No modules | +| 23-26 | gcc15+, clang19+, msvc | modules supported | -Note that modules support is currently tested only on clang++-19 and above and g++-15. +Note that `CXX_MODULES` support is currently tested only on msvc, clang++-19, +and above and g++-15. -As a header only library no building is required to use in a project -- simply make -the `include` directory available add add the following to your source. +As a header only library no building is required to use in a project -- simply +make the `include` directory available add add the following to your source. ```cpp #include @@ -104,6 +103,7 @@ the `include` directory available add add the following to your source. import beman.scope; ``` + With modules import needs to be after any includes to avoid compilation errors. ## Building beman.scope @@ -129,17 +129,18 @@ Build-time dependencies: from root of repo: ```shell -mkdir build; cd build; -cmake .. -DCMAKE_CXX_COMPILER=g++-15 -DCMAKE_CXX_STANDARD=26 -G=Ninja -ninja -j 5 -v; ctest +cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER=g++-15 -DCMAKE_CXX_STANDARD=26 +ninja -v -C build all ``` or using cmake presets + ```shell cmake --workflow --preset gcc-release cmake --install build/gcc-release --prefix /opt/beman.scope ``` -# License + +## License Source is licensed with the Apache License v2.0 with LLVM Exceptions. @@ -151,10 +152,11 @@ Documentation and associated papers are licensed with the Creative Commons Attri The intent is that the source and documentation are available for use by people how they wish. -The README itself is licensed with CC0 1.0 Universal. Copy the contents and incorporate in your own work as you see fit. +The README itself is licensed with CC0 1.0 Universal. Copy the contents and incorporate in your own work as you see +fit. // SPDX-License-Identifier: CC0-1.0 -# Contributing +## Contributing Please do! Issues and pull requests are appreciated. diff --git a/cmake/cxx-modules-rules.cmake b/cmake/cxx-modules-rules.cmake new file mode 100644 index 0000000..91939b3 --- /dev/null +++ b/cmake/cxx-modules-rules.cmake @@ -0,0 +1,155 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# A CMake language file to be included as the last step of all project() command calls. +# This file must be included/used as CMAKE_PROJECT_INCLUDE -> after project() +# + +# ---- The include guard applies within the current directory and below ---- +include_guard(DIRECTORY) + +if(NOT PROJECT_NAME) + message( + FATAL_ERROR + "This CMake file has to be included as the last step of all project() command calls!" + ) +endif() + +# Use modules? default NO! +if(NOT DEFINED CMAKE_CXX_SCAN_FOR_MODULES) + set(CMAKE_CXX_SCAN_FOR_MODULES OFF) +endif() + +# Control whether the test target depends on the all target. +set(CMAKE_SKIP_TEST_ALL_DEPENDENCY OFF) + +# gersemi: off +option(CMAKE_EXPORT_COMPILE_COMMANDS "Prepare run-clang-tidy" ${PROJECT_IS_TOP_LEVEL}) +if(CMAKE_EXPORT_COMPILE_COMMANDS) + message( + STATUS + "CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES=${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}" + ) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() +# gersemi: on + +# Ensure non-empty default build type for single-config +get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT isMultiConfig) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type") +endif() +set(CMAKE_DEBUG_POSTFIX _d) + +# ------------------------------------------------------------------------------ +# This property setting also needs to be consistent between the installed shared +# library and its consumer, otherwise most toolchains will once again reject the +# consumer's generated BMI. +# ------------------------------------------------------------------------------ +if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 23) +endif() + +# Neither of these two are technically needed, but they make the expectation clear +set(CMAKE_CXX_EXTENSIONS ON) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# NOTE: only with Ninja generator install of bmi files works yet! +if(CMAKE_GENERATOR MATCHES "Ninja") + if( + CMAKE_CXX_COMPILER_ID STREQUAL "Clang" + AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.0 + ) + set(CMAKE_CXX_SCAN_FOR_MODULES ON) + + if(NOT LINUX) + string(APPEND CMAKE_CXX_MODULE_MAP_FLAG " -fmodules-reduced-bmi") + endif() + + add_compile_options($ENV{CXXFLAGS}) + add_link_options($ENV{CXXFLAGS}) + elseif( + CMAKE_CXX_COMPILER_ID STREQUAL "GNU" + AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15.0 + ) + set(CMAKE_CXX_SCAN_FOR_MODULES ON) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(CMAKE_CXX_SCAN_FOR_MODULES ON) + else() + set(CMAKE_CXX_SCAN_FOR_MODULES OFF) + endif() +endif() + +if(CMAKE_CXX_STDLIB_MODULES_JSON) + message( + STATUS + "CMAKE_CXX_STDLIB_MODULES_JSON=${CMAKE_CXX_STDLIB_MODULES_JSON}" + ) +endif() + +if(NOT DEFINED CMAKE_CXX_MODULE_STD) + set(CMAKE_CXX_MODULE_STD OFF) +endif() + +if(CMAKE_CXX_STANDARD GREATER_EQUAL 20) + option(BEMAN_USE_MODULES "Build CXX_MODULES" ${CMAKE_CXX_SCAN_FOR_MODULES}) +endif() +message(STATUS "BEMAN_USE_MODULES=${BEMAN_USE_MODULES}") + +option( + BEMAN_USE_STD_MODULE + "Check if 'import std;' is possible with the toolchain?" + OFF +) +message(STATUS "BEMAN_USE_STD_MODULE=${BEMAN_USE_STD_MODULE}") + +if(BEMAN_USE_MODULES AND BEMAN_USE_STD_MODULE) + # ------------------------------------------------------------------------- + # Tell CMake that we explicitly want `import std`. + # This will initialize the property on all targets declared after this to 1 + # ------------------------------------------------------------------------- + message( + STATUS + "CMAKE_CXX_COMPILER_IMPORT_STD=${CMAKE_CXX_COMPILER_IMPORT_STD}" + ) + if(${CMAKE_CXX_STANDARD} IN_LIST CMAKE_CXX_COMPILER_IMPORT_STD) + set(CMAKE_CXX_MODULE_STD ON) + set(CMAKE_CXX_SCAN_FOR_MODULES ON) + option( + BEMAN_HAS_IMPORT_STD + "Build with import std; is possible and used!" + ${CMAKE_CXX_MODULE_STD} + ) + message(STATUS "CMAKE_CXX_MODULE_STD=${CMAKE_CXX_MODULE_STD}") + else() + set(CMAKE_CXX_MODULE_STD OFF) + message(WARNING "CMAKE_CXX_MODULE_STD=${CMAKE_CXX_MODULE_STD}") + endif() + message(STATUS "BEMAN_HAS_IMPORT_STD=${BEMAN_HAS_IMPORT_STD}") +endif() + +if(NOT BEMAN_USE_MODULES) + set(CMAKE_CXX_SCAN_FOR_MODULES OFF) +endif() +message(STATUS "CMAKE_CXX_SCAN_FOR_MODULES=${CMAKE_CXX_SCAN_FOR_MODULES}") + +# ------------------------------------------------------------------------------ +# Avoid creating CMAKE_..._OUTPUT_DIRECTORY as cache variables, they should not +# be under the control of the developer. They should be controlled by the +# project because parts of the project may make assumptions about the relative +# layout of the binaries. More importantly, leaving them as ordinary variables +# also means they can be unset within subdirectories where test executables are +# defined, allowing them to avoid being collected with the other main binaries +# and cluttering up that area. +# ------------------------------------------------------------------------------ +set(stageDir ${CMAKE_CURRENT_BINARY_DIR}/stagedir) +include(GNUInstallDirs) + +if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${stageDir}/${CMAKE_INSTALL_BINDIR}) +endif() +if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${stageDir}/${CMAKE_INSTALL_LIBDIR}) +endif() +if(NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${stageDir}/${CMAKE_INSTALL_LIBDIR}) +endif() diff --git a/cmake/prelude.cmake b/cmake/prelude.cmake new file mode 100644 index 0000000..e1572d4 --- /dev/null +++ b/cmake/prelude.cmake @@ -0,0 +1,122 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# This file must be included/used as CMAKE_PROJECT_TOP_LEVEL_INCLUDES -> before project() is called! +# + +# ---- The include guard applies globally to the whole build ---- +include_guard(GLOBAL) + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message( + FATAL_ERROR + "In-source builds are not supported. " + "Please read the BUILDING document before trying to build this project. " + "You may need to delete 'CMakeCache.txt' and 'CMakeFiles/' first." + ) +endif() + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) + +# --------------------------------------------------------------------------- +if(NOT BEMAN_USE_STD_MODULE) + return() +endif() +# --------------------------------------------------------------------------- + +if(PROJECT_NAME) + message( + WARNING + "This CMake file has to be included before first project() command call!" + ) +endif() + +# gersemi: off +# --------------------------------------------------------------------------- +# check if import std; is supported by CMAKE_CXX_COMPILER +# --------------------------------------------------------------------------- +if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.3 AND CMAKE_VERSION VERSION_LESS 4.4) + set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "451f2fe2-a8a2-47c3-bc32-94786d8fc91b") +elseif(CMAKE_VERSION VERSION_GREATER_EQUAL 4.2 AND CMAKE_VERSION VERSION_LESS 4.3) + set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444") +endif() +# gersemi: on + +# --------------------------------------------------------------------------- +# TODO(CK): Do we need this HACK still for linux too? +# --------------------------------------------------------------------------- +if(NOT APPLE) + return() +endif() + +# FIXME: clang++ we still needs to export CXX=clang++ +if("$ENV{CXX}" STREQUAL "" AND CMAKE_CXX_COMPILER) + message(WARNING "\$CXX is not set") + set(ENV{CXX} ${CMAKE_CXX_COMPILER}) +endif() + +# --------------------------------------------------------------------------- +# Workaround needed for CMAKE and clang++ to find the libc++.modules.json file +# --------------------------------------------------------------------------- +if( + CMAKE_VERSION VERSION_GREATER_EQUAL 4.2 + AND ("$ENV{CXX}" MATCHES "clang" OR CMAKE_CXX_COMPILER MATCHES "clang") +) + # NOTE: Always use libc++ + # see https://releases.llvm.org/19.1.0/projects/libcxx/docs/index.html + set(ENV{CXXFLAGS} -stdlib=libc++) + message(STATUS "CXXFLAGS=-stdlib=libc++") + + if(APPLE) + execute_process( + OUTPUT_VARIABLE LLVM_PREFIX + COMMAND brew --prefix llvm + COMMAND_ECHO STDOUT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + file(REAL_PATH ${LLVM_PREFIX} LLVM_DIR) + set(LLVM_DIR ${LLVM_DIR} CACHE FILEPATH "") + + message(STATUS "LLVM_DIR=${LLVM_DIR}") + add_link_options(-L${LLVM_DIR}/lib/c++) + include_directories(SYSTEM ${LLVM_DIR}/include) + + # /usr/local/Cellar/llvm/21.1.8_1/lib/c++/libc++.modules.json + # "/usr/local/Cellar/llvm/21.1.8_1/share/libc++/v1/std.cppm", + set(CMAKE_CXX_STDLIB_MODULES_JSON + ${LLVM_DIR}/lib/c++/libc++.modules.json + ) + elseif(LINUX) + execute_process( + OUTPUT_VARIABLE LLVM_MODULES + COMMAND clang++ -print-file-name=libc++.modules.json + COMMAND_ECHO STDOUT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(NOT CMAKE_CXX_STDLIB_MODULES_JSON) + set(CMAKE_CXX_STDLIB_MODULES_JSON ${LLVM_MODULES}) + endif() + message( + STATUS + "CMAKE_CXX_STDLIB_MODULES_JSON=${CMAKE_CXX_STDLIB_MODULES_JSON}" + ) + endif() + + if(EXISTS ${CMAKE_CXX_STDLIB_MODULES_JSON}) + message( + STATUS + "CMAKE_CXX_STDLIB_MODULES_JSON=${CMAKE_CXX_STDLIB_MODULES_JSON}" + ) + # gersemi: off + set(CACHE{CMAKE_CXX_STDLIB_MODULES_JSON} + TYPE FILEPATH + HELP "Result of: clang++ -print-file-name=c++/libc++.modules.json" + VALUE ${CMAKE_CXX_STDLIB_MODULES_JSON} + ) + # gersemi: on + else() + message( + FATAL_ERROR + "File does NOT EXISTS! ${CMAKE_CXX_STDLIB_MODULES_JSON}" + ) + endif() +endif() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 080bb33..2bfb138 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,34 +2,38 @@ cmake_minimum_required(VERSION 3.30...4.3) +include(../cmake/prelude.cmake) + project(beman.scope.example LANGUAGES CXX) if(PROJECT_IS_TOP_LEVEL) - if(NOT DEFINED CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 23) - set(CMAKE_CXX_EXTENSIONS YES) - set(CMAKE_CXX_STANDARD_REQUIRED YES) - endif() - set(CMAKE_CXX_SCAN_FOR_MODULES OFF) + include(../cmake/cxx-modules-rules.cmake) + + find_package(beman.scope 0.1.0 EXACT REQUIRED) - find_package(beman.scope REQUIRED) + option(BEMAN_SCOPE_IMPORT_STD "Use import std;" ${BEMAN_HAS_IMPORT_STD}) enable_testing() endif() set(ALL_EXAMPLES scope_example unique_resource unique_resource-file) -# module tests will only compile with gcc15 or clang20 and above -# NOTE: needs C++23 or newer! CK -if(CMAKE_CXX_SCAN_FOR_MODULES) - list(APPEND ALL_EXAMPLES scope-module) -endif() - message("Examples to be built: ${ALL_EXAMPLES}") foreach(example ${ALL_EXAMPLES}) add_executable(${example}) target_sources(${example} PRIVATE ${example}.cpp) - target_link_libraries(${example} PRIVATE beman::scope) + target_link_libraries(${example} PRIVATE beman::scope_headers) add_test(NAME ${example} COMMAND ${example}) endforeach() + +# module tests will only compile with gcc15 or clang20 and above +# NOTE: needs C++23 or newer! CK +if(BEMAN_USE_MODULES) + add_executable(scope_module scope-module.cpp) + target_link_libraries(scope_module PRIVATE beman::scope) + if(BEMAN_SCOPE_IMPORT_STD) + set_target_properties(scope_module PROPERTIES CXX_MODULE_STD ON) + endif() + add_test(NAME scope_module COMMAND scope_module) +endif() diff --git a/examples/scope-module.cpp b/examples/scope-module.cpp index b866f54..d01b68c 100644 --- a/examples/scope-module.cpp +++ b/examples/scope-module.cpp @@ -45,9 +45,9 @@ int main() { bool cleaned{true}; { // clang-format off - beman::scope::scope_exit _se([&exit_ran] { exit_ran = true; }); - beman::scope::scope_success _ss([&success_ran] { success_ran = true; }); - beman::scope::scope_fail _sf([&fail_ran] { fail_ran = true; }); + beman::scope::scope_exit const _se([&exit_ran] { exit_ran = true; }); + beman::scope::scope_success const _ss([&success_ran] { success_ran = true; }); + beman::scope::scope_fail const _sf([&fail_ran] { fail_ran = true; }); auto resource_ptr = beman::scope::unique_resource(new DummyResource(cleaned), [](DummyResource* ptr) { ptr->cleaned = true; delete ptr; }); // clang-format on diff --git a/examples/scope_example.cpp b/examples/scope_example.cpp index 9746049..744ce73 100644 --- a/examples/scope_example.cpp +++ b/examples/scope_example.cpp @@ -16,12 +16,14 @@ void print_exit_status(std::string_view name, bool exit_status, bool did_throw) // Randomly throw an exception (50% chance) void maybe_throw() { - if (std::rand() >= RAND_MAX / 2) + if (std::rand() >= RAND_MAX / 2) { throw std::exception{}; + } } int main() { - bool exit_status{false}, did_throw{false}; + bool exit_status{false}; + bool did_throw{false}; // Manual handling at "end of scope" try { diff --git a/examples/unique_resource-file.cpp b/examples/unique_resource-file.cpp index ed76cda..2130979 100644 --- a/examples/unique_resource-file.cpp +++ b/examples/unique_resource-file.cpp @@ -19,7 +19,7 @@ int main() { } ); - if (!file.get()) { + if (file.get() == nullptr) { std::cerr << "Failed to open file.\n"; return 1; } diff --git a/examples/unique_resource.cpp b/examples/unique_resource.cpp index ea26a98..b9e2f76 100644 --- a/examples/unique_resource.cpp +++ b/examples/unique_resource.cpp @@ -14,7 +14,7 @@ int main() { ( new int[arr_size], // acquire array resource // Cleanup function - [](int* ptr) { delete[] ptr; + [](const int* ptr) { delete[] ptr; std::cout << "Array deleted.\n"; } ); diff --git a/include/beman/scope/beman.scope.cppm b/include/beman/scope/beman.scope.cppm index 945df0f..3385d96 100644 --- a/include/beman/scope/beman.scope.cppm +++ b/include/beman/scope/beman.scope.cppm @@ -19,7 +19,6 @@ import std; // TODO(CK): #include #endif -#define BEMAN_SCOPE_USE_DANIELA_ADVICE #ifndef BEMAN_SCOPE_USE_DANIELA_ADVICE #include #endif diff --git a/include/beman/scope/scope_impl.hpp b/include/beman/scope/scope_impl.hpp index 9181b52..6f3923e 100644 --- a/include/beman/scope/scope_impl.hpp +++ b/include/beman/scope/scope_impl.hpp @@ -5,7 +5,7 @@ #define BEMAN_SCOPE_USE_FALLBACK -#ifdef BEMAN_SCOPE_IMPORT_STD +#ifndef BEMAN_SCOPE_IMPORT_STD #include #if defined(__cpp_concepts) && __cpp_concepts >= 201907L @@ -47,9 +47,11 @@ #define BEMAN_SCOPE_EXPORT #endif - #include - #include - #include + #ifndef BEMAN_SCOPE_IMPORT_STD + #include + #include + #include + #endif namespace beman::scope { @@ -320,7 +322,7 @@ constexpr auto make_unique_resource_checked(R&& r, const Invalid& invalid, D&& d } // namespace beman::scope -#elifdef BEMAN_SCOPE_USE_STD_EXPERIMENTAL +#elif defined(BEMAN_SCOPE_USE_STD_EXPERIMENTAL) namespace beman::scope { using ::std::experimental::scope_exit; @@ -328,7 +330,7 @@ using ::std::experimental::scope_fail; using ::std::experimental::scope_success; using ::std::experimental::unique_resource; } // namespace beman::scope - // + #endif // BEMAN_SCOPE_USE_FALLBACK #endif // SCOPE_IMPL_HPP diff --git a/makefile b/makefile index 2014fa3..1b1ccde 100644 --- a/makefile +++ b/makefile @@ -13,37 +13,45 @@ ifeq (${hostSystemName},Darwin) export PATH:=${LLVM_DIR}/bin:${PATH} export CMAKE_CXX_STDLIB_MODULES_JSON:=${LLVM_DIR}/lib/c++/libc++.modules.json - export CXX=clang++ - export LDFLAGS=-L$(LLVM_DIR)/lib/c++ -lc++abi # XXX -lc++ - # FIXME: export GCOV:="llvm-cov gcov" + export CXXFLAGS:=-stdlib=libc++ + export LDFLAGS:=-L$(LLVM_DIR)/lib/c++ -lc++abi # XXX -lc++ + export CXX:=clang++ + export GCOV:="llvm-cov gcov" ### TODO: to test g++-15: export GCC_PREFIX:=$(shell brew --prefix gcc) export GCC_DIR:=$(shell realpath ${GCC_PREFIX}) # export CMAKE_CXX_STDLIB_MODULES_JSON:=${GCC_DIR}/lib/gcc/current/libstdc++.modules.json - # export CXX:=g++-15 # export CXXFLAGS:=-stdlib=libstdc++ + # export LDFLAGS:=-L$(GCC_DIR)/lib/gcc/current # XXX -lstdc++ -lstdc++exp + # export CXX:=g++-15 # export GCOV:="gcov" else ifeq (${hostSystemName},Linux) - export LLVM_DIR:=/usr/lib/llvm-20 + export LLVM_DIR:=/usr/lib/llvm-20 export PATH:=${LLVM_DIR}/bin:${PATH} export CXX:=clang++-20 endif -.PHONY: all install coverage gclean distclean format +.PHONY: all ctest install coverage gclean distclean format all: build/compile_commands.json ln -sf $< . - ninja -C build + ninja -C build all all_verify_interface_header_sets + ninja -C build test + +ctest: + ctest --test-dir build --verbose --rerun-failed --output-on-failure build/compile_commands.json: CMakeLists.txt makefile cmake -S . -B build -G Ninja --log-level=DEBUG -D CMAKE_BUILD_TYPE=Release \ - -D CMAKE_EXPERIMENTAL_CXX_IMPORT_STD="d0edc3af-4c50-42ea-a356-e2862fe7a444" \ -D CMAKE_CXX_STDLIB_MODULES_JSON=${CMAKE_CXX_STDLIB_MODULES_JSON} \ - -D CMAKE_CXX_STANDARD=23 -D CMAKE_CXX_EXTENSIONS=YES -D CMAKE_CXX_STANDARD_REQUIRED=YES \ - -D BEMAN_SCOPE_IMPORT_STD=NO \ + -D CMAKE_CXX_STANDARD=26 -D CMAKE_CXX_EXTENSIONS=YES -D CMAKE_CXX_STANDARD_REQUIRED=YES \ -D CMAKE_INSTALL_MESSAGE=LAZY \ + -D BEMAN_USE_MODULES=YES \ + -D BEMAN_USE_STD_MODULE=YES \ + -D BEMAN_SCOPE_USE_DANIELA_ADVICE=YES \ + -D CMAKE_SKIP_TEST_ALL_DEPENDENCY=NO \ # XXX -D CMAKE_CXX_FLAGS='-fno-inline --coverage' \ # XXX -D CMAKE_SKIP_INSTALL_RULES=YES # --fresh diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 88b2711..8fac05f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,11 +2,11 @@ include(FetchContent) -if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(_FIND_PACKAGE_ARGS) -else() - set(_FIND_PACKAGE_ARGS FIND_PACKAGE_ARGS 3.13) -endif() +# if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") +# set(_FIND_PACKAGE_ARGS) +# else() +set(_FIND_PACKAGE_ARGS FIND_PACKAGE_ARGS 3.13) +# endif() FetchContent_Declare( Catch2 @@ -25,21 +25,26 @@ set(ALL_TESTNAMES unique_resource_2 ) +include(Catch) + # module tests will only compile with gcc15 or clang20 and above -if(CMAKE_CXX_SCAN_FOR_MODULES) - list(APPEND ALL_TESTNAMES module) +if(BEMAN_USE_MODULES) + add_executable(test.module module.test.cpp) + target_link_libraries( + test.module + PRIVATE Catch2::Catch2WithMain beman::scope + ) + catch_discover_tests(test.module) endif() message("Tests to be built: ${ALL_TESTNAMES}") -include(Catch) - foreach(testname ${ALL_TESTNAMES}) add_executable(test.${testname}) target_sources(test.${testname} PRIVATE ${testname}.test.cpp) target_link_libraries( test.${testname} - PRIVATE Catch2::Catch2WithMain beman::scope + PRIVATE Catch2::Catch2WithMain beman::scope_headers ) catch_discover_tests(test.${testname}) endforeach() @@ -62,11 +67,13 @@ if(BEMAN_SCOPE_INSTALL_CONFIG_FILE_PACKAGE) ${CMAKE_GENERATOR} --build-makeprogram ${CMAKE_MAKE_PROGRAM} --build-options # "-D BEMAN_SCOPE_IMPORT_STD=${BEMAN_SCOPE_IMPORT_STD}" + "-D BEMAN_SCOPE_USE_DANIELA_ADVICE=${BEMAN_SCOPE_USE_DANIELA_ADVICE}" + "-D BEMAN_USE_STD_MODULE=${BEMAN_USE_STD_MODULE}" + "-D CMAKE_BUILD_TYPE=$" "-D CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" - "-D CMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" "-D CMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}" - "-D CMAKE_CXX_SCAN_FOR_MODULES=${CMAKE_CXX_SCAN_FOR_MODULES}" - "-D CMAKE_BUILD_TYPE=$" + "-D CMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" "-D CMAKE_PREFIX_PATH=${CMAKE_BINARY_DIR}/stagedir" + "-D CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" ) endif() From 5c6aa81cc7634948afc68d98f21182e03507b79d Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Tue, 3 Mar 2026 13:05:51 +0100 Subject: [PATCH 21/22] Try to prevent linker problems on OSX with gcc-15 --- CMakeLists.txt | 57 ++++++++++++++++++++++++++++++++++++--- examples/scope-module.cpp | 5 ++-- makefile | 8 +++--- tests/CMakeLists.txt | 14 ++++++---- tests/module.test.cpp | 3 +++ 5 files changed, 72 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de8c06d..02e17ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ if(BEMAN_USE_MODULES) set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) endif() +#=============================================================================== # gersemi: off @@ -56,15 +57,51 @@ if(BEMAN_USE_MODULES) option(BEMAN_SCOPE_IMPORT_STD "Use import std;" ${BEMAN_HAS_IMPORT_STD}) add_library(beman.scope) - include(GenerateExportHeader) - generate_export_header( beman.scope BASE_NAME beman.scope EXPORT_FILE_NAME beman/scope/modules_export.hpp ) + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # --------------------------------------------------------------------------- + # TODO(daniela): I do mean C++20 + # --------------------------------------------------------------------------- + target_compile_options( + beman.scope + PUBLIC + -fsized-deallocation + -faligned-allocation + -Wno-include-angled-in-module-purview + ) + endif() + if(BEMAN_SCOPE_IMPORT_STD) + # --------------------------------------------------------------------------- + # Ensure binaries find the correct libstdc++ at runtime when using a + # non-system GCC (e.g. /opt/gcc-16). Without this, CTest fails because the + # dynamic linker finds the system's older libstdc++ which lacks required + # GLIBCXX symbols. + # --------------------------------------------------------------------------- + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(_suffix .so) + if(APPLE) + set(_suffix .dylib) + endif() + execute_process( + COMMAND + ${CMAKE_CXX_COMPILER} -print-file-name=libstdc++${_suffix} + OUTPUT_VARIABLE _gcc_stdcxx_path + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(_gcc_stdcxx_path) + cmake_path(GET _gcc_stdcxx_path PARENT_PATH _gcc_lib_dir) + get_filename_component(_gcc_lib_dir "${_gcc_lib_dir}" REALPATH) + list(APPEND CMAKE_BUILD_RPATH "${_gcc_lib_dir}") + message(STATUS "GCC libstdc++ rpath: ${_gcc_lib_dir}") + endif() + endif() set_target_properties(beman.scope PROPERTIES CXX_MODULE_STD ON) target_compile_definitions(beman.scope PUBLIC BEMAN_SCOPE_IMPORT_STD) endif() @@ -91,17 +128,24 @@ if(BEMAN_USE_MODULES) FILES include/beman/scope/beman.scope.cppm ) + # --------------------------------------------------------------------------- # CMake requires the language standard to be specified as compile feature # when a target provides C++23 modules and the target will be installed + # --------------------------------------------------------------------------- target_compile_features(beman.scope PUBLIC cxx_std_${CMAKE_CXX_STANDARD}) - # FIXME: Quickfix only to prevent linker problems on windows dll? CK + # --------------------------------------------------------------------------- + # FIXME: Quickfix only to prevent linker problems on windows with dll + # it should not needed and it does not help? CK + # We have NO exported symbols and so NO generated .lib on windows! + # --------------------------------------------------------------------------- if(WIN32 AND BUILD_SHARED_LIBS) set_target_properties( beman.scope PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON ) endif() + set_target_properties(beman.scope PROPERTIES EXPORT_NAME scope) list(APPEND BEMAN_SCOPE_TARGETS beman.scope) endif() @@ -130,7 +174,9 @@ include(GNUInstallDirs) set(package_install_dir ${CMAKE_INSTALL_LIBDIR}/cmake/beman.scope) -# TBD: always? CK +# --------------------------------------------------------------------------- +# TBD: install targets always? CK +# --------------------------------------------------------------------------- install( TARGETS ${BEMAN_SCOPE_TARGETS} COMPONENT beman.scope @@ -168,6 +214,9 @@ if(BEMAN_SCOPE_INSTALL_CONFIG_FILE_PACKAGE) ) endif() +# --------------------------------------------------------------------------- +# NOTE: we use ctest in examples too! CK +# --------------------------------------------------------------------------- enable_testing() if(BEMAN_SCOPE_BUILD_TESTS) diff --git a/examples/scope-module.cpp b/examples/scope-module.cpp index d01b68c..1d1dbc6 100644 --- a/examples/scope-module.cpp +++ b/examples/scope-module.cpp @@ -15,14 +15,15 @@ // destroy noisy // scope exit: true success: true fail: false +// NOTE: not included in gcc-15 libstdc++ std module? CK #include -// NOTE: this needs C++23! CK +// for g++-15 the order is important -- import after #includes #ifdef BEMAN_SCOPE_IMPORT_STD +// NOTE: this needs C++23! CK import std; #endif -// for g++-15 the order is important -- import after #includes import beman.scope; namespace { diff --git a/makefile b/makefile index 1b1ccde..bff6854 100644 --- a/makefile +++ b/makefile @@ -3,7 +3,7 @@ .SUFFIXES: MAKEFLAGS+= --no-builtin-rules # Disable the built-in implicit rules. -# MAKEFLAGS+= --warn-undefined-variables # Warn when an undefined variable is referenced. +MAKEFLAGS+= --warn-undefined-variables # Warn when an undefined variable is referenced. export hostSystemName=$(shell uname) @@ -33,7 +33,7 @@ else ifeq (${hostSystemName},Linux) export CXX:=clang++-20 endif -.PHONY: all ctest install coverage gclean distclean format +.PHONY: all ctest cinstall coverage gclean distclean format all: build/compile_commands.json ln -sf $< . @@ -44,7 +44,7 @@ ctest: ctest --test-dir build --verbose --rerun-failed --output-on-failure build/compile_commands.json: CMakeLists.txt makefile - cmake -S . -B build -G Ninja --log-level=DEBUG -D CMAKE_BUILD_TYPE=Release \ + cmake -S . -B build -G Ninja --log-level=VERBOSE -D CMAKE_BUILD_TYPE=Release \ -D CMAKE_CXX_STDLIB_MODULES_JSON=${CMAKE_CXX_STDLIB_MODULES_JSON} \ -D CMAKE_CXX_STANDARD=26 -D CMAKE_CXX_EXTENSIONS=YES -D CMAKE_CXX_STANDARD_REQUIRED=YES \ -D CMAKE_INSTALL_MESSAGE=LAZY \ @@ -55,7 +55,7 @@ build/compile_commands.json: CMakeLists.txt makefile # XXX -D CMAKE_CXX_FLAGS='-fno-inline --coverage' \ # XXX -D CMAKE_SKIP_INSTALL_RULES=YES # --fresh -install: build/cmake_install.cmake +cinstall: build/cmake_install.cmake cmake --install build distclean: # XXX clean diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8fac05f..f84de11 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,11 +2,11 @@ include(FetchContent) -# if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") -# set(_FIND_PACKAGE_ARGS) -# else() -set(_FIND_PACKAGE_ARGS FIND_PACKAGE_ARGS 3.13) -# endif() +if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(_FIND_PACKAGE_ARGS) +else() + set(_FIND_PACKAGE_ARGS FIND_PACKAGE_ARGS 3.13) +endif() FetchContent_Declare( Catch2 @@ -34,6 +34,10 @@ if(BEMAN_USE_MODULES) test.module PRIVATE Catch2::Catch2WithMain beman::scope ) + if(BEMAN_SCOPE_IMPORT_STD) + set_target_properties(test.module PROPERTIES CXX_MODULE_STD ON) + endif() + # NO! add_test(NAME test.module COMMAND test.module) catch_discover_tests(test.module) endif() diff --git a/tests/module.test.cpp b/tests/module.test.cpp index d5f7ec6..a72bfb1 100644 --- a/tests/module.test.cpp +++ b/tests/module.test.cpp @@ -4,6 +4,9 @@ #include // for g++-15 the order is important -- import after #includes +#ifdef BEMAN_SCOPE_IMPORT_STD +import std; +#endif import beman.scope; namespace { From cfca6f4ea79c8d274bc88706f36f3e7457b4c789 Mon Sep 17 00:00:00 2001 From: ClausKlein Date: Wed, 25 Mar 2026 19:37:03 +0100 Subject: [PATCH 22/22] Use beman_install_library() cmake function --- .pre-commit-config.yaml | 6 +- CMakeLists.txt | 48 +---- cmake/Config.cmake.in | 12 ++ cmake/beman-install-library.cmake | 322 ++++++++++++++++++++++++++++++ cmake/beman.scope-config.cmake | 8 - cmake/prelude.cmake | 25 ++- 6 files changed, 359 insertions(+), 62 deletions(-) create mode 100644 cmake/Config.cmake.in create mode 100644 cmake/beman-install-library.cmake delete mode 100644 cmake/beman.scope-config.cmake diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c4395de..0e5fdfd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,14 +14,14 @@ repos: # This brings in a portable version of clang-format. # See also: https://github.com/ssciwr/clang-format-wheel - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v22.1.0 + rev: v22.1.1 hooks: - id: clang-format types_or: [c++, c, json] # CMake linting and formatting - repo: https://github.com/BlankSpruce/gersemi - rev: 0.26.0 + rev: 0.26.1 hooks: - id: gersemi name: CMake linting @@ -36,7 +36,7 @@ repos: # - id: markdownlint - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: v2.4.2 hooks: - id: codespell args: ["--ignore-words-list=wil"] diff --git a/CMakeLists.txt b/CMakeLists.txt index 02e17ed..12ca2eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,49 +170,11 @@ set_target_properties( EXPORT_NAME scope_headers ) -include(GNUInstallDirs) - -set(package_install_dir ${CMAKE_INSTALL_LIBDIR}/cmake/beman.scope) - -# --------------------------------------------------------------------------- -# TBD: install targets always? CK -# --------------------------------------------------------------------------- -install( - TARGETS ${BEMAN_SCOPE_TARGETS} - COMPONENT beman.scope - EXPORT beman.scope-targets - FILE_SET CXX_MODULES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - CXX_MODULES_BMI - DESTINATION - ${package_install_dir}/bmi-${CMAKE_CXX_COMPILER_ID}_$ - FILE_SET HEADERS -) - -if(BEMAN_SCOPE_INSTALL_CONFIG_FILE_PACKAGE) - include(CMakePackageConfigHelpers) - - write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/beman.scope-config-version.cmake - COMPATIBILITY ExactVersion - ) - - install( - FILES - cmake/beman.scope-config.cmake - ${CMAKE_CURRENT_BINARY_DIR}/beman.scope-config-version.cmake - DESTINATION ${package_install_dir} - COMPONENT beman.scope - ) - - install( - EXPORT beman.scope-targets - DESTINATION ${package_install_dir} - NAMESPACE beman:: - CXX_MODULES_DIRECTORY - cxx-modules - COMPONENT beman.scope - ) -endif() +# +# Install the libraries and its FILE_SET's. +# +include(cmake/beman-install-library.cmake) +beman_install_library(beman.scope TARGETS beman.scope beman.scope_headers) # --------------------------------------------------------------------------- # NOTE: we use ctest in examples too! CK diff --git a/cmake/Config.cmake.in b/cmake/Config.cmake.in new file mode 100644 index 0000000..81adf80 --- /dev/null +++ b/cmake/Config.cmake.in @@ -0,0 +1,12 @@ +# cmake/Config.cmake.in -*-makefile-*- +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +include(CMakeFindDependencyMacro) + +@BEMAN_FIND_DEPENDENCIES@ + +@PACKAGE_INIT@ + +include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake) + +check_required_components(@PROJECT_NAME@) diff --git a/cmake/beman-install-library.cmake b/cmake/beman-install-library.cmake new file mode 100644 index 0000000..919df9b --- /dev/null +++ b/cmake/beman-install-library.cmake @@ -0,0 +1,322 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +include_guard(GLOBAL) + +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + +# beman_install_library +# ===================== +# +# Installs a library (or set of targets) along with headers, C++ modules, +# and optional CMake package configuration files. +# +# Usage: +# ------ +# beman_install_library( +# TARGETS [ ...] +# [DEPENDENCIES [ ...]] +# [NAMESPACE ] +# [EXPORT_NAME ] +# [DESTINATION ] +# ) +# +# Arguments: +# ---------- +# +# name +# Logical package name (e.g. "beman.utility"). +# Used to derive config file names and cache variable prefixes. +# +# TARGETS (required) +# List of CMake targets to install. +# +# DEPENDENCIES (optional) +# Semicolon-separated list, one dependency per entry. +# Each entry is a valid find_dependency() argument list. +# Note: you must use the bracket form for quoting if not only a package name is used! +# "[===[beman.inplace_vector 1.0.0]===] [===[beman.scope 0.0.1 EXACT]===] fmt" +# +# NAMESPACE (optional) +# Namespace for exported targets. +# Defaults to "beman::". +# +# EXPORT_NAME (optional) +# Name of the CMake export set. +# Defaults to "-targets". +# +# DESTINATION (optional) +# The install destination for CXX_MODULES. +# Defaults to ${CMAKE_INSTALL_LIBDIR}/cmake/${name}/modules. +# +# Brief +# ----- +# +# This function installs the specified project TARGETS and its FILE_SET +# HEADERS to the default CMAKE install destination. +# +# It also handles the installation of the CMake config package files if +# needed. If the given targets has a PUBLIC FILE_SET CXX_MODULE, it will also +# installed to the given DESTINATION +# +# Cache variables: +# ---------------- +# +# BEMAN_INSTALL_CONFIG_FILE_PACKAGES +# List of package names for which config files should be installed. +# +# _INSTALL_CONFIG_FILE_PACKAGE +# Per-package override to enable/disable config file installation. +# is the uppercased package name with dots replaced by underscores. +# +# Caveats +# ------- +# +# **Only one `PUBLIC FILE_SET CXX_MODULES` is yet supported to install with this +# function!** +# +# **Only header files contained in a `PUBLIC FILE_SET TYPE HEADERS` will be +# install with this function!** + +function(beman_install_library name) + # ---------------------------- + # Argument parsing + # ---------------------------- + set(oneValueArgs NAMESPACE EXPORT_NAME DESTINATION) + set(multiValueArgs TARGETS DEPENDENCIES) + + cmake_parse_arguments( + BEMAN + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + if(NOT BEMAN_TARGETS) + message( + FATAL_ERROR + "beman_install_library(${name}): TARGETS must be specified" + ) + endif() + + if(CMAKE_SKIP_INSTALL_RULES) + message( + WARNING + "beman_install_library(${name}): not installing targets '${BEMAN_TARGETS}' due to CMAKE_SKIP_INSTALL_RULES" + ) + return() + endif() + + set(_config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${name}") + + # ---------------------------- + # Defaults + # ---------------------------- + if(NOT BEMAN_NAMESPACE) + set(BEMAN_NAMESPACE "beman::") + endif() + + if(NOT BEMAN_EXPORT_NAME) + set(BEMAN_EXPORT_NAME "${name}-targets") + endif() + + if(NOT BEMAN_DESTINATION) + set(BEMAN_DESTINATION "${_config_install_dir}/modules") + endif() + + string(REPLACE "beman." "" install_component_name "${name}") + message( + VERBOSE + "beman-install-library(${name}): COMPONENT '${install_component_name}'" + ) + + # -------------------------------------------------- + # Install each target with all of its file sets + # -------------------------------------------------- + foreach(_tgt IN LISTS BEMAN_TARGETS) + if(NOT TARGET "${_tgt}") + message( + WARNING + "beman_install_library(${name}): '${_tgt}' is not a target" + ) + continue() + endif() + + # Given foo.bar, the component name is bar + string(REPLACE "." ";" name_parts "${_tgt}") + # fail if the name doesn't look like foo.bar + list(LENGTH name_parts name_parts_length) + if(NOT name_parts_length EQUAL 2) + message( + FATAL_ERROR + "beman_install_library(${name}): expects a name of the form 'beman.', got '${_tgt}'" + ) + endif() + list(GET name_parts -1 component_name) + set_target_properties( + "${_tgt}" + PROPERTIES EXPORT_NAME "${component_name}" + ) + message( + VERBOSE + "beman_install_library(${name}): EXPORT_NAME ${component_name} for TARGET '${_tgt}'" + ) + + # Get the list of interface header sets, exact one expected! + set(_install_header_set_args) + get_target_property( + _available_header_sets + ${_tgt} + INTERFACE_HEADER_SETS + ) + if(_available_header_sets) + message( + VERBOSE + "beman-install-library(${name}): '${_tgt}' has INTERFACE_HEADER_SETS=${_available_header_sets}" + ) + foreach(_install_header_set IN LISTS _available_header_sets) + list( + APPEND _install_header_set_args + FILE_SET + "${_install_header_set}" + COMPONENT + "${install_component_name}_Development" + ) + endforeach() + else() + set(_install_header_set_args FILE_SET HEADERS) # Note: empty FILE_SET in this case! CK + endif() + + # Detect presence of PUBLIC C++ module file sets. Note: exact one is expected! + get_target_property(_module_sets "${_tgt}" INTERFACE_CXX_MODULE_SETS) + if(_module_sets) + message( + VERBOSE + "beman-install-library(${name}): '${_tgt}' has INTERFACE_CXX_MODULE_SETS=${_module_sets}" + ) + install( + TARGETS "${_tgt}" + EXPORT ${BEMAN_EXPORT_NAME} + ARCHIVE COMPONENT "${install_component_name}_Development" + LIBRARY + COMPONENT "${install_component_name}_Runtime" + NAMELINK_COMPONENT "${install_component_name}_Development" + RUNTIME COMPONENT "${install_component_name}_Runtime" + ${_install_header_set_args} + FILE_SET ${_module_sets} + DESTINATION "${BEMAN_DESTINATION}" + COMPONENT "${install_component_name}_Development" + # NOTE: There's currently no convention for this location! CK + CXX_MODULES_BMI + DESTINATION + ${_config_install_dir}/bmi-${CMAKE_CXX_COMPILER_ID}_$ + COMPONENT "${install_component_name}_Development" + ) + else() + install( + TARGETS "${_tgt}" + EXPORT ${BEMAN_EXPORT_NAME} + ARCHIVE COMPONENT "${install_component_name}_Development" + LIBRARY + COMPONENT "${install_component_name}_Runtime" + NAMELINK_COMPONENT "${install_component_name}_Development" + RUNTIME COMPONENT "${install_component_name}_Runtime" + ${_install_header_set_args} + ) + endif() + endforeach() + + # -------------------------------------------------- + # Export targets + # -------------------------------------------------- + # gersemi: off + install( + EXPORT ${BEMAN_EXPORT_NAME} + NAMESPACE ${BEMAN_NAMESPACE} + CXX_MODULES_DIRECTORY cxx-modules + DESTINATION ${_config_install_dir} + COMPONENT "${install_component_name}_Development" + ) + # gersemi: on + + # ---------------------------------------- + # Config file installation logic + # + # Precedence (highest to lowest): + # 1. Per-package variable _INSTALL_CONFIG_FILE_PACKAGE + # 2. Allow-list BEMAN_INSTALL_CONFIG_FILE_PACKAGES (if defined) + # 3. Default: ON + # ---------------------------------------- + string(TOUPPER "${name}" _pkg_upper) + string(REPLACE "." "_" _pkg_prefix "${_pkg_upper}") + + option( + ${_pkg_prefix}_INSTALL_CONFIG_FILE_PACKAGE + "Enable creating and installing a CMake config-file package. Default: ON. Values: { ON, OFF }." + ON + ) + + set(_pkg_var "${_pkg_prefix}_INSTALL_CONFIG_FILE_PACKAGE") + + # Default: install config files + set(_install_config ON) + + # If the allow-list is defined, only install for packages in the list + if(DEFINED BEMAN_INSTALL_CONFIG_FILE_PACKAGES) + if(NOT "${name}" IN_LIST BEMAN_INSTALL_CONFIG_FILE_PACKAGES) + set(_install_config OFF) + endif() + endif() + + # Per-package override takes highest precedence + if(DEFINED ${_pkg_var}) + set(_install_config ${${_pkg_var}}) + endif() + + # ---------------------------------------- + # expand dependencies + # ---------------------------------------- + set(_beman_find_deps "") + foreach(dep IN LISTS BEMAN_DEPENDENCIES) + message( + VERBOSE + "beman-install-library(${name}): Add find_dependency(${dep})" + ) + string(APPEND _beman_find_deps "find_dependency(${dep})\n") + endforeach() + set(BEMAN_FIND_DEPENDENCIES "${_beman_find_deps}") + + # ---------------------------------------- + # Generate + install config files + # ---------------------------------------- + if(_install_config) + configure_package_config_file( + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config.cmake" + INSTALL_DESTINATION ${_config_install_dir} + ) + + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config-version.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion + ) + + install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config-version.cmake" + DESTINATION ${_config_install_dir} + COMPONENT "${install_component_name}_Development" + ) + else() + message( + WARNING + "beman-install-library(${name}): Not installing a config package for '${name}'" + ) + endif() +endfunction() + +set(CPACK_GENERATOR TGZ) +include(CPack) diff --git a/cmake/beman.scope-config.cmake b/cmake/beman.scope-config.cmake deleted file mode 100644 index e4102c5..0000000 --- a/cmake/beman.scope-config.cmake +++ /dev/null @@ -1,8 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/beman.scope-targets.cmake) - -foreach(comp IN LISTS beman.scope_FIND_COMPONENTS) - if(beman.scope_FIND_REQUIRED_${comp}) - set(beman.scope_FOUND FALSE) - return() - endif() -endforeach() diff --git a/cmake/prelude.cmake b/cmake/prelude.cmake index e1572d4..c0394e2 100644 --- a/cmake/prelude.cmake +++ b/cmake/prelude.cmake @@ -17,20 +17,29 @@ endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) +# gersemi: off # --------------------------------------------------------------------------- -if(NOT BEMAN_USE_STD_MODULE) - return() +# The CMAKE_EXPERIMENTAL_CXX_IMPORT_STD is not longer needed except for OSX +# --------------------------------------------------------------------------- +if(NOT BEMAN_USE_STD_MODULE OR CMAKE_VERSION VERSION_GREATER_EQUAL 4.4) + if(NOT APPLE) + return() + endif() endif() # --------------------------------------------------------------------------- -if(PROJECT_NAME) - message( - WARNING - "This CMake file has to be included before first project() command call!" - ) +# --------------------------------------------------------------------------- +# check if import std; is supported by CMAKE_CXX_COMPILER +# --------------------------------------------------------------------------- +if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.2 AND CMAKE_VERSION VERSION_LESS 4.4) + if(PROJECT_NAME) + message( + WARNING + "This CMake file has to be included before first project() command call!" + ) + endif() endif() -# gersemi: off # --------------------------------------------------------------------------- # check if import std; is supported by CMAKE_CXX_COMPILER # ---------------------------------------------------------------------------