CheckLanguage does not take the C++ standard into account

Hi,

I’ve run into an issue recently that I think CMake could be helping with in a better way. :thinking: In one of our projects we tend to make use of the very latest version of certain compilers at the same time. Which does not always work. For instance right now one cannot use CUDA 12.6 with oneAPI 2024.2.1 as the host compiler.

/home/krasznaa/software/nvidia/cuda-12.6.0/x86_64/bin/nvcc
-forward-unknown-to-host-compiler
-ccbin=/home/krasznaa/software/intel/oneapi-2024.2.1/compiler/2024.2/bin/compiler/clang++
-allow-unsupported-compiler -std=c++20 -c main.cu -o main.cu.o
/usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/type_traits(3433):
error: type name is not allowed
       : bool_constant<__is_layout_compatible(_Tp, _Up)>
                                              ^

/usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/type_traits(3433):
error: type name is not allowed
       : bool_constant<__is_layout_compatible(_Tp, _Up)>
                                                   ^

/usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/type_traits(3433):
error: identifier "__is_layout_compatible" is undefined
       : bool_constant<__is_layout_compatible(_Tp, _Up)>
                       ^

/usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/type_traits(3440):
error: type name is not allowed
         = __is_layout_compatible(_Tp, _Up);
                                  ^

/usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/type_traits(3440):
error: type name is not allowed
         = __is_layout_compatible(_Tp, _Up);
                                       ^

5 errors detected in the compilation of "main.cu".

That part has nothing to do with CMake, the “interesting” part is just that this compiler combination happens to not have issues when using C++17. But it does with C++20. So now, when I write the following CMake code:

cmake_minimum_required(VERSION 3.26)
project(CheckLanguageCUDA VERSION 0.0.1 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20 CACHE STRING "Host compiler C++ version")
set(CMAKE_CUDA_STANDARD 20 CACHE STRING "CUDA compiler C++ version")

include(CheckLanguage)
check_language(CUDA)

if(CMAKE_CUDA_COMPILER)
   enable_language(CUDA)
endif()

, I get the following output:

-- The CXX compiler identification is IntelLLVM 2024.2.1
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /home/krasznaa/software/intel/oneapi-2024.2.1/compiler/2024.2/bin/icpx - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Looking for a CUDA compiler
-- Looking for a CUDA compiler - /home/krasznaa/software/nvidia/cuda-12.6.0/x86_64/bin/nvcc
-- Looking for a CUDA host compiler - /home/krasznaa/software/intel/oneapi-2024.2.1/compiler/2024.2/bin/icpx
-- The CUDA compiler identification is NVIDIA 12.6.20
-- Detecting CUDA compiler ABI info
-- Detecting CUDA compiler ABI info - failed
-- Check for working CUDA compiler: /home/krasznaa/software/nvidia/cuda-12.6.0/x86_64/bin/nvcc
-- Check for working CUDA compiler: /home/krasznaa/software/nvidia/cuda-12.6.0/x86_64/bin/nvcc - broken
CMake Error at /home/krasznaa/software/kitware/cmake-3.30.2/x86_64-ubuntu2204-gcc11-opt/share/cmake-3.30/Modules/CMakeTestCUDACompiler.cmake:59 (message):
  The CUDA compiler

    "/home/krasznaa/software/nvidia/cuda-12.6.0/x86_64/bin/nvcc"

  is not able to compile a simple test program.

  It fails with the following output:

    Change Dir: '/home/krasznaa/development/cmake/build/CMakeFiles/CMakeScratch/TryCompile-ym6qLI'
    
    Run Build Command(s): /home/krasznaa/software/kitware/cmake-3.30.2/x86_64-ubuntu2204-gcc11-opt/bin/cmake -E env VERBOSE=1 /usr/bin/gmake -f Makefile cmTC_fac61/fast
    /usr/bin/gmake  -f CMakeFiles/cmTC_fac61.dir/build.make CMakeFiles/cmTC_fac61.dir/build
    gmake[1]: Entering directory '/home/krasznaa/development/cmake/build/CMakeFiles/CMakeScratch/TryCompile-ym6qLI'
    Building CUDA object CMakeFiles/cmTC_fac61.dir/main.cu.o
    /home/krasznaa/software/nvidia/cuda-12.6.0/x86_64/bin/nvcc -forward-unknown-to-host-compiler -ccbin=/home/krasznaa/software/intel/oneapi-2024.2.1/compiler/2024.2/bin/icpx   -allow-unsupported-compiler  -std=c++20 "--generate-code=arch=compute_52,code=[compute_52,sm_52]" -MD -MT CMakeFiles/cmTC_fac61.dir/main.cu.o -MF CMakeFiles/cmTC_fac61.dir/main.cu.o.d -x cu -c /home/krasznaa/development/cmake/build/CMakeFiles/CMakeScratch/TryCompile-ym6qLI/main.cu -o CMakeFiles/cmTC_fac61.dir/main.cu.o
    /usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/type_traits(3433): error: type name is not allowed
          : bool_constant<__is_layout_compatible(_Tp, _Up)>
                                                 ^
    
    /usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/type_traits(3433): error: type name is not allowed
          : bool_constant<__is_layout_compatible(_Tp, _Up)>
                                                      ^
    
    /usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/type_traits(3433): error: identifier "__is_layout_compatible" is undefined
          : bool_constant<__is_layout_compatible(_Tp, _Up)>
                          ^
    
    /usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/type_traits(3440): error: type name is not allowed
            = __is_layout_compatible(_Tp, _Up);
                                     ^
    
    /usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/type_traits(3440): error: type name is not allowed
            = __is_layout_compatible(_Tp, _Up);
                                          ^
    
    5 errors detected in the compilation of "/home/krasznaa/development/cmake/build/CMakeFiles/CMakeScratch/TryCompile-ym6qLI/main.cu".
    gmake[1]: *** [CMakeFiles/cmTC_fac61.dir/build.make:79: CMakeFiles/cmTC_fac61.dir/main.cu.o] Error 2
    gmake[1]: Leaving directory '/home/krasznaa/development/cmake/build/CMakeFiles/CMakeScratch/TryCompile-ym6qLI'
    gmake: *** [Makefile:127: cmTC_fac61/fast] Error 2
    
    

  

  CMake will not be able to correctly generate this project.
Call Stack (most recent call first):
  CMakeLists.txt:12 (enable_language)


-- Configuring incomplete, errors occurred!

This is because CheckLanguage doesn’t propagate (among other things) the CMAKE_<LANG>_STANDARD variables to the test project that it sets up. Which can result in an error like this.

Now… the setup that I’m describing here is broken. So it’s not the end of the world that CMake doesn’t handle it quite correctly at the moment. But still, I wonder if check_language(...) could potentially be taught to work correctly in such situations as well. :thinking:

Cheers,
Attila

Does patching your CheckLanguage.cmake work with this change?

diff --git a/Modules/CheckLanguage.cmake b/Modules/CheckLanguage.cmake
index 35ad0368d6..82c736eba3 100644
--- a/Modules/CheckLanguage.cmake
+++ b/Modules/CheckLanguage.cmake
@@ -115,6 +115,11 @@ file(WRITE \"\${CMAKE_CURRENT_BINARY_DIR}/result.cmake\"
     else()
       set(_D_CMAKE_LANG_PLATFORM "")
     endif()
+    if(CMAKE_${lang}_STANDARD)
+      set(_D_CMAKE_LANG_STANDARD "-DCMAKE_${lang}_STANDARD:STRING=${CMAKE_${lang}_STANDARD}")
+    else()
+      set(_D_CMAKE_LANG_STANDARD "")
+    endif()
     execute_process(
       WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/Check${lang}
       COMMAND ${CMAKE_COMMAND} . -G ${CMAKE_GENERATOR}
@@ -124,6 +129,7 @@ file(WRITE \"\${CMAKE_CURRENT_BINARY_DIR}/result.cmake\"
                                  ${_D_CMAKE_MAKE_PROGRAM}
                                  ${_D_CMAKE_TOOLCHAIN_FILE}
                                  ${_D_CMAKE_LANG_PLATFORM}
+                                 ${_D_CMAKE_LANG_STANDARD}
       OUTPUT_VARIABLE _cl_output
       ERROR_VARIABLE _cl_output
       RESULT_VARIABLE _cl_result

you can copy CheckLanguage.cmake locally and patch as above to try it. E.g. in your CMakeLists.txt

include(${CMAKE_CURRENT_SOURCE_DIR}/CheckLanguage.cmake)

That change does indeed achieve the behaviour that I’d like. :+1:

-- The CXX compiler identification is IntelLLVM 2024.2.1
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /home/krasznaa/software/intel/oneapi-2024.2.1/compiler/2024.2/bin/icpx - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Looking for a CUDA compiler
-- Looking for a CUDA compiler - NOTFOUND
-- Configuring done (2.8s)
-- Generating done (0.0s)
-- Build files have been written to: /home/krasznaa/Development/build

Policy CMP0067 controls this aspect. When it is set to NEW (which it would be in your example project), the underlying try_compile() call that check_language() uses should be propagating the language standard variables to the test sub-build.

I wonder if in your experiments you are not resetting the CMAKE_CXX_STANDARD and CMAKE_CUDA_STANDARD variables. These wouldn’t normally be made cache variables like in your example. Can you repeat your tests, but change those two lines to the following (change the value between 20 and 17 as per your previous testing):

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CUDA_STANDARD 20)

The results should be the same either way, but if you make them cache variables, you have to remember to force their value when you re-test with a different value to the first run.

1 Like

Hi Craig,

No, the variable type is not an important aspect here. I tried, but using “simple variables” in my example code doesn’t change anything with how check_language(...) behaves. And in any case, I was configuring my build from scratch each time for this example, so the build directory remembering the value of the cache variables was not a concern.

As @scivision showed, CheckLanguage.cmake doesn’t actually use try_compile(...). It uses execute_process(...) to set up a whole other cmake project under the hood. So CMP0067 unfortunately doesn’t help here. :slightly_frowning_face:

Best,
Attila

Ah, right, this one is different. Taking a step back, CheckLanguage only tries to answer the question “Can I enable this language at all?”. You are wanting to ask a different question, one that is quite complex because you’re actually trying to check combinations of things.

What I would recommend is enable your languages before you set any language standards at all. That should succeed. Then you can use one of the try_compile()-based commands to check if you will be able to use a particular language standard for the combination of compilers that are enabled. If you end up working out that you do have CUDA, but not a toolchain you can actually use, then use some custom project-specific variable to indicate that and check that throughout your project where you need to provide different behavior for whether you have a suitable CUDA toolchain or not.

The line here is really fuzzy though, isn’t it?

CheckLanguage.cmake does forward a bunch of info from the main project down to the test project already. Even things like CMAKE_HIP_PLATFORM. So I’m not so sure that also forwarding CMAKE_HIP_STANDARD (or in my case CMAKE_CUDA_STANDARD) would be that fundamentally different. :thinking:

But as I started, this is not a hill that I want to die on. I just wanted to raise the issue for consideration.

Best,
Attila

https://gitlab.kitware.com/cmake/cmake/-/merge_requests/9739
attempts to implement this change.

Edit: the MR was closed, with Brad King noting more discussion is needed.

Thinking about this more, I also think like Craig above that try_compile() is a more appropriate solution. I have several projects that combine C++ and Fortran functions calling each other, which can require specific compiler flags for older compilers. I use try_compile() to detect problems successfully.

Those are about making sure the check project uses the same toolchain and target platform as enable_language would in the calling project.