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