mirror of
https://github.com/bazelbuild/rules_cc
synced 2024-11-30 22:41:22 +00:00
204 lines
7.5 KiB
Python
204 lines
7.5 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."""
|
||
|
|
||
|
# LINT.IfChange
|
||
|
|
||
|
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))
|
||
|
|
||
|
cc_helper = struct(
|
||
|
get_expanded_env = _get_expanded_env,
|
||
|
tokenize = _tokenize,
|
||
|
)
|
||
|
|
||
|
# LINT.ThenChange(https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl)
|