diff --git a/.bazelrc b/.bazelrc index a5b9664ce6..a1f14356c4 100644 --- a/.bazelrc +++ b/.bazelrc @@ -96,3 +96,7 @@ import %workspace%/specialized_configs.bazelrc import %workspace%/buildbuddy.bazelrc try-import user.bazelrc + +# TODO: Delete before merging. rules_runfiles_group's describer API isn't in the +# BCR yet; see https://github.com/bazelbuild/rules_java/pull/368. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/MODULE.bazel b/MODULE.bazel index a03eb49a01..b2d52ac564 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -9,6 +9,7 @@ bazel_dep(name = "bazel_skylib", version = "1.8.2") bazel_dep(name = "package_metadata", version = "0.0.7") bazel_dep(name = "platforms", version = "0.0.11") bazel_dep(name = "rules_cc", version = "0.2.17") +bazel_dep(name = "rules_runfiles_group", version = "99.0.0-dev.20261019.1.d105dfa") bazel_dep(name = "toml.bzl", version = "0.4.1") # Those are loaded only when using py_proto_library diff --git a/examples/bzlmod/.bazelrc b/examples/bzlmod/.bazelrc index 28a44a7523..aaeaa7befe 100644 --- a/examples/bzlmod/.bazelrc +++ b/examples/bzlmod/.bazelrc @@ -24,3 +24,6 @@ test --test_output=errors --enable_runfiles # Windows requires these for multi-python support: build --enable_runfiles common:bazel7.x --incompatible_python_disallow_native_rules + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/examples/multi_python_versions/.bazelrc b/examples/multi_python_versions/.bazelrc index 97a973bd85..e019fc2592 100644 --- a/examples/multi_python_versions/.bazelrc +++ b/examples/multi_python_versions/.bazelrc @@ -5,3 +5,6 @@ build --enable_runfiles coverage --java_runtime_version=remotejdk_11 common:bazel7.x --incompatible_python_disallow_native_rules + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/examples/pip_parse/.bazelrc b/examples/pip_parse/.bazelrc index f263a1744d..4796c06451 100644 --- a/examples/pip_parse/.bazelrc +++ b/examples/pip_parse/.bazelrc @@ -1,3 +1,6 @@ # https://docs.bazel.build/versions/main/best-practices.html#using-the-bazelrc-file try-import %workspace%/user.bazelrc common --incompatible_python_disallow_native_rules + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/gazelle/.bazelrc b/gazelle/.bazelrc index c098d0c392..eee906dd3d 100644 --- a/gazelle/.bazelrc +++ b/gazelle/.bazelrc @@ -27,3 +27,6 @@ build --enable_runfiles common --noincompatible_no_implicit_file_export common:bazel7.x --incompatible_python_disallow_native_rules + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/gazelle/examples/bzlmod_build_file_generation/.bazelrc b/gazelle/examples/bzlmod_build_file_generation/.bazelrc index 31097b41de..4192d2cbfa 100644 --- a/gazelle/examples/bzlmod_build_file_generation/.bazelrc +++ b/gazelle/examples/bzlmod_build_file_generation/.bazelrc @@ -14,3 +14,6 @@ common:bazel7.x --incompatible_python_disallow_native_rules # rules_python code. In the BCR presubmits, this override is removed # and the bazel_dep version of rules_python is used. common --override_module=rules_python=../../../ + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/internal_dev_deps.bzl b/internal_dev_deps.bzl index 68b8f89e76..291da0810b 100644 --- a/internal_dev_deps.bzl +++ b/internal_dev_deps.bzl @@ -252,9 +252,9 @@ def rules_python_internal_deps(): http_archive( name = "bazel_features", - sha256 = "d7787da289a7fb497352211ad200ec9f698822a9e0757a4976fd9f713ff372b3", - strip_prefix = "bazel_features-1.9.1", - url = "https://github.com/bazel-contrib/bazel_features/releases/download/v1.9.1/bazel_features-v1.9.1.tar.gz", + sha256 = "5450bfb2c8b4bc961c75368838f86156f563cc9adef1be7d504fc5619d54daab", + strip_prefix = "bazel_features-1.51.0", + url = "https://github.com/bazel-contrib/bazel_features/releases/download/v1.51.0/bazel_features-v1.51.0.tar.gz", ) http_archive( diff --git a/news/3757.added.md b/news/3757.added.md new file mode 100644 index 0000000000..dc8758af62 --- /dev/null +++ b/news/3757.added.md @@ -0,0 +1,5 @@ +(rules) `py_binary` and `py_test` describe their runfiles groups for +[rules_runfiles_group](https://github.com/bazel-contrib/rules_runfiles_group), +so packaging rules like rules_img's `image_from_binary` can put the runtime, +each PyPI package and each library in a layer of its own. Needs Bazel 8.8 or +later ([#3757](https://github.com/bazel-contrib/rules_python/issues/3757)). diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel index 075b77b05d..0d25eb3e38 100644 --- a/python/private/BUILD.bazel +++ b/python/private/BUILD.bazel @@ -20,6 +20,12 @@ load(":bazel_config_mode.bzl", "bazel_config_mode") load(":distribution_filegroup.bzl", "distribution_filegroup") load(":py_exec_tools_toolchain.bzl", "current_interpreter_executable") load(":py_interpreter_program.bzl", "py_interpreter_program") +load( + ":runfiles_groups.bzl", + "py_executable_runfiles_group_describer", + "py_library_runfiles_group_describer", + "py_runtime_pair_runfiles_group_describer", +) load(":sentinel_impl.bzl", "sentinel") load(":stamp_impl.bzl", "stamp_build_setting") load(":uncachable_version_file.bzl", "define_uncachable_version_file") @@ -849,6 +855,23 @@ bzl_library( ], ) +bzl_library( + name = "runfiles_groups", + srcs = ["runfiles_groups.bzl"], + deps = [ + ":attributes", + ":common", + ":flags", + ":py_executable", + ":py_executable_info", + ":py_info", + ":py_internal", + ":py_library", + ":reexports", + "@rules_runfiles_group//runfiles_group:describe", + ], +) + bzl_library( name = "runtime_env_repo", srcs = ["runtime_env_repo.bzl"], @@ -1044,3 +1067,21 @@ bzl_library( name = "visibility", srcs = ["visibility.bzl"], ) + +# Runfiles group describers of the Python rules (see runfiles_groups.bzl), held +# by their _aspect_rule_hints attributes. Not actually public; only public +# because they're implicit dependencies of rules built via the rule builders. +py_library_runfiles_group_describer( + name = "py_library_runfiles_group_describer", + visibility = ["//visibility:public"], +) + +py_executable_runfiles_group_describer( + name = "py_executable_runfiles_group_describer", + visibility = ["//visibility:public"], +) + +py_runtime_pair_runfiles_group_describer( + name = "py_runtime_pair_runfiles_group_describer", + visibility = ["//visibility:public"], +) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index e8240da894..b7360b742a 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -93,10 +93,17 @@ EXECUTABLE_ATTRS = dicts.add( WINDOWS_CONSTRAINTS_ATTRS, # starlark flags attributes { + # Read by rules_runfiles_group's packaging aspects; see runfiles_groups.bzl. + "_aspect_rule_hints": attr.label_list(default = [ + "//python/private:py_executable_runfiles_group_describer", + # What //python:toolchain_type is an alias of. + "@bazel_tools//tools/python:toolchain_type", + ]), "_build_python_zip_flag": attr.label(default = "//python/config_settings:build_python_zip"), "_default_to_explicit_init_py_flag": attr.label(default = "//python/config_settings:incompatible_default_to_explicit_init_py"), "_python_import_all_repositories_flag": attr.label(default = "//python/config_settings:experimental_python_import_all_repositories"), "_python_path_flag": attr.label(default = "//python/config_settings:python_path"), + "_runfiles_group_attrs": attr.string_list(default = ["_debugger_flag", "data", "deps", "srcs"]), }, { "interpreter_args": lambda: attrb.StringList( diff --git a/python/private/py_library.bzl b/python/private/py_library.bzl index 9b1e064853..b3368f5960 100644 --- a/python/private/py_library.bzl +++ b/python/private/py_library.bzl @@ -116,6 +116,11 @@ This allows optimizing the generation of symlinks to be cheaper at analysis time "_add_srcs_to_runfiles_flag": lambda: attrb.Label( default = labels.ADD_SRCS_TO_RUNFILES, ), + # Read by rules_runfiles_group's packaging aspects; see runfiles_groups.bzl. + "_aspect_rule_hints": lambda: attrb.LabelList( + default = ["//python/private:py_library_runfiles_group_describer"], + ), + "_runfiles_group_attrs": lambda: attrb.StringList(default = ["data", "deps", "srcs"]), }, ) diff --git a/python/private/py_repositories.bzl b/python/private/py_repositories.bzl index 3489613a6c..8511f83286 100644 --- a/python/private/py_repositories.bzl +++ b/python/private/py_repositories.bzl @@ -72,6 +72,12 @@ def py_repositories(transition_settings = []): strip_prefix = "rules_cc-0.1.5", urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.1.5/rules_cc-0.1.5.tar.gz"], ) + http_archive( + name = "rules_runfiles_group", + sha256 = "0c5a2998f970e8ec27daa9bc10c3cd0d4a7167371acd70974a75ab8aafb41c74", + strip_prefix = "rules_runfiles_group-d105dfab75fb550bcfc0d2cc033b27fad0dfc2e4", + url = "https://bazel-contrib.github.io/rules_runfiles_group/assets/rules_runfiles_group/99.0.0-dev.20261019.1.d105dfa/source.tar.gz", + ) http_archive( name = "toml.bzl", sha256 = "63633c762bdb4d836add5a9a81deeeae93c4b2edbd62ac032733020b5652b90a", diff --git a/python/private/py_runtime_pair_rule.bzl b/python/private/py_runtime_pair_rule.bzl index c339e0bd67..ea153a0e0d 100644 --- a/python/private/py_runtime_pair_rule.bzl +++ b/python/private/py_runtime_pair_rule.bzl @@ -133,6 +133,11 @@ The runtime to use for Python 3 targets. Must have `python_version` set to `PY3`. """, ), + # Read by rules_runfiles_group's packaging aspects; see runfiles_groups.bzl. + "_aspect_rule_hints": attr.label_list( + default = ["//python/private:py_runtime_pair_runfiles_group_describer"], + ), + "_runfiles_group_attrs": attr.string_list(default = []), "_visible_for_testing": attr.label( default = labels.VISIBLE_FOR_TESTING, ), diff --git a/python/private/runfiles_groups.bzl b/python/private/runfiles_groups.bzl new file mode 100644 index 0000000000..d07d8deb80 --- /dev/null +++ b/python/private/runfiles_groups.bzl @@ -0,0 +1,489 @@ +"""Runfiles group describers of the Python rules, for rules_runfiles_group. + +Packaging rules built on rules_runfiles_group (e.g. rules_img's +`image_from_binary`) split a binary's runfiles into groups, typically one image +layer each. They walk the binary with an aspect and call the describer each +target lists in its `_aspect_rule_hints`, which says what runfiles the target +adds itself and which attributes the aspect merges groups in from. + +A `py_binary` or `py_test` gets these groups: + +* `rules_python#runtime`: the interpreter and standard library, described by the + Python toolchain's `py_runtime_pair`. The aspect only walks toolchains on + Bazel 8.8 or later. +* `rules_python#pypi/`: one per PyPI distribution. +* One per `py_library`, and one per rule target in `data`, named by its label. +* The binary's own: its sources, bootstrap files and venv. + +Only `//python/private:BUILD.bazel` loads this file, and the rules refer to the +describers by label: with Bazel 8's autoloads, loading rules_runfiles_group from +the files that define the rules is a load cycle. +""" + +load("@rules_runfiles_group//runfiles_group:describe.bzl", "describe") +load(":attributes.bzl", "PycCollectionAttr") +load(":common.bzl", "is_py_source", "py_info_runfiles_files") +load(":flags.bzl", "PyiDepsToRunfilesFlag") +load(":py_executable.bzl", "get_runtime_files") +load(":py_executable_info.bzl", "PyExecutableInfo") +load(":py_info.bzl", "PyInfo") +load(":py_internal.bzl", "py_internal") +load(":py_library.bzl", "get_package_and_version") +load(":reexports.bzl", "BuiltinPyInfo") + +# Stamped on every group, so Python groups stay together when a packager has to +# merge groups to fit a layer limit. +_MERGE_AFFINITY = "rules_python" + +_RUNTIME_GROUP = "rules_python#runtime" + +# Named by distribution rather than by label: whl_library's repository names are +# an implementation detail. +_PYPI_GROUP_PREFIX = "rules_python#pypi/" + +# The toolchain type //python:toolchain_type is an alias of; the aspect matches +# toolchains by the actual label. +_RUNTIME_MERGE_FROM = describe.merge_from( + toolchain = Label("@bazel_tools//tools/python:toolchain_type"), +) + +def _pyc_collection_enabled(ctx, pyc_collection = PycCollectionAttr.INHERIT): + """Whether a binary includes implicit pyc files instead of their sources. + + Args: + ctx: {type}`ctx` the aspect ctx. + pyc_collection: {type}`str` a binary's `pyc_collection` attribute; the + default gives the configuration's choice. + + Returns: + {type}`bool` + """ + + # PycCollectionAttr only reads these two attributes. + return PycCollectionAttr.is_pyc_collection_enabled(struct(attr = struct( + pyc_collection = pyc_collection, + _precompile_flag = ctx.rule.attr._precompile_flag, + ))) + +def _is_file(target): + """Whether a target is a single file rather than a rule. + + Args: + target: {type}`Target` an attribute's dependency. + + Returns: + {type}`bool` + """ + files = target[DefaultInfo].files + if not py_internal.is_singleton_depset(files): + return False + file = files.to_list()[0] + + # A source file owns itself and a generated file is owned by the rule that + # generates it, while a rule's own outputs are owned by the rule. + return (file.owner == target.label) == file.is_source + +def _is_empty(runfiles): + """Whether a runfiles object holds nothing. + + Args: + runfiles: {type}`runfiles` + + Returns: + {type}`bool` + """ + return not (runfiles.files or runfiles.symlinks or runfiles.root_symlinks) + +def _has_py_info(target): + """Whether a target provides PyInfo or the builtin PyInfo. + + Args: + target: {type}`Target` + + Returns: + {type}`bool` + """ + return PyInfo in target or (BuiltinPyInfo != None and BuiltinPyInfo in target) + +def _py_info_files(target, pyc_collection_enabled): + """The files a binary takes from a target's PyInfo. + + Args: + target: {type}`Target` + pyc_collection_enabled: {type}`bool` whether the binary includes + implicit pyc files instead of their sources. + + Returns: + {type}`list[depset[File]]` empty for a target without PyInfo. + """ + if PyInfo in target: + return py_info_runfiles_files(target[PyInfo], pyc_collection_enabled) + if BuiltinPyInfo != None and BuiltinPyInfo in target: + return py_info_runfiles_files(target[BuiltinPyInfo], pyc_collection_enabled) + return [] + +def _union(ctx, contents): + """Combines group contents into one. + + Args: + ctx: {type}`ctx` the aspect ctx. + contents: {type}`list[depset[File] | runfiles]` + + Returns: + {type}`depset[File] | runfiles` a depset if every content is one. + """ + files = [c for c in contents if type(c) == "depset"] + runfiles = [c for c in contents if type(c) != "depset"] + if not runfiles: + return depset(transitive = files) + return ctx.runfiles(transitive_files = depset(transitive = files)).merge_all(runfiles) + +def _kind(label): + """The `kind` of the group of a target's own files. + + Args: + label: {type}`Label` the target's label. + + Returns: + {type}`str` `first_party` for the main repository, else `third_party`. + """ + return "first_party" if label.repo_name == "" else "third_party" + +def _pypi_entry(package, content, weight = None): + """An entry in the group of a PyPI distribution. + + Args: + package: {type}`str` the distribution name, as normalized by + `get_package_and_version`. + content: {type}`depset[File] | runfiles` + weight: {type}`int | None` see `describe.entry`. + + Returns: + {type}`RunfilesGroupInfo` + """ + return describe.entry( + name = _PYPI_GROUP_PREFIX + package, + content = content, + kind = "third_party", + rank = describe.RANK_SHARED_DEPS, + weight = weight, + merge_affinity = _MERGE_AFFINITY, + ) + +def _own_files(target, ctx, pyc_collection_enabled): + """The files a target itself puts into a binary's runfiles. + + That is its sources (or their implicit pyc files), its other outputs, and + the plain files in its `data`. Its dependencies' files are left to their own + groups. + + Args: + target: {type}`Target` a `py_library`, `py_binary` or `py_test`. + ctx: {type}`ctx` the aspect ctx. + pyc_collection_enabled: {type}`bool` whether the binary includes + implicit pyc files instead of their sources. + + Returns: + {type}`depset[File]` + """ + info = target[PyInfo] + + # A Python target in srcs (whl_library's :pkg wraps its :srcs) describes its + # own files. + foreign = { + f: None + for dep in ctx.rule.attr.srcs + if _has_py_info(dep) + for f in dep[DefaultInfo].files.to_list() + } + + # Sources that aren't .py files (directories, .pyc files) are outputs, but + # never runfiles. + sources = {f: None for f in info.direct_original_sources.to_list() if f not in foreign} + direct = [ + f + for f in target[DefaultInfo].files.to_list() + if f not in foreign and (f not in sources or is_py_source(f)) + ] + transitive = [] + if pyc_collection_enabled: + transitive.append(info.direct_implicit_pyc_files) + else: + direct.extend([f for f in sources if is_py_source(f)]) + + # A plain file listed in several targets' data lands in each of their groups. + for dep in ctx.rule.attr.data: + if _is_file(dep): + direct.append(dep[DefaultInfo].files.to_list()[0]) + return depset(direct, transitive = transitive) + +def _dep_fallback(ctx, dep, py_outputs): + """The group of a dependency that doesn't describe its own groups. + + Args: + ctx: {type}`ctx` the aspect ctx of the merging target. + dep: {type}`Target` the dependency. + py_outputs: {type}`bool` whether to include the dependency's .py outputs + when it has no PyInfo. + + Returns: + {type}`RunfilesGroupInfo | None` None if it contributes nothing. + """ + content = _py_info_files(dep, _pyc_collection_enabled(ctx)) + if py_outputs and not content: + content.append(depset([f for f in dep[DefaultInfo].files.to_list() if is_py_source(f)])) + runfiles = dep[DefaultInfo].default_runfiles + if not _is_empty(runfiles): + content.append(runfiles) + if not content: + return None + return describe.entry(name = dep.label, content = _union(ctx, content)) + +def _library_dep_fallback(ctx, dep): + """`_dep_fallback` for a `py_library`'s `deps`. + + A `py_library` adds the .py outputs of a dependency without PyInfo to its own + PyInfo, so the dependency's group holds them. + + Args: + ctx: {type}`ctx` the aspect ctx of the library. + dep: {type}`Target` the dependency. + + Returns: + {type}`RunfilesGroupInfo | None` + """ + return _dep_fallback(ctx, dep, py_outputs = True) + +def _executable_dep_fallback(ctx, dep): + """`_dep_fallback` for a binary's `deps`. + + Args: + ctx: {type}`ctx` the aspect ctx of the binary. + dep: {type}`Target` the dependency. + + Returns: + {type}`RunfilesGroupInfo | None` + """ + return _dep_fallback(ctx, dep, py_outputs = False) + +def _data_fallback(ctx, dep): + """The group of a rule target in `data` that doesn't describe its own groups. + + Args: + ctx: {type}`ctx` the aspect ctx of the merging target. + dep: {type}`Target` the dependency. + + Returns: + {type}`RunfilesGroupInfo | None` None for a plain file, which is in the + merging target's own group, or a target that contributes nothing. + """ + default_info = dep[DefaultInfo] + if _is_file(dep) or (not default_info.files and _is_empty(default_info.data_runfiles)): + return None + content = _union(ctx, [default_info.files, default_info.data_runfiles]) + return describe.entry(name = dep.label, content = content) + +def _srcs_fallback(ctx, dep): + """The group of a rule target in `srcs` that doesn't describe its own groups. + + Args: + ctx: {type}`ctx` the aspect ctx of the merging target. + dep: {type}`Target` the dependency. + + Returns: + {type}`RunfilesGroupInfo | None` None for a plain file, which is in the + merging target's own group. + """ + if _is_file(dep): + return None + return _dep_fallback(ctx, dep, py_outputs = False) + +def _describe_py_library(target, ctx): + """Describes a `py_library`: one group for its own files. + + The group is named after the distribution for a library with a + `.dist-info/METADATA` file in `data` (i.e. one from a wheel), and takes + everything in its `data` too. Otherwise it's named by the library's label. + Its weight is the number of files in `srcs` and `data`. + + Args: + target: {type}`Target` the library. + ctx: {type}`ctx` the aspect ctx. + + Returns: + {type}`struct` from `describe.node`. + """ + pyc_collection_enabled = _pyc_collection_enabled(ctx) + content = _own_files(target, ctx, pyc_collection_enabled) + + # With pyi_deps_to_runfiles, a binary takes the files of pyi_deps from this + # target's PyInfo, but not their runfiles. + if PyiDepsToRunfilesFlag.is_enabled(struct(attr = ctx.rule.attr, var = ctx.var)): + content = depset(transitive = [content] + [ + files + for dep in ctx.rule.attr.pyi_deps + for files in _py_info_files(dep, pyc_collection_enabled) + ]) + + weight = len(ctx.rule.files.srcs) + len(ctx.rule.files.data) + data = describe.merge_from("data", fallback = _data_fallback) + package, _ = get_package_and_version(ctx.rule.files.data) + if package: + entry = _pypi_entry(package, content, weight) + + # whl_library lists a console script in `data` both on its own and in a + # filegroup, which would put it in two groups. + data = describe.merge_from("data", fallback = _data_fallback, into = entry.name) + else: + kind = _kind(ctx.label) + entry = describe.entry( + name = ctx.label, + content = content, + kind = kind, + rank = describe.RANK_SHARED_DEPS if kind == "third_party" else describe.RANK_EXECUTABLE, + weight = weight, + merge_affinity = _MERGE_AFFINITY, + ) + return describe.node( + add = [entry] if content else [], + merge_from = [ + data, + describe.merge_from("deps", fallback = _library_dep_fallback), + describe.merge_from("srcs", fallback = _srcs_fallback), + ], + ) + +py_library_runfiles_group_describer = describe.make_describer_rule( + describe = _describe_py_library, +) + +def _venv_entries(ctx, venv_runfiles): + """Moves the venv's links to PyPI distribution files into their groups. + + A packager that stores symlinks as copies of their targets can then keep each + link next to the file it points to, instead of copying the file (typically a + shared library) into the binary's group. + + Args: + ctx: {type}`ctx` the aspect ctx of the binary. + venv_runfiles: {type}`runfiles` the binary's `PyExecutableInfo.venv_runfiles`. + + Returns: + {type}`tuple[list[RunfilesGroupInfo], runfiles]` the entries for the + distributions' groups, and the venv runfiles left for the binary's. + """ + + # Keyed by every file an entry covers, not just the one it links to: where + # distributions share a directory (namespace packages), the venv links each + # file in it instead. + packages = { + file: entry.package + for entry in depset(transitive = [ + dep[PyInfo].venv_symlinks + for dep in ctx.rule.attr.deps + if PyInfo in dep + ]).to_list() + if entry.package + for file in entry.files.to_list() + } + by_package = {} + own_links = {} + for link in venv_runfiles.root_symlinks.to_list(): + package = packages.get(link.target_file) + links = by_package.setdefault(package, {}) if package else own_links + links[link.path] = link.target_file + entries = [ + _pypi_entry(package, ctx.runfiles(root_symlinks = links)) + for package, links in by_package.items() + ] + return entries, ctx.runfiles(transitive_files = venv_runfiles.files, root_symlinks = own_links) + +def _describe_py_executable(target, ctx): + """Describes a `py_binary` or `py_test`. + + Its group holds its own files, bootstrap files and venv, and is the group + the packager puts the executable in. The runtime and the dependencies' + groups are merged in. + + Args: + target: {type}`Target` the binary. + ctx: {type}`ctx` the aspect ctx. + + Returns: + {type}`struct` from `describe.node`. + """ + info = target[PyExecutableInfo] + pyc_collection_enabled = _pyc_collection_enabled(ctx, ctx.rule.attr.pyc_collection) + content = [ + _own_files(target, ctx, pyc_collection_enabled), + depset([f for f in (info.build_data_file, info.stage2_bootstrap) if f]), + ] + venv_runfiles = info.venv_runfiles + add = [] + + # Two cases where the binary's group takes its dependencies: + # * The legacy __init__.py files: they're generated for every directory in + # the runfiles, the runtime's included. Checking for them is cheap unless + # they exist. + # * A pyc selection that differs from the configuration's, which is the one + # dependencies describe their files with. + if info.app_runfiles.empty_filenames: + content.append(info.runfiles_without_exe) + merge_from = [] + elif pyc_collection_enabled != _pyc_collection_enabled(ctx): + content.append(info.app_runfiles) + merge_from = [_RUNTIME_MERGE_FROM] + else: + if venv_runfiles: + add, venv_runfiles = _venv_entries(ctx, venv_runfiles) + merge_from = [ + _RUNTIME_MERGE_FROM, + describe.merge_from("_debugger_flag", fallback = _executable_dep_fallback), + describe.merge_from("data", fallback = _data_fallback), + describe.merge_from("deps", fallback = _executable_dep_fallback), + describe.merge_from("srcs", fallback = _srcs_fallback), + ] + content.extend([rf for rf in (venv_runfiles, info.venv_interpreter_runfiles) if rf]) + + return describe.node( + add = [describe.entry( + name = ctx.label, + content = _union(ctx, content), + kind = _kind(ctx.label), + merge_affinity = _MERGE_AFFINITY, + )] + add, + merge_from = merge_from, + executable_group = ctx.label, + ) + +py_executable_runfiles_group_describer = describe.make_describer_rule( + describe = _describe_py_executable, +) + +def _describe_py_runtime_pair(target, ctx): + """Describes a Python toolchain: one group for its runtime's files. + + Every binary using the toolchain merges in the same group, so a packager + builds it once. + + Args: + target: {type}`Target` the `py_runtime_pair`. + ctx: {type}`ctx` the aspect ctx. + + Returns: + {type}`struct` from `describe.node`. + """ + runtime = target[platform_common.ToolchainInfo].py3_runtime + files = get_runtime_files(ctx, runtime) if runtime else depset() + return describe.node(add = [describe.entry( + name = _RUNTIME_GROUP, + content = files, + kind = "foundation", + rank = describe.RANK_FOUNDATION, + do_not_merge = True, + merge_affinity = _MERGE_AFFINITY, + )] if files else []) + +py_runtime_pair_runfiles_group_describer = describe.make_describer_rule( + describe = _describe_py_runtime_pair, +) diff --git a/sphinxdocs/.bazelrc b/sphinxdocs/.bazelrc index 6989c2656a..094382a169 100644 --- a/sphinxdocs/.bazelrc +++ b/sphinxdocs/.bazelrc @@ -35,3 +35,6 @@ common:fast-tests --test_tag_filters=-large,-enormous,-integration-test build --config=pyrefly build:pyrefly --aspects=//tests/support/pyrefly:pyrefly.bzl%pyrefly_aspect build:pyrefly --output_groups=+pyrefly + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/sphinxdocs/integration_tests/persistent_worker/workspace/.bazelrc b/sphinxdocs/integration_tests/persistent_worker/workspace/.bazelrc index f3162b9547..d0ac68f7cb 100644 --- a/sphinxdocs/integration_tests/persistent_worker/workspace/.bazelrc +++ b/sphinxdocs/integration_tests/persistent_worker/workspace/.bazelrc @@ -6,3 +6,6 @@ common --http_timeout_scaling=10.0 common --experimental_repository_downloader_retries=10 common --experimental_downloader_config=downloader_config.cfg common --lockfile_mode=off + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/bzlmod_lockfile/.bazelrc b/tests/integration/bzlmod_lockfile/.bazelrc index 3687a11fae..1c624e47cc 100644 --- a/tests/integration/bzlmod_lockfile/.bazelrc +++ b/tests/integration/bzlmod_lockfile/.bazelrc @@ -7,3 +7,6 @@ common --experimental_isolated_extension_usages # https://docs.bazel.build/versions/main/best-practices.html#using-the-bazelrc-file try-import %workspace%/user.bazelrc + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/bzlmod_lockfile/MODULE.bazel.lock b/tests/integration/bzlmod_lockfile/MODULE.bazel.lock index f86bf246f1..100f3e611c 100644 --- a/tests/integration/bzlmod_lockfile/MODULE.bazel.lock +++ b/tests/integration/bzlmod_lockfile/MODULE.bazel.lock @@ -1,6 +1,155 @@ { "lockFileVersion": 26, "registryFileHashes": { + "https://bazel-contrib.github.io/rules_runfiles_group/bazel_registry.json": "ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/abseil-cpp/20210324.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/abseil-cpp/20211102.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/abseil-cpp/20230125.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/abseil-cpp/20230802.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/abseil-cpp/20230802.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/abseil-cpp/20240116.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/abseil-cpp/20240116.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/abseil-cpp/20250127.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/abseil-cpp/20250512.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/abseil-cpp/20250814.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/apple_support/1.11.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/apple_support/1.15.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/apple_support/1.21.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/apple_support/1.21.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/apple_support/1.24.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/apple_support/1.24.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.10.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.11.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.15.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.17.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.18.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.19.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.21.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.23.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.27.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.28.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.3.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.30.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.33.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.4.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.42.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.50.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_features/1.51.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.0.3/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.1.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.2.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.2.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.3.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.4.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.4.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.5.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.6.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.7.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.7.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.8.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/bazel_skylib/1.8.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/buildozer/8.5.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/google_benchmark/1.8.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/googletest/1.11.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/googletest/1.14.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/googletest/1.15.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/googletest/1.17.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/jsoncpp/1.9.5/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/jsoncpp/1.9.6/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/libpfm/4.11.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/nlohmann_json/3.6.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/package_metadata/0.0.3/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/package_metadata/0.0.7/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/platforms/0.0.10/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/platforms/0.0.11/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/platforms/0.0.4/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/platforms/0.0.5/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/platforms/0.0.6/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/platforms/0.0.7/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/platforms/0.0.8/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/platforms/0.0.9/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/platforms/1.0.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/platforms/1.1.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/protobuf/21.7/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/protobuf/27.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/protobuf/29.0-rc2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/protobuf/29.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/protobuf/3.19.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/protobuf/32.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/protobuf/33.4/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/pybind11_bazel/2.11.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/pybind11_bazel/2.12.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/re2/2023-09-01/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/re2/2024-07-02/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_android/0.1.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_apple/3.16.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_apple/4.1.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.0.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.0.10/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.0.13/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.0.15/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.0.16/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.0.17/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.0.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.0.6/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.0.8/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.0.9/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.1.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.1.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.2.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.2.13/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.2.17/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.2.4/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_cc/0.2.8/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_fuzzing/0.5.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_java/4.0.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_java/5.3.5/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_java/6.5.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_java/7.10.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_java/7.12.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_java/7.2.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_java/7.6.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_java/8.6.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_java/9.1.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_jvm_external/4.4.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_jvm_external/5.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_jvm_external/5.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_jvm_external/6.3/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_jvm_external/6.7/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_kotlin/1.9.6/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_license/0.0.3/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_license/0.0.7/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_license/1.0.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_pkg/0.7.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_pkg/1.0.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_proto/4.0.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_proto/6.0.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_proto/7.1.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_runfiles_group/99.0.0-dev.20261019.1.d105dfa/MODULE.bazel": "4b7f46fc4ef79043bdc3792459339d0d5351cb92e645162197a0d309da3fc814", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_runfiles_group/99.0.0-dev.20261019.1.d105dfa/source.json": "d9d04acc3ab7dba753c6219bbc01a9b2e9dddf698e034f0de3841374daf24672", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_rust/0.73.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_shell/0.2.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_shell/0.3.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_shell/0.6.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_swift/1.16.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_swift/2.1.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_swift/2.4.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/rules_swift/3.1.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/stardoc/0.5.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/stardoc/0.5.3/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/stardoc/0.7.0/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/toml.bzl/0.4.1/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/zlib/1.2.11/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "not found", + "https://bazel-contrib.github.io/rules_runfiles_group/modules/zlib/1.3.1/MODULE.bazel": "not found", "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", @@ -37,7 +186,8 @@ "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", "https://bcr.bazel.build/modules/bazel_features/1.50.0/MODULE.bazel": "2083ef9c7a469f520890483ccf8e0189d6e71e2117e7752e15e6554433d5ae3e", - "https://bcr.bazel.build/modules/bazel_features/1.50.0/source.json": "e0ee3debde2789ff56e4452e612d126925ba9ab64d4bde79c67f099d2902df9b", + "https://bcr.bazel.build/modules/bazel_features/1.51.0/MODULE.bazel": "8e1310d09db6ee2e4f19f9994e360aca941ddc32083edc8d9bdc19c83c94cee4", + "https://bcr.bazel.build/modules/bazel_features/1.51.0/source.json": "d5af1f1748d2b4ace5f04087030c0a1cfd93f5be6bdf0b5c1beb24ed0fd24955", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", @@ -289,7 +439,7 @@ }, "@@rules_rust+//crate_universe/private:internal_extensions.bzl%cu_nr": { "general": { - "bzlTransitiveDigest": "C59iXZ0yL27grwDeD63yBucdmChxaRa5m/NvLxalsyQ=", + "bzlTransitiveDigest": "IO6xg76Pp9YTQhgLeeHYRQocQjstmsDHBiTPgtRujCQ=", "usagesDigest": "+1w4EnEfZLFpf8Ak18jhmCWsOTE75Noy2i16whyXYWs=", "recordedInputs": [ "REPO_MAPPING:apple_support+,bazel_skylib bazel_skylib+", diff --git a/tests/integration/compile_pip_requirements/.bazelrc b/tests/integration/compile_pip_requirements/.bazelrc index b85f03bcb6..05ca7bfdd9 100644 --- a/tests/integration/compile_pip_requirements/.bazelrc +++ b/tests/integration/compile_pip_requirements/.bazelrc @@ -3,3 +3,6 @@ test --test_output=errors # Windows requires these for multi-python support: build --enable_runfiles common:bazel7.x --incompatible_python_disallow_native_rules + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/compile_pip_requirements_test_from_external_repo/.bazelrc b/tests/integration/compile_pip_requirements_test_from_external_repo/.bazelrc index ab10c8caf7..fb11f5b00a 100644 --- a/tests/integration/compile_pip_requirements_test_from_external_repo/.bazelrc +++ b/tests/integration/compile_pip_requirements_test_from_external_repo/.bazelrc @@ -1,2 +1,5 @@ test --test_output=errors common:bazel7.x --incompatible_python_disallow_native_rules + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/custom_commands/.bazelrc b/tests/integration/custom_commands/.bazelrc new file mode 100644 index 0000000000..88cad8b7df --- /dev/null +++ b/tests/integration/custom_commands/.bazelrc @@ -0,0 +1,2 @@ +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/local_toolchains/.bazelrc b/tests/integration/local_toolchains/.bazelrc index 0fbb7678d1..ee70dd411d 100644 --- a/tests/integration/local_toolchains/.bazelrc +++ b/tests/integration/local_toolchains/.bazelrc @@ -8,3 +8,6 @@ build --enable_runfiles common:bazel7.x --incompatible_python_disallow_native_rules build --//:py=local common --announce_rc + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/pip_parse/.bazelrc b/tests/integration/pip_parse/.bazelrc index a74909297d..1ca4ceb309 100644 --- a/tests/integration/pip_parse/.bazelrc +++ b/tests/integration/pip_parse/.bazelrc @@ -6,3 +6,6 @@ build --enable_runfiles try-import %workspace%/user.bazelrc common:bazel7.x --incompatible_python_disallow_native_rules + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/pip_parse_isolated/.bazelrc b/tests/integration/pip_parse_isolated/.bazelrc index 227ce5a4cd..0ceaf23001 100644 --- a/tests/integration/pip_parse_isolated/.bazelrc +++ b/tests/integration/pip_parse_isolated/.bazelrc @@ -6,3 +6,6 @@ common --experimental_isolated_extension_usages # https://docs.bazel.build/versions/main/best-practices.html#using-the-bazelrc-file try-import %workspace%/user.bazelrc + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/prebuilt_toolchain/.bazelrc b/tests/integration/prebuilt_toolchain/.bazelrc index e1df951d06..4cb03b81e3 100644 --- a/tests/integration/prebuilt_toolchain/.bazelrc +++ b/tests/integration/prebuilt_toolchain/.bazelrc @@ -1,3 +1,6 @@ common --lockfile_mode=off test --test_output=errors build --enable_runfiles + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/py_cc_toolchain_registered/.bazelrc b/tests/integration/py_cc_toolchain_registered/.bazelrc index fb31561892..7c7caf3b62 100644 --- a/tests/integration/py_cc_toolchain_registered/.bazelrc +++ b/tests/integration/py_cc_toolchain_registered/.bazelrc @@ -1,3 +1,6 @@ # This aids debugging on failure build --toolchain_resolution_debug=python common:bazel7.x --incompatible_python_disallow_native_rules + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/runtime_manifests/.bazelrc b/tests/integration/runtime_manifests/.bazelrc index d4d45a5ea7..8c93874cc3 100644 --- a/tests/integration/runtime_manifests/.bazelrc +++ b/tests/integration/runtime_manifests/.bazelrc @@ -2,3 +2,6 @@ common:fast-tests --build_tests_only=true common:fast-tests --build_tag_filters=-large,-enormous,-integration-test common:fast-tests --test_tag_filters=-large,-enormous,-integration-test + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/toolchain_target_settings/.bazelrc b/tests/integration/toolchain_target_settings/.bazelrc index e1df951d06..4cb03b81e3 100644 --- a/tests/integration/toolchain_target_settings/.bazelrc +++ b/tests/integration/toolchain_target_settings/.bazelrc @@ -1,3 +1,6 @@ common --lockfile_mode=off test --test_output=errors build --enable_runfiles + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/unified_pypi/.bazelrc b/tests/integration/unified_pypi/.bazelrc index b3a24e8605..36e8549e1f 100644 --- a/tests/integration/unified_pypi/.bazelrc +++ b/tests/integration/unified_pypi/.bazelrc @@ -1 +1,4 @@ common --experimental_enable_bzlmod + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/uv_lock/.bazelrc b/tests/integration/uv_lock/.bazelrc index 511f8a2413..ac80834a56 100644 --- a/tests/integration/uv_lock/.bazelrc +++ b/tests/integration/uv_lock/.bazelrc @@ -3,3 +3,6 @@ common --experimental_isolated_extension_usages common --action_env=UV_EXTRA_INDEX_URL try-import %workspace%/user.bazelrc + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/validate_test_main/.bazelrc b/tests/integration/validate_test_main/.bazelrc new file mode 100644 index 0000000000..88cad8b7df --- /dev/null +++ b/tests/integration/validate_test_main/.bazelrc @@ -0,0 +1,2 @@ +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/whl_library/.bazelrc b/tests/integration/whl_library/.bazelrc index eee47a8f19..7591e29f40 100644 --- a/tests/integration/whl_library/.bazelrc +++ b/tests/integration/whl_library/.bazelrc @@ -1,2 +1,5 @@ common --incompatible_default_to_explicit_init_py test --test_output=errors + +# TODO: Delete before merging; see the root .bazelrc. +common --registry=https://bazel-contrib.github.io/rules_runfiles_group --registry=https://bcr.bazel.build diff --git a/tests/integration/whl_library/test_contents.py b/tests/integration/whl_library/test_contents.py index 58f13354e3..587cb48ed1 100644 --- a/tests/integration/whl_library/test_contents.py +++ b/tests/integration/whl_library/test_contents.py @@ -171,6 +171,7 @@ def test_whl_deps_ar_the_same(self): "@rules_python//python/config_settings:precompile_source_retention", "@rules_python//python/config_settings:pyi_deps_to_runfiles", "@rules_python//python/config_settings:venvs_site_packages", + "@rules_python//python/private:py_library_runfiles_group_describer", "@rules_python//python/private:sentinel", }, ) diff --git a/tests/runfiles_groups/BUILD.bazel b/tests/runfiles_groups/BUILD.bazel new file mode 100644 index 0000000000..46e2ecf344 --- /dev/null +++ b/tests/runfiles_groups/BUILD.bazel @@ -0,0 +1,208 @@ +load("@rules_runfiles_group//runfiles_group:runfiles_group_analysis_test.bzl", "runfiles_group_analysis_test") +load("//python:py_binary.bzl", "py_binary") +load("//python:py_library.bzl", "py_library") +load("//python:py_test.bzl", "py_test") +load("//tests/support:py_reconfig.bzl", "py_reconfig_binary") +load(":runfiles_groups_tests.bzl", "WALKS_TOOLCHAINS", "runfiles_groups_test_suite") +load(":test_rules.bzl", "outputs_and_runfiles", "outputs_only") + +outputs_only(name = "outputs_only") + +outputs_and_runfiles(name = "outputs_and_runfiles") + +genrule( + name = "generated", + outs = ["generated.txt"], + cmd = "touch $@", +) + +genrule( + name = "generated_file", + outs = ["generated_file.txt"], + cmd = "touch $@", +) + +filegroup( + name = "files", + srcs = ["fg.txt"], +) + +py_library( + name = "lib_a", + srcs = ["liba.py"], + data = [ + "data.txt", + "generated_file.txt", + ":outputs_only", + ], +) + +filegroup( + name = "srcs_with_runfiles", + srcs = ["libc.py"], + data = ["srcs_data.txt"], +) + +py_library( + name = "lib_b", + srcs = [ + "libb.py", + ":srcs_with_runfiles", + ], + data = [":outputs_and_runfiles"], + deps = [":lib_a"], +) + +# A console script, listed in the wheel's data both on its own and in a +# filegroup, like whl_library does. +genrule( + name = "foo_console_script", + outs = ["scripts/foo"], + cmd = "touch $@", +) + +filegroup( + name = "foo_bin", + srcs = [":foo_console_script"], +) + +# Looks like a library from a wheel: grouped by package name. +py_library( + name = "foo_pkg", + srcs = ["site-packages/foo_pkg.py"], + data = [ + "site-packages/_foo_native.so", + "site-packages/foo_pkg-1.0.dist-info/METADATA", + ":foo_bin", + ":foo_console_script", + ], + experimental_venvs_site_packages = "//python/config_settings:venvs_site_packages", + imports = ["site-packages"], +) + +# Wraps foo_pkg like whl_library's :pkg wraps its :srcs; adds no group. +py_library( + name = "foo_pkg_wrapper", + srcs = [":foo_pkg"], + precompile = "disabled", + deps = [":foo_pkg"], +) + +_DATA = [ + ":files", + ":generated", + ":outputs_and_runfiles", + ":outputs_only", + "bin.txt", +] + +py_binary( + name = "bin", + srcs = ["main.py"], + data = _DATA, + main = "main.py", + deps = [ + ":foo_pkg_wrapper", + ":lib_b", + ], +) + +py_test( + name = "test", + srcs = ["main.py"], + data = _DATA, + main = "main.py", + tags = ["manual"], + deps = [":lib_b"], +) + +py_binary( + name = "bin_in_data", + srcs = ["outer.py"], + data = [":bin"], + main = "outer.py", + deps = [":lib_a"], +) + +# Libraries describe their implicit pyc files. +py_reconfig_binary( + name = "bin_precompiled", + srcs = ["main.py"], + config_settings = { + "//python/config_settings:precompile": "enabled", + }, + main = "main.py", + deps = [":lib_b"], +) + +# The venv's links to foo_pkg's files go into foo_pkg's group. +py_binary( + name = "bin_venv", + srcs = ["main.py"], + config_settings = { + "//python/config_settings:venvs_site_packages": "yes", + } | select({ + "@platforms//os:windows": {}, + "//conditions:default": { + "//python/config_settings:bootstrap_impl": "script", + }, + }), + main = "main.py", + deps = [":foo_pkg_wrapper"], +) + +# Falls back to one group for itself and its deps, because its pyc selection +# differs from what its deps describe. +py_binary( + name = "bin_pyc_collection", + srcs = ["main.py"], + main = "main.py", + pyc_collection = "include_pyc", + deps = [":lib_b"], +) + +# Falls back to one group for itself and its deps, because the generated +# __init__.py files only exist in its own runfiles. +py_binary( + name = "bin_legacy_init", + srcs = ["main.py"], + legacy_create_init = 1, + main = "main.py", + deps = [":lib_b"], +) + +runfiles_group_analysis_test( + name = "runfiles_group_test", + binaries = [ + ":bin", + ":bin_in_data", + ":bin_legacy_init", + ":bin_precompiled", + ":bin_pyc_collection", + ":bin_venv", + ":test", + ], + overlapping_group_behavior = "error", + target_compatible_with = WALKS_TOOLCHAINS, +) + +runfiles_group_analysis_test( + name = "bin_groups_test", + binaries = [":bin"], + expected_executable_group = "@@//tests/runfiles_groups:bin", + expected_group_names = [ + "rules_python#runtime", + "rules_python#pypi/foo_pkg", + "@@//tests/runfiles_groups:files", + "@@//tests/runfiles_groups:generated", + "@@//tests/runfiles_groups:lib_a", + "@@//tests/runfiles_groups:lib_b", + "@@//tests/runfiles_groups:outputs_and_runfiles", + "@@//tests/runfiles_groups:outputs_only", + "@@//tests/runfiles_groups:srcs_with_runfiles", + "@@//tests/runfiles_groups:bin", + ], + target_compatible_with = WALKS_TOOLCHAINS, +) + +runfiles_groups_test_suite(name = "runfiles_groups_tests") diff --git a/tests/runfiles_groups/bin.txt b/tests/runfiles_groups/bin.txt new file mode 100644 index 0000000000..ba077a4031 --- /dev/null +++ b/tests/runfiles_groups/bin.txt @@ -0,0 +1 @@ +bin diff --git a/tests/runfiles_groups/data.txt b/tests/runfiles_groups/data.txt new file mode 100644 index 0000000000..1269488f7f --- /dev/null +++ b/tests/runfiles_groups/data.txt @@ -0,0 +1 @@ +data diff --git a/tests/runfiles_groups/fg.txt b/tests/runfiles_groups/fg.txt new file mode 100644 index 0000000000..0da04c7866 --- /dev/null +++ b/tests/runfiles_groups/fg.txt @@ -0,0 +1 @@ +fg diff --git a/tests/runfiles_groups/liba.py b/tests/runfiles_groups/liba.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/runfiles_groups/libb.py b/tests/runfiles_groups/libb.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/runfiles_groups/libc.py b/tests/runfiles_groups/libc.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/runfiles_groups/main.py b/tests/runfiles_groups/main.py new file mode 100644 index 0000000000..11b15b1a45 --- /dev/null +++ b/tests/runfiles_groups/main.py @@ -0,0 +1 @@ +print("hello") diff --git a/tests/runfiles_groups/ns_alpha/site-packages/ns_alpha-1.0.dist-info/METADATA b/tests/runfiles_groups/ns_alpha/site-packages/ns_alpha-1.0.dist-info/METADATA new file mode 100644 index 0000000000..6c8624ec12 --- /dev/null +++ b/tests/runfiles_groups/ns_alpha/site-packages/ns_alpha-1.0.dist-info/METADATA @@ -0,0 +1,3 @@ +Metadata-Version: 2.1 +Name: ns-alpha +Version: 1.0 diff --git a/tests/runfiles_groups/ns_alpha/site-packages/nspkg/__init__.py b/tests/runfiles_groups/ns_alpha/site-packages/nspkg/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/runfiles_groups/ns_alpha/site-packages/nspkg/alpha.py b/tests/runfiles_groups/ns_alpha/site-packages/nspkg/alpha.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/runfiles_groups/ns_beta/site-packages/ns_beta-1.0.dist-info/METADATA b/tests/runfiles_groups/ns_beta/site-packages/ns_beta-1.0.dist-info/METADATA new file mode 100644 index 0000000000..b101664cb1 --- /dev/null +++ b/tests/runfiles_groups/ns_beta/site-packages/ns_beta-1.0.dist-info/METADATA @@ -0,0 +1,3 @@ +Metadata-Version: 2.1 +Name: ns-beta +Version: 1.0 diff --git a/tests/runfiles_groups/ns_beta/site-packages/nspkg/__init__.py b/tests/runfiles_groups/ns_beta/site-packages/nspkg/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/runfiles_groups/ns_beta/site-packages/nspkg/beta.py b/tests/runfiles_groups/ns_beta/site-packages/nspkg/beta.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/runfiles_groups/outer.py b/tests/runfiles_groups/outer.py new file mode 100644 index 0000000000..11b15b1a45 --- /dev/null +++ b/tests/runfiles_groups/outer.py @@ -0,0 +1 @@ +print("hello") diff --git a/tests/runfiles_groups/runfiles_groups_tests.bzl b/tests/runfiles_groups/runfiles_groups_tests.bzl new file mode 100644 index 0000000000..bade20f360 --- /dev/null +++ b/tests/runfiles_groups/runfiles_groups_tests.bzl @@ -0,0 +1,113 @@ +"""Tests for what ends up in which runfiles group.""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test") +load("@rules_testing//lib:test_suite.bzl", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", rt_util = "util") +load("//python:py_binary.bzl", "py_binary") +load("//python:py_library.bzl", "py_library") +load("//python/private:common_labels.bzl", "labels") # buildifier: disable=bzl-visibility +load("//tests/support:support.bzl", "NOT_WINDOWS") +load(":test_rules.bzl", "ResolvedGroupsInfo", "bucket_ml", "resolved_groups") + +# Packagers walk the Python toolchain through toolchains_aspects, which takes +# "*" since Bazel 8.8.0 and a propagation filter since Bazel 9.0.0. +WALKS_TOOLCHAINS = [] if ( + bazel_features.rules.aspect_propagation_context or + bazel_features.rules.supports_toolchains_aspects_star +) else ["@platforms//:incompatible"] + +_tests = [] + +def _namespace_distributions(name): + """Defines two distributions that both ship nspkg/__init__.py. + + Like pkgutil-style namespace packages, so the venv can't link nspkg as + one directory. + """ + for distribution, module in [("ns_alpha", "alpha"), ("ns_beta", "beta")]: + rt_util.helper_target( + py_library, + name = name + "_" + distribution, + srcs = [ + "{}/site-packages/nspkg/__init__.py".format(distribution), + "{}/site-packages/nspkg/{}.py".format(distribution, module), + ], + data = ["{0}/site-packages/{0}-1.0.dist-info/METADATA".format(distribution)], + experimental_venvs_site_packages = "//python/config_settings:venvs_site_packages", + imports = ["{}/site-packages".format(distribution)], + ) + return [name + "_ns_alpha", name + "_ns_beta"] + +def _test_namespace_package_venv_links(name): + rt_util.helper_target( + py_binary, + name = name + "_bin", + srcs = ["main.py"], + main = "main.py", + deps = _namespace_distributions(name), + ) + rt_util.helper_target( + resolved_groups, + name = name + "_subject", + binary = name + "_bin", + ) + analysis_test( + name = name, + impl = _test_namespace_package_venv_links_impl, + target = name + "_subject", + config_settings = { + labels.BOOTSTRAP_IMPL: "script", + labels.VENVS_SITE_PACKAGES: "yes", + }, + attr_values = {"target_compatible_with": WALKS_TOOLCHAINS + NOT_WINDOWS}, + ) + +def _test_namespace_package_venv_links_impl(env, target): + groups = target[ResolvedGroupsInfo].groups + + for distribution, module in [("ns_alpha", "alpha"), ("ns_beta", "beta")]: + env.expect.that_collection( + groups["rules_python#pypi/" + distribution].root_symlinks, + ).contains_predicate(matching.str_endswith("site-packages/nspkg/{}.py".format(module))) + + binary = groups[str(target.label).removesuffix("_subject") + "_bin"] + env.expect.that_collection(binary.root_symlinks).not_contains_predicate( + matching.str_matches("*site-packages/nspkg/*"), + ) + +_tests.append(_test_namespace_package_venv_links) + +def _test_configure_with_aspect_hints(name): + rt_util.helper_target(bucket_ml, name = name + "_bucket_ml") + rt_util.helper_target( + py_binary, + name = name + "_bin", + srcs = ["main.py"], + main = "main.py", + deps = _namespace_distributions(name) + [":foo_pkg"], + aspect_hints = [name + "_bucket_ml"], + ) + rt_util.helper_target( + resolved_groups, + name = name + "_subject", + binary = name + "_bin", + ) + analysis_test( + name = name, + impl = _test_configure_with_aspect_hints_impl, + target = name + "_subject", + attr_values = {"target_compatible_with": WALKS_TOOLCHAINS}, + ) + +def _test_configure_with_aspect_hints_impl(env, target): + groups = target[ResolvedGroupsInfo].groups + for distribution in ["ns_alpha", "ns_beta"]: + env.expect.that_str(groups["rules_python#pypi/" + distribution].merge_affinity).equals("ml") + env.expect.that_str(groups["rules_python#pypi/foo_pkg"].merge_affinity).equals("rules_python") + +_tests.append(_test_configure_with_aspect_hints) + +def runfiles_groups_test_suite(name): + test_suite(name = name, tests = _tests) diff --git a/tests/runfiles_groups/site-packages/_foo_native.so b/tests/runfiles_groups/site-packages/_foo_native.so new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/runfiles_groups/site-packages/foo_pkg-1.0.dist-info/METADATA b/tests/runfiles_groups/site-packages/foo_pkg-1.0.dist-info/METADATA new file mode 100644 index 0000000000..a7a47b1539 --- /dev/null +++ b/tests/runfiles_groups/site-packages/foo_pkg-1.0.dist-info/METADATA @@ -0,0 +1,3 @@ +Metadata-Version: 2.1 +Name: foo-pkg +Version: 1.0 diff --git a/tests/runfiles_groups/site-packages/foo_pkg.py b/tests/runfiles_groups/site-packages/foo_pkg.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/runfiles_groups/srcs_data.txt b/tests/runfiles_groups/srcs_data.txt new file mode 100644 index 0000000000..587be6b4c3 --- /dev/null +++ b/tests/runfiles_groups/srcs_data.txt @@ -0,0 +1 @@ +x diff --git a/tests/runfiles_groups/test_rules.bzl b/tests/runfiles_groups/test_rules.bzl new file mode 100644 index 0000000000..4f8078315b --- /dev/null +++ b/tests/runfiles_groups/test_rules.bzl @@ -0,0 +1,93 @@ +"""Rules for testing the runfiles group describers.""" + +load("@rules_runfiles_group//runfiles_group:identity_packager.bzl", "RunfilesGroupIdentityInfo", "runfiles_groups_identity_aspect") +load("@rules_runfiles_group//runfiles_group:lib.bzl", "runfiles_groups") +load("@rules_runfiles_group//runfiles_group:providers.bzl", "RunfilesGroupTransformInfo") + +def _outputs_only_impl(ctx): + files = [ctx.actions.declare_file(ctx.label.name + suffix) for suffix in (".a", ".b")] + for f in files: + ctx.actions.write(f, "") + return [DefaultInfo(files = depset(files))] + +# Default outputs, no runfiles. +outputs_only = rule(implementation = _outputs_only_impl) + +def _outputs_and_runfiles_impl(ctx): + output = ctx.actions.declare_file(ctx.label.name + ".out") + runfile = ctx.actions.declare_file(ctx.label.name + ".runfile") + ctx.actions.write(output, "") + ctx.actions.write(runfile, "") + return [DefaultInfo(files = depset([output]), runfiles = ctx.runfiles([runfile]))] + +# Default outputs and runfiles that differ. +outputs_and_runfiles = rule(implementation = _outputs_and_runfiles_impl) + +ResolvedGroupsInfo = provider( + doc = "A binary's runfiles groups, as a packager would resolve them.", + fields = { + "groups": """ +:type: dict[str, struct] + +By group name: `merge_affinity`, `do_not_merge`, and `root_symlinks`, the paths +of the group's root symlinks. +""", + }, +) + +_AspectHintsInfo = provider( + doc = "A binary's aspect_hints.", + fields = {"aspect_hints": "list[Target]"}, +) + +def _aspect_hints_aspect_impl(_target, ctx): + return [_AspectHintsInfo(aspect_hints = ctx.rule.attr.aspect_hints)] + +_aspect_hints_aspect = aspect(implementation = _aspect_hints_aspect_impl) + +def _resolved_groups_impl(ctx): + binary = ctx.attr.binary + resolved = runfiles_groups.finalize( + ctx, + binary[RunfilesGroupIdentityInfo], + runfiles_groups.IDENTITY_OPS, + aspect_hints = binary[_AspectHintsInfo].aspect_hints, + ) + groups = {} + for group in resolved.groups: + runfiles = runfiles_groups.runfiles(ctx, group.handle) + groups[runfiles_groups.name_str(group.name)] = struct( + merge_affinity = group.merge_affinity, + do_not_merge = group.do_not_merge, + root_symlinks = [link.path for link in runfiles.root_symlinks.to_list()], + ) + return [ResolvedGroupsInfo(groups = groups)] + +# Resolves a binary's groups with the identity packager, applying the +# transforms in the binary's aspect_hints like a real packager does. +resolved_groups = rule( + implementation = _resolved_groups_impl, + attrs = { + "binary": attr.label(aspects = [_aspect_hints_aspect, runfiles_groups_identity_aspect]), + }, +) + +# Stand-ins for heavy distributions like torch and nvidia-*. +_ML_DISTRIBUTIONS = ["rules_python#pypi/ns_alpha", "rules_python#pypi/ns_beta"] + +def _bucket_ml(resolved): + return runfiles_groups.resolved( + [ + runfiles_groups.derive(group, merge_affinity = "ml") if runfiles_groups.name_str(group.name) in _ML_DISTRIBUTIONS else group + for group in resolved.groups + ], + executable_group = resolved.executable_group, + ) + +def _bucket_ml_impl(_ctx): + return [RunfilesGroupTransformInfo(transform = _bucket_ml)] + +# An example of configuring how a binary's groups are packaged: in a binary's +# aspect_hints, it gives the ML distributions their own merge affinity, so a +# packager that has to merge groups merges them with each other, not the rest. +bucket_ml = rule(implementation = _bucket_ml_impl)