| # pylint: disable=g-bad-file-header |
| # Copyright 2016 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. |
| """Configuring the C++ toolchain on Unix platforms.""" |
| |
| |
| load( |
| "@bazel_tools//tools/cpp:lib_cc_configure.bzl", |
| "escape_string", |
| "get_env_var", |
| "which", |
| "tpl", |
| ) |
| |
| |
| def _get_value(it): |
| """Convert `it` in serialized protobuf format.""" |
| if type(it) == "int": |
| return str(it) |
| elif type(it) == "bool": |
| return "true" if it else "false" |
| else: |
| return "\"%s\"" % it |
| |
| |
| def _build_crosstool(d, prefix=" "): |
| """Convert `d` to a string version of a CROSSTOOL file content.""" |
| lines = [] |
| for k in d: |
| if type(d[k]) == "list": |
| for it in d[k]: |
| lines.append("%s%s: %s" % (prefix, k, _get_value(it))) |
| else: |
| lines.append("%s%s: %s" % (prefix, k, _get_value(d[k]))) |
| return "\n".join(lines) |
| |
| |
| def _build_tool_path(d): |
| """Build the list of %-escaped tool_path for the CROSSTOOL file.""" |
| lines = [] |
| for k in d: |
| lines.append(" tool_path {name: \"%s\" path: \"%s\" }" % (k, escape_string(d[k]))) |
| return "\n".join(lines) |
| |
| |
| def _get_tool_paths(repository_ctx, darwin, cc): |
| """Compute the path to the various tools. Doesn't %-escape the result!""" |
| return {k: which(repository_ctx, k, "/usr/bin/" + k) |
| for k in [ |
| "ld", |
| "cpp", |
| "dwp", |
| "gcov", |
| "nm", |
| "objcopy", |
| "objdump", |
| "strip", |
| ]} + { |
| "gcc": cc, |
| "ar": "/usr/bin/libtool" |
| if darwin else which(repository_ctx, "ar", "/usr/bin/ar") |
| } |
| |
| |
| def _escaped_cplus_include_paths(repository_ctx): |
| """Use ${CPLUS_INCLUDE_PATH} to compute the %-escaped list of flags for cxxflag.""" |
| if "CPLUS_INCLUDE_PATH" in repository_ctx.os.environ: |
| result = [] |
| for p in repository_ctx.os.environ["CPLUS_INCLUDE_PATH"].split(":"): |
| p = escape_string(str(repository_ctx.path(p))) # Normalize the path |
| result.append("-I" + p) |
| return result |
| else: |
| return [] |
| |
| |
| _INC_DIR_MARKER_BEGIN = "#include <...>" |
| |
| # OSX add " (framework directory)" at the end of line, strip it. |
| _OSX_FRAMEWORK_SUFFIX = " (framework directory)" |
| _OSX_FRAMEWORK_SUFFIX_LEN = len(_OSX_FRAMEWORK_SUFFIX) |
| |
| def _cxx_inc_convert(path): |
| """Convert path returned by cc -E xc++ in a complete path. Doesn't %-escape the path!""" |
| path = path.strip() |
| if path.endswith(_OSX_FRAMEWORK_SUFFIX): |
| path = path[:-_OSX_FRAMEWORK_SUFFIX_LEN].strip() |
| return path |
| |
| |
| def get_escaped_cxx_inc_directories(repository_ctx, cc): |
| """Compute the list of default %-escaped C++ include directories.""" |
| result = repository_ctx.execute([cc, "-E", "-xc++", "-", "-v"]) |
| index1 = result.stderr.find(_INC_DIR_MARKER_BEGIN) |
| if index1 == -1: |
| return [] |
| index1 = result.stderr.find("\n", index1) |
| if index1 == -1: |
| return [] |
| index2 = result.stderr.rfind("\n ") |
| if index2 == -1 or index2 < index1: |
| return [] |
| index2 = result.stderr.find("\n", index2 + 1) |
| if index2 == -1: |
| inc_dirs = result.stderr[index1 + 1:] |
| else: |
| inc_dirs = result.stderr[index1 + 1:index2].strip() |
| |
| return [escape_string(repository_ctx.path(_cxx_inc_convert(p))) |
| for p in inc_dirs.split("\n")] |
| |
| |
| def _add_option_if_supported(repository_ctx, cc, option): |
| """Checks that `option` is supported by the C compiler. Doesn't %-escape the option.""" |
| result = repository_ctx.execute([ |
| cc, |
| option, |
| "-o", |
| "/dev/null", |
| "-c", |
| str(repository_ctx.path("tools/cpp/empty.cc")) |
| ]) |
| return [option] if result.stderr.find(option) == -1 else [] |
| |
| |
| def _is_gold_supported(repository_ctx, cc): |
| """Checks that `gold` is supported by the C compiler.""" |
| result = repository_ctx.execute([ |
| cc, |
| "-fuse-ld=gold", |
| "-o", |
| "/dev/null", |
| # Some macos clang versions don't fail when setting -fuse-ld=gold, adding |
| # these lines to force it to. This also means that we will not detect |
| # gold when only a very old (year 2010 and older) is present. |
| "-Wl,--start-lib", |
| "-Wl,--end-lib", |
| str(repository_ctx.path("tools/cpp/empty.cc")) |
| ]) |
| return result.return_code == 0 |
| |
| |
| def _crosstool_content(repository_ctx, cc, cpu_value, darwin): |
| """Return the content for the CROSSTOOL file, in a dictionary.""" |
| supports_gold_linker = _is_gold_supported(repository_ctx, cc) |
| return { |
| "abi_version": escape_string(get_env_var(repository_ctx, "ABI_VERSION", "local", False)), |
| "abi_libc_version": escape_string(get_env_var(repository_ctx, "ABI_LIBC_VERSION", "local", False)), |
| "builtin_sysroot": "", |
| "compiler": escape_string(get_env_var(repository_ctx, "BAZEL_COMPILER", "compiler", False)), |
| "host_system_name": escape_string(get_env_var(repository_ctx, "BAZEL_HOST_SYSTEM", "local", False)), |
| "needsPic": True, |
| "supports_gold_linker": supports_gold_linker, |
| "supports_incremental_linker": False, |
| "supports_fission": False, |
| "supports_interface_shared_objects": False, |
| "supports_normalizing_ar": False, |
| "supports_start_end_lib": supports_gold_linker, |
| "target_libc": "macosx" if darwin else escape_string(get_env_var(repository_ctx, "BAZEL_TARGET_LIBC", "local", False)), |
| "target_cpu": escape_string(get_env_var(repository_ctx, "BAZEL_TARGET_CPU", cpu_value, False)), |
| "target_system_name": escape_string(get_env_var(repository_ctx, "BAZEL_TARGET_SYSTEM", "local", False)), |
| "cxx_flag": [ |
| "-std=c++0x", |
| ] + _escaped_cplus_include_paths(repository_ctx), |
| "linker_flag": [ |
| "-lstdc++", |
| "-lm", # Some systems expect -lm in addition to -lstdc++ |
| # Anticipated future default. |
| ] + ( |
| ["-fuse-ld=gold"] if supports_gold_linker else [] |
| ) + _add_option_if_supported( |
| repository_ctx, cc, "-Wl,-no-as-needed" |
| ) + _add_option_if_supported( |
| repository_ctx, cc, "-Wl,-z,relro,-z,now" |
| ) + ([ |
| "-undefined", |
| "dynamic_lookup", |
| "-headerpad_max_install_names", |
| ] if darwin else [ |
| "-B" + str(repository_ctx.path(cc).dirname), |
| # Always have -B/usr/bin, see https://github.com/bazelbuild/bazel/issues/760. |
| "-B/usr/bin", |
| # Gold linker only? Can we enable this by default? |
| # "-Wl,--warn-execstack", |
| # "-Wl,--detect-odr-violations" |
| ] + _add_option_if_supported( |
| # Have gcc return the exit code from ld. |
| repository_ctx, cc, "-pass-exit-codes") |
| ), |
| "cxx_builtin_include_directory": get_escaped_cxx_inc_directories(repository_ctx, cc), |
| "objcopy_embed_flag": ["-I", "binary"], |
| "unfiltered_cxx_flag": |
| # If the compiler sometimes rewrites paths in the .d files without symlinks |
| # (ie when they're shorter), it confuses Bazel's logic for verifying all |
| # #included header files are listed as inputs to the action. |
| _add_option_if_supported(repository_ctx, cc, "-fno-canonical-system-headers") + [ |
| # Make C++ compilation deterministic. Use linkstamping instead of these |
| # compiler symbols. |
| "-Wno-builtin-macro-redefined", |
| "-D__DATE__=\\\"redacted\\\"", |
| "-D__TIMESTAMP__=\\\"redacted\\\"", |
| "-D__TIME__=\\\"redacted\\\"" |
| ], |
| "compiler_flag": [ |
| # Security hardening requires optimization. |
| # We need to undef it as some distributions now have it enabled by default. |
| "-U_FORTIFY_SOURCE", |
| "-fstack-protector", |
| # All warnings are enabled. Maybe enable -Werror as well? |
| "-Wall", |
| # Enable a few more warnings that aren't part of -Wall. |
| ] + (["-Wthread-safety", "-Wself-assign"] if darwin else [ |
| "-B" + escape_string(str(repository_ctx.path(cc).dirname)), |
| # Always have -B/usr/bin, see https://github.com/bazelbuild/bazel/issues/760. |
| "-B/usr/bin", |
| ]) + ( |
| # Disable problematic warnings. |
| _add_option_if_supported(repository_ctx, cc, "-Wunused-but-set-parameter") + |
| # has false positives |
| _add_option_if_supported(repository_ctx, cc, "-Wno-free-nonheap-object") + |
| # Enable coloring even if there's no attached terminal. Bazel removes the |
| # escape sequences if --nocolor is specified. |
| _add_option_if_supported(repository_ctx, cc, "-fcolor-diagnostics")) + [ |
| # Keep stack frames for debugging, even in opt mode. |
| "-fno-omit-frame-pointer", |
| ], |
| } |
| |
| |
| def _opt_content(darwin): |
| """Return the content of the opt specific section of the CROSSTOOL file.""" |
| return { |
| "compiler_flag": [ |
| # No debug symbols. |
| # Maybe we should enable https://gcc.gnu.org/wiki/DebugFission for opt or |
| # even generally? However, that can't happen here, as it requires special |
| # handling in Bazel. |
| "-g0", |
| |
| # Conservative choice for -O |
| # -O3 can increase binary size and even slow down the resulting binaries. |
| # Profile first and / or use FDO if you need better performance than this. |
| "-O2", |
| |
| # Security hardening on by default. |
| # Conservative choice; -D_FORTIFY_SOURCE=2 may be unsafe in some cases. |
| "-D_FORTIFY_SOURCE=1", |
| |
| # Disable assertions |
| "-DNDEBUG", |
| |
| # Removal of unused code and data at link time (can this increase binary size in some cases?). |
| "-ffunction-sections", |
| "-fdata-sections" |
| ], |
| "linker_flag": [] if darwin else ["-Wl,--gc-sections"] |
| } |
| |
| |
| def _dbg_content(): |
| """Return the content of the dbg specific section of the CROSSTOOL file.""" |
| # Enable debug symbols |
| return {"compiler_flag": "-g"} |
| |
| |
| def get_env(repository_ctx): |
| """Convert the environment in a list of export if in Homebrew. Doesn't %-escape the result!""" |
| env = repository_ctx.os.environ |
| if "HOMEBREW_RUBY_PATH" in env: |
| return "\n".join([ |
| "export %s='%s'" % (k, env[k].replace("'", "'\\''")) |
| for k in env |
| if k != "_" and k.find(".") == -1 |
| ]) |
| else: |
| return "" |
| |
| |
| def _coverage_feature(darwin): |
| if darwin: |
| compile_flags = """flag_group { |
| flag: '-fprofile-instr-generate' |
| flag: '-fcoverage-mapping' |
| }""" |
| link_flags = """flag_group { |
| flag: '-fprofile-instr-generate' |
| }""" |
| else: |
| compile_flags = """flag_group { |
| flag: '-fprofile-arcs' |
| flag: '-ftest-coverage' |
| }""" |
| link_flags = """flag_group { |
| flag: '-lgcov' |
| }""" |
| return """ |
| feature { |
| name: 'coverage' |
| provides: 'profile' |
| flag_set { |
| action: 'preprocess-assemble' |
| action: 'c-compile' |
| action: 'c++-compile' |
| action: 'c++-header-parsing' |
| action: 'c++-header-preprocessing' |
| action: 'c++-module-compile' |
| """ + compile_flags + """ |
| |
| |
| |
| } |
| flag_set { |
| action: 'c++-link-interface-dynamic-library' |
| action: 'c++-link-dynamic-library' |
| action: 'c++-link-executable' |
| """ + link_flags + """ |
| } |
| } |
| """ |
| |
| |
| def find_cc(repository_ctx): |
| """Find the C++ compiler. Doesn't %-escape the result.""" |
| cc_name = "gcc" |
| cc_environ = repository_ctx.os.environ.get("CC") |
| cc_paren = "" |
| if cc_environ != None: |
| cc_environ = cc_environ.strip() |
| if cc_environ: |
| cc_name = cc_environ |
| cc_paren = " (%s)" % cc_environ |
| if cc_name.startswith("/"): |
| # Absolute path, maybe we should make this suported by our which function. |
| return cc_name |
| cc = repository_ctx.which(cc_name) |
| if cc == None: |
| fail( |
| ("Cannot find gcc or CC%s, either correct your path or set the CC" |
| + " environment variable") % cc_paren) |
| return cc |
| |
| |
| def configure_unix_toolchain(repository_ctx, cpu_value): |
| """Configure C++ toolchain on Unix platforms.""" |
| darwin = cpu_value == "darwin" |
| cc = find_cc(repository_ctx) |
| tool_paths = _get_tool_paths(repository_ctx, darwin, |
| "cc_wrapper.sh" if darwin else str(cc)) |
| crosstool_content = _crosstool_content(repository_ctx, cc, cpu_value, darwin) |
| opt_content = _opt_content(darwin) |
| dbg_content = _dbg_content() |
| tpl(repository_ctx, "BUILD", { |
| "%{name}": cpu_value, |
| "%{supports_param_files}": "0" if darwin else "1", |
| "%{cc_compiler_deps}": ":cc_wrapper" if darwin else ":empty", |
| "%{compiler}": get_env_var(repository_ctx, "BAZEL_COMPILER", "compiler", False), |
| }) |
| tpl(repository_ctx, |
| "osx_cc_wrapper.sh" if darwin else "linux_cc_wrapper.sh", |
| {"%{cc}": escape_string(str(cc)), |
| "%{env}": escape_string(get_env(repository_ctx))}, |
| "cc_wrapper.sh") |
| tpl(repository_ctx, "CROSSTOOL", { |
| "%{cpu}": escape_string(cpu_value), |
| "%{default_toolchain_name}": escape_string( |
| get_env_var(repository_ctx, |
| "CC_TOOLCHAIN_NAME", |
| "local", |
| False)), |
| "%{toolchain_name}": escape_string( |
| get_env_var(repository_ctx, "CC_TOOLCHAIN_NAME", "local", False)), |
| "%{content}": _build_crosstool(crosstool_content) + "\n" + |
| _build_tool_path(tool_paths), |
| "%{opt_content}": _build_crosstool(opt_content, " "), |
| "%{dbg_content}": _build_crosstool(dbg_content, " "), |
| "%{cxx_builtin_include_directory}": "", |
| "%{coverage}": _coverage_feature(darwin), |
| "%{msvc_env_tmp}": "", |
| "%{msvc_env_path}": "", |
| "%{msvc_env_include}": "", |
| "%{msvc_env_lib}": "", |
| "%{crt_option}": "", |
| "%{crt_debug_option}": "", |
| "%{crt_library}": "", |
| "%{crt_debug_library}": "", |
| "%{msvc_cl_path}": "", |
| "%{msvc_link_path}": "", |
| "%{msvc_lib_path}": "", |
| "%{compilation_mode_content}": "", |
| }) |