1# Copyright 2019 Google LLC 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# 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, 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"""Starlark rules for projects using Sandboxed API.""" 16 17load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts") 18load("//sandboxed_api/bazel:embed_data.bzl", "sapi_cc_embed_data") 19load( 20 "//sandboxed_api/bazel:proto.bzl", 21 _sapi_proto_library = "sapi_proto_library", 22) 23load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain") 24 25# Reexport symbols 26sapi_proto_library = _sapi_proto_library 27 28# Helper functions 29def append_arg(arguments, name, value): 30 if value: 31 arguments.append("{}".format(name)) 32 arguments.append(value) 33 34def append_all(arguments, name, values): 35 if values: 36 for v in values: 37 append_arg(arguments, name, v) 38 39def get_embed_dir(): 40 return native.package_name() 41 42def make_exec_label(label): 43 return attr.label( 44 default = label, 45 cfg = "exec", 46 allow_files = True, 47 executable = True, 48 ) 49 50# buildifier: disable=function-docstring 51def select_generator(ctx): 52 if ctx.attr.generator_version == 1: 53 return ctx.executable._generator_v1 54 return ctx.executable._generator_v2 55 56def sort_deps(deps): 57 """Sorts a list of dependencies. 58 59 This does not convert absolute references targeting the current package 60 into relative ones. 61 62 Args: 63 deps: List of labels to be sorted 64 Returns: 65 A sorted list of dependencies, with local deps (starting with ":") first. 66 """ 67 68 deps = depset(deps).to_list() 69 colon_deps = [x for x in deps if x.startswith(":")] 70 other_deps = [x for x in deps if not x.startswith(":")] 71 return sorted(colon_deps) + sorted(other_deps) 72 73def cc_library_virtual_includes(target): 74 """Checks a target for virtual includes. 75 76 Those can be created by the deprecated `cc_inc_library` rule, or by using 77 a combination of `cc_library()`s `includes`, `include_prefix` and 78 `strip_include_prefix` attributes. 79 80 Args: 81 target: The Target to analyze 82 Returns: 83 A depset with include paths generated by cc_inc_library targets. 84 """ 85 cc_ctx = target[CcInfo].compilation_context 86 87 includes = [] 88 for f in cc_ctx.headers.to_list(): 89 p = f.path 90 if not p.startswith("blaze-out") and not p.startswith("bazel-out"): 91 continue 92 for path_marker in ["/_virtual_includes/", "/_/"]: 93 i = p.find(path_marker) 94 if i == -1: 95 continue 96 includes.append(p[:i] + path_marker + 97 p[i + len(path_marker):].split("/", 1)[0]) 98 99 return depset(includes) 100 101def _sapi_interface_impl(ctx): 102 cpp_toolchain = find_cpp_toolchain(ctx) 103 generator = select_generator(ctx) 104 use_clang_generator = ctx.attr.generator_version == 2 105 106 # TODO(szwl): warn if input_files is not set and we didn't find anything 107 input_files_paths = [] 108 input_files = [] 109 110 args = [] 111 append_arg(args, "--sapi_name", ctx.attr.lib_name) 112 append_arg(args, "--sapi_out", ctx.outputs.out.path) 113 append_arg(args, "--sapi_embed_dir", ctx.attr.embed_dir) 114 append_arg(args, "--sapi_embed_name", ctx.attr.embed_name) 115 append_arg(args, "--sapi_functions", ",".join(ctx.attr.functions)) 116 append_arg(args, "--sapi_ns", ctx.attr.namespace) 117 118 if ctx.attr.limit_scan_depth: 119 args.append("--sapi_limit_scan_depth") 120 121 # Parse provided files. 122 123 # The parser doesn't need the entire set of transitive headers 124 # here, just the top-level cc_library headers. 125 # 126 # Allow all headers through that contain the dependency's 127 # package path. Including extra headers is harmless except that 128 # we may hit Bazel's file-count limit, so be conservative and 129 # pass a lot through that we don't strictly need. 130 # 131 extra_flags = [] 132 cc_ctx = ctx.attr.lib[CcInfo].compilation_context 133 134 # Append all headers as dependencies 135 input_files += cc_ctx.headers.to_list() 136 137 # Gather direct include paths as well as virtual ones 138 quote_includes = (cc_ctx.quote_includes.to_list() + 139 cc_library_virtual_includes(ctx.attr.lib).to_list()) 140 141 if use_clang_generator: 142 input_files += cpp_toolchain.all_files.to_list() 143 144 # TODO(cblichmann): Get language standard from the toolchain 145 extra_flags.append("--extra-arg=-std=c++17") 146 147 # Disable warnings in parsed code 148 extra_flags.append("--extra-arg=-Wno-everything") 149 extra_flags += ["--extra-arg=-D{}".format(d) for d in cc_ctx.defines.to_list()] 150 extra_flags += ["--extra-arg=-isystem{}".format(i) for i in cc_ctx.system_includes.to_list()] 151 extra_flags += ["--extra-arg=-iquote{}".format(i) for i in quote_includes] 152 extra_flags += ["--extra-arg=-isystem{}".format(d) for d in cpp_toolchain.built_in_include_directories] 153 else: 154 append_all(extra_flags, "-D", cc_ctx.defines.to_list()) 155 append_all(extra_flags, "-isystem", cc_ctx.system_includes.to_list()) 156 append_all(extra_flags, "-iquote", quote_includes) 157 158 if ctx.attr.input_files: 159 for f in ctx.files.input_files: 160 input_files.append(f) 161 input_files_paths.append(f.path) 162 else: 163 # Try to find files automatically 164 for h in cc_ctx.direct_headers: 165 if h.extension != "h" or "/PROTECTED/" in h.path: 166 continue 167 168 # Include only headers coming from the target 169 # not ones that it depends on by comparing the label packages. 170 if (h.owner.package == ctx.attr.lib.label.package): 171 input_files_paths.append(h.path) 172 173 if use_clang_generator: 174 args += extra_flags + input_files_paths 175 else: 176 append_arg(args, "--sapi_in", ",".join(input_files_paths)) 177 args += ["--"] + extra_flags 178 179 progress_msg = ("Generating {} from {} header files." + 180 "").format(ctx.outputs.out.short_path, len(input_files_paths)) 181 ctx.actions.run( 182 inputs = input_files, 183 outputs = [ctx.outputs.out], 184 arguments = args, 185 mnemonic = "SapiInterfaceGen", 186 progress_message = progress_msg, 187 executable = generator, 188 ) 189 190# Build rule that generates SAPI interface. 191sapi_interface = rule( 192 implementation = _sapi_interface_impl, 193 attrs = { 194 "out": attr.output(mandatory = True), 195 "embed_dir": attr.string(), 196 "embed_name": attr.string(), 197 "functions": attr.string_list( 198 allow_empty = True, 199 default = [], 200 ), 201 "input_files": attr.label_list( 202 providers = [CcInfo], 203 allow_files = True, 204 ), 205 "lib": attr.label( 206 providers = [CcInfo], 207 mandatory = True, 208 ), 209 "lib_name": attr.string(mandatory = True), 210 "namespace": attr.string(), 211 "limit_scan_depth": attr.bool(default = False), 212 "api_version": attr.int( 213 default = 1, 214 values = [1], # Only a single version is defined right now 215 ), 216 "generator_version": attr.int( 217 default = 1, 218 values = [1, 2], 219 ), 220 "_generator_v1": make_exec_label( 221 "//sandboxed_api/tools/generator2:sapi_generator", 222 ), 223 "_generator_v2": make_exec_label( 224 # TODO(cblichmann): Add prebuilt version of Clang based generator 225 "//sandboxed_api/tools/clang_generator:generator_tool", 226 ), 227 "_cc_toolchain": attr.label( 228 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), 229 ), 230 }, 231 output_to_genfiles = True, 232 toolchains = use_cpp_toolchain(), 233) 234 235def sapi_library( 236 name, 237 lib, 238 lib_name, 239 malloc = "@bazel_tools//tools/cpp:malloc", 240 namespace = "", 241 api_version = 1, 242 embed = True, 243 add_default_deps = True, 244 limit_scan_depth = False, 245 srcs = [], 246 data = [], 247 hdrs = [], 248 copts = sapi_platform_copts(), 249 defines = [], 250 functions = [], 251 header = "", 252 input_files = [], 253 deps = [], 254 tags = [], 255 generator_version = 1, 256 visibility = None, 257 compatible_with = None, 258 default_copts = []): 259 """Provides the implementation of a Sandboxed API library. 260 261 Args: 262 name: Name of the sandboxed library 263 lib: Label of the library target to sandbox 264 lib_name: Name of the class which will proxy the library functions from 265 the functions list 266 malloc: Override the default dependency on malloc 267 namespace: A C++ namespace identifier to place the API class into 268 embed: Whether the SAPI library should be embedded inside the host code 269 add_default_deps: Add SAPI dependencies to target (deprecated) 270 limit_scan_depth: Limit include depth for header generator (deprecated) 271 api_version: Which version of the Sandboxed API to generate. Currently, 272 only version 1 is defined. 273 srcs: Any additional sources to include with the sandboxed library 274 data: To be used with srcs, any additional data files to make available 275 to the sandboxed library. 276 hdrs: Like srcs, any additional headers to include with the sandboxed 277 library 278 copts: Add these options to the C++ compilation command. See 279 cc_library.copts. 280 defines: List of defines to add to the compile line. See 281 cc_library.defines. 282 functions: A list for function to use from host code 283 header: If set, do not generate a header, but use the specified one 284 (deprecated). 285 input_files: List of source files which the SAPI interface generator 286 should scan for function declarations 287 deps: Extra dependencies to add to the SAPI library 288 tags: Extra tags to associate with the target 289 generator_version: Which version the the interface generator to use 290 (experimental). Version 1 uses the Python/libclang based `generator2`, 291 version 2 uses the newer C++ implementation that uses the full clang 292 compiler front-end for parsing. Both emit equivalent Sandboxed APIs. 293 visibility: Target visibility 294 compatible_with: The list of environments this target can be built for, 295 in addition to default-supported environments. 296 default_copts: List of package level default copts, an additional 297 attribute since copts already has default value. 298 """ 299 300 common = { 301 "tags": tags, 302 } 303 if visibility: 304 common["visibility"] = visibility 305 306 if compatible_with != None: 307 common["compatible_with"] = compatible_with 308 309 generated_header = name + ".sapi.h" 310 311 # Reference (pull into the archive) required functions only. If the functions' 312 # array is empty, pull in the whole archive (may not compile with MSAN). 313 exported_funcs = ["-Wl,-u," + s for s in functions] 314 if (not exported_funcs): 315 exported_funcs = [ 316 "-Wl,--whole-archive", 317 "-Wl,--allow-multiple-definition", 318 ] 319 320 lib_hdrs = hdrs or [] 321 if header: 322 lib_hdrs += [header] 323 else: 324 lib_hdrs += [generated_header] 325 326 default_deps = ["//sandboxed_api/sandbox2"] 327 328 # Library that contains generated interface and sandboxed binary as a data 329 # dependency. Add this as a dependency instead of original library. 330 native.cc_library( 331 name = name, 332 srcs = srcs, 333 data = [":" + name + ".bin"] + data, 334 hdrs = lib_hdrs, 335 copts = default_copts + copts, 336 defines = defines, 337 deps = sort_deps( 338 [ 339 "@com_google_absl//absl/base:core_headers", 340 "@com_google_absl//absl/status", 341 "@com_google_absl//absl/status:statusor", 342 "//sandboxed_api:sapi", 343 "//sandboxed_api/util:status", 344 "//sandboxed_api:vars", 345 ] + deps + 346 ([":" + name + "_embed"] if embed else []) + 347 (default_deps if add_default_deps else []), 348 ), 349 **common 350 ) 351 352 native.cc_binary( 353 name = name + ".bin", 354 linkopts = [ 355 "-ldl", # For dlopen(), dlsym() 356 # The sandboxing client must have access to all 357 "-Wl,-E", # symbols used in the sandboxed library, so these 358 ] + exported_funcs, # must be both referenced, and exported 359 malloc = malloc, 360 deps = [ 361 ":" + name + ".lib", 362 "//sandboxed_api:client", 363 ], 364 copts = default_copts, 365 **common 366 ) 367 368 native.cc_library( 369 name = name + ".lib", 370 deps = [lib], 371 alwayslink = 1, # All functions are linked into depending binaries 372 copts = default_copts, 373 **common 374 ) 375 376 embed_name = "" 377 embed_dir = "" 378 if embed: 379 embed_name = name 380 381 sapi_cc_embed_data( 382 name = name + "_embed", 383 srcs = [name + ".bin"], 384 namespace = namespace, 385 **common 386 ) 387 embed_dir = get_embed_dir() 388 389 sapi_interface( 390 name = name + ".interface", 391 lib_name = lib_name, 392 lib = lib, 393 functions = functions, 394 input_files = input_files, 395 out = generated_header, 396 embed_name = embed_name, 397 embed_dir = embed_dir, 398 namespace = namespace, 399 api_version = api_version, 400 generator_version = generator_version, 401 limit_scan_depth = limit_scan_depth, 402 **common 403 ) 404