1# Copyright 2020 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14include_guard(GLOBAL) 15 16# The PW_ROOT environment variable should be set in bootstrap. If it is not set, 17# set it to the root of the Pigweed repository. 18if("$ENV{PW_ROOT}" STREQUAL "") 19 get_filename_component(pw_root "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) 20 message("The PW_ROOT environment variable is not set; " 21 "using ${pw_root} within CMake") 22 set(ENV{PW_ROOT} "${pw_root}") 23endif() 24 25# Wrapper around cmake_parse_arguments that fails with an error if any arguments 26# remained unparsed. 27macro(_pw_parse_argv_strict function start_arg options one multi) 28 cmake_parse_arguments(PARSE_ARGV 29 "${start_arg}" arg "${options}" "${one}" "${multi}" 30 ) 31 if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "") 32 set(_all_args ${options} ${one} ${multi}) 33 message(FATAL_ERROR 34 "Unexpected arguments to ${function}: ${arg_UNPARSED_ARGUMENTS}\n" 35 "Valid arguments: ${_all_args}" 36 ) 37 endif() 38endmacro() 39 40# Automatically creates a library and test targets for the files in a module. 41# This function is only suitable for simple modules that meet the following 42# requirements: 43# 44# - The module exposes exactly one library. 45# - All source files in the module directory are included in the library. 46# - Each test in the module has 47# - exactly one source .cc file, 48# - optionally, one .c source with the same base name as the .cc file, 49# - only a dependency on the main module library. 50# - The module is not a facade. 51# 52# Modules that do not meet these requirements may not use 53# pw_auto_add_simple_module. Instead, define the module's libraries and tests 54# with pw_add_module_library, pw_add_facade, pw_add_test, and the standard CMake 55# functions, such as add_library, target_link_libraries, etc. 56# 57# This function does the following: 58# 59# 1. Find all .c and .cc files in the module's root directory. 60# 2. Create a library with the module name using pw_add_module_library with 61# all source files that do not end with _test.cc. 62# 3. Declare a test for each source file that ends with _test.cc. 63# 64# Args: 65# 66# IMPLEMENTS_FACADE - this module implements the specified facade 67# PUBLIC_DEPS - public target_link_libraries arguments 68# PRIVATE_DEPS - private target_link_libraries arguments 69# 70function(pw_auto_add_simple_module MODULE) 71 _pw_parse_argv_strict(pw_auto_add_simple_module 1 72 "" 73 "IMPLEMENTS_FACADE" 74 "PUBLIC_DEPS;PRIVATE_DEPS;TEST_DEPS" 75 ) 76 77 file(GLOB all_sources *.cc *.c) 78 79 # Create a library with all source files not ending in _test. 80 set(sources "${all_sources}") 81 list(FILTER sources EXCLUDE REGEX "_test(\\.cc|(_c)?\\.c)$") # *_test.cc 82 list(FILTER sources EXCLUDE REGEX "^test(\\.cc|(_c)?\\.c)$") # test.cc 83 list(FILTER sources EXCLUDE REGEX "_fuzzer\\.cc$") 84 85 file(GLOB_RECURSE headers *.h) 86 87 if(arg_IMPLEMENTS_FACADE) 88 set(groups backends) 89 else() 90 set(groups modules "${MODULE}") 91 endif() 92 93 pw_add_module_library("${MODULE}" 94 IMPLEMENTS_FACADES 95 ${arg_IMPLEMENTS_FACADE} 96 PUBLIC_DEPS 97 ${arg_PUBLIC_DEPS} 98 PRIVATE_DEPS 99 ${arg_PRIVATE_DEPS} 100 SOURCES 101 ${sources} 102 HEADERS 103 ${headers} 104 ) 105 106 pw_auto_add_module_tests("${MODULE}" 107 PRIVATE_DEPS 108 ${arg_PUBLIC_DEPS} 109 ${arg_PRIVATE_DEPS} 110 ${arg_TEST_DEPS} 111 GROUPS 112 ${groups} 113 ) 114endfunction(pw_auto_add_simple_module) 115 116# Creates a test for each source file ending in _test. Tests with mutliple .cc 117# files or different dependencies than the module will not work correctly. 118# 119# Args: 120# 121# PRIVATE_DEPS - dependencies to apply to all tests 122# GROUPS - groups in addition to MODULE to which to add these tests 123# 124function(pw_auto_add_module_tests MODULE) 125 _pw_parse_argv_strict(pw_auto_add_module_tests 1 126 "" 127 "" 128 "PRIVATE_DEPS;GROUPS" 129 ) 130 131 file(GLOB cc_tests *_test.cc) 132 133 foreach(test IN LISTS cc_tests) 134 get_filename_component(test_name "${test}" NAME_WE) 135 136 # Find a .c test corresponding with the test .cc file, if any. 137 file(GLOB c_test "${test_name}.c" "${test_name}_c.c") 138 139 pw_add_test("${MODULE}.${test_name}" 140 SOURCES 141 "${test}" 142 ${c_test} 143 DEPS 144 "$<TARGET_NAME_IF_EXISTS:${MODULE}>" 145 ${arg_PRIVATE_DEPS} 146 GROUPS 147 "${MODULE}" 148 ${arg_GROUPS} 149 ) 150 endforeach() 151endfunction(pw_auto_add_module_tests) 152 153# Sets the provided variable to the common library arguments. 154macro(_pw_library_args variable) 155 set("${variable}" SOURCES HEADERS PUBLIC_DEPS PRIVATE_DEPS ${ARGN}) 156endmacro() 157 158# Creates a library in a module. The library has access to the public/ include 159# directory. 160# 161# Args: 162# 163# SOURCES - source files for this library 164# HEADERS - header files for this library 165# PUBLIC_DEPS - public target_link_libraries arguments 166# PRIVATE_DEPS - private target_link_libraries arguments 167# PUBLIC_INCLUDES - public target_include_directories argument 168# PRIVATE_INCLUDES - public target_include_directories argument 169# IMPLEMENTS_FACADES - which facades this library implements 170# PUBLIC_DEFINES - public target_compile_definitions arguments 171# PRIVATE_DEFINES - private target_compile_definitions arguments 172# PUBLIC_COMPILE_OPTIONS - public target_compile_options arguments 173# PRIVATE_COMPILE_OPTIONS - private target_compile_options arguments 174# PUBLIC_LINK_OPTIONS - public target_link_options arguments 175# PRIVATE_LINK_OPTIONS - private target_link_options arguments 176# 177function(pw_add_module_library NAME) 178 _pw_library_args( 179 list_args 180 PUBLIC_INCLUDES PRIVATE_INCLUDES 181 IMPLEMENTS_FACADES 182 PUBLIC_DEFINES PRIVATE_DEFINES 183 PUBLIC_COMPILE_OPTIONS PRIVATE_COMPILE_OPTIONS 184 PUBLIC_LINK_OPTIONS PRIVATE_LINK_OPTIONS 185 ) 186 _pw_parse_argv_strict(pw_add_module_library 1 "" "" "${list_args}") 187 188 # Check that the library's name is prefixed by the module name. 189 get_filename_component(module "${CMAKE_CURRENT_SOURCE_DIR}" NAME) 190 191 if(NOT "${NAME}" MATCHES "${module}(\\.[^\\.]+)?(\\.facade)?$") 192 message(FATAL_ERROR 193 "Module libraries must match the module name or be in the form " 194 "'MODULE_NAME.LIBRARY_NAME'. The library '${NAME}' does not match." 195 ) 196 endif() 197 198 # Instead of forking all of the code below or injecting an empty source file, 199 # conditionally select PUBLIC vs INTERFACE depending on whether there are 200 # sources to compile. 201 if(NOT "${arg_SOURCES}" STREQUAL "") 202 add_library("${NAME}" EXCLUDE_FROM_ALL) 203 set(public_or_interface PUBLIC) 204 else("${arg_SOURCES}" STREQUAL "") 205 add_library("${NAME}" EXCLUDE_FROM_ALL INTERFACE) 206 set(public_or_interface INTERFACE) 207 endif(NOT "${arg_SOURCES}" STREQUAL "") 208 209 target_sources("${NAME}" PRIVATE ${arg_SOURCES} ${arg_HEADERS}) 210 211 # CMake 3.22 does not have a notion of target_headers yet, so in the mean 212 # time we ask for headers to be specified for consistency with GN & Bazel and 213 # to improve the IDE experience. However, we do want to ensure all the headers 214 # which are otherwise ignored by CMake are present. 215 # 216 # See https://gitlab.kitware.com/cmake/cmake/-/issues/22468 for adding support 217 # to CMake to associate headers with targets properly for CMake 3.23. 218 foreach(header IN ITEMS ${arg_HEADERS}) 219 get_filename_component(header "${header}" ABSOLUTE) 220 if(NOT EXISTS ${header}) 221 message(FATAL_ERROR "Header not found: \"${header}\"") 222 endif() 223 endforeach() 224 225 if(NOT "${arg_PUBLIC_INCLUDES}" STREQUAL "") 226 target_include_directories("${NAME}" 227 ${public_or_interface} 228 ${arg_PUBLIC_INCLUDES} 229 ) 230 else("${arg_PUBLIC_INCLUDES}" STREQUAL "") 231 # TODO(pwbug/601): Deprecate this legacy implicit PUBLIC_INCLUDES. 232 target_include_directories("${NAME}" ${public_or_interface} public) 233 endif(NOT "${arg_PUBLIC_INCLUDES}" STREQUAL "") 234 235 if(NOT "${arg_PRIVATE_INCLUDES}" STREQUAL "") 236 target_include_directories("${NAME}" PRIVATE ${arg_PRIVATE_INCLUDES}) 237 endif(NOT "${arg_PRIVATE_INCLUDES}" STREQUAL "") 238 239 target_link_libraries("${NAME}" 240 ${public_or_interface} 241 pw_build 242 ${arg_PUBLIC_DEPS} 243 ) 244 245 if(NOT "${arg_SOURCES}" STREQUAL "") 246 target_link_libraries("${NAME}" 247 PRIVATE 248 pw_build.warnings 249 ${arg_PRIVATE_DEPS} 250 ) 251 endif(NOT "${arg_SOURCES}" STREQUAL "") 252 253 if(NOT "${arg_IMPLEMENTS_FACADES}" STREQUAL "") 254 target_include_directories("${NAME}" 255 ${public_or_interface} 256 public_overrides 257 ) 258 if("${arg_PUBLIC_INCLUDES}" STREQUAL "") 259 # TODO(pwbug/601): Deprecate this legacy implicit PUBLIC_INCLUDES. 260 target_include_directories("${NAME}" 261 ${public_or_interface} 262 public_overrides 263 ) 264 endif("${arg_PUBLIC_INCLUDES}" STREQUAL "") 265 set(facades ${arg_IMPLEMENTS_FACADES}) 266 list(TRANSFORM facades APPEND ".facade") 267 target_link_libraries("${NAME}" ${public_or_interface} ${facades}) 268 endif(NOT "${arg_IMPLEMENTS_FACADES}" STREQUAL "") 269 270 if(NOT "${arg_PUBLIC_DEFINES}" STREQUAL "") 271 target_compile_definitions("${NAME}" 272 ${public_or_interface} 273 ${arg_PUBLIC_DEFINES} 274 ) 275 endif(NOT "${arg_PUBLIC_DEFINES}" STREQUAL "") 276 277 if(NOT "${arg_PRIVATE_DEFINES}" STREQUAL "") 278 target_compile_definitions("${NAME}" PRIVATE ${arg_PRIVATE_DEFINES}) 279 endif(NOT "${arg_PRIVATE_DEFINES}" STREQUAL "") 280 281 if(NOT "${arg_PUBLIC_COMPILE_OPTIONS}" STREQUAL "") 282 target_compile_options("${NAME}" 283 ${public_or_interface} 284 ${arg_PUBLIC_COMPILE_OPTIONS} 285 ) 286 endif(NOT "${arg_PUBLIC_COMPILE_OPTIONS}" STREQUAL "") 287 288 if(NOT "${arg_PRIVATE_COMPILE_OPTIONS}" STREQUAL "") 289 target_compile_options("${NAME}" PRIVATE ${arg_PRIVATE_COMPILE_OPTIONS}) 290 endif(NOT "${arg_PRIVATE_COMPILE_OPTIONS}" STREQUAL "") 291 292 if(NOT "${arg_PUBLIC_LINK_OPTIONS}" STREQUAL "") 293 target_link_options("${NAME}" 294 ${public_or_interface} 295 ${arg_PUBLIC_LINK_OPTIONS} 296 ) 297 endif(NOT "${arg_PUBLIC_LINK_OPTIONS}" STREQUAL "") 298 299 if(NOT "${arg_PRIVATE_LINK_OPTIONS}" STREQUAL "") 300 target_link_options("${NAME}" PRIVATE ${arg_PRIVATE_LINK_OPTIONS}) 301 endif(NOT "${arg_PRIVATE_LINK_OPTIONS}" STREQUAL "") 302endfunction(pw_add_module_library) 303 304# Declares a module as a facade. 305# 306# Facades are declared as two libraries to avoid circular dependencies. 307# Libraries that use the facade depend on a library named for the module. The 308# module that implements the facade depends on a library named 309# MODULE_NAME.facade. 310# 311# pw_add_facade accepts the same arguments as pw_add_module_library, except for 312# IMPLEMENTS_FACADES. It also accepts the following argument: 313# 314# DEFAULT_BACKEND - which backend to use by default 315# 316function(pw_add_facade NAME) 317 _pw_library_args(list_args) 318 _pw_parse_argv_strict(pw_add_facade 1 "" "DEFAULT_BACKEND" "${list_args}") 319 320 # If no backend is set, a script that displays an error message is used 321 # instead. If the facade is used in the build, it fails with this error. 322 add_custom_target("${NAME}._no_backend_set_message" 323 COMMAND 324 "${CMAKE_COMMAND}" -E echo 325 "ERROR: Attempted to build the ${NAME} facade with no backend." 326 "Configure the ${NAME} backend using pw_set_backend or remove all dependencies on it." 327 "See https://pigweed.dev/pw_build." 328 COMMAND 329 "${CMAKE_COMMAND}" -E false 330 ) 331 add_library("${NAME}.NO_BACKEND_SET" INTERFACE) 332 add_dependencies("${NAME}.NO_BACKEND_SET" "${NAME}._no_backend_set_message") 333 334 # Set the default backend to the error message if no default is specified. 335 if("${arg_DEFAULT_BACKEND}" STREQUAL "") 336 set(arg_DEFAULT_BACKEND "${NAME}.NO_BACKEND_SET") 337 endif() 338 339 # Declare the backend variable for this facade. 340 set("${NAME}_BACKEND" "${arg_DEFAULT_BACKEND}" CACHE STRING 341 "Backend for ${NAME}") 342 343 # This target is never used; it simply tests that the specified backend 344 # actually exists in the build. The generator expression will fail to evaluate 345 # if the target is not defined. 346 add_custom_target(_pw_check_that_backend_for_${NAME}_is_defined 347 COMMAND 348 ${CMAKE_COMMAND} -E echo "$<TARGET_PROPERTY:${${NAME}_BACKEND},TYPE>" 349 ) 350 351 # Define the facade library, which is used by the backend to avoid circular 352 # dependencies. 353 add_library("${NAME}.facade" INTERFACE) 354 target_include_directories("${NAME}.facade" INTERFACE public) 355 target_link_libraries("${NAME}.facade" INTERFACE ${arg_PUBLIC_DEPS}) 356 357 # Define the public-facing library for this facade, which depends on the 358 # header files in .facade target and exposes the dependency on the backend. 359 pw_add_module_library("${NAME}" 360 SOURCES 361 ${arg_SOURCES} 362 HEADERS 363 ${arg_HEADERS} 364 PUBLIC_DEPS 365 "${NAME}.facade" 366 "${${NAME}_BACKEND}" 367 ) 368endfunction(pw_add_facade) 369 370# Sets which backend to use for the given facade. 371function(pw_set_backend FACADE BACKEND) 372 set("${FACADE}_BACKEND" "${BACKEND}" CACHE STRING "Backend for ${NAME}" FORCE) 373endfunction(pw_set_backend) 374 375# Set up the default pw_build_DEFAULT_MODULE_CONFIG. 376set("pw_build_DEFAULT_MODULE_CONFIG" pw_build.empty CACHE STRING 377 "Default implementation for all Pigweed module configurations.") 378 379# Declares a module configuration variable for module libraries to depend on. 380# Configs should be set to libraries which can be used to provide defines 381# directly or though included header files. 382# 383# The configs can be selected either through the pw_set_module_config function 384# to set the pw_build_DEFAULT_MODULE_CONFIG used by default for all Pigweed 385# modules or by selecting a specific one for the given NAME'd configuration. 386# 387# Args: 388# 389# NAME: name to use for the target which can be depended on for the config. 390function(pw_add_module_config NAME) 391 _pw_parse_argv_strict(pw_add_module_config 1 "" "" "") 392 393 # Declare the module configuration variable for this module. 394 set("${NAME}" "${pw_build_DEFAULT_MODULE_CONFIG}" 395 CACHE STRING "Module configuration for ${NAME}") 396endfunction(pw_add_module_config) 397 398# Sets which config library to use for the given module. 399# 400# This can be used to set a specific module configuration or the default 401# module configuration used for all Pigweed modules: 402# 403# pw_set_module_config(pw_build_DEFAULT_MODULE_CONFIG my_config) 404# pw_set_module_config(pw_foo_CONFIG my_foo_config) 405function(pw_set_module_config NAME LIBRARY) 406 _pw_parse_argv_strict(pw_set_module_config 2 "" "" "") 407 408 # Update the module configuration variable. 409 set("${NAME}" "${LIBRARY}" CACHE STRING "Config for ${NAME}" FORCE) 410endfunction(pw_set_module_config) 411 412# Declares a unit test. Creates two targets: 413# 414# * <TEST_NAME> - the test executable 415# * <TEST_NAME>.run - builds and runs the test 416# 417# Args: 418# 419# NAME: name to use for the target 420# SOURCES: source files for this test 421# DEPS: libraries on which this test depends 422# GROUPS: groups to which to add this test; if none are specified, the test is 423# added to the 'default' and 'all' groups 424# 425function(pw_add_test NAME) 426 _pw_parse_argv_strict(pw_add_test 1 "" "" "SOURCES;DEPS;GROUPS") 427 428 add_executable("${NAME}" EXCLUDE_FROM_ALL ${arg_SOURCES}) 429 target_link_libraries("${NAME}" 430 PRIVATE 431 pw_unit_test 432 pw_unit_test.main 433 ${arg_DEPS} 434 ) 435 # Tests require at least one source file. 436 if(NOT arg_SOURCES) 437 target_sources("${NAME}" PRIVATE $<TARGET_PROPERTY:pw_build.empty,SOURCES>) 438 endif() 439 440 # Define a target for running the test. The target creates a stamp file to 441 # indicate successful test completion. This allows running tests in parallel 442 # with Ninja's full dependency resolution. 443 add_custom_command( 444 COMMAND 445 # TODO(hepler): This only runs local test binaries. Execute a test runner 446 # instead to support device test runs. 447 "$<TARGET_FILE:${NAME}>" 448 COMMAND 449 "${CMAKE_COMMAND}" -E touch "${NAME}.stamp" 450 DEPENDS 451 "${NAME}" 452 OUTPUT 453 "${NAME}.stamp" 454 ) 455 add_custom_target("${NAME}.run" DEPENDS "${NAME}.stamp") 456 457 # Always add tests to the "all" group. If no groups are provided, add the 458 # test to the "default" group. 459 if(arg_GROUPS) 460 set(groups all ${arg_GROUPS}) 461 else() 462 set(groups all default) 463 endif() 464 465 list(REMOVE_DUPLICATES groups) 466 pw_add_test_to_groups("${NAME}" ${groups}) 467endfunction(pw_add_test) 468 469# Adds a test target to the specified test groups. Test groups can be built with 470# the pw_tests_GROUP_NAME target or executed with the pw_run_tests_GROUP_NAME 471# target. 472function(pw_add_test_to_groups TEST_NAME) 473 foreach(group IN LISTS ARGN) 474 if(NOT TARGET "pw_tests.${group}") 475 add_custom_target("pw_tests.${group}") 476 add_custom_target("pw_run_tests.${group}") 477 endif() 478 479 add_dependencies("pw_tests.${group}" "${TEST_NAME}") 480 add_dependencies("pw_run_tests.${group}" "${TEST_NAME}.run") 481 endforeach() 482endfunction(pw_add_test_to_groups) 483