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. Installation components
- 10. Tests
- 11. 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 coding 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 commands (functions, macros) 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:
set(variable_name "value")set(VARIABLE_NAME "value")-
In most cases, the preferred style is to use all lowercase letters.
add_library(php_foo src.c) function(php_function_name argument_name another_argument) if(argument_name) set(variable_name "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(parent_dir "foo/bar") set(child_dir "${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 else(), endif(),
endforeach(), endwhile(), endfunction(), and endmacro(). The optional
legacy argument in these commands is 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(some_path ${CMAKE_SOURCE_DIR}/main/php_config.h)use:
set(some_path ${PROJECT_SOURCE_DIR}/file.h)
# and
set(some_path ${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_DIRandCMAKE_BINARY_DIR: Denote the project source and build directories from the firstproject()call in the rootCMakeLists.txt.PROJECT_SOURCE_DIRandPROJECT_BINARY_DIR: Denote the project source and build directories from the most recentproject()call.<ProjectName>_SOURCE_DIRand<ProjectName>_BINARY_DIRrepresent the project source and build directories from the most recentproject(ProjectName ...)call.
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. To be more specific, the target and host platform can be also
determined by the:
CMAKE_SYSTEM_NAMEvariable or thePLATFORM_IDgenerator expression to identify the target platform (which is also the name used during cross-compilation).CMAKE_HOST_SYSTEM_NAMEvariable 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.
To determine the target processor use the
CMAKE_C_COMPILER_ARCHITECTURE_ID
variable. For example:
if(CMAKE_C_COMPILER_ARCHITECTURE_ID MATCHES "(x86_64|x64)")
# Target CPU is 64-bit x86.
endif()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 snake_case.
function(foo)
set(variable_name <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(VARIABLE_NAME <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(CACHE{PHP_VAR} TYPE <type> HELP <help> VALUE <value>)
# Cache variable as a boolean option
option(PHP_FOO "<help>" [value])
# Cache variables created by CMake command invocations. For example
find_program(PHP_SED_EXECUTABLE NAMES 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.
Cache variables, either internal ones, or those designed to be adjusted by the
user during the configuration phase, for example, through the presets, command
line, or by using GUI, such as cmake-gui or ccmake, are recommended to be
prefixed with PHP_ to facilitate their grouping within the GUI or IDE.
option(PHP_ENABLE_FOO "<help>" [value])
cmake_dependent_option(PHP_ENABLE_BAR "<help>" <value> <depends> <force>)
set(CACHE{PHP_FOO_BAR} TYPE <type> HELP <help> VALUE <value>)
# Zend Engine configuration variables
option(PHP_ZEND_ENABLE_FOO "<help>" [value])
# Configuration variables related to PHP extensions
option(PHP_EXT_FOO "<help>" [value])
# Configuration variables related to PHP SAPI modules
option(PHP_SAPI_FOO "<help>" [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(_temporary_variable <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, while variable _ is used as a container for string() command
argument and not used in the code later on:
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(some_variable "")Avoid this approach:
set(some_variable)
# or
unset(some_variable)The latter is equivalent to unset(some_variable), which can unintentionally
expose a cache variable with the same name if it exists. For example:
set(CACHE{some_variable} TYPE INTERNAL HELP "Some cache variable" VALUE "Foo")
# ...
set(some_variable)
message(STATUS "${some_variable}")
# Outputs: FooSetting 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)This sets variable <PackageName>_FOUND variable, which is managed by the
CMake's
FindPackageHandleStandardArgs
module. Find modules 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
upstream 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(PHP_FOO "<help>" ON)
# The IMPORTED property is set to TRUE and cannot be modified after being set
add_library(php_foo UNKNOWN IMPORTED)
get_target_property(value php_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 argument_name)
# Function body
endfunction()
macro(php_macro_name argument_name)
# 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_mainTarget name of the PHP main binding.
-
php_zendandphp_zend_*: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>_, php_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 [...])Installation components should follow the kebab-case naming convention and
they should be prefixed with php-:
install(
TARGETS php_foo_bar
# ...
COMPONENT php-foo-bar
)Test names added with add_test() command should follow the PascalCase naming
convention and should be prefixed with Php:
add_test(NAME PhpRunTests COMMAND ...)
add_test(NAME PhpSapiEmbedSharedBasic COMMAND ...)
add_test(NAME PhpUnitTest COMMAND ...)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.
The gersemi tool can check and fix
CMake code style:
gersemi --check --indent 2 --diff --definitions cmake -- cmakeThe 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 jsonOption --in-place or -i fixes particular CMake file in-place instead of
dumping the formatted content to stdout:
cmake-format -i path/to/cmake/fileThe 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/...>