Halide 21.0.0
Halide compiler and libraries
|
This document specifies the coding standards we adhere to when authoring new CMake code. If you need directions for building Halide, see BuildingHalideWithCMake.md. If you are looking for Halide's CMake package documentation, see HalideCMakePackage.md.
This document is necessary for two major reasons. First, due to its long history, size, and dedication to backwards compatibility, CMake is incredibly difficult to learn and full of traps. Second, Halide bundles its own LLVM-based native code generator, which CMake deeply does not expect. This means we routinely push CMake's build model to its limit.
Therefore, we must be careful to write high-quality CMake code so that it is clear when CMake's limitations are being tested. While not comprehensive, the guide outlines the code quality expectations we have as they apply to CMake.
When contributing new CMake code to Halide, keep in mind that the minimum version is 3.28. Therefore, it is not only possible, but required, to use modern CMake best practices.
The following are some common mistakes that lead to subtly broken builds.
As mentioned above, using directory properties is brittle, and they are therefore not allowed. The following functions may not appear in any new CMake code.
Command | Alternative |
---|---|
add_compile_definitions | Use target_compile_definitions |
add_compile_options | Use target_compile_options |
add_definitions | Use target_compile_definitions |
add_link_options | Use target_link_options, but prefer not to use either |
include_directories | Use target_include_directories |
link_directories | Use target_link_libraries |
link_libraries | Use target_link_libraries |
remove_definitions | Generator expressions in target_compile_definitions |
set_directory_properties | Use (cache) variables or target properties |
set_property(DIRECTORY) | Use (cache) variables or target properties (custom properties excluded, but require justification) |
target_link_libraries(target lib) | Use target_link_libraries with a visibility specifier (eg. PRIVATE) |
As an example, it was once common practice to write code similar to this:
However, this has two major pitfalls. First, it applies to all targets created in that directory, even those before the call to include_directories and those created in include()-ed CMake files. As CMake files get larger and more complex, this behavior gets harder to pinpoint. This is particularly vexing when using the link_libraries or add_definitions commands. Second, this form does not provide a way to propagate the include directory to consumers of my_lib. The correct way to do this is:
This is better in many ways. It only affects the target in question. It propagates the include path to the targets linking to it (via PUBLIC). It also correctly exports the host-filesystem-specific include path when installing or packaging the target and installs the headers themselves, too.
If common properties need to be grouped together, use an INTERFACE target (better) or write a function (worse).
There are also several functions that are disallowed for other reasons:
Command | Reason | Alternative |
---|---|---|
aux_source_directory | Interacts poorly with incremental builds and Git | List source files explicitly |
build_command | CTest internal function | Use CTest build-and-test mode via CMAKE_CTEST_COMMAND |
cmake_host_system_information | Usually misleading information. | Inspect toolchain variables and use generator expressions. |
cmake_policy(... OLD) | OLD policies are deprecated by definition. | Instead, fix the code to work with the new policy. |
create_test_sourcelist | We use our own unit testing solution | See the adding tests section. |
define_property | Adds unnecessary complexity | Use a cache variable. Exceptions under special circumstances. |
enable_language | Halide is C/C++ only | FindCUDAToolkit, appropriately guarded. |
file(GLOB ...) | Interacts poorly with incremental builds and Git | List source files explicitly. Allowed if not globbing for source files. |
fltk_wrap_ui | Halide does not use FLTK | None |
include_external_msproject | Halide must remain portable | Write a CMake package config file or find module. |
include_guard | Use of recursive inclusion is not allowed | Write (recursive) functions. |
include_regular_expression | Changes default dependency checking behavior | None |
load_cache | Superseded by FetchContent/ExternalProject | Use aforementioned modules |
macro | CMake macros are not hygienic and are therefore error-prone | Use functions instead. |
site_name | Privacy: do not want leak host name information | Provide a cache variable, generate a unique name. |
variable_watch | Debugging helper | None. Not needed in production. |
Do not introduce any dependencies via find_package without broader approval. Importantly, never introduce a new use of FetchContent; prefer to add dependencies to vcpkg.json.
Any variables that are specific to languages that are not enabled should, of course, be avoided. But of greater concern are variables that are easy to misuse or should not be overridden for our end-users. The following (non-exhaustive) list of variables shall not be used in code merged into main.
Variable | Reason | Alternative |
---|---|---|
CMAKE_ROOT | Code smell | Rely on find_package search options; include HINTS if necessary |
CMAKE_DEBUG_TARGET_PROPERTIES | Debugging helper | None |
CMAKE_FIND_DEBUG_MODE | Debugging helper | None |
CMAKE_RULE_MESSAGES | Debugging helper | None |
CMAKE_VERBOSE_MAKEFILE | Debugging helper | None |
CMAKE_BACKWARDS_COMPATIBILITY | Deprecated | None |
CMAKE_BUILD_TOOL | Deprecated | ${CMAKE_COMMAND} --build or CMAKE_MAKE_PROGRAM (but see below) |
CMAKE_CACHEFILE_DIR | Deprecated | CMAKE_BINARY_DIR, but see below |
CMAKE_CFG_INTDIR | Deprecated | $<CONFIG>, $<TARGET_FILE:..>, target resolution of add_custom_command, etc. |
CMAKE_CL_64 | Deprecated | CMAKE_SIZEOF_VOID_P |
CMAKE_COMPILER_IS_* | Deprecated | CMAKE_<LANG>_COMPILER_ID |
CMAKE_HOME_DIRECTORY | Deprecated | CMAKE_SOURCE_DIR, but see below |
CMAKE_DIRECTORY_LABELS | Directory property | None |
CMAKE_BUILD_TYPE | Only applies to single-config generators. | $<CONFIG> |
CMAKE_*_FLAGS* (w/o _INIT) | User-only | Write a toolchain file with the corresponding _INIT variable |
CMAKE_COLOR_MAKEFILE | User-only | None |
CMAKE_ERROR_DEPRECATED | User-only | None |
CMAKE_CONFIGURATION_TYPES | We only support the four standard build types | None |
Of course feel free to insert debugging helpers while developing but please remove them before review. Finally, the following variables are allowed, but their use must be motivated:
Variable | Reason | Alternative |
---|---|---|
CMAKE_SOURCE_DIR | Points to global source root, not Halide's. | Halide_SOURCE_DIR or PROJECT_SOURCE_DIR |
CMAKE_BINARY_DIR | Points to global build root, not Halide's | Halide_BINARY_DIR or PROJECT_BINARY_DIR |
CMAKE_MAKE_PROGRAM | CMake abstracts over differences in the build tool. | Prefer CTest's build and test mode or CMake's --build mode |
CMAKE_CROSSCOMPILING | Often misleading. | Inspect relevant variables directly, eg. CMAKE_SYSTEM_NAME |
BUILD_SHARED_LIBS | Could override user setting | None, but be careful to restore value when overriding for a dependency |
Any use of these functions or variables will block a PR.
When adding a file to any of the folders under test, be aware that CI expects that every .c and .cpp appears in the CMakeLists.txt file on its own line, possibly as a comment. This is to avoid globbing and also to ensure that added files are not missed.
For most test types, it should be as simple as adding to the existing lists, which must remain in alphabetical order. Generator tests are trickier, but following the existing examples is a safe way to go.
If you're contributing a new app to Halide: great! Thank you! There are a few guidelines you should follow when writing a new app.