Modern CMake: The big leap

Modern cmake has come a long way: 20+ years ago, CMake was “a way to use global variables to bludgeon your way to a compilation happening”.

Today, we use interface libraries to marshal things and build more coherent descriptions.

Except, we don’t: the expression in quotes above is from chat gpt just now.

People’s perception of CMake is still based on massive quantities of globals and spaghetti code.

It seems to me that there are two big components to the final transition from “CMake 1” to “CMake 4” (if you will)

  1. INTERFACE library: punctum obscurum sed momenti

I think the entire trajectory of cmake pivoted when you introduced INTERFACE libraries, but of itself that also reflects the very worst of early cmake.

It’s time for interface libraries to mature into a formal basis for both libraries and targets and to thus become a key part to learning cmake.

Nobody thinks “Hmm, I wonder if cmake implements INTERFACE libraries”, but “a way to do namespaces?” or "scope"s. Scope is already a thing, and namespace is too overloaded.

“target” almost seems obvious, but for the purpose of this post I’m going to run with “association”.

cmake_minimum_required (VERSION 4.0)
project (ModernCMake LANGUAGES C CXX)

# Create a namespace target to which properties, variables, dependencies, everything can
# be attached.
add_association (modern-cmake CONFLICTS ancient-cmake)
set_target_properties (modern-cmake PROPERTIES VERSION "4.0")
if (CMAKE_COMPILER_IDENTITY "angry-cpp")
  target_compile_options(modern-cmake "-no-foul-language")
endif ()

add_executable (my-executable my.cpp WITH modern-cmake)
  1. Fully embrace namespacing (assocation) in variable naming

Another barrier to people using target properties instead of global variables is ease-of-access.

Correctly:

set_target_properties (SomeTarget PROPERTIES USE_JSON ON)
...
get_property (use_json TARGET OtherTarget PROPERTY USE_JSON)
if (NOT use_json_NOT_FOUND)
  ...
endif ()
unset (use_json)
unset (use_json_NOT_FOUND)

This doubles as weight against people thinking this is a valid approach and pushing them back to globals.

Instead, there needs to be a way to reference “variables associated with a target”, i.e name scoping.

Can’t use ‘:’ because of generator expressions, can’t use ‘#’ because of comments, and I’m not sure if ‘.’ might not be a problem since I feel I’ve seen it in some (ill considered) variable names. So, maybe ‘/’:

set_target_properties (SomeTarget PROPERTIES USE_JSON "rapid")
...
if (OtherTarget/USE_JSON)
  message (STATUS "Using Json: ${OtherTarget/USE_JSON}")
endif ()

There are other places this would be natural to reason into being:

add_association (LibraryFeatures)
option (LibraryFeatures/USE_JSON "Build Library with Json capabilities" ON)
cmake_dependent_option (LibraryFeatures/USE_JSON_SETTINGS "Use json for settings files" ON "LibraryFeatures/USE_JSON" OFF)
  1. Down with globals, ish.

“4.0” above was tongue in cheek, but maybe not. Perhaps this would be a good point to use policies so that a project requiring >= 4.0 turns on warnings about using “set” to set global variables (while still allowing it until 4.x), unless it has descended into a subdirectory that lowered the requirements.

block (scope_for variables)
  set (SOME_GLOBAL_NAME ON)  # << fine, it's scoped
endblock ()

set_global (SOME_GLOBAL_NAME ON)  # << fine, you're saying it's categorically global

# Transition aide and diagnostic tool
protect_global (SOME_GLOBAL_NAME_ OFF)  # << tell cmake this variable is no-longer mutable
set (SOME_GLOBAL_NAME OFF)  # << error, it's protected
set_global (SOME_GLOBAL_NAME OFF)  # << ditto
# /CMakeLists.txt
cmake_minimum_required(VERSION 4.0)
...
set (A_GLOBAL TRUE)   # << warning, use a target variable/property or set_global

set_global (A_GLOBAL TRUE)   # << no warning

subdir (older)
# /older/CMakeLists.txt
cmake_minimum_required (VERSION 3.21)
set (A_GLOBAL OFF)  # << no warning unless user enables an associated policy.
1 Like