llvm-project/utils/bazel/terminfo.bzl

203 lines
7.3 KiB
Python

# 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]
# 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"],
)