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