refactor(pypi): split whl_library_targets (#4101)
Summary:
- Split the macros into 2 separate files for easier management.
- Split the tests for each macro as well.
Work towards #2948
---------
Co-authored-by: Richard Levasseur <richardlev@gmail.com>
diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel
index 2d774e5..d533582 100644
--- a/python/private/pypi/BUILD.bazel
+++ b/python/private/pypi/BUILD.bazel
@@ -500,17 +500,8 @@
name = "whl_library_targets",
srcs = ["whl_library_targets.bzl"],
deps = [
- ":env_marker_setting",
- ":gen_wheel_record",
- ":labels",
- ":namespace_pkgs",
- ":pep508_deps",
- ":venv_entry_point",
- ":venv_rewrite_shebang",
- "//python:py_binary",
- "//python:py_library",
- "//python/private:normalize_name",
- "@bazel_skylib//rules:copy_file",
+ ":whl_library_deps_targets",
+ ":whl_library_srcs",
],
)
@@ -591,6 +582,32 @@
)
bzl_library(
+ name = "whl_library_deps_targets",
+ srcs = ["whl_library_deps_targets.bzl"],
+ deps = [
+ ":env_marker_setting",
+ ":labels",
+ ":pep508_deps",
+ "//python:py_library",
+ "//python/private:normalize_name",
+ ],
+)
+
+bzl_library(
+ name = "whl_library_srcs",
+ srcs = ["whl_library_srcs.bzl"],
+ deps = [
+ ":gen_wheel_record",
+ ":labels",
+ ":namespace_pkgs",
+ ":venv_entry_point",
+ ":venv_rewrite_shebang",
+ "//python:py_library",
+ "@bazel_skylib//rules:copy_file",
+ ],
+)
+
+bzl_library(
name = "argparse",
srcs = ["argparse.bzl"],
)
diff --git a/python/private/pypi/generate_whl_library_build_bazel.bzl b/python/private/pypi/generate_whl_library_build_bazel.bzl
index ceee319..bec45bb 100644
--- a/python/private/pypi/generate_whl_library_build_bazel.bzl
+++ b/python/private/pypi/generate_whl_library_build_bazel.bzl
@@ -101,7 +101,7 @@
kwargs["requires_dist"] = requires_dist
loads.extend([
- """load("@rules_python//python/private/pypi:whl_library_targets.bzl", "{}")""".format(fn),
+ """load("@rules_python//python/private/pypi:{}.bzl", "{}")""".format(fn, fn),
])
additional_content = []
diff --git a/python/private/pypi/whl_library_deps_targets.bzl b/python/private/pypi/whl_library_deps_targets.bzl
new file mode 100644
index 0000000..89e9b74
--- /dev/null
+++ b/python/private/pypi/whl_library_deps_targets.bzl
@@ -0,0 +1,215 @@
+"""Macro to generate all of the targets present in a {obj}`whl_library`."""
+
+load("//python:py_library.bzl", "py_library")
+load("//python/private:normalize_name.bzl", "normalize_name")
+load(":env_marker_setting.bzl", "env_marker_setting")
+load(
+ ":labels.bzl",
+ "DATA_LABEL",
+ "DIST_INFO_LABEL",
+ "EXTRACTED_WHEEL_FILES",
+ "PY_LIBRARY_IMPL_LABEL",
+ "PY_LIBRARY_PUBLIC_LABEL",
+ "PY_SRCS_LABEL",
+ "WHEEL_FILE",
+ "WHEEL_FILE_IMPL_LABEL",
+ "WHEEL_FILE_PUBLIC_LABEL",
+)
+load(":pep508_deps.bzl", "deps")
+
+def whl_library_deps_targets(
+ *,
+ name = None,
+ repo,
+ aliases = None,
+ metadata_name,
+ requires_dist,
+ extras,
+ include = [],
+ group_deps = [],
+ group_name = None,
+ dep_template,
+ tags = [],
+ visibility = ["//visibility:public"],
+ native = native,
+ rules = struct(
+ py_library = py_library,
+ env_marker_setting = env_marker_setting,
+ )):
+ """Create all of the whl_library targets.
+
+ Args:
+ name: {type}`str` The wheel filename
+ metadata_name: {type}`str` The package name as written in wheel `METADATA`.
+ group_deps: {type}`list[str]` names of fellow members of the group (if
+ any). These will be excluded from generated deps lists so as to avoid
+ direct cycles. These dependencies will be provided at runtime by the
+ group rules which wrap this library and its fellows together.
+ requires_dist: {type}`list[str]` The list of `Requires-Dist` values from
+ the whl `METADATA`.
+ extras: {type}`list[str]` The list of requested extras. This essentially includes extra transitive dependencies in the final targets depending on the wheel `METADATA`.
+ include: {type}`list[str]` The list of packages to include.
+ group_name: {type}`str | None` name of the dependency group (if any).
+ dep_template: {type}`str | None` The dep_template to use.
+ tags: {type}`list[str]` The tags set on the targets.
+ repo: {type}`str | Label | None` The BUILD.bazel label to the parent repo that has the
+ sources. If none, then will take the targets from the current dir.
+ aliases: {type}`dict[str, str] | None` The list of aliases to create in the parent repo. If None, will create
+ the default values. Empty list means no aliases.
+ visibility: {type}`list[str]` The visibility of the targets.
+ native: {type}`native` The native struct for overriding in tests.
+ rules: {type}`struct` A struct with references to rules for creating targets.
+ """
+ repo_label = Label(repo).same_package_label if repo else (lambda x: x)
+ if aliases == None:
+ aliases = {
+ EXTRACTED_WHEEL_FILES: repo_label(EXTRACTED_WHEEL_FILES),
+ DIST_INFO_LABEL: repo_label(DIST_INFO_LABEL),
+ DATA_LABEL: repo_label(DATA_LABEL),
+ }
+
+ # If this library is a member of a group, its public label aliases need to
+ # point to the group implementation rule not the implementation rules. We
+ # also need to mark the implementation rules as visible to the group
+ # implementation.
+ if group_name and "//:" in dep_template:
+ # This is the legacy behaviour where the group library is outside the hub repo
+ #
+ # It is expected to disappear when we drop WORKSPACE or drop the vendoring of
+ # pip_parse `requirements.bzl` in WORKSPACE. The alternative would be to add
+ # another argument to the macro, but it is already full of arguments.
+ label_tmpl = dep_template.format(
+ name = "_config",
+ target = normalize_name(group_name) + "_{}",
+ ).replace(
+ "//:",
+ "//_groups:",
+ )
+ aliases = aliases | {
+ PY_LIBRARY_PUBLIC_LABEL: label_tmpl.format(PY_LIBRARY_PUBLIC_LABEL),
+ WHEEL_FILE_PUBLIC_LABEL: label_tmpl.format(WHEEL_FILE_PUBLIC_LABEL),
+ }
+ impl_vis = [dep_template.format(
+ name = "_config",
+ target = "__pkg__",
+ ).replace(
+ "//:",
+ "//_groups:",
+ )]
+
+ py_library_label = PY_LIBRARY_IMPL_LABEL
+ whl_file_label = WHEEL_FILE_IMPL_LABEL
+ else:
+ py_library_label = PY_LIBRARY_PUBLIC_LABEL
+ whl_file_label = WHEEL_FILE_PUBLIC_LABEL
+ if group_name:
+ impl_vis = [dep_template.format(name = "", target = "__subpackages__")]
+ else:
+ impl_vis = visibility
+
+ if not requires_dist:
+ # If the package is in a group but has no deps, we still need the public labels to
+ # point at the srcs targets so that the group implementation can use them. We don't
+ # need any of the extra targets, so just create the aliases.
+ aliases = aliases | {
+ py_library_label: repo_label(PY_SRCS_LABEL),
+ whl_file_label: repo_label(WHEEL_FILE),
+ }
+
+ for alias, actual in aliases.items():
+ native.alias(
+ name = alias,
+ actual = actual,
+ visibility = visibility,
+ )
+
+ if not requires_dist:
+ # If there are extras, then they will be visible in requires_dist.
+ return
+
+ package_deps = _parse_requires_dist(
+ name = metadata_name,
+ requires_dist = requires_dist,
+ excludes = group_deps,
+ extras = extras,
+ include = include,
+ )
+
+ _config_settings(
+ dependencies_with_markers = package_deps.deps_select,
+ rules = rules,
+ visibility = ["//visibility:private"],
+ )
+
+ if hasattr(native, "filegroup"):
+ # We include the whl file as srcs so that `$(location :whl)` expands to the whl file.
+ # The transitive dependencies are available via the `data` attribute.
+ native.filegroup(
+ name = whl_file_label,
+ srcs = [repo_label(WHEEL_FILE)],
+ data = _deps(
+ deps = [],
+ package_deps = package_deps,
+ tmpl = dep_template.format(name = "{}", target = WHEEL_FILE_PUBLIC_LABEL),
+ ),
+ visibility = impl_vis,
+ )
+
+ if hasattr(rules, "py_library"):
+ rules.py_library(
+ name = py_library_label,
+ # We include as srcs to ensure that the (locations :pkg) works as expected.
+ srcs = [repo_label(PY_SRCS_LABEL)],
+ deps = _deps(
+ # We include as deps, so that `PyInfo` and friends (e.g. `pyi_srcs`) get
+ # propagated. Just passing the target as `srcs` is not enough to propagate
+ # `pyi_srcs`, see `tests/base_rules/py_library`.
+ deps = [repo_label(PY_SRCS_LABEL)],
+ package_deps = package_deps,
+ tmpl = dep_template.format(name = "{}", target = PY_LIBRARY_PUBLIC_LABEL),
+ ),
+ tags = tags,
+ visibility = impl_vis,
+ )
+
+def _parse_requires_dist(
+ *,
+ name,
+ requires_dist,
+ excludes,
+ include,
+ extras):
+ return deps(
+ name = normalize_name(name),
+ requires_dist = requires_dist,
+ excludes = excludes,
+ include = include,
+ extras = extras,
+ )
+
+def _config_settings(dependencies_with_markers, rules, **kwargs):
+ """Generate config settings for the targets.
+
+ Args:
+ dependencies_with_markers: {type}`dict[str, str]` The markers to evaluate by
+ each dep.
+ rules: used for testing
+ **kwargs: Extra kwargs to pass to the rule.
+ """
+ for dep, expression in dependencies_with_markers.items():
+ rules.env_marker_setting(
+ name = "include_{}".format(dep),
+ expression = expression,
+ **kwargs
+ )
+
+def _deps(deps, package_deps, tmpl):
+ deps = [] + deps + [tmpl.format(d) for d in sorted(package_deps.deps)]
+
+ for dep in package_deps.deps_select:
+ deps = deps + select({
+ ":is_include_{}_true".format(dep): [tmpl.format(dep)],
+ "//conditions:default": [],
+ })
+
+ return deps
diff --git a/python/private/pypi/whl_library_srcs.bzl b/python/private/pypi/whl_library_srcs.bzl
new file mode 100644
index 0000000..80a0d8a
--- /dev/null
+++ b/python/private/pypi/whl_library_srcs.bzl
@@ -0,0 +1,244 @@
+"""Macro to generate all of the targets present in a {obj}`whl_library`."""
+
+load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
+load("//python:py_library.bzl", "py_library")
+load(":gen_wheel_record.bzl", "gen_wheel_record")
+load(
+ ":labels.bzl",
+ "DATA_LABEL",
+ "DIST_INFO_LABEL",
+ "EXTRACTED_WHEEL_FILES",
+ "PY_SRCS_LABEL",
+ "WHEEL_FILE",
+)
+load(":namespace_pkgs.bzl", _create_inits = "create_inits")
+load(":venv_entry_point.bzl", "venv_entry_point")
+load(":venv_rewrite_shebang.bzl", "venv_rewrite_shebang")
+
+# Files that are special to the Bazel processing of things.
+_BAZEL_REPO_FILE_GLOBS = [
+ "BUILD",
+ "BUILD.bazel",
+ "REPO.bazel",
+ "WORKSPACE",
+ "WORKSPACE.bzlmod",
+ "WORKSPACE.bazel",
+]
+
+_IS_VENV_SITE_PACKAGES_YES = Label("//python/config_settings:_is_venvs_site_packages_yes")
+_VENV_SITE_PACKAGES_FLAG = Label("//python/config_settings:venvs_site_packages")
+
+def whl_library_srcs(
+ *,
+ name,
+ sdist_filename = None,
+ data_exclude = [],
+ srcs_exclude = [],
+ tags = [],
+ filegroups = None,
+ entry_points = {},
+ data = [],
+ copy_files = {},
+ copy_executables = {},
+ native = native,
+ enable_implicit_namespace_pkgs = False,
+ namespace_package_files = [],
+ visibility = ["//visibility:public"],
+ rules = struct(
+ copy_file = copy_file,
+ py_library = py_library,
+ venv_entry_point = venv_entry_point,
+ venv_rewrite_shebang = venv_rewrite_shebang,
+ gen_wheel_record = gen_wheel_record,
+ create_inits = _create_inits,
+ )):
+ """Create all of the whl_library targets.
+
+ Args:
+ name: {type}`str` The file to match for including it into the `whl`
+ filegroup. This may be also parsed to generate extra metadata.
+ sdist_filename: {type}`str | None` If the wheel was built from an sdist,
+ the filename of the sdist.
+ visibility: {type}`list[str]` The visibility of the source targets.
+ tags: {type}`list[str]` The tags set on the `py_library`.
+ entry_points: {type}`list[dict]` A list of parsed entry point definitions.
+ filegroups: {type}`dict[str, list[str]] | None` A dictionary of the target
+ names and the glob matches. If `None`, defaults will be used.
+ copy_executables: {type}`dict[str, str]` The mapping between src and
+ dest locations for the targets.
+ copy_files: {type}`dict[str, str]` The mapping between src and
+ dest locations for the targets.
+ data_exclude: {type}`list[str]` The globs for data attribute exclusion
+ in `py_library`.
+ srcs_exclude: {type}`list[str]` The globs for srcs attribute exclusion
+ in `py_library`.
+ data: {type}`list[str]` A list of labels to include as part of the `data` attribute in `py_library`.
+ enable_implicit_namespace_pkgs: {type}`boolean` generate __init__.py
+ files for namespace pkgs.
+ namespace_package_files: {type}`list[str]` A list of labels of files whose
+ directories are namespace packages.
+ native: {type}`native` The native struct for overriding in tests.
+ rules: {type}`struct` A struct with references to rules for creating targets.
+ """
+ tags = sorted(tags)
+ data = [] + data
+
+ bins_for_data_label = []
+
+ for ep_dict in entry_points.values():
+ kwargs = dict(ep_dict)
+ ep_name = kwargs.pop("name")
+ ep_target_name = "bin/{}".format(ep_name)
+ rules.venv_entry_point(
+ name = ep_target_name,
+ **kwargs
+ )
+ bins_for_data_label.append(ep_target_name)
+ data.append(ep_target_name)
+
+ existing_bin_names = {ep["name"].lower(): None for ep in entry_points.values()}
+ for p in native.glob(["bin/*"], allow_empty = True):
+ existing_bin_names[p[len("bin/"):].lower()] = None
+
+ for src_path in native.glob(["rewrite-bin/*"], allow_empty = True):
+ script_name = src_path[len("rewrite-bin/"):]
+ if script_name.lower() in existing_bin_names:
+ continue
+ rewrite_target_name = "bin/{}".format(script_name)
+ rules.venv_rewrite_shebang(
+ name = rewrite_target_name,
+ src = src_path,
+ package = name,
+ )
+ bins_for_data_label.append(rewrite_target_name)
+ data.append(rewrite_target_name)
+
+ record_srcs = native.glob(["rewrite-record/*/RECORD"], allow_empty = True)
+ record_target_name = "record"
+ if record_srcs:
+ rules.gen_wheel_record(
+ name = record_target_name,
+ srcs = record_srcs,
+ tags = ["manual"],
+ )
+ data.append(record_target_name)
+
+ if filegroups == None:
+ filegroups = {
+ EXTRACTED_WHEEL_FILES: dict(
+ include = ["**"],
+ # The Bazel repo files are always excluded; only the sdist
+ # filename is conditional on `sdist_filename`.
+ exclude = _BAZEL_REPO_FILE_GLOBS + (
+ [sdist_filename] if sdist_filename else []
+ ),
+ ),
+ DIST_INFO_LABEL: dict(
+ include = ["site-packages/*.dist-info/**"],
+ ),
+ DATA_LABEL: dict(
+ include = ["data/**", "bin/**", "include/**"],
+ ),
+ }
+
+ for filegroup_name, glob_kwargs in filegroups.items():
+ glob_kwargs = {"allow_empty": True} | glob_kwargs
+ srcs = native.glob(**glob_kwargs)
+ if filegroup_name == DATA_LABEL:
+ srcs = srcs + bins_for_data_label
+ if filegroup_name == DIST_INFO_LABEL and record_srcs:
+ srcs = srcs + [record_target_name]
+ native.filegroup(
+ name = filegroup_name,
+ srcs = srcs,
+ visibility = visibility,
+ )
+
+ for src, dest in copy_files.items():
+ rules.copy_file(
+ name = dest + ".copy",
+ src = src,
+ out = dest,
+ visibility = visibility,
+ )
+ data.append(dest)
+ for src, dest in copy_executables.items():
+ rules.copy_file(
+ name = dest + ".copy",
+ src = src,
+ out = dest,
+ is_executable = True,
+ visibility = visibility,
+ )
+ data.append(dest)
+
+ if hasattr(native, "filegroup"):
+ native.filegroup(
+ name = WHEEL_FILE,
+ srcs = [name],
+ visibility = visibility,
+ )
+
+ if hasattr(rules, "py_library"):
+ srcs = native.glob(
+ ["site-packages/**/*.py"],
+ exclude = srcs_exclude,
+ # Empty sources are allowed to support wheels that don't have any
+ # pure-Python code, e.g. pymssql, which is written in Cython.
+ allow_empty = True,
+ )
+
+ # NOTE: pyi files should probably be excluded because they're carried
+ # by the pyi_srcs attribute. However, historical behavior included
+ # them in data and some tools currently rely on that.
+ _data_exclude = [
+ "**/*.py",
+ "**/*.pyc",
+ "**/*.pyc.*", # During pyc creation, temp files named *.pyc.NNNN are created
+ ]
+ if sdist_filename:
+ _data_exclude.append("**/*.dist-info/RECORD")
+ for item in data_exclude:
+ if item not in _data_exclude:
+ _data_exclude.append(item)
+
+ data = data + native.glob(
+ ["site-packages/**/*"],
+ exclude = _data_exclude,
+ allow_empty = True,
+ )
+
+ pyi_srcs = native.glob(
+ ["site-packages/**/*.pyi"],
+ allow_empty = True,
+ )
+
+ if not enable_implicit_namespace_pkgs:
+ generated_namespace_package_files = select({
+ _IS_VENV_SITE_PACKAGES_YES: [],
+ "//conditions:default": rules.create_inits(
+ srcs = srcs + data + pyi_srcs,
+ ignored_dirnames = [], # If you need to ignore certain folders, you can patch rules_python here to do so.
+ root = "site-packages",
+ ),
+ })
+ namespace_package_files += generated_namespace_package_files
+ srcs = srcs + generated_namespace_package_files
+
+ # This is done after create_inits() is called so that the data scheme
+ # files don't have such files created in their directories.
+ data = data + [DATA_LABEL]
+
+ rules.py_library(
+ name = PY_SRCS_LABEL,
+ srcs = srcs,
+ pyi_srcs = pyi_srcs,
+ data = data,
+ # This makes this directory a top-level in the python import
+ # search path for anything that depends on this.
+ imports = ["site-packages"],
+ tags = tags,
+ visibility = visibility,
+ experimental_venvs_site_packages = _VENV_SITE_PACKAGES_FLAG,
+ namespace_package_files = namespace_package_files,
+ )
diff --git a/python/private/pypi/whl_library_targets.bzl b/python/private/pypi/whl_library_targets.bzl
index 18a5e8b..b8d51d0 100644
--- a/python/private/pypi/whl_library_targets.bzl
+++ b/python/private/pypi/whl_library_targets.bzl
@@ -14,40 +14,11 @@
"""Macro to generate all of the targets present in a {obj}`whl_library`."""
-load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
-load("//python:py_library.bzl", "py_library")
-load("//python/private:normalize_name.bzl", "normalize_name")
-load(":env_marker_setting.bzl", "env_marker_setting")
-load(":gen_wheel_record.bzl", "gen_wheel_record")
-load(
- ":labels.bzl",
- "DATA_LABEL",
- "DIST_INFO_LABEL",
- "EXTRACTED_WHEEL_FILES",
- "PY_LIBRARY_IMPL_LABEL",
- "PY_LIBRARY_PUBLIC_LABEL",
- "PY_SRCS_LABEL",
- "WHEEL_FILE",
- "WHEEL_FILE_IMPL_LABEL",
- "WHEEL_FILE_PUBLIC_LABEL",
-)
-load(":namespace_pkgs.bzl", _create_inits = "create_inits")
-load(":pep508_deps.bzl", "deps")
-load(":venv_entry_point.bzl", "venv_entry_point")
-load(":venv_rewrite_shebang.bzl", "venv_rewrite_shebang")
+load(":whl_library_deps_targets.bzl", _whl_library_deps_targets = "whl_library_deps_targets")
+load(":whl_library_srcs.bzl", _whl_library_srcs = "whl_library_srcs")
-# Files that are special to the Bazel processing of things.
-_BAZEL_REPO_FILE_GLOBS = [
- "BUILD",
- "BUILD.bazel",
- "REPO.bazel",
- "WORKSPACE",
- "WORKSPACE.bzlmod",
- "WORKSPACE.bazel",
-]
-
-_IS_VENV_SITE_PACKAGES_YES = Label("//python/config_settings:_is_venvs_site_packages_yes")
-_VENV_SITE_PACKAGES_FLAG = Label("//python/config_settings:venvs_site_packages")
+whl_library_deps_targets = _whl_library_deps_targets
+whl_library_srcs = _whl_library_srcs
def whl_library_targets(
*,
@@ -99,7 +70,6 @@
visibility: {type}`list[str]` The visibility of the targets.
**kwargs: Extra args passed to the {obj}`whl_library_deps_targets` and {obj}`whl_library_srcs`.
"""
- create_extra_targets = bool(requires_dist or group_name) and dep_template
whl_library_srcs(
name = name,
sdist_filename = sdist_filename,
@@ -113,440 +83,19 @@
copy_executables = copy_executables,
enable_implicit_namespace_pkgs = enable_implicit_namespace_pkgs,
namespace_package_files = namespace_package_files,
- # If there are no dependencies, then let's create the targets with public labels.
- # Note, we are not supporting grouping the packages in this case, but that is fine.
- whl_name = WHEEL_FILE if create_extra_targets else WHEEL_FILE_PUBLIC_LABEL,
- pkg_name = PY_SRCS_LABEL if create_extra_targets else PY_LIBRARY_PUBLIC_LABEL,
**kwargs
)
- if create_extra_targets:
- whl_library_deps_targets(
- name = name,
- metadata_name = metadata_name,
- requires_dist = requires_dist,
- group_deps = group_deps, # only needed if requires_dist is present
- extras = extras, # only needed if requires_dist is present
- include = include, # only needed if requires_dist is present
- group_name = group_name, # only needed if requires_dist is present
- dep_template = dep_template, # only needed if requires_dist is present
- repo = None, # set aliases in the same repo
- aliases = {},
- **kwargs
- )
-
-def whl_library_srcs(
- *,
- name,
- sdist_filename = None,
- data_exclude = [],
- srcs_exclude = [],
- tags = [],
- filegroups = None,
- entry_points = {},
- data = [],
- copy_files = {},
- copy_executables = {},
- native = native,
- enable_implicit_namespace_pkgs = False,
- namespace_package_files = [],
- whl_name = WHEEL_FILE,
- pkg_name = PY_SRCS_LABEL,
- visibility = ["//visibility:public"],
- rules = struct(
- copy_file = copy_file,
- py_library = py_library,
- venv_entry_point = venv_entry_point,
- venv_rewrite_shebang = venv_rewrite_shebang,
- gen_wheel_record = gen_wheel_record,
- create_inits = _create_inits,
- )):
- """Create all of the whl_library targets.
-
- Args:
- name: {type}`str` The file to match for including it into the `whl`
- filegroup. This may be also parsed to generate extra metadata.
- sdist_filename: {type}`str | None` If the wheel was built from an sdist,
- the filename of the sdist.
- visibility: {type}`list[str]` The visibility of the source targets.
- tags: {type}`list[str]` The tags set on the `py_library`.
- entry_points: {type}`list[dict]` A list of parsed entry point definitions.
- filegroups: {type}`dict[str, list[str]] | None` A dictionary of the target
- names and the glob matches. If `None`, defaults will be used.
- copy_executables: {type}`dict[str, str]` The mapping between src and
- dest locations for the targets.
- copy_files: {type}`dict[str, str]` The mapping between src and
- dest locations for the targets.
- data_exclude: {type}`list[str]` The globs for data attribute exclusion
- in `py_library`.
- srcs_exclude: {type}`list[str]` The globs for srcs attribute exclusion
- in `py_library`.
- data: {type}`list[str]` A list of labels to include as part of the `data` attribute in `py_library`.
- enable_implicit_namespace_pkgs: {type}`boolean` generate __init__.py
- files for namespace pkgs.
- namespace_package_files: {type}`list[str]` A list of labels of files whose
- directories are namespace packages.
- whl_name: {type}`str` The label name to use for the wheel filegroup target.
- pkg_name: {type}`str` The label name to use for the py_library target.
- native: {type}`native` The native struct for overriding in tests.
- rules: {type}`struct` A struct with references to rules for creating targets.
- """
- tags = sorted(tags)
- data = [] + data
-
- bins_for_data_label = []
-
- for ep_dict in entry_points.values():
- kwargs = dict(ep_dict)
- ep_name = kwargs.pop("name")
- ep_target_name = "bin/{}".format(ep_name)
- rules.venv_entry_point(
- name = ep_target_name,
- **kwargs
- )
- bins_for_data_label.append(ep_target_name)
- data.append(ep_target_name)
-
- existing_bin_names = {ep["name"].lower(): None for ep in entry_points.values()}
- for p in native.glob(["bin/*"], allow_empty = True):
- existing_bin_names[p[len("bin/"):].lower()] = None
-
- for src_path in native.glob(["rewrite-bin/*"], allow_empty = True):
- script_name = src_path[len("rewrite-bin/"):]
- if script_name.lower() in existing_bin_names:
- continue
- rewrite_target_name = "bin/{}".format(script_name)
- rules.venv_rewrite_shebang(
- name = rewrite_target_name,
- src = src_path,
- package = name,
- )
- bins_for_data_label.append(rewrite_target_name)
- data.append(rewrite_target_name)
-
- record_srcs = native.glob(["rewrite-record/*/RECORD"], allow_empty = True)
- record_target_name = "record"
- if record_srcs:
- rules.gen_wheel_record(
- name = record_target_name,
- srcs = record_srcs,
- tags = ["manual"],
- )
- data.append(record_target_name)
-
- if filegroups == None:
- filegroups = {
- EXTRACTED_WHEEL_FILES: dict(
- include = ["**"],
- # The Bazel repo files are always excluded; only the sdist
- # filename is conditional on `sdist_filename`.
- exclude = _BAZEL_REPO_FILE_GLOBS + (
- [sdist_filename] if sdist_filename else []
- ),
- ),
- DIST_INFO_LABEL: dict(
- include = ["site-packages/*.dist-info/**"],
- ),
- DATA_LABEL: dict(
- include = ["data/**", "bin/**", "include/**"],
- ),
- }
-
- for filegroup_name, glob_kwargs in filegroups.items():
- glob_kwargs = {"allow_empty": True} | glob_kwargs
- srcs = native.glob(**glob_kwargs)
- if filegroup_name == DATA_LABEL:
- srcs = srcs + bins_for_data_label
- if filegroup_name == DIST_INFO_LABEL and record_srcs:
- srcs = srcs + [record_target_name]
- native.filegroup(
- name = filegroup_name,
- srcs = srcs,
- visibility = visibility,
- )
-
- for src, dest in copy_files.items():
- rules.copy_file(
- name = dest + ".copy",
- src = src,
- out = dest,
- visibility = visibility,
- )
- data.append(dest)
- for src, dest in copy_executables.items():
- rules.copy_file(
- name = dest + ".copy",
- src = src,
- out = dest,
- is_executable = True,
- visibility = visibility,
- )
- data.append(dest)
-
- if hasattr(native, "filegroup"):
- native.filegroup(
- name = whl_name,
- srcs = [name],
- visibility = visibility,
- )
-
- if hasattr(rules, "py_library"):
- srcs = native.glob(
- ["site-packages/**/*.py"],
- exclude = srcs_exclude,
- # Empty sources are allowed to support wheels that don't have any
- # pure-Python code, e.g. pymssql, which is written in Cython.
- allow_empty = True,
- )
-
- # NOTE: pyi files should probably be excluded because they're carried
- # by the pyi_srcs attribute. However, historical behavior included
- # them in data and some tools currently rely on that.
- _data_exclude = [
- "**/*.py",
- "**/*.pyc",
- "**/*.pyc.*", # During pyc creation, temp files named *.pyc.NNNN are created
- ]
- if sdist_filename:
- _data_exclude.append("**/*.dist-info/RECORD")
- for item in data_exclude:
- if item not in _data_exclude:
- _data_exclude.append(item)
-
- data = data + native.glob(
- ["site-packages/**/*"],
- exclude = _data_exclude,
- allow_empty = True,
- )
-
- pyi_srcs = native.glob(
- ["site-packages/**/*.pyi"],
- allow_empty = True,
- )
-
- if not enable_implicit_namespace_pkgs:
- generated_namespace_package_files = select({
- _IS_VENV_SITE_PACKAGES_YES: [],
- "//conditions:default": rules.create_inits(
- srcs = srcs + data + pyi_srcs,
- ignored_dirnames = [], # If you need to ignore certain folders, you can patch rules_python here to do so.
- root = "site-packages",
- ),
- })
- namespace_package_files += generated_namespace_package_files
- srcs = srcs + generated_namespace_package_files
-
- # This is done after create_inits() is called so that the data scheme
- # files don't have such files created in their directories.
- data = data + [DATA_LABEL]
-
- rules.py_library(
- name = pkg_name,
- srcs = srcs,
- pyi_srcs = pyi_srcs,
- data = data,
- # This makes this directory a top-level in the python import
- # search path for anything that depends on this.
- imports = ["site-packages"],
- tags = tags,
- visibility = visibility,
- experimental_venvs_site_packages = _VENV_SITE_PACKAGES_FLAG,
- namespace_package_files = namespace_package_files,
- )
-
-def whl_library_deps_targets(
- *,
- name = None,
- repo,
- aliases = None,
- metadata_name,
- requires_dist,
- extras,
- include = [],
- group_deps = [],
- group_name = None,
- dep_template,
- tags = [],
- visibility = ["//visibility:public"],
- native = native,
- rules = struct(
- py_library = py_library,
- env_marker_setting = env_marker_setting,
- )):
- """Create all of the whl_library targets.
-
- Args:
- name: {type}`str` The wheel filename
- metadata_name: {type}`str` The package name as written in wheel `METADATA`.
- group_deps: {type}`list[str]` names of fellow members of the group (if
- any). These will be excluded from generated deps lists so as to avoid
- direct cycles. These dependencies will be provided at runtime by the
- group rules which wrap this library and its fellows together.
- requires_dist: {type}`list[str]` The list of `Requires-Dist` values from
- the whl `METADATA`.
- extras: {type}`list[str]` The list of requested extras. This essentially includes extra transitive dependencies in the final targets depending on the wheel `METADATA`.
- include: {type}`list[str]` The list of packages to include.
- group_name: {type}`str | None` name of the dependency group (if any).
- dep_template: {type}`str | None` The dep_template to use.
- tags: {type}`list[str]` The tags set on the targets.
- repo: {type}`str | Label | None` The BUILD.bazel label to the parent repo that has the
- sources. If none, then will take the targets from the current dir.
- aliases: {type}`dict[str, str] | None` The list of aliases to create in the parent repo. If None, will create
- the default values. Empty list means no aliases.
- visibility: {type}`list[str]` The visibility of the targets.
- native: {type}`native` The native struct for overriding in tests.
- rules: {type}`struct` A struct with references to rules for creating targets.
- """
- repo_label = Label(repo).same_package_label if repo else (lambda x: x)
- if aliases == None:
- aliases = {
- EXTRACTED_WHEEL_FILES: repo_label(EXTRACTED_WHEEL_FILES),
- DIST_INFO_LABEL: repo_label(DIST_INFO_LABEL),
- DATA_LABEL: repo_label(DATA_LABEL),
- }
-
- # If this library is a member of a group, its public label aliases need to
- # point to the group implementation rule not the implementation rules. We
- # also need to mark the implementation rules as visible to the group
- # implementation.
- if group_name and "//:" in dep_template:
- # This is the legacy behaviour where the group library is outside the hub repo
- #
- # It is expected to disappear when we drop WORKSPACE or drop the vendoring of
- # pip_parse `requirements.bzl` in WORKSPACE. The alternative would be to add
- # another argument to the macro, but it is already full of arguments.
- label_tmpl = dep_template.format(
- name = "_config",
- target = normalize_name(group_name) + "_{}",
- ).replace(
- "//:",
- "//_groups:",
- )
- aliases = aliases | {
- PY_LIBRARY_PUBLIC_LABEL: label_tmpl.format(PY_LIBRARY_PUBLIC_LABEL),
- WHEEL_FILE_PUBLIC_LABEL: label_tmpl.format(WHEEL_FILE_PUBLIC_LABEL),
- }
- impl_vis = [dep_template.format(
- name = "_config",
- target = "__pkg__",
- ).replace(
- "//:",
- "//_groups:",
- )]
-
- py_library_label = PY_LIBRARY_IMPL_LABEL
- whl_file_label = WHEEL_FILE_IMPL_LABEL
- else:
- py_library_label = PY_LIBRARY_PUBLIC_LABEL
- whl_file_label = WHEEL_FILE_PUBLIC_LABEL
- if group_name:
- impl_vis = [dep_template.format(name = "", target = "__subpackages__")]
- else:
- impl_vis = visibility
-
- if not requires_dist:
- # If the package is in a group but has no deps, we still need the public labels to
- # point at the srcs targets so that the group implementation can use them. We don't
- # need any of the extra targets, so just create the aliases.
- aliases = aliases | {
- py_library_label: repo_label(PY_SRCS_LABEL),
- whl_file_label: repo_label(WHEEL_FILE),
- }
-
- for alias, actual in aliases.items():
- native.alias(
- name = alias,
- actual = actual,
- visibility = visibility,
- )
-
- if not requires_dist:
- # If there are extras, then they will be visible in requires_dist.
- return
-
- package_deps = _parse_requires_dist(
- name = metadata_name,
+ whl_library_deps_targets(
+ name = name,
+ metadata_name = metadata_name,
requires_dist = requires_dist,
- excludes = group_deps,
- extras = extras,
- include = include,
+ dep_template = dep_template, # only needed if requires_dist or group_name is present
+ group_deps = group_deps, # only needed if group_name is present
+ group_name = group_name, # must specify group_deps together
+ extras = extras, # only needed if requires_dist is present
+ include = include, # only needed if requires_dist is present
+ repo = None, # set aliases in the same repo
+ aliases = {},
+ **kwargs
)
-
- _config_settings(
- dependencies_with_markers = package_deps.deps_select,
- rules = rules,
- visibility = ["//visibility:private"],
- )
-
- if hasattr(native, "filegroup"):
- # We include the whl file as srcs so that `$(location :whl)` expands to the whl file.
- # The transitive dependencies are available via the `data` attribute.
- native.filegroup(
- name = whl_file_label,
- srcs = [repo_label(WHEEL_FILE)],
- data = _deps(
- deps = [],
- package_deps = package_deps,
- tmpl = dep_template.format(name = "{}", target = WHEEL_FILE_PUBLIC_LABEL),
- ),
- visibility = impl_vis,
- )
-
- if hasattr(rules, "py_library"):
- rules.py_library(
- name = py_library_label,
- # We include as srcs to ensure that the (locations :pkg) works as expected.
- srcs = [repo_label(PY_SRCS_LABEL)],
- deps = _deps(
- # We include as deps, so that `PyInfo` and friends (e.g. `pyi_srcs`) get
- # propagated. Just passing the target as `srcs` is not enough to propagate
- # `pyi_srcs`, see `tests/base_rules/py_library`.
- deps = [repo_label(PY_SRCS_LABEL)],
- package_deps = package_deps,
- tmpl = dep_template.format(name = "{}", target = PY_LIBRARY_PUBLIC_LABEL),
- ),
- tags = tags,
- visibility = impl_vis,
- )
-
-def _parse_requires_dist(
- *,
- name,
- requires_dist,
- excludes,
- include,
- extras):
- return deps(
- name = normalize_name(name),
- requires_dist = requires_dist,
- excludes = excludes,
- include = include,
- extras = extras,
- )
-
-def _config_settings(dependencies_with_markers, rules, **kwargs):
- """Generate config settings for the targets.
-
- Args:
- dependencies_with_markers: {type}`dict[str, str]` The markers to evaluate by
- each dep.
- rules: used for testing
- **kwargs: Extra kwargs to pass to the rule.
- """
- for dep, expression in dependencies_with_markers.items():
- rules.env_marker_setting(
- name = "include_{}".format(dep),
- expression = expression,
- **kwargs
- )
-
-def _deps(deps, package_deps, tmpl):
- deps = [] + deps + [tmpl.format(d) for d in sorted(package_deps.deps)]
-
- for dep in package_deps.deps_select:
- deps = deps + select({
- ":is_include_{}_true".format(dep): [tmpl.format(dep)],
- "//conditions:default": [],
- })
-
- return deps
diff --git a/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl b/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl
index bb03d9a..d3a390d 100644
--- a/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl
+++ b/tests/pypi/generate_whl_library_build_bazel/generate_whl_library_build_bazel_tests.bzl
@@ -241,6 +241,34 @@
_tests.append(_test_all_with_loads)
+def _test_deps_targets(env):
+ want = """\
+load("@package_metadata//rules:package_metadata.bzl", "package_metadata")
+load("@rules_python//python/private/pypi:whl_library_deps_targets.bzl", "whl_library_deps_targets")
+
+package(default_visibility = ["//visibility:public"])
+
+whl_library_deps_targets(
+ metadata_name = "foo",
+ name = "foo.whl",
+ repo = "@some_repo",
+ tags = [
+ "pypi_name=foo",
+ "pypi_version=0",
+ ],
+)
+"""
+ actual = generate_whl_library_build_bazel(
+ metadata_version = "0",
+ metadata_name = "foo",
+ name = "foo.whl",
+ repo = "@some_repo",
+ config_load = "",
+ )
+ env.expect.that_str(actual.replace("@@", "@")).equals(want)
+
+_tests.append(_test_deps_targets)
+
def generate_whl_library_build_bazel_test_suite(name):
"""Create the test suite.
diff --git a/tests/pypi/whl_library_deps_targets/BUILD.bazel b/tests/pypi/whl_library_deps_targets/BUILD.bazel
new file mode 100644
index 0000000..789a86d
--- /dev/null
+++ b/tests/pypi/whl_library_deps_targets/BUILD.bazel
@@ -0,0 +1,5 @@
+load(":whl_library_deps_targets_tests.bzl", "whl_library_deps_targets_test_suite")
+
+whl_library_deps_targets_test_suite(
+ name = "whl_library_deps_targets_tests",
+)
diff --git a/tests/pypi/whl_library_deps_targets/whl_library_deps_targets_tests.bzl b/tests/pypi/whl_library_deps_targets/whl_library_deps_targets_tests.bzl
new file mode 100644
index 0000000..ad6ab6f
--- /dev/null
+++ b/tests/pypi/whl_library_deps_targets/whl_library_deps_targets_tests.bzl
@@ -0,0 +1,147 @@
+""
+
+load("@rules_testing//lib:test_suite.bzl", "test_suite")
+load("//python/private/pypi:whl_library_deps_targets.bzl", "whl_library_deps_targets") # buildifier: disable=bzl-visibility
+load("//tests/support/mocks:mocks.bzl", "mocks")
+
+_tests = []
+
+def _test_whl_library_deps_targets(env):
+ filegroup_calls = []
+ py_library_calls = []
+ env_marker_setting_calls = []
+
+ m_glob = mocks.glob()
+
+ m_glob.results.append([]) # bin
+ m_glob.results.append([]) # rewrite-bin
+ m_glob.results.append(["site-packages/foo/SRCS.py"]) # srcs
+ m_glob.results.append(["site-packages/foo/DATA.txt"]) # data
+ m_glob.results.append(["site-packages/foo/PYI.pyi"]) # pyi
+
+ whl_library_deps_targets(
+ name = "foo-0-py3-none-any.whl",
+ metadata_name = "Foo",
+ dep_template = "@pypi//{name}:{target}",
+ requires_dist = [
+ "foo", # this self-edge will be ignored
+ "bar",
+ "bar-baz; python_version < \"8.2\"",
+ "booo", # this is effectively excluded due to the list below
+ ],
+ include = ["foo", "bar", "bar_baz"],
+ # Overrides for testing
+ repo = None,
+ aliases = None,
+ extras = [],
+ native = struct(
+ filegroup = lambda **kwargs: filegroup_calls.append(kwargs),
+ alias = lambda **kwargs: None,
+ config_setting = lambda **_: None,
+ glob = m_glob.glob,
+ ),
+ rules = struct(
+ py_library = lambda **kwargs: py_library_calls.append(kwargs),
+ env_marker_setting = lambda **kwargs: env_marker_setting_calls.append(kwargs),
+ create_inits = lambda *args, **kwargs: ["_create_inits_target"],
+ venv_rewrite_shebang = lambda **kwargs: None,
+ ),
+ )
+
+ env.expect.that_collection(filegroup_calls).contains_exactly([
+ {
+ "name": "whl",
+ # NOTE @aignas 2026-07-25: depending on the brackets position one may get different
+ # results in the expectation.
+ "srcs": ["whl_file"],
+ "data": ["@pypi//bar:whl"] + select({
+ ":is_include_bar_baz_true": ["@pypi//bar_baz:whl"],
+ "//conditions:default": [],
+ }),
+ "visibility": ["//visibility:public"],
+ },
+ ]) # buildifier: @unsorted-dict-items
+
+ env.expect.that_collection(py_library_calls).has_size(1)
+ if len(py_library_calls) != 1:
+ return
+ py_library_call = py_library_calls[0]
+
+ env.expect.that_dict(py_library_call).contains_exactly({
+ "name": "pkg",
+ "srcs": ["srcs"],
+ "deps": ["srcs", "@pypi//bar:pkg"] + select({
+ ":is_include_bar_baz_true": ["@pypi//bar_baz:pkg"],
+ "//conditions:default": [],
+ }),
+ "tags": [],
+ "visibility": ["//visibility:public"],
+ }) # buildifier: @unsorted-dict-items
+
+ env.expect.that_collection(m_glob.calls).contains_exactly([])
+
+ env.expect.that_collection(env_marker_setting_calls).contains_exactly([
+ {
+ "name": "include_bar_baz",
+ "expression": "python_version < \"8.2\"",
+ "visibility": ["//visibility:private"],
+ },
+ ]) # buildifier: @unsorted-dict-items
+
+_tests.append(_test_whl_library_deps_targets)
+
+def _test_whl_library_deps_targets_no_deps(env):
+ alias_calls = []
+ filegroup_calls = []
+ py_library_calls = []
+ env_marker_setting_calls = []
+
+ whl_library_deps_targets(
+ name = "foo-0-py3-none-any.whl",
+ metadata_name = "Foo",
+ dep_template = "@pypi//{name}:{target}",
+ requires_dist = [],
+ group_name = "qux",
+ repo = None,
+ aliases = {},
+ extras = [],
+ native = struct(
+ filegroup = lambda **kwargs: filegroup_calls.append(kwargs),
+ alias = lambda **kwargs: alias_calls.append(kwargs),
+ config_setting = lambda **_: None,
+ glob = lambda **_: [],
+ ),
+ rules = struct(
+ py_library = lambda **kwargs: py_library_calls.append(kwargs),
+ env_marker_setting = lambda **kwargs: env_marker_setting_calls.append(kwargs),
+ ),
+ )
+
+ # If the package is in a group but has no deps, then the public labels should be aliases
+ # to the srcs targets and no other targets should be created.
+ env.expect.that_collection(alias_calls).contains_exactly([
+ {
+ "name": "pkg",
+ "actual": "srcs",
+ "visibility": ["//visibility:public"],
+ },
+ {
+ "name": "whl",
+ "actual": "whl_file",
+ "visibility": ["//visibility:public"],
+ },
+ ]) # buildifier: @unsorted-dict-items
+
+ env.expect.that_collection(filegroup_calls).contains_exactly([])
+ env.expect.that_collection(py_library_calls).contains_exactly([])
+ env.expect.that_collection(env_marker_setting_calls).contains_exactly([])
+
+_tests.append(_test_whl_library_deps_targets_no_deps)
+
+def whl_library_deps_targets_test_suite(name):
+ """create the test suite.
+
+ args:
+ name: the name of the test suite
+ """
+ test_suite(name = name, basic_tests = _tests)
diff --git a/tests/pypi/whl_library_srcs/BUILD.bazel b/tests/pypi/whl_library_srcs/BUILD.bazel
new file mode 100644
index 0000000..f525148
--- /dev/null
+++ b/tests/pypi/whl_library_srcs/BUILD.bazel
@@ -0,0 +1,5 @@
+load(":whl_library_srcs_tests.bzl", "whl_library_srcs_test_suite")
+
+whl_library_srcs_test_suite(
+ name = "whl_library_srcs_tests",
+)
diff --git a/tests/pypi/whl_library_srcs/whl_library_srcs_tests.bzl b/tests/pypi/whl_library_srcs/whl_library_srcs_tests.bzl
new file mode 100644
index 0000000..1499170
--- /dev/null
+++ b/tests/pypi/whl_library_srcs/whl_library_srcs_tests.bzl
@@ -0,0 +1,211 @@
+# Copyright 2024 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""
+
+load("@rules_testing//lib:test_suite.bzl", "test_suite")
+load("//python/private/pypi:whl_library_srcs.bzl", "whl_library_srcs") # buildifier: disable=bzl-visibility
+load("//tests/support/mocks:mocks.bzl", "mocks")
+
+_tests = []
+
+def _test_filegroups(env):
+ calls = []
+
+ def glob(include, *, exclude = [], allow_empty):
+ _ = exclude # @unused
+ env.expect.that_bool(allow_empty).equals(True)
+ if include in [["rewrite-bin/*"], ["bin/*"], ["rewrite-record/*/RECORD"]]:
+ return []
+ return include
+
+ whl_library_srcs(
+ name = "",
+ native = struct(
+ filegroup = lambda **kwargs: calls.append(kwargs),
+ glob = glob,
+ ),
+ rules = struct(
+ venv_rewrite_shebang = lambda **kwargs: None,
+ gen_wheel_record = lambda **kwargs: None,
+ ),
+ )
+
+ env.expect.that_collection(calls, expr = "filegroup calls").contains_exactly([
+ {
+ "name": "dist_info",
+ "srcs": ["site-packages/*.dist-info/**"],
+ "visibility": ["//visibility:public"],
+ },
+ {
+ "name": "data",
+ "srcs": ["data/**", "bin/**", "include/**"],
+ "visibility": ["//visibility:public"],
+ },
+ {
+ "name": "extracted_whl_files",
+ "srcs": ["**"],
+ "visibility": ["//visibility:public"],
+ },
+ {
+ "name": "whl_file",
+ "srcs": [""],
+ "visibility": ["//visibility:public"],
+ },
+ ]) # buildifier: @unsorted-dict-items
+
+_tests.append(_test_filegroups)
+
+def _test_copy(env):
+ calls = []
+
+ whl_library_srcs(
+ name = "",
+ filegroups = {},
+ copy_files = {"file_src": "file_dest"},
+ copy_executables = {"exec_src": "exec_dest"},
+ native = struct(
+ glob = lambda *args, **kwargs: [],
+ ),
+ rules = struct(
+ copy_file = lambda **kwargs: calls.append(kwargs),
+ venv_rewrite_shebang = lambda **kwargs: None,
+ gen_wheel_record = lambda **kwargs: None,
+ ),
+ )
+
+ env.expect.that_collection(calls).contains_exactly([
+ {
+ "name": "file_dest.copy",
+ "out": "file_dest",
+ "src": "file_src",
+ "visibility": ["//visibility:public"],
+ },
+ {
+ "is_executable": True,
+ "name": "exec_dest.copy",
+ "out": "exec_dest",
+ "src": "exec_src",
+ "visibility": ["//visibility:public"],
+ },
+ ])
+
+_tests.append(_test_copy)
+
+def _test_sdist_excludes_record(env):
+ py_library_calls = []
+ m_glob = mocks.glob()
+ m_glob.results.append([]) # bin
+ m_glob.results.append([]) # rewrite-bin
+ m_glob.results.append([]) # rewrite-record
+ m_glob.results.append([]) # srcs
+ m_glob.results.append([]) # data
+ m_glob.results.append([]) # pyi
+
+ whl_library_srcs(
+ name = "foo.whl",
+ sdist_filename = "foo.tar.gz",
+ filegroups = {},
+ native = struct(
+ filegroup = lambda **_: None,
+ config_setting = lambda **_: None,
+ glob = m_glob.glob,
+ ),
+ rules = struct(
+ py_library = lambda **kwargs: py_library_calls.append(kwargs),
+ create_inits = lambda **kwargs: [],
+ venv_rewrite_shebang = lambda **kwargs: None,
+ gen_wheel_record = lambda **kwargs: None,
+ ),
+ )
+
+ env.expect.that_collection(m_glob.calls).contains_at_least([
+ mocks.glob_call(
+ ["site-packages/**/*"],
+ exclude = [
+ "**/*.py",
+ "**/*.pyc",
+ "**/*.pyc.*",
+ "**/*.dist-info/RECORD",
+ ],
+ allow_empty = True,
+ ),
+ ])
+
+_tests.append(_test_sdist_excludes_record)
+
+def _test_exclude_bazel_files(env):
+ # Regression test: the `extracted_whl_files` glob must always exclude the
+ # Bazel repo files, even when the wheel is not built from an sdist.
+ for sdist_filename in [None, "foo.tar.gz"]:
+ m_glob = mocks.glob()
+ m_glob.results.append([]) # bin
+ m_glob.results.append([]) # rewrite-bin
+ m_glob.results.append([]) # rewrite-record
+ m_glob.results.append([]) # extracted_whl_files
+ m_glob.results.append([]) # dist_info
+ m_glob.results.append([]) # data
+
+ whl_library_srcs(
+ name = "foo.whl",
+ sdist_filename = sdist_filename,
+ native = struct(
+ filegroup = lambda **_: None,
+ glob = m_glob.glob,
+ ),
+ rules = struct(
+ venv_rewrite_shebang = lambda **kwargs: None,
+ gen_wheel_record = lambda **kwargs: None,
+ ),
+ )
+
+ expected_exclude = [
+ "BUILD",
+ "BUILD.bazel",
+ "REPO.bazel",
+ "WORKSPACE",
+ "WORKSPACE.bzlmod",
+ "WORKSPACE.bazel",
+ ]
+ if sdist_filename:
+ expected_exclude.append(sdist_filename)
+
+ env.expect.that_collection(m_glob.calls).contains_exactly([
+ mocks.glob_call(["bin/*"], allow_empty = True),
+ mocks.glob_call(["rewrite-bin/*"], allow_empty = True),
+ mocks.glob_call(["rewrite-record/*/RECORD"], allow_empty = True),
+ mocks.glob_call(
+ include = ["**"],
+ exclude = expected_exclude,
+ allow_empty = True,
+ ),
+ mocks.glob_call(
+ include = ["site-packages/*.dist-info/**"],
+ allow_empty = True,
+ ),
+ mocks.glob_call(
+ include = ["data/**", "bin/**", "include/**"],
+ allow_empty = True,
+ ),
+ ])
+
+_tests.append(_test_exclude_bazel_files)
+
+def whl_library_srcs_test_suite(name):
+ """create the test suite.
+
+ args:
+ name: the name of the test suite
+ """
+ test_suite(name = name, basic_tests = _tests)
diff --git a/tests/pypi/whl_library_targets/BUILD.bazel b/tests/pypi/whl_library_targets/BUILD.bazel
deleted file mode 100644
index f3d25c2..0000000
--- a/tests/pypi/whl_library_targets/BUILD.bazel
+++ /dev/null
@@ -1,5 +0,0 @@
-load(":whl_library_targets_tests.bzl", "whl_library_targets_test_suite")
-
-whl_library_targets_test_suite(
- name = "whl_library_targets_tests",
-)
diff --git a/tests/pypi/whl_library_targets/whl_library_targets_tests.bzl b/tests/pypi/whl_library_targets/whl_library_targets_tests.bzl
deleted file mode 100644
index 3fe1b99..0000000
--- a/tests/pypi/whl_library_targets/whl_library_targets_tests.bzl
+++ /dev/null
@@ -1,347 +0,0 @@
-# Copyright 2024 The Bazel Authors. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-""
-
-load("@rules_testing//lib:test_suite.bzl", "test_suite")
-load(
- "//python/private/pypi:whl_library_targets.bzl",
- "whl_library_deps_targets",
- "whl_library_srcs",
-) # buildifier: disable=bzl-visibility
-load("//tests/support/mocks:mocks.bzl", "mocks")
-
-_tests = []
-
-def _test_filegroups(env):
- calls = []
-
- def glob(include, *, exclude = [], allow_empty):
- _ = exclude # @unused
- env.expect.that_bool(allow_empty).equals(True)
- if include in [["rewrite-bin/*"], ["bin/*"], ["rewrite-record/*/RECORD"]]:
- return []
- return include
-
- whl_library_srcs(
- name = "",
- native = struct(
- filegroup = lambda **kwargs: calls.append(kwargs),
- glob = glob,
- ),
- rules = struct(
- venv_rewrite_shebang = lambda **kwargs: None,
- gen_wheel_record = lambda **kwargs: None,
- ),
- )
-
- env.expect.that_collection(calls, expr = "filegroup calls").contains_exactly([
- {
- "name": "dist_info",
- "srcs": ["site-packages/*.dist-info/**"],
- "visibility": ["//visibility:public"],
- },
- {
- "name": "data",
- "srcs": ["data/**", "bin/**", "include/**"],
- "visibility": ["//visibility:public"],
- },
- {
- "name": "extracted_whl_files",
- "srcs": ["**"],
- "visibility": ["//visibility:public"],
- },
- {
- "name": "whl_file",
- "srcs": [""],
- "visibility": ["//visibility:public"],
- },
- ]) # buildifier: @unsorted-dict-items
-
-_tests.append(_test_filegroups)
-
-def _test_copy(env):
- calls = []
-
- whl_library_srcs(
- name = "",
- filegroups = {},
- copy_files = {"file_src": "file_dest"},
- copy_executables = {"exec_src": "exec_dest"},
- native = struct(
- glob = lambda *args, **kwargs: [],
- ),
- rules = struct(
- copy_file = lambda **kwargs: calls.append(kwargs),
- venv_rewrite_shebang = lambda **kwargs: None,
- gen_wheel_record = lambda **kwargs: None,
- ),
- )
-
- env.expect.that_collection(calls).contains_exactly([
- {
- "name": "file_dest.copy",
- "out": "file_dest",
- "src": "file_src",
- "visibility": ["//visibility:public"],
- },
- {
- "is_executable": True,
- "name": "exec_dest.copy",
- "out": "exec_dest",
- "src": "exec_src",
- "visibility": ["//visibility:public"],
- },
- ])
-
-_tests.append(_test_copy)
-
-def _test_whl_library_deps_targets(env):
- filegroup_calls = []
- py_library_calls = []
- env_marker_setting_calls = []
-
- m_glob = mocks.glob()
-
- m_glob.results.append([]) # bin
- m_glob.results.append([]) # rewrite-bin
- m_glob.results.append(["site-packages/foo/SRCS.py"]) # srcs
- m_glob.results.append(["site-packages/foo/DATA.txt"]) # data
- m_glob.results.append(["site-packages/foo/PYI.pyi"]) # pyi
-
- whl_library_deps_targets(
- name = "foo-0-py3-none-any.whl",
- metadata_name = "Foo",
- dep_template = "@pypi//{name}:{target}",
- requires_dist = [
- "foo", # this self-edge will be ignored
- "bar",
- "bar-baz; python_version < \"8.2\"",
- "booo", # this is effectively excluded due to the list below
- ],
- include = ["foo", "bar", "bar_baz"],
- # Overrides for testing
- repo = None,
- aliases = None,
- extras = [],
- native = struct(
- filegroup = lambda **kwargs: filegroup_calls.append(kwargs),
- alias = lambda **kwargs: None,
- config_setting = lambda **_: None,
- glob = m_glob.glob,
- ),
- rules = struct(
- py_library = lambda **kwargs: py_library_calls.append(kwargs),
- env_marker_setting = lambda **kwargs: env_marker_setting_calls.append(kwargs),
- create_inits = lambda *args, **kwargs: ["_create_inits_target"],
- venv_rewrite_shebang = lambda **kwargs: None,
- ),
- )
-
- env.expect.that_collection(filegroup_calls).contains_exactly([
- {
- "name": "whl",
- # NOTE @aignas 2026-07-25: depending on the brackets position one may get different
- # results in the expectation.
- "srcs": ["whl_file"],
- "data": ["@pypi//bar:whl"] + select({
- ":is_include_bar_baz_true": ["@pypi//bar_baz:whl"],
- "//conditions:default": [],
- }),
- "visibility": ["//visibility:public"],
- },
- ]) # buildifier: @unsorted-dict-items
-
- env.expect.that_collection(py_library_calls).has_size(1)
- if len(py_library_calls) != 1:
- return
- py_library_call = py_library_calls[0]
-
- env.expect.that_dict(py_library_call).contains_exactly({
- "name": "pkg",
- "srcs": ["srcs"],
- "deps": ["srcs", "@pypi//bar:pkg"] + select({
- ":is_include_bar_baz_true": ["@pypi//bar_baz:pkg"],
- "//conditions:default": [],
- }),
- "tags": [],
- "visibility": ["//visibility:public"],
- }) # buildifier: @unsorted-dict-items
-
- env.expect.that_collection(m_glob.calls).contains_exactly([])
-
- env.expect.that_collection(env_marker_setting_calls).contains_exactly([
- {
- "name": "include_bar_baz",
- "expression": "python_version < \"8.2\"",
- "visibility": ["//visibility:private"],
- },
- ]) # buildifier: @unsorted-dict-items
-
-_tests.append(_test_whl_library_deps_targets)
-
-def _test_whl_library_deps_targets_no_deps(env):
- alias_calls = []
- filegroup_calls = []
- py_library_calls = []
- env_marker_setting_calls = []
-
- whl_library_deps_targets(
- name = "foo-0-py3-none-any.whl",
- metadata_name = "Foo",
- dep_template = "@pypi//{name}:{target}",
- requires_dist = [],
- group_name = "qux",
- repo = None,
- aliases = {},
- extras = [],
- native = struct(
- filegroup = lambda **kwargs: filegroup_calls.append(kwargs),
- alias = lambda **kwargs: alias_calls.append(kwargs),
- config_setting = lambda **_: None,
- glob = lambda **_: [],
- ),
- rules = struct(
- py_library = lambda **kwargs: py_library_calls.append(kwargs),
- env_marker_setting = lambda **kwargs: env_marker_setting_calls.append(kwargs),
- ),
- )
-
- # If the package is in a group but has no deps, then the public labels should be aliases
- # to the srcs targets and no other targets should be created.
- env.expect.that_collection(alias_calls).contains_exactly([
- {
- "name": "pkg",
- "actual": "srcs",
- "visibility": ["//visibility:public"],
- },
- {
- "name": "whl",
- "actual": "whl_file",
- "visibility": ["//visibility:public"],
- },
- ]) # buildifier: @unsorted-dict-items
-
- env.expect.that_collection(filegroup_calls).contains_exactly([])
- env.expect.that_collection(py_library_calls).contains_exactly([])
- env.expect.that_collection(env_marker_setting_calls).contains_exactly([])
-
-_tests.append(_test_whl_library_deps_targets_no_deps)
-
-def _test_sdist_excludes_record(env):
- py_library_calls = []
- m_glob = mocks.glob()
- m_glob.results.append([]) # bin
- m_glob.results.append([]) # rewrite-bin
- m_glob.results.append([]) # rewrite-record
- m_glob.results.append([]) # srcs
- m_glob.results.append([]) # data
- m_glob.results.append([]) # pyi
-
- whl_library_srcs(
- name = "foo.whl",
- sdist_filename = "foo.tar.gz",
- filegroups = {},
- native = struct(
- filegroup = lambda **_: None,
- config_setting = lambda **_: None,
- glob = m_glob.glob,
- ),
- rules = struct(
- py_library = lambda **kwargs: py_library_calls.append(kwargs),
- create_inits = lambda **kwargs: [],
- venv_rewrite_shebang = lambda **kwargs: None,
- gen_wheel_record = lambda **kwargs: None,
- ),
- )
-
- env.expect.that_collection(m_glob.calls).contains_at_least([
- mocks.glob_call(
- ["site-packages/**/*"],
- exclude = [
- "**/*.py",
- "**/*.pyc",
- "**/*.pyc.*",
- "**/*.dist-info/RECORD",
- ],
- allow_empty = True,
- ),
- ])
-
-_tests.append(_test_sdist_excludes_record)
-
-def _test_exclude_bazel_files(env):
- # Regression test: the `extracted_whl_files` glob must always exclude the
- # Bazel repo files, even when the wheel is not built from an sdist.
- for sdist_filename in [None, "foo.tar.gz"]:
- m_glob = mocks.glob()
- m_glob.results.append([]) # bin
- m_glob.results.append([]) # rewrite-bin
- m_glob.results.append([]) # rewrite-record
- m_glob.results.append([]) # extracted_whl_files
- m_glob.results.append([]) # dist_info
- m_glob.results.append([]) # data
-
- whl_library_srcs(
- name = "foo.whl",
- sdist_filename = sdist_filename,
- native = struct(
- filegroup = lambda **_: None,
- glob = m_glob.glob,
- ),
- rules = struct(
- venv_rewrite_shebang = lambda **kwargs: None,
- gen_wheel_record = lambda **kwargs: None,
- ),
- )
-
- expected_exclude = [
- "BUILD",
- "BUILD.bazel",
- "REPO.bazel",
- "WORKSPACE",
- "WORKSPACE.bzlmod",
- "WORKSPACE.bazel",
- ]
- if sdist_filename:
- expected_exclude.append(sdist_filename)
-
- env.expect.that_collection(m_glob.calls).contains_exactly([
- mocks.glob_call(["bin/*"], allow_empty = True),
- mocks.glob_call(["rewrite-bin/*"], allow_empty = True),
- mocks.glob_call(["rewrite-record/*/RECORD"], allow_empty = True),
- mocks.glob_call(
- include = ["**"],
- exclude = expected_exclude,
- allow_empty = True,
- ),
- mocks.glob_call(
- include = ["site-packages/*.dist-info/**"],
- allow_empty = True,
- ),
- mocks.glob_call(
- include = ["data/**", "bin/**", "include/**"],
- allow_empty = True,
- ),
- ])
-
-_tests.append(_test_exclude_bazel_files)
-
-def whl_library_targets_test_suite(name):
- """create the test suite.
-
- args:
- name: the name of the test suite
- """
- test_suite(name = name, basic_tests = _tests)