How to test for CXX module availability

I’ve been trying to find some way to detect if the current generators and/or compiler supports c++ modules. Right now, I just have a giant set of "if"s, but given that cmake will bark at me that Visual Studio 2019 generator doesn’t support modules, it seems like I ought to be an if for that?

Looking to start transitioning to using modules for a mid-large (~2m 1st loc, 20k cmake 1st, ~6.8m 3rd loc, ~45k cmake) codebase that is routinely constrained by portability across 10+ platforms; I have a working solution but it’s definitely not something I would put into long-term production. 2am-pager-duty me would disapprove.

I have a just running example here GitHub - ClausKlein/cmake-init-modules: C++20 Modules, CMake, And Shared Libraries

The main restrictions are: you must use the most resent tools and the Ninja generator!

# This property setting also needs to be consistent between the
# installed shared library and its consumer, otherwise most
# toolchains will once again reject the consumer's generated BMI.
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if(CMAKE_GENERATOR STREQUAL "Ninja")
  if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 20.0
  )
    set(ALGO_USE_MODULES TRUE)
    string(APPEND CMAKE_CXX_MODULE_MAP_FLAG " -fmodules-reduced-bmi")

    # see https://releases.llvm.org/20.0.0/projects/libcxx/docs/ReleaseNotes.html
    # Always use libc++
    if(APPLE)
      execute_process(OUTPUT_VARIABLE LLVM_PREFIX COMMAND brew --prefix llvm@20 COMMAND_ECHO STDOUT)
      string(STRIP ${LLVM_PREFIX} LLVM_PREFIX)
      file(REAL_PATH ${LLVM_PREFIX} LLVM_ROOT)
      set(LLVM_ROOT ${LLVM_ROOT} CACHE PATH "")

      message(STATUS "LLVM_ROOT=${LLVM_ROOT}")
      add_link_options(-L${LLVM_ROOT}/lib/c++)
      include_directories(SYSTEM ${LLVM_ROOT}/include)
    endif()

    add_compile_options(-stdlib=libc++)
    add_link_options(-stdlib=libc++)
  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15.0
  )
    set(ALGO_USE_MODULES TRUE)
  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    set(ALGO_USE_MODULES TRUE)
  endif()
endif()

# Tell CMake that we explicitly want `import std`.
# This will initialize the property on all targets declared after this to 1
message(STATUS "CMAKE_CXX_COMPILER_IMPORT_STD=${CMAKE_CXX_COMPILER_IMPORT_STD}")
if(23 IN_LIST CMAKE_CXX_COMPILER_IMPORT_STD)
  set(CMAKE_CXX_MODULE_STD ON)
  message(STATUS "CMAKE_CXX_MODULE_STD=${CMAKE_CXX_MODULE_STD}")
endif()

message(STATUS "ALGO_USE_MODULES=${ALGO_USE_MODULES}")
message(STATUS "CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES=${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}")
1 Like

I have something similar but since we have to target variations of Linux, Android, Windows, MacOS, iOS, tvOS, watchOS, Xbox, Playstation, Switch, and some others, multiple architectures and simulators, it’s just a couple hundred lines longer :sad_but_relieved_face: So for the moment, instead, I’m using this stub just to experiment with our codebase and shelved the original.

But CMake clearly knows that Visual Studio 16 2019 generator can’t do it:

if (CMAKE_CXX_STANDARD GREATER_EQUAL 20 AND MSVC_VERSION GREATER_EQUAL 1930)
	set (NUO_CXX_MODULES_AVAILABLE YES)
endif ()
if (NUO_CXX_MODULES_AVAILABLE)
	message (STATUS "-- Optional C++ modules available")
endif ()

function (NUO_ENABLE_OPTIONAL_CXX_MODULES)
    # args_MODULE_FILES is set to indicate that this is a module provider,
    # vs a consumer.
	# e.g
	#  NUO_ENABLE_OPTIONAL_CXX_MODULES(TARGET NuoProcess EXPORTS YES IMPORTS NO)
	# Use "MODULE_FILES" to specify which .ixx files to use as module definitions.
    cmake_parse_arguments (
        args
        ""
        "TARGET;EXPORTS;IMPORTS"
        "MODULE_FILES"
        ${ARGN}
    )
    # TODO: Make messages 'VERBOSE' when we start to use this more commonly
    set (MESSAGE_LEVEL STATUS)

    # check for unused arguments
    if (args_UNPARSED_ARGUMENTS)
        message (FATAL_ERROR "NUO_ENABLE_CXX_MODULES: Unrecognized arguments: ${args_UNPARSED_ARGUMENTS}")
    endif ()
    if (NOT args_TARGET)
        message (FATAL_ERROR "NUO_ENABLE_CXX_MODULES: Missing required argument: TARGET")
    endif ()

	if (NOT NUO_CXX_MODULES_AVAILABLE)
		message (${MESSAGE_LEVEL} "-- Non-module flavor of ${args_TARGET}")
		return ()
	endif ()

	if (args_EXPORTS AND NOT args_MODULE_FILES)
		set (args_MODULE_FILES "${CMAKE_CURRENT_LIST_DIR}/${args_TARGET}.ixx")
		if (NOT EXISTS ${args_MODULE_FILES})
			message (FATAL_ERROR "NUO_ENABLE_CXX_MODULES: Default Module file not found: ${args_MODULE_FILES}")
		endif ()
	endif ()
	if (args_MODULE_FILES AND NOT args_EXPORTS)
		message (FATAL_ERROR "NUO_ENABLE_CXX_MODULES: Module files specified without EXPORTS YES")
	endif ()

	message (${MESSAGE_LEVEL} "-- Enabling C++ modules for target ${args_TARGET}: IMPORTS=${args_IMPORTS}, EXPORTS=${args_EXPORTS}")

	# Tell the code, with a #define.
	if (args_EXPORTS)
		target_compile_definitions (${args_TARGET} PRIVATE -DNUO_CXX_MODULES_ENABLED=1)
	endif ()
	if (args_IMPORTS)
		target_compile_definitions (${args_TARGET} PRIVATE -DNUO_CXX_IMPORTS_ENABLED=1)
	endif ()

	# Allow the module to use other modules.
	if (args_IMPORTS)
		set_target_properties (${args_TARGET} PROPERTIES CMAKE_CXX_SCAN_FOR_MODULES ON)
	endif ()

	# If this is an module provider, add the module definitions as sources.
	if (args_MODULE_FILES)
		target_sources (
			${args_TARGET}
			PUBLIC
				FILE_SET cxx_modules TYPE CXX_MODULES
				FILES ${args_MODULE_FILES}
		)
	endif ()
endfunction()

BTW, isn’t “CMAKE_CXX_MODULE_STD” supposed to be “1”? Or is that just for CMake 4? As in, it’s an integer revision number for the module standard that cmake itself is implementing?

AFAIK it is a simple CMake option (bool) → 1, ON TRUE, not empty value?

  # Needs C++20, but doesn't use modules
  add_executable(AnotherApp main.cpp)
  target_compile_features(AnotherApp PRIVATE cxx_std_20)
  set_target_properties(AnotherApp PROPERTIES
      CXX_SCAN_FOR_MODULES FALSE
  )

see https://cmake.org/cmake/help/latest/manual/cmake-cxxmodules.7.html#generator-support