diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 348beba..e005487 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -27,7 +27,7 @@ jobs: - name: Setup build environment uses: lukka/get-cmake@latest with: - cmakeVersion: "~3.25.0" + cmakeVersion: "~3.28.0" ninjaVersion: "^1.11.1" - name: Setup MSVC if: startsWith(matrix.presets.platform, 'windows') @@ -80,7 +80,7 @@ jobs: - name: Install Ninja uses: lukka/get-cmake@latest with: - cmakeVersion: "~3.25.0" + cmakeVersion: "~4.0.0" ninjaVersion: "^1.11.1" - name: Setup MSVC if: startsWith(matrix.platform.os, 'windows') @@ -139,7 +139,7 @@ jobs: - name: Setup build environment uses: lukka/get-cmake@latest with: - cmakeVersion: "~3.25.0" + cmakeVersion: "~3.28.0" ninjaVersion: "^1.11.1" - name: Print installed softwares run: | @@ -183,7 +183,7 @@ jobs: - name: Setup build environment uses: lukka/get-cmake@latest with: - cmakeVersion: "~3.25.0" + cmakeVersion: "~4.0.0" ninjaVersion: "^1.11.1" - name: Install Compiler id: install-compiler diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 029fa76..f3c4332 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -73,4 +73,4 @@ jobs: with: tool_name: pre-commit level: warning - reviewdog_flags: "-fail-level=none" + reviewdog_flags: "-fail-level=error" diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a3a79e..afdaf7d 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.25) +cmake_minimum_required(VERSION 3.28) project( beman.scope @@ -9,6 +9,19 @@ project( VERSION 0.0.1 ) +# gersemi: off + +# Modules opt in only on compilers that support 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() + set(CMAKE_CXX_SCAN_FOR_MODULES 0) +endif() + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + # [CMAKE.SKIP_TESTS] option( BEMAN_SCOPE_BUILD_TESTS @@ -29,10 +42,39 @@ option( ${PROJECT_IS_TOP_LEVEL} ) -add_library(beman.scope INTERFACE) +message( + "Compiler is: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}" +) +message( + "cmake is: ${CMAKE_VERSION} modules scan : ${CMAKE_CXX_SCAN_FOR_MODULES}" +) + +if(CMAKE_CXX_SCAN_FOR_MODULES) + add_library(beman.scope) + target_sources( + beman.scope + PUBLIC + FILE_SET HEADERS + BASE_DIRS include + FILES include/beman/scope/scope.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 + FILES include/beman/scope/scope.hpp + ) +endif() + add_library(beman::scope ALIAS beman.scope) -# gersemi: off set_target_properties( beman.scope PROPERTIES @@ -40,24 +82,21 @@ set_target_properties( EXPORT_NAME scope ) -target_sources( - beman.scope - INTERFACE - FILE_SET HEADERS - BASE_DIRS include - FILES include/beman/scope/scope.hpp -) +include(GNUInstallDirs) install( TARGETS beman.scope - EXPORT beman.scope-targets COMPONENT beman.scope + EXPORT beman.scope-targets + + FILE_SET CXX_MODULES + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILE_SET HEADERS ) + # gersemi: on if(BEMAN_SCOPE_INSTALL_CONFIG_FILE_PACKAGE) - include(GNUInstallDirs) include(CMakePackageConfigHelpers) write_basic_package_version_file( @@ -77,6 +116,7 @@ if(BEMAN_SCOPE_INSTALL_CONFIG_FILE_PACKAGE) EXPORT beman.scope-targets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/beman.scope NAMESPACE beman:: + CXX_MODULES_DIRECTORY cxx-modules COMPONENT beman.scope ) endif() diff --git a/README.md b/README.md index ae6c14d..55003db 100644 --- a/README.md +++ b/README.md @@ -85,23 +85,32 @@ 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 -and is thus currently available only on GCC13 and up, or Clang 19 and up -- in C++20 mode. +and is thus currently available only on g++-13 and up, or clang 19 and up -- in C++20 mode. + +Note that modules support is currently tested only on 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. ```cpp #include + +//modular version + +import beman.scope; ``` +Withmodules import needs to be after any includes to avoid compilation errors. ## Building beman.scope -Building is only required to run tests and examples. +Building is only required to run tests and examples. All compilers build and +run `include` based tests. Compilers known to support modules are automatically +detected added to tests. ### Build Dependencies -The library itself has no build dependencies other than Catch2 for testing -and cmake. +The library itself has no build dependencies other than Catch2 for testing. +And for building cmake and ninja. Makefiles are supported in non-modular builds. Build-time dependencies: @@ -110,14 +119,21 @@ Build-time dependencies: - CMake defaults to "Unix Makefiles" on POSIX systems - `catch2` for building tests -### How to build beman.scope +### How to build beman.scope tests and examples + +from root of repo: ```shell -cmake --workflow --preset gcc-debug +mkdir build; cd build; +cmake .. -DCMAKE_CXX_COMPILER=g++-15 -DCMAKE_CXX_STANDARD=26 -G=Ninja +ninja -j 5 -v; ctest +``` + +or using cmake presets +```shell cmake --workflow --preset gcc-release cmake --install build/gcc-release --prefix /opt/beman.scope ``` - # License Source is licensed with the Apache 2.0 license with LLVM exceptions diff --git a/examples/scope-module.cpp b/examples/scope-module.cpp new file mode 100644 index 0000000..c5f26e8 --- /dev/null +++ b/examples/scope-module.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// This example uses c++20 modules with beman.scope +// +// The following are by hand instructions for compiling with g++-15 +// first line generates gcm.cache file for standard headers - one time only +// g++-15 -std=c++26 -O2 -fmodules -fsearch-include-path -fmodule-only -c bits/std.cc +// g++-15 -std=c++26 -O2 -fmodules -fmodule-only -c ${scope_top}/include/beman/scope/beman.scope.cppm +// g++-15 -std=c++26 -fmodules scope-module.cpp +// +// prints: +// --> scope start +// construct noisy +// --> scope end +// destroy noisy +// scope exit: true success: true fail: false + +import std; +import beman.scope; + +// clang-format off +struct noisy_resource { + noisy_resource() { std::print( "construct noisy\n" ); } + ~noisy_resource() { std::print( "destroy noisy\n" ); } +}; + +int main() { + + bool exit_ran, success_ran, fail_ran = false; + { + 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; }); + auto resource_ptr = beman::scope::unique_resource(new noisy_resource(), + // Cleanup function + [](noisy_resource* ptr) { delete ptr; }); + std::print("--> scope end\n"); + } // Normal scope exit + + std::print("scope exit: {} success: {} fail: {} \n", exit_ran, success_ran, fail_ran); +} diff --git a/include/beman/scope/beman.scope.cppm b/include/beman/scope/beman.scope.cppm new file mode 100644 index 0000000..59ce381 --- /dev/null +++ b/include/beman/scope/beman.scope.cppm @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// create the beman.scope.gcm in gcm.cache directory +// g++-15 -std=c++26 -O2 -fmodules -fmodule-only -c ${scopetop}/include/beman/scope/beman.scope.cppm +module; + +#include "scope.hpp" + +export module beman.scope; + +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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 55d7d48..1444b00 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,7 +9,12 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(Catch2) -set(ALL_TESTNAMES scope_success scope_exit scope_fail unique_resource) +# module tests will only compile with gcc15 or clang20 and above +if(CMAKE_CXX_SCAN_FOR_MODULES) + set(ALL_TESTNAMES scope_success scope_exit scope_fail unique_resource module) +else() + set(ALL_TESTNAMES scope_success scope_exit scope_fail unique_resource) +endif() message("Tests to be built: ${ALL_TESTNAMES}") diff --git a/tests/module.test.cpp b/tests/module.test.cpp new file mode 100644 index 0000000..a4b22fc --- /dev/null +++ b/tests/module.test.cpp @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define CATCH_CONFIG_MAIN +#include + +// for g++-15 the order is important -- import after includes +import beman.scope; + +struct DummyResource { + bool& cleaned; + + DummyResource(bool& flag) : cleaned(flag) { cleaned = false; } + + bool is_clean() const { return cleaned; } +}; + +TEST_CASE("module-test", "[scope_module_test]") { + bool exit_ran, success_ran, fail_ran = false; + 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); + // clang-format on + } // Normal scope exit + + REQUIRE(exit_ran == true); + REQUIRE(success_ran == true); + REQUIRE(fail_ran == false); + REQUIRE(cleaned == true); + +}