Skip to content

Conversation

@nickelpro
Copy link
Member

This is a big block of changes (well, as big as it gets with exemplar) that does two things:

  • Incorporates lessons learned from updating beman.scope to be packageable, as well as discussions had at C++Now
  • Converts beman.exemplar to be "modules-first", which is to say it requires module support from the toolchain in order to run tests and build examples, but still supports using the traditional header files when installed as a library

This does not violate anything in the Beman standard, but rather makes use of things we might want to have concrete consensus on now that they've arisen as issues.

I do not intend for this to be merged outright, it's more a piece of performance art. It's easier to debate pros and cons with a concrete implementation of all those pros and cons laid out.

Related Discussions:

@nickelpro nickelpro force-pushed the modules-cml-overhaul branch from 0466eb7 to 307fb4c Compare May 31, 2025 00:32
@nickelpro
Copy link
Member Author

Well the good news is the lint passes.

Most of the test failures are on compilers that don't support modules and the MSVC ones are... something

@nickelpro nickelpro force-pushed the modules-cml-overhaul branch from 307fb4c to 10f35cd Compare May 31, 2025 01:11
@JeffGarland
Copy link
Member

I do not intend for this to be merged outright, it's more a piece of performance art. It's easier to debate pros and cons with a concrete implementation of all those pros and cons laid out.

I'm going to be clapping the performance personally :)

os: ubuntu-latest
toolchain: "cmake/gnu-toolchain.cmake"
cpp_version: 17
cpp_version: 20
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too me this is consistent with other discussions -- it's simply too taxing on library authors to support below 20. We can leave that to boost...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we had this discussion last year at sync.
The consensus is to leave minimum support version undefined but still encourage libraries to support lower version if possible.
The big argument for 20 was concept support, the big argument for below companies like Adobe is still using 17 and we want their usage experience.
Exemplar can support 17 (hell, it can probably support 11, or even 99!), so to signify this we made exemplar support 17.

I think we can revisit this once 26 comes out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The consensus is to leave minimum support version undefined but still encourage libraries to support lower version if possible.

Consensus can be changed. And, technically I'm not against leaving the minimum undefined, but my personal recommendation to library authors is to not bother with versions below 20. Concepts, ranges, and a bunch of other things are just too consequential in building a good modern library. We have limited intellectual capital here and time wasted on supporting older version isn't applied to building a better future. Remember, we're in the pivot to work on c++29 libraries. And for 29 don't be surprised if we see people requiring 26 for reflection and contracts.

Let me back this up with some data, however. According to the 2024 survey, C++20 is not allowed at all in 37% of projects. The year over year progress from 2023, was in the 6% range -- from 43% not allowed in 2023 to 37%. About 50% answered that they expected to be able to upgrade this year. But conservatively I expect we'd be in the 30% exclusion range and dropping fast. When you note that 8% are still excluded from C++17 this is really only a 22% exclusion. This is all reasonable as far as I'm concerned -- the slow adopters will eventually get there and our focus here is on the future, not the past.

As a side note, the barriers to adopting 20+ with an older code base are small in my experience. We've continued to break very little legacy code so for lots of stuff it's simply changing the compiler flags and moving on.

Copy link
Member

@wusatosi wusatosi Jun 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah looks like good points, take this to the sync.
I don't think most libs can support versions below C++20 anyway.

I think you basically have to convince david on this.

)

add_subdirectory(src/beman/exemplar)
if(BEMAN_EXEMPLAR_INSTALL_CONFIG_FILE_PACKAGE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is exemplar and all except you are learning cmake -- maybe a comment to explain the function?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is exemplar and all except you are learning cmake

From previous PRs, I don't think this is the intention. CC @camio

@nickelpro nickelpro force-pushed the modules-cml-overhaul branch from 10f35cd to 18fc27f Compare May 31, 2025 01:25
@nickelpro
Copy link
Member Author

nickelpro commented May 31, 2025

I've pinged @wusatosi about the CI failures. The root of it is despite appearing to run on many different compilers, actually all the GNU builds run on GCC 13 (which doesn't support modules) and all the LLVM builds run on Clang 18 (which doesn't support modules unless you turn off language extensions, which we don't go out of our way to do). I haven't investigated the MSVC failure yet.

So the CI has been lying a little bit for a long time. If you wanted to test your code on GCC 13 and Clang 18 though it worked really good. Very tested.

@JeffGarland
Copy link
Member

So the CI has been lying a little bit for a long time. If you wanted to test your code on GCC 13 and Clang 18 though it worked really good. Very tested.

Good catch -- obviously no one is looking closely at the CI lol

@wusatosi
Copy link
Member

wusatosi commented May 31, 2025

I've pinged @wusatosi about the CI failures. The root of it is despite appearing to run on many different compilers, actually all the GNU builds run on GCC 13 (which doesn't support modules) and all the LLVM builds run on Clang 18 (which doesn't support modules unless you turn off language extensions, which we don't go out of our way to do). I haven't investigated the MSVC failure yet.

So the CI has been lying a little bit for a long time. If you wanted to test your code on GCC 13 and Clang 18 though it worked really good. Very tested.

Weird, I have manually tested this before.
I will switch this to testingcontainer, I was planning on a larger scale refactoring, but look like this is broken to the point that a quick fix is warrented.

Edit: This issue was introduced by #159 .

Edit2: PR up #168

@wusatosi
Copy link
Member

wusatosi commented May 31, 2025

CI fail is not only because proper version is not selected.

Please check compiler test seris, e.g. llvm 18 CI test.
Error message is as following:

/home/runner/work/exemplar/exemplar/examples/identity_direct_usage.cpp:6:8: fatal error: module 'beman.exemplar' not found
    6 | import beman.exemplar;
      | ~~~~~~~^~~~~

See: https://github.com/bemanproject/exemplar/actions/runs/15358684463/job/43222580631?pr=167#step:5:69

Edit: okay I am mistaken, clang 18 does not support modules.

workflow_dispatch:
schedule:
- cron: '30 15 * * *'
- cron: "30 15 * * *"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch, I think we actually want to change them all the single qutote because github expressions does not support double quote...

that's definitely topics of another PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm for this refactoring, but can we make this a single PR, it's unclear exactly what has been changed.

@wusatosi wusatosi mentioned this pull request Jun 1, 2025
1 task
@JeffGarland
Copy link
Member

JeffGarland commented Jun 1, 2025

CI fail is not only because proper version is not selected.

Please check compiler test seris, e.g. llvm 18 CI test. Error message is as following:

/home/runner/work/exemplar/exemplar/examples/identity_direct_usage.cpp:6:8: fatal error: module 'beman.exemplar' not found
    6 | import beman.exemplar;
      | ~~~~~~~^~~~~

See: https://github.com/bemanproject/exemplar/actions/runs/15358684463/job/43222580631?pr=167#step:5:69

Edit: okay I am mistaken, clang 18 does not support modules.

And in scope PR what I did is check the compiler versions and exclude anything that isn't clang-20 or g++-15 (not yet in CI). I don't have clang19 setup yet, so I wasn't confident modules would work there.

And related note -- scope CI is actually testing other compilers and versions.

@wusatosi
Copy link
Member

wusatosi commented Jun 1, 2025

And related note -- scope CI is actually testing other compilers and versions.

Thanks for checking, the bug is introduced in #159 which as far as I know isn't used anywhere yet.

And in scope PR what I did is check the compiler versions and exclude anything that isn't clang-20 or g++-15 (not yet in CI). I don't have clang19 setup yet, so I wasn't confident modules would work there.

This is out of scope, but if we want usage experience we probably should not be this aggressive on using the latest compilers.

@JeffGarland
Copy link
Member

JeffGarland commented Jun 2, 2025

And in scope PR what I did is check the compiler versions and exclude anything that isn't clang-20 or g++-15 (not yet in CI). I don't have clang19 setup yet, so I wasn't confident modules would work there.

I setup clang++-19 and have now included it in the modules supporting compilers since it appears to work fine.

This is out of scope, but if we want usage experience we probably should not be this aggressive on using the latest compilers.

We can only support modules usage on compilers that are capable of supporting modules (that's probably around 4 at the moment). The non module users can have a broader range of compilers. Also, while I understand your advocacy for earlier c++ versions and feature sets, I will continue to remind you that it's not the focus of Beman. Again, let Boost do that -- they've self selected on that -- we're about the future, not the past. I expect much more in the way of libraries using advanced compilers as reflection and contracts get compiler support. Which by it's very nature excludes everyone below 26.

@wusatosi
Copy link
Member

wusatosi commented Jun 2, 2025

We can only support modules usage on compilers that are capable of supporting modules (that's probably around 4 at the moment). The non module users can have a broader range of compilers.

I definitely support this.

I expect much more in the way of libraries using advanced compilers as reflection and contracts get compiler support. Which by it's very nature excludes everyone below 26.

While I understand supporting 17 maybe not achievable for most beman library (inplace vector doesn't support it either), I do think it is very useful for libraries to support lower C++ versions, e.g. C++20 for right now. In a few years, you can argue for C++23. But if we are always only supporting the latest compilers, latest C++ versions, it will limit us on how we gather usage experience.

while I understand your advocacy for earlier c++ versions and feature sets, I will continue to remind you that it's not the focus of Beman.

User feedback is literally in our mission:

support the efficient design and adoption of the highest quality C++ standard libraries through implementation experience, user feedback, and technical expertise.

@nickelpro
Copy link
Member Author

We can do a dumb thing where we flag-fork the examples to use import when it's available and #include otherwise, but I don't love that because it's ugly boilerplate. This isn't a problem for the library itself, it only matters for tests and examples where we're more free to use ugly solutions.

@dietmarkuehl
Copy link
Member

We can do a dumb thing where we flag-fork the examples to use import when it's available and #include otherwise, but I don't love that because it's ugly boilerplate. This isn't a problem for the library itself, it only matters for tests and examples where we're more free to use ugly solutions.

You want the exampmes to use import beman.exemplar;? We could choose to not compile them on platfforms which don’t support them, possibly with instructions on how to fix things.

While I want to support using modules, I wouldn’t want to force them. I that sense I think it would be good if Beam libraries do support modules but using them via headers should also be possible.

@nickelpro
Copy link
Member Author

nickelpro commented Jun 4, 2025

I want examples and tests to use modules where feasible, yes, because that's the only way to ensure the full API is being properly exported by the interface unit.

Being usable via headers is just as important, but given that the typical interface unit is an include of the headers followed by a bunch of export expressions, testing the module interface also tests the headers (but not vice-versa).

@wusatosi
Copy link
Member

wusatosi commented Jun 4, 2025

Just to double check, this is meant to be merged into mainline, not only a POC right @nickelpro

@nickelpro
Copy link
Member Author

If we want all the things that this PR does, it's meant to be merged.

If we don't want all the things it does, it's an illustration for the general approach and proving out things like the CI problems.

@steve-downey
Copy link
Member

While I understand supporting 17 maybe not achievable
Basically before C++20 you are emulating concepts and traits that can't be implemented correctly without compiler support. Some might be, but traits in particular are usually compiler builtins we could not make work without compiler support.

While I would not discourage a highly motivated developer, I will also say that if you are emulating concepts for a library proposal with SFINAE or other metaprogramming techniques, you still also need to implement the standard ones that are specified in Constraints and Mandates clauses, or you are not really implementing the standard proposal.

That's not a criticism of the quality of a library, or the utility of having it available at $job.

But it isn't the evidence I want to see that the specification is correct---and getting the specification correct is tricky.

Moreover, it's also very likely that eliminating the features enabled by C++20 and up can make the implementation, and even the specification, worse.

The related question of how far to go with downlevel compilers is not very far. Again, putting into use is a goal, but if gcc-12 doesn't work, so much worse for gcc-12, unless the fix is very trivial.

This might not be the best place to discuss policy, but it is a place.

@nickelpro
Copy link
Member Author

nickelpro commented Jun 4, 2025

My two cents is that libraries that aim to be a part of the standard have a secondary duty to dogfood the standard. If modules, or concepts, or pattern matching, or whatever, are totally unworkable in the implementation of a standards proposal, that is something the people who go to committee meetings should have first hand experience with.

To that end, using modern compilers with the newer standard features is good source of nutritious kibble.

COMPONENT beman.exemplar

FILE_SET CXX_MODULES
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this intended to install the cppm files to include tree?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There has been ongoing arguments about where they should go, since they aren't files that one should #include, but there are a two packages that have the module file in include, vulkan and gcc-15 if I recall correctly, so there's some precedent. It doesn't really matter terribly as long as the metadata file for the module points to the location so the module can be compiled in the context of the consumer, with the consumers compiler, flags, etc.

Copy link
Member Author

@nickelpro nickelpro Jun 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They exist outside the bounds of what is defined by the FHS. In spirit they are closest to header files, being architecture-independent interface descriptions, so they go in include.

@ClausKlein
Copy link

ClausKlein commented Jun 6, 2025

Only outstanding issues are #168 and a solution for Apple Clang

I would forget apple-clang which does't fully support POSIX and CXX_MODULES at all.

Also on OSX llvm-19 and llvm-20 with both I have still problems if installed with brew.

see https://gitlab.kitware.com/cmake/cmake/-/issues/25965#note_1575723
and https://gitlab.kitware.com/cmake/cmake/-/issues/25965#note_1645118

I use gcc-15 installed with brew, which works fine together with cmake v4.0.2

@nickelpro
Copy link
Member Author

It's not super likely CMake will support spins of a compiler which change default behavior like that. The bug should go upstream to the Homebrew cask, they shouldn't be modifying the install such that clang needs help to find its own resources.

@neatudarius
Copy link
Member

neatudarius commented Jun 16, 2025

  • Incorporates lessons learned from updating beman.scope to be packageable, as well as discussions had at C++Now
  • Converts beman.exemplar to be "modules-first", which is to say it requires module support from the toolchain in order to run tests and build examples, but still supports using the traditional header files when installed as a library

Can we have the module-first be trigger by a compile option?

e.g. have 2 build modes

  • non-module
  • with-module

And we'll duplicate these 2 examples from examples to have

  • examples/identity_as_default_projection_with_modules.cpp
  • examples/identity_as_default_projection.cpp

TBD, I also think we should have tests with module-version and non-module version.

CC: @nickelpro @JeffGarland

@steve-downey
Copy link
Member

Can we have the module-first be trigger by a compile option?

e.g. have 2 build modes

  • non-module
  • with-module

And we'll duplicate these 2 examples from examples to have

For development workflows you need to build and test both of them. I'm not sure about packaging workflows. For super-project workflows it probably is just a matter of which targets are depended on, and should be transparent to the consumer? I hope, or we have deeper issues for the build system.

@nickelpro
Copy link
Member Author

nickelpro commented Jun 17, 2025

Can we have the module-first be trigger by a compile option?

There's no reason for or advantage to such a division beyond being able to lower the required CMake/compiler versions for the packaging machine.

For consuming of the package there is no advantage whatsoever.

I'm not opposed in-principle, but every option should have a motivating use case for why it exists.

@nickelpro
Copy link
Member Author

it probably is just a matter of which targets are depended on, and should be transparent to the consumer? I hope, or we have deeper issues for the build system.

A single target supports both workflows, a header-based consumer need not even be aware that a module is available and vice-versa.

@neatudarius
Copy link
Member

it probably is just a matter of which targets are depended on, and should be transparent to the consumer? I hope, or we have deeper issues for the build system.

A single target supports both workflows, a header-based consumer need not even be aware that a module is available and vice-versa.

I think we have 3 different things:

  • the actual library, which should be able to be built in 2 modes: modules and non-modules version. We should not assume it's always header only (check - iterator interfaces).
  • examples/: here we need to have 2 examples with beman.examplar with modules and the same examples without modules. This is what the users will do.
  • tests/: We should have lots of tests with the non-module version of the library to get coverage. Then 1-2 tests which just tests we can build and link with the module version - e.g. call a simple function from the library, just a basic case.

I had a similar discussion with @JeffGarland yesterday and it seems to share the same opinion.

@nickelpro
Copy link
Member Author

nickelpro commented Jun 17, 2025

the actual library, which should be able to be built in 2 modes: modules and non-modules version. We should not assume it's always header only (check - iterator interfaces).

This is just one mode. There is no semantic difference between with and without modules. I'm asking for a use case so I can explain this more practically, "I want to be able to do X".

examples/: here we need to have 2 examples with beman.examplar with modules and the same examples without modules. This is what the users will do.

All the examples will be identical except for a single line, the one that imports the module or includes the header. I do not think the examples should be replicated to demonstrate a single line can be changed. This can maybe be a configuration option, but honestly what's wrong with just leaving a comment like this PR does?

tests/: We should have lots of tests with the non-module version of the library to get coverage. Then 1-2 tests which just tests we can build and link with the module version - e.g. call a simple function from the library, just a basic case.

This doesn't do anything, the exact same code is run. The coverage is identical.

@ClausKlein
Copy link

There is no semantic difference between with and without modules

That is not possible:

bash-5.2$ cmake --workflow appleclang-debug --fresh 
Executing workflow step 1 of 3: configure preset "appleclang-debug"

-- The CXX compiler identification is AppleClang 16.0.0.16000026
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- The C compiler identification is AppleClang 16.0.0.16000026
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE
-- Looking for __cpp_lib_ranges
-- Looking for __cpp_lib_ranges - found
Examples to be built: identity_direct_usage;identity_as_default_projection
-- Configuring done (8.6s)
CMake Error in CMakeLists.txt:
  The target named "beman.exemplar" has C++ sources that may use modules, but
  the compiler does not provide a way to discover the import graph
  dependencies.  See the cmake-cxxmodules(7) manual for details.  Use the
  CMAKE_CXX_SCAN_FOR_MODULES variable to enable or disable scanning.


-- Generating done (0.0s)
CMake Generate step failed.  Build files cannot be regenerated correctly.
bash-5.2$ 

@ClausKlein
Copy link

ClausKlein commented Jun 17, 2025

There is an additional problem with gest, if locally installed, the version is not right checked:

bash-5.2$ CXX=g++-15 cmake -S . -B build --fresh  -D CMAKE_CXX_STANDARD=20
-- The CXX compiler identification is GNU 15.1.0
-- Checking whether CXX compiler has -isysroot
-- Checking whether CXX compiler has -isysroot - yes
-- Checking whether CXX compiler supports OSX deployment target flag
-- Checking whether CXX compiler supports OSX deployment target flag - yes
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/local/bin/g++-15 - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found GTest: /usr/local/lib/libgtest.a (Required is at least version "2.17")
-- Looking for __cpp_lib_ranges
-- Looking for __cpp_lib_ranges - found
Examples to be built: identity_direct_usage;identity_as_default_projection
-- Configuring done (2.4s)
-- Generating done (0.0s)
-- Build files have been written to: /Users/clausklein/Workspace/cpp/beman-project/exemplar/build
bash-5.2$ 


FAILED: tests/beman/exemplar/beman.exemplar.tests.identity tests/beman/exemplar/beman.exemplar.tests.identity[1]_tests.cmake /Users/clausklein/Workspace/cpp/beman-project/exemplar/build/tests/beman/exemplar/beman.exemplar.tests.identity[1]_tests.cmake 
: && /usr/local/bin/g++-15  -Wl,-search_paths_first -Wl,-headerpad_max_install_names  tests/beman/exemplar/CMakeFiles/beman.exemplar.tests.identity.dir/identity.test.cpp.o -o tests/beman/exemplar/beman.exemplar.tests.identity  libbeman.exemplar.a  /usr/local/lib/libgtest_main.a  /usr/local/lib/libgtest.a && cd /Users/clausklein/Workspace/cpp/beman-project/exemplar/build/tests/beman/exemplar && /Users/clausklein/.direnv/python-3.13.3/lib/python3.13/site-packages/cmake/data/bin/cmake -D TEST_TARGET=beman.exemplar.tests.identity -D TEST_EXECUTABLE=/Users/clausklein/Workspace/cpp/beman-project/exemplar/build/tests/beman/exemplar/beman.exemplar.tests.identity -D TEST_EXECUTOR= -D TEST_WORKING_DIR=/Users/clausklein/Workspace/cpp/beman-project/exemplar/build/tests/beman/exemplar -D TEST_EXTRA_ARGS= -D TEST_PROPERTIES= -D TEST_PREFIX= -D TEST_SUFFIX= -D TEST_FILTER= -D NO_PRETTY_TYPES=FALSE -D NO_PRETTY_VALUES=FALSE -D TEST_LIST=beman.exemplar.tests.identity_TESTS -D CTEST_FILE=/Users/clausklein/Workspace/cpp/beman-project/exemplar/build/tests/beman/exemplar/beman.exemplar.tests.identity[1]_tests.cmake -D TEST_DISCOVERY_TIMEOUT=5 -D TEST_DISCOVERY_EXTRA_ARGS= -D TEST_XML_OUTPUT_DIR= -P /Users/clausklein/.direnv/python-3.13.3/lib/python3.13/site-packages/cmake/data/share/cmake-4.0/Modules/GoogleTestAddTests.cmake
Undefined symbols for architecture x86_64:
  "testing::internal::MakeAndRegisterTestInfo(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, char const*, char const*, char const*, testing::internal::CodeLocation, void const*, void (*)(), void (*)(), testing::internal::TestFactoryBase*)", referenced from:
      __static_initialization_and_destruction_0() in identity.test.cpp.o
      __static_initialization_and_destruction_0() in identity.test.cpp.o
      __static_initialization_and_destruction_0() in identity.test.cpp.o
      __static_initialization_and_destruction_0() in identity.test.cpp.o
  "testing::internal::EqFailure(char const*, char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, bool)", referenced from:
      testing::AssertionResult testing::internal::CmpHelperEQFailure<int, int>(char const*, char const*, int const&, int const&) in identity.test.cpp.o
      testing::AssertionResult testing::internal::CmpHelperEQFailure<int const*, int const*>(char const*, char const*, int const* const&, int const* const&) in identity.test.cpp.o
  "std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>::find(char, unsigned long) const", referenced from:
      testing::internal::FormatDeathTestOutput(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&) in libgtest.a[2](gtest-all.cc.o)

# # # 

   NOTE: a missing vtable usually means the first non-inline virtual member function has no definition.
  "vtable for std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char>>", referenced from:
      std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char>>::basic_stringstream[abi:ue170006]() in libgtest.a[2](gtest-all.cc.o)
   NOTE: a missing vtable usually means the first non-inline virtual member function has no definition.
  "vtable for std::__1::basic_ostringstream<char, std::__1::char_traits<char>, std::__1::allocator<char>>", referenced from:
      std::__1::basic_ostringstream<char, std::__1::char_traits<char>, std::__1::allocator<char>>::basic_ostringstream[abi:ue170006]() in libgtest.a[2](gtest-all.cc.o)
   NOTE: a missing vtable usually means the first non-inline virtual member function has no definition.
ld: symbol(s) not found for architecture x86_64
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.
bash-5.2$ 


@ClausKlein
Copy link

@nickelpro as you noted in a review, it the project must be build without presets!

bash-5.2$ git diff CMakeLists.txt
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a241e5e..23563d2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-cmake_minimum_required(VERSION 3.28)
+cmake_minimum_required(VERSION 3.28...4.0)
 
+set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES cmake/use-fetch-content.cmake)
 project(
     beman.exemplar # CMake Project Name, which is also the name of the top-level
     # targets (e.g., library, executable, etc.).
bash-5.2$ 

@nickelpro
Copy link
Member Author

nickelpro commented Jun 17, 2025

In a suitably provisioned environment, yes. However, how the user chooses to provide GTest is none of our business. They may have already downloaded it from vcpkg or the Ubuntu repos, or installed it from source.

If they need one-click builds without regards for dependency management, then they use presets. You should never set() any CMAKE_* global in a CML. It's not up to you what a user wants to pass as the top-level include, overriding their choice is always wrong.

The issue you're referencing broke tests with idiomatic CMake usage in a correctly provisioned environment.

@ClausKlein
Copy link

ClausKlein commented Jun 17, 2025

@nickelpro sorry, but the find_package() without version is not reliable!

And with an other invalid version set, it is silently ignored:

Found GTest: /usr/local/lib/libgtest.a (Required is at least version "2.17")

You see the linker errors on OSX with gtest installed with brew!

cat lockfile.json:

{
  "dependencies": [
    {
      "name": "googletest",
      "package_name": "GTest",
      "git_repository": "https://github.com/google/googletest.git",
      "git_tag": "6910c9d9165801d8827d628cb72eb7ea9dd538c5"
    }
  ]
}
  • Which version is meant with this tag?
  • How should the use know, how to compile it?

@nickelpro
Copy link
Member Author

nickelpro commented Jun 17, 2025

I wouldn't generally provide a <version> tag to find_package() unless you're 100% about the version requirement. The documentation calls this out:

The <PackageName> is the only mandatory argument. The <version> is often omitted, and REQUIRED should be given if the project cannot be configured successfully without the package.

The correct answer is to rely on the shipped dependency manifest(s) to provision the correct versions of dependencies, trying to verify that after the fact is a redundancy that can only lead to errors.

I didn't write the FetchContent machinery and I agree it leaves a lot to be desired. It exists because there was no consensus to give up on FetchContent. FetchContent cannot work as a general purpose package manager, trying to make it into one is error prone. Better manifest formats are more readable than this ad-hoc one, but honestly it's the best you can do with FetchContent.

Downstream packagers shouldn't know anything about project dependency manifests, or the CML, or anything. The Debian maintainer assigned to package your library doesn't want to read any of those things and does not care. They want a ReadMe that gives a set of package names and compatibility ranges, which they will pull from their own Debian repos when packaging your project.

Same goes for the Arch Linux TU, the vcpkg contributor, the Conan recipe author, etc, etc, etc. They all have their own dependency sources they're going to use, dependency manifests are irrelevant to them and they do not want to learn a hundred different manifest formats and build system conventions.

The correct way to talk to packagers about dependencies is a bulleted list in the ReadMe.

@nickelpro
Copy link
Member Author

For the AppleClang failure:

Yes, as mentioned above it makes packaging with AppleClang impossible. AppleClang can still consume an installed Exemplar built by a compiler that knows about modules, but it cannot build exemplar because it doesn't support enough of C++20 and Exemplar now requires C++20.

I mentioned this when I said:

There's no reason for or advantage to such a division beyond being able to lower the required CMake/compiler versions for the packaging machine.

I'm not sold on my own position here. It might be reasonable to allow compilers which don't support modules to package a module library. I don't love it though, because the downstream consuming the package has no mechanism to infer this particular build of Exemplar 2.3.1 (or whatever) was built without modules support.

I'm thinking on it, I might have a hack for Apple Clang.

@JeffGarland
Copy link
Member

With the Bulgaria meeting and everything else going on I'd lost track of this PR -- looks like this is mostly ready to go?

Copy link

@ClausKlein ClausKlein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please do not disable the macOS platform!

@nickelpro
Copy link
Member Author

No, this is not ready.

As discussed CMake needs to do the right thing on platforms where modules are not available, principally AppleClang but also older compilers. I'll rework this to perform "the right thing" and then push for review and merging.

@camio
Copy link
Member

camio commented Oct 10, 2025

Closing out this PR since there hasn't been activity since July. Feel free to reopen if there's interest in continuing it in the short term.

@camio camio closed this Oct 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants