# 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): 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] # 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( [ "@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( 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 = """$(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"], )