Damien Martin-Guillerez | 8fa5ae6 | 2016-03-02 16:24:13 +0000 | [diff] [blame^] | 1 | # Copyright 2016 The Bazel Authors. All rights reserved. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | """Rules for configuring the C++ toolchain (experimental).""" |
| 15 | |
| 16 | |
| 17 | def _get_value(it): |
| 18 | """Convert `it` in serialized protobuf format.""" |
| 19 | if type(it) == "int": |
| 20 | return str(it) |
| 21 | elif type(it) == "bool": |
| 22 | return "true" if it else "false" |
| 23 | else: |
| 24 | return "\"%s\"" % it |
| 25 | |
| 26 | |
| 27 | def _build_crosstool(d, prefix=" "): |
| 28 | """Convert `d` to a string version of a CROSSTOOL file content.""" |
| 29 | lines = [] |
| 30 | for k in d: |
| 31 | if type(d[k]) == "list": |
| 32 | for it in d[k]: |
| 33 | lines.append("%s%s: %s" % (prefix, k, _get_value(it))) |
| 34 | else: |
| 35 | lines.append("%s%s: %s" % (prefix, k, _get_value(d[k]))) |
| 36 | return "\n".join(lines) |
| 37 | |
| 38 | |
| 39 | def _build_tool_path(d): |
| 40 | """Build the list of tool_path for the CROSSTOOL file.""" |
| 41 | lines = [] |
| 42 | for k in d: |
| 43 | lines.append(" tool_path {name: \"%s\" path: \"%s\" }" % (k, d[k])) |
| 44 | return "\n".join(lines) |
| 45 | |
| 46 | |
| 47 | def _which(ctx, cmd, default): |
| 48 | """A wrapper around ctx.which() to provide a fallback value.""" |
| 49 | result = ctx.which(cmd) |
| 50 | return default if result == None else str(result) |
| 51 | |
| 52 | |
| 53 | def _get_tool_paths(ctx, darwin, cc): |
| 54 | """Compute the path to the various tools.""" |
| 55 | return {k: _which(ctx, k, "/usr/bin/" + k) |
| 56 | for k in [ |
| 57 | "ld", |
| 58 | "cpp", |
| 59 | "dwp", |
| 60 | "gcov", |
| 61 | "nm", |
| 62 | "objcopy", |
| 63 | "objdump", |
| 64 | "strip", |
| 65 | ]} + { |
| 66 | "gcc": cc, |
| 67 | "ar": "/usr/bin/libtool" |
| 68 | if darwin else _which(ctx, "ar", "/usr/bin/ar") |
| 69 | } |
| 70 | |
| 71 | |
| 72 | def _ld_library_paths(ctx): |
| 73 | """Use ${LD_LIBRARY_PATH} to compute the list -Wl,rpath flags.""" |
| 74 | if "LD_LIBRARY_PATH" in ctx.os.environ: |
| 75 | result = [] |
| 76 | for p in ctx.os.environ["LD_LIBRARY_PATH"].split(":"): |
| 77 | p = ctx.path(p) # Normalize the path |
| 78 | result.append("-Wl,rpath," + p) |
| 79 | result.append("-L" + p) |
| 80 | return result |
| 81 | else: |
| 82 | return [] |
| 83 | |
| 84 | |
| 85 | def _get_cpu_value(ctx): |
| 86 | """Compute the cpu_value based on the OS name.""" |
| 87 | return "darwin" if ctx.os.name.lower().startswith("mac os") else "local" |
| 88 | |
| 89 | |
| 90 | _INC_DIR_MARKER_BEGIN = "#include <...> search starts here:" |
| 91 | _INC_DIR_MARKER_END = "End of search list." |
| 92 | |
| 93 | |
| 94 | def _get_cxx_inc_directories(ctx, cc): |
| 95 | """Compute the list of default C++ include directories.""" |
| 96 | result = ctx.execute([cc, "-E", "-xc++", "-", "-v"]) |
| 97 | index1 = result.stderr.find(_INC_DIR_MARKER_BEGIN) |
| 98 | if index1 == -1: |
| 99 | return [] |
| 100 | index2 = result.stderr.find(_INC_DIR_MARKER_END, index1) |
| 101 | if index2 == -1: |
| 102 | return [] |
| 103 | inc_dirs = result.stderr[index1 + len(_INC_DIR_MARKER_BEGIN):index2].strip() |
| 104 | return [ctx.path(p.strip()) for p in inc_dirs.split("\n")] |
| 105 | |
| 106 | |
| 107 | def _add_option_if_supported(ctx, cc, option): |
| 108 | """Checks that `option` is supported by the C compiler.""" |
| 109 | result = ctx.execute([cc, option]) |
| 110 | return [option] if result.stderr.find(option) == -1 else [] |
| 111 | |
| 112 | |
| 113 | def _crosstool_content(ctx, cc, cpu_value, darwin): |
| 114 | """Return the content for the CROSSTOOL file, in a dictionary.""" |
| 115 | return { |
| 116 | "abi_version": "local", |
| 117 | "abi_libc_version": "local", |
| 118 | "builtin_sysroot": "", |
| 119 | "compiler": "compiler", |
| 120 | "host_system_name": "local", |
| 121 | "needsPic": True, |
| 122 | "supports_gold_linker": False, |
| 123 | "supports_incremental_linker": False, |
| 124 | "supports_fission": False, |
| 125 | "supports_interface_shared_objects": False, |
| 126 | "supports_normalizing_ar": False, |
| 127 | "supports_start_end_lib": False, |
| 128 | "supports_thin_archives": False, |
| 129 | "target_libc": "macosx" if darwin else "local", |
| 130 | "target_cpu": cpu_value, |
| 131 | "target_system_name": "local", |
| 132 | "cxx_flag": "-std=c++0x", |
| 133 | "linker_flag": [ |
| 134 | "-lstdc++", |
| 135 | # Anticipated future default. |
| 136 | "-no-canonical-prefixes" |
| 137 | ] + (["-undefined", "dynamic_lookup"] if darwin else [ |
| 138 | "-B/usr/bin", |
| 139 | # Have gcc return the exit code from ld. |
| 140 | "-pass-exit-codes", |
| 141 | # Stamp the binary with a unique identifier. |
| 142 | "-Wl,--build-id=md5", |
| 143 | "-Wl,--hash-style=gnu" |
| 144 | # Gold linker only? Can we enable this by default? |
| 145 | # "-Wl,--warn-execstack", |
| 146 | # "-Wl,--detect-odr-violations" |
| 147 | ]) + _ld_library_paths(ctx), |
| 148 | "ar_flag": ["-static", "-s", "-o"] if darwin else [], |
| 149 | "cxx_builtin_include_directory": _get_cxx_inc_directories(ctx, cc), |
| 150 | "objcopy_embed_flag": ["-I", "binary"], |
| 151 | "unfiltered_cxx_flag": [ |
| 152 | # Anticipated future default. |
| 153 | "-no-canonical-prefixes", |
| 154 | ] + ([] if darwin else ["-fno-canonical-system-headers"]) + [ |
| 155 | # Make C++ compilation deterministic. Use linkstamping instead of these |
| 156 | # compiler symbols. |
| 157 | "-Wno-builtin-macro-redefined", |
| 158 | "-D__DATE__=\\\"redacted\\\"", |
| 159 | "-D__TIMESTAMP__=\\\"redacted\\\"", |
| 160 | "-D__TIME__=\\\"redacted\\\"" |
| 161 | ], |
| 162 | "compiler_flag": [ |
| 163 | # Security hardening on by default. |
| 164 | # Conservative choice; -D_FORTIFY_SOURCE=2 may be unsafe in some cases. |
| 165 | # We need to undef it before redefining it as some distributions now have |
| 166 | # it enabled by default. |
| 167 | "-U_FORTIFY_SOURCE", |
| 168 | "-D_FORTIFY_SOURCE=1", |
| 169 | "-fstack-protector", |
| 170 | # All warnings are enabled. Maybe enable -Werror as well? |
| 171 | "-Wall", |
| 172 | # Enable a few more warnings that aren't part of -Wall. |
| 173 | ] + (["-Wthread-safety", "-Wself-assign"] if darwin else [ |
| 174 | "-Wunused-but-set-parameter", |
| 175 | # Disable some that are problematic. |
| 176 | "-Wno-free-nonheap-object", # has false positives |
| 177 | "-Wl,-z,-relro,-z,now" |
| 178 | ]) + ( |
| 179 | # Enable coloring even if there's no attached terminal. Bazel removes the |
| 180 | # escape sequences if --nocolor is specified. |
| 181 | _add_option_if_supported(ctx, cc, "-fcolor-diagnostics")) + [ |
| 182 | # Keep stack frames for debugging, even in opt mode. |
| 183 | "-fno-omit-frame-pointer", |
| 184 | ], |
| 185 | } |
| 186 | |
| 187 | |
| 188 | def _opt_content(darwin): |
| 189 | """Return the content of the opt specific section of the CROSSTOOL file.""" |
| 190 | return { |
| 191 | "compiler_flag": [ |
| 192 | # No debug symbols. |
| 193 | # Maybe we should enable https://gcc.gnu.org/wiki/DebugFission for opt or |
| 194 | # even generally? However, that can't happen here, as it requires special |
| 195 | # handling in Bazel. |
| 196 | "-g0", |
| 197 | |
| 198 | # Conservative choice for -O |
| 199 | # -O3 can increase binary size and even slow down the resulting binaries. |
| 200 | # Profile first and / or use FDO if you need better performance than this. |
| 201 | "-O2", |
| 202 | |
| 203 | # Disable assertions |
| 204 | "-DNDEBUG", |
| 205 | |
| 206 | # Removal of unused code and data at link time (can this increase binary size in some cases?). |
| 207 | "-ffunction-sections", |
| 208 | "-fdata-sections" |
| 209 | ], |
| 210 | "linker_flag": [] if darwin else ["-Wl,--gc-sections"] |
| 211 | } |
| 212 | |
| 213 | |
| 214 | def _dbg_content(): |
| 215 | """Return the content of the dbg specific section of the CROSSTOOL file.""" |
| 216 | # Enable debug symbols |
| 217 | return {"compiler_flag": "-g"} |
| 218 | |
| 219 | |
| 220 | def _find_cc(ctx): |
| 221 | """Find the C++ compiler.""" |
| 222 | if "CC" in ctx.os.environ: |
| 223 | return ctx.path(ctx.os.environ["CC"]) |
| 224 | else: |
| 225 | cc = ctx.which("gcc") |
| 226 | if cc == None: |
| 227 | fail( |
| 228 | "Cannot find gcc, either correct your path or set the CC environment" |
| 229 | " variable") |
| 230 | return cc |
| 231 | |
| 232 | |
| 233 | def _tpl(ctx, tpl, substitutions={}): |
| 234 | ctx.template(tpl, Label("@bazel_tools//tools/cpp:%s.tpl" % tpl), |
| 235 | substitutions) |
| 236 | |
| 237 | |
| 238 | def _impl(ctx): |
| 239 | cpu_value = _get_cpu_value(ctx) |
| 240 | darwin = cpu_value == "darwin" |
| 241 | cc = _find_cc(ctx) |
| 242 | crosstool_cc = "osx_cc_wrapper.sh" if darwin else str(cc) |
| 243 | darwin = cpu_value == "darwin" |
| 244 | tool_paths = _get_tool_paths(ctx, darwin, crosstool_cc) |
| 245 | crosstool_content = _crosstool_content(ctx, cc, cpu_value, darwin) |
| 246 | opt_content = _opt_content(darwin) |
| 247 | dbg_content = _dbg_content() |
| 248 | _tpl(ctx, "BUILD", { |
| 249 | "%{name}": cpu_value, |
| 250 | "%{supports_param_files}": "0" if darwin else "1" |
| 251 | }) |
| 252 | _tpl(ctx, "osx_gcc_wrapper.sh", {"%{cc}": str(cc)}) |
| 253 | _tpl(ctx, "CROSSTOOL", { |
| 254 | "%{cpu}": cpu_value, |
| 255 | "%{content}": _build_crosstool(crosstool_content) + "\n" + |
| 256 | _build_tool_path(tool_paths), |
| 257 | "%{opt_content}": _build_crosstool(opt_content, " "), |
| 258 | "%{dbg_content}": _build_crosstool(dbg_content, " "), |
| 259 | }) |
| 260 | |
| 261 | |
| 262 | cc_autoconf = repository_rule(_impl, local=True) |
| 263 | |
| 264 | |
| 265 | def cc_configure(): |
| 266 | """A C++ configuration rules that generate the crosstool file.""" |
| 267 | cc_autoconf(name="local_config_cc") |
| 268 | native.bind(name="cc_toolchain", actual="@local_config_cc//:toolchain") |