xref: /aosp_15_r20/build/bazel/rules/abi/abi_dump.bzl (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1# Copyright (C) 2022 The Android Open Source Project
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#     http://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
15load("@bazel_skylib//lib:paths.bzl", "paths")
16load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
17load(
18    "@bazel_tools//tools/build_defs/cc:action_names.bzl",
19    "CPP_COMPILE_ACTION_NAME",
20    "C_COMPILE_ACTION_NAME",
21)
22load("@soong_injection//api_levels:platform_versions.bzl", "platform_versions")
23load("//build/bazel/platforms:platform_utils.bzl", "platforms")
24load(
25    "//build/bazel/rules/cc:cc_library_common.bzl",
26    "build_compilation_flags",
27    "get_non_header_srcs",
28    "is_bionic_lib",
29    "is_bootstrap_lib",
30    "parse_apex_sdk_version",
31)
32load("//build/bazel/rules/cc:cc_library_static.bzl", "CcStaticLibraryInfo")
33
34AbiDumpInfo = provider(fields = ["dump_files"])
35AbiDiffInfo = provider(fields = ["diff_files"])
36
37_ABI_CLASS_PLATFORM = "platform"
38
39def _abi_dump_aspect_impl(target, ctx):
40    if not _abi_diff_enabled(ctx, ctx.label.name, True):
41        return [
42            AbiDumpInfo(
43                dump_files = depset(),
44            ),
45        ]
46
47    transitive_dumps = []
48    direct_dumps = []
49
50    if CcStaticLibraryInfo in target:
51        direct_dumps.extend(_create_abi_dumps(
52            ctx,
53            target,
54            ctx.rule.files.srcs_cpp,
55            ctx.rule.attr.copts_cpp,
56            CPP_COMPILE_ACTION_NAME,
57        ))
58        direct_dumps.extend(_create_abi_dumps(
59            ctx,
60            target,
61            ctx.rule.files.srcs_c,
62            ctx.rule.attr.copts_c,
63            C_COMPILE_ACTION_NAME,
64        ))
65
66        for dep in ctx.rule.attr.static_deps:
67            if AbiDumpInfo in dep:
68                transitive_dumps.append(dep[AbiDumpInfo].dump_files)
69
70    return [
71        AbiDumpInfo(
72            dump_files = depset(
73                direct_dumps,
74                transitive = transitive_dumps,
75            ),
76        ),
77    ]
78
79abi_dump_aspect = aspect(
80    implementation = _abi_dump_aspect_impl,
81    attr_aspects = ["static_deps", "whole_archive_deps"],
82    attrs = {
83        "_skip_abi_checks": attr.label(
84            default = "//build/bazel/flags/cc/abi:skip_abi_checks",
85        ),
86        # Need this in order to call _abi_diff_enabled in the aspects code.
87        "_within_apex": attr.label(
88            default = "//build/bazel/rules/apex:within_apex",
89        ),
90        "_abi_dumper": attr.label(
91            allow_files = True,
92            executable = True,
93            cfg = "exec",
94            default = Label("//prebuilts/clang-tools:linux-x86/bin/header-abi-dumper"),
95        ),
96        "_platform_utils": attr.label(default = Label("//build/bazel/platforms:platform_utils")),
97    },
98    toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
99    fragments = ["cpp"],
100    provides = [AbiDumpInfo],
101)
102
103def _create_abi_dumps(ctx, target, srcs, user_flags, action_name):
104    dumps = []
105
106    if len(srcs) == 0:
107        return dumps
108
109    compilation_context, compilation_flags = build_compilation_flags(
110        ctx,
111        ctx.rule.attr.roots + ctx.rule.attr.deps + ctx.rule.attr.includes,
112        user_flags,
113        action_name,
114    )
115    sources, headers = get_non_header_srcs(srcs)
116
117    header_inputs = (
118        headers +
119        compilation_context.headers.to_list() +
120        compilation_context.direct_headers +
121        compilation_context.direct_private_headers +
122        compilation_context.direct_public_headers +
123        compilation_context.direct_textual_headers
124    )
125    objects = []
126    linker_inputs = target[CcInfo].linking_context.linker_inputs.to_list()
127
128    # These are created in cc_library_static and there should be only one
129    # linker_inputs and one libraries
130    if CcInfo in target and len(linker_inputs) == 1 and len(linker_inputs[0].libraries) == 1:
131        objects = linker_inputs[0].libraries[0].objects
132    for file in sources:
133        output = _create_abi_dump(ctx, target, file, objects, header_inputs, compilation_flags)
134        dumps.append(output)
135
136    return dumps
137
138def _include_flag(flag):
139    return ["-I", flag]
140
141def _create_abi_dump(ctx, target, src, objects, header_inputs, compilation_flags):
142    """ Utility function to generate abi dump file."""
143
144    file = paths.join(src.dirname, target.label.name + "." + src.basename + ".sdump")
145    output = ctx.actions.declare_file(file)
146    args = ctx.actions.args()
147
148    args.add("--root-dir", ".")
149    args.add("-o", output)
150    args.add(src)
151
152    args.add_all(ctx.rule.attr.exports[0][CcInfo].compilation_context.includes.to_list(), map_each = _include_flag)
153
154    args.add("--")
155    args.add_all(compilation_flags)
156
157    # The following two args come from here:
158    # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/builder.go;l=247;drc=ba17c7243d0e297efbc6fb5385d6d5aa81db9152
159    args.add("-w")
160
161    # TODO(b/254625084): support darwin as well.
162    args.add("-isystem", "prebuilts/clang-tools/linux-x86/clang-headers")
163
164    ctx.actions.run(
165        inputs = [src] + header_inputs + objects,
166        executable = ctx.executable._abi_dumper,
167        outputs = [output],
168        arguments = [args],
169        # TODO(b/186116353): enable sandbox once the bug is fixed.
170        execution_requirements = {
171            "no-sandbox": "1",
172        },
173        mnemonic = "AbiDump",
174    )
175
176    return output
177
178def create_linked_abi_dump(ctx, dump_files):
179    """ Utility function to generate abi dump files."""
180    shared_files = ctx.attr.shared[DefaultInfo].files.to_list()
181    if len(shared_files) != 1:
182        fail("Expected only one shared library file")
183
184    file = ctx.attr.soname + ".lsdump"
185    output = ctx.actions.declare_file(file)
186    args = ctx.actions.args()
187
188    args.add("--root-dir", ".")
189    args.add("-o", output)
190    args.add("-so", shared_files[0])
191    inputs = dump_files + [shared_files[0]]
192
193    if ctx.file.symbol_file:
194        args.add("-v", ctx.file.symbol_file.path)
195        inputs.append(ctx.file.symbol_file)
196    for v in ctx.attr.exclude_symbol_versions:
197        args.add("--exclude-symbol-version", v)
198    for t in ctx.attr.exclude_symbol_tags:
199        args.add("--exclude-symbol-tag", t)
200
201    args.add("-arch", platforms.get_target_arch(ctx.attr._platform_utils))
202
203    args.add_all(ctx.attr.root[CcInfo].compilation_context.includes.to_list(), map_each = _include_flag)
204
205    args.add_all([d.path for d in dump_files])
206
207    ctx.actions.run(
208        inputs = inputs,
209        executable = ctx.executable._abi_linker,
210        outputs = [output],
211        arguments = [args],
212        # TODO(b/186116353): enable sandbox once the bug is fixed.
213        execution_requirements = {
214            "no-sandbox": "1",
215        },
216        mnemonic = "AbiLink",
217    )
218
219    return output
220
221def find_abi_config(_ctx):
222    sdk_version = str(platform_versions.platform_sdk_version)
223    prev_version = int(parse_apex_sdk_version(sdk_version))
224    version = "current"
225    if platform_versions.platform_sdk_final:
226        prev_version -= 1
227        version = sdk_version
228
229    return prev_version, version
230
231def create_abi_diff(ctx, dump_file):
232    prev_version, version = find_abi_config(ctx)
233
234    arch = platforms.get_target_arch(ctx.attr._platform_utils)
235    bitness = platforms.get_target_bitness(ctx.attr._platform_utils)
236    abi_class = _ABI_CLASS_PLATFORM
237
238    # The logic below comes from:
239    # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/library.go;l=1891;drc=c645853ab73ac8c5889b42f4ce7dc9353ee8fd35
240    abi_reference_file = None
241    if not platform_versions.platform_sdk_final:
242        abi_reference_file = _find_abi_ref_file(ctx, prev_version, arch, bitness, abi_class, dump_file.basename)
243        if not abi_reference_file:
244            prev_version -= 1
245
246    diff_files = []
247
248    # We need to do the abi check for the previous version and current version if the reference
249    # abi dump files are available. If the current previous version doesn't have the reference
250    # abi dump file we will check against one version earlier.
251    if not abi_reference_file:
252        abi_reference_file = _find_abi_ref_file(ctx, prev_version, arch, bitness, abi_class, dump_file.basename)
253    if abi_reference_file:
254        diff_files.append(_run_abi_diff(ctx, arch, prev_version, dump_file, abi_reference_file, True))
255
256    abi_reference_file = _find_abi_ref_file(ctx, version, arch, bitness, abi_class, dump_file.basename)
257    if abi_reference_file:
258        diff_files.append(_run_abi_diff(ctx, arch, version, dump_file, abi_reference_file, False))
259
260    return diff_files
261
262def _run_abi_diff(ctx, arch, version, dump_file, abi_reference_file, prev_version_diff):
263    lib_name = ctx.attr.soname.removesuffix(".so")
264
265    args = ctx.actions.args()
266
267    if ctx.attr.check_all_apis:
268        args.add("-check-all-apis")
269    else:
270        args.add_all(["-allow-unreferenced-changes", "-allow-unreferenced-elf-symbol-changes"])
271
272    if prev_version_diff:
273        args.add("-target-version", version + 1)
274        diff_file_name = ctx.attr.soname + "." + str(version) + ".abidiff"
275    else:
276        args.add("-target-version", "current")
277        diff_file_name = ctx.attr.soname + ".abidiff"
278
279    args.add("-allow-extensions")
280
281    if len(ctx.attr.diff_flags) > 0:
282        args.add_all(ctx.attr.diff_flags)
283
284    args.add("-lib", lib_name)
285    args.add("-arch", arch)
286
287    diff_file = ctx.actions.declare_file(diff_file_name)
288    args.add("-o", diff_file)
289    args.add("-new", dump_file)
290    args.add("-old", abi_reference_file)
291
292    ctx.actions.run(
293        inputs = [dump_file, abi_reference_file],
294        executable = ctx.executable._abi_diff,
295        outputs = [diff_file],
296        arguments = [args],
297        execution_requirements = {
298            "no-sandbox": "1",
299        },
300        mnemonic = "AbiDiff",
301    )
302
303    return diff_file
304
305def _find_abi_ref_file(ctx, version, arch, bitness, abi_class, lsdump_name):
306    # Currently we only support platform.
307    if abi_class == _ABI_CLASS_PLATFORM:
308        abi_ref_dumps = ctx.attr.abi_ref_dumps_platform
309    else:
310        fail("Unsupported ABI class: %s" % abi_class)
311
312    # The expected reference abi dump file
313    ref_dump_file = paths.join(
314        ctx.attr.ref_dumps_home,
315        abi_class,
316        str(version),
317        str(bitness),
318        arch,
319        "source-based",
320        lsdump_name,
321    )
322
323    ref_file = None
324
325    for file in abi_ref_dumps.files.to_list():
326        if ref_dump_file == file.path:
327            ref_file = file
328            break
329
330    return ref_file
331
332def _abi_diff_enabled(ctx, lib_name, is_aspect):
333    # The logic here is based on:
334    # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/sabi.go;l=103;drc=cb0ac95bde896fa2aa59193a37ceb580758c322c
335
336    if ctx.attr._skip_abi_checks[BuildSettingInfo].value:
337        return False
338    if not platforms.is_target_android(ctx.attr._platform_utils):
339        return False
340    if ctx.coverage_instrumented():
341        return False
342    if ctx.attr._within_apex[BuildSettingInfo].value:
343        if not is_aspect and not ctx.attr.has_stubs:
344            return False
345
346        # Logic comes from here:
347        # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/sabi.go;l=158;drc=cb0ac95bde896fa2aa59193a37ceb580758c322c
348
349    elif is_bionic_lib(lib_name) or is_bootstrap_lib(lib_name):
350        return False
351
352    # TODO(b/260611960): handle all the other checks in sabi.go
353    return True
354
355def _abi_dump_impl(ctx):
356    diff_files = depset()
357    if _abi_diff_enabled(ctx, ctx.attr.soname.removesuffix(".so"), False) and ctx.attr.root != None:
358        dump_files = ctx.attr.root[AbiDumpInfo].dump_files.to_list()
359        linked_dump_file = create_linked_abi_dump(ctx, dump_files)
360        diff_files = depset(create_abi_diff(ctx, linked_dump_file))
361
362    return ([
363        DefaultInfo(files = diff_files),
364        AbiDiffInfo(diff_files = diff_files),
365    ])
366
367abi_dump = rule(
368    implementation = _abi_dump_impl,
369    attrs = {
370        "shared": attr.label(mandatory = True, providers = [CcSharedLibraryInfo]),
371        "root": attr.label(providers = [CcInfo], aspects = [abi_dump_aspect]),
372        "soname": attr.string(mandatory = True),
373        "has_stubs": attr.bool(default = False),
374        "enabled": attr.bool(default = False),
375        "explicitly_disabled": attr.bool(default = False),
376        "symbol_file": attr.label(allow_single_file = True),
377        "exclude_symbol_versions": attr.string_list(default = []),
378        "exclude_symbol_tags": attr.string_list(default = []),
379        "check_all_apis": attr.bool(default = False),
380        "diff_flags": attr.string_list(default = []),
381        "abi_ref_dumps_platform": attr.label(default = "//prebuilts/abi-dumps/platform:bp2build_all_srcs"),
382        "ref_dumps_home": attr.string(default = "prebuilts/abi-dumps"),
383        "_skip_abi_checks": attr.label(
384            default = "//build/bazel/flags/cc/abi:skip_abi_checks",
385        ),
386        "_within_apex": attr.label(
387            default = "//build/bazel/rules/apex:within_apex",
388        ),
389        # TODO(b/254625084): For the following tools we need to support darwin as well.
390        "_abi_dumper": attr.label(
391            allow_files = True,
392            executable = True,
393            cfg = "exec",
394            default = Label("//prebuilts/clang-tools:linux-x86/bin/header-abi-dumper"),
395        ),
396        "_abi_linker": attr.label(
397            allow_files = True,
398            executable = True,
399            cfg = "exec",
400            default = Label("//prebuilts/clang-tools:linux-x86/bin/header-abi-linker"),
401        ),
402        "_abi_diff": attr.label(
403            allow_files = True,
404            executable = True,
405            cfg = "exec",
406            default = Label("//prebuilts/clang-tools:linux-x86/bin/header-abi-diff"),
407        ),
408        "_platform_utils": attr.label(default = Label("//build/bazel/platforms:platform_utils")),
409    },
410    fragments = ["cpp"],
411    toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
412)
413