xref: /aosp_15_r20/external/sandboxed-api/sandboxed_api/bazel/sapi.bzl (revision ec63e07ab9515d95e79c211197c445ef84cefa6a)
1# Copyright 2019 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Starlark rules for projects using Sandboxed API."""
16
17load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts")
18load("//sandboxed_api/bazel:embed_data.bzl", "sapi_cc_embed_data")
19load(
20    "//sandboxed_api/bazel:proto.bzl",
21    _sapi_proto_library = "sapi_proto_library",
22)
23load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain")
24
25# Reexport symbols
26sapi_proto_library = _sapi_proto_library
27
28# Helper functions
29def append_arg(arguments, name, value):
30    if value:
31        arguments.append("{}".format(name))
32        arguments.append(value)
33
34def append_all(arguments, name, values):
35    if values:
36        for v in values:
37            append_arg(arguments, name, v)
38
39def get_embed_dir():
40    return native.package_name()
41
42def make_exec_label(label):
43    return attr.label(
44        default = label,
45        cfg = "exec",
46        allow_files = True,
47        executable = True,
48    )
49
50# buildifier: disable=function-docstring
51def select_generator(ctx):
52    if ctx.attr.generator_version == 1:
53        return ctx.executable._generator_v1
54    return ctx.executable._generator_v2
55
56def sort_deps(deps):
57    """Sorts a list of dependencies.
58
59    This does not convert absolute references targeting the current package
60    into relative ones.
61
62    Args:
63      deps: List of labels to be sorted
64    Returns:
65      A sorted list of dependencies, with local deps (starting with ":") first.
66    """
67
68    deps = depset(deps).to_list()
69    colon_deps = [x for x in deps if x.startswith(":")]
70    other_deps = [x for x in deps if not x.startswith(":")]
71    return sorted(colon_deps) + sorted(other_deps)
72
73def cc_library_virtual_includes(target):
74    """Checks a target for virtual includes.
75
76    Those can be created by the deprecated `cc_inc_library` rule, or by using
77    a combination of `cc_library()`s `includes`, `include_prefix` and
78    `strip_include_prefix` attributes.
79
80    Args:
81      target: The Target to analyze
82    Returns:
83      A depset with include paths generated by cc_inc_library targets.
84    """
85    cc_ctx = target[CcInfo].compilation_context
86
87    includes = []
88    for f in cc_ctx.headers.to_list():
89        p = f.path
90        if not p.startswith("blaze-out") and not p.startswith("bazel-out"):
91            continue
92        for path_marker in ["/_virtual_includes/", "/_/"]:
93            i = p.find(path_marker)
94            if i == -1:
95                continue
96            includes.append(p[:i] + path_marker +
97                            p[i + len(path_marker):].split("/", 1)[0])
98
99    return depset(includes)
100
101def _sapi_interface_impl(ctx):
102    cpp_toolchain = find_cpp_toolchain(ctx)
103    generator = select_generator(ctx)
104    use_clang_generator = ctx.attr.generator_version == 2
105
106    # TODO(szwl): warn if input_files is not set and we didn't find anything
107    input_files_paths = []
108    input_files = []
109
110    args = []
111    append_arg(args, "--sapi_name", ctx.attr.lib_name)
112    append_arg(args, "--sapi_out", ctx.outputs.out.path)
113    append_arg(args, "--sapi_embed_dir", ctx.attr.embed_dir)
114    append_arg(args, "--sapi_embed_name", ctx.attr.embed_name)
115    append_arg(args, "--sapi_functions", ",".join(ctx.attr.functions))
116    append_arg(args, "--sapi_ns", ctx.attr.namespace)
117
118    if ctx.attr.limit_scan_depth:
119        args.append("--sapi_limit_scan_depth")
120
121    # Parse provided files.
122
123    # The parser doesn't need the entire set of transitive headers
124    # here, just the top-level cc_library headers.
125    #
126    # Allow all headers through that contain the dependency's
127    # package path. Including extra headers is harmless except that
128    # we may hit Bazel's file-count limit, so be conservative and
129    # pass a lot through that we don't strictly need.
130    #
131    extra_flags = []
132    cc_ctx = ctx.attr.lib[CcInfo].compilation_context
133
134    # Append all headers as dependencies
135    input_files += cc_ctx.headers.to_list()
136
137    # Gather direct include paths as well as virtual ones
138    quote_includes = (cc_ctx.quote_includes.to_list() +
139                      cc_library_virtual_includes(ctx.attr.lib).to_list())
140
141    if use_clang_generator:
142        input_files += cpp_toolchain.all_files.to_list()
143
144        # TODO(cblichmann): Get language standard from the toolchain
145        extra_flags.append("--extra-arg=-std=c++17")
146
147        # Disable warnings in parsed code
148        extra_flags.append("--extra-arg=-Wno-everything")
149        extra_flags += ["--extra-arg=-D{}".format(d) for d in cc_ctx.defines.to_list()]
150        extra_flags += ["--extra-arg=-isystem{}".format(i) for i in cc_ctx.system_includes.to_list()]
151        extra_flags += ["--extra-arg=-iquote{}".format(i) for i in quote_includes]
152        extra_flags += ["--extra-arg=-isystem{}".format(d) for d in cpp_toolchain.built_in_include_directories]
153    else:
154        append_all(extra_flags, "-D", cc_ctx.defines.to_list())
155        append_all(extra_flags, "-isystem", cc_ctx.system_includes.to_list())
156        append_all(extra_flags, "-iquote", quote_includes)
157
158    if ctx.attr.input_files:
159        for f in ctx.files.input_files:
160            input_files.append(f)
161            input_files_paths.append(f.path)
162    else:
163        # Try to find files automatically
164        for h in cc_ctx.direct_headers:
165            if h.extension != "h" or "/PROTECTED/" in h.path:
166                continue
167
168            # Include only headers coming from the target
169            # not ones that it depends on by comparing the label packages.
170            if (h.owner.package == ctx.attr.lib.label.package):
171                input_files_paths.append(h.path)
172
173    if use_clang_generator:
174        args += extra_flags + input_files_paths
175    else:
176        append_arg(args, "--sapi_in", ",".join(input_files_paths))
177        args += ["--"] + extra_flags
178
179    progress_msg = ("Generating {} from {} header files." +
180                    "").format(ctx.outputs.out.short_path, len(input_files_paths))
181    ctx.actions.run(
182        inputs = input_files,
183        outputs = [ctx.outputs.out],
184        arguments = args,
185        mnemonic = "SapiInterfaceGen",
186        progress_message = progress_msg,
187        executable = generator,
188    )
189
190# Build rule that generates SAPI interface.
191sapi_interface = rule(
192    implementation = _sapi_interface_impl,
193    attrs = {
194        "out": attr.output(mandatory = True),
195        "embed_dir": attr.string(),
196        "embed_name": attr.string(),
197        "functions": attr.string_list(
198            allow_empty = True,
199            default = [],
200        ),
201        "input_files": attr.label_list(
202            providers = [CcInfo],
203            allow_files = True,
204        ),
205        "lib": attr.label(
206            providers = [CcInfo],
207            mandatory = True,
208        ),
209        "lib_name": attr.string(mandatory = True),
210        "namespace": attr.string(),
211        "limit_scan_depth": attr.bool(default = False),
212        "api_version": attr.int(
213            default = 1,
214            values = [1],  # Only a single version is defined right now
215        ),
216        "generator_version": attr.int(
217            default = 1,
218            values = [1, 2],
219        ),
220        "_generator_v1": make_exec_label(
221            "//sandboxed_api/tools/generator2:sapi_generator",
222        ),
223        "_generator_v2": make_exec_label(
224            # TODO(cblichmann): Add prebuilt version of Clang based generator
225            "//sandboxed_api/tools/clang_generator:generator_tool",
226        ),
227        "_cc_toolchain": attr.label(
228            default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
229        ),
230    },
231    output_to_genfiles = True,
232    toolchains = use_cpp_toolchain(),
233)
234
235def sapi_library(
236        name,
237        lib,
238        lib_name,
239        malloc = "@bazel_tools//tools/cpp:malloc",
240        namespace = "",
241        api_version = 1,
242        embed = True,
243        add_default_deps = True,
244        limit_scan_depth = False,
245        srcs = [],
246        data = [],
247        hdrs = [],
248        copts = sapi_platform_copts(),
249        defines = [],
250        functions = [],
251        header = "",
252        input_files = [],
253        deps = [],
254        tags = [],
255        generator_version = 1,
256        visibility = None,
257        compatible_with = None,
258        default_copts = []):
259    """Provides the implementation of a Sandboxed API library.
260
261    Args:
262      name: Name of the sandboxed library
263      lib: Label of the library target to sandbox
264      lib_name: Name of the class which will proxy the library functions from
265        the functions list
266      malloc: Override the default dependency on malloc
267      namespace: A C++ namespace identifier to place the API class into
268      embed: Whether the SAPI library should be embedded inside the host code
269      add_default_deps: Add SAPI dependencies to target (deprecated)
270      limit_scan_depth: Limit include depth for header generator (deprecated)
271      api_version: Which version of the Sandboxed API to generate. Currently,
272        only version 1 is defined.
273      srcs: Any additional sources to include with the sandboxed library
274      data: To be used with srcs, any additional data files to make available
275        to the sandboxed library.
276      hdrs: Like srcs, any additional headers to include with the sandboxed
277        library
278      copts: Add these options to the C++ compilation command. See
279        cc_library.copts.
280      defines: List of defines to add to the compile line. See
281        cc_library.defines.
282      functions: A list for function to use from host code
283      header: If set, do not generate a header, but use the specified one
284        (deprecated).
285      input_files: List of source files which the SAPI interface generator
286        should scan for function declarations
287      deps: Extra dependencies to add to the SAPI library
288      tags: Extra tags to associate with the target
289      generator_version: Which version the the interface generator to use
290        (experimental). Version 1 uses the Python/libclang based `generator2`,
291        version 2 uses the newer C++ implementation that uses the full clang
292        compiler front-end for parsing. Both emit equivalent Sandboxed APIs.
293      visibility: Target visibility
294      compatible_with: The list of environments this target can be built for,
295        in addition to default-supported environments.
296      default_copts: List of package level default copts, an additional
297        attribute since copts already has default value.
298    """
299
300    common = {
301        "tags": tags,
302    }
303    if visibility:
304        common["visibility"] = visibility
305
306    if compatible_with != None:
307        common["compatible_with"] = compatible_with
308
309    generated_header = name + ".sapi.h"
310
311    # Reference (pull into the archive) required functions only. If the functions'
312    # array is empty, pull in the whole archive (may not compile with MSAN).
313    exported_funcs = ["-Wl,-u," + s for s in functions]
314    if (not exported_funcs):
315        exported_funcs = [
316            "-Wl,--whole-archive",
317            "-Wl,--allow-multiple-definition",
318        ]
319
320    lib_hdrs = hdrs or []
321    if header:
322        lib_hdrs += [header]
323    else:
324        lib_hdrs += [generated_header]
325
326    default_deps = ["//sandboxed_api/sandbox2"]
327
328    # Library that contains generated interface and sandboxed binary as a data
329    # dependency. Add this as a dependency instead of original library.
330    native.cc_library(
331        name = name,
332        srcs = srcs,
333        data = [":" + name + ".bin"] + data,
334        hdrs = lib_hdrs,
335        copts = default_copts + copts,
336        defines = defines,
337        deps = sort_deps(
338            [
339                "@com_google_absl//absl/base:core_headers",
340                "@com_google_absl//absl/status",
341                "@com_google_absl//absl/status:statusor",
342                "//sandboxed_api:sapi",
343                "//sandboxed_api/util:status",
344                "//sandboxed_api:vars",
345            ] + deps +
346            ([":" + name + "_embed"] if embed else []) +
347            (default_deps if add_default_deps else []),
348        ),
349        **common
350    )
351
352    native.cc_binary(
353        name = name + ".bin",
354        linkopts = [
355            "-ldl",  # For dlopen(), dlsym()
356            # The sandboxing client must have access to all
357            "-Wl,-E",  # symbols used in the sandboxed library, so these
358        ] + exported_funcs,  # must be both referenced, and exported
359        malloc = malloc,
360        deps = [
361            ":" + name + ".lib",
362            "//sandboxed_api:client",
363        ],
364        copts = default_copts,
365        **common
366    )
367
368    native.cc_library(
369        name = name + ".lib",
370        deps = [lib],
371        alwayslink = 1,  # All functions are linked into depending binaries
372        copts = default_copts,
373        **common
374    )
375
376    embed_name = ""
377    embed_dir = ""
378    if embed:
379        embed_name = name
380
381        sapi_cc_embed_data(
382            name = name + "_embed",
383            srcs = [name + ".bin"],
384            namespace = namespace,
385            **common
386        )
387        embed_dir = get_embed_dir()
388
389    sapi_interface(
390        name = name + ".interface",
391        lib_name = lib_name,
392        lib = lib,
393        functions = functions,
394        input_files = input_files,
395        out = generated_header,
396        embed_name = embed_name,
397        embed_dir = embed_dir,
398        namespace = namespace,
399        api_version = api_version,
400        generator_version = generator_version,
401        limit_scan_depth = limit_scan_depth,
402        **common
403    )
404