# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@bazel_skylib//lib:new_sets.bzl", "sets")
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@prelude//utils:selects.bzl", "selects")
# @lint-ignore-every FBCODEBZLADDLOADS
load("@prelude//utils:type_defs.bzl", "is_list", "is_select", "is_tuple")
load("@shim//build_defs:auto_headers.bzl", "AutoHeaders", "get_auto_headers")
prelude = native
_C_SOURCE_EXTS = (
".c",
)
_CPP_SOURCE_EXTS = (
".cc",
".cpp",
)
_SOURCE_EXTS = _C_SOURCE_EXTS + _CPP_SOURCE_EXTS
# These header suffixes are used to logically group C/C++ source (e.g.
# `foo/Bar.cpp`) with headers with the following suffixes (e.g. `foo/Bar.h` and
# `foo/Bar-inl.tcc`), such that the source provides all implementation for
# methods/classes declared in the headers.
#
# This is important for a couple reasons:
# 1) Automatic dependencies: Tooling can use this property to automatically
# manage TARGETS dependencies by extracting `#include` references in sources
# and looking up the rules which "provide" them.
# 2) Modules: This logical group can be combined into a standalone C/C++ module
# (when such support is available).
_HEADER_SUFFIXES = (
".h",
".hpp",
".tcc",
"-inl.h",
"-inl.hpp",
"-inl.tcc",
"-defs.h",
"-defs.hpp",
"-defs.tcc",
)
def _get_headers_from_sources(srcs):
"""
Return the headers likely associated with the given sources
Args:
srcs: A list of strings representing files or build targets
Returns:
A list of header files corresponding to the list of sources. These files are
validated to exist based on glob()
"""
split_srcs = [
paths.split_extension(src_filename)
for src_filename in [_get_src_filename(src) for src in srcs]
if "//" not in src_filename and not src_filename.startswith(":")
]
# For e.g. foo.cpp grab a glob on foo.h, foo-inl.h, etc
headers = [
base + header_ext
for base, ext in split_srcs
if ext in _SOURCE_EXTS
for header_ext in _HEADER_SUFFIXES
]
# Avoid a warning for an empty glob pattern if there are no headers.
return glob(headers) if headers else []
def _get_src_filename(src):
"""
Return filename from a potentilly tuple value entry in srcs attribute
"""
if is_tuple(src):
s, _ = src
return s
return src
def _update_headers_with_src_headers(src_headers, out_headers):
"""
Helper function to update raw headers with headers from srcs
"""
src_headers = sets.to_list(sets.difference(src_headers, sets.make(out_headers)))
# Looks simple, right? But if a header is explicitly added in, say, a
# dictionary mapping, we want to make sure to keep the original mapping
# and drop the F -> F mapping
if is_list(out_headers):
out_headers.extend(sorted(src_headers))
else:
# Let it throw AttributeError if update() can't be found neither
out_headers.update({k: k for k in src_headers})
return out_headers
def prebuilt_cpp_library(
headers = None,
linker_flags = None,
private_linker_flags = None,
**kwargs):
prelude.prebuilt_cxx_library(
exported_headers = headers,
exported_linker_flags = linker_flags,
linker_flags = private_linker_flags,
**kwargs
)
def cpp_library(
name,
deps = [],
srcs = [],
external_deps = [],
exported_deps = [],
exported_external_deps = [],
undefined_symbols = None,
visibility = ["PUBLIC"],
auto_headers = None,
arch_preprocessor_flags = None,
modular_headers = None,
os_deps = [],
arch_compiler_flags = None,
tags = None,
linker_flags = None,
private_linker_flags = None,
exported_linker_flags = None,
headers = None,
private_headers = None,
propagated_pp_flags = (),
**kwargs):
base_path = native.package_name()
oss_depends_on_folly = read_config("oss_depends_on", "folly", False)
header_base_path = base_path
if oss_depends_on_folly and header_base_path.startswith("folly"):
header_base_path = header_base_path.replace("folly/", "", 1)
_unused = (undefined_symbols, arch_preprocessor_flags, modular_headers, arch_compiler_flags, tags, propagated_pp_flags) # @unused
if os_deps:
deps += _select_os_deps(_fix_dict_deps(os_deps))
if headers == None:
headers = []
if tags != None and "oss_dependency" in tags:
if oss_depends_on_folly:
headers = [item.replace("//:", "//folly:") if item == "//:folly-config.h" else item for item in headers]
if is_select(srcs) and auto_headers == AutoHeaders.SOURCES:
# Validate `srcs` and `auto_headers` before the config check
fail(
"//{}:{}: `select` srcs cannot support AutoHeaders.SOURCES".format(base_path, name),
)
auto_headers = get_auto_headers(auto_headers)
if auto_headers == AutoHeaders.SOURCES and not is_select(srcs):
src_headers = sets.make(_get_headers_from_sources(srcs))
if private_headers:
src_headers = sets.difference(src_headers, sets.make(private_headers))
headers = selects.apply(
headers,
partial(_update_headers_with_src_headers, src_headers),
)
if not is_select(linker_flags):
linker_flags = linker_flags or []
linker_flags = list(linker_flags)
if exported_linker_flags != None:
linker_flags += exported_linker_flags
prelude.cxx_library(
name = name,
srcs = srcs,
deps = _maybe_select_map(deps + external_deps_to_targets(external_deps), _fix_deps),
exported_deps = _maybe_select_map(exported_deps + external_deps_to_targets(exported_external_deps), _fix_deps),
visibility = visibility,
preferred_linkage = "static",
exported_headers = headers,
headers = private_headers,
exported_linker_flags = linker_flags,
linker_flags = private_linker_flags,
header_namespace = header_base_path,
**kwargs
)
def cpp_unittest(
deps = [],
external_deps = [],
visibility = ["PUBLIC"],
supports_static_listing = None,
allocator = None,
owner = None,
tags = None,
emails = None,
extract_helper_lib = None,
compiler_specific_flags = None,
default_strip_mode = None,
srcs = [],
**kwargs):
_unused = (supports_static_listing, allocator, owner, tags, emails, extract_helper_lib, compiler_specific_flags, default_strip_mode) # @unused
srcs = srcs + ["shim//third-party/googletest:gtest_main.cpp"]
prelude.cxx_test(
deps = _maybe_select_map(deps + external_deps_to_targets(external_deps), _fix_deps),
visibility = visibility,
srcs = srcs,
**kwargs
)
def cpp_binary(
deps = [],
external_deps = [],
visibility = ["PUBLIC"],
dlopen_enabled = None,
compiler_specific_flags = None,
os_linker_flags = None,
allocator = None,
modules = None,
**kwargs):
_unused = (dlopen_enabled, compiler_specific_flags, os_linker_flags, allocator, modules) # @unused
prelude.cxx_binary(
deps = _maybe_select_map(deps + external_deps_to_targets(external_deps), _fix_deps),
visibility = visibility,
**kwargs
)
def rust_library(
rustc_flags = [],
deps = [],
named_deps = None,
os_deps = None,
test_deps = None,
test_env = None,
test_os_deps = None,
autocargo = None,
unittests = None,
mapped_srcs = {},
visibility = ["PUBLIC"],
**kwargs):
_unused = (test_deps, test_env, test_os_deps, named_deps, autocargo, unittests, visibility) # @unused
deps = _maybe_select_map(deps, _fix_deps)
mapped_srcs = _maybe_select_map(mapped_srcs, _fix_mapped_srcs)
if os_deps:
deps += _select_os_deps(_fix_dict_deps(os_deps))
# Reset visibility because internal and external paths are different.
visibility = ["PUBLIC"]
prelude.rust_library(
rustc_flags = rustc_flags + [_CFG_BUCK_BUILD],
deps = deps,
visibility = visibility,
mapped_srcs = mapped_srcs,
**kwargs
)
def rust_binary(
rustc_flags = [],
deps = [],
autocargo = None,
unittests = None,
allocator = None,
default_strip_mode = None,
visibility = ["PUBLIC"],
**kwargs):
_unused = (unittests, allocator, default_strip_mode, autocargo) # @unused
deps = _maybe_select_map(deps, _fix_deps)
# @lint-ignore BUCKLINT: avoid "Direct usage of native rules is not allowed."
prelude.rust_binary(
rustc_flags = rustc_flags + [_CFG_BUCK_BUILD],
deps = deps,
visibility = visibility,
**kwargs
)
def rust_unittest(
rustc_flags = [],
deps = [],
visibility = ["PUBLIC"],
**kwargs):
deps = _maybe_select_map(deps, _fix_deps)
prelude.rust_test(
rustc_flags = rustc_flags + [_CFG_BUCK_BUILD],
deps = deps,
visibility = visibility,
**kwargs
)
def rust_protobuf_library(
name,
srcs,
build_script,
protos,
build_env = None,
deps = [],
test_deps = None,
doctests = True):
if build_env:
build_env = {
k: _fix_dep_in_string(v)
for k, v in build_env.items()
}
build_name = name + "-build"
proto_name = name + "-proto"
rust_binary(
name = build_name,
srcs = [build_script],
crate_root = build_script,
deps = [
"fbsource//third-party/rust:tonic-build",
"//buck2/app/buck2_protoc_dev:buck2_protoc_dev",
],
)
build_env = build_env or {}
build_env.update(
{
"PROTOC": "$(exe buck//third-party/proto:protoc)",
"PROTOC_INCLUDE": "$(location buck//third-party/proto:google_protobuf)",
},
)
prelude.genrule(
name = proto_name,
srcs = protos + [
"buck//third-party/proto:google_protobuf",
],
out = ".",
cmd = "$(exe :" + build_name + ")",
env = build_env,
)
rust_library(
name = name,
srcs = srcs,
doctests = doctests,
env = {
# This is where prost looks for generated .rs files
"OUT_DIR": "$(location :{})".format(proto_name),
},
test_deps = test_deps,
deps = [
"fbsource//third-party/rust:prost",
"fbsource//third-party/rust:prost-types",
] + (deps or []),
)
def ocaml_binary(
deps = [],
visibility = ["PUBLIC"],
**kwargs):
deps = _maybe_select_map(deps, _fix_deps)
prelude.ocaml_binary(
deps = deps,
visibility = visibility,
**kwargs
)
_CFG_BUCK_BUILD = "--cfg=buck_build"
def _maybe_select_map(v, mapper):
if is_select(v):
return select_map(v, mapper)
return mapper(v)
def _select_os_deps(xss) -> Select:
d = {
"prelude//os:" + os: xs
for os, xs in xss
}
d["DEFAULT"] = []
return select(d)
def _fix_dict_deps(xss):
return [
(k, _fix_deps(xs))
for k, xs in xss
]
def _fix_mapped_srcs(xs: dict[str, str]):
# For reasons, this is source -> file path, which is the opposite of what
# it should be.
return {_fix_dep(k): v for (k, v) in xs.items()}
def _fix_deps(xs):
if is_select(xs):
return xs
return filter(None, map(_fix_dep, xs))
def _fix_dep(x: str) -> [
None,
str,
]:
if x == "//common/rust/shed/fbinit:fbinit":
return "fbsource//third-party/rust:fbinit"
elif x == "//common/rust/shed/sorted_vector_map:sorted_vector_map":
return "fbsource//third-party/rust:sorted_vector_map"
elif x == "//watchman/rust/watchman_client:watchman_client":
return "fbsource//third-party/rust:watchman_client"
elif x.startswith("fbsource//third-party/rust:") or x.startswith(":"):
return x
elif x.startswith("//buck2/facebook/"):
return None
elif x.startswith("//buck2/"):
return "root//" + x.removeprefix("//buck2/")
elif x.startswith("fbcode//common/ocaml/interop/"):
return "root//" + x.removeprefix("fbcode//common/ocaml/interop/")
elif x.startswith("fbcode//third-party-buck/platform010/build/supercaml"):
return "shim//third-party/ocaml" + x.removeprefix("fbcode//third-party-buck/platform010/build/supercaml")
elif x.startswith("fbcode//third-party-buck/platform010/build"):
return "shim//third-party" + x.removeprefix("fbcode//third-party-buck/platform010/build")
elif x.startswith("fbsource//third-party"):
return "shim//third-party" + x.removeprefix("fbsource//third-party")
elif x.startswith("third-party//"):
return "shim//third-party/" + x.removeprefix("third-party//")
elif x.startswith("//folly"):
oss_depends_on_folly = read_config("oss_depends_on", "folly", False)
if oss_depends_on_folly:
return "root//folly/" + x.removeprefix("//")
return "root//" + x.removeprefix("//")
elif x.startswith("root//folly"):
return x
elif x.startswith("//fizz"):
return "root//" + x.removeprefix("//")
elif x.startswith("shim//"):
return x
else:
fail("Dependency is unaccounted for `{}`.\n".format(x) +
"Did you forget 'oss-disable'?")
def _fix_dep_in_string(x: str) -> str:
"""Replace internal labels in string values such as env-vars."""
return (x
.replace("//buck2/", "root//"))
# Do a nasty conversion of e.g. ("supercaml", None, "ocaml-dev") to
# 'fbcode//third-party-buck/platform010/build/supercaml:ocaml-dev'
# (which will then get mapped to `shim//third-party/ocaml:ocaml-dev`).
def external_dep_to_target(t):
if type(t) == type(()):
return "fbcode//third-party-buck/platform010/build/{}:{}".format(t[0], t[2])
else:
return "fbcode//third-party-buck/platform010/build/{}:{}".format(t, t)
def external_deps_to_targets(ts):
return [external_dep_to_target(t) for t in ts]