1# Copyright 2019 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. 14 15import("//build_overrides/pigweed.gni") 16 17import("$dir_pw_build/error.gni") 18import("$dir_pw_build/target_types.gni") 19 20# Declare a facade. 21# 22# A Pigweed facade is an API layer that has a single implementation it must 23# link against. Typically this will be done by pointing a build arg like 24# `pw_[module]_BACKEND` at a backend implementation for that module. 25# 26# pw_facade creates two targets: 27# 28# $target_name: The public-facing pw_source_set that provides the API and 29# implementation (backend). Users of the facade should depend on this. 30# $target_name.facade: A private source_set that provides ONLY the API. ONLY 31# backends should depend on this. 32# 33# If the target name matches the directory name (e.g. //foo:foo), a ":facade" 34# alias of the facade target (e.g. //foo:facade) is also provided. This avoids 35# the need to repeat the directory name, for consistency with the main target. 36# 37# The facade's headers are split out into the *.facade target to avoid circular 38# dependencies. Here's a concrete example to illustrate why this is needed: 39# 40# foo_BACKEND = "//foo:foo_backend_bar" 41# 42# pw_facade("foo") { 43# backend = foo_BACKEND 44# public = [ "foo.h" ] 45# sources = [ "foo.cc" ] 46# } 47# 48# pw_source_set("foo_backend_bar") { 49# deps = [ ":foo.facade" ] 50# sources = [ "bar.cc" ] 51# } 52# 53# This creates the following dependency graph: 54# 55# foo.facade <-. 56# ^ \ 57# | \ 58# | \ 59# foo ----------> foo_backend_bar 60# 61# This allows foo_backend_bar to include "foo.h". If you tried to directly 62# depend on `foo` from `foo_backend_bar`, you'd get a dependency cycle error in 63# GN. 64# 65# Accepts the standard pw_source_set args with the following additions: 66# 67# Args: 68# backend: (required) The dependency that implements this facade (a GN 69# variable) 70# public: (required) The headers exposed by this facade. A facade acts as a 71# tool to break dependency cycles that come from the backend trying to 72# include headers from the facade itself. If the facade doesn't expose any 73# headers, it's basically the same as just depending directly on the build 74# arg that `backend` is set to. 75# require_link_deps: A list of build targets that must be included in 76# pw_build_LINK_DEPS if this facade is used. This mechanism is used to 77# avoid circular dependencies in low-level facades like pw_assert. 78# 79template("pw_facade") { 80 assert(defined(invoker.backend), 81 "pw_facade requires a reference to a backend variable for the facade") 82 83 # Only define the .facade subtarget if it has a public attribute to share. 84 if (defined(invoker.public) || defined(invoker.public_deps) || 85 defined(invoker.public_configs)) { 86 _facade_name = "$target_name.facade" 87 88 # Define //foo:facade alias 89 if (get_path_info(get_label_info(":$target_name", "dir"), "name") == 90 get_label_info(":$target_name", "name")) { 91 group("facade") { 92 public_deps = [ ":$_facade_name" ] 93 } 94 } 95 } else { 96 _facade_name = "" 97 } 98 99 _facade_vars = [ 100 # allow_circular_includes_from should very rarely be used, but when it is, 101 # it only applies to headers, so should be in the .facade target. 102 "allow_circular_includes_from", 103 "public_configs", 104 "public_deps", 105 "public", 106 "visibility", 107 ] 108 if (_facade_name != "") { 109 pw_source_set(_facade_name) { 110 forward_variables_from(invoker, _facade_vars, [ "require_link_deps" ]) 111 } 112 } 113 114 if (invoker.backend == "") { 115 # Try to guess the name of the facade's backend variable. 116 _dir = get_path_info(get_label_info(":$target_name", "dir"), "name") 117 if (target_name == _dir) { 118 _varname = target_name + "_BACKEND" 119 } else { 120 # There is no way to capitalize this string in GN, so use <FACADE_NAME> 121 # instead of the lowercase target name. 122 _varname = _dir + "_<FACADE_NAME>_BACKEND" 123 } 124 125 # If backend is not set to anything, create a script that emits an error. 126 # This will be added as a data dependency to the actual target, so that 127 # attempting to build the facade without a backend fails with a relevant 128 # error message. 129 pw_error(target_name + ".NO_BACKEND_SET") { 130 _label = get_label_info(":${invoker.target_name}", "label_no_toolchain") 131 message_lines = [ 132 "Attempted to build the $_label facade with no backend.", 133 "", 134 "If you are using this facade, ensure you have configured a backend ", 135 "properly. The build arg for the facade must be set to a valid ", 136 "backend in the toolchain. For example, you may need to add a line ", 137 "like the following to the toolchain's .gni file:", 138 "", 139 " $_varname = \"//path/to/the:backend\"", 140 "", 141 "Alternatively, if the target depending on this facade is a `pw_test`", 142 "which should only be built in toolchains with a provided backend,", 143 "consider adding an `enable_if` to the dependent target:", 144 "", 145 " pw_test(...) {", 146 " enable_if = $_varname != \"\"", 147 " ...", 148 " }", 149 "", 150 "If you are NOT using this facade, this error may have been triggered ", 151 "by trying to build all targets.", 152 ] 153 } 154 } 155 156 # Create a target that defines the main facade library. Always emit this 157 # target, even if the backend isn't defined, so that the dependency graph is 158 # correctly expressed for gn check. 159 pw_source_set(target_name) { 160 # The main library contains everything else specified in the template. 161 _ignore_vars = _facade_vars + [ 162 "backend", 163 "require_link_deps", 164 ] 165 forward_variables_from(invoker, "*", _ignore_vars) 166 167 # If the backend is set, inject it as a dependency. 168 if (invoker.backend != "") { 169 public_deps = [ invoker.backend ] 170 } else { 171 # If the backend is not set, depend on the *.NO_BACKEND_SET target. 172 public_deps = [ ":$target_name.NO_BACKEND_SET" ] 173 } 174 175 if (_facade_name != "") { 176 public_deps += [ ":$_facade_name" ] 177 } 178 } 179 180 if (defined(invoker.require_link_deps) && invoker.backend != "") { 181 # Check that the specified labels are listed in pw_build_LINK_DEPS. This 182 # ensures these dependencies are available during linking, even if nothing 183 # else in the build depends on them. 184 foreach(label, invoker.require_link_deps) { 185 _required_dep = get_label_info(label, "label_no_toolchain") 186 _dep_is_in_link_dependencies = false 187 188 foreach(link_dep, pw_build_LINK_DEPS) { 189 if (get_label_info(link_dep, "label_no_toolchain") == _required_dep) { 190 _dep_is_in_link_dependencies = true 191 } 192 } 193 194 _facade = get_label_info(":$target_name", "label_no_toolchain") 195 assert(_dep_is_in_link_dependencies, 196 "$_required_dep must be listed in the pw_build_LINK_DEPS build " + 197 "arg when the $_facade facade is in use. Please update your " + 198 "toolchain configuration.") 199 } 200 } else { 201 not_needed(invoker, [ "require_link_deps" ]) 202 } 203} 204