xref: /aosp_15_r20/external/pigweed/pw_build/pigweed.cmake (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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
16cmake_minimum_required(VERSION 3.19)
17
18# The PW_ROOT environment variable should be set in bootstrap. If it is not set,
19# set it to the root of the Pigweed repository.
20if("$ENV{PW_ROOT}" STREQUAL "")
21  get_filename_component(pw_root "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
22  message("The PW_ROOT environment variable is not set; "
23          "using ${pw_root} within CMake")
24  set(ENV{PW_ROOT} "${pw_root}")
25endif()
26
27# TOOD(ewout, hepler): Remove this legacy include once all users pull in
28# pw_unit_test/test.cmake for test functions and variables instead of relying
29# on them to be provided by pw_build/pigweed.cmake.
30include("$ENV{PW_ROOT}/pw_unit_test/test.cmake")
31
32# Wrapper around cmake_parse_arguments that fails with an error if any arguments
33# remained unparsed or a required argument was not provided.
34#
35# All parsed arguments are prefixed with "arg_". This helper can only be used
36# by functions, not macros.
37#
38# Required Arguments:
39#
40#   NUM_POSITIONAL_ARGS - PARSE_ARGV <N> arguments for
41#                              cmake_parse_arguments
42#
43# Optional Args:
44#
45#   OPTION_ARGS - <option> arguments for cmake_parse_arguments
46#   ONE_VALUE_ARGS - <one_value_keywords> arguments for cmake_parse_arguments
47#   MULTI_VALUE_ARGS - <multi_value_keywords> arguments for
48#                           cmake_parse_arguments
49#   REQUIRED_ARGS - required arguments which must be set, these may any
50#                        argument type (<option>, <one_value_keywords>, and/or
51#                        <multi_value_keywords>)
52#
53macro(pw_parse_arguments)
54  # First parse the arguments to this macro.
55  cmake_parse_arguments(
56    pw_parse_arg "" "NUM_POSITIONAL_ARGS"
57    "OPTION_ARGS;ONE_VALUE_ARGS;MULTI_VALUE_ARGS;REQUIRED_ARGS"
58    ${ARGN}
59  )
60  pw_require_args("pw_parse_arguments" "pw_parse_arg_" NUM_POSITIONAL_ARGS)
61  if(NOT "${pw_parse_arg_UNPARSED_ARGUMENTS}" STREQUAL "")
62    message(FATAL_ERROR "Unexpected arguments to pw_parse_arguments: "
63            "${pw_parse_arg_UNPARSED_ARGUMENTS}")
64  endif()
65
66  # Now that we have the macro's arguments, process the caller's arguments.
67  pw_parse_arguments_strict("${CMAKE_CURRENT_FUNCTION}"
68    "${pw_parse_arg_NUM_POSITIONAL_ARGS}"
69    "${pw_parse_arg_OPTION_ARGS}"
70    "${pw_parse_arg_ONE_VALUE_ARGS}"
71    "${pw_parse_arg_MULTI_VALUE_ARGS}"
72  )
73  pw_require_args("${CMAKE_CURRENT_FUNCTION}" "arg_"
74                  ${pw_parse_arg_REQUIRED_ARGS})
75endmacro()
76
77# TODO(ewout, hepler): Deprecate this function in favor of pw_parse_arguments.
78# Wrapper around cmake_parse_arguments that fails with an error if any arguments
79# remained unparsed.
80macro(pw_parse_arguments_strict function start_arg options one multi)
81  cmake_parse_arguments(PARSE_ARGV
82      "${start_arg}" arg "${options}" "${one}" "${multi}"
83  )
84  if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "")
85    set(_all_args ${options} ${one} ${multi})
86    message(FATAL_ERROR
87        "Unexpected arguments to ${function}: ${arg_UNPARSED_ARGUMENTS}\n"
88        "Valid arguments: ${_all_args}"
89    )
90  endif()
91endmacro()
92
93# Checks that one or more variables are set. This is used to check that
94# arguments were provided to a function. Fails with FATAL_ERROR if
95# ${ARG_PREFIX}${name} is empty. The FUNCTION_NAME is used in the error message.
96# If FUNCTION_NAME is "", it is set to CMAKE_CURRENT_FUNCTION.
97#
98# Usage:
99#
100#   pw_require_args(FUNCTION_NAME ARG_PREFIX ARG_NAME [ARG_NAME ...])
101#
102# Examples:
103#
104#   # Checks that arg_FOO is non-empty, using the current function name.
105#   pw_require_args("" arg_ FOO)
106#
107#   # Checks that FOO and BAR are non-empty, using function name "do_the_thing".
108#   pw_require_args(do_the_thing "" FOO BAR)
109#
110macro(pw_require_args FUNCTION_NAME ARG_PREFIX)
111  if("${FUNCTION_NAME}" STREQUAL "")
112    set(_pw_require_args_FUNCTION_NAME "${CMAKE_CURRENT_FUNCTION}")
113  else()
114    set(_pw_require_args_FUNCTION_NAME "${FUNCTION_NAME}")
115  endif()
116
117  foreach(name IN ITEMS ${ARGN})
118    if("${${ARG_PREFIX}${name}}" STREQUAL "")
119      message(FATAL_ERROR "A value must be provided for ${name} in "
120          "${_pw_require_args_FUNCTION_NAME}.")
121    endif()
122  endforeach()
123endmacro()
124
125# pw_target_link_targets: CMake target only form of target_link_libraries.
126#
127# Helper wrapper around target_link_libraries which only supports CMake targets
128# and detects when the target does not exist.
129#
130# NOTE: Generator expressions are not supported.
131#
132# Due to the processing order of list files, the list of targets has to be
133# checked at the end of the root CMake list file. Instead of requiring all
134# list files to be modified, a DEFER CALL is used.
135#
136# Required Args:
137#
138#   <name> - The library target to add the TARGET link dependencies to.
139#
140# Optional Args:
141#
142#   INTERFACE - interface target_link_libraries arguments which are all TARGETs.
143#   PUBLIC - public target_link_libraries arguments which are all TARGETs.
144#   PRIVATE - private target_link_libraries arguments which are all TARGETs.
145function(pw_target_link_targets NAME)
146  set(types INTERFACE PUBLIC PRIVATE )
147  pw_parse_arguments(
148    NUM_POSITIONAL_ARGS
149      1
150    MULTI_VALUE_ARGS
151      ${types}
152  )
153
154  if(NOT TARGET "${NAME}")
155    message(FATAL_ERROR "\"${NAME}\" must be a TARGET library")
156  endif()
157
158  foreach(type IN LISTS types)
159    foreach(library IN LISTS arg_${type})
160      target_link_libraries(${NAME} ${type} ${library})
161      if(NOT TARGET ${library})
162        # It's possible the target has not yet been defined due to the ordering
163        # of add_subdirectory. Ergo defer the call until the end of the
164        # configuration phase.
165
166        # cmake_language(DEFER ...) evaluates arguments at the time the deferred
167        # call is executed, ergo wrap it in a cmake_language(EVAL CODE ...) to
168        # evaluate the arguments now. The arguments are wrapped in brackets to
169        # avoid re-evaluation at the deferred call.
170        cmake_language(EVAL CODE
171          "cmake_language(DEFER DIRECTORY ${CMAKE_SOURCE_DIR} CALL
172                          _pw_target_link_targets_deferred_check
173                          [[${NAME}]] [[${type}]] ${library})"
174        )
175      endif()
176    endforeach()
177  endforeach()
178endfunction()
179
180# Runs any deferred library checks for pw_target_link_targets.
181#
182# Required Args:
183#
184#   <name> - The name of the library target to add the link dependencies to.
185#   <type> - The type of the library (INTERFACE, PUBLIC, PRIVATE).
186#   <library> - The library to check to assert it's a TARGET.
187function(_pw_target_link_targets_deferred_check NAME TYPE LIBRARY)
188  if(NOT TARGET ${LIBRARY})
189      message(FATAL_ERROR
190        "${NAME}'s ${TYPE} dep \"${LIBRARY}\" is not a target.")
191  endif()
192endfunction()
193
194# Sets the provided variable to the multi_value_keywords from pw_add_library.
195macro(_pw_add_library_multi_value_args variable)
196  set("${variable}" SOURCES HEADERS
197                    PUBLIC_DEPS PRIVATE_DEPS
198                    PUBLIC_INCLUDES PRIVATE_INCLUDES
199                    PUBLIC_DEFINES PRIVATE_DEFINES
200                    PUBLIC_COMPILE_OPTIONS PRIVATE_COMPILE_OPTIONS
201                    PUBLIC_LINK_OPTIONS PRIVATE_LINK_OPTIONS "${ARGN}")
202endmacro()
203
204# pw_add_library_generic: Creates a CMake library target.
205#
206# Required Args:
207#
208#   <name> - The name of the library target to be created.
209#   <type> - The library type which must be INTERFACE, OBJECT, STATIC, or
210#            SHARED.
211#
212# Optional Args:
213#
214#   SOURCES - source files for this library
215#   HEADERS - header files for this library
216#   PUBLIC_DEPS - public pw_target_link_targets arguments
217#   PRIVATE_DEPS - private pw_target_link_targets arguments
218#   PUBLIC_INCLUDES - public target_include_directories argument
219#   PRIVATE_INCLUDES - public target_include_directories argument
220#   PUBLIC_DEFINES - public target_compile_definitions arguments
221#   PRIVATE_DEFINES - private target_compile_definitions arguments
222#   PUBLIC_COMPILE_OPTIONS - public target_compile_options arguments
223#   PRIVATE_COMPILE_OPTIONS - private target_compile_options arguments
224#   PRIVATE_COMPILE_OPTIONS_DEPS_BEFORE - private target_compile_options BEFORE
225#     arguments from the specified deps's INTERFACE_COMPILE_OPTIONS. Note that
226#     these deps are not pulled in as target_link_libraries. This should not be
227#     exposed by the non-generic API.
228#   PUBLIC_LINK_OPTIONS - public target_link_options arguments
229#   PRIVATE_LINK_OPTIONS - private target_link_options arguments
230function(pw_add_library_generic NAME TYPE)
231  set(supported_library_types INTERFACE OBJECT STATIC SHARED)
232  if(NOT "${TYPE}" IN_LIST supported_library_types)
233    message(FATAL_ERROR "\"${TYPE}\" is not a valid library type for ${NAME}. "
234          "Must be INTERFACE, OBJECT, STATIC, or SHARED.")
235  endif()
236
237  _pw_add_library_multi_value_args(multi_value_args)
238  pw_parse_arguments(
239    NUM_POSITIONAL_ARGS
240      2
241    MULTI_VALUE_ARGS
242      ${multi_value_args}
243      PRIVATE_COMPILE_OPTIONS_DEPS_BEFORE
244  )
245
246  # CMake 3.22 does not have a notion of target_headers yet, so in the mean
247  # time we ask for headers to be specified for consistency with GN & Bazel and
248  # to improve the IDE experience. However, we do want to ensure all the headers
249  # which are otherwise ignored by CMake are present.
250  #
251  # See https://gitlab.kitware.com/cmake/cmake/-/issues/22468 for adding support
252  # to CMake to associate headers with targets properly for CMake 3.23.
253  foreach(header IN ITEMS ${arg_HEADERS})
254    get_filename_component(header "${header}" ABSOLUTE)
255    if(NOT EXISTS ${header})
256      message(FATAL_ERROR "Header not found: \"${header}\"")
257    endif()
258  endforeach()
259
260  # In order to more easily create the various types of libraries, two hidden
261  # targets are created: NAME._config and NAME._public_config which loosely
262  # mirror the GN configs although we also carry target link dependencies
263  # through these.
264
265  # Add the NAME._config target_link_libraries dependency with the
266  # PRIVATE_INCLUDES, PRIVATE_DEFINES, PRIVATE_COMPILE_OPTIONS,
267  # PRIVATE_LINK_OPTIONS, and PRIVATE_DEPS.
268  add_library("${NAME}._config" INTERFACE EXCLUDE_FROM_ALL)
269  target_include_directories("${NAME}._config"
270    INTERFACE
271      ${arg_PRIVATE_INCLUDES}
272  )
273  target_compile_definitions("${NAME}._config"
274    INTERFACE
275      ${arg_PRIVATE_DEFINES}
276  )
277  target_compile_options("${NAME}._config"
278    INTERFACE
279      ${arg_PRIVATE_COMPILE_OPTIONS}
280  )
281  target_link_options("${NAME}._config"
282    INTERFACE
283      ${arg_PRIVATE_LINK_OPTIONS}
284  )
285  pw_target_link_targets("${NAME}._config"
286    INTERFACE
287      ${arg_PRIVATE_DEPS}
288  )
289
290  # Add the NAME._public_config target_link_libraries dependency with the
291  # PUBLIC_INCLUDES, PUBLIC_DEFINES, PUBLIC_COMPILE_OPTIONS,
292  # PUBLIC_LINK_OPTIONS, and PUBLIC_DEPS.
293  add_library("${NAME}._public_config" INTERFACE EXCLUDE_FROM_ALL)
294  target_include_directories("${NAME}._public_config"
295    INTERFACE
296      ${arg_PUBLIC_INCLUDES}
297  )
298  target_compile_definitions("${NAME}._public_config"
299    INTERFACE
300      ${arg_PUBLIC_DEFINES}
301  )
302  target_compile_options("${NAME}._public_config"
303    INTERFACE
304      ${arg_PUBLIC_COMPILE_OPTIONS}
305  )
306  target_link_options("${NAME}._public_config"
307    INTERFACE
308      ${arg_PUBLIC_LINK_OPTIONS}
309  )
310  pw_target_link_targets("${NAME}._public_config"
311    INTERFACE
312      ${arg_PUBLIC_DEPS}
313  )
314
315  # Instantiate the library depending on the type using the NAME._config and
316  # NAME._public_config libraries we just created.
317  if("${TYPE}" STREQUAL "INTERFACE")
318    if(NOT "${arg_SOURCES}" STREQUAL "")
319      message(
320        SEND_ERROR "${NAME} cannot have sources as it's an INTERFACE library")
321    endif(NOT "${arg_SOURCES}" STREQUAL "")
322
323    add_library("${NAME}" INTERFACE EXCLUDE_FROM_ALL)
324    target_sources("${NAME}" PRIVATE ${arg_HEADERS})
325    pw_target_link_targets("${NAME}"
326      INTERFACE
327        "${NAME}._public_config"
328    )
329  elseif(("${TYPE}" STREQUAL "STATIC") OR ("${TYPE}" STREQUAL "SHARED"))
330    if("${arg_SOURCES}" STREQUAL "")
331      message(
332        SEND_ERROR "${NAME} must have SOURCES as it's not an INTERFACE library")
333    endif("${arg_SOURCES}" STREQUAL "")
334
335    add_library("${NAME}" "${TYPE}" EXCLUDE_FROM_ALL)
336    target_sources("${NAME}" PRIVATE ${arg_HEADERS} ${arg_SOURCES})
337    pw_target_link_targets("${NAME}"
338      PUBLIC
339        "${NAME}._public_config"
340      PRIVATE
341        "${NAME}._config"
342    )
343    foreach(compile_option_dep IN LISTS arg_PRIVATE_COMPILE_OPTIONS_DEPS_BEFORE)
344      # This will fail at build time if the target does not exist.
345      target_compile_options("${NAME}" BEFORE PRIVATE
346          $<TARGET_PROPERTY:${compile_option_dep},INTERFACE_COMPILE_OPTIONS>
347      )
348    endforeach()
349  elseif("${TYPE}" STREQUAL "OBJECT")
350    if("${arg_SOURCES}" STREQUAL "")
351      message(
352        SEND_ERROR "${NAME} must have SOURCES as it's not an INTERFACE library")
353    endif("${arg_SOURCES}" STREQUAL "")
354
355    # In order to support OBJECT libraries while maintaining transitive
356    # linking dependencies, the library has to be split up into two where the
357    # outer interface library forwards not only the internal object library
358    # but also its TARGET_OBJECTS.
359    add_library("${NAME}._object" OBJECT EXCLUDE_FROM_ALL)
360    target_sources("${NAME}._object" PRIVATE ${arg_SOURCES})
361    pw_target_link_targets("${NAME}._object"
362      PRIVATE
363        "${NAME}._public_config"
364        "${NAME}._config"
365    )
366    foreach(compile_option_dep IN LISTS arg_PRIVATE_COMPILE_OPTIONS_DEPS_BEFORE)
367      # This will fail at build time if the target does not exist.
368      target_compile_options("${NAME}._object" BEFORE PRIVATE
369          $<TARGET_PROPERTY:${compile_option_dep},INTERFACE_COMPILE_OPTIONS>
370      )
371    endforeach()
372
373    add_library("${NAME}" INTERFACE EXCLUDE_FROM_ALL)
374    target_sources("${NAME}" PRIVATE ${arg_HEADERS})
375    pw_target_link_targets("${NAME}"
376      INTERFACE
377        "${NAME}._public_config"
378        "${NAME}._object"
379    )
380    target_link_libraries("${NAME}"
381      INTERFACE
382        $<TARGET_OBJECTS:${NAME}._object>
383    )
384  else()
385    message(FATAL_ERROR "Unsupported libary type: ${TYPE}")
386  endif()
387endfunction(pw_add_library_generic)
388
389# Checks that the library's name is prefixed by the relative path with dot
390# separators instead of forward slashes. Ignores paths not under the root
391# directory.
392#
393# Optional Args:
394#
395#   REMAP_PREFIXES - support remapping a prefix for checks
396#
397function(_pw_check_name_is_relative_to_root NAME ROOT)
398  pw_parse_arguments(
399    NUM_POSITIONAL_ARGS
400      2
401    MULTI_VALUE_ARGS
402      REMAP_PREFIXES
403  )
404
405  file(RELATIVE_PATH rel_path "${ROOT}" "${CMAKE_CURRENT_SOURCE_DIR}")
406  if("${rel_path}" MATCHES "^\\.\\.")
407    return()  # Ignore paths not under ROOT
408  endif()
409
410  list(LENGTH arg_REMAP_PREFIXES remap_arg_count)
411  if("${remap_arg_count}" EQUAL 2)
412    list(GET arg_REMAP_PREFIXES 0 from_prefix)
413    list(GET arg_REMAP_PREFIXES 1 to_prefix)
414    string(REGEX REPLACE "^${from_prefix}" "${to_prefix}" rel_path "${rel_path}")
415  elseif(NOT "${remap_arg_count}" EQUAL 0)
416    message(FATAL_ERROR
417        "If REMAP_PREFIXES is specified, exactly two arguments must be given.")
418  endif()
419
420  if(NOT "${rel_path}" MATCHES "^\\.\\..*")
421    string(REPLACE "/" "." dot_rel_path "${rel_path}")
422    if(NOT "${NAME}" MATCHES "^${dot_rel_path}(\\.[^\\.]+)?(\\.facade)?$")
423      message(FATAL_ERROR
424          "Module libraries under ${ROOT} must match the module name or be in "
425          "the form 'PATH_TO.THE_TARGET.NAME'. The library '${NAME}' does not "
426          "match. Expected ${dot_rel_path}.LIBRARY_NAME"
427      )
428    endif()
429  endif()
430endfunction(_pw_check_name_is_relative_to_root)
431
432# Creates a pw module library.
433#
434# Required Args:
435#
436#   <name> - The name of the library target to be created.
437#   <type> - The library type which must be INTERFACE, OBJECT, STATIC or SHARED.
438#
439# Optional Args:
440#
441#   SOURCES - source files for this library
442#   HEADERS - header files for this library
443#   PUBLIC_DEPS - public pw_target_link_targets arguments
444#   PRIVATE_DEPS - private pw_target_link_targets arguments
445#   PUBLIC_INCLUDES - public target_include_directories argument
446#   PRIVATE_INCLUDES - public target_include_directories argument
447#   PUBLIC_DEFINES - public target_compile_definitions arguments
448#   PRIVATE_DEFINES - private target_compile_definitions arguments
449#   PUBLIC_COMPILE_OPTIONS - public target_compile_options arguments
450#   PRIVATE_COMPILE_OPTIONS - private target_compile_options arguments
451#   PUBLIC_LINK_OPTIONS - public target_link_options arguments
452#   PRIVATE_LINK_OPTIONS - private target_link_options arguments
453#
454function(pw_add_library NAME TYPE)
455  _pw_add_library_multi_value_args(pw_add_library_generic_multi_value_args)
456  pw_parse_arguments(
457    NUM_POSITIONAL_ARGS
458      2
459    MULTI_VALUE_ARGS
460      ${pw_add_library_generic_multi_value_args}
461  )
462
463  _pw_check_name_is_relative_to_root("${NAME}" "$ENV{PW_ROOT}"
464    REMAP_PREFIXES
465      third_party pw_third_party
466  )
467
468  pw_add_library_generic(${NAME} ${TYPE}
469    SOURCES
470      ${arg_SOURCES}
471    HEADERS
472      ${arg_HEADERS}
473    PUBLIC_DEPS
474      # TODO: b/232141950 - Apply compilation options that affect ABI
475      # globally in the CMake build instead of injecting them into libraries.
476      pw_build
477      ${arg_PUBLIC_DEPS}
478    PRIVATE_DEPS
479      ${arg_PRIVATE_DEPS}
480    PUBLIC_INCLUDES
481      ${arg_PUBLIC_INCLUDES}
482    PRIVATE_INCLUDES
483      ${arg_PRIVATE_INCLUDES}
484    PUBLIC_DEFINES
485      ${arg_PUBLIC_DEFINES}
486    PRIVATE_DEFINES
487      ${arg_PRIVATE_DEFINES}
488    PUBLIC_COMPILE_OPTIONS
489      ${arg_PUBLIC_COMPILE_OPTIONS}
490    PRIVATE_COMPILE_OPTIONS_DEPS_BEFORE
491      pw_build.warnings
492    PRIVATE_COMPILE_OPTIONS
493      ${arg_PRIVATE_COMPILE_OPTIONS}
494    PUBLIC_LINK_OPTIONS
495      ${arg_PUBLIC_LINK_OPTIONS}
496    PRIVATE_LINK_OPTIONS
497      ${arg_PRIVATE_LINK_OPTIONS}
498  )
499endfunction(pw_add_library)
500
501# Declares a module as a facade.
502#
503# Facades are declared as two libraries to avoid circular dependencies.
504# Libraries that use the facade depend on a library named for the module. The
505# module that implements the facade depends on a library named
506# MODULE_NAME.facade.
507#
508# pw_add_facade accepts the same arguments as pw_add_library.
509# It also accepts the following argument:
510#
511#  BACKEND - The name of the facade's backend variable.
512function(pw_add_facade NAME TYPE)
513  _pw_add_library_multi_value_args(multi_value_args)
514  pw_parse_arguments(
515    NUM_POSITIONAL_ARGS
516      2
517    ONE_VALUE_ARGS
518      BACKEND
519    MULTI_VALUE_ARGS
520      ${multi_value_args}
521  )
522
523  _pw_check_name_is_relative_to_root("${NAME}" "$ENV{PW_ROOT}"
524    REMAP_PREFIXES
525      third_party pw_third_party
526  )
527
528  pw_add_facade_generic("${NAME}" "${TYPE}"
529    BACKEND
530      ${arg_BACKEND}
531    SOURCES
532      ${arg_SOURCES}
533    HEADERS
534      ${arg_HEADERS}
535    PUBLIC_DEPS
536      # TODO: b/232141950 - Apply compilation options that affect ABI
537      # globally in the CMake build instead of injecting them into libraries.
538      pw_build
539      ${arg_PUBLIC_DEPS}
540    PRIVATE_DEPS
541      ${arg_PRIVATE_DEPS}
542    PUBLIC_INCLUDES
543      ${arg_PUBLIC_INCLUDES}
544    PRIVATE_INCLUDES
545      ${arg_PRIVATE_INCLUDES}
546    PUBLIC_DEFINES
547      ${arg_PUBLIC_DEFINES}
548    PRIVATE_DEFINES
549      ${arg_PRIVATE_DEFINES}
550    PUBLIC_COMPILE_OPTIONS
551      ${arg_PUBLIC_COMPILE_OPTIONS}
552    PRIVATE_COMPILE_OPTIONS_DEPS_BEFORE
553      pw_build.warnings
554    PRIVATE_COMPILE_OPTIONS
555      ${arg_PRIVATE_COMPILE_OPTIONS}
556    PUBLIC_LINK_OPTIONS
557      ${arg_PUBLIC_LINK_OPTIONS}
558    PRIVATE_LINK_OPTIONS
559      ${arg_PRIVATE_LINK_OPTIONS}
560  )
561endfunction(pw_add_facade)
562
563# pw_add_facade_generic: Creates a CMake facade library target.
564#
565# Facades are declared as two libraries to avoid circular dependencies.
566# Libraries that use the facade depend on the <name> of this target. The
567# libraries that implement this facade have to depend on an internal library
568# named <name>.facade.
569#
570# Required Args:
571#
572#   <name> - The name for the public facade target (<name>) for all users and
573#            the suffixed facade target for backend implementers (<name.facade).
574#   <type> - The library type which must be INTERFACE, OBJECT, STATIC, or
575#            SHARED.
576#   BACKEND - The name of the facade's backend variable.
577#
578# Optional Args:
579#
580#   SOURCES - source files for this library
581#   HEADERS - header files for this library
582#   PUBLIC_DEPS - public pw_target_link_targets arguments
583#   PRIVATE_DEPS - private pw_target_link_targets arguments
584#   PUBLIC_INCLUDES - public target_include_directories argument
585#   PRIVATE_INCLUDES - public target_include_directories argument
586#   PUBLIC_DEFINES - public target_compile_definitions arguments
587#   PRIVATE_DEFINES - private target_compile_definitions arguments
588#   PUBLIC_COMPILE_OPTIONS - public target_compile_options arguments
589#   PRIVATE_COMPILE_OPTIONS_DEPS_BEFORE - private target_compile_options BEFORE
590#     arguments from the specified deps's INTERFACE_COMPILE_OPTIONS. Note that
591#     these deps are not pulled in as target_link_libraries. This should not be
592#     exposed by the non-generic API.
593#   PRIVATE_COMPILE_OPTIONS - private target_compile_options arguments
594#   PUBLIC_LINK_OPTIONS - public target_link_options arguments
595#   PRIVATE_LINK_OPTIONS - private target_link_options arguments
596function(pw_add_facade_generic NAME TYPE)
597  set(supported_library_types INTERFACE OBJECT STATIC SHARED)
598  if(NOT "${TYPE}" IN_LIST supported_library_types)
599    message(FATAL_ERROR "\"${TYPE}\" is not a valid library type for ${NAME}. "
600          "Must be INTERFACE, OBJECT, STATIC, or SHARED.")
601  endif()
602
603  _pw_add_library_multi_value_args(multi_value_args)
604  pw_parse_arguments(
605    NUM_POSITIONAL_ARGS
606      2
607    ONE_VALUE_ARGS
608      BACKEND
609    MULTI_VALUE_ARGS
610      ${multi_value_args}
611      PRIVATE_COMPILE_OPTIONS_DEPS_BEFORE
612    REQUIRED_ARGS
613      BACKEND
614  )
615
616  if(NOT DEFINED "${arg_BACKEND}")
617    message(FATAL_ERROR "${NAME}'s backend variable ${arg_BACKEND} has not "
618            "been defined, you may be missing a pw_add_backend_variable or "
619            "the *.cmake import to that file.")
620  endif()
621  string(REGEX MATCH ".+_BACKEND" backend_ends_in_backend "${arg_BACKEND}")
622  if(NOT backend_ends_in_backend)
623    message(FATAL_ERROR "The ${NAME} pw_add_generic_facade's BACKEND argument "
624            "(${arg_BACKEND}) must end in _BACKEND (${name_ends_in_backend})")
625  endif()
626
627  set(backend_target "${${arg_BACKEND}}")
628  if ("${backend_target}" STREQUAL "")
629    # If no backend is set, a script that displays an error message is used
630    # instead. If the facade is used in the build, it fails with this error.
631    pw_add_error_target("${NAME}.NO_BACKEND_SET"
632      MESSAGE
633        "Attempted to build the ${NAME} facade with no backend set. "
634        "Configure the ${NAME} backend using pw_set_backend or remove all "
635        "dependencies on it. See https://pigweed.dev/pw_build."
636    )
637
638    set(backend_target "${NAME}.NO_BACKEND_SET")
639  endif()
640
641  # Define the facade library, which is used by the backend to avoid circular
642  # dependencies.
643  pw_add_library_generic("${NAME}.facade" INTERFACE
644    HEADERS
645      ${arg_HEADERS}
646    PUBLIC_INCLUDES
647      ${arg_PUBLIC_INCLUDES}
648    PUBLIC_DEPS
649      ${arg_PUBLIC_DEPS}
650    PUBLIC_DEFINES
651      ${arg_PUBLIC_DEFINES}
652    PUBLIC_COMPILE_OPTIONS
653      ${arg_PUBLIC_COMPILE_OPTIONS}
654    PUBLIC_LINK_OPTIONS
655      ${arg_PUBLIC_LINK_OPTIONS}
656  )
657
658  # Define the public-facing library for this facade, which depends on the
659  # header files and public interface aspects from the .facade target and
660  # exposes the dependency on the backend along with the private library
661  # target components.
662  pw_add_library_generic("${NAME}" "${TYPE}"
663    PUBLIC_DEPS
664      "${NAME}.facade"
665      "${backend_target}"
666    SOURCES
667      ${arg_SOURCES}
668    PRIVATE_INCLUDES
669      ${arg_PRIVATE_INCLUDES}
670    PRIVATE_DEPS
671      ${arg_PRIVATE_DEPS}
672    PRIVATE_DEFINES
673      ${arg_PRIVATE_DEFINES}
674    PRIVATE_COMPILE_OPTIONS_DEPS_BEFORE
675      ${arg_PRIVATE_COMPILE_OPTIONS_DEPS_BEFORE}
676    PRIVATE_COMPILE_OPTIONS
677      ${arg_PRIVATE_COMPILE_OPTIONS}
678    PRIVATE_LINK_OPTIONS
679      ${arg_PRIVATE_LINK_OPTIONS}
680  )
681endfunction(pw_add_facade_generic)
682
683# Declare a facade's backend variables which can be overriden later by using
684# pw_set_backend.
685#
686# Required Arguments:
687#   NAME - Name of the facade's backend variable.
688#
689# Optional Arguments:
690#   DEFAULT_BACKEND - Optional default backend selection for the facade.
691#
692function(pw_add_backend_variable NAME)
693  pw_parse_arguments(
694    NUM_POSITIONAL_ARGS
695      1
696    ONE_VALUE_ARGS
697      DEFAULT_BACKEND
698  )
699
700  string(REGEX MATCH ".+_BACKEND" name_ends_in_backend "${NAME}")
701  if(NOT name_ends_in_backend)
702    message(FATAL_ERROR "The ${NAME} pw_add_backend_variable's NAME argument "
703            "must end in _BACKEND")
704  endif()
705
706  set("${NAME}" "${arg_DEFAULT_BACKEND}" CACHE STRING
707      "${NAME} backend variable for a facade")
708endfunction()
709
710# Sets which backend to use for the given facade's backend variable.
711function(pw_set_backend NAME BACKEND)
712  # TODO(ewout, hepler): Deprecate this temporarily support which permits the
713  # direct facade name directly, instead of the facade's backend variable name.
714  # Also update this to later assert the variable is DEFINED to catch typos.
715  string(REGEX MATCH ".+_BACKEND" name_ends_in_backend "${NAME}")
716  if(NOT name_ends_in_backend)
717    set(NAME "${NAME}_BACKEND")
718  endif()
719  if(NOT DEFINED "${NAME}")
720    message(WARNING "${NAME} was not defined when pw_set_backend was invoked, "
721            "you may be missing a pw_add_backend_variable or the *.cmake "
722            "import to that file.")
723  endif()
724
725  set("${NAME}" "${BACKEND}" CACHE STRING "backend variable for a facade" FORCE)
726endfunction(pw_set_backend)
727
728# Zephyr specific wrapper for pw_set_backend.
729function(pw_set_zephyr_backend_ifdef COND FACADE BACKEND BACKEND_DECL)
730  if(${${COND}})
731    if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/${BACKEND_DECL}")
732      message(FATAL_ERROR
733          "Can't find backend declaration file '${CMAKE_CURRENT_LIST_DIR}/${BACKEND_DECL}'")
734    endif()
735    include("${CMAKE_CURRENT_LIST_DIR}/${BACKEND_DECL}")
736    pw_set_backend("${FACADE}" "${BACKEND}")
737  endif()
738endfunction()
739
740# Zephyr specific wrapper to convert a pw library to a Zephyr library
741function(pw_zephyrize_libraries_ifdef COND)
742  if(DEFINED Zephyr_FOUND)
743    if(${${COND}})
744      zephyr_link_libraries(${ARGN})
745      foreach(lib ${ARGN})
746        target_link_libraries(${lib} INTERFACE zephyr_interface)
747      endforeach()
748    endif()
749  endif()
750endfunction()
751
752# Zephyr function allowing conversion of Kconfig values to Pigweed configs
753function(pw_set_config_from_zephyr ZEPHYR_CONFIG PW_CONFIG)
754  if(${ZEPHYR_CONFIG})
755    add_compile_definitions(${PW_CONFIG}=${${ZEPHYR_CONFIG}})
756  endif()
757endfunction()
758
759# Set up the default pw_build_DEFAULT_MODULE_CONFIG.
760set("pw_build_DEFAULT_MODULE_CONFIG" pw_build.empty CACHE STRING
761    "Default implementation for all Pigweed module configurations.")
762
763# Declares a module configuration variable for module libraries to depend on.
764# Configs should be set to libraries which can be used to provide defines
765# directly or though included header files.
766#
767# The configs can be selected either through the pw_set_module_config function
768# to set the pw_build_DEFAULT_MODULE_CONFIG used by default for all Pigweed
769# modules or by selecting a specific one for the given NAME'd configuration.
770#
771# Args:
772#
773#   NAME: name to use for the target which can be depended on for the config.
774function(pw_add_module_config NAME)
775  pw_parse_arguments(NUM_POSITIONAL_ARGS 1)
776
777  # Declare the module configuration variable for this module.
778  set("${NAME}" "${pw_build_DEFAULT_MODULE_CONFIG}"
779      CACHE STRING "Module configuration for ${NAME}")
780endfunction(pw_add_module_config)
781
782# Sets which config library to use for the given module.
783#
784# This can be used to set a specific module configuration or the default
785# module configuration used for all Pigweed modules:
786#
787#   pw_set_module_config(pw_build_DEFAULT_MODULE_CONFIG my_config)
788#   pw_set_module_config(pw_foo_CONFIG my_foo_config)
789function(pw_set_module_config NAME LIBRARY)
790  pw_parse_arguments(NUM_POSITIONAL_ARGS 2)
791
792  # Update the module configuration variable.
793  set("${NAME}" "${LIBRARY}" CACHE STRING "Config for ${NAME}" FORCE)
794endfunction(pw_set_module_config)
795
796# Adds compiler options to all targets built by CMake. Flags may be added any
797# time after this function is defined. The effect is global; all targets added
798# before or after a pw_add_global_compile_options call will be built with the
799# flags, regardless of where the files are located.
800#
801# pw_add_global_compile_options takes one optional named argument:
802#
803#   LANGUAGES: Which languages (ASM, C, CXX) to apply the options to. Flags
804#       apply to all languages by default.
805#
806# All other arguments are interpreted as compiler options.
807function(pw_add_global_compile_options)
808  cmake_parse_arguments(PARSE_ARGV 0 args "" "" "LANGUAGES")
809
810  set(supported_build_languages ASM C CXX)
811
812  if(NOT args_LANGUAGES)
813    set(args_LANGUAGES ${supported_build_languages})
814  endif()
815
816  # Check the selected language.
817  foreach(lang IN LISTS args_LANGUAGES)
818    if(NOT "${lang}" IN_LIST supported_build_languages)
819      message(FATAL_ERROR "'${lang}' is not a supported language. "
820              "Supported languages: ${supported_build_languages}")
821    endif()
822  endforeach()
823
824  # Enumerate which flags variables to set.
825  foreach(lang IN LISTS args_LANGUAGES)
826    list(APPEND cmake_flags_variables "CMAKE_${lang}_FLAGS")
827  endforeach()
828
829  # Set each flag for each specified flags variable.
830  foreach(variable IN LISTS cmake_flags_variables)
831    foreach(flag IN LISTS args_UNPARSED_ARGUMENTS)
832      set(${variable} "${${variable}} ${flag}" CACHE INTERNAL "" FORCE)
833    endforeach()
834  endforeach()
835endfunction(pw_add_global_compile_options)
836
837# pw_add_error_target: Creates a CMake target which fails to build and prints a
838#                      message
839#
840# This function prints a message and causes a build failure only if you attempt
841# to build the target. This is useful when FATAL_ERROR messages cannot be used
842# to catch problems during the CMake configuration phase.
843#
844# Args:
845#
846#   NAME: name to use for the target
847#   MESSAGE: The message to print, prefixed with "ERROR: ". The message may be
848#            composed of multiple pieces by passing multiple strings.
849#
850function(pw_add_error_target NAME)
851  pw_parse_arguments(
852    NUM_POSITIONAL_ARGS
853      1
854    MULTI_VALUE_ARGS
855      MESSAGE
856  )
857
858  # In case the message is comprised of multiple strings, stitch them together.
859  set(message "ERROR: ")
860  foreach(line IN LISTS arg_MESSAGE)
861    string(APPEND message "${line}")
862  endforeach()
863
864  add_custom_target("${NAME}._error_message"
865    COMMAND
866      "${CMAKE_COMMAND}" -E echo "${message}"
867    COMMAND
868      "${CMAKE_COMMAND}" -E false
869  )
870
871  # A static library is provided, in case this rule nominally provides a
872  # compiled output, e.g. to enable $<TARGET_FILE:"${NAME}">.
873  pw_add_library_generic("${NAME}" STATIC
874    SOURCES
875      $<TARGET_PROPERTY:pw_build.empty,SOURCES>
876  )
877  add_dependencies("${NAME}" "${NAME}._error_message")
878endfunction(pw_add_error_target)
879
880# Rebases a set of files to a new root path and optionally appends extensions
881# to them. This is particularly useful for file generators.
882#
883# Required Arguments:
884#
885#   <var>        - Variable to store the rebased file list in.
886#   <new_root>   - The new root to rebase file paths onto.
887#   <root>       - The current root to rebase off of.
888#   <files>      - The list of files to rebase.
889#   <extensions> - List of extensions to replace the existing file extensions
890#                  with.
891#
892# Examples:
893#
894# list(APPEND files "public/proj/foo.def" "public/proj/bar.def")
895#
896# pw_rebase_paths(out_files "/tmp" "${CMAKE_CURRENT_SOURCE_DIR}/public"
897#   ${files} "")
898# out_files => [ "/tmp/proj/foo.def", "/tmp/proj/bar.def" ]
899#
900# pw_rebase_paths(out_files "/tmp" "${CMAKE_CURRENT_SOURCE_DIR}/public"
901#   ${files} ".h")
902# out_files => [ "/tmp/proj/foo.h", "/tmp/proj/bar.h" ]
903#
904# list (APPEND exts ".h" ".cc")
905# pw_rebase_paths(out_files "/tmp" "${CMAKE_CURRENT_SOURCE_DIR}/public"
906#   ${files} ${exts})
907# out_files => [ "/tmp/proj/foo.h", "/tmp/proj/bar.h",
908#                "/tmp/proj/foo.cc", "/tmp/proj/bar.cc" ]
909function(pw_rebase_paths VAR NEW_ROOT ROOT FILES EXTENSIONS)
910  foreach(file IN LISTS FILES)
911    get_filename_component(file "${file}" ABSOLUTE)
912    file(RELATIVE_PATH file "${ROOT}" "${file}")
913
914    if("${EXTENSIONS}" STREQUAL "")
915      list(APPEND mirrored_files "${NEW_ROOT}/${file}")
916    else()
917      foreach(ext IN LISTS EXTENSIONS)
918        get_filename_component(dir "${file}" DIRECTORY)
919        get_filename_component(name "${file}" NAME_WE)
920        list(APPEND mirrored_files "${NEW_ROOT}/${dir}/${name}${ext}")
921      endforeach()
922    endif()
923  endforeach()
924
925  set("${VAR}"
926    "${mirrored_files}"
927    PARENT_SCOPE)
928endfunction(pw_rebase_paths)
929