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