# 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. # 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) } 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 # instead of the lowercase target name. _varname = _dir + "__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 = [ "backend" ] + _facade_vars 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" ] } } }