aboutsummaryrefslogtreecommitdiff
path: root/pw_build/facade.gni
blob: a823360896890c96e8c10e726129410d9b4f403d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# 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" ])
  }
}