| # Copyright 2019 The Pigweed Authors | 
 | # | 
 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not | 
 | # use this file except in compliance with the License. You may obtain a copy of | 
 | # the License at | 
 | # | 
 | #     https://www.apache.org/licenses/LICENSE-2.0 | 
 | # | 
 | # Unless required by applicable law or agreed to in writing, software | 
 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | 
 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | 
 | # License for the specific language governing permissions and limitations under | 
 | # the License. | 
 |  | 
 | import("//build_overrides/pigweed.gni") | 
 |  | 
 | import("$dir_pw_build/error.gni") | 
 | import("$dir_pw_build/target_types.gni") | 
 |  | 
 | # Declare a facade. | 
 | # | 
 | # A Pigweed facade is an API layer that has a single implementation it must | 
 | # link against. Typically this will be done by pointing a build arg like | 
 | # `pw_[module]_BACKEND` at a backend implementation for that module. | 
 | # | 
 | # pw_facade creates two targets: | 
 | # | 
 | #   $target_name: The public-facing pw_source_set that provides the API and | 
 | #     implementation (backend). Users of the facade should depend on this. | 
 | #   $target_name.facade: A private source_set that provides ONLY the API. ONLY | 
 | #     backends should depend on this. | 
 | # | 
 | # If the target name matches the directory name (e.g. //foo:foo), a ":facade" | 
 | # alias of the facade target (e.g. //foo:facade) is also provided. This avoids | 
 | # the need to repeat the directory name, for consistency with the main target. | 
 | # | 
 | # The facade's headers are split out into the *.facade target to avoid circular | 
 | # dependencies. Here's a concrete example to illustrate why this is needed: | 
 | # | 
 | #   foo_BACKEND = "//foo:foo_backend_bar" | 
 | # | 
 | #   pw_facade("foo") { | 
 | #     backend = foo_BACKEND | 
 | #     public = [ "foo.h" ] | 
 | #     sources = [ "foo.cc" ] | 
 | #   } | 
 | # | 
 | #   pw_source_set("foo_backend_bar") { | 
 | #     deps = [ ":foo.facade" ] | 
 | #     sources = [ "bar.cc" ] | 
 | #   } | 
 | # | 
 | # This creates the following dependency graph: | 
 | # | 
 | #   foo.facade  <-. | 
 | #    ^             \ | 
 | #    |              \ | 
 | #    |               \ | 
 | #   foo  ---------->  foo_backend_bar | 
 | # | 
 | # This allows foo_backend_bar to include "foo.h". If you tried to directly | 
 | # depend on `foo` from `foo_backend_bar`, you'd get a dependency cycle error in | 
 | # GN. | 
 | # | 
 | # Accepts the standard pw_source_set args with the following additions: | 
 | # | 
 | # Args: | 
 | #  backend: (required) The dependency that implements this facade (a GN | 
 | #    variable) | 
 | #  public: (required) The headers exposed by this facade. A facade acts as a | 
 | #    tool to break dependency cycles that come from the backend trying to | 
 | #    include headers from the facade itself. If the facade doesn't expose any | 
 | #    headers, it's basically the same as just depending directly on the build | 
 | #    arg that `backend` is set to. | 
 | #  require_link_deps: A list of build targets that must be included in | 
 | #    pw_build_LINK_DEPS if this facade is used. This mechanism is used to | 
 | #    avoid circular dependencies in low-level facades like pw_assert. | 
 | # | 
 | template("pw_facade") { | 
 |   assert(defined(invoker.backend), | 
 |          "pw_facade requires a reference to a backend variable for the facade") | 
 |   assert(defined(invoker.public), | 
 |          "If your facade does not explicitly expose an API that a backend " + | 
 |              "must depend on, you can just directly depend on the build arg " + | 
 |              "that the `backend` template argument would have been set to.") | 
 |  | 
 |   _facade_name = "$target_name.facade" | 
 |  | 
 |   if (get_path_info(get_label_info(":$target_name", "dir"), "name") == | 
 |       get_label_info(":$target_name", "name")) { | 
 |     group("facade") { | 
 |       public_deps = [ ":$_facade_name" ] | 
 |     } | 
 |   } | 
 |  | 
 |   _facade_vars = [ | 
 |     # allow_circular_includes_from should very rarely be used, but when it is, | 
 |     # it only applies to headers, so should be in the .facade target. | 
 |     "allow_circular_includes_from", | 
 |     "public_configs", | 
 |     "public_deps", | 
 |     "public", | 
 |   ] | 
 |   pw_source_set(_facade_name) { | 
 |     forward_variables_from(invoker, _facade_vars, [ "require_link_deps" ]) | 
 |   } | 
 |  | 
 |   if (invoker.backend == "") { | 
 |     # Try to guess the name of the facade's backend variable. | 
 |     _dir = get_path_info(get_label_info(":$target_name", "dir"), "name") | 
 |     if (target_name == _dir) { | 
 |       _varname = target_name + "_BACKEND" | 
 |     } else { | 
 |       # There is no way to capitalize this string in GN, so use <FACADE_NAME> | 
 |       # instead of the lowercase target name. | 
 |       _varname = _dir + "_<FACADE_NAME>_BACKEND" | 
 |     } | 
 |  | 
 |     # If backend is not set to anything, create a script that emits an error. | 
 |     # This will be added as a data dependency to the actual target, so that | 
 |     # attempting to build the facade without a backend fails with a relevant | 
 |     # error message. | 
 |     pw_error(target_name + ".NO_BACKEND_SET") { | 
 |       _label = get_label_info(":${invoker.target_name}", "label_no_toolchain") | 
 |       message_lines = [ | 
 |         "Attempted to build the $_label facade with no backend.", | 
 |         "", | 
 |         "If you are using this facade, ensure you have configured a backend ", | 
 |         "properly. The build arg for the facade must be set to a valid ", | 
 |         "backend in the toolchain. For example, you may need to add a line ", | 
 |         "like the following to the toolchain's .gni file:", | 
 |         "", | 
 |         "  $_varname = \"//path/to/the:backend\"", | 
 |         "", | 
 |         "If you are NOT using this facade, this error may have been triggered ", | 
 |         "by trying to build all targets.", | 
 |       ] | 
 |     } | 
 |   } | 
 |  | 
 |   # Create a target that defines the main facade library. Always emit this | 
 |   # target, even if the backend isn't defined, so that the dependency graph is | 
 |   # correctly expressed for gn check. | 
 |   pw_source_set(target_name) { | 
 |     # The main library contains everything else specified in the template. | 
 |     _ignore_vars = _facade_vars + [ | 
 |                      "backend", | 
 |                      "require_link_deps", | 
 |                    ] | 
 |     forward_variables_from(invoker, "*", _ignore_vars) | 
 |  | 
 |     public_deps = [ ":$_facade_name" ] | 
 |  | 
 |     # If the backend is set, inject it as a dependency. | 
 |     if (invoker.backend != "") { | 
 |       public_deps += [ invoker.backend ] | 
 |     } else { | 
 |       # If the backend is not set, depend on the *.NO_BACKEND_SET target. | 
 |       public_deps += [ ":$target_name.NO_BACKEND_SET" ] | 
 |     } | 
 |   } | 
 |  | 
 |   if (defined(invoker.require_link_deps) && invoker.backend != "") { | 
 |     # Check that the specified labels are listed in pw_build_LINK_DEPS. This | 
 |     # ensures these dependencies are available during linking, even if nothing | 
 |     # else in the build depends on them. | 
 |     foreach(label, invoker.require_link_deps) { | 
 |       _required_dep = get_label_info(label, "label_no_toolchain") | 
 |       _dep_is_in_link_dependencies = false | 
 |  | 
 |       foreach(link_dep, pw_build_LINK_DEPS) { | 
 |         if (get_label_info(link_dep, "label_no_toolchain") == _required_dep) { | 
 |           _dep_is_in_link_dependencies = true | 
 |         } | 
 |       } | 
 |  | 
 |       _facade_name = get_label_info(":$target_name", "label_no_toolchain") | 
 |       assert(_dep_is_in_link_dependencies, | 
 |              "$_required_dep must be listed in the pw_build_LINK_DEPS build " + | 
 |                  "arg when the $_facade_name facade is in use. Please update " + | 
 |                  "your toolchain configuration.") | 
 |     } | 
 |   } else { | 
 |     not_needed(invoker, [ "require_link_deps" ]) | 
 |   } | 
 | } |