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 assert(defined(invoker.public), 83 "If your facade does not explicitly expose an API that a backend " + 84 "must depend on, you can just directly depend on the build arg " + 85 "that the `backend` template argument would have been set to.") 86 87 _facade_name = "$target_name.facade" 88 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 96 _facade_vars = [ 97 # allow_circular_includes_from should very rarely be used, but when it is, 98 # it only applies to headers, so should be in the .facade target. 99 "allow_circular_includes_from", 100 "public_configs", 101 "public_deps", 102 "public", 103 ] 104 pw_source_set(_facade_name) { 105 forward_variables_from(invoker, _facade_vars, [ "require_link_deps" ]) 106 } 107 108 if (invoker.backend == "") { 109 # Try to guess the name of the facade's backend variable. 110 _dir = get_path_info(get_label_info(":$target_name", "dir"), "name") 111 if (target_name == _dir) { 112 _varname = target_name + "_BACKEND" 113 } else { 114 # There is no way to capitalize this string in GN, so use <FACADE_NAME> 115 # instead of the lowercase target name. 116 _varname = _dir + "_<FACADE_NAME>_BACKEND" 117 } 118 119 # If backend is not set to anything, create a script that emits an error. 120 # This will be added as a data dependency to the actual target, so that 121 # attempting to build the facade without a backend fails with a relevant 122 # error message. 123 pw_error(target_name + ".NO_BACKEND_SET") { 124 _label = get_label_info(":${invoker.target_name}", "label_no_toolchain") 125 message_lines = [ 126 "Attempted to build the $_label facade with no backend.", 127 "", 128 "If you are using this facade, ensure you have configured a backend ", 129 "properly. The build arg for the facade must be set to a valid ", 130 "backend in the toolchain. For example, you may need to add a line ", 131 "like the following to the toolchain's .gni file:", 132 "", 133 " $_varname = \"//path/to/the:backend\"", 134 "", 135 "If you are NOT using this facade, this error may have been triggered ", 136 "by trying to build all targets.", 137 ] 138 } 139 } 140 141 # Create a target that defines the main facade library. Always emit this 142 # target, even if the backend isn't defined, so that the dependency graph is 143 # correctly expressed for gn check. 144 pw_source_set(target_name) { 145 # The main library contains everything else specified in the template. 146 _ignore_vars = _facade_vars + [ 147 "backend", 148 "require_link_deps", 149 ] 150 forward_variables_from(invoker, "*", _ignore_vars) 151 152 public_deps = [ ":$_facade_name" ] 153 154 # If the backend is set, inject it as a dependency. 155 if (invoker.backend != "") { 156 public_deps += [ invoker.backend ] 157 } else { 158 # If the backend is not set, depend on the *.NO_BACKEND_SET target. 159 public_deps += [ ":$target_name.NO_BACKEND_SET" ] 160 } 161 } 162 163 if (defined(invoker.require_link_deps) && invoker.backend != "") { 164 # Check that the specified labels are listed in pw_build_LINK_DEPS. This 165 # ensures these dependencies are available during linking, even if nothing 166 # else in the build depends on them. 167 foreach(label, invoker.require_link_deps) { 168 _required_dep = get_label_info(label, "label_no_toolchain") 169 _dep_is_in_link_dependencies = false 170 171 foreach(link_dep, pw_build_LINK_DEPS) { 172 if (get_label_info(link_dep, "label_no_toolchain") == _required_dep) { 173 _dep_is_in_link_dependencies = true 174 } 175 } 176 177 _facade_name = get_label_info(":$target_name", "label_no_toolchain") 178 assert(_dep_is_in_link_dependencies, 179 "$_required_dep must be listed in the pw_build_LINK_DEPS build " + 180 "arg when the $_facade_name facade is in use. Please update " + 181 "your toolchain configuration.") 182 } 183 } else { 184 not_needed(invoker, [ "require_link_deps" ]) 185 } 186} 187