blob: 63e1927af96b18a4c767a7b33cd31553edb755dd [file] [log] [blame]
BAZEL_LIB_ADDITIONAL_PATHS_ENV_VAR = "BAZEL_LIB_ADDITIONAL_PATHS"
BAZEL_LIB_OVERRIDE_PATHS_ENV_VAR = "BAZEL_LIB_OVERRIDE_PATHS"
BAZEL_INCLUDE_ADDITIONAL_PATHS_ENV_VAR = "BAZEL_INCLUDE_ADDITIONAL_PATHS"
BAZEL_INCLUDE_OVERRIDE_PATHS_ENV_VAR = "BAZEL_INCLUDE_OVERRIDE_PATHS"
ENV_VAR_SEPARATOR = ","
ENV_VAR_ASSIGNMENT = "="
def _make_flags(array_of_strings, prefix):
flags = []
if array_of_strings:
for s in array_of_strings:
flags.append(prefix + s)
return " ".join(flags)
def _split_env_var(repo_ctx, var_name):
value = repo_ctx.os.environ.get(var_name)
if value:
assignments = value.split(ENV_VAR_SEPARATOR)
dict = {}
for assignment in assignments:
pair = assignment.split(ENV_VAR_ASSIGNMENT)
if len(pair) != 2:
fail(
"Assignments should have form 'name=value', but encountered {} in env variable {}"
.format(assignment, var_name),
)
key, value = pair[0], pair[1]
if not dict.get(key):
dict[key] = []
dict[key].append(value)
return dict
else:
return {}
def _get_list_from_env_var(repo_ctx, var_name, key):
return _split_env_var(repo_ctx, var_name).get(key, default = [])
def _execute_bash(repo_ctx, cmd):
return repo_ctx.execute(["/bin/bash", "-c", cmd]).stdout.replace("\n", "")
def _find_linker(repo_ctx):
ld = _execute_bash(repo_ctx, "which ld")
lld = _execute_bash(repo_ctx, "which lld")
if ld:
return ld
elif lld:
return lld
else:
fail("No linker found")
def _find_compiler(repo_ctx):
gcc = _execute_bash(repo_ctx, "which g++")
clang = _execute_bash(repo_ctx, "which clang++")
if gcc:
return gcc
elif clang:
return clang
else:
fail("No compiler found")
def _find_lib_path(repo_ctx, lib_name, archive_names, lib_path_hints):
override_paths = _get_list_from_env_var(repo_ctx, BAZEL_LIB_OVERRIDE_PATHS_ENV_VAR, lib_name)
additional_paths = _get_list_from_env_var(repo_ctx, BAZEL_LIB_ADDITIONAL_PATHS_ENV_VAR, lib_name)
# Directories will be searched in order
path_flags = _make_flags(override_paths + lib_path_hints + additional_paths, "-L")
linker = _find_linker(repo_ctx)
for archive_name in archive_names:
cmd = """
{} -verbose -l:{} {} 2>/dev/null | \\
grep succeeded | \\
head -1 | \\
sed -e 's/^\\s*attempt to open //' -e 's/ succeeded\\s*$//'
""".format(
linker,
archive_name,
path_flags,
)
path = _execute_bash(repo_ctx, cmd)
if path:
return (archive_name, path)
return ("", "")
def _find_header_path(repo_ctx, lib_name, header_name, includes):
override_paths = _get_list_from_env_var(repo_ctx, BAZEL_INCLUDE_OVERRIDE_PATHS_ENV_VAR, lib_name)
additional_paths = _get_list_from_env_var(repo_ctx, BAZEL_INCLUDE_ADDITIONAL_PATHS_ENV_VAR, lib_name)
# See https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html
override_include_flags = _make_flags(override_paths, "-I")
standard_include_flags = _make_flags(includes, "-isystem")
additional_include_flags = _make_flags(additional_paths, "-idirafter")
compiler = _find_compiler(repo_ctx)
# Taken from https://stackoverflow.com/questions/63052707/which-header-exactly-will-c-preprocessor-include/63052918#63052918
cmd = """
f=\"{}\"; \\
echo | \\
{} -E {} {} {} -Wp,-v - 2>&1 | \\
sed '\\~^ /~!d; s/ //' | \\
while IFS= read -r path; \\
do if [[ -e \"$path/$f\" ]]; \\
then echo \"$path/$f\"; \\
break; \\
fi; \\
done
""".format(
header_name,
compiler,
override_include_flags,
standard_include_flags,
additional_include_flags,
)
return _execute_bash(repo_ctx, cmd)
def system_library_impl(repo_ctx):
repo_name = repo_ctx.attr.name
includes = repo_ctx.attr.includes
hdrs = repo_ctx.attr.hdrs
optional_hdrs = repo_ctx.attr.optional_hdrs
deps = repo_ctx.attr.deps
lib_path_hints = repo_ctx.attr.lib_path_hints
static_lib_names = repo_ctx.attr.static_lib_names
shared_lib_names = repo_ctx.attr.shared_lib_names
static_lib_name, static_lib_path = _find_lib_path(repo_ctx, repo_name, static_lib_names, lib_path_hints)
shared_lib_name, shared_lib_path = _find_lib_path(repo_ctx, repo_name, shared_lib_names, lib_path_hints)
if not static_lib_path and not shared_lib_path:
fail("Library {} could not be found".format(repo_name))
hdr_names = []
hdr_paths = []
for hdr in hdrs:
hdr_path = _find_header_path(repo_ctx, repo_name, hdr, includes)
if hdr_path:
repo_ctx.symlink(hdr_path, hdr)
hdr_names.append(hdr)
hdr_paths.append(hdr_path)
else:
fail("Could not find required header {}".format(hdr))
for hdr in optional_hdrs:
hdr_path = _find_header_path(repo_ctx, repo_name, hdr, includes)
if hdr_path:
repo_ctx.symlink(hdr_path, hdr)
hdr_names.append(hdr)
hdr_paths.append(hdr_path)
hdrs_param = "hdrs = {},".format(str(hdr_names))
# This is needed for the case when quote-includes and system-includes alternate in the include chain, i.e.
# #include <SDL2/SDL.h> -> #include "SDL_main.h" -> #include <SDL2/_real_SDL_config.h> -> #include "SDL_platform.h"
# The problem is that the quote-includes are assumed to be
# in the same directory as the header they are included from - they have no subdir prefix ("SDL2/") in their paths
include_subdirs = {}
for hdr in hdr_names:
path_segments = hdr.split("/")
path_segments.pop()
current_path_segments = ["external", repo_name]
for segment in path_segments:
current_path_segments.append(segment)
current_path = "/".join(current_path_segments)
include_subdirs.update({current_path: None})
includes_param = "includes = {},".format(str(include_subdirs.keys()))
deps_names = []
for dep in deps:
dep_name = repr("@" + dep)
deps_names.append(dep_name)
deps_param = "deps = [{}],".format(",".join(deps_names))
link_hdrs_command = "mkdir -p $(RULEDIR)/remote \n"
remote_hdrs = []
for path, hdr in zip(hdr_paths, hdr_names):
remote_hdr = "remote/" + hdr
remote_hdrs.append(remote_hdr)
link_hdrs_command += "cp {path} $(RULEDIR)/{hdr}\n ".format(
path = path,
hdr = remote_hdr,
)
link_remote_static_library_genrule = ""
link_remote_shared_library_genrule = ""
remote_static_library_param = ""
remote_shared_library_param = ""
static_library_param = ""
shared_library_param = ""
if static_lib_path:
repo_ctx.symlink(static_lib_path, static_lib_name)
static_library_param = "static_library = \"{}\",".format(static_lib_name)
remote_static_library = "remote/" + static_lib_name
link_library_command = "mkdir -p $(RULEDIR)/remote && cp {path} $(RULEDIR)/{lib}".format(
path = static_lib_path,
lib = remote_static_library,
)
remote_static_library_param = "static_library = \"remote_link_static_library\","
link_remote_static_library_genrule = \
"""
genrule(
name = "remote_link_static_library",
outs = ["{remote_static_library}"],
cmd = {link_library_command}
)
""".format(
link_library_command = repr(link_library_command),
remote_static_library = remote_static_library,
)
if shared_lib_path:
repo_ctx.symlink(shared_lib_path, shared_lib_name)
shared_library_param = "shared_library = \"{}\",".format(shared_lib_name)
remote_shared_library = "remote/" + shared_lib_name
link_library_command = "mkdir -p $(RULEDIR)/remote && cp {path} $(RULEDIR)/{lib}".format(
path = shared_lib_path,
lib = remote_shared_library,
)
remote_shared_library_param = "shared_library = \"remote_link_shared_library\","
link_remote_shared_library_genrule = \
"""
genrule(
name = "remote_link_shared_library",
outs = ["{remote_shared_library}"],
cmd = {link_library_command}
)
""".format(
link_library_command = repr(link_library_command),
remote_shared_library = remote_shared_library,
)
repo_ctx.file(
"BUILD",
executable = False,
content =
"""
load("@bazel_tools//tools/build_defs/cc:cc_import.bzl", "cc_import")
cc_import(
name = "local_includes",
{static_library}
{shared_library}
{hdrs}
{deps}
{includes}
)
genrule(
name = "remote_link_headers",
outs = {remote_hdrs},
cmd = {link_hdrs_command}
)
{link_remote_static_library_genrule}
{link_remote_shared_library_genrule}
cc_import(
name = "remote_includes",
hdrs = [":remote_link_headers"],
{remote_static_library}
{remote_shared_library}
{deps}
{includes}
)
alias(
name = "{name}",
actual = select({{
"@bazel_tools//src/conditions:remote": "remote_includes",
"//conditions:default": "local_includes",
}}),
visibility = ["//visibility:public"],
)
""".format(
static_library = static_library_param,
shared_library = shared_library_param,
hdrs = hdrs_param,
deps = deps_param,
hdr_names = str(hdr_names),
link_hdrs_command = repr(link_hdrs_command),
link_library_command = repr(link_library_command),
name = repo_name,
includes = includes_param,
remote_hdrs = remote_hdrs,
link_remote_static_library_genrule = link_remote_static_library_genrule,
link_remote_shared_library_genrule = link_remote_shared_library_genrule,
remote_static_library = remote_static_library_param,
remote_shared_library = remote_shared_library_param,
),
)
system_library = repository_rule(
implementation = system_library_impl,
local = True,
remotable = True,
environ = [
BAZEL_LIB_ADDITIONAL_PATHS_ENV_VAR,
BAZEL_LIB_OVERRIDE_PATHS_ENV_VAR,
BAZEL_INCLUDE_ADDITIONAL_PATHS_ENV_VAR,
BAZEL_INCLUDE_OVERRIDE_PATHS_ENV_VAR,
],
attrs = {
"static_lib_names": attr.string_list(),
"shared_lib_names": attr.string_list(),
"lib_path_hints": attr.string_list(),
"includes": attr.string_list(),
"hdrs": attr.string_list(mandatory = True, allow_empty = False),
"optional_hdrs": attr.string_list(),
"deps": attr.string_list(),
},
)