xref: /aosp_15_r20/external/bazelbuild-rules_android/rules/utils.bzl (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
1# Copyright 2018 The Bazel Authors. All rights reserved.
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
15"""Utilities for the Android rules."""
16
17load(":providers.bzl", "FailureInfo")
18
19ANDROID_TOOLCHAIN_TYPE = Label("//toolchains/android:toolchain_type")
20
21_CUU = "\033[A"
22_EL = "\033[K"
23_DEFAULT = "\033[0m"
24_BOLD = "\033[1m"
25_RED = "\033[31m"
26_GREEN = "\033[32m"
27_MAGENTA = "\033[35m"
28_ERASE_PREV_LINE = "\n" + _CUU + _EL
29
30_INFO = _ERASE_PREV_LINE + _GREEN + "INFO: " + _DEFAULT + "%s"
31_WARNING = _ERASE_PREV_LINE + _MAGENTA + "WARNING: " + _DEFAULT + "%s"
32_ERROR = _ERASE_PREV_LINE + _BOLD + _RED + "ERROR: " + _DEFAULT + "%s"
33
34_WORD_CHARS = {
35    "A": True,
36    "B": True,
37    "C": True,
38    "D": True,
39    "E": True,
40    "F": True,
41    "G": True,
42    "H": True,
43    "I": True,
44    "J": True,
45    "K": True,
46    "L": True,
47    "M": True,
48    "N": True,
49    "O": True,
50    "P": True,
51    "Q": True,
52    "R": True,
53    "S": True,
54    "T": True,
55    "U": True,
56    "V": True,
57    "W": True,
58    "X": True,
59    "Y": True,
60    "Z": True,
61    "a": True,
62    "b": True,
63    "c": True,
64    "d": True,
65    "e": True,
66    "f": True,
67    "g": True,
68    "h": True,
69    "i": True,
70    "j": True,
71    "k": True,
72    "l": True,
73    "m": True,
74    "n": True,
75    "o": True,
76    "p": True,
77    "q": True,
78    "r": True,
79    "s": True,
80    "t": True,
81    "u": True,
82    "v": True,
83    "w": True,
84    "x": True,
85    "y": True,
86    "z": True,
87    "0": True,
88    "1": True,
89    "2": True,
90    "3": True,
91    "4": True,
92    "5": True,
93    "6": True,
94    "7": True,
95    "8": True,
96    "9": True,
97    "_": True,
98}
99
100_HEX_CHAR = {
101    0x0: "0",
102    0x1: "1",
103    0x2: "2",
104    0x3: "3",
105    0x4: "4",
106    0x5: "5",
107    0x6: "6",
108    0x7: "7",
109    0x8: "8",
110    0x9: "9",
111    0xA: "A",
112    0xB: "B",
113    0xC: "C",
114    0xD: "D",
115    0xE: "E",
116    0xF: "F",
117}
118
119_JAVA_RESERVED = {
120    "abstract": True,
121    "assert": True,
122    "boolean": True,
123    "break": True,
124    "byte": True,
125    "case": True,
126    "catch": True,
127    "char": True,
128    "class": True,
129    "const": True,
130    "continue": True,
131    "default": True,
132    "do": True,
133    "double": True,
134    "else": True,
135    "enum": True,
136    "extends": True,
137    "final": True,
138    "finally": True,
139    "float": True,
140    "for": True,
141    "goto": True,
142    "if": True,
143    "implements": True,
144    "import": True,
145    "instanceof": True,
146    "int": True,
147    "interface": True,
148    "long": True,
149    "native": True,
150    "new": True,
151    "package": True,
152    "private": True,
153    "protected": True,
154    "public": True,
155    "return": True,
156    "short": True,
157    "static": True,
158    "strictfp": True,
159    "super": True,
160    "switch": True,
161    "synchronized": True,
162    "this": True,
163    "throw": True,
164    "throws": True,
165    "transient": True,
166    "try": True,
167    "void": True,
168    "volatile": True,
169    "while": True,
170    "true": True,
171    "false": True,
172    "null": True,
173}
174
175def _collect_providers(provider, *all_deps):
176    """Collects the requested providers from the given list of deps."""
177    providers = []
178    for deps in all_deps:
179        for dep in deps:
180            if provider in dep:
181                providers.append(dep[provider])
182    return providers
183
184def _join_depsets(providers, attr, order = "default"):
185    """Returns a merged depset using 'attr' from each provider in 'providers'."""
186    return depset(transitive = [getattr(p, attr) for p in providers], order = order)
187
188def _first(collection):
189    """Returns the first item in the collection."""
190    for i in collection:
191        return i
192    return _error("The collection is empty.")
193
194def _only(collection):
195    """Returns the only item in the collection."""
196    if len(collection) != 1:
197        _error("Expected one element, has %s." % len(collection))
198    return _first(collection)
199
200def _list_or_depset_to_list(list_or_depset):
201    if type(list_or_depset) == "list":
202        return list_or_depset
203    elif type(list_or_depset) == "depset":
204        return list_or_depset.to_list()
205    else:
206        return _error("Expected a list or a depset. Got %s" % type(list_or_depset))
207
208def _copy_file(ctx, src, dest):
209    if src.is_directory or dest.is_directory:
210        fail("Cannot use copy_file with directories")
211    ctx.actions.run_shell(
212        command = "cp --reflink=auto $1 $2",
213        arguments = [src.path, dest.path],
214        inputs = [src],
215        outputs = [dest],
216        mnemonic = "CopyFile",
217        progress_message = "Copy %s to %s" % (src.short_path, dest.short_path),
218    )
219
220def _copy_dir(ctx, src, dest):
221    if not src.is_directory:
222        fail("copy_dir src must be a directory")
223    ctx.actions.run_shell(
224        command = "cp -r --reflink=auto $1 $2",
225        arguments = [src.path, dest.path],
226        inputs = [src],
227        outputs = [dest],
228        mnemonic = "CopyDir",
229        progress_message = "Copy %s to %s" % (src.short_path, dest.short_path),
230    )
231
232def _info(msg):
233    """Print info."""
234    print(_INFO % msg)
235
236def _warn(msg):
237    """Print warning."""
238    print(_WARNING % msg)
239
240def _debug(msg):
241    """Print debug."""
242    print("\n%s" % msg)
243
244def _error(msg):
245    """Print error and fail."""
246    fail(_ERASE_PREV_LINE + _CUU + _ERASE_PREV_LINE + _CUU + _ERROR % msg)
247
248def _expand_var(config_vars, value):
249    """Expands make variables of the form $(SOME_VAR_NAME) for a single value.
250
251    "$$(SOME_VAR_NAME)" is escaped to a literal value of "$(SOME_VAR_NAME)" instead of being
252    expanded.
253
254    Args:
255      config_vars: String dictionary which maps config variables to their expanded values.
256      value: The string to apply substitutions to.
257
258    Returns:
259      The string value with substitutions applied.
260    """
261    parts = value.split("$(")
262    replacement = parts[0]
263    last_char = replacement[-1] if replacement else ""
264    for part in parts[1:]:
265        var_end = part.find(")")
266        if last_char == "$":
267            # If "$$(..." is found, treat it as "$(..."
268            replacement += "(" + part
269        elif var_end == -1 or part[:var_end] not in config_vars:
270            replacement += "$(" + part
271        else:
272            replacement += config_vars[part[:var_end]] + part[var_end + 1:]
273        last_char = replacement[-1] if replacement else ""
274    return replacement
275
276def _expand_make_vars(ctx, vals):
277    """Expands make variables of the form $(SOME_VAR_NAME).
278
279    Args:
280      ctx: The rules context.
281      vals: Dictionary. Values of the form $(...) will be replaced.
282
283    Returns:
284      A dictionary containing vals.keys() and the expanded values.
285    """
286    res = {}
287    for k, v in vals.items():
288        res[k] = _expand_var(ctx.var, v)
289    return res
290
291def _dedupe_split_attr(attr):
292    if not attr:
293        return []
294    arch = _first(sorted(attr.keys()))
295    return attr[arch]
296
297def _get_runfiles(ctx, attrs):
298    runfiles = ctx.runfiles()
299    for attr in attrs:
300        executable = attr[DefaultInfo].files_to_run.executable
301        if executable:
302            runfiles = runfiles.merge(ctx.runfiles([executable]))
303        runfiles = runfiles.merge(
304            ctx.runfiles(
305                # Wrap DefaultInfo.files in depset to strip ordering.
306                transitive_files = depset(
307                    transitive = [attr[DefaultInfo].files],
308                ),
309            ),
310        )
311        runfiles = runfiles.merge(attr[DefaultInfo].default_runfiles)
312    return runfiles
313
314def _sanitize_string(s, replacement = ""):
315    """Sanitizes a string by replacing all non-word characters.
316
317    This matches the \\w regex character class [A_Za-z0-9_].
318
319    Args:
320      s: String to sanitize.
321      replacement: Replacement for all non-word characters. Optional.
322
323    Returns:
324      The original string with all non-word characters replaced.
325    """
326    return "".join([s[i] if s[i] in _WORD_CHARS else replacement for i in range(len(s))])
327
328def _hex(n, pad = True):
329    """Convert an integer number to an uppercase hexadecimal string.
330
331    Args:
332      n: Integer number.
333      pad: Optional. Pad the result to 8 characters with leading zeroes. Default = True.
334
335    Returns:
336      Return a representation of an integer number as a hexadecimal string.
337    """
338    hex_str = ""
339    for _ in range(8):
340        r = n % 16
341        n = n // 16
342        hex_str = _HEX_CHAR[r] + hex_str
343    if pad:
344        return hex_str
345    else:
346        return hex_str.lstrip("0")
347
348def _sanitize_java_package(pkg):
349    return ".".join(["xxx" if p in _JAVA_RESERVED else p for p in pkg.split(".")])
350
351def _check_for_failures(label, *all_deps):
352    """Collects FailureInfo providers from the given list of deps and fails if there's at least one."""
353    failure_infos = _collect_providers(FailureInfo, *all_deps)
354    if failure_infos:
355        error = "in label '%s':" % label
356        for failure_info in failure_infos:
357            error += "\n\t" + failure_info.error
358        _error(error)
359
360def _run_validation(
361        ctx,
362        validation_out,
363        executable,
364        outputs = [],
365        tools = [],
366        **args):
367    """Creates an action that runs an executable as a validation.
368
369    Note: When the validation executable fails, it should return a non-zero
370    value to signify a validation failure.
371
372    Args:
373      ctx: The context.
374      validation_out: A File. The output of the executable is piped to the
375        file. This artifact should then be propagated to "validations" in the
376        OutputGroupInfo.
377      executable: See ctx.actions.run#executable.
378      outputs: See ctx.actions.run#outputs.
379      tools: See ctx.actions.run#tools.
380      **args: Remaining args are directly propagated to ctx.actions.run_shell.
381        See ctx.actions.run_shell for further documentation.
382    """
383    exec_type = type(executable)
384    exec_bin = None
385    exec_bin_path = None
386    if exec_type == "FilesToRunProvider":
387        exec_bin = executable.executable
388        exec_bin_path = exec_bin.path
389    elif exec_type == "File":
390        exec_bin = executable
391        exec_bin_path = exec_bin.path
392    elif exec_type == type(""):
393        exec_bin_path = executable
394    else:
395        fail(
396            "Error, executable should be a File, FilesToRunProvider or a " +
397            "string that represents a path to a tool, got: %s" % exec_type,
398        )
399
400    ctx.actions.run_shell(
401        command = """#!/bin/bash
402set -eu
403set -o pipefail # Returns the executables failure code, if it fails.
404
405EXECUTABLE={executable}
406VALIDATION_OUT={validation_out}
407
408"${{EXECUTABLE}}" $@ 2>&1 | tee -a "${{VALIDATION_OUT}}"
409""".format(
410            executable = exec_bin_path,
411            validation_out = validation_out.path,
412        ),
413        tools = tools + ([exec_bin] if exec_bin else []),
414        outputs = [validation_out] + outputs,
415        **args
416    )
417
418def get_android_toolchain(ctx):
419    return ctx.toolchains[ANDROID_TOOLCHAIN_TYPE]
420
421def get_android_sdk(ctx):
422    if hasattr(ctx.fragments.android, "incompatible_use_toolchain_resolution") and ctx.fragments.android.incompatible_use_toolchain_resolution:
423        return ctx.toolchains["//toolchains/android_sdk:toolchain_type"].android_sdk_info
424    else:
425        return ctx.attr._android_sdk[AndroidSdkInfo]
426
427def _get_compilation_mode(ctx):
428    """Retrieves the compilation mode from the context.
429
430    Returns:
431      A string that represents the compilation mode.
432    """
433    return ctx.var["COMPILATION_MODE"]
434
435compilation_mode = struct(
436    DBG = "dbg",
437    FASTBUILD = "fastbuild",
438    OPT = "opt",
439    get = _get_compilation_mode,
440)
441
442utils = struct(
443    check_for_failures = _check_for_failures,
444    collect_providers = _collect_providers,
445    copy_file = _copy_file,
446    copy_dir = _copy_dir,
447    expand_make_vars = _expand_make_vars,
448    first = _first,
449    dedupe_split_attr = _dedupe_split_attr,
450    get_runfiles = _get_runfiles,
451    join_depsets = _join_depsets,
452    only = _only,
453    run_validation = _run_validation,
454    sanitize_string = _sanitize_string,
455    sanitize_java_package = _sanitize_java_package,
456    hex = _hex,
457    list_or_depset_to_list = _list_or_depset_to_list,
458)
459
460log = struct(
461    debug = _debug,
462    error = _error,
463    info = _info,
464    warn = _warn,
465)
466
467testing = struct(
468    expand_var = _expand_var,
469)
470