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# Wrapper around cmake_parse_arguments that fails with an error if any arguments 17# remained unparsed. 18macro(_pw_parse_argv_strict function start_arg options one multi) 19 cmake_parse_arguments(PARSE_ARGV 20 "${start_arg}" arg "${options}" "${one}" "${multi}" 21 ) 22 if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "") 23 set(_all_args ${options} ${one} ${multi}) 24 message(FATAL_ERROR 25 "Unexpected arguments to ${function}: ${arg_UNPARSED_ARGUMENTS}\n" 26 "Valid arguments: ${_all_args}" 27 ) 28 endif() 29endmacro() 30 31# Automatically creates a library and test targets for the files in a module. 32# This function is only suitable for simple modules that meet the following 33# requirements: 34# 35# - The module exposes exactly one library. 36# - All source files in the module directory are included in the library. 37# - Each test in the module has 38# - exactly one source .cc file, 39# - optionally, one .c source with the same base name as the .cc file, 40# - only a dependency on the main module library. 41# - The module is not a facade. 42# 43# Modules that do not meet these requirements may not use 44# pw_auto_add_simple_module. Instead, define the module's libraries and tests 45# with pw_add_module_library, pw_add_facade, pw_add_test, and the standard CMake 46# functions, such as add_library, target_link_libraries, etc. 47# 48# This function does the following: 49# 50# 1. Find all .c and .cc files in the module's root directory. 51# 2. Create a library with the module name using pw_add_module_library with 52# all source files that do not end with _test.cc. 53# 3. Declare a test for each source file that ends with _test.cc. 54# 55# Args: 56# 57# IMPLEMENTS_FACADE - this module implements the specified facade 58# PUBLIC_DEPS - public target_link_libraries arguments 59# PRIVATE_DEPS - private target_link_libraries arguments 60# 61function(pw_auto_add_simple_module MODULE) 62 _pw_parse_argv_strict(pw_auto_add_simple_module 1 63 "" 64 "IMPLEMENTS_FACADE" 65 "PUBLIC_DEPS;PRIVATE_DEPS;TEST_DEPS" 66 ) 67 68 file(GLOB all_sources *.cc *.c) 69 70 # Create a library with all source files not ending in _test. 71 set(sources "${all_sources}") 72 list(FILTER sources EXCLUDE REGEX "_test(\\.cc|(_c)?\\.c)$") 73 list(FILTER sources EXCLUDE REGEX "_fuzzer\\.cc$") 74 75 file(GLOB_RECURSE headers *.h) 76 77 if(arg_IMPLEMENTS_FACADE) 78 set(groups backends) 79 else() 80 set(groups modules "${MODULE}") 81 endif() 82 83 pw_add_module_library("${MODULE}" 84 IMPLEMENTS_FACADES 85 ${arg_IMPLEMENTS_FACADE} 86 PUBLIC_DEPS 87 ${arg_PUBLIC_DEPS} 88 PRIVATE_DEPS 89 ${arg_PRIVATE_DEPS} 90 SOURCES 91 ${sources} 92 HEADERS 93 ${headers} 94 ) 95 96 if(arg_IMPLEMENTS_FACADE) 97 target_include_directories("${MODULE}" PUBLIC public_overrides) 98 endif() 99 100 pw_auto_add_module_tests("${MODULE}" 101 PRIVATE_DEPS 102 ${arg_PUBLIC_DEPS} 103 ${arg_PRIVATE_DEPS} 104 ${arg_TEST_DEPS} 105 GROUPS 106 ${groups} 107 ) 108endfunction(pw_auto_add_simple_module) 109 110# Creates a test for each source file ending in _test. Tests with mutliple .cc 111# files or different dependencies than the module will not work correctly. 112# 113# Args: 114# 115# PRIVATE_DEPS - dependencies to apply to all tests 116# GROUPS - groups in addition to MODULE to which to add these tests 117# 118function(pw_auto_add_module_tests MODULE) 119 _pw_parse_argv_strict(pw_auto_add_module_tests 1 120 "" 121 "" 122 "PRIVATE_DEPS;GROUPS" 123 ) 124 125 file(GLOB cc_tests *_test.cc) 126 127 foreach(test IN LISTS cc_tests) 128 get_filename_component(test_name "${test}" NAME_WE) 129 130 # Find a .c test corresponding with the test .cc file, if any. 131 file(GLOB c_test "${test_name}.c" "${test_name}_c.c") 132 133 pw_add_test("${MODULE}.${test_name}" 134 SOURCES 135 "${test}" 136 ${c_test} 137 DEPS 138 "$<TARGET_NAME_IF_EXISTS:${MODULE}>" 139 ${arg_PRIVATE_DEPS} 140 GROUPS 141 "${MODULE}" 142 ${arg_GROUPS} 143 ) 144 endforeach() 145endfunction(pw_auto_add_module_tests) 146 147# Sets the provided variable to the common library arguments. 148macro(_pw_library_args variable) 149 set("${variable}" SOURCES HEADERS PUBLIC_DEPS PRIVATE_DEPS ${ARGN}) 150endmacro() 151 152# Creates a library in a module. The library has access to the public/ include 153# directory. 154# 155# Args: 156# 157# SOURCES - source files for this library 158# HEADERS - header files for this library 159# PUBLIC_DEPS - public target_link_libraries arguments 160# PRIVATE_DEPS - private target_link_libraries arguments 161# IMPLEMENTS_FACADES - which facades this library implements 162# 163function(pw_add_module_library NAME) 164 _pw_library_args(list_args IMPLEMENTS_FACADES) 165 _pw_parse_argv_strict(pw_add_module_library 1 "" "" "${list_args}") 166 167 # Check that the library's name is prefixed by the module name. 168 get_filename_component(module "${CMAKE_CURRENT_SOURCE_DIR}" NAME) 169 170 if(NOT "${NAME}" MATCHES "${module}(\\.[^\\.]+)?(\\.facade)?$") 171 message(FATAL_ERROR 172 "Module libraries must match the module name or be in the form " 173 "'MODULE_NAME.LIBRARY_NAME'. The library '${NAME}' does not match." 174 ) 175 endif() 176 177 add_library("${NAME}" EXCLUDE_FROM_ALL ${arg_HEADERS} ${arg_SOURCES}) 178 target_include_directories("${NAME}" PUBLIC public) 179 target_link_libraries("${NAME}" 180 PUBLIC 181 pw_build 182 ${arg_PUBLIC_DEPS} 183 PRIVATE 184 pw_build.strict_warnings 185 pw_build.extra_strict_warnings 186 ${arg_PRIVATE_DEPS} 187 ) 188 189 if(NOT "${arg_IMPLEMENTS_FACADES}" STREQUAL "") 190 target_include_directories("${NAME}" PUBLIC public_overrides) 191 set(facades ${arg_IMPLEMENTS_FACADES}) 192 list(TRANSFORM facades APPEND ".facade") 193 target_link_libraries("${NAME}" PUBLIC ${facades}) 194 endif() 195 196 # Libraries require at least one source file. 197 if(NOT arg_SOURCES) 198 target_sources("${NAME}" PRIVATE $<TARGET_PROPERTY:pw_build.empty,SOURCES>) 199 endif() 200endfunction(pw_add_module_library) 201 202# Declares a module as a facade. 203# 204# Facades are declared as two libraries to avoid circular dependencies. 205# Libraries that use the facade depend on a library named for the module. The 206# module that implements the facade depends on a library named 207# MODULE_NAME.facade. 208# 209# pw_add_facade accepts the same arguments as pw_add_module_library, except for 210# IMPLEMENTS_FACADES. It also accepts the following argument: 211# 212# DEFAULT_BACKEND - which backend to use by default 213# 214function(pw_add_facade NAME) 215 _pw_library_args(list_args) 216 _pw_parse_argv_strict(pw_add_facade 1 "" "DEFAULT_BACKEND" "${list_args}") 217 218 # If no backend is set, a script that displays an error message is used 219 # instead. If the facade is used in the build, it fails with this error. 220 add_custom_target("${NAME}._no_backend_set_message" 221 COMMAND 222 python "$ENV{PW_ROOT}/pw_build/py/pw_build/null_backend.py" "${NAME}" 223 ) 224 add_library("${NAME}.NO_BACKEND_SET" INTERFACE) 225 add_dependencies("${NAME}.NO_BACKEND_SET" "${NAME}._no_backend_set_message") 226 227 # Set the default backend to the error message if no default is specified. 228 if("${arg_DEFAULT_BACKEND}" STREQUAL "") 229 set(arg_DEFAULT_BACKEND "${NAME}.NO_BACKEND_SET") 230 endif() 231 232 # Declare the backend variable for this facade. 233 set("${NAME}_BACKEND" "${arg_DEFAULT_BACKEND}" CACHE STRING 234 "Backend for ${NAME}") 235 236 # Define the facade library, which is used by the backend to avoid circular 237 # dependencies. 238 add_library("${NAME}.facade" INTERFACE) 239 target_include_directories("${NAME}.facade" INTERFACE public) 240 target_link_libraries("${NAME}.facade" INTERFACE ${arg_PUBLIC_DEPS}) 241 242 # Define the public-facing library for this facade, which depends on the 243 # header files in .facade target and exposes the dependency on the backend. 244 pw_add_module_library("${NAME}" 245 SOURCES 246 ${arg_SOURCES} 247 HEADERS 248 ${arg_HEADERS} 249 PUBLIC_DEPS 250 "${NAME}.facade" 251 "${${NAME}_BACKEND}" 252 ) 253endfunction(pw_add_facade) 254 255# Sets which backend to use for the given facade. 256function(pw_set_backend FACADE BACKEND) 257 set("${FACADE}_BACKEND" "${BACKEND}" CACHE STRING "Backend for ${NAME}" FORCE) 258endfunction(pw_set_backend) 259 260# Declares a unit test. Creates two targets: 261# 262# * <TEST_NAME> - the test executable 263# * <TEST_NAME>.run - builds and runs the test 264# 265# Args: 266# 267# NAME: name to use for the target 268# SOURCES: source files for this test 269# DEPS: libraries on which this test depends 270# GROUPS: groups to which to add this test; if none are specified, the test is 271# added to the 'default' and 'all' groups 272# 273function(pw_add_test NAME) 274 _pw_parse_argv_strict(pw_add_test 1 "" "" "SOURCES;DEPS;GROUPS") 275 276 add_executable("${NAME}" EXCLUDE_FROM_ALL ${arg_SOURCES}) 277 target_link_libraries("${NAME}" 278 PRIVATE 279 pw_unit_test 280 pw_unit_test.main 281 ${arg_DEPS} 282 ) 283 284 # Define a target for running the test. The target creates a stamp file to 285 # indicate successful test completion. This allows running tests in parallel 286 # with Ninja's full dependency resolution. 287 add_custom_command( 288 COMMAND 289 # TODO(hepler): This only runs local test binaries. Execute a test runner 290 # instead to support device test runs. 291 "$<TARGET_FILE:${NAME}>" 292 COMMAND 293 "${CMAKE_COMMAND}" -E touch "${NAME}.stamp" 294 DEPENDS 295 "${NAME}" 296 OUTPUT 297 "${NAME}.stamp" 298 ) 299 add_custom_target("${NAME}.run" DEPENDS "${NAME}.stamp") 300 301 # Always add tests to the "all" group. If no groups are provided, add the 302 # test to the "default" group. 303 if(arg_GROUPS) 304 set(groups all ${arg_GROUPS}) 305 else() 306 set(groups all default) 307 endif() 308 309 list(REMOVE_DUPLICATES groups) 310 pw_add_test_to_groups("${NAME}" ${groups}) 311endfunction(pw_add_test) 312 313# Adds a test target to the specified test groups. Test groups can be built with 314# the pw_tests_GROUP_NAME target or executed with the pw_run_tests_GROUP_NAME 315# target. 316function(pw_add_test_to_groups TEST_NAME) 317 foreach(group IN LISTS ARGN) 318 if(NOT TARGET "pw_tests.${group}") 319 add_custom_target("pw_tests.${group}") 320 add_custom_target("pw_run_tests.${group}") 321 endif() 322 323 add_dependencies("pw_tests.${group}" "${TEST_NAME}") 324 add_dependencies("pw_run_tests.${group}" "${TEST_NAME}.run") 325 endforeach() 326endfunction(pw_add_test_to_groups) 327