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