• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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    "execute",
24    "get_env_var",
25    "get_starlark_list",
26    "resolve_labels",
27    "split_escaped",
28    "which",
29    "write_builtin_include_directory_paths",
30)
31
32def _uniq(iterable):
33    """Remove duplicates from a list."""
34
35    unique_elements = {element: None for element in iterable}
36    return unique_elements.keys()
37
38def _generate_system_module_map(repository_ctx, dirs, script_path):
39    return execute(repository_ctx, [script_path] + dirs)
40
41def _prepare_include_path(repo_ctx, path):
42    """Resolve include path before outputting it into the crosstool.
43
44    Args:
45      repo_ctx: repository_ctx object.
46      path: an include path to be resolved.
47
48    Returns:
49      Resolved include path. Resulting path is absolute if it is outside the
50      repository and relative otherwise.
51    """
52
53    repo_root = str(repo_ctx.path("."))
54
55    # We're on UNIX, so the path delimiter is '/'.
56    repo_root += "/"
57    path = str(repo_ctx.path(path))
58    if path.startswith(repo_root):
59        return path[len(repo_root):]
60    return path
61
62def _find_tool(repository_ctx, tool, overridden_tools):
63    """Find a tool for repository, taking overridden tools into account."""
64    if tool in overridden_tools:
65        return overridden_tools[tool]
66    return which(repository_ctx, tool, "/usr/bin/" + tool)
67
68def _get_tool_paths(repository_ctx, overridden_tools):
69    """Compute the %-escaped path to the various tools"""
70    return dict({
71        k: escape_string(_find_tool(repository_ctx, k, overridden_tools))
72        for k in [
73            "ar",
74            "ld",
75            "llvm-cov",
76            "llvm-profdata",
77            "cpp",
78            "gcc",
79            "dwp",
80            "gcov",
81            "nm",
82            "objcopy",
83            "objdump",
84            "strip",
85            "c++filt",
86        ]
87    }.items())
88
89def _escaped_cplus_include_paths(repository_ctx):
90    """Use ${CPLUS_INCLUDE_PATH} to compute the %-escaped list of flags for cxxflag."""
91    if "CPLUS_INCLUDE_PATH" in repository_ctx.os.environ:
92        result = []
93        for p in repository_ctx.os.environ["CPLUS_INCLUDE_PATH"].split(":"):
94            p = escape_string(str(repository_ctx.path(p)))  # Normalize the path
95            result.append("-I" + p)
96        return result
97    else:
98        return []
99
100_INC_DIR_MARKER_BEGIN = "#include <...>"
101
102# OSX add " (framework directory)" at the end of line, strip it.
103_OSX_FRAMEWORK_SUFFIX = " (framework directory)"
104_OSX_FRAMEWORK_SUFFIX_LEN = len(_OSX_FRAMEWORK_SUFFIX)
105
106def _cxx_inc_convert(path):
107    """Convert path returned by cc -E xc++ in a complete path. Doesn't %-escape the path!"""
108    path = path.strip()
109    if path.endswith(_OSX_FRAMEWORK_SUFFIX):
110        path = path[:-_OSX_FRAMEWORK_SUFFIX_LEN].strip()
111    return path
112
113def _get_cxx_include_directories(repository_ctx, print_resource_dir_supported, cc, lang_flag, additional_flags = []):
114    """Compute the list of C++ include directories."""
115    result = repository_ctx.execute([cc, "-E", lang_flag, "-", "-v"] + additional_flags)
116    index1 = result.stderr.find(_INC_DIR_MARKER_BEGIN)
117    if index1 == -1:
118        return []
119    index1 = result.stderr.find("\n", index1)
120    if index1 == -1:
121        return []
122    index2 = result.stderr.rfind("\n ")
123    if index2 == -1 or index2 < index1:
124        return []
125    index2 = result.stderr.find("\n", index2 + 1)
126    if index2 == -1:
127        inc_dirs = result.stderr[index1 + 1:]
128    else:
129        inc_dirs = result.stderr[index1 + 1:index2].strip()
130
131    inc_directories = [
132        _prepare_include_path(repository_ctx, _cxx_inc_convert(p))
133        for p in inc_dirs.split("\n")
134    ]
135
136    if print_resource_dir_supported:
137        resource_dir = repository_ctx.execute(
138            [cc, "-print-resource-dir"] + additional_flags,
139        ).stdout.strip() + "/share"
140        inc_directories.append(_prepare_include_path(repository_ctx, resource_dir))
141
142    return inc_directories
143
144def _is_compiler_option_supported(repository_ctx, cc, option):
145    """Checks that `option` is supported by the C compiler. Doesn't %-escape the option."""
146    result = repository_ctx.execute([
147        cc,
148        option,
149        "-o",
150        "/dev/null",
151        "-c",
152        str(repository_ctx.path("tools/cpp/empty.cc")),
153    ])
154    return result.stderr.find(option) == -1
155
156def _is_linker_option_supported(repository_ctx, cc, force_linker_flags, option, pattern):
157    """Checks that `option` is supported by the C linker. Doesn't %-escape the option."""
158    result = repository_ctx.execute([cc] + force_linker_flags + [
159        option,
160        "-o",
161        "/dev/null",
162        str(repository_ctx.path("tools/cpp/empty.cc")),
163    ])
164    return result.stderr.find(pattern) == -1
165
166def _find_linker_path(repository_ctx, cc, linker, is_clang):
167    """Checks if a given linker is supported by the C compiler.
168
169    Args:
170      repository_ctx: repository_ctx.
171      cc: path to the C compiler.
172      linker: linker to find
173      is_clang: whether the compiler is known to be clang
174
175    Returns:
176      String to put as value to -fuse-ld= flag, or None if linker couldn't be found.
177    """
178    result = repository_ctx.execute([
179        cc,
180        str(repository_ctx.path("tools/cpp/empty.cc")),
181        "-o",
182        "/dev/null",
183        # Some macOS clang versions don't fail when setting -fuse-ld=gold, adding
184        # these lines to force it to. This also means that we will not detect
185        # gold when only a very old (year 2010 and older) is present.
186        "-Wl,--start-lib",
187        "-Wl,--end-lib",
188        "-fuse-ld=" + linker,
189        "-v",
190    ])
191    if result.return_code != 0:
192        return None
193
194    if not is_clang:
195        return linker
196
197    # Extract linker path from:
198    # /usr/bin/clang ...
199    # "/usr/bin/ld.lld" -pie -z ...
200    linker_command = result.stderr.splitlines()[-1]
201    return linker_command.strip().split(" ")[0].strip("\"'")
202
203def _add_compiler_option_if_supported(repository_ctx, cc, option):
204    """Returns `[option]` if supported, `[]` otherwise. Doesn't %-escape the option."""
205    return [option] if _is_compiler_option_supported(repository_ctx, cc, option) else []
206
207def _add_linker_option_if_supported(repository_ctx, cc, force_linker_flags, option, pattern):
208    """Returns `[option]` if supported, `[]` otherwise. Doesn't %-escape the option."""
209    return [option] if _is_linker_option_supported(repository_ctx, cc, force_linker_flags, option, pattern) else []
210
211def _get_no_canonical_prefixes_opt(repository_ctx, cc):
212    # If the compiler sometimes rewrites paths in the .d files without symlinks
213    # (ie when they're shorter), it confuses Bazel's logic for verifying all
214    # #included header files are listed as inputs to the action.
215
216    # The '-fno-canonical-system-headers' should be enough, but clang does not
217    # support it, so we also try '-no-canonical-prefixes' if first option does
218    # not work.
219    opt = _add_compiler_option_if_supported(
220        repository_ctx,
221        cc,
222        "-fno-canonical-system-headers",
223    )
224    if len(opt) == 0:
225        return _add_compiler_option_if_supported(
226            repository_ctx,
227            cc,
228            "-no-canonical-prefixes",
229        )
230    return opt
231
232def get_env(repository_ctx):
233    """Convert the environment in a list of export if in Homebrew. Doesn't %-escape the result!
234
235    Args:
236      repository_ctx: The repository context.
237    Returns:
238      empty string or a list of exports in case we're running with homebrew. Don't ask me why.
239    """
240    env = repository_ctx.os.environ
241    if "HOMEBREW_RUBY_PATH" in env:
242        return "\n".join([
243            "export %s='%s'" % (k, env[k].replace("'", "'\\''"))
244            for k in env
245            if k != "_" and k.find(".") == -1
246        ])
247    else:
248        return ""
249
250def _coverage_flags(repository_ctx, darwin):
251    use_llvm_cov = "1" == get_env_var(
252        repository_ctx,
253        "BAZEL_USE_LLVM_NATIVE_COVERAGE",
254        default = "0",
255        enable_warning = False,
256    )
257    if darwin or use_llvm_cov:
258        compile_flags = '"-fprofile-instr-generate",  "-fcoverage-mapping"'
259        link_flags = '"-fprofile-instr-generate"'
260    else:
261        # gcc requires --coverage being passed for compilation and linking
262        # https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html#Instrumentation-Options
263        compile_flags = '"--coverage"'
264        link_flags = '"--coverage"'
265    return compile_flags, link_flags
266
267def _is_clang(repository_ctx, cc):
268    return "clang" in repository_ctx.execute([cc, "-v"]).stderr
269
270def _is_gcc(repository_ctx, cc):
271    # GCC's version output uses the basename of argv[0] as the program name:
272    # https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=gcc/gcc.cc;h=158461167951c1b9540322fb19be6a89d6da07fc;hb=HEAD#l8728
273    cc_stdout = repository_ctx.execute([cc, "--version"]).stdout
274    return cc_stdout.startswith("gcc ") or cc_stdout.startswith("gcc-")
275
276def _get_compiler_name(repository_ctx, cc):
277    if _is_clang(repository_ctx, cc):
278        return "clang"
279    if _is_gcc(repository_ctx, cc):
280        return "gcc"
281    return "compiler"
282
283def _find_generic(repository_ctx, name, env_name, overridden_tools, warn = False, silent = False):
284    """Find a generic C++ toolchain tool. Doesn't %-escape the result."""
285
286    if name in overridden_tools:
287        return overridden_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 supported 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, overridden_tools):
312    """Find the C compiler (gcc or clang) for the repository, considering overridden tools.
313
314    Args:
315      repository_ctx: The repository context.
316      overridden_tools: A dictionary of overridden tools.
317
318    Returns:
319      The path to the C compiler.
320    """
321    cc = _find_generic(repository_ctx, "gcc", "CC", overridden_tools)
322    if _is_clang(repository_ctx, cc):
323        # If clang is run through a symlink with -no-canonical-prefixes, it does
324        # not find its own include directory, which includes the headers for
325        # libc++. Resolving the potential symlink here prevents this.
326        result = repository_ctx.execute(["readlink", "-f", cc])
327        if result.return_code == 0:
328            return result.stdout.strip()
329    return cc
330
331def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools):
332    """Configure C++ toolchain on Unix platforms.
333
334    Args:
335        repository_ctx: The repository context.
336        cpu_value: The CPU value.
337        overridden_tools: A dictionary of overridden tools.
338    """
339    paths = resolve_labels(repository_ctx, [
340        "@rules_cc//cc/private/toolchain:BUILD.tpl",
341        "@rules_cc//cc/private/toolchain:generate_system_module_map.sh",
342        "@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl",
343        "@rules_cc//cc/private/toolchain:unix_cc_toolchain_config.bzl",
344        "@rules_cc//cc/private/toolchain:linux_cc_wrapper.sh.tpl",
345        "@rules_cc//cc/private/toolchain:validate_static_library.sh.tpl",
346        "@rules_cc//cc/private/toolchain:osx_cc_wrapper.sh.tpl",
347        "@rules_cc//cc/private/toolchain:clang_deps_scanner_wrapper.sh.tpl",
348        "@rules_cc//cc/private/toolchain:gcc_deps_scanner_wrapper.sh.tpl",
349    ])
350
351    repository_ctx.symlink(
352        paths["@rules_cc//cc/private/toolchain:unix_cc_toolchain_config.bzl"],
353        "cc_toolchain_config.bzl",
354    )
355
356    repository_ctx.symlink(
357        paths["@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl"],
358        "armeabi_cc_toolchain_config.bzl",
359    )
360
361    repository_ctx.file("tools/cpp/empty.cc", "int main() {}")
362    darwin = cpu_value.startswith("darwin")
363    bsd = cpu_value == "freebsd" or cpu_value == "openbsd"
364
365    cc = find_cc(repository_ctx, overridden_tools)
366    is_clang = _is_clang(repository_ctx, cc)
367    overridden_tools = dict(overridden_tools)
368    overridden_tools["gcc"] = cc
369    overridden_tools["gcov"] = _find_generic(
370        repository_ctx,
371        "gcov",
372        "GCOV",
373        overridden_tools,
374        warn = True,
375        silent = True,
376    )
377    overridden_tools["llvm-cov"] = _find_generic(
378        repository_ctx,
379        "llvm-cov",
380        "BAZEL_LLVM_COV",
381        overridden_tools,
382        warn = True,
383        silent = True,
384    )
385    overridden_tools["llvm-profdata"] = _find_generic(
386        repository_ctx,
387        "llvm-profdata",
388        "BAZEL_LLVM_PROFDATA",
389        overridden_tools,
390        warn = True,
391        silent = True,
392    )
393    overridden_tools["ar"] = _find_generic(
394        repository_ctx,
395        "ar",
396        "AR",
397        overridden_tools,
398        warn = True,
399        silent = True,
400    )
401    if darwin:
402        overridden_tools["gcc"] = "cc_wrapper.sh"
403        overridden_tools["ar"] = _find_generic(repository_ctx, "libtool", "LIBTOOL", overridden_tools)
404
405    auto_configure_warning_maybe(repository_ctx, "CC used: " + str(cc))
406    tool_paths = _get_tool_paths(repository_ctx, overridden_tools)
407    tool_paths["cpp-module-deps-scanner"] = "deps_scanner_wrapper.sh"
408
409    # The parse_header tool needs to be a wrapper around the compiler as it has
410    # to touch the output file.
411    tool_paths["parse_headers"] = "cc_wrapper.sh"
412    cc_toolchain_identifier = escape_string(get_env_var(
413        repository_ctx,
414        "CC_TOOLCHAIN_NAME",
415        "local",
416        False,
417    ))
418
419    if "nm" in tool_paths and "c++filt" in tool_paths:
420        repository_ctx.template(
421            "validate_static_library.sh",
422            paths["@rules_cc//cc/private/toolchain:validate_static_library.sh.tpl"],
423            {
424                "%{c++filt}": escape_string(str(repository_ctx.path(tool_paths["c++filt"]))),
425                # Certain weak symbols are otherwise listed with type T in the output of nm on macOS.
426                "%{nm_extra_args}": "--no-weak" if darwin else "",
427                "%{nm}": escape_string(str(repository_ctx.path(tool_paths["nm"]))),
428            },
429        )
430        tool_paths["validate_static_library"] = "validate_static_library.sh"
431
432    cc_wrapper_src = (
433        "@rules_cc//cc/private/toolchain:osx_cc_wrapper.sh.tpl" if darwin else "@rules_cc//cc/private/toolchain:linux_cc_wrapper.sh.tpl"
434    )
435    repository_ctx.template(
436        "cc_wrapper.sh",
437        paths[cc_wrapper_src],
438        {
439            "%{cc}": escape_string(str(cc)),
440            "%{env}": escape_string(get_env(repository_ctx)),
441        },
442    )
443    deps_scanner_wrapper_src = (
444        "@rules_cc//cc/private/toolchain:clang_deps_scanner_wrapper.sh.tpl" if is_clang else "@rules_cc//cc/private/toolchain:gcc_deps_scanner_wrapper.sh.tpl"
445    )
446    deps_scanner = "cpp-module-deps-scanner_not_found"
447    if is_clang:
448        cc_str = str(cc)
449        path_arr = cc_str.split("/")[:-1]
450        path_arr.append("clang-scan-deps")
451        deps_scanner = "/".join(path_arr)
452    repository_ctx.template(
453        "deps_scanner_wrapper.sh",
454        paths[deps_scanner_wrapper_src],
455        {
456            "%{cc}": escape_string(str(cc)),
457            "%{deps_scanner}": escape_string(deps_scanner),
458            "%{env}": escape_string(get_env(repository_ctx)),
459        },
460    )
461
462    conly_opts = split_escaped(get_env_var(
463        repository_ctx,
464        "BAZEL_CONLYOPTS",
465        "",
466        False,
467    ), ":")
468
469    cxx_opts = split_escaped(get_env_var(
470        repository_ctx,
471        "BAZEL_CXXOPTS",
472        "-std=c++17",
473        False,
474    ), ":")
475
476    gold_or_lld_linker_path = (
477        _find_linker_path(repository_ctx, cc, "lld", is_clang) or
478        _find_linker_path(repository_ctx, cc, "gold", is_clang)
479    )
480    cc_path = repository_ctx.path(cc)
481    if not str(cc_path).startswith(str(repository_ctx.path(".")) + "/"):
482        # cc is outside the repository, set -B
483        bin_search_flags = ["-B" + escape_string(str(cc_path.dirname))]
484    else:
485        # cc is inside the repository, don't set -B.
486        bin_search_flags = []
487    if not gold_or_lld_linker_path:
488        ld_path = repository_ctx.path(tool_paths["ld"])
489        if ld_path.dirname != cc_path.dirname:
490            bin_search_flags.append("-B" + str(ld_path.dirname))
491    force_linker_flags = []
492    if gold_or_lld_linker_path:
493        force_linker_flags.append("-fuse-ld=" + gold_or_lld_linker_path)
494
495    # TODO: It's unclear why these flags aren't added on macOS.
496    if bin_search_flags and not darwin:
497        force_linker_flags.extend(bin_search_flags)
498    use_libcpp = darwin or bsd
499    is_as_needed_supported = _is_linker_option_supported(
500        repository_ctx,
501        cc,
502        force_linker_flags,
503        "-Wl,-no-as-needed",
504        "-no-as-needed",
505    )
506    is_push_state_supported = _is_linker_option_supported(
507        repository_ctx,
508        cc,
509        force_linker_flags,
510        "-Wl,--push-state",
511        "--push-state",
512    )
513    if use_libcpp:
514        bazel_default_libs = ["-lc++", "-lm"]
515    else:
516        bazel_default_libs = ["-lstdc++", "-lm"]
517    if is_as_needed_supported and is_push_state_supported:
518        # Do not link against C++ standard libraries unless they are actually
519        # used.
520        # We assume that --push-state support implies --pop-state support.
521        bazel_linklibs_elements = [
522            arg
523            for lib in bazel_default_libs
524            for arg in ["-Wl,--push-state,-as-needed", lib, "-Wl,--pop-state"]
525        ]
526    else:
527        bazel_linklibs_elements = bazel_default_libs
528    bazel_linklibs = ":".join(bazel_linklibs_elements)
529    bazel_linkopts = ""
530
531    link_opts = split_escaped(get_env_var(
532        repository_ctx,
533        "BAZEL_LINKOPTS",
534        bazel_linkopts,
535        False,
536    ), ":")
537    link_libs = split_escaped(get_env_var(
538        repository_ctx,
539        "BAZEL_LINKLIBS",
540        bazel_linklibs,
541        False,
542    ), ":")
543    coverage_compile_flags, coverage_link_flags = _coverage_flags(repository_ctx, darwin)
544    print_resource_dir_supported = _is_compiler_option_supported(
545        repository_ctx,
546        cc,
547        "-print-resource-dir",
548    )
549    no_canonical_prefixes_opt = _get_no_canonical_prefixes_opt(repository_ctx, cc)
550    builtin_include_directories = _uniq(
551        _get_cxx_include_directories(repository_ctx, print_resource_dir_supported, cc, "-xc", conly_opts) +
552        _get_cxx_include_directories(repository_ctx, print_resource_dir_supported, cc, "-xc++", cxx_opts) +
553        _get_cxx_include_directories(
554            repository_ctx,
555            print_resource_dir_supported,
556            cc,
557            "-xc++",
558            cxx_opts + ["-stdlib=libc++"],
559        ) +
560        _get_cxx_include_directories(
561            repository_ctx,
562            print_resource_dir_supported,
563            cc,
564            "-xc",
565            no_canonical_prefixes_opt,
566        ) +
567        _get_cxx_include_directories(
568            repository_ctx,
569            print_resource_dir_supported,
570            cc,
571            "-xc++",
572            cxx_opts + no_canonical_prefixes_opt,
573        ) +
574        _get_cxx_include_directories(
575            repository_ctx,
576            print_resource_dir_supported,
577            cc,
578            "-xc++",
579            cxx_opts + no_canonical_prefixes_opt + ["-stdlib=libc++"],
580        ) +
581        # Always included in case the user has Xcode + the CLT installed, both
582        # paths can be used interchangeably
583        ["/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"],
584    )
585
586    generate_modulemap = is_clang
587    if generate_modulemap:
588        repository_ctx.file("module.modulemap", _generate_system_module_map(
589            repository_ctx,
590            builtin_include_directories,
591            paths["@rules_cc//cc/private/toolchain:generate_system_module_map.sh"],
592        ))
593    extra_flags_per_feature = {}
594    if is_clang:
595        # Only supported by LLVM 14 and later, but required with C++20 and
596        # layering_check as C++ modules are the default.
597        # https://github.com/llvm/llvm-project/commit/0556138624edf48621dd49a463dbe12e7101f17d
598        result = repository_ctx.execute([
599            cc,
600            "-Xclang",
601            "-fno-cxx-modules",
602            "-o",
603            "/dev/null",
604            "-c",
605            str(repository_ctx.path("tools/cpp/empty.cc")),
606        ])
607        if "-fno-cxx-modules" not in result.stderr:
608            extra_flags_per_feature["use_module_maps"] = ["-Xclang", "-fno-cxx-modules"]
609
610    write_builtin_include_directory_paths(repository_ctx, cc, builtin_include_directories)
611    repository_ctx.template(
612        "BUILD",
613        paths["@rules_cc//cc/private/toolchain:BUILD.tpl"],
614        # @unsorted-dict-items
615        {
616            "%{abi_libc_version}": escape_string(get_env_var(
617                repository_ctx,
618                "ABI_LIBC_VERSION",
619                "local",
620                False,
621            )),
622            "%{abi_version}": escape_string(get_env_var(
623                repository_ctx,
624                "ABI_VERSION",
625                "local",
626                False,
627            )),
628            "%{cc_compiler_deps}": get_starlark_list([
629                ":builtin_include_directory_paths",
630                ":cc_wrapper",
631                ":deps_scanner_wrapper",
632            ] + (
633                [":validate_static_library"] if "validate_static_library" in tool_paths else []
634            )),
635            "%{cc_toolchain_identifier}": cc_toolchain_identifier,
636            "%{compile_flags}": get_starlark_list(
637                [
638                    "-fstack-protector",
639                    # All warnings are enabled.
640                    "-Wall",
641                    # Enable a few more warnings that aren't part of -Wall.
642                ] + ((
643                    _add_compiler_option_if_supported(repository_ctx, cc, "-Wthread-safety") +
644                    _add_compiler_option_if_supported(repository_ctx, cc, "-Wself-assign")
645                )) + (
646                    # Disable problematic warnings.
647                    _add_compiler_option_if_supported(repository_ctx, cc, "-Wunused-but-set-parameter") +
648                    # has false positives
649                    _add_compiler_option_if_supported(repository_ctx, cc, "-Wno-free-nonheap-object") +
650                    # Enable coloring even if there's no attached terminal. Bazel removes the
651                    # escape sequences if --nocolor is specified.
652                    _add_compiler_option_if_supported(repository_ctx, cc, "-fcolor-diagnostics")
653                ) + [
654                    # Keep stack frames for debugging, even in opt mode.
655                    "-fno-omit-frame-pointer",
656                ],
657            ),
658            "%{compiler}": escape_string(get_env_var(
659                repository_ctx,
660                "BAZEL_COMPILER",
661                _get_compiler_name(repository_ctx, cc),
662                False,
663            )),
664            "%{conly_flags}": get_starlark_list(conly_opts),
665            "%{coverage_compile_flags}": coverage_compile_flags,
666            "%{coverage_link_flags}": coverage_link_flags,
667            "%{cxx_builtin_include_directories}": get_starlark_list(builtin_include_directories),
668            "%{cxx_flags}": get_starlark_list(cxx_opts + _escaped_cplus_include_paths(repository_ctx)),
669            "%{dbg_compile_flags}": get_starlark_list(["-g"]),
670            "%{extra_flags_per_feature}": repr(extra_flags_per_feature),
671            "%{host_system_name}": escape_string(get_env_var(
672                repository_ctx,
673                "BAZEL_HOST_SYSTEM",
674                "local",
675                False,
676            )),
677            "%{link_flags}": get_starlark_list(force_linker_flags + (
678                ["-Wl,-no-as-needed"] if is_as_needed_supported else []
679            ) + _add_linker_option_if_supported(
680                repository_ctx,
681                cc,
682                force_linker_flags,
683                "-Wl,-z,relro,-z,now",
684                "-z",
685            ) + (
686                [
687                    "-headerpad_max_install_names",
688                ] if darwin else [
689                    # Gold linker only? Can we enable this by default?
690                    # "-Wl,--warn-execstack",
691                    # "-Wl,--detect-odr-violations"
692                ] + _add_compiler_option_if_supported(
693                    # Have gcc return the exit code from ld.
694                    repository_ctx,
695                    cc,
696                    "-pass-exit-codes",
697                )
698            ) + link_opts),
699            "%{link_libs}": get_starlark_list(link_libs),
700            "%{modulemap}": ("\":module.modulemap\"" if generate_modulemap else "None"),
701            "%{name}": cpu_value,
702            "%{opt_compile_flags}": get_starlark_list(
703                [
704                    # No debug symbols.
705                    # Maybe we should enable https://gcc.gnu.org/wiki/DebugFission for opt or
706                    # even generally? However, that can't happen here, as it requires special
707                    # handling in Bazel.
708                    "-g0",
709
710                    # Conservative choice for -O
711                    # -O3 can increase binary size and even slow down the resulting binaries.
712                    # Profile first and / or use FDO if you need better performance than this.
713                    "-O2",
714
715                    # Security hardening on by default.
716                    # Conservative choice; -D_FORTIFY_SOURCE=2 may be unsafe in some cases.
717                    "-D_FORTIFY_SOURCE=1",
718
719                    # Disable assertions
720                    "-DNDEBUG",
721
722                    # Removal of unused code and data at link time (can this increase binary
723                    # size in some cases?).
724                    "-ffunction-sections",
725                    "-fdata-sections",
726                ],
727            ),
728            "%{opt_link_flags}": get_starlark_list(
729                ["-Wl,-dead_strip"] if darwin else _add_linker_option_if_supported(
730                    repository_ctx,
731                    cc,
732                    force_linker_flags,
733                    "-Wl,--gc-sections",
734                    "-gc-sections",
735                ),
736            ),
737            "%{supports_start_end_lib}": "True" if gold_or_lld_linker_path else "False",
738            "%{target_cpu}": escape_string(get_env_var(
739                repository_ctx,
740                "BAZEL_TARGET_CPU",
741                cpu_value,
742                False,
743            )),
744            "%{target_libc}": "macosx" if darwin else escape_string(get_env_var(
745                repository_ctx,
746                "BAZEL_TARGET_LIBC",
747                "local",
748                False,
749            )),
750            "%{target_system_name}": escape_string(get_env_var(
751                repository_ctx,
752                "BAZEL_TARGET_SYSTEM",
753                "local",
754                False,
755            )),
756            "%{tool_paths}": ",\n        ".join(
757                ['"%s": "%s"' % (k, v) for k, v in tool_paths.items() if v != None],
758            ),
759            "%{unfiltered_compile_flags}": get_starlark_list(
760                _get_no_canonical_prefixes_opt(repository_ctx, cc) + [
761                    # Make C++ compilation deterministic. Use linkstamping instead of these
762                    # compiler symbols.
763                    "-Wno-builtin-macro-redefined",
764                    "-D__DATE__=\\\"redacted\\\"",
765                    "-D__TIMESTAMP__=\\\"redacted\\\"",
766                    "-D__TIME__=\\\"redacted\\\"",
767                ],
768            ),
769        },
770    )
771