• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (C) 2022 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://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,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Bazel rules for exporting API contributions of CC libraries"""
16
17load("@bazel_skylib//lib:paths.bzl", "paths")
18load("@bazel_skylib//lib:sets.bzl", "sets")
19load("//build/bazel/rules/cc:cc_constants.bzl", "constants")
20load(":api_surface.bzl", "MODULE_LIB_API", "PUBLIC_API", "VENDOR_API")
21
22"""A Bazel provider that encapsulates the headers presented to an API surface"""
23CcApiHeaderInfo = provider(
24    fields = {
25        "name": "Name identifying the header files",
26        "root": "Directory containing the header files, relative to workspace root. This will become the -I parameter in consuming API domains. This defaults to the current Bazel package",
27        "headers": "The header (.h) files presented by the library to an API surface",
28        "system": "bool, This will determine whether the include path will be -I or -isystem",
29        "arch": "Target arch of devices that use these header files to compile. The default is empty, which means that it is arch-agnostic",
30    },
31)
32
33def _cc_api_header_impl(ctx):
34    """Implementation for the cc_api_headers rule.
35    This rule does not have any build actions, but returns a `CcApiHeaderInfo` provider object"""
36    headers_filepath = [header.path for header in ctx.files.hdrs]
37    root = paths.dirname(ctx.build_file_path)
38    if ctx.attr.include_dir:
39        root = paths.join(root, ctx.attr.include_dir)
40    info = CcApiHeaderInfo(
41        name = ctx.label.name,
42        root = root,
43        headers = headers_filepath,
44        system = ctx.attr.system,
45        arch = ctx.attr.arch,
46    )
47
48    # TODO: Use depset for CcApiHeaderInfoList to optimize merges in `_cc_api_contribution_impl`
49    return [
50        info,
51        CcApiHeaderInfoList(
52            headers_list = [info],
53        ),
54    ]
55
56"""A bazel rule that encapsulates the header contributions of a CC library to an API surface
57This rule does not contain the API symbolfile (.map.txt). The API symbolfile is part of the cc_api_contribution rule
58This layering is necessary since the symbols present in a single .map.txt file can be defined in different include directories
59e.g.
60├── Android.bp
61├── BUILD
62├── include <-- cc_api_headers
63├── include_other <-- cc_api_headers
64├── libfoo.map.txt
65"""
66cc_api_headers = rule(
67    implementation = _cc_api_header_impl,
68    attrs = {
69        "include_dir": attr.string(
70            mandatory = False,
71            doc = "Directory containing the header files, relative to the Bazel package. This relative path will be joined with the Bazel package path to become the -I parameter in the consuming API domain",
72        ),
73        "hdrs": attr.label_list(
74            mandatory = True,
75            allow_files = constants.hdr_dot_exts,
76            doc = "List of .h files presented to the API surface. Glob patterns are allowed",
77        ),
78        "system": attr.bool(
79            default = False,
80            doc = "Boolean to indicate whether these are system headers",
81        ),
82        "arch": attr.string(
83            mandatory = False,
84            values = ["arm", "arm64", "x86", "x86_64"],
85            doc = "Arch of the target device. The default is empty, which means that the headers are arch-agnostic",
86        ),
87    },
88)
89
90"""List container for multiple CcApiHeaderInfo providers"""
91CcApiHeaderInfoList = provider(
92    fields = {
93        "headers_list": "List of CcApiHeaderInfo providers presented by a target",
94    },
95)
96
97def _cc_api_library_headers_impl(ctx):
98    hdrs_info = []
99    for hdr in ctx.attr.hdrs:
100        for hdr_info in hdr[CcApiHeaderInfoList].headers_list:
101            hdrs_info.append(hdr_info)
102
103    return [
104        CcApiHeaderInfoList(
105            headers_list = hdrs_info,
106        ),
107    ]
108
109_cc_api_library_headers = rule(
110    implementation = _cc_api_library_headers_impl,
111    attrs = {
112        "hdrs": attr.label_list(
113            mandatory = True,
114            providers = [CcApiHeaderInfoList],
115        ),
116    },
117)
118
119# Internal header library targets created by cc_api_library_headers macro
120# Bazel does not allow target name to end with `/`
121def _header_target_name(name, include_dir):
122    return name + "_" + paths.normalize(include_dir)
123
124def cc_api_library_headers(
125        name,
126        hdrs = [],  # @unused
127        export_includes = [],
128        export_system_includes = [],
129        arch = None,
130        deps = [],
131        **kwargs):
132    header_deps = []
133    for include in export_includes:
134        _name = _header_target_name(name, include)
135
136        # export_include = "." causes the following error in glob
137        # Error in glob: segment '.' not permitted
138        # Normalize path before globbing
139        fragments = [include, "**/*.h"]
140        normpath = paths.normalize(paths.join(*fragments))
141
142        cc_api_headers(
143            name = _name,
144            include_dir = include,
145            hdrs = native.glob([normpath]),
146            system = False,
147            arch = arch,
148        )
149        header_deps.append(_name)
150
151    for system_include in export_system_includes:
152        _name = _header_target_name(name, system_include)
153        cc_api_headers(
154            name = _name,
155            include_dir = system_include,
156            hdrs = native.glob([paths.join(system_include, "**/*.h")]),
157            system = True,
158            arch = arch,
159        )
160        header_deps.append(_name)
161
162    # deps should be exported
163    header_deps.extend(deps)
164
165    _cc_api_library_headers(
166        name = name,
167        hdrs = header_deps,
168        **kwargs
169    )
170
171"""A Bazel provider that encapsulates the contributions of a CC library to an API surface"""
172CcApiContributionInfo = provider(
173    fields = {
174        "name": "Name of the cc library",
175        "api": "Path of map.txt describing the stable APIs of the library. Path is relative to workspace root",
176        "headers": "metadata of the header files of the cc library",
177        "api_surfaces": "API surface(s) this library contributes to",
178    },
179)
180
181VALID_CC_API_SURFACES = [
182    PUBLIC_API,
183    MODULE_LIB_API,  # API surface provided by platform and mainline modules to other mainline modules
184    VENDOR_API,
185]
186
187def _validate_api_surfaces(api_surfaces):
188    for api_surface in api_surfaces:
189        if api_surface not in VALID_CC_API_SURFACES:
190            fail(api_surface, " is not a valid API surface. Acceptable values: ", VALID_CC_API_SURFACES)
191
192def _cc_api_contribution_impl(ctx):
193    """Implemenation for the cc_api_contribution rule
194    This rule does not have any build actions, but returns a `CcApiContributionInfo` provider object"""
195    api_filepath = ctx.file.api.path
196    hdrs_info = sets.make()
197    for hdr in ctx.attr.hdrs:
198        for hdr_info in hdr[CcApiHeaderInfoList].headers_list:
199            sets.insert(hdrs_info, hdr_info)
200
201    name = ctx.attr.library_name or ctx.label.name
202    _validate_api_surfaces(ctx.attr.api_surfaces)
203
204    return [
205        CcApiContributionInfo(
206            name = name,
207            api = api_filepath,
208            headers = sets.to_list(hdrs_info),
209            api_surfaces = ctx.attr.api_surfaces,
210        ),
211    ]
212
213cc_api_contribution = rule(
214    implementation = _cc_api_contribution_impl,
215    attrs = {
216        "library_name": attr.string(
217            mandatory = False,
218            doc = "Name of the library. This can be different from `name` to prevent name collision with the implementation of the library in the same Bazel package. Defaults to label.name",
219        ),
220        "api": attr.label(
221            mandatory = True,
222            allow_single_file = [".map.txt", ".map"],
223            doc = ".map.txt file of the library",
224        ),
225        "hdrs": attr.label_list(
226            mandatory = False,
227            providers = [CcApiHeaderInfoList],
228            doc = "Header contributions of the cc library. This should return a `CcApiHeaderInfo` provider",
229        ),
230        "api_surfaces": attr.string_list(
231            doc = "API surface(s) this library contributes to. See VALID_CC_API_SURFACES in cc_api_contribution.bzl for valid values for API surfaces",
232            default = ["publicapi"],
233        ),
234    },
235)
236