Refactor cc_configure.bzl

Split original cc_configure.bzl into 4 different files:

lib_cc_configure.bzl: base library for C++ toolchain configuration
unix_cc_configure.bzl: For Unix platforms
osx_cc_configure.bzl: For macOS
windows_cc_configure.bzl: For Windows

Change-Id: I76fa44294c6ca4304f0a94f3a1c57d6e76141667
PiperOrigin-RevId: 159801973
diff --git a/tools/cpp/unix_cc_configure.bzl b/tools/cpp/unix_cc_configure.bzl
new file mode 100644
index 0000000..fbbbdf5
--- /dev/null
+++ b/tools/cpp/unix_cc_configure.bzl
@@ -0,0 +1,401 @@
+# 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}": "",
+  })