| # 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\"", |
| "", |
| "Alternatively, if the target depending on this facade is a `pw_test`", |
| "which should only be built in toolchains with a provided backend,", |
| "consider adding an `enable_if` to the dependent target:", |
| "", |
| " pw_test(...) {", |
| " enable_if = $_varname != \"\"", |
| " ...", |
| " }", |
| "", |
| "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" ]) |
| } |
| } |