Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce CMake toolchain #76

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open

Conversation

wusatosi
Copy link
Member

@wusatosi wusatosi commented Nov 14, 2024

This PR adopts @bretbrownjr 's suggestion in #44 (review).

You can use variables like CMAKE_CXX_FLAGS_Debug_INIT to tune what a Debug or Release build entails, for what it's worth. It's maybe a little nicer to use those variables instead of CMAKE_CXX_FLAGS. But not a huge deal.

https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_FLAGS_CONFIG_INIT.html

This PR introduces toolchain files for supported platforms:

  • gcc
  • clang
  • msvc

Updated CI and preset to use the new toolchain files.

Race with #82

cmake/toolchain.cmake Outdated Show resolved Hide resolved
Copy link
Member

@steve-downey steve-downey left a comment

Choose a reason for hiding this comment

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

"We need to verify CMAKE_CXX_COMPILER_ID for g++ on macos is AppleClang."

Confirm what the compiler identification is for the default false g++ on Darwin is.

Marking "Request changes" so this doesn't get landed prematurely.

cmake/toolchain.cmake Outdated Show resolved Hide resolved
@ClausKlein

This comment was marked as resolved.

@wusatosi

This comment was marked as resolved.

@steve-downey
Copy link
Member

So, yes:
The CXX compiler identification is AppleClang 16.0.0.16000026

@steve-downey
Copy link
Member

Not sure if picking the toolchain for Darwin better, or making the generic toolchain smarter would work better, but either should work?

@ClausKlein
Copy link

Wait, that can't work!

iMac:exemplar clausklein$ cmake --preset gcc-debug  --trace-expand --trace-source=toolchain.cmake
Put cmake in trace mode, but with variables expanded.
Put cmake in trace mode, but output only lines of a specified file. Multiple options are allowed.
Preset CMake variables:

  BEMAN_BUILDSYS_SANITIZER="ASan"
  CMAKE_BUILD_TYPE="Debug"
  CMAKE_CXX_COMPILER="g++"
  CMAKE_CXX_STANDARD="20"
  CMAKE_TOOLCHAIN_FILE="cmake/toolchain.cmake"

/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(3):  set(CMAKE_C_FLAGS_RELEASE_INIT -O3 )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(4):  set(CMAKE_CXX_FLAGS_RELEASE_INIT -O3 )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(6):  set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT -O3 )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(7):  set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT -O3 )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(16):  if(DEFINED BEMAN_BUILDSYS_SANITIZER )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(17):  if(BEMAN_BUILDSYS_SANITIZER STREQUAL ASan )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(19):  set(SANITIZER_FLAGS -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(24):  if(NOT CMAKE_CXX_COMPILER_ID )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(25):  message(WARNING toolchain is used before CMAKE_CXX_COMPILER_ID was set! )
CMake Warning at cmake/toolchain.cmake:25 (message):
  toolchain is used before CMAKE_CXX_COMPILER_ID was set!
Call Stack (most recent call first):
  build/gcc-debug/CMakeFiles/3.31.0-dirty/CMakeSystem.cmake:6 (include)
  CMakeLists.txt:5 (project)


/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(27):  if(APPLE )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(28):  message(STATUS Using GCC on macOS; excluding -fsanitize=leak )
-- Using GCC on macOS; excluding -fsanitize=leak
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(44):  list(APPEND CMAKE_C_FLAGS_DEBUG_INIT -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(45):  list(APPEND CMAKE_CXX_FLAGS_DEBUG_INIT -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined )
-- The CXX compiler identification is AppleClang 16.0.0.16000026
-- Detecting CXX compiler ABI info
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(3):  set(CMAKE_C_FLAGS_RELEASE_INIT -O3 )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(4):  set(CMAKE_CXX_FLAGS_RELEASE_INIT -O3 )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(6):  set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT -O3 )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(7):  set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT -O3 )
/Users/clausklein/Workspace/cpp/beman-project/exemplar/cmake/toolchain.cmake(16):  if(DEFINED BEMAN_BUILDSYS_SANITIZER )
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
Examples to be built: identity_direct_usage;identity_as_default_projection
-- Configuring done (2.4s)

@wusatosi
Copy link
Member Author

Not sure if picking the toolchain for Darwin better, or making the generic toolchain smarter would work better, but either should work?

I am leaning on having separate tool chain files and having like a central tool chain dispatch logic based on compiler and platform.

But then I realized there's not that much variance in building across platforms and compilers, at least in exemplar, to warrant separate files.

@ClausKlein
Copy link

But then I realized there's not that much variance in building across platforms and compilers, at least in exemplar, to warrant separate files.

That is why often I use the project_options
see i.e.: https://github.com/aminya/project_options/blob/main/src/Sanitizers.cmake

@wusatosi
Copy link
Member Author

That is why often I use the project_options see i.e.: https://github.com/aminya/project_options/blob/main/src/Sanitizers.cmake

That's project looks fantastic!

I can bring this up in weekly sync and see if we want to use this.
Clang-tidy support, coverage, doxygen (and potentially vcpkg) are improvement features we would love to have, it would be fantastic if we can get all these done with this dependency.

@wusatosi
Copy link
Member Author

Ah I think tool chain file is executed before project(), so CMAKE_CXX_COMPILER_ID maybe unset?

@wusatosi wusatosi force-pushed the toolchain branch 2 times, most recently from 5f20499 to 117fcd9 Compare November 15, 2024 04:30
@wusatosi wusatosi force-pushed the toolchain branch 2 times, most recently from a3f18b3 to b000f40 Compare November 15, 2024 04:49
@wusatosi
Copy link
Member Author

This PR proposes to add complexity to the top-level CMakeLists.txt and platform-specific flag selection. What I am proposing involves no changes to the top-level CMakeLists.txt file and no platform-specific flag selection. All complexity is moved to CI and the toolchains it uses. I disagree that my proposal goes against the minimal CMake approach.

Okay I can try to go back to implement toolchain file

@wusatosi
Copy link
Member Author

@camio I implemented this using toolchain files, is this more what you are looking for?

@wusatosi wusatosi requested a review from camio December 12, 2024 00:25
cmake/gnu-toolchain.cmake Show resolved Hide resolved
set(CMAKE_C_COMPILER gcc)
set(CMAKE_CXX_COMPILER g++)

if(BEMAN_BUILDSYS_SANITIZER STREQUAL "ASan")
Copy link
Member

Choose a reason for hiding this comment

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

Modeling sanitizers as a build type works better, either that or an entirely distinct toolchain so Thread and Memory can be uniformly applied to all packages. If everything in an address space aren't using msan or tsan the reports they provide are broken, so you have to rebuild and relink the whole world consistently.
UB sanitizer and address, don't suffer the same problems.

So something like (not tested!):

set(CMAKE_CXX_FLAGS_ASAN
    "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -fsanitize=address,undefined,leak"
    CACHE STRING
    "C++ ASAN Flags"
    FORCE
)

Also at -O0 there's often no undefined behavior emitted for the sanitizer to see, for the same reasons that debug builds seg fault less often.

Copy link
Member

Choose a reason for hiding this comment

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

(optional26 doesn't use the _INIT variables because it's copied from ancient sources before that rule was clarified. Above should be using the *_INIT vars)

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the write-up.

Separate ASAN as build targets seems a bit like overkill for the exemplars use case.

The design goal here isn't to have a full fledged instrumentation based analysis build system but just a quick hand for "enable all flags for sanitizers".

Given there's no dependency for exemplar, and the current recommendation for dependency management is to build with dependency's source code instead of including the dependent library at link time. I don't think there's value in complications here.

Copy link
Member

Choose a reason for hiding this comment

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

Remember, though, exemplar doesn't do anything. It's entire purpose is to serve as a starting point and reference point for further work. Everything we've done is entirely overkill for providing ... checks notes ... std::identity.

Recommending building as part of the dependers source tree is a huge overstatement. We're making that possible, but it's still a terrible idea and does not scale to large systems. Getting to the point where we play well with package systems with public visibility is still on the todo list. (I haven't made it work with my internal one, but I know exactly how to.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah I see where u r coming from, I didn't think about exemplar as a dependency for other libraries (being a dependent) and were only commenting on its use of dependency and a standalone development library / CI test target.

I think you are right, there should be an ASAN target to produce an ASAN enabled library so someone could link us as a dependency to use. I get what you are talking about. But I think this is more of a package/ export issue, outside of scope for this PR for now and to be honest outside of my skill tree for now.

Again again again, the main motivation here is just to simply CI/ workflow.

Honestly I am tentatively waiting for someone to implement package export, do a quick write up, evaluate it and yonk it over (just like code coverage).

Could we delegate this suggestion to another PR? Let me know if I should add something/ structure this tool chain in anticipation of this feature.

Copy link
Contributor

Choose a reason for hiding this comment

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

Modeling sanitizers as a build type works better, either that or an entirely distinct toolchain so Thread and Memory can be uniformly applied to all packages.

How so? This isn't obvious for me.

Copy link
Member

Choose a reason for hiding this comment

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

Tools like IDEs usually treat selecting the build type as a first class UI operation, where setting custom options is much more buried, so if running sanitizers is a build type, it's much easier to run them reliably that way.

The alternative of using a distinct toolchain where e.g. memory sanitizer is part of the default build types makes it easier to apply the same options across many packages. That's particularly useful if you're using a package manager and getting pre-built libraries from them if they're available. Mixing thread or memory sanitizer libraries with ones without produces far too many false positives as the sanitizer can't do the tracking for memory initialization or lock acquisition/release in the libraries that aren't instrumented.

Address and undefined behavior sanitizers don't have that tracing/tracking problem, so they're reasonably accurate if just the code under test is instrumented.

@steve-downey
Copy link
Member

Figuring out better ergonomics for handling sanitizers (and fuzzers, coverage, and the rest of the laundry list) can be ongoing work. Getting sanitizers in CI is an immediate improvement.

I would base the sanitizers on the release or relwithdebinfo profile in CI, as debug tends to not exercise any of the runtime problems that the sanitizers detect.

@@ -0,0 +1,21 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

include_guard(GLOBAL)
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you consider omitting this include guard?

Copy link
Member Author

Choose a reason for hiding this comment

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

I will verify this afterwards.

Copy link
Member

Choose a reason for hiding this comment

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

Cmake will process the toolchain file a random number of times if it isn't guarded. If you are careful and all the string operations are idempotent this is harmless, but it's a common source of bugs, as well as overhead in configuration.

Copy link
Member Author

@wusatosi wusatosi Dec 20, 2024

Choose a reason for hiding this comment

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

@steve-downey I think I've seen what you've described in a previous iteration, where the toolchain gets executed multiple times adding duplicated flags. If we assign string in this style: set(CMAKE_CXX_FLAGS_DEBUG_INIT "${CMAKE_CXX_FLAGS_DEBUG_INIT} ${OTHER_FLAGS}"), we may have trouble with the toolchain getting executed multiple times.

This doesn't happen on this PR as the previous values of XXX_INIT doesn't get carried over.

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like this is not needed. I have omitted include guard for current toolchain file, we can include this in the future if needed.

Copy link
Member

Choose a reason for hiding this comment

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

That it doesn't produce incorrect results this time doesn't mean the template should drop it. It still adds overhead on reconfigure and can produce confusing bugs if it's not idempotent.
Begin as you mean to proceed in the example we are providing as the basis for a project.

set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)

if(BEMAN_BUILDSYS_SANITIZER STREQUAL "ASan")
Copy link
Contributor

Choose a reason for hiding this comment

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

ASan already has a well-known meaning in the community: address sanitizer. Using the same word to mean "all sanitizers" is confusing.

What about "MaxSan" (maximum sanitizers) which would mean getting as many sanitizers enabled as possible.

Copy link
Member Author

Choose a reason for hiding this comment

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

👌 good suggestion

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 my fault, to the extent this is coming from optional26. Address composes with many others, and I didn't try to find a better name for the collection. MaxSan is at least not wrong, and I don't have a better idea.

Copy link
Member Author

Choose a reason for hiding this comment

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

Adopted!

Comment on lines 9 to 14
set(CMAKE_CXX_FLAGS_DEBUG_INIT
"-fsanitize=address -fsanitize=leak -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined"
)
set(CMAKE_C_FLAGS_DEBUG_INIT
"-fsanitize=address -fsanitize=leak -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this not also apply to release builds?

Copy link
Member Author

@wusatosi wusatosi Dec 20, 2024

Choose a reason for hiding this comment

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

Addressed, nice catch

Comment on lines 9 to 10
set(CMAKE_CXX_FLAGS_DEBUG_INIT "/fsanitize=address /Zi")
set(CMAKE_C_FLAGS_DEBUG_INIT "/fsanitize=address /Zi")
Copy link
Contributor

Choose a reason for hiding this comment

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

Is /Zi is related to ASan? Should it be enabled in all build modes?

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.

Do I need to include documentation for this?

Omitting this flag will cause warning C5072.

MSVC side documentation:
https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-c5072?view=msvc-170

Copy link
Member

Choose a reason for hiding this comment

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

If they fix it, we could remove the flag? "Why" comments are useful.

cmake/llvm-toolchain.cmake Show resolved Hide resolved
@wusatosi wusatosi requested a review from camio December 25, 2024 02:27
@wusatosi
Copy link
Member Author

@camio this PR is ready for another round of review.

@wusatosi wusatosi changed the title Specify sanitizer parameters in CMake Introduce CMake toolchain Dec 25, 2024
@wusatosi wusatosi requested a review from steve-downey January 6, 2025 18:51
Copy link
Contributor

@camio camio left a comment

Choose a reason for hiding this comment

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

I left a couple minor naming suggestions, but otherwise this looks ready to ship.

set(CMAKE_C_FLAGS_DEBUG_INIT "${SANITIZER_FLAGS}")
set(CMAKE_CXX_FLAGS_DEBUG_INIT "${SANITIZER_FLAGS}")

set(RELEASE_FLAG "-O3 ${SANITIZER_FLAGS}")
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this variable should be prefixed with BEMAN_BUILDSYS_ to avoid conflicts with project variables.

set(CMAKE_C_FLAGS_DEBUG_INIT "${SANITIZER_FLAGS}")
set(CMAKE_CXX_FLAGS_DEBUG_INIT "${SANITIZER_FLAGS}")

set(RELEASE_FLAG "-O3 ${SANITIZER_FLAGS}")
Copy link
Contributor

Choose a reason for hiding this comment

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

FLAG -> FLAGS to match the existing CMake conventions.

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.

4 participants