Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion MODERNIZATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ document/replace in 1.4.
| 2.5 | streamr-trackerless-network, streamr-libstreamrproxyclient | tn `:protos` over NetworkRpc; proxyclient imports only, C header untouched; full iOS XCFramework + Android smoke. **Final metrics** |
| 2.6 | Consolidation (MANDATORY, interleaved) | Per package, once its last dependent is module-based: move declarations into module purview, delete the package's `include/` tree (grep-enforced). End state: no internal headers anywhere; `#include` only for third-party, generated proto, and the C API header. Finalize lint posture; docs |

## Phase 2.0 — Scaffolding + baselines (PR pending)
## Phase 2.0 — Scaffolding + baselines ✅ (PR #28, merged)
- `cmake/StreamrModules.cmake` (canonical, synced to all packages): Ninja +
non-AppleClang guards, CMP0155 NEW, `streamr_add_module_library()`
(STATIC + `FILE_SET CXX_MODULES` rooted at `modules/`),
Expand Down Expand Up @@ -456,6 +456,41 @@ header parsing, and the top of the expensive list is exactly the
primary metric). Capture the Linux numbers on the self-hosted arm64 box
and an iOS build-only timing before the Phase 2.4 checkpoint.

## Phase 2.1 — Canary: streamr-eventemitter + streamr-json (PR pending)
First real modules. Both packages carry the designed façade shape: one
`.cppm` partition per public header (header `#include`d in the global
module fragment, public names re-exported with `export using`), a primary
interface unit `export import`ing the partitions, INTERFACE→STATIC via
`streamr_add_module_library()`, package tests + example flipped to
`import streamr.<pkg>;`.
- **All gates passed on macOS:**
- Standalone package builds produce BMIs + archives; `export(TARGETS …
CXX_MODULES_DIRECTORY …)` generates the module-consumption info
(smoke-test gate — no fallback needed).
- Downstream `find_package` + `#include` consumers (streamr-utils
standalone) build unchanged — the GMF façade is ODR-safe as designed.
- Root tree: full build + **307/307 tests** (16 eventemitter + 57 json of
them now run through `import`).
- **clangd canary came out BETTER than planned**: clangd 22 lints
import-using `.cpp` files through the CMake-generated module maps —
no experimental flag, no lint exclusions. The planned fallback
(excluding import-using files) was not needed.
- **Findings for the next phases:**
- `import` does not leak transitive std includes the way textual
inclusion did — flipped TUs must include what they use (test needed
`<list>`/`<tuple>`; the example needed `<string>`). Expect a small
include-adding pass with every package flip.
- One genuine clangd-modules quirk: the *constrained*
`std::string(std::string_view)` constructor template is not resolved
in import-using files (plain constructors are fine; the compiler
accepts either). Worked around in the example; watch for recurrence.
- clangd's diagnostics in import-using files can carry module-expanded
line numbers in secondary notes — primary locations are correct.
- Lint posture per plan: `.cppm` files get clang-format only (package
lint.sh extended); headers remain the fully-linted source of truth.
- iOS gate via the PR's `iosbuild` keyword (module lib builds; tests are
host-only). Android modules validation lands with its phase-2.5 gate.

## Lint/IDE survival
- During the façade stage, headers remain the fully-linted source of truth
(`lint.sh` globs only `*.hpp/*.cpp`); `.cppm` added to clang-format only.
Expand Down
27 changes: 22 additions & 5 deletions packages/streamr-eventemitter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,36 @@ set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake")
project(streamr-eventemitter CXX)

message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}")

add_library(streamr-eventemitter INTERFACE)

# C++ modules support (guards, policies, helpers) — must come after
# project() because it inspects the compiler id.
include(${CMAKE_CURRENT_SOURCE_DIR}/StreamrModules.cmake)

# Module façade (MODERNIZATION.md Part 2): the package is now a STATIC
# library whose module interface units re-export the public headers.
# #include consumers are unaffected; import consumers get the BMIs.
streamr_add_module_library(streamr-eventemitter
FILES
modules/streamr.eventemitter.cppm
modules/streamr.eventemitter-EventEmitter.cppm)
add_library(streamr::streamr-eventemitter ALIAS streamr-eventemitter)

target_include_directories(
streamr-eventemitter INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
streamr-eventemitter PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)

# TODO: Is there a better way to do this?
# - Apparently not: https://gitlab.kitware.com/cmake/cmake/-/issues/20511
# - At least parse the deps from the vcpkg.eventemitter file and use them to find_package() in this file and to add the
# find_package() calls to the wrapper

# CXX_MODULES_DIRECTORY: the export now also carries the C++ module
# interface information so that a standalone downstream build can compile
# BMIs from this build tree (Phase 2.1 smoke-test gate).
export(TARGETS streamr-eventemitter
NAMESPACE streamr::
FILE streamr-eventemitter-config-in.cmake)
FILE streamr-eventemitter-config-in.cmake
CXX_MODULES_DIRECTORY streamr-eventemitter-modules)

file(WRITE "${CMAKE_BINARY_DIR}/streamr-eventemitter-config.cmake"
"list(APPEND CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH})\n"
Expand All @@ -48,7 +62,10 @@ if(NOT IOS)
find_package(GTest CONFIG REQUIRED)

add_executable(streamr-eventemitter-test-unit test/unit/EventEmitterTest.cpp)
target_link_libraries(streamr-eventemitter-test-unit
# The test imports streamr.eventemitter, so its sources need module
# dependency scanning.
streamr_enable_imports(streamr-eventemitter-test-unit)
target_link_libraries(streamr-eventemitter-test-unit
PUBLIC streamr-eventemitter
PUBLIC GTest::gtest
PUBLIC GTest::gtest_main
Expand Down
9 changes: 9 additions & 0 deletions packages/streamr-eventemitter/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,12 @@ clangd-tidy -p ./build $FILES

echo "Running clang-format --dry-run on $FILES"
../../run-clang-format.py $FILES

# Module interface units: format check only. clangd-tidy is not run on
# .cppm files (headers remain the fully linted source of truth during the
# façade migration; clangd modules support is still experimental).
MODULE_FILES=$(find ./modules -type f -name "*.cppm" 2>/dev/null | xargs echo)
if [ -n "$MODULE_FILES" ]; then
echo "Running clang-format --dry-run on $MODULE_FILES"
../../run-clang-format.py $MODULE_FILES
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Façade partition over streamr-eventemitter/EventEmitter.hpp. The header
// is included in the global module fragment — its entities stay attached to
// the global module, so `import` and `#include` consumers can be mixed
// ODR-safely during the migration — and the public names are re-exported.
module;

#include "streamr-eventemitter/EventEmitter.hpp"

export module streamr.eventemitter:EventEmitter;

export namespace streamr::eventemitter {

using streamr::eventemitter::BoundEvent;
using streamr::eventemitter::Event;
using streamr::eventemitter::EventEmitter;
using streamr::eventemitter::EventEmitterImpl;
using streamr::eventemitter::HandlerToken;
using streamr::eventemitter::MatchingCallbackType;
using streamr::eventemitter::MatchingEventType;
using streamr::eventemitter::ReplayEventEmitter;
using streamr::eventemitter::StoredEvent;

} // namespace streamr::eventemitter
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Primary module interface unit of streamr.eventemitter. Consumers write
// `import streamr.eventemitter;` and get every partition re-exported.
// (Façade migration, MODERNIZATION.md Part 2: one partition per public
// header; headers remain the source of truth until consolidation.)
export module streamr.eventemitter;

export import :EventEmitter;
4 changes: 3 additions & 1 deletion packages/streamr-eventemitter/test/unit/EventEmitterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
#include <cstddef>
#include <future>
#include <iostream>
#include <list>
#include <string_view>
#include <thread>
#include <tuple>
#include <gtest/gtest.h>

#include "streamr-eventemitter/EventEmitter.hpp"
import streamr.eventemitter;

using streamr::eventemitter::Event;
using streamr::eventemitter::EventEmitter;
Expand Down
34 changes: 28 additions & 6 deletions packages/streamr-json/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,38 @@ if(NOT TARGET Boost::pfr)
set_target_properties(Boost::pfr PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS})
endif()

add_library(streamr-json INTERFACE)
# C++ modules support (guards, policies, helpers) — must come after
# project() because it inspects the compiler id.
include(${CMAKE_CURRENT_SOURCE_DIR}/StreamrModules.cmake)

# Module façade (MODERNIZATION.md Part 2): the package is now a STATIC
# library whose module interface units re-export the public headers.
# #include consumers are unaffected; import consumers get the BMIs.
streamr_add_module_library(streamr-json
FILES
modules/streamr.json.cppm
modules/streamr.json-jsonConcepts.cppm
modules/streamr.json-toJson.cppm
modules/streamr.json-toString.cppm)
add_library(streamr::streamr-json ALIAS streamr-json)

target_include_directories(
streamr-json INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
streamr-json PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)

target_link_libraries(streamr-json INTERFACE Boost::pfr)
target_link_libraries(streamr-json INTERFACE nlohmann_json::nlohmann_json)
# PUBLIC (was INTERFACE): the module interface units themselves compile
# against boost-pfr/nlohmann in their global module fragments.
target_link_libraries(streamr-json PUBLIC Boost::pfr)
target_link_libraries(streamr-json PUBLIC nlohmann_json::nlohmann_json)

if(NOT IOS)
enable_testing()

add_executable(streamr-json-test-unit test/unit/TestJsonConcepts.cpp test/unit/toStringTest.cpp test/unit/toJsonTest.cpp)
target_link_libraries(streamr-json-test-unit
# The tests import streamr.json, so their sources need module dependency
# scanning.
streamr_enable_imports(streamr-json-test-unit)
target_link_libraries(streamr-json-test-unit
PRIVATE streamr-json
PRIVATE GTest::gtest
PRIVATE GTest::gtest_main
Expand All @@ -71,6 +88,7 @@ if(NOT IOS)
endif()

add_executable(streamr-json-example src/examples/JsonExample.cpp)
streamr_enable_imports(streamr-json-example)
target_link_libraries(streamr-json-example PRIVATE streamr-json)

endif()
Expand All @@ -80,9 +98,13 @@ endif()
# - At least parse the deps from the vcpkg.json file and use them to find_package() in this file and to add the
# find_package() calls to the wrapper

# CXX_MODULES_DIRECTORY: the export now also carries the C++ module
# interface information so that a standalone downstream build can compile
# BMIs from this build tree (Phase 2.1 smoke-test gate).
export(TARGETS streamr-json
NAMESPACE streamr::
FILE streamr-json-config-in.cmake)
FILE streamr-json-config-in.cmake
CXX_MODULES_DIRECTORY streamr-json-modules)

file(WRITE "${CMAKE_BINARY_DIR}/streamr-json-config.cmake"
"list(APPEND CMAKE_FIND_ROOT_PATH \"${CMAKE_FIND_ROOT_PATH}\")\n"
Expand Down
9 changes: 9 additions & 0 deletions packages/streamr-json/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,12 @@ clangd-tidy -p ./build $FILES

echo "Running clang-format --dry-run on $FILES"
../../run-clang-format.py $FILES

# Module interface units: format check only. clangd-tidy is not run on
# .cppm files (headers remain the fully linted source of truth during the
# façade migration; clangd modules support is still experimental).
MODULE_FILES=$(find ./modules -type f -name "*.cppm" 2>/dev/null | xargs echo)
if [ -n "$MODULE_FILES" ]; then
echo "Running clang-format --dry-run on $MODULE_FILES"
../../run-clang-format.py $MODULE_FILES
fi
23 changes: 23 additions & 0 deletions packages/streamr-json/modules/streamr.json-jsonConcepts.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Façade partition over streamr-json/jsonConcepts.hpp (see the
// streamr.eventemitter partitions for the pattern rationale). nlohmann and
// boost-pfr arrive in the global module fragment and are NOT re-exported —
// third-party libraries stay #include on the consumer side.
module;

#include "streamr-json/jsonConcepts.hpp"

export module streamr.json:jsonConcepts;

export namespace streamr::json {

using streamr::json::AssignableToNlohmannJson;
using streamr::json::AssociativeType;
using streamr::json::InitializerList;
using streamr::json::IterableType;
using streamr::json::NotAssignableToNlohmannJson;
using streamr::json::PointerLike;
using streamr::json::PointerType;
using streamr::json::ReflectableType;
using streamr::json::TypeWithToJson;

} // namespace streamr::json
22 changes: 22 additions & 0 deletions packages/streamr-json/modules/streamr.json-toJson.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Façade partition over streamr-json/toJson.hpp. `using
// streamr::json::toJson;` re-exports the whole overload set (all concept
//-constrained specializations).
module;

#include "streamr-json/toJson.hpp"

export module streamr.json:toJson;

export namespace streamr::json {

using streamr::json::addStructElementsToJson;
using streamr::json::addStructElementToJson;
using streamr::json::AssignableToJsonBuilder;
using streamr::json::json;
using streamr::json::JsonBuilder;
using streamr::json::JsonInitializerList;
using streamr::json::pointerToJson;
using streamr::json::StreamrJsonInitializerList;
using streamr::json::toJson;

} // namespace streamr::json
13 changes: 13 additions & 0 deletions packages/streamr-json/modules/streamr.json-toString.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Façade partition over streamr-json/toString.hpp.
module;

#include "streamr-json/toString.hpp"

export module streamr.json:toString;

export namespace streamr::json {

using streamr::json::toString;
using streamr::json::TypeWithToString;

} // namespace streamr::json
9 changes: 9 additions & 0 deletions packages/streamr-json/modules/streamr.json.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Primary module interface unit of streamr.json. Consumers write
// `import streamr.json;` and get every partition re-exported.
// (Façade migration, MODERNIZATION.md Part 2: one partition per public
// header; headers remain the source of truth until consolidation.)
export module streamr.json;

export import :jsonConcepts;
export import :toJson;
export import :toString;
22 changes: 16 additions & 6 deletions packages/streamr-json/src/examples/JsonExample.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#include <exception>
#include <iostream>
#include <string_view>
#include "streamr-json/toJson.hpp"
#include "streamr-json/toString.hpp"
#include <string>
#include <utility>
#include <nlohmann/json.hpp>

import streamr.json;

using streamr::json::StreamrJsonInitializerList;
using streamr::json::toJson;
Expand Down Expand Up @@ -34,8 +37,12 @@ class ClassWithPrivateSection {
std::string name;

public:
ClassWithPrivateSection(int data, std::string_view name)
: data(data), name(name) {}
// NB: takes std::string by value instead of std::string_view: clangd's
// (still experimental) modules support cannot resolve the constrained
// std::string(string_view) constructor template in import-using files,
// and this is an example — not worth a lint exclusion.
ClassWithPrivateSection(int data, std::string name)
: data(data), name(std::move(name)) {}

// We need to provide a toJson()/toString() methods to be able
// to serialize the class because it has private sections
Expand All @@ -46,7 +53,7 @@ class ClassWithPrivateSection {
[[nodiscard]] std::string toString() const { return (toJson()).dump(); }
};

int main() {
int main() try {
MyStruct s{.x = 1, .y = "hello"};

// Converting a struct to a json object
Expand Down Expand Up @@ -85,4 +92,7 @@ int main() {
std::cout << str2 << '\n';

return 0;
} catch (const std::exception& e) {
std::cerr << "Example failed: " << e.what() << '\n';
return 1;
}
3 changes: 2 additions & 1 deletion packages/streamr-json/test/unit/TestJsonConcepts.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <gtest/gtest.h>
#include "streamr-json/toJson.hpp"

import streamr.json;

using streamr::json::toJson; // NOLINT

Expand Down
2 changes: 1 addition & 1 deletion packages/streamr-json/test/unit/toJsonTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

#include "TestClass.hpp"
#include "WeatherData.hpp"
#include "streamr-json/toJson.hpp"
import streamr.json;

using streamr::json::toJson;

Expand Down
4 changes: 2 additions & 2 deletions packages/streamr-json/test/unit/toStringTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
#include <vector>
#include <gtest/gtest.h>

#include "streamr-json/toString.hpp"

#include "TestClass.hpp"
#include "WeatherData.hpp"

import streamr.json;

using streamr::json::toString;

// NOLINTBEGIN(readability-magic-numbers)
Expand Down
Loading