|
| 1 | +"""dd_strip_debug: split a binary/library into stripped + debug-only outputs. |
| 2 | +
|
| 3 | +Wraps any single label that produces one default output file (a go_binary, |
| 4 | +cc_binary, cc_shared_library, or rust_binary). The wrapped rule's DEFAULT |
| 5 | +output is untouched and stays unstripped, so `bazel build //some:target` |
| 6 | +behavior never changes; the split outputs are only materialized for |
| 7 | +consumers that ask for them via OutputGroupInfo (`--output_groups=+debug` or |
| 8 | +`+stripped`) or by reading DdStripInfo directly (see dd_pkg_files_stripped.bzl). |
| 9 | +
|
| 10 | +dd_go_binary_with_debug / dd_cc_binary_with_debug / dd_rust_binary_with_debug |
| 11 | +are convenience macros that create the wrapped binary/library under a private |
| 12 | +`<name>_unstripped` target and wrap it with dd_strip_debug under the |
| 13 | +requested `name`, so callers just swap the rule name and keep every other |
| 14 | +kwarg unchanged. |
| 15 | +""" |
| 16 | + |
| 17 | +load("@rules_cc//cc:defs.bzl", "cc_binary") |
| 18 | +load("@rules_go//go:def.bzl", "go_binary") |
| 19 | +load(":dd_strip_info.bzl", "DdStripInfo") |
| 20 | + |
| 21 | +_TOOLCHAIN_TYPE = "//bazel/toolchains/dd_strip:dd_strip_toolchain_type" |
| 22 | + |
| 23 | +def _passthrough(original, excluded): |
| 24 | + return [ |
| 25 | + DefaultInfo(files = depset([original])), |
| 26 | + DdStripInfo( |
| 27 | + stripped_file = original, |
| 28 | + debug_file = None, |
| 29 | + original_file = original, |
| 30 | + excluded = excluded, |
| 31 | + ), |
| 32 | + OutputGroupInfo(stripped = depset([original]), debug = depset([])), |
| 33 | + ] |
| 34 | + |
| 35 | +def _dd_strip_debug_impl(ctx): |
| 36 | + original = ctx.file.input |
| 37 | + |
| 38 | + if ctx.attr.exclude: |
| 39 | + return _passthrough(original, excluded = True) |
| 40 | + |
| 41 | + toolchain = ctx.toolchains[_TOOLCHAIN_TYPE] |
| 42 | + if toolchain == None or not toolchain.available: |
| 43 | + # No strip/split driver for this platform: pass the original through |
| 44 | + # rather than failing the build (parity with DdStripInfo.excluded). |
| 45 | + return _passthrough(original, excluded = True) |
| 46 | + |
| 47 | + stripped = ctx.actions.declare_file(ctx.label.name + ".stripped") |
| 48 | + if toolchain.debug_is_directory: |
| 49 | + debug = ctx.actions.declare_directory(ctx.label.name + ".debug") |
| 50 | + else: |
| 51 | + debug = ctx.actions.declare_file(ctx.label.name + ".debug") |
| 52 | + |
| 53 | + args = ctx.actions.args() |
| 54 | + args.add(original.path) |
| 55 | + args.add(stripped.path) |
| 56 | + args.add(debug.path) |
| 57 | + ctx.actions.run( |
| 58 | + executable = toolchain.driver, |
| 59 | + arguments = [args], |
| 60 | + inputs = [original], |
| 61 | + outputs = [stripped, debug], |
| 62 | + toolchain = _TOOLCHAIN_TYPE, |
| 63 | + mnemonic = "DdStripDebug", |
| 64 | + progress_message = "Splitting debug info for %s" % original.short_path, |
| 65 | + ) |
| 66 | + |
| 67 | + return [ |
| 68 | + # Default output stays unstripped: bazel build //x:x never changes. |
| 69 | + DefaultInfo(files = depset([original])), |
| 70 | + DdStripInfo( |
| 71 | + stripped_file = stripped, |
| 72 | + debug_file = debug, |
| 73 | + original_file = original, |
| 74 | + excluded = False, |
| 75 | + ), |
| 76 | + OutputGroupInfo(stripped = depset([stripped]), debug = depset([debug])), |
| 77 | + ] |
| 78 | + |
| 79 | +dd_strip_debug = rule( |
| 80 | + implementation = _dd_strip_debug_impl, |
| 81 | + doc = """Wraps a single-output binary/library target, exposing: |
| 82 | +
|
| 83 | + - DefaultInfo: the original, unstripped file (unchanged build behavior). |
| 84 | + - OutputGroupInfo(stripped = [...]): the stripped file, for packaging. |
| 85 | + - OutputGroupInfo(debug = [...]): the debug-only file/dSYM directory. |
| 86 | + - DdStripInfo: the same three files plus whether stripping was skipped. |
| 87 | +
|
| 88 | + When exclude = True (parity with omnibus's strip_exclude, e.g. for eBPF |
| 89 | + .o objects handled separately by _stripped_ebpf), or when no dd_strip |
| 90 | + toolchain is registered for the current platform, stripped_file equals |
| 91 | + original_file and debug_file is None -- no stripping occurs and |
| 92 | + DdStripInfo.excluded is True.""", |
| 93 | + attrs = { |
| 94 | + "input": attr.label( |
| 95 | + doc = "Label producing a single default output file to strip (a binary or shared library).", |
| 96 | + mandatory = True, |
| 97 | + allow_single_file = True, |
| 98 | + ), |
| 99 | + "exclude": attr.bool( |
| 100 | + doc = "Skip stripping and expose the original file unchanged, parity with omnibus's strip_exclude.", |
| 101 | + default = False, |
| 102 | + ), |
| 103 | + }, |
| 104 | + toolchains = [config_common.toolchain_type(_TOOLCHAIN_TYPE, mandatory = False)], |
| 105 | +) |
| 106 | + |
| 107 | +def _wrap(binary_rule, name, strip_exclude, visibility, kwargs): |
| 108 | + unstripped_name = name + "_unstripped" |
| 109 | + binary_rule( |
| 110 | + name = unstripped_name, |
| 111 | + visibility = ["//visibility:private"], |
| 112 | + tags = (kwargs.pop("tags", None) or []) + ["manual"], |
| 113 | + **kwargs |
| 114 | + ) |
| 115 | + dd_strip_debug( |
| 116 | + name = name, |
| 117 | + input = ":" + unstripped_name, |
| 118 | + exclude = strip_exclude, |
| 119 | + visibility = visibility, |
| 120 | + ) |
| 121 | + |
| 122 | +def dd_go_binary_with_debug(name, strip_exclude = False, visibility = None, **kwargs): |
| 123 | + """A go_binary wrapped with dd_strip_debug. |
| 124 | +
|
| 125 | + Accepts all go_binary attributes via **kwargs. See dd_strip_debug for the |
| 126 | + behavior of the wrapping (default output stays unstripped; use |
| 127 | + --output_groups=+stripped/+debug or DdStripInfo to get the split). |
| 128 | +
|
| 129 | + Args: |
| 130 | + name: target name. The underlying go_binary is created as |
| 131 | + "<name>_unstripped" (private, tags = ["manual"]). |
| 132 | + strip_exclude: parity with omnibus's strip_exclude -- skip stripping. |
| 133 | + visibility: visibility of the resulting dd_strip_debug target. |
| 134 | + **kwargs: forwarded to go_binary. |
| 135 | + """ |
| 136 | + _wrap(go_binary, name, strip_exclude, visibility, kwargs) |
| 137 | + |
| 138 | +def dd_cc_binary_with_debug(name, strip_exclude = False, visibility = None, **kwargs): |
| 139 | + """A cc_binary wrapped with dd_strip_debug. See dd_go_binary_with_debug.""" |
| 140 | + _wrap(cc_binary, name, strip_exclude, visibility, kwargs) |
| 141 | + |
| 142 | +def dd_rust_binary_with_debug(name, strip_exclude = False, visibility = None, **kwargs): |
| 143 | + """A rust_binary wrapped with dd_strip_debug. See dd_go_binary_with_debug.""" |
| 144 | + |
| 145 | + # Deferred import: rules_rust is not a dependency of every consumer of |
| 146 | + # this file (e.g. dd_go_binary_with_debug-only callers), and loading it |
| 147 | + # unconditionally would pull rules_rust into packages that never use it. |
| 148 | + _wrap(_rust_binary(), name, strip_exclude, visibility, kwargs) |
| 149 | + |
| 150 | +def _rust_binary(): |
| 151 | + return Label("@rules_rust//rust:defs.bzl%rust_binary") |
0 commit comments