rules_cc/cc/common/cc_helper.bzl

296 lines
11 KiB
Python

# 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)