• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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