1# Copyright 2020 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"""Utility functions for C++ rules.""" 15 16load("//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_TYPE") 17load(":cc_common.bzl", "cc_common") 18load(":visibility.bzl", "INTERNAL_VISIBILITY") 19 20visibility(INTERNAL_VISIBILITY) 21 22# LINT.IfChange(linker_mode) 23linker_mode = struct( 24 LINKING_DYNAMIC = "dynamic_linking_mode", 25 LINKING_STATIC = "static_linking_mode", 26) 27# LINT.ThenChange(https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl:linker_mode) 28 29# LINT.IfChange(forked_exports) 30def _get_static_mode_params_for_dynamic_library_libraries(libs): 31 linker_inputs = [] 32 for lib in libs.to_list(): 33 if lib.pic_static_library: 34 linker_inputs.append(lib.pic_static_library) 35 elif lib.static_library: 36 linker_inputs.append(lib.static_library) 37 elif lib.interface_library: 38 linker_inputs.append(lib.interface_library) 39 else: 40 linker_inputs.append(lib.dynamic_library) 41 return linker_inputs 42 43def _create_strip_action(ctx, cc_toolchain, cpp_config, input, output, feature_configuration): 44 if cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "no_stripping"): 45 ctx.actions.symlink( 46 output = output, 47 target_file = input, 48 progress_message = "Symlinking original binary as stripped binary", 49 ) 50 return 51 52 if not cc_common.action_is_enabled(feature_configuration = feature_configuration, action_name = "strip"): 53 fail("Expected action_config for 'strip' to be configured.") 54 55 variables = cc_common.create_compile_variables( 56 cc_toolchain = cc_toolchain, 57 feature_configuration = feature_configuration, 58 output_file = output.path, 59 input_file = input.path, 60 strip_opts = cpp_config.strip_opts(), 61 ) 62 command_line = cc_common.get_memory_inefficient_command_line( 63 feature_configuration = feature_configuration, 64 action_name = "strip", 65 variables = variables, 66 ) 67 env = cc_common.get_environment_variables( 68 feature_configuration = feature_configuration, 69 action_name = "strip", 70 variables = variables, 71 ) 72 execution_info = {} 73 for execution_requirement in cc_common.get_tool_requirement_for_action(feature_configuration = feature_configuration, action_name = "strip"): 74 execution_info[execution_requirement] = "" 75 ctx.actions.run( 76 inputs = depset( 77 direct = [input], 78 transitive = [cc_toolchain._strip_files], 79 ), 80 outputs = [output], 81 use_default_shell_env = True, 82 env = env, 83 executable = cc_common.get_tool_for_action(feature_configuration = feature_configuration, action_name = "strip"), 84 toolchain = CC_TOOLCHAIN_TYPE, 85 execution_requirements = execution_info, 86 progress_message = "Stripping {} for {}".format(output.short_path, ctx.label), 87 mnemonic = "CcStrip", 88 arguments = command_line, 89 ) 90 91def _lookup_var(ctx, additional_vars, var): 92 expanded_make_var_ctx = ctx.var.get(var) 93 expanded_make_var_additional = additional_vars.get(var) 94 if expanded_make_var_additional != None: 95 return expanded_make_var_additional 96 if expanded_make_var_ctx != None: 97 return expanded_make_var_ctx 98 fail("{}: {} not defined".format(ctx.label, "$(" + var + ")")) 99 100def _expand_nested_variable(ctx, additional_vars, exp, execpath = True, targets = []): 101 # If make variable is predefined path variable(like $(location ...)) 102 # we will expand it first. 103 if exp.find(" ") != -1: 104 if not execpath: 105 if exp.startswith("location"): 106 exp = exp.replace("location", "rootpath", 1) 107 data_targets = [] 108 if ctx.attr.data != None: 109 data_targets = ctx.attr.data 110 111 # Make sure we do not duplicate targets. 112 unified_targets_set = {} 113 for data_target in data_targets: 114 unified_targets_set[data_target] = True 115 for target in targets: 116 unified_targets_set[target] = True 117 return ctx.expand_location("$({})".format(exp), targets = unified_targets_set.keys()) 118 119 # Recursively expand nested make variables, but since there is no recursion 120 # in Starlark we will do it via for loop. 121 unbounded_recursion = True 122 123 # The only way to check if the unbounded recursion is happening or not 124 # is to have a look at the depth of the recursion. 125 # 10 seems to be a reasonable number, since it is highly unexpected 126 # to have nested make variables which are expanding more than 10 times. 127 for _ in range(10): 128 exp = _lookup_var(ctx, additional_vars, exp) 129 if len(exp) >= 3 and exp[0] == "$" and exp[1] == "(" and exp[len(exp) - 1] == ")": 130 # Try to expand once more. 131 exp = exp[2:len(exp) - 1] 132 continue 133 unbounded_recursion = False 134 break 135 136 if unbounded_recursion: 137 fail("potentially unbounded recursion during expansion of {}".format(exp)) 138 return exp 139 140def _expand(ctx, expression, additional_make_variable_substitutions, execpath = True, targets = []): 141 idx = 0 142 last_make_var_end = 0 143 result = [] 144 n = len(expression) 145 for _ in range(n): 146 if idx >= n: 147 break 148 if expression[idx] != "$": 149 idx += 1 150 continue 151 152 idx += 1 153 154 # We've met $$ pattern, so $ is escaped. 155 if idx < n and expression[idx] == "$": 156 idx += 1 157 result.append(expression[last_make_var_end:idx - 1]) 158 last_make_var_end = idx 159 # We might have found a potential start for Make Variable. 160 161 elif idx < n and expression[idx] == "(": 162 # Try to find the closing parentheses. 163 make_var_start = idx 164 make_var_end = make_var_start 165 for j in range(idx + 1, n): 166 if expression[j] == ")": 167 make_var_end = j 168 break 169 170 # Note we cannot go out of string's bounds here, 171 # because of this check. 172 # If start of the variable is different from the end, 173 # we found a make variable. 174 if make_var_start != make_var_end: 175 # Some clarifications: 176 # *****$(MAKE_VAR_1)*******$(MAKE_VAR_2)***** 177 # ^ ^ ^ 178 # | | | 179 # last_make_var_end make_var_start make_var_end 180 result.append(expression[last_make_var_end:make_var_start - 1]) 181 make_var = expression[make_var_start + 1:make_var_end] 182 exp = _expand_nested_variable(ctx, additional_make_variable_substitutions, make_var, execpath, targets) 183 result.append(exp) 184 185 # Update indexes. 186 idx = make_var_end + 1 187 last_make_var_end = idx 188 189 # Add the last substring which would be skipped by for loop. 190 if last_make_var_end < n: 191 result.append(expression[last_make_var_end:n]) 192 193 return "".join(result) 194 195def _get_expanded_env(ctx, additional_make_variable_substitutions): 196 if not hasattr(ctx.attr, "env"): 197 fail("could not find rule attribute named: 'env'") 198 expanded_env = {} 199 for k in ctx.attr.env: 200 expanded_env[k] = _expand( 201 ctx, 202 ctx.attr.env[k], 203 additional_make_variable_substitutions, 204 # By default, Starlark `ctx.expand_location` has `execpath` semantics. 205 # For legacy attributes, e.g. `env`, we want `rootpath` semantics instead. 206 execpath = False, 207 ) 208 return expanded_env 209 210# Implementation of Bourne shell tokenization. 211# Tokenizes str and appends result to the options list. 212def _tokenize(options, options_string): 213 token = [] 214 force_token = False 215 quotation = "\0" 216 length = len(options_string) 217 218 # Since it is impossible to modify loop variable inside loop 219 # in Starlark, and also there is no while loop, I have to 220 # use this ugly hack. 221 i = -1 222 for _ in range(length): 223 i += 1 224 if i >= length: 225 break 226 c = options_string[i] 227 if quotation != "\0": 228 # In quotation. 229 if c == quotation: 230 # End quotation. 231 quotation = "\0" 232 elif c == "\\" and quotation == "\"": 233 i += 1 234 if i == length: 235 fail("backslash at the end of the string: {}".format(options_string)) 236 c = options_string[i] 237 if c != "\\" and c != "\"": 238 token.append("\\") 239 token.append(c) 240 else: 241 # Regular char, in quotation. 242 token.append(c) 243 else: 244 # Not in quotation. 245 if c == "'" or c == "\"": 246 # Begin single double quotation. 247 quotation = c 248 force_token = True 249 elif c == " " or c == "\t": 250 # Space not quoted. 251 if force_token or len(token) > 0: 252 options.append("".join(token)) 253 token = [] 254 force_token = False 255 elif c == "\\": 256 # Backslash not quoted. 257 i += 1 258 if i == length: 259 fail("backslash at the end of the string: {}".format(options_string)) 260 token.append(options_string[i]) 261 else: 262 # Regular char, not quoted. 263 token.append(c) 264 if quotation != "\0": 265 fail("unterminated quotation at the end of the string: {}".format(options_string)) 266 267 if force_token or len(token) > 0: 268 options.append("".join(token)) 269 270def _should_use_pic(ctx, cc_toolchain, feature_configuration): 271 """Whether to use pic files 272 273 Args: 274 ctx: (RuleContext) 275 cc_toolchain: (CcToolchainInfo) 276 feature_configuration: (FeatureConfiguration) 277 278 Returns: 279 (bool) 280 """ 281 return ctx.fragments.cpp.force_pic() or ( 282 cc_toolchain.needs_pic_for_dynamic_libraries(feature_configuration = feature_configuration) and ( 283 ctx.var["COMPILATION_MODE"] != "opt" or 284 cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "prefer_pic_for_opt_binaries") 285 ) 286 ) 287 288cc_helper = struct( 289 create_strip_action = _create_strip_action, 290 get_expanded_env = _get_expanded_env, 291 get_static_mode_params_for_dynamic_library_libraries = _get_static_mode_params_for_dynamic_library_libraries, 292 should_use_pic = _should_use_pic, 293 tokenize = _tokenize, 294) 295# LINT.ThenChange(https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl:forked_exports) 296