xref: /aosp_15_r20/external/pigweed/pw_build/facade.gni (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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