# Copyright 2020 The Bazel Authors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Utility functions for C++ rules.""" load("//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_TYPE") load(":cc_common.bzl", "cc_common") load(":visibility.bzl", "INTERNAL_VISIBILITY") visibility(INTERNAL_VISIBILITY) # LINT.IfChange(linker_mode) linker_mode = struct( LINKING_DYNAMIC = "dynamic_linking_mode", LINKING_STATIC = "static_linking_mode", ) # LINT.ThenChange(https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl:linker_mode) # LINT.IfChange(forked_exports) def _get_static_mode_params_for_dynamic_library_libraries(libs): linker_inputs = [] for lib in libs.to_list(): if lib.pic_static_library: linker_inputs.append(lib.pic_static_library) elif lib.static_library: linker_inputs.append(lib.static_library) elif lib.interface_library: linker_inputs.append(lib.interface_library) else: linker_inputs.append(lib.dynamic_library) return linker_inputs def _create_strip_action(ctx, cc_toolchain, cpp_config, input, output, feature_configuration): if cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "no_stripping"): ctx.actions.symlink( output = output, target_file = input, progress_message = "Symlinking original binary as stripped binary", ) return if not cc_common.action_is_enabled(feature_configuration = feature_configuration, action_name = "strip"): fail("Expected action_config for 'strip' to be configured.") variables = cc_common.create_compile_variables( cc_toolchain = cc_toolchain, feature_configuration = feature_configuration, output_file = output.path, input_file = input.path, strip_opts = cpp_config.strip_opts(), ) command_line = cc_common.get_memory_inefficient_command_line( feature_configuration = feature_configuration, action_name = "strip", variables = variables, ) env = cc_common.get_environment_variables( feature_configuration = feature_configuration, action_name = "strip", variables = variables, ) execution_info = {} for execution_requirement in cc_common.get_tool_requirement_for_action(feature_configuration = feature_configuration, action_name = "strip"): execution_info[execution_requirement] = "" ctx.actions.run( inputs = depset( direct = [input], transitive = [cc_toolchain._strip_files], ), outputs = [output], use_default_shell_env = True, env = env, executable = cc_common.get_tool_for_action(feature_configuration = feature_configuration, action_name = "strip"), toolchain = CC_TOOLCHAIN_TYPE, execution_requirements = execution_info, progress_message = "Stripping {} for {}".format(output.short_path, ctx.label), mnemonic = "CcStrip", arguments = command_line, ) def _lookup_var(ctx, additional_vars, var): expanded_make_var_ctx = ctx.var.get(var) expanded_make_var_additional = additional_vars.get(var) if expanded_make_var_additional != None: return expanded_make_var_additional if expanded_make_var_ctx != None: return expanded_make_var_ctx fail("{}: {} not defined".format(ctx.label, "$(" + var + ")")) def _expand_nested_variable(ctx, additional_vars, exp, execpath = True, targets = []): # If make variable is predefined path variable(like $(location ...)) # we will expand it first. if exp.find(" ") != -1: if not execpath: if exp.startswith("location"): exp = exp.replace("location", "rootpath", 1) data_targets = [] if ctx.attr.data != None: data_targets = ctx.attr.data # Make sure we do not duplicate targets. unified_targets_set = {} for data_target in data_targets: unified_targets_set[data_target] = True for target in targets: unified_targets_set[target] = True return ctx.expand_location("$({})".format(exp), targets = unified_targets_set.keys()) # Recursively expand nested make variables, but since there is no recursion # in Starlark we will do it via for loop. unbounded_recursion = True # The only way to check if the unbounded recursion is happening or not # is to have a look at the depth of the recursion. # 10 seems to be a reasonable number, since it is highly unexpected # to have nested make variables which are expanding more than 10 times. for _ in range(10): exp = _lookup_var(ctx, additional_vars, exp) if len(exp) >= 3 and exp[0] == "$" and exp[1] == "(" and exp[len(exp) - 1] == ")": # Try to expand once more. exp = exp[2:len(exp) - 1] continue unbounded_recursion = False break if unbounded_recursion: fail("potentially unbounded recursion during expansion of {}".format(exp)) return exp def _expand(ctx, expression, additional_make_variable_substitutions, execpath = True, targets = []): idx = 0 last_make_var_end = 0 result = [] n = len(expression) for _ in range(n): if idx >= n: break if expression[idx] != "$": idx += 1 continue idx += 1 # We've met $$ pattern, so $ is escaped. if idx < n and expression[idx] == "$": idx += 1 result.append(expression[last_make_var_end:idx - 1]) last_make_var_end = idx # We might have found a potential start for Make Variable. elif idx < n and expression[idx] == "(": # Try to find the closing parentheses. make_var_start = idx make_var_end = make_var_start for j in range(idx + 1, n): if expression[j] == ")": make_var_end = j break # Note we cannot go out of string's bounds here, # because of this check. # If start of the variable is different from the end, # we found a make variable. if make_var_start != make_var_end: # Some clarifications: # *****$(MAKE_VAR_1)*******$(MAKE_VAR_2)***** # ^ ^ ^ # | | | # last_make_var_end make_var_start make_var_end result.append(expression[last_make_var_end:make_var_start - 1]) make_var = expression[make_var_start + 1:make_var_end] exp = _expand_nested_variable(ctx, additional_make_variable_substitutions, make_var, execpath, targets) result.append(exp) # Update indexes. idx = make_var_end + 1 last_make_var_end = idx # Add the last substring which would be skipped by for loop. if last_make_var_end < n: result.append(expression[last_make_var_end:n]) return "".join(result) def _get_expanded_env(ctx, additional_make_variable_substitutions): if not hasattr(ctx.attr, "env"): fail("could not find rule attribute named: 'env'") expanded_env = {} for k in ctx.attr.env: expanded_env[k] = _expand( ctx, ctx.attr.env[k], additional_make_variable_substitutions, # By default, Starlark `ctx.expand_location` has `execpath` semantics. # For legacy attributes, e.g. `env`, we want `rootpath` semantics instead. execpath = False, ) return expanded_env # Implementation of Bourne shell tokenization. # Tokenizes str and appends result to the options list. def _tokenize(options, options_string): token = [] force_token = False quotation = "\0" length = len(options_string) # Since it is impossible to modify loop variable inside loop # in Starlark, and also there is no while loop, I have to # use this ugly hack. i = -1 for _ in range(length): i += 1 if i >= length: break c = options_string[i] if quotation != "\0": # In quotation. if c == quotation: # End quotation. quotation = "\0" elif c == "\\" and quotation == "\"": i += 1 if i == length: fail("backslash at the end of the string: {}".format(options_string)) c = options_string[i] if c != "\\" and c != "\"": token.append("\\") token.append(c) else: # Regular char, in quotation. token.append(c) else: # Not in quotation. if c == "'" or c == "\"": # Begin single double quotation. quotation = c force_token = True elif c == " " or c == "\t": # Space not quoted. if force_token or len(token) > 0: options.append("".join(token)) token = [] force_token = False elif c == "\\": # Backslash not quoted. i += 1 if i == length: fail("backslash at the end of the string: {}".format(options_string)) token.append(options_string[i]) else: # Regular char, not quoted. token.append(c) if quotation != "\0": fail("unterminated quotation at the end of the string: {}".format(options_string)) if force_token or len(token) > 0: options.append("".join(token)) def _should_use_pic(ctx, cc_toolchain, feature_configuration): """Whether to use pic files Args: ctx: (RuleContext) cc_toolchain: (CcToolchainInfo) feature_configuration: (FeatureConfiguration) Returns: (bool) """ return ctx.fragments.cpp.force_pic() or ( cc_toolchain.needs_pic_for_dynamic_libraries(feature_configuration = feature_configuration) and ( ctx.var["COMPILATION_MODE"] != "opt" or cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "prefer_pic_for_opt_binaries") ) ) cc_helper = struct( create_strip_action = _create_strip_action, get_expanded_env = _get_expanded_env, get_static_mode_params_for_dynamic_library_libraries = _get_static_mode_params_for_dynamic_library_libraries, should_use_pic = _should_use_pic, tokenize = _tokenize, ) # LINT.ThenChange(https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl:forked_exports)