• 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# 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