beman-project / exemplar

Example Beman project
Other
7 stars 14 forks source link

Give user a better error message when the selected compiler is missing required functionality #40

Open camio opened 2 weeks ago

camio commented 2 weeks ago

When the user selects a compiler+flags combination that is insufficient to build the project, they're left with a build error that provides no hint that the compiler+flags combination is the problem. This frequently comes up when a user doesn't use -DCMAKE_CXX_STANDARD when invoking CMake. See https://github.com/beman-project/exemplar/pull/38 and https://github.com/beman-project/inplace_vector/pull/6 for examples of this.

@ComicSansMS and @DeveloperPaul123 suggested using cmake-compile-features with language standard arguments, but this has the side-effect of modifying the compiler flags selected by the user running CMake which can create incompatibilities.

This task is to incorporate a try_compile in CMakeLists.txt that attempts to compile a C++ file with the required language features. If it fails, a CMake-time error can be emitted that suggests the user use -DCMAKE_CXX_STANDARD. For beman.example, try_compile will verify namespace foo::bar compatibility.

See https://github.com/beman-project/exemplar/pull/38#issuecomment-2387271443 and https://github.com/beman-project/inplace_vector/pull/6#discussion_r1771995457 for some discussions on this.

bretbrownjr commented 2 weeks ago

Makes sense, and we probably want to invent a project structure to make the test source files discoverable or configurable and a CMake module to drive the testing.

I don't like the idea of copy/pasting this into each library.

However, we can start with a one-off implementation in exemplar and factor it out into something reusable across libraries later.

wusatosi commented 2 weeks ago

An alternative would be to compile a C++ project with bunch of feature tests.

E.g.

#ifndef __cpp_constexpr
static_assert(false, "Compiler must support constexpr");
#endif
bretbrownjr commented 2 weeks ago

I like that idea better when possible.

Pros: It's more standard and portable, requiring less machinery.

On the other hand: It cannot be as specific (i.e., certain constructs that might have quality of implementation issues), and it cannot test for features that do not have standard macros.

wusatosi commented 2 weeks ago

Feature test need to work with a minimum standard C++ version, otherwise we may need to exhaustively check basic functionalities (e.g. constexpr).

Agree with @bretbrownjr that feature test cannot test for quality of implementation issue. I believe most new language feature come with a feature test flag.

The quality of implementation issue will need to be resolved by a minimum compiler version, there's no way around it. This approach is not descriptive to why the user's compiler is not supported, but is also an easy-to-maintain and testable alternative. And honestly this isn't too hard to figure out.

ComicSansMS commented 2 weeks ago

Repeating my comment from https://github.com/beman-project/exemplar/pull/38:

I don't quite follow the rationale for not wanting to put the minimum language standard in the CMake script itself. You have a de-facto PUBLIC requirement on the language version in the library, whether you advertise it through the build system or not. Clients that are not at least using C++17 will not be able to consume the library.

Imho, if clients want to ensure that they always compile with a specific language version, that is the responsibility of the client's build system, not of beman.

I don't see any benefit in pulling this option to the command line, when it is a mandatory option and forgetting to specify it will just stop the library from building. Imho, command line options should be used for optional things, e.g. whether you want to build with or without tests enabled, not mandatory ones.

DeveloperPaul123 commented 2 weeks ago

I agree with @ComicSansMS . I don't really see a reason for not requiring a minimum C++ standard version when building a Beman project. We can use the C++ standard flags in a way that they will not be tied to the library itself so that it doesn't pollute down stream build systems for our consumers. Instead we can use these flags privately when building tests and examples inside a Beman project.

I don't see any benefit in pulling this option to the command line, when it is a mandatory option and forgetting to specify it will just stop the library from building. Imho, command line options should be used for optional things, e.g. whether you want to build with or without tests enabled, not mandatory ones.

I also agree with this. I think CMAKE_CXX_STANDARD should be part of the CMakeLists.txt instead of having to pass it in through the command line.

camio commented 2 weeks ago

I don't quite follow the rationale for not wanting to put the minimum language standard in the CMake script itself.

My understanding is that using target_compile_features could result in the following two situations which would be prevented by what is proposed in this issue. Does this help?

Situation 1: exploits at version upgrade

Consider a large project Big that produces a DLL and, by policy, uses a compiler with C++17 mode flags.

Big decides to use beman.hip_hop for some functionality and incorporates it using add_subdirectory(beman.hip_hop) and the beman::hip_hop target. So far everything is fine because beman.hip_hop uses target_compile_features(beman.hip_hop PUBLIC cxx_std_17) which is compatible with Big's flags.

Now beman.hip_hop developers decide to take advantage of a C++ 20 feature and change the target property to cxx_std_20. Big updates and everything compiles properly and all tests pass because depending on beman::hip_hop silently changes the build flags for all of Big to use -std=c++20.

The Big dll gets deployed and linked against executables built for C++17. Unfortuately, there was an ABI break between -std=c++17 and -std=c++20 for this platform. This causes an exploitable memory access violation and I get another damn letter saying my kids' personal data has been compromised.

Situation 2: bad error message with incompatible compiler+flags combination

Someone compiles beman.hip_hop using a version of gcc that supports C++17, but not C++20. beman.hip_hop uses target_compile_features(beman.hip_hop PUBLIC cxx_std_20) but, because this gcc version doesn't have a -std=c++20 flag, cmake inserts the -std=c++17 flag instead. Instead of failing at CMake time, there is a failure at build time because a C++20 feature is being used. The user isn't given a hint that the compiler itself is the problem.

DeveloperPaul123 commented 2 weeks ago

Thanks for the examples @camio . I think I misunderstood a little bit and I want to clarify my stance.

For INTERFACE targets (i.e. header only libraries) I'm of the opinion that we should not set compile feature flags on the library target itself as this will propagate publicly as David outlined. Instead, we set the necessary flags in the build targets when doing development (i.e. the tests and/or examples). This is what I meant to say in my previous comment.

For library targets, these flags should be set privately.

Of the options above, I do like the idea of using feature macros where possible, but as pointed out, we can't test for everything. It seems that try_compile will offer the clearest information to users in the event they try to use a compiler that does not support all the features needed for a specific Beman library.