• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2020 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Utility functions for C++ rules."""
15
16load("//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_TYPE")
17load(":cc_common.bzl", "cc_common")
18load(":visibility.bzl", "INTERNAL_VISIBILITY")
19
20visibility(INTERNAL_VISIBILITY)
21
22# LINT.IfChange(linker_mode)
23linker_mode = struct(
24    LINKING_DYNAMIC = "dynamic_linking_mode",
25    LINKING_STATIC = "static_linking_mode",
26)
27# LINT.ThenChange(https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl:linker_mode)
28
29# LINT.IfChange(forked_exports)
30def _get_static_mode_params_for_dynamic_library_libraries(libs):
31    linker_inputs = []
32    for lib in libs.to_list():
33        if lib.pic_static_library:
34            linker_inputs.append(lib.pic_static_library)
35        elif lib.static_library:
36            linker_inputs.append(lib.static_library)
37        elif lib.interface_library:
38            linker_inputs.append(lib.interface_library)
39        else:
40            linker_inputs.append(lib.dynamic_library)
41    return linker_inputs
42
43def _create_strip_action(ctx, cc_toolchain, cpp_config, input, output, feature_configuration):
44    if cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "no_stripping"):
45        ctx.actions.symlink(
46            output = output,
47            target_file = input,
48            progress_message = "Symlinking original binary as stripped binary",
49        )
50        return
51
52    if not cc_common.action_is_enabled(feature_configuration = feature_configuration, action_name = "strip"):
53        fail("Expected action_config for 'strip' to be configured.")
54
55    variables = cc_common.create_compile_variables(
56        cc_toolchain = cc_toolchain,
57        feature_configuration = feature_configuration,
58        output_file = output.path,
59        input_file = input.path,
60        strip_opts = cpp_config.strip_opts(),
61    )
62    command_line = cc_common.get_memory_inefficient_command_line(
63        feature_configuration = feature_configuration,
64        action_name = "strip",
65        variables = variables,
66    )
67    env = cc_common.get_environment_variables(
68        feature_configuration = feature_configuration,
69        action_name = "strip",
70        variables = variables,
71    )
72    execution_info = {}
73    for execution_requirement in cc_common.get_tool_requirement_for_action(feature_configuration = feature_configuration, action_name = "strip"):
74        execution_info[execution_requirement] = ""
75    ctx.actions.run(
76        inputs = depset(
77            direct = [input],
78            transitive = [cc_toolchain._strip_files],
79        ),
80        outputs = [output],
81        use_default_shell_env = True,
82        env = env,
83        executable = cc_common.get_tool_for_action(feature_configuration = feature_configuration, action_name = "strip"),
84        toolchain = CC_TOOLCHAIN_TYPE,
85        execution_requirements = execution_info,
86        progress_message = "Stripping {} for {}".format(output.short_path, ctx.label),
87        mnemonic = "CcStrip",
88        arguments = command_line,
89    )
90
91def _lookup_var(ctx, additional_vars, var):
92    expanded_make_var_ctx = ctx.var.get(var)
93    expanded_make_var_additional = additional_vars.get(var)
94    if expanded_make_var_additional != None:
95        return expanded_make_var_additional
96    if expanded_make_var_ctx != None:
97        return expanded_make_var_ctx
98    fail("{}: {} not defined".format(ctx.label, "$(" + var + ")"))
99
100def _expand_nested_variable(ctx, additional_vars, exp, execpath = True, targets = []):
101    # If make variable is predefined path variable(like $(location ...))
102    # we will expand it first.
103    if exp.find(" ") != -1:
104        if not execpath:
105            if exp.startswith("location"):
106                exp = exp.replace("location", "rootpath", 1)
107        data_targets = []
108        if ctx.attr.data != None:
109            data_targets = ctx.attr.data
110
111        # Make sure we do not duplicate targets.
112        unified_targets_set = {}
113        for data_target in data_targets:
114            unified_targets_set[data_target] = True
115        for target in targets:
116            unified_targets_set[target] = True
117        return ctx.expand_location("$({})".format(exp), targets = unified_targets_set.keys())
118
119    # Recursively expand nested make variables, but since there is no recursion
120    # in Starlark we will do it via for loop.
121    unbounded_recursion = True
122
123    # The only way to check if the unbounded recursion is happening or not
124    # is to have a look at the depth of the recursion.
125    # 10 seems to be a reasonable number, since it is highly unexpected
126    # to have nested make variables which are expanding more than 10 times.
127    for _ in range(10):
128        exp = _lookup_var(ctx, additional_vars, exp)
129        if len(exp) >= 3 and exp[0] == "$" and exp[1] == "(" and exp[len(exp) - 1] == ")":
130            # Try to expand once more.
131            exp = exp[2:len(exp) - 1]
132            continue
133        unbounded_recursion = False
134        break
135
136    if unbounded_recursion:
137        fail("potentially unbounded recursion during expansion of {}".format(exp))
138    return exp
139
140def _expand(ctx, expression, additional_make_variable_substitutions, execpath = True, targets = []):
141    idx = 0
142    last_make_var_end = 0
143    result = []
144    n = len(expression)
145    for _ in range(n):
146        if idx >= n:
147            break
148        if expression[idx] != "$":
149            idx += 1
150            continue
151
152        idx += 1
153
154        # We've met $$ pattern, so $ is escaped.
155        if idx < n and expression[idx] == "$":
156            idx += 1
157            result.append(expression[last_make_var_end:idx - 1])
158            last_make_var_end = idx
159            # We might have found a potential start for Make Variable.
160
161        elif idx < n and expression[idx] == "(":
162            # Try to find the closing parentheses.
163            make_var_start = idx
164            make_var_end = make_var_start
165            for j in range(idx + 1, n):
166                if expression[j] == ")":
167                    make_var_end = j
168                    break
169
170            # Note we cannot go out of string's bounds here,
171            # because of this check.
172            # If start of the variable is different from the end,
173            # we found a make variable.
174            if make_var_start != make_var_end:
175                # Some clarifications:
176                # *****$(MAKE_VAR_1)*******$(MAKE_VAR_2)*****
177                #                   ^       ^          ^
178                #                   |       |          |
179                #   last_make_var_end  make_var_start make_var_end
180                result.append(expression[last_make_var_end:make_var_start - 1])
181                make_var = expression[make_var_start + 1:make_var_end]
182                exp = _expand_nested_variable(ctx, additional_make_variable_substitutions, make_var, execpath, targets)
183                result.append(exp)
184
185                # Update indexes.
186                idx = make_var_end + 1
187                last_make_var_end = idx
188
189    # Add the last substring which would be skipped by for loop.
190    if last_make_var_end < n:
191        result.append(expression[last_make_var_end:n])
192
193    return "".join(result)
194
195def _get_expanded_env(ctx, additional_make_variable_substitutions):
196    if not hasattr(ctx.attr, "env"):
197        fail("could not find rule attribute named: 'env'")
198    expanded_env = {}
199    for k in ctx.attr.env:
200        expanded_env[k] = _expand(
201            ctx,
202            ctx.attr.env[k],
203            additional_make_variable_substitutions,
204            # By default, Starlark `ctx.expand_location` has `execpath` semantics.
205            # For legacy attributes, e.g. `env`, we want `rootpath` semantics instead.
206            execpath = False,
207        )
208    return expanded_env
209
210# Implementation of Bourne shell tokenization.
211# Tokenizes str and appends result to the options list.
212def _tokenize(options, options_string):
213    token = []
214    force_token = False
215    quotation = "\0"
216    length = len(options_string)
217
218    # Since it is impossible to modify loop variable inside loop
219    # in Starlark, and also there is no while loop, I have to
220    # use this ugly hack.
221    i = -1
222    for _ in range(length):
223        i += 1
224        if i >= length:
225            break
226        c = options_string[i]
227        if quotation != "\0":
228            # In quotation.
229            if c == quotation:
230                # End quotation.
231                quotation = "\0"
232            elif c == "\\" and quotation == "\"":
233                i += 1
234                if i == length:
235                    fail("backslash at the end of the string: {}".format(options_string))
236                c = options_string[i]
237                if c != "\\" and c != "\"":
238                    token.append("\\")
239                token.append(c)
240            else:
241                # Regular char, in quotation.
242                token.append(c)
243        else:
244            # Not in quotation.
245            if c == "'" or c == "\"":
246                # Begin single double quotation.
247                quotation = c
248                force_token = True
249            elif c == " " or c == "\t":
250                # Space not quoted.
251                if force_token or len(token) > 0:
252                    options.append("".join(token))
253                    token = []
254                    force_token = False
255            elif c == "\\":
256                # Backslash not quoted.
257                i += 1
258                if i == length:
259                    fail("backslash at the end of the string: {}".format(options_string))
260                token.append(options_string[i])
261            else:
262                # Regular char, not quoted.
263                token.append(c)
264    if quotation != "\0":
265        fail("unterminated quotation at the end of the string: {}".format(options_string))
266
267    if force_token or len(token) > 0:
268        options.append("".join(token))
269
270def _should_use_pic(ctx, cc_toolchain, feature_configuration):
271    """Whether to use pic files
272
273    Args:
274        ctx: (RuleContext)
275        cc_toolchain: (CcToolchainInfo)
276        feature_configuration: (FeatureConfiguration)
277
278    Returns:
279        (bool)
280    """
281    return ctx.fragments.cpp.force_pic() or (
282        cc_toolchain.needs_pic_for_dynamic_libraries(feature_configuration = feature_configuration) and (
283            ctx.var["COMPILATION_MODE"] != "opt" or
284            cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "prefer_pic_for_opt_binaries")
285        )
286    )
287
288cc_helper = struct(
289    create_strip_action = _create_strip_action,
290    get_expanded_env = _get_expanded_env,
291    get_static_mode_params_for_dynamic_library_libraries = _get_static_mode_params_for_dynamic_library_libraries,
292    should_use_pic = _should_use_pic,
293    tokenize = _tokenize,
294)
295# LINT.ThenChange(https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl:forked_exports)
296