• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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