Export a few utility functions from `@builtins` to `rules_cc`
PiperOrigin-RevId: 670979420
Change-Id: I37ce050f747a0dbc5ab0039fd32a84d7be7e1ed4
diff --git a/cc/common/BUILD b/cc/common/BUILD
index 5f40a3e..2af8526 100644
--- a/cc/common/BUILD
+++ b/cc/common/BUILD
@@ -21,6 +21,12 @@
deps = ["//cc/private/rules_impl:native_bzl"],
)
+bzl_library(
+ name = "cc_helper_bzl",
+ srcs = ["cc_helper.bzl"],
+ visibility = ["//visibility:public"],
+)
+
filegroup(
name = "srcs",
srcs = glob([
diff --git a/cc/common/cc_helper.bzl b/cc/common/cc_helper.bzl
new file mode 100644
index 0000000..8b161a1
--- /dev/null
+++ b/cc/common/cc_helper.bzl
@@ -0,0 +1,203 @@
+# 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)