xref: /aosp_15_r20/external/bazelbuild-rules_cc/cc/private/toolchain/unix_cc_configure.bzl (revision eed53cd41c5909d05eedc7ad9720bb158fd93452)
1# pylint: disable=g-bad-file-header
2# Copyright 2016 The Bazel Authors. All rights reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#    http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Configuring the C++ toolchain on Unix platforms."""
16
17load(
18    ":lib_cc_configure.bzl",
19    "auto_configure_fail",
20    "auto_configure_warning",
21    "auto_configure_warning_maybe",
22    "escape_string",
23    "get_env_var",
24    "get_starlark_list",
25    "resolve_labels",
26    "split_escaped",
27    "which",
28    "write_builtin_include_directory_paths",
29)
30
31def _uniq(iterable):
32    """Remove duplicates from a list."""
33
34    unique_elements = {element: None for element in iterable}
35    return unique_elements.keys()
36
37def _prepare_include_path(repo_ctx, path):
38    """Resolve and sanitize include path before outputting it into the crosstool.
39
40    Args:
41      repo_ctx: repository_ctx object.
42      path: an include path to be sanitized.
43
44    Returns:
45      Sanitized include path that can be written to the crosstoot. Resulting path
46      is absolute if it is outside the repository and relative otherwise.
47    """
48
49    repo_root = str(repo_ctx.path("."))
50
51    # We're on UNIX, so the path delimiter is '/'.
52    repo_root += "/"
53    path = str(repo_ctx.path(path))
54    if path.startswith(repo_root):
55        return escape_string(path[len(repo_root):])
56    return escape_string(path)
57
58def _find_tool(repository_ctx, tool, overriden_tools):
59    """Find a tool for repository, taking overriden tools into account."""
60    if tool in overriden_tools:
61        return overriden_tools[tool]
62    return which(repository_ctx, tool, "/usr/bin/" + tool)
63
64def _get_tool_paths(repository_ctx, overriden_tools):
65    """Compute the %-escaped path to the various tools"""
66    return dict({
67        k: escape_string(_find_tool(repository_ctx, k, overriden_tools))
68        for k in [
69            "ar",
70            "ld",
71            "cpp",
72            "gcc",
73            "dwp",
74            "gcov",
75            "nm",
76            "objcopy",
77            "objdump",
78            "strip",
79        ]
80    }.items())
81
82def _escaped_cplus_include_paths(repository_ctx):
83    """Use ${CPLUS_INCLUDE_PATH} to compute the %-escaped list of flags for cxxflag."""
84    if "CPLUS_INCLUDE_PATH" in repository_ctx.os.environ:
85        result = []
86        for p in repository_ctx.os.environ["CPLUS_INCLUDE_PATH"].split(":"):
87            p = escape_string(str(repository_ctx.path(p)))  # Normalize the path
88            result.append("-I" + p)
89        return result
90    else:
91        return []
92
93_INC_DIR_MARKER_BEGIN = "#include <...>"
94
95# OSX add " (framework directory)" at the end of line, strip it.
96_OSX_FRAMEWORK_SUFFIX = " (framework directory)"
97_OSX_FRAMEWORK_SUFFIX_LEN = len(_OSX_FRAMEWORK_SUFFIX)
98
99def _cxx_inc_convert(path):
100    """Convert path returned by cc -E xc++ in a complete path. Doesn't %-escape the path!"""
101    path = path.strip()
102    if path.endswith(_OSX_FRAMEWORK_SUFFIX):
103        path = path[:-_OSX_FRAMEWORK_SUFFIX_LEN].strip()
104    return path
105
106def get_escaped_cxx_inc_directories(repository_ctx, cc, lang_flag, additional_flags = []):
107    """Compute the list of default %-escaped C++ include directories.
108
109    Args:
110      repository_ctx: The repository context.
111      cc: path to the C compiler.
112      lang_flag: value for the language flag (c, c++).
113      additional_flags: additional flags to pass to cc.
114    Returns:
115      a list of escaped system include directories.
116    """
117    result = repository_ctx.execute([cc, "-E", lang_flag, "-", "-v"] + additional_flags)
118    index1 = result.stderr.find(_INC_DIR_MARKER_BEGIN)
119    if index1 == -1:
120        return []
121    index1 = result.stderr.find("\n", index1)
122    if index1 == -1:
123        return []
124    index2 = result.stderr.rfind("\n ")
125    if index2 == -1 or index2 < index1:
126        return []
127    index2 = result.stderr.find("\n", index2 + 1)
128    if index2 == -1:
129        inc_dirs = result.stderr[index1 + 1:]
130    else:
131        inc_dirs = result.stderr[index1 + 1:index2].strip()
132
133    inc_directories = [
134        _prepare_include_path(repository_ctx, _cxx_inc_convert(p))
135        for p in inc_dirs.split("\n")
136    ]
137
138    if _is_compiler_option_supported(repository_ctx, cc, "-print-resource-dir"):
139        resource_dir = repository_ctx.execute(
140            [cc, "-print-resource-dir"],
141        ).stdout.strip() + "/share"
142        inc_directories.append(_prepare_include_path(repository_ctx, resource_dir))
143
144    return inc_directories
145
146def _is_compiler_option_supported(repository_ctx, cc, option):
147    """Checks that `option` is supported by the C compiler. Doesn't %-escape the option."""
148    result = repository_ctx.execute([
149        cc,
150        option,
151        "-o",
152        "/dev/null",
153        "-c",
154        str(repository_ctx.path("tools/cpp/empty.cc")),
155    ])
156    return result.stderr.find(option) == -1
157
158def _is_linker_option_supported(repository_ctx, cc, option, pattern):
159    """Checks that `option` is supported by the C linker. Doesn't %-escape the option."""
160    result = repository_ctx.execute([
161        cc,
162        option,
163        "-o",
164        "/dev/null",
165        str(repository_ctx.path("tools/cpp/empty.cc")),
166    ])
167    return result.stderr.find(pattern) == -1
168
169def _find_gold_linker_path(repository_ctx, cc):
170    """Checks if `gold` is supported by the C compiler.
171
172    Args:
173      repository_ctx: repository_ctx.
174      cc: path to the C compiler.
175
176    Returns:
177      String to put as value to -fuse-ld= flag, or None if gold couldn't be found.
178    """
179    result = repository_ctx.execute([
180        cc,
181        str(repository_ctx.path("tools/cpp/empty.cc")),
182        "-o",
183        "/dev/null",
184        # Some macos clang versions don't fail when setting -fuse-ld=gold, adding
185        # these lines to force it to. This also means that we will not detect
186        # gold when only a very old (year 2010 and older) is present.
187        "-Wl,--start-lib",
188        "-Wl,--end-lib",
189        "-fuse-ld=gold",
190        "-v",
191    ])
192    if result.return_code != 0:
193        return None
194
195    for line in result.stderr.splitlines():
196        if line.find("gold") == -1:
197            continue
198        for flag in line.split(" "):
199            if flag.find("gold") == -1:
200                continue
201            if flag.find("--enable-gold") > -1 or flag.find("--with-plugin-ld") > -1:
202                # skip build configuration options of gcc itself
203                # TODO(hlopko): Add redhat-like worker on the CI (#9392)
204                continue
205
206            # flag is '-fuse-ld=gold' for GCC or "/usr/lib/ld.gold" for Clang
207            # strip space, single quote, and double quotes
208            flag = flag.strip(" \"'")
209
210            # remove -fuse-ld= from GCC output so we have only the flag value part
211            flag = flag.replace("-fuse-ld=", "")
212            return flag
213    auto_configure_warning(
214        "CC with -fuse-ld=gold returned 0, but its -v output " +
215        "didn't contain 'gold', falling back to the default linker.",
216    )
217    return None
218
219def _add_compiler_option_if_supported(repository_ctx, cc, option):
220    """Returns `[option]` if supported, `[]` otherwise. Doesn't %-escape the option."""
221    return [option] if _is_compiler_option_supported(repository_ctx, cc, option) else []
222
223def _add_linker_option_if_supported(repository_ctx, cc, option, pattern):
224    """Returns `[option]` if supported, `[]` otherwise. Doesn't %-escape the option."""
225    return [option] if _is_linker_option_supported(repository_ctx, cc, option, pattern) else []
226
227def _get_no_canonical_prefixes_opt(repository_ctx, cc):
228    # If the compiler sometimes rewrites paths in the .d files without symlinks
229    # (ie when they're shorter), it confuses Bazel's logic for verifying all
230    # #included header files are listed as inputs to the action.
231
232    # The '-fno-canonical-system-headers' should be enough, but clang does not
233    # support it, so we also try '-no-canonical-prefixes' if first option does
234    # not work.
235    opt = _add_compiler_option_if_supported(
236        repository_ctx,
237        cc,
238        "-fno-canonical-system-headers",
239    )
240    if len(opt) == 0:
241        return _add_compiler_option_if_supported(
242            repository_ctx,
243            cc,
244            "-no-canonical-prefixes",
245        )
246    return opt
247
248def get_env(repository_ctx):
249    """Convert the environment in a list of export if in Homebrew. Doesn't %-escape the result!
250
251    Args:
252      repository_ctx: The repository context.
253    Returns:
254      empty string or a list of exports in case we're running with homebrew. Don't ask me why.
255    """
256    env = repository_ctx.os.environ
257    if "HOMEBREW_RUBY_PATH" in env:
258        return "\n".join([
259            "export %s='%s'" % (k, env[k].replace("'", "'\\''"))
260            for k in env
261            if k != "_" and k.find(".") == -1
262        ])
263    else:
264        return ""
265
266def _coverage_flags(repository_ctx, darwin):
267    use_llvm_cov = "1" == get_env_var(
268        repository_ctx,
269        "BAZEL_USE_LLVM_NATIVE_COVERAGE",
270        default = "0",
271        enable_warning = False,
272    )
273    if darwin or use_llvm_cov:
274        compile_flags = '"-fprofile-instr-generate",  "-fcoverage-mapping"'
275        link_flags = '"-fprofile-instr-generate"'
276    else:
277        # gcc requires --coverage being passed for compilation and linking
278        # https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html#Instrumentation-Options
279        compile_flags = '"--coverage"'
280        link_flags = '"--coverage"'
281    return compile_flags, link_flags
282
283def _find_generic(repository_ctx, name, env_name, overriden_tools, warn = False, silent = False):
284    """Find a generic C++ toolchain tool. Doesn't %-escape the result."""
285
286    if name in overriden_tools:
287        return overriden_tools[name]
288
289    result = name
290    env_value = repository_ctx.os.environ.get(env_name)
291    env_value_with_paren = ""
292    if env_value != None:
293        env_value = env_value.strip()
294        if env_value:
295            result = env_value
296            env_value_with_paren = " (%s)" % env_value
297    if result.startswith("/"):
298        # Absolute path, maybe we should make this suported by our which function.
299        return result
300    result = repository_ctx.which(result)
301    if result == None:
302        msg = ("Cannot find %s or %s%s; either correct your path or set the %s" +
303               " environment variable") % (name, env_name, env_value_with_paren, env_name)
304        if warn:
305            if not silent:
306                auto_configure_warning(msg)
307        else:
308            auto_configure_fail(msg)
309    return result
310
311def find_cc(repository_ctx, overriden_tools):
312    return _find_generic(repository_ctx, "gcc", "CC", overriden_tools)
313
314def configure_unix_toolchain(repository_ctx, cpu_value, overriden_tools):
315    """Configure C++ toolchain on Unix platforms.
316
317    Args:
318      repository_ctx: The repository context.
319      cpu_value: current cpu name.
320      overriden_tools: overriden tools.
321    """
322    paths = resolve_labels(repository_ctx, [
323        "@rules_cc//cc/private/toolchain:BUILD.tpl",
324        "@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl",
325        "@rules_cc//cc/private/toolchain:unix_cc_toolchain_config.bzl",
326        "@rules_cc//cc/private/toolchain:linux_cc_wrapper.sh.tpl",
327        "@rules_cc//cc/private/toolchain:osx_cc_wrapper.sh.tpl",
328    ])
329
330    repository_ctx.symlink(
331        paths["@rules_cc//cc/private/toolchain:unix_cc_toolchain_config.bzl"],
332        "cc_toolchain_config.bzl",
333    )
334
335    repository_ctx.symlink(
336        paths["@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl"],
337        "armeabi_cc_toolchain_config.bzl",
338    )
339
340    repository_ctx.file("tools/cpp/empty.cc", "int main() {}")
341    darwin = cpu_value == "darwin"
342
343    cc = _find_generic(repository_ctx, "gcc", "CC", overriden_tools)
344    overriden_tools = dict(overriden_tools)
345    overriden_tools["gcc"] = cc
346    overriden_tools["gcov"] = _find_generic(
347        repository_ctx,
348        "gcov",
349        "GCOV",
350        overriden_tools,
351        warn = True,
352        silent = True,
353    )
354    if darwin:
355        overriden_tools["gcc"] = "cc_wrapper.sh"
356        overriden_tools["ar"] = "/usr/bin/libtool"
357    auto_configure_warning_maybe(repository_ctx, "CC used: " + str(cc))
358    tool_paths = _get_tool_paths(repository_ctx, overriden_tools)
359    cc_toolchain_identifier = escape_string(get_env_var(
360        repository_ctx,
361        "CC_TOOLCHAIN_NAME",
362        "local",
363        False,
364    ))
365
366    cc_wrapper_src = (
367        "@rules_cc//cc/private/toolchain:osx_cc_wrapper.sh.tpl" if darwin else "@rules_cc//cc/private/toolchain:linux_cc_wrapper.sh.tpl"
368    )
369    repository_ctx.template(
370        "cc_wrapper.sh",
371        paths[cc_wrapper_src],
372        {
373            "%{cc}": escape_string(str(cc)),
374            "%{env}": escape_string(get_env(repository_ctx)),
375        },
376    )
377
378    cxx_opts = split_escaped(get_env_var(
379        repository_ctx,
380        "BAZEL_CXXOPTS",
381        "-std=c++0x",
382        False,
383    ), ":")
384
385    bazel_linklibs = "-lstdc++:-lm"
386    bazel_linkopts = ""
387    link_opts = split_escaped(get_env_var(
388        repository_ctx,
389        "BAZEL_LINKOPTS",
390        bazel_linkopts,
391        False,
392    ), ":")
393    link_libs = split_escaped(get_env_var(
394        repository_ctx,
395        "BAZEL_LINKLIBS",
396        bazel_linklibs,
397        False,
398    ), ":")
399    gold_linker_path = _find_gold_linker_path(repository_ctx, cc)
400    cc_path = repository_ctx.path(cc)
401    if not str(cc_path).startswith(str(repository_ctx.path(".")) + "/"):
402        # cc is outside the repository, set -B
403        bin_search_flag = ["-B" + escape_string(str(cc_path.dirname))]
404    else:
405        # cc is inside the repository, don't set -B.
406        bin_search_flag = []
407
408    coverage_compile_flags, coverage_link_flags = _coverage_flags(repository_ctx, darwin)
409    builtin_include_directories = _uniq(
410        get_escaped_cxx_inc_directories(repository_ctx, cc, "-xc") +
411        get_escaped_cxx_inc_directories(repository_ctx, cc, "-xc++", cxx_opts) +
412        get_escaped_cxx_inc_directories(
413            repository_ctx,
414            cc,
415            "-xc",
416            _get_no_canonical_prefixes_opt(repository_ctx, cc),
417        ) +
418        get_escaped_cxx_inc_directories(
419            repository_ctx,
420            cc,
421            "-xc++",
422            cxx_opts + _get_no_canonical_prefixes_opt(repository_ctx, cc),
423        ),
424    )
425
426    write_builtin_include_directory_paths(repository_ctx, cc, builtin_include_directories)
427    repository_ctx.template(
428        "BUILD",
429        paths["@rules_cc//cc/private/toolchain:BUILD.tpl"],
430        {
431            "%{abi_libc_version}": escape_string(get_env_var(
432                repository_ctx,
433                "ABI_LIBC_VERSION",
434                "local",
435                False,
436            )),
437            "%{abi_version}": escape_string(get_env_var(
438                repository_ctx,
439                "ABI_VERSION",
440                "local",
441                False,
442            )),
443            "%{cc_compiler_deps}": get_starlark_list([":builtin_include_directory_paths"] + (
444                [":cc_wrapper"] if darwin else []
445            )),
446            "%{cc_toolchain_identifier}": cc_toolchain_identifier,
447            "%{compile_flags}": get_starlark_list(
448                [
449                    # Security hardening requires optimization.
450                    # We need to undef it as some distributions now have it enabled by default.
451                    "-U_FORTIFY_SOURCE",
452                    "-fstack-protector",
453                    # All warnings are enabled. Maybe enable -Werror as well?
454                    "-Wall",
455                    # Enable a few more warnings that aren't part of -Wall.
456                ] + (
457                    _add_compiler_option_if_supported(repository_ctx, cc, "-Wthread-safety") +
458                    _add_compiler_option_if_supported(repository_ctx, cc, "-Wself-assign")
459                ) + (
460                    # Disable problematic warnings.
461                    _add_compiler_option_if_supported(repository_ctx, cc, "-Wunused-but-set-parameter") +
462                    # has false positives
463                    _add_compiler_option_if_supported(repository_ctx, cc, "-Wno-free-nonheap-object") +
464                    # Enable coloring even if there's no attached terminal. Bazel removes the
465                    # escape sequences if --nocolor is specified.
466                    _add_compiler_option_if_supported(repository_ctx, cc, "-fcolor-diagnostics")
467                ) + [
468                    # Keep stack frames for debugging, even in opt mode.
469                    "-fno-omit-frame-pointer",
470                ],
471            ),
472            "%{compiler}": escape_string(get_env_var(
473                repository_ctx,
474                "BAZEL_COMPILER",
475                "compiler",
476                False,
477            )),
478            "%{coverage_compile_flags}": coverage_compile_flags,
479            "%{coverage_link_flags}": coverage_link_flags,
480            "%{cxx_builtin_include_directories}": get_starlark_list(builtin_include_directories),
481            "%{cxx_flags}": get_starlark_list(cxx_opts + _escaped_cplus_include_paths(repository_ctx)),
482            "%{dbg_compile_flags}": get_starlark_list(["-g"]),
483            "%{host_system_name}": escape_string(get_env_var(
484                repository_ctx,
485                "BAZEL_HOST_SYSTEM",
486                "local",
487                False,
488            )),
489            "%{link_flags}": get_starlark_list((
490                ["-fuse-ld=" + gold_linker_path] if gold_linker_path else []
491            ) + _add_linker_option_if_supported(
492                repository_ctx,
493                cc,
494                "-Wl,-no-as-needed",
495                "-no-as-needed",
496            ) + _add_linker_option_if_supported(
497                repository_ctx,
498                cc,
499                "-Wl,-z,relro,-z,now",
500                "-z",
501            ) + (
502                [
503                    "-undefined",
504                    "dynamic_lookup",
505                    "-headerpad_max_install_names",
506                ] if darwin else bin_search_flag + [
507                    # Gold linker only? Can we enable this by default?
508                    # "-Wl,--warn-execstack",
509                    # "-Wl,--detect-odr-violations"
510                ] + _add_compiler_option_if_supported(
511                    # Have gcc return the exit code from ld.
512                    repository_ctx,
513                    cc,
514                    "-pass-exit-codes",
515                )
516            ) + link_opts),
517            "%{link_libs}": get_starlark_list(link_libs),
518            "%{name}": cpu_value,
519            "%{opt_compile_flags}": get_starlark_list(
520                [
521                    # No debug symbols.
522                    # Maybe we should enable https://gcc.gnu.org/wiki/DebugFission for opt or
523                    # even generally? However, that can't happen here, as it requires special
524                    # handling in Bazel.
525                    "-g0",
526
527                    # Conservative choice for -O
528                    # -O3 can increase binary size and even slow down the resulting binaries.
529                    # Profile first and / or use FDO if you need better performance than this.
530                    "-O2",
531
532                    # Security hardening on by default.
533                    # Conservative choice; -D_FORTIFY_SOURCE=2 may be unsafe in some cases.
534                    "-D_FORTIFY_SOURCE=1",
535
536                    # Disable assertions
537                    "-DNDEBUG",
538
539                    # Removal of unused code and data at link time (can this increase binary
540                    # size in some cases?).
541                    "-ffunction-sections",
542                    "-fdata-sections",
543                ],
544            ),
545            "%{opt_link_flags}": get_starlark_list(
546                [] if darwin else _add_linker_option_if_supported(
547                    repository_ctx,
548                    cc,
549                    "-Wl,--gc-sections",
550                    "-gc-sections",
551                ),
552            ),
553            "%{supports_param_files}": "0" if darwin else "1",
554            "%{supports_start_end_lib}": "True" if gold_linker_path else "False",
555            "%{target_cpu}": escape_string(get_env_var(
556                repository_ctx,
557                "BAZEL_TARGET_CPU",
558                cpu_value,
559                False,
560            )),
561            "%{target_libc}": "macosx" if darwin else escape_string(get_env_var(
562                repository_ctx,
563                "BAZEL_TARGET_LIBC",
564                "local",
565                False,
566            )),
567            "%{target_system_name}": escape_string(get_env_var(
568                repository_ctx,
569                "BAZEL_TARGET_SYSTEM",
570                "local",
571                False,
572            )),
573            "%{tool_paths}": ",\n        ".join(
574                ['"%s": "%s"' % (k, v) for k, v in tool_paths.items()],
575            ),
576            "%{unfiltered_compile_flags}": get_starlark_list(
577                _get_no_canonical_prefixes_opt(repository_ctx, cc) + [
578                    # Make C++ compilation deterministic. Use linkstamping instead of these
579                    # compiler symbols.
580                    "-Wno-builtin-macro-redefined",
581                    "-D__DATE__=\\\"redacted\\\"",
582                    "-D__TIMESTAMP__=\\\"redacted\\\"",
583                    "-D__TIME__=\\\"redacted\\\"",
584                ],
585            ),
586        },
587    )
588