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