This repository adheres to established code style practices within the CMake ecosystem.
- 1. Introduction
- 2. General guidelines
- 3. Variables
- 4. Modules
- 5. Booleans
- 6. Functions and macros
- 7. Targets
- 8. Properties
- 9. Determining platform
- 10. See also
CMake is quite lenient regarding code style, but applying consistency for writing CMake files can enhance both code quality and comprehension of the build system, especially when multiple developers are involved. Following some naming conventions can maintain a clear and organized CMake project structure while avoiding conflicts with external libraries and CMake scope.
For instance, it's important to note that CMake functions, macros, and commands are not case-sensitive. In other words, the following two expressions are equivalent:
add_library(foo src.c)
ADD_LIBRARY(foo src.c)
On the contrary, variable names are case-sensitive.
-
In most cases, the preferred style is to use all lowercase letters.
add_library(foo src.c) function(bar argument) if(argument) set(var "value") endif() # ... endfunction() target_include_directories(...)
-
During development check that variables are properly initialized and used to avoid unexpected behavior and errors in the build process:
cmake --warn-uninitialized -S <source-directory> -B <build-directory>
-
Long strings can be split into multiple lines by using line continuation with a backslash character (
\
) followed by a new line:message(STATUS "\ This string is concatenated \ to a single line.\ ") # Output: This string is concatenated to a single line.
-
When defining path variables, exclude the trailing directory delimiter (
/
). This practice facilitates concatenation of such variables:set(parentDir "foo/bar") set(childDir "${parentDir}/baz")
Tip
Code strings or regular expressions, can alternatively be passed as bracket
arguments ([[
, ]]
, [=[
, ]=]
, [==[
, ]==]
, etc), helping to avoid
the need for escaping characters:
install(CODE [[
execute_process(
COMMAND ${CMAKE_COMMAND} -E echo "${variable} references aren't evaluated"
)
set(version "1.2")
if(version MATCHES [=[^[0-9]\.[0-9]$]=])
message(STATUS "Nested bracket argument with varying '=' characters")
endif()
]])
To make the code easier to read, use empty commands for endif()
,
endfunction()
, endforeach()
, endmacro()
, endwhile()
, else()
, and
similar end commands. The optional argument in end command is legacy CMake and
not recommended anymore.
For example, do this:
if(foo)
# ...
else()
# ...
endif()
and not this:
if(foo)
# ...
else(foo)
# ...
endif(foo)
The variable CMAKE_SOURCE_DIR
represents the top level project source code
directory, housing C and CMake files. Conversely, CMAKE_BINARY_DIR
signifies
the binary (also called build) directory where built artifacts are output.
cmake -S <source-directory> -B <binary-directory>
For enhanced project portability, it is recommended to use PROJECT_SOURCE_DIR
and PROJECT_BINARY_DIR
, or <ProjectName>_SOURCE_DIR
and
<ProjectName>_BINARY_DIR
, over CMAKE_SOURCE_DIR
and CMAKE_BINARY_DIR
.
For example, instead of:
set(somePath ${CMAKE_SOURCE_DIR}/main/php_config.h)
use:
set(somePath ${PROJECT_SOURCE_DIR}/file.h)
# and
set(somePath ${PHP_SOURCE_DIR}/main/php_config.h)
These variables succinctly define the root directories of the project, ensuring
consistency and ease of integration when employed in CMake files. In case of a
single CMake project()
usage, there isn't any difference between CMAKE_*_DIR
or PROJECT_*_DIR
. However, when multiple project()
invocations occur, and
project directories are added via add_subdirectory()
or external inclusions,
these variables become distinct.
CMAKE_SOURCE_DIR
andCMAKE_BINARY_DIR
: Denote the project source and build directories from the firstproject()
call in the rootCMakeLists.txt
.PROJECT_SOURCE_DIR
andPROJECT_BINARY_DIR
: Denote the project source and build directories from the most recentproject()
call.<ProjectName>_SOURCE_DIR
and<ProjectName>_BINARY_DIR
represent the project source and build directories from the most recentproject(ProjectName ...)
call.
Use check_compiler_flag()
, check_source_compiles()
, check_source_runs()
instead of language-specific check_<LANG>_...()
.
CMake variables can be categorized based on their scope, which helps organize and manage them effectively within the project.
Variables with a scope inside functions and blocks. These should preferably be in camelCase.
function(foo)
set(variableName <value>)
# ...
endfunction()
The block()
command can be used to restrict the variable scope to a specific
block of code:
block()
set(bar <value>)
# ...
endblock()
Variable bar
in the above example is uninitialized beyond the block's scope.
Directory variables are those confined to the current CMakeLists.txt
and its
child directories. To distinguish them, these variables should be in
UPPER_CASE.
set(VAR <value>)
This naming convention helps identify the variables that pertain to the current directory and its descendants.
Cache variables are stored and persist across the entire build system. They should be UPPER_CASE.
# Cache variable
set(VAR <value> CACHE <type> "<help_text>")
# Cache variable as a boolean option
option(FOO "<help_text>" [value])
# Cache variables created by CMake command invocations. For example
find_program(SED_EXECUTABLE sed)
When naming variables, it is considered good practice to restrict their names to alphanumeric characters and underscores, enhancing readability.
Variables prefixed with CMAKE_
, _CMAKE_
, and _<any-cmake-command-name>
are
reserved for CMake's internal use.
Configuration variables are cache variables designed to be adjusted by the user
during the configuration phase, either through the presets, command line, or by
using GUI, such as cmake-gui or ccmake. It is recommended to prefix them with
PHP_
, and similar to facilitate their grouping within the GUI or IDE.
option(PHP_ENABLE_FOO "<help_text>" [value])
cmake_dependent_option(PHP_ENABLE_BAR "<help_text>" <value> <depends> <force>)
set(PHP_FOO_BAR <value>... CACHE <BOOL|FILEPATH|PATH|STRING> "<help_text>")
# Zend Engine configuration variables
option(ZEND_ENABLE_FOO "<help_text>" [value])
# Configuration variables related to PHP extensions
option(PHP_EXT_FOO "<help_text>" [value])
# Configuration variables related to PHP SAPI modules
option(PHP_SAPI_FOO "<help_text>" [value])
Find module variables are established and confined to the directory scope when
employing the find_package(PackageName)
command. These variables are
structured as <PackageName>_UPPER_CASE
, with PackageName
capable of being in
any case.
It's customary to prefix temporary variables that are intended for use within a
specific code block with an underscore (_
). This naming convention indicates
that these variables are meant exclusively for internal use within the current
CMake file and should not be accessed outside of that context.
set(_temporaryVariable <value>)
Tip
Variables named _
can be used for values that are not important for code.
For example, here only the matched value of variable CMAKE_MATCH_1
is
important:
string(REGEX MATCH "foo\\(([0-9]+)\\)" _ "${content}")
message(STATUS "${CMAKE_MATCH_1}")
In CMake, it's common practice to reset local variables within a specific scope to avoid unintended use of previous values. When ensuring a variable is empty before use, explicitly set it to an empty string:
set(someVariable "")
Avoid this approach:
set(someVariable)
The latter is equivalent to unset(someVariable)
, which can unintentionally
expose a cache variable with the same name if it exists. For example:
set(someVariable "Foo" CACHE INTERNAL "Some cache variable")
# ...
set(someVariable)
message(STATUS "${someVariable}")
# Outputs: Foo
Setting the variable to an empty string ensures it is safely initialized without interference from cache variables.
CMake modules are located in the cmake/modules
directory.
Find modules in this repository follow standard CMake naming conventions for
find modules. For example, find module Find<PackageName>.cmake
can be loaded
by:
find_package(PackageName)
It sets variable <PackageName>_FOUND
and other optional variables, such as
<PackageName>_VERSION
, <PackageName>_INCLUDE_DIRS
, which are managed by
CMake's FindPackageHandleStandardArgs
. Recommendation for find modules is that
they should expose imported targets, such as PackageName::PackageName
which
can be then linked to a target in the project:
find_package(PackageName)
target_link_libraries(php PRIVATE PackageName::PackageName)
PackageName
can be in any case (a-zA-Z0-9), with PascalCase or package
original name case preferred.
Utility modules typically adhere to the PascalCase.cmake
pattern. They are
prefixed with PHP
by residing in the PHP directory (cmake/modules/PHP
) and
can be included like this:
include(PHP/PascalCase)
This approach is adopted for convenience to prevent any potential conflicts with upstream CMake modules.
Tip
When CMakeLists.txt
becomes too complex for all-in-one configuration file,
some PHP extensions, SAPIs and Zend Engine include configure checks from local
modules located in their cmake
subdirectories for simplicity.
CMake interprets 1
, ON
, YES
, TRUE
, and Y
as representing boolean true
values, while 0
, OFF
, NO
, FALSE
, N
, IGNORE
, NOTFOUND
, an empty
string, or any value ending with the suffix -NOTFOUND
are considered boolean
false values. Named boolean constants are case-insensitive (e.g., on
, Off
,
True
).
A general convention is to use ON
and OFF
for boolean values that can be
modified by the user, and TRUE
and FALSE
for intrinsic values that cannot or
should not be modified externally. For example:
# Boolean variables that can be modified by the user use ON/OFF values
option(FOO "<help_text>" ON)
# The IMPORTED property is set to TRUE and cannot be modified after being set
add_library(foo UNKNOWN IMPORTED)
get_target_property(value foo IMPORTED)
message(STATUS "value=${value}")
# Outputs: value=TRUE
# Similarly, intrinsic values in the code use TRUE/FALSE
set(HAVE_FOO TRUE)
Functions are generally favored over macros due to their ability to establish their own variable scope, unlike macros where variables remain visible in the outer scope. Macros are primarily used in specific cases where setting variables within the current scope of CMake code is required.
CMake function and macro names possess global scope, so it is recommended to
prefix them contextually, for example php_
. It is preferred to adhere to the
snake_case style.
function(php_function_name argumentName)
# Function body
endfunction()
macro(php_macro_name argumentName)
# Macro body
endmacro()
Similarly, like variables, functions and macros exclusively used within a single
CMake module or CMakeLists.txt
file should be prefixed with an underscore
(_
). This prefix serves as a signal to external code to refrain from using
them.
function(_php_internal_function_name)
# Function body
endfunction()
CMake targets are defined with add_library()
, add_executable()
, and
add_custom_target()
. Target naming conventions in this repository are intended
to prevent clashes with existing system library names, especially when dealing
with libraries imported with find_package()
command or FetchContent
module.
Naming pattern when creating libraries and executables across the build system:
-
php_ext_<extension_name>
Targets associated with PHP extensions. Replace
<extension_name>
with the name of the PHP extension. -
php_sapi_<sapi_name>
Targets associated with PHP SAPIs (PHP Server APIs). Replace
<sapi_name>
with the name of the PHP SAPI. -
php_main
Target name of the PHP main binding.
-
zend
andzend_*
:Targets associated with the Zend Engine.
Additionally, customizing the target output file name on the disk can be done by
setting target property OUTPUT_NAME
.
add_executable(php_sapi_<sapi_name> ...)
set_target_properties(php_sapi_<sapi_name> PROPERTIES OUTPUT_NAME php)
To make it easier to work with targets across the build system, it is recommended to use aliases as linkable targets:
# Creating a library
add_library(php_<target_name> ...)
# Creating an alias target for a library
add_library(PHP::<component_name> ALIAS php_<target_name>)
# Linking target using the alias
target_link_library(php_some_target PRIVATE PHP::<component_name>)
Using alias targets can have a performance and distinct benefit because whenever
CMake sees a double colon (::
) in the target name, it will limit the search to
CMake targets only, unlike other naming patterns where CMake will search for
link flags, paths, or library names as well.
Tip
PHP extensions and SAPIs use nested namespaces for their distinct convenience. However, CMake does not differentiate these from single-level namespaces.
# PHP extensions:
add_library(php_ext_bcmath)
add_library(PHP::ext::bcmath ALIAS php_ext_bcmath)
PHP SAPIs:
add_executable(php_sapi_cli)
add_executable(PHP::sapi::cli ALIAS php_sapi_cli)
Custom targets should be defined with clear names that indicate their purpose,
such as php_generate_something
. These targets can be customized to perform
specific actions during the build process. They should be prefixed with the
target context. For example, php_
, php_ext_<extension_name>_
,
php_sapi_<sapi_name>_
, zend_
, or similar.
add_custom_target(php_generate_something ...)
In this repository, CMake custom properties follow the UPPER_CASE naming
convention and are consistently prefixed with a context-specific identifier,
such as PHP_
.
define_property(<scope> PROPERTY PHP_CUSTOM_PROPERTY_NAME [...])
CMake provides variables such as APPLE
, LINUX
, UNIX
, WIN32
, etc, for the
target systems, and CMAKE_HOST_APPLE
, CMAKE_HOST_LINUX
, etc, for the host
systems. However, they might be removed in the future CMake versions and they
can be also ambiguous in certain cases. Better practice is to be specific and
use:
CMAKE_SYSTEM_NAME
in code orPLATFORM_ID
in generator expressions to check the target platform (which is also the name used during cross-compilation).- And the
CMAKE_HOST_SYSTEM_NAME
to identify the platform where CMake is performing the build.
When building on the platform for which the build is targeted,
CMAKE_SYSTEM_NAME
and CMAKE_HOST_SYSTEM_NAME
are equivalent.
For example, detecting Linux target system:
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# ...
endif()
In generator expressions, PLATFORM_ID
can be used to detect target platforms:
target_compile_definitions(php PRIVATE $<$<PLATFORM_ID:Linux,FreeBSD>:FOOBAR>)
Note
All values known to CMake for CMAKE_SYSTEM_NAME
, CMAKE_HOST_SYSTEM_NAME
,
and PLATFORM_ID
are listed in the
CMake documentation.
When cross-compiling the CMAKE_SYSTEM_PROCESSOR
is determined from the
toolchain file. When compiling on the machine for which the build is also
targeted, the CMAKE_SYSTEM_PROCESSOR
and CMAKE_HOST_SYSTEM_PROCESSOR
will be
the same.
The method for determining the processor varies depending on the system. For
example, on FreeBSD the x86_64
is detected as amd64
, on Windows it is
AMD64
(detected from the PROCESSOR_ARCHITECTURE
environment variable):
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$")
# Target CPU is 64-bit x86.
endif()
Values for CMAKE_HOST_SYSTEM_PROCESSOR
and CMAKE_SYSTEM_PROCESSOR
:
Value | Note |
---|---|
aarch64.* |
64-bit ARM processor (aarch64 or aarch64_be ) |
alpha |
64-bit DEC Alpha processor |
amd64 |
64-bit x86 processor on FreeBSD |
AMD64 |
64-bit x86 processor on Windows |
arm |
32-bit ARM processor |
arm64 |
64-bit ARM processor |
ARM64 |
64-bit ARM processor on Windows |
EM64T |
64-bit processor on Windows XP-64 (obsolete) |
i.?86.* |
32-bit x86 processor |
IA-64 |
64-bit Intel Itanium processor on Windows |
loongarch32 |
32-bit Loongson processor |
loongarch64 |
64-bit Loongson processor |
mips |
32-bit MIPS processor |
mips64 |
64-bit MIPS processor |
powerpc |
PowerPC processor |
ppc |
32-bit PPC (PowerPC) processor |
ppc64 |
64-bit PPC (PowerPC) processor |
riscv64.* |
64-bit RISC-V Open ISA processor |
sparc |
32-bit SPARC processor |
sparc64 |
64-bit SPARC processor |
x86_64 |
64-bit x86 processor, on FreeBSD named amd64 |
x86 |
32-bit x86 processor |
X86 |
32-bit x86 processor on Windows |
There are some tools available for formatting and linting CMake files. While these tools can offer valuable assistance, it's worth emphasizing that the current recommendation is generally not to rely on any specific linting tool. This is primarily due to their varying levels of utility and a lack of updates to keep pace with new CMake versions. It's worth mentioning that this recommendation may evolve in the future as these tools continue to develop.
For convenience there is a custom helper script added to this repository that checks CMake files:
./bin/check-cmake.php
The gersemi
tool can check and fix
CMake code style:
gersemi --check --indent 2 --diff --definitions cmake -- cmake
The cmake-format
tool can
find formatting issues and sync the CMake code style:
cmake-format --check <CMakeLists.txt cmake/...>
It can utilize the configuration file (default cmake-format.[json|py|yaml]
) or
by passing the --config-files
or -c
option:
cmake-format -c path/to/cmake-format.json --check -- <CMakeLists.txt cmake/...>
Default configuration in JSON format can be printed to stdout:
cmake-format --dump-config json
Option --in-place
or -i
fixes particular CMake file in-place instead of
dumping the formatted content to stdout:
cmake-format -i path/to/cmake/file
The cmake-lint
tool is part of the cmakelang project and can help with linting CMake files:
cmake-lint <CMakeLists.txt cmake/...>
This tool can also utilize the cmake-format.[json|py|yaml]
file using the -c
option.
For linting there is also a separate and useful cmakelint tool which similarly lints and helps to better structure CMake files:
cmakelint <cmake/CMakeLists.txt cmake/...>