1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
# This file is licensed under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Repository rules to configure the terminfo used by LLVM.
Most users should pick one of the explicit rules to configure their use of terminfo
with LLVM:
- `llvm_terminfo_system` will detect and link against a terminfo-implementing
system library (non-hermetically).
- 'llvm_terminfo_disable` will disable terminfo completely.
If you would like to make your build configurable, you can use
`llvm_terminfo_from_env`. By default, this will disable terminfo, but will
inspect the environment variable (most easily set with a `--repo_env` flag to
the Bazel invocation) `BAZEL_LLVM_TERMINFO_STRATEGY`. If it is set to
`system` then it will behave the same as `llvm_terminfo_system`. Any other
setting will disable terminfo the same as not setting it at all.
"""
def _llvm_terminfo_disable_impl(repository_ctx):
repository_ctx.template(
"BUILD",
repository_ctx.attr._disable_build_template,
executable = False,
)
_terminfo_disable_attrs = {
"_disable_build_template": attr.label(
default = Label("//utils/bazel/deps_impl:terminfo_disable.BUILD"),
allow_single_file = True,
),
}
llvm_terminfo_disable = repository_rule(
implementation = _llvm_terminfo_disable_impl,
attrs = _terminfo_disable_attrs,
)
def _find_c_compiler(repository_ctx):
"""Returns the path to a plausible C compiler.
This routine will only reliably work on roughly POSIX-y systems as it
ultimately falls back on the `cc` binary. Fortunately, the thing we are
trying to use it for (detecting if a trivial source file can compile and
link against a particular library) requires very little.
"""
cc_env = repository_ctx.os.environ.get("CC")
cc = None
if cc_env:
if "/" in cc_env:
return repository_ctx.path(cc_env)
else:
return repository_ctx.which(cc_env)
# Look for Clang, GCC, and the POSIX / UNIX specified C compiler
# binaries.
for compiler in ["clang", "gcc", "c99", "c89", "cc"]:
cc = repository_ctx.which(compiler)
if cc:
return cc
return None
def _try_link(repository_ctx, cc, source, linker_flags):
"""Returns `True` if able to link the source with the linker flag.
Given a source file that contains references to library routines, this
will check that when linked with the provided linker flag, those
references are successfully resolved. This routine assumes a generally
POSIX-y and GCC-ish compiler and environment and shouldn't be expected to
work outside of that.
"""
cmd = [
cc,
# Force discard the linked executable.
"-o",
"/dev/null",
# Leave language detection to the compiler.
source,
]
# The linker flag must be valid for a compiler invocation of the link step,
# so just append them to the command.
cmd += linker_flags
exec_result = repository_ctx.execute(cmd, timeout = 20)
return exec_result.return_code == 0
def _llvm_terminfo_system_impl(repository_ctx):
# LLVM doesn't need terminfo support on Windows, so just disable it.
if repository_ctx.os.name.lower().find("windows") != -1:
_llvm_terminfo_disable_impl(repository_ctx)
return
if len(repository_ctx.attr.system_linkopts) > 0:
linkopts = repository_ctx.attr.system_linkopts
else:
required = repository_ctx.attr.system_required
# Find a C compiler we can use to detect viable linkopts on this system.
cc = _find_c_compiler(repository_ctx)
if not cc:
if required:
fail("Failed to find a C compiler executable")
else:
_llvm_terminfo_disable_impl(repository_ctx)
return
# Get the source file we use to detect successful linking of terminfo.
source = repository_ctx.path(repository_ctx.attr._terminfo_test_source)
# Collect the candidate linkopts and wrap them into a list. Ideally,
# these would be provided as lists, but Bazel doesn't currently
# support that. See: https://github.com/bazelbuild/bazel/issues/12178
linkopts_candidates = [[x] for x in repository_ctx.attr.candidate_system_linkopts]
linkopts = None
# For each candidate, try to use it to link our test source file.
for linkopts_candidate in linkopts_candidates:
if _try_link(repository_ctx, cc, source, linkopts_candidate):
linkopts = linkopts_candidate
break
# If we never found a viable linkopts candidate, either error or disable
# terminfo for LLVM.
if not linkopts:
if required:
fail("Failed to detect which linkopt would successfully provide the " +
"necessary terminfo functionality")
else:
_llvm_terminfo_disable_impl(repository_ctx)
return
repository_ctx.template(
"BUILD",
repository_ctx.attr._system_build_template,
substitutions = {
"{TERMINFO_LINKOPTS}": str(linkopts),
},
executable = False,
)
def _merge_attrs(attrs_list):
attrs = {}
for input_attrs in attrs_list:
attrs.update(input_attrs)
return attrs
_terminfo_system_attrs = _merge_attrs([_terminfo_disable_attrs, {
"_system_build_template": attr.label(
default = Label("//utils/bazel/deps_impl:terminfo_system.BUILD"),
allow_single_file = True,
),
"_terminfo_test_source": attr.label(
default = Label("//utils/bazel/deps_impl:terminfo_test.c"),
allow_single_file = True,
),
"candidate_system_linkopts": attr.string_list(
default = [
"-lterminfo",
"-ltinfo",
"-lcurses",
"-lncurses",
"-lncursesw",
],
doc = "Candidate linkopts to test and see if they can link " +
"successfully.",
),
"system_required": attr.bool(
default = False,
doc = "Require that one of the candidates is detected successfully on POSIX platforms where it is needed.",
),
"system_linkopts": attr.string_list(
default = [],
doc = "If non-empty, a specific array of linkopts to use to " +
"successfully link against the terminfo library. No " +
"detection is performed if this option is provided, it " +
"directly forces the use of these link options. No test is " +
"run to determine if they are valid or work correctly either.",
),
}])
llvm_terminfo_system = repository_rule(
implementation = _llvm_terminfo_system_impl,
configure = True,
local = True,
attrs = _terminfo_system_attrs,
)
def _llvm_terminfo_from_env_impl(repository_ctx):
terminfo_strategy = repository_ctx.os.environ.get("BAZEL_LLVM_TERMINFO_STRATEGY")
if terminfo_strategy == "system":
_llvm_terminfo_system_impl(repository_ctx)
else:
_llvm_terminfo_disable_impl(repository_ctx)
llvm_terminfo_from_env = repository_rule(
implementation = _llvm_terminfo_from_env_impl,
configure = True,
local = True,
attrs = _merge_attrs([_terminfo_disable_attrs, _terminfo_system_attrs]),
environ = ["BAZEL_LLVM_TERMINFO_STRATEGY", "CC"],
)
|