sandboxed-api/sandboxed_api/bazel/sapi.bzl
Christian Blichmann 92a8247777 Bazel: Add full support for virtual includes
This change adds support for using the `includes`, `include_prefix` and
`strip_include_prefix` attributes of the `cc_library()` rule. Without it,
the libtooling based header generator will not be able to find all
necessary includes as it is much stricter than the current libclang based
one in that regard.

PiperOrigin-RevId: 491574088
Change-Id: Icb9f7d2719472ee1afa5df85b185c527a3c64994
2022-11-29 02:17:48 -08:00

396 lines
13 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.
"""Starlark rules for projects using Sandboxed API."""
load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts")
load("//sandboxed_api/bazel:embed_data.bzl", "sapi_cc_embed_data")
load(
"//sandboxed_api/bazel:proto.bzl",
_sapi_proto_library = "sapi_proto_library",
)
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain")
# Reexport symbols
sapi_proto_library = _sapi_proto_library
# 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 make_exec_label(label):
return attr.label(
default = label,
cfg = "exec",
allow_files = True,
executable = True,
)
# buildifier: disable=function-docstring
def select_generator(ctx):
if ctx.attr.generator_version == 1:
return ctx.executable._generator_v1
return ctx.executable._generator_v2
def sort_deps(deps):
"""Sorts a list of dependencies.
This does not convert absolute references targeting the current package
into relative ones.
Args:
deps: List of labels to be sorted
Returns:
A sorted list of dependencies, with local deps (starting with ":") first.
"""
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 cc_library_virtual_includes(target):
"""Checks a target for virtual includes.
Those can be created by the deprecated `cc_inc_library` rule, or by using
a combination of `cc_library()`s `includes`, `include_prefix` and
`strip_include_prefix` attributes.
Args:
target: The Target to analyze
Returns:
A depset with include paths generated by cc_inc_library targets.
"""
cc_ctx = target[CcInfo].compilation_context
includes = []
for f in cc_ctx.headers.to_list():
p = f.path
if not p.startswith("blaze-out") and not p.startswith("bazel-out"):
continue
for path_marker in ["/_virtual_includes/", "/_/"]:
i = p.find(path_marker)
if i == -1:
continue
includes.append(p[:i] + path_marker +
p[i + len(path_marker):].split("/", 1)[0])
return depset(includes)
def _sapi_interface_impl(ctx):
cpp_toolchain = find_cpp_toolchain(ctx)
generator = select_generator(ctx)
use_clang_generator = ctx.attr.generator_version == 2
# 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.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()
# Gather direct include paths as well as virtual ones
quote_includes = (cc_ctx.quote_includes.to_list() +
cc_library_virtual_includes(ctx.attr.lib).to_list())
if use_clang_generator:
input_files += cpp_toolchain.all_files.to_list()
# TODO(cblichmann): Get language standard from the toolchain
extra_flags.append("--extra-arg=-std=c++17")
# Disable warnings in parsed code
extra_flags.append("--extra-arg=-Wno-everything")
extra_flags += ["--extra-arg=-isystem{}".format(d) for d in cpp_toolchain.built_in_include_directories]
extra_flags += ["--extra-arg=-D{}".format(d) for d in cc_ctx.defines.to_list()]
extra_flags += ["--extra-arg=-isystem{}".format(i) for i in cc_ctx.system_includes.to_list()]
extra_flags += ["--extra-arg=-iquote{}".format(i) for i in quote_includes]
else:
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 f in ctx.files.input_files:
input_files.append(f)
input_files_paths.append(f.path)
else:
# Try to find files automatically
for h in cc_ctx.direct_headers:
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)
if use_clang_generator:
args += extra_flags + input_files_paths
else:
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,
mnemonic = "SapiInterfaceGen",
progress_message = progress_msg,
executable = 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 = [],
),
"input_files": attr.label_list(
providers = [CcInfo],
allow_files = True,
),
"lib": attr.label(
providers = [CcInfo],
mandatory = True,
),
"lib_name": attr.string(mandatory = True),
"namespace": attr.string(),
"limit_scan_depth": attr.bool(default = False),
"api_version": attr.int(
default = 1,
values = [1], # Only a single version is defined right now
),
"generator_version": attr.int(
default = 1,
values = [1, 2],
),
"_generator_v1": make_exec_label(
"//sandboxed_api/tools/generator2:sapi_generator",
),
"_generator_v2": make_exec_label(
# TODO(cblichmann): Add prebuilt version of Clang based generator
"//sandboxed_api/tools/clang_generator:generator_tool",
),
"_cc_toolchain": attr.label(
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
),
},
output_to_genfiles = True,
toolchains = use_cpp_toolchain(),
)
def sapi_library(
name,
lib,
lib_name,
namespace = "",
api_version = 1,
embed = True,
add_default_deps = True,
limit_scan_depth = False,
srcs = [],
data = [],
hdrs = [],
copts = sapi_platform_copts(),
defines = [],
functions = [],
header = "",
input_files = [],
deps = [],
tags = [],
generator_version = 1,
visibility = None,
default_copts = []):
"""Provides the implementation of a Sandboxed API library.
Args:
name: Name of the sandboxed library
lib: Label of the library target to sandbox
lib_name: Name of the class which will proxy the library functions from
the functions list
malloc: Override the default dependency on malloc
namespace: A C++ namespace identifier to place the API class into
embed: Whether the SAPI library should be embedded inside the host code
add_default_deps: Add SAPI dependencies to target (deprecated)
limit_scan_depth: Limit include depth for header generator (deprecated)
api_version: Which version of the Sandboxed API to generate. Currently,
only version 1 is defined.
srcs: Any additional sources to include with the sandboxed library
data: To be used with srcs, any additional data files to make available
to the sandboxed library.
hdrs: Like srcs, any additional headers to include with the sandboxed
library
copts: Add these options to the C++ compilation command. See
cc_library.copts.
defines: List of defines to add to the compile line. See
cc_library.defines.
functions: A list for function to use from host code
header: If set, do not generate a header, but use the specified one
(deprecated).
input_files: List of source files which the SAPI interface generator
should scan for function declarations
deps: Extra dependencies to add to the SAPI library
tags: Extra tags to associate with the target
generator_version: Which version the the interface generator to use
(experimental). Version 1 uses the Python/libclang based `generator2`,
version 2 uses the newer C++ implementation that uses the full clang
compiler front-end for parsing. Both emit equivalent Sandboxed APIs.
visibility: Target visibility
default_copts: List of package level default copts, an additional
attribute since copts already has default value.
"""
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,-u," + 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 = ["//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,
data = [":" + name + ".bin"] + data,
hdrs = lib_hdrs,
copts = default_copts + copts,
defines = defines,
deps = sort_deps(
[
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"//sandboxed_api:sapi",
"//sandboxed_api/util:status",
"//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
"-Wl,-E", # symbols used in the sandboxed library, so these
] + exported_funcs, # must be both referenced, and exported
deps = [
":" + name + ".lib",
"//sandboxed_api:client",
],
copts = default_copts,
**common
)
native.cc_library(
name = name + ".lib",
deps = [lib],
alwayslink = 1, # All functions are linked into depending binaries
copts = default_copts,
**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,
api_version = api_version,
generator_version = generator_version,
limit_scan_depth = limit_scan_depth,
**common
)