diff --git a/cc/common/BUILD b/cc/common/BUILD index 5f40a3e7..2af8526d 100644 --- a/cc/common/BUILD +++ b/cc/common/BUILD @@ -21,6 +21,12 @@ bzl_library( 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 00000000..8b161a1a --- /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)