mirror of https://github.com/bazelbuild/rules_cc
296 lines
11 KiB
Python
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)
|