sandboxed-api/sandboxed_api/bazel/sapi.bzl
Christian Blichmann 6d97220e5c
Use common repository prefix everywhere
Build macros in earlier versions of Bazel (pre-1.0) needed to specify
targets in a different format, depending from where they were
included/expanded at build time.

For example, a `sapi_library()` macro invocation in one of the directories
under `examples`, always needed to depend on the SAPI main library as
`//sandboxed_api:sapi`. When using SAPI from another Bazel project, the
same macro would have needed to depend on
`@com_google_sandboxed_api//sandboxed_api:sapi`. The `sapi_library()`
macro was thus checking the repository name and conditionally changed
the dependencies. This approach is brittle and as of Bazel 3.1.0 no
longer works.

This CL simple removes the conditional prefix and unconditionally uses
`@com_google_sandboxed_api`.

Tested on Bazel 1.2.1, 2.2.0 and 3.1.0
2020-05-11 16:51:53 +02:00

263 lines
8.6 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
#
# http://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):
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]
# Parse provided files.
# The parser doesn't need the entire set of transitive headers
# here, just the top-level cc_library headers. It would be nice
# if Skylark or Bazel provided this, but it is surprisingly hard
# to get.
#
# 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 = []
if ctx.attr.lib[CcInfo]:
cc_ctx = ctx.attr.lib[CcInfo].compilation_context
# Append system headers as dependencies
input_files += cc_ctx.headers.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", cc_ctx.quote_includes.to_list())
if ctx.attr.input_files:
for h in cc_ctx.headers.to_list():
# Collect all headers as dependency in case libclang needs them.
if h.extension == "h" and "/PROTECTED/" not in h.path:
input_files.append(h)
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)
# Try to find files automatically.
else:
for h in cc_ctx.headers.to_list():
# Collect all headers as dependency in case clang needs them.
if h.extension == "h" and "/PROTECTED/" not in h.path:
input_files.append(h)
# 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
else:
# TODO(szwl): Error out if the lib has no cc.
pass
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(mandatory = True),
"lib_name": attr.string(mandatory = True),
"namespace": attr.string(),
"isystem": attr.label(),
"_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(
[
rprefix + "//sandboxed_api:sapi",
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(
srcs = [name + ".bin"],
name = name + "_embed",
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 = """echo |
$(CC) -E -x c++ - -v 2>&1 |
awk '/> search starts here:/{flag=1;next}/End of search/{flag=0}flag' > $@
""",
toolchains = ["@bazel_tools//tools/cpp:current_cc_toolchain"],
)