sandboxed-api/sandboxed_api/bazel/sapi.bzl
Christian Blichmann 60fcc5b63e Limit the number of includes fed into the header generator
Use [`direct_headers`](https://bazel.build/rules/lib/CompilationContext#direct_headers)
from the Bazel/Blaze compilation context instead of _all_ transitive headers.

For the clang based generator, this means we don't try to parse
`textual_headers`, which will fail (they are by definition not
stand-alone, after all).

PiperOrigin-RevId: 431899423
Change-Id: I7a9dfa0dd93eba14b506b0e7ca6db3ed59b55dd6
2022-03-02 03:41:41 -08:00

261 lines
8.4 KiB
Python

# Copyright 2019 Google LLC
#
# 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.
"""Macros that simplifies header and library generation for Sandboxed API."""
load("//sandboxed_api/bazel:embed_data.bzl", "sapi_cc_embed_data")
# Helper functions
def append_arg(arguments, name, value):
if value:
arguments.append("{}".format(name))
arguments.append(value)
def append_all(arguments, name, values):
if values:
for v in values:
append_arg(arguments, name, v)
def get_embed_dir():
return native.package_name()
def sort_deps(deps):
deps = depset(deps).to_list()
colon_deps = [x for x in deps if x.startswith(":")]
other_deps = [x for x in deps if not x.startswith(":")]
return sorted(colon_deps) + sorted(other_deps)
def sapi_interface_impl(ctx):
"""Implementation of build rule that generates SAPI interface."""
# TODO(szwl): warn if input_files is not set and we didn't find anything
input_files_paths = []
input_files = []
args = []
append_arg(args, "--sapi_name", ctx.attr.lib_name)
append_arg(args, "--sapi_out", ctx.outputs.out.path)
append_arg(args, "--sapi_embed_dir", ctx.attr.embed_dir)
append_arg(args, "--sapi_embed_name", ctx.attr.embed_name)
append_arg(args, "--sapi_functions", ",".join(ctx.attr.functions))
append_arg(args, "--sapi_ns", ctx.attr.namespace)
if ctx.attr.isystem:
isystem = ctx.attr.isystem.files.to_list()[0]
append_arg(args, "--sapi_isystem", isystem.path)
input_files += [isystem]
if ctx.attr.limit_scan_depth:
args.append("--sapi_limit_scan_depth")
# Parse provided files.
# The parser doesn't need the entire set of transitive headers
# here, just the top-level cc_library headers.
#
# Allow all headers through that contain the dependency's
# package path. Including extra headers is harmless except that
# we may hit Bazel's file-count limit, so be conservative and
# pass a lot through that we don't strictly need.
extra_flags = []
cc_ctx = ctx.attr.lib[CcInfo].compilation_context
# Append all headers as dependencies
input_files += cc_ctx.headers.to_list()
quote_includes = cc_ctx.quote_includes.to_list()
append_all(extra_flags, "-D", cc_ctx.defines.to_list())
append_all(extra_flags, "-isystem", cc_ctx.system_includes.to_list())
append_all(extra_flags, "-iquote", quote_includes)
if ctx.attr.input_files:
for target in ctx.attr.input_files:
if target.files:
for f in target.files.to_list():
input_files_paths.append(f.path)
input_files.append(f)
else:
# Try to find files automatically
for h in cc_ctx.direct_headers:
# Collect all headers as dependency.
if h.extension != "h" or "/PROTECTED/" in h.path:
continue
# Include only headers coming from the target
# not ones that it depends on by comparing the label packages.
if (h.owner.package == ctx.attr.lib.label.package):
input_files_paths.append(h.path)
append_arg(args, "--sapi_in", ",".join(input_files_paths))
args += ["--"] + extra_flags
progress_msg = ("Generating {} from {} header files." +
"").format(ctx.outputs.out.short_path, len(input_files_paths))
ctx.actions.run(
inputs = input_files,
outputs = [ctx.outputs.out],
arguments = args,
progress_message = progress_msg,
executable = ctx.executable._sapi_generator,
)
# Build rule that generates SAPI interface.
sapi_interface = rule(
implementation = sapi_interface_impl,
attrs = {
"out": attr.output(mandatory = True),
"embed_dir": attr.string(),
"embed_name": attr.string(),
"functions": attr.string_list(allow_empty = True, default = []),
"include_prefix": attr.string(),
"input_files": attr.label_list(allow_files = True),
"lib": attr.label(providers = [CcInfo], mandatory = True),
"lib_name": attr.string(mandatory = True),
"namespace": attr.string(),
"isystem": attr.label(),
"limit_scan_depth": attr.bool(default = False),
"_sapi_generator": attr.label(
executable = True,
cfg = "host",
allow_files = True,
default = Label("@com_google_sandboxed_api//sandboxed_api/" +
"tools/generator2:sapi_generator"),
),
},
output_to_genfiles = True,
)
def sapi_library(
name,
lib,
lib_name,
namespace = "",
embed = True,
add_default_deps = True,
srcs = [],
hdrs = [],
functions = [],
header = "",
input_files = [],
deps = [],
tags = [],
visibility = None):
"""Provides the implementation of a Sandboxed API library."""
rprefix = "@com_google_sandboxed_api"
common = {
"tags": tags,
}
if visibility:
common["visibility"] = visibility
generated_header = name + ".sapi.h"
# Reference (pull into the archive) required functions only. If the functions'
# array is empty, pull in the whole archive (may not compile with MSAN).
exported_funcs = ["-Wl,--export-dynamic-symbol," + s for s in functions]
if (not exported_funcs):
exported_funcs = [
"-Wl,--whole-archive",
"-Wl,--allow-multiple-definition",
]
lib_hdrs = hdrs or []
if header:
lib_hdrs += [header]
else:
lib_hdrs += [generated_header]
default_deps = [rprefix + "//sandboxed_api/sandbox2"]
# Library that contains generated interface and sandboxed binary as a data
# dependency. Add this as a dependency instead of original library.
native.cc_library(
name = name,
srcs = srcs,
hdrs = lib_hdrs,
data = [":" + name + ".bin"],
deps = sort_deps(
[
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
rprefix + "//sandboxed_api:sapi",
rprefix + "//sandboxed_api/util:status",
rprefix + "//sandboxed_api:vars",
] + deps +
([":" + name + "_embed"] if embed else []) +
(default_deps if add_default_deps else []),
),
**common
)
native.cc_binary(
name = name + ".bin",
linkopts = [
"-ldl", # For dlopen(), dlsym()
# The sandboxing client must have access to all symbols used in
# the sandboxed library, so these must be both referenced, and
# exported
"-Wl,-E",
] + exported_funcs,
deps = [
":" + name + ".lib",
rprefix + "//sandboxed_api:client",
],
**common
)
native.cc_library(
name = name + ".lib",
deps = [lib],
alwayslink = 1, # All functions are linked into depending binaries
**common
)
embed_name = ""
embed_dir = ""
if embed:
embed_name = name
sapi_cc_embed_data(
name = name + "_embed",
srcs = [name + ".bin"],
namespace = namespace,
**common
)
embed_dir = get_embed_dir()
sapi_interface(
name = name + ".interface",
lib_name = lib_name,
lib = lib,
functions = functions,
input_files = input_files,
out = generated_header,
embed_name = embed_name,
embed_dir = embed_dir,
namespace = namespace,
isystem = ":" + name + ".isystem",
**common
)
native.genrule(
name = name + ".isystem",
outs = [name + ".isystem.list"],
cmd = """$(CC) -E -x c++ -v /dev/null 2>&1 |
awk '/> search starts here:/{f=1;next}/^End of search/{f=0}f{print $$1}' > $@
""",
toolchains = ["@bazel_tools//tools/cpp:current_cc_toolchain"],
)