antlir/bzl/oss_shim_impl.bzl (608 lines of code) (raw):

# Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. load("@bazel_skylib//lib:paths.bzl", "paths") load("@bazel_skylib//lib:types.bzl", "types") load("@config//:config.bzl", _do_not_use_repo_cfg = "do_not_use_repo_cfg") load("//third-party/fedora33/kernel:kernels.bzl", "kernels") # @lint-ignore-every BUCKLINT # @lint-ignore-every BUCKRESTRICTEDSYNTAX _RULE_TYPE_KWARG = "antlir_rule" _RULE_PRIVATE = "antlir-private" _RULE_USER_FACING = "user-facing" _RULE_USER_INTERNAL = "user-internal" _ALLOWED_RULES = [ _RULE_PRIVATE, _RULE_USER_FACING, _RULE_USER_INTERNAL, ] # The default native platform to use for shared libraries and static binary # dependencies. Right now this tooling only supports one platform and so # this is not a method, but in the future as we support other native platforms # (like Debian, Arch Linux, etc..) this should be expanded to allow for those. _DEFAULT_NATIVE_PLATFORM = "fedora33" # Serves two important purposes: # - Ensures that all user-instanted rules are annotated with # `antlir_rule = "user-{facing,internal}", which is important for FB CI. # - Discourages users from loading rules or functions from `oss_shim.bzl`. def _assert_package(): package = native.package_name() if package == "antlir/compiler/test_images": fail( '`antlir/compiler/test_images` is treated as "outside of the ' + 'Antlir codebase" for the purposes of testing `antlir_rule`. ' + "Therefore, you may not access `oss_shim.bzl` directly from its " + "build file -- instead add and use a shim inside of " + "`antlir/compiler/test_images/defs.bzl`. You may also get this " + "error if you are adding a new user-instantiatable rule to the " + "Antlir API. If so, read the `antlir_rule` section in " + "`website/docs/coding-conventions/bzl-and-targets.md`", ) # In OSS, the shimmed rules are preferred over the native rules (the # implicit loads of native rules is disabled) for consistency. Everything # in the main cell except the above exception(s) are allowed to use # oss_shim.bzl cell = _repository_name() # TODO: if antlir is intended to _only_ be used as a Buck cell, the '@' # check should be disabled. This is not currently the way the project is # setup, so it is required for now. if cell != "@antlir" and cell != "@": fail( 'Package `{}` must not `load("//antlir/bzl:'.format(package) + 'oss_shim.bzl")`. Antlir devs: read about `antlir_rule` in ' + "`website/docs/coding-conventions/bzl-and-targets.md`.", ) def _invert_dict(d): """ In OSS Buck some of the dicts used by targets (`srcs` and `resources` specifically) are inverted, where internally this: resources = { "//target:name": "label_of_resource" } In OSS Buck this is: resources = { "label_of_resource": "//target:name" } """ if d and types.is_dict(d): result = {value: key for key, value in d.items()} if len(result) != len(d): fail("_invert_dict fail! len(result): " + len(result) + " != len(d): " + len(d)) return result else: return d def _kernel(version): """ Resolve a kernel version to its corresponding kernel artifact. Currently, the only `kernel_artifact` available is in //third-party/fedora33/kernel:kernels.bzl. a `kernel_artifact`is a struct containing the following members: - uname - vmlinuz: compressed vmlinux - modules: kernel modules - headers: Includes the C header files that specify the interface between the Linux kernel and user-space libraries and programs. - devel: Contains the kernel headers and makefiles sufficient to build modules against the kernel package. """ if version in kernels: return kernels[version] else: fail("Unknown kernel version: {}".format(version)) def _normalize_deps(deps, more_deps = None): """ Create a single list of deps from one or 2 provided lists of deps. Additionally exclude any deps that have `/facebook` in the path as these are internal and require internal FB infra. """ _deps = deps or [] _more_deps = more_deps or [] _deps = _deps + _more_deps derps = [] for dep in _deps: if dep.find("facebook") < 0: derps.append(dep) return derps def _normalize_resources(resources): """ Exclude any resources that have `/facebook` in the path as these are internal and require internal FB infra. Only applies to resources specified in the `dict` format Will also go ahead an invert the dictionary using `_invert_dict` """ if resources and types.is_dict(resources): _normalized_dict_keys = _normalize_deps(resources.keys()) _normalized_resources = {key: resources[key] for key in _normalized_dict_keys} return _invert_dict(_normalized_resources) else: return resources def _normalize_coverage(coverages): """ Exclude any coverage requirements that have `facebook` in the path as these are internal. """ return [ (percent, dep) for percent, dep in coverages if "facebook" not in dep ] if coverages else None def _normalize_visibility(vis, name = None): """ OSS Buck has a slightly different handling of visibility. The default is to be not visible. For more info see: https://buck.build/concept/visibility.html """ if vis == None: return ["PUBLIC"] else: return vis def _normalize_pkg_style(style): """ Internally, zip and fastzip internally behave similar to how an `inplace` python binary behaves in OSS Buck. """ if not style: return None # Support some aliases that are used internally, otherwise return the style # directly if it is unrecognized if style in ("zip", "fastzip"): return "inplace" if style in ("xar",): return "standalone" return style def _third_party_library(project, rule = None, platform = None): """ Generate a target for a third-party library. This will return a target that is normalized into the form (see the README in `//third-party/...` more info on these targets): //third-party/<platform>/<project>:<rule> or //third-party/python:<project> for python rules Thee are currently only 2 platforms supported in OSS: - python - fedora33 If `platform` is not provided it is assumed to be `fedora33`. If `rule` is not provided it is assumed to be the same as `project`. """ _assert_package() # Antlir-private: only use with `oss_shim.bzl` macros. if not rule: rule = project if not platform: platform = _DEFAULT_NATIVE_PLATFORM if platform == "rust": if not rule == project: fail("rust dependencies must omit rule or be identical to project") # some projects have different paths if they are vendored out of fbsource return { "gazebo": "//generated/buck2/gazebo/gazebo:gazebo", "slog_glog_fmt": "//generated/common/rust/shed/slog_glog_fmt:slog_glog_fmt", "starlark": "//generated/buck2/starlark-rust/starlark:starlark", "starlark_derive": "//generated/buck2/starlark-rust/starlark_derive:starlark_derive", }.get(project, "//generated/third-party/rust:" + project) if platform == "python": if not rule == project: fail("python projects must omit rule or be identical to project") return "//third-party/python:" + project # We don't yet have this in OSS if project == "util-linux": if rule == "blkid": return None return "//third-party/" + platform + "/" + project + ":" + rule def _third_party_source(project, rule = "tarball"): """ Generate a target for a third-party source tarball. This will return a target that is normalized into the form (see the README in `//third-party/...` more info on these targets): //third-party/<platform>/<project>:<rule> If `platform` is not provided it is assumed to be `fedora33`. If `rule` is not provided it is assumed to be `tarball`. """ return "//third-party/source/{}:{}".format(project, rule) def _wrap_internal(fn, args, kwargs): """ Wrap a build target rule with support for the `antlir_rule` kwarg. Three rule types are supported: - "antlir-private" (default): Such rules MAY NOT be defined in user packages (outside of the Antlir codebase) -- see `_assert_package()`. - "user-internal": May be defined by user packages, but does not produce a build artifact that the user can use **directly**, e.g. "SUFFIX-<name>" intermediate rules, or `image.{feature,layer}`. - "user-facing": Allowed in user packages, and builds artifacts that the end user can use directly, e.g. `package.new`. Rules are private by default to force Antlir devs to explicitly annotate "user-internal" and "user-facing" rules. See also `website/docs/coding-conventions/bzl-and-targets.md`. """ rule_type = kwargs.pop(_RULE_TYPE_KWARG, _RULE_PRIVATE) if rule_type == _RULE_PRIVATE: # Private rules should only be defined within `antlir/`. _assert_package() elif rule_type not in _ALLOWED_RULES: fail( "Bad value {}, must be one of {}".format(rule_type, _ALLOWED_RULES), _RULE_TYPE_KWARG, ) # Antlir build outputs should not be visible outside of antlir by default. This # helps prevent our abstractions from leaking into other codebases as Antlir # becomes more widely adopted. kwargs["visibility"] = _normalize_visibility(kwargs.pop("visibility", None)) + [ "//antlir/...", "//bot_generated/antlir/...", "//metalos/...", ] fn(*args, **kwargs) def _command_alias(*args, **kwargs): _wrap_internal(native.command_alias, args, kwargs) def _filegroup(*args, **kwargs): _wrap_internal(native.filegroup, args, kwargs) def _genrule(*args, **kwargs): # For future use to support target platforms kwargs.pop("flavor_config", None) if "out" not in kwargs: kwargs["out"] = "out" _wrap_internal(native.genrule, args, kwargs) def _http_file(*args, **kwargs): _wrap_internal(native.http_file, args, kwargs) def _http_archive(*args, **kwargs): _wrap_internal(native.http_archive, args, kwargs) def _sh_binary(*args, **kwargs): _wrap_internal(native.sh_binary, args, kwargs) def _sh_test(*args, **kwargs): _wrap_internal(native.sh_test, args, kwargs) def _worker_tool(*args, **kwargs): _wrap_internal(native.worker_tool, args, kwargs) def _cxx_external_deps(kwargs): external_deps = kwargs.pop("external_deps", []) return ["//third-party/cxx:" + lib for _project, _version, lib in external_deps] def _impl_cpp_binary(name, tags = None, **kwargs): native.cxx_binary( name = name, labels = tags or [], deps = _normalize_deps(kwargs.pop("deps", []), _cxx_external_deps(kwargs)), **kwargs ) def _cpp_binary(*args, **kwargs): _wrap_internal(_impl_cpp_binary, args, kwargs) def _impl_cpp_library(name, tags = None, **kwargs): native.cxx_library( name = name, labels = tags or [], deps = _normalize_deps(kwargs.pop("deps", []), _cxx_external_deps(kwargs)), **kwargs ) def _cpp_library(*args, **kwargs): _wrap_internal(_impl_cpp_library, args, kwargs) def _impl_cpp_unittest(name, tags = None, **kwargs): native.cxx_test( name = name, labels = tags or [], deps = _normalize_deps(kwargs.pop("deps", []), _cxx_external_deps(kwargs)), **kwargs ) def _cpp_unittest(*args, **kwargs): _wrap_internal(_impl_cpp_unittest, args, kwargs) def _cxx_genrule(*args, **kwargs): _wrap_internal(_impl_cxx_genrule, args, kwargs) def _impl_cxx_genrule(tags = None, **kwargs): native.cxx_genrule( labels = tags or [], **kwargs ) def _export_file(*args, **kwargs): _wrap_internal(native.export_file, args, kwargs) def _impl_python_binary( name, main_module, par_style = None, resources = None, visibility = None, **kwargs): _impl_python_library( name = name + "-library", resources = resources, visibility = visibility, **kwargs ) native.python_binary( name = name, main_module = main_module, package_style = _normalize_pkg_style(par_style), deps = [":" + name + "-library"], visibility = visibility, ) def _python_binary(*args, **kwargs): _wrap_internal(_impl_python_binary, args, kwargs) def _impl_python_library( name, deps = None, resources = None, srcs = None, **kwargs): native.python_library( name = name, deps = _normalize_deps(deps), resources = _normalize_resources(resources), srcs = _invert_dict(srcs), **kwargs ) def _python_library(*args, **kwargs): _wrap_internal(_impl_python_library, args, kwargs) def _impl_python_unittest( cpp_deps = "ignored", deps = None, needed_coverage = None, par_style = None, tags = None, resources = None, srcs = None, **kwargs): native.python_test( deps = _normalize_deps(deps), labels = tags or [], needed_coverage = _normalize_coverage(needed_coverage), package_style = _normalize_pkg_style(par_style), resources = _normalize_resources(resources), srcs = _invert_dict(srcs), **kwargs ) def _python_unittest(*args, **kwargs): _wrap_internal(_impl_python_unittest, args, kwargs) def _split_rust_kwargs(kwargs): # process some kwargs common to all rust targets, as well as some split # kwargs from rust_{binary,library} that are forwarded to implicit # rust_unittest targets if not kwargs.get("crate_root", None): topsrc_options = (kwargs.get("name") + ".rs", "main.rs") topsrc = [] for src in (kwargs.get("srcs", None) or []): if src.startswith(":"): continue if paths.basename(src) in topsrc_options: topsrc.append(src) if len(topsrc) == 1: kwargs["crate_root"] = topsrc[0] test_kwargs = None # automatically generate a unittest target if the caller did not explicitly # opt out if kwargs.pop("unittests", True): test_mapped_srcs = kwargs.get("mapped_srcs", {}) test_mapped_srcs.update(kwargs.pop("test_mapped_srcs", {})) test_kwargs = { "crate": kwargs.get("crate", kwargs.get("name").replace("-", "_")), "crate_root": kwargs.get("crate_root"), "deps": kwargs.get("deps", []) + kwargs.pop("test_deps", []), "labels": kwargs.get("labels", []), "mapped_srcs": test_mapped_srcs, "name": kwargs.get("name") + "-unittest", "srcs": kwargs.get("srcs", []) + kwargs.pop("test_srcs", []), } return kwargs, test_kwargs def _rust_unittest(*args, **kwargs): kwargs.pop("allocator", None) kwargs.pop("nodefaultlibs", None) _wrap_internal(native.rust_test, args, kwargs) def _rust_binary(*args, **kwargs): # Inside FB, we are a little special and explicitly use `malloc` as our # allocator, and avoid linking to some always-present FB libraries in order # to keep our environment simple and produce small binaries. In OSS, this # isn't required (yet), since the default platforms will be close to this # goal already. kwargs.pop("allocator", None) kwargs.pop("nodefaultlibs", None) kwargs, test_kwargs = _split_rust_kwargs(kwargs) _wrap_internal(native.rust_binary, args, kwargs) if test_kwargs: _rust_unittest(**test_kwargs) def _rust_library(*args, **kwargs): kwargs.pop("autocargo", None) kwargs, test_kwargs = _split_rust_kwargs(kwargs) _wrap_internal(native.rust_library, args, kwargs) if test_kwargs: _rust_unittest(**test_kwargs) def _rust_bindgen_library(name, *args, **kwargs): print("{}: rust_bindgen_library not yet supported in oss".format(name)) # generate a target so that the target graph for //antlir/... is not broken, # but crates that require this will definitely not compile _rust_library( name = name, mapped_srcs = { "//antlir:empty": "src/lib.rs", }, unittests = False, ) # This is heavily inspired by the fbcode rust bindgen rule but isn't exactly the same. # A few differences are: # - removed other platform related stuff like windows rules # - changed the -sym to use exported_linker_flags because we transform that automatically in fbcode def _rust_python_extension( name, base_module = None, module_name = None, deps = None, types = (), visibility = None, **kwargs): real_deps = [] real_deps.append("//generated/third-party/rust:cpython") if deps != None: real_deps.extend(deps) visibility = visibility or [] _rust_library( name = name + "-lib", visibility = ["//{}:{}".format(native.package_name(), name)] + visibility, # Make sure we get linked into the otherwise empty C++ python extension # below. preferred_linkage = "static", # Disable unit tests -- a Python extension is never meant to be compiled # as a standalone executable, and will be missing symbols like # _Py_Dealloc normally provided into it by Python if you try to link # it as one. unittests = False, deps = real_deps, **kwargs ) # TODO Currently, Rust rules don't support `link_whole`, so use # a workaround to propagate an undefined symbol to force the above library # to get linked into the extension below. symbol_name = "PyInit_{}".format(module_name or name) _cpp_library( name = name + "-sym", exported_linker_flags = ["-u" + symbol_name], preferred_linkage = "static", visibility = ["//{}:{}".format(native.package_name(), name)] + visibility, ) _cpp_python_extension( name = name, base_module = base_module, module_name = module_name, types = types, # We're just here to wrap the rust library above. deps = [ ":" + name + "-lib", ":" + name + "-sym", ], ) # Again very heavily inspired by the fbcode version of this function. Mostly just # removed extra stuff that wasn't needed for our more narror usecase def _cpp_python_extension( name, deps = (), types = (), base_module = None, **kwargs): if types: _python_library( name = name + "__types_subs", srcs = types, base_module = base_module, ) deps = list(deps) deps.append(":" + name + "__types_stubs") native.cxx_python_extension( name = name, deps = deps, base_module = base_module, **kwargs ) # Use = in the default filename to avoid clashing with RPM names. # The constant must match `update_allowed_versions.py`. # Omits `_wrap_internal` due to perf paranoia -- we have a callsite per RPM. def _rpm_vset(name, src = "empty=rpm=vset"): native.export_file( name = name, src = src, mode = "reference", # `image.layer`s all over the repo will depend on these visibility = ["PUBLIC"], ) def _thrift_library(name, *args, languages = (), **kwargs): print("thrift_library not yet supported in oss") # generate an empty target so that the target graph in oss tests is not # broken, it will just fail to build things that depend on this if "rust" in languages: _rust_library( name = name + "-rust", mapped_srcs = {"//antlir:empty": "src/lib.rs"}, ) ### BEGIN COPY-PASTA (@fbcode_macros//build_defs/lib:rule_target_types.bzl) # @lint-ignore BUILDIFIERLINT _RuleTargetProvider = provider(fields = [ "name", # The name of the rule "base_path", # The base package within the repository "repo", # Either the cell or None (for the root cell) ]) def _RuleTarget(repo, base_path, name): return _RuleTargetProvider(name = name, base_path = base_path, repo = repo) ### END COPY-PASTA ### BEGIN COPY-PASTA (@fbcode_macros//build_defs/lib:target_utils.bzl) def _parse_target(target, default_repo = None, default_base_path = None): if target.count(":") != 1: fail('rule name must contain exactly one ":": "{}"'.format(target)) repo_and_base_path, name = target.split(":") # Parse relative target. if not repo_and_base_path: return _RuleTarget(default_repo, default_base_path, name) # Parse absolute targets. if repo_and_base_path.count("//") != 1: fail('absolute rule name must contain one "//" before ":": "{}"'.format(target)) repo, base_path = repo_and_base_path.split("//", 1) repo = repo or default_repo return _RuleTarget(repo, base_path, name) def _to_label(repo, path, name): return "{}//{}:{}".format(repo, path, name) ### END COPY-PASTA ### BEGIN COPY-PASTA (@fbcode_macros//build_defs/lib:common_paths.bzl) def _get_buck_out_path(): # The buck out path can either be configured using the project.buck_out # key, or it can be provided on the command line via the --isolation-prefix # argument, in which case it appears as the buck.base_buck_out_dir key). # The dance here is to ensure that buck.base_buck_out_dir always defines # the root of the buck output directory and any configured directory is # beneath that. This matches the logic that exists within Buck itself. config_out = native.read_config("project", "buck_out", None) base_dir = native.read_config("buck", "base_buck_out_dir", "buck-out") if config_out == None: return base_dir if config_out.startswith("buck-out") and base_dir != "buck-out": return config_out.replace("buck-out", base_dir) return config_out ### END COPY-PASTA def _repository_name(): return "@" def _get_antlir_cell_name(): return "" def _is_buck2(): return False # Please keep each section lexicographically sorted. shim = struct( # # Rules -- IMPORTANT -- wrap **ALL** rules with `_wrap_internal` # buck_command_alias = _command_alias, buck_filegroup = _filegroup, buck_genrule = _genrule, buck_sh_binary = _sh_binary, buck_sh_test = _sh_test, buck_worker_tool = _worker_tool, # # Utility functions -- use `_assert_package()`, if at all possible. # config = struct( get_buck_out_path = _get_buck_out_path, get_antlir_cell_name = _get_antlir_cell_name, ), cpp_binary = _cpp_binary, cpp_library = _cpp_library, cpp_unittest = _cpp_unittest, cxx_genrule = _cxx_genrule, # # Constants # default_vm_image = struct( layer = "//antlir/vm:default-image", package = "//antlir/vm:default-image.btrfs", ), do_not_use_repo_cfg = _do_not_use_repo_cfg, export_file = _export_file, get_visibility = _normalize_visibility, http_file = _http_file, http_archive = _http_archive, is_buck2 = _is_buck2, kernel_get = struct( base_target = "//third-party/fedora33/kernel", default = _kernel("5.8.15-301.fc33.x86_64"), get = _kernel, versions = kernels, ), platform_utils = None, python_binary = _python_binary, python_library = _python_library, python_unittest = _python_unittest, repository_name = _repository_name, rust_binary = _rust_binary, rust_bindgen_library = _rust_bindgen_library, rust_library = _rust_library, rust_python_extension = _rust_python_extension, rust_unittest = _rust_unittest, rpm_vset = _rpm_vset, # Not wrapped due to perf paranoia. thrift_library = _thrift_library, target_utils = struct( parse_target = _parse_target, to_label = _to_label, ), third_party = struct( library = _third_party_library, source = _third_party_source, ), )