| # 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=-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] |
| extra_flags += ["--extra-arg=-isystem{}".format(d) for d in cpp_toolchain.built_in_include_directories] |
| 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, |
| malloc = "@bazel_tools//tools/cpp:malloc", |
| 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, |
| compatible_with = 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 |
| compatible_with: The list of environments this target can be built for, |
| in addition to default-supported environments. |
| 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 |
| |
| if compatible_with != None: |
| common["compatible_with"] = compatible_with |
| |
| 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 |
| malloc = malloc, |
| 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 |
| ) |