llvm-project/utils/bazel/terminfo.bzl
jonmeow 4691f00a63 Initialize terminfo.bzl linkopts to None
The underlying issue here is that line 125 checks if linkopts was assigned (by line 120) but it's not initialized, so that becomes a crash. This was noticed by someone trying to use Docker, so no terminfo library installed.

Reviewed By: GMNGeoffrey

Differential Revision: https://reviews.llvm.org/D118270
2022-01-26 11:03:26 -08:00

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