diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index c14912c2c9..d70340f184 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -49,6 +49,7 @@ def _whl_mods_impl(whl_mods_dict): data = mods.data, data_exclude_glob = mods.data_exclude_glob, srcs_exclude_glob = mods.srcs_exclude_glob, + enable_implicit_namespace_pkgs = mods.enable_implicit_namespace_pkgs, )) _whl_mods_repo( @@ -178,6 +179,7 @@ You cannot use both the additive_build_content and additive_build_content_file a data = whl_mod.data, data_exclude_glob = whl_mod.data_exclude_glob, srcs_exclude_glob = whl_mod.srcs_exclude_glob, + enable_implicit_namespace_pkgs = whl_mod.enable_implicit_namespace_pkgs, ) config = build_config(module_ctx = module_ctx, enable_pipstar = enable_pipstar) @@ -741,6 +743,12 @@ cannot have a child module that uses the same `hub_name`. doc = "The whl name that the modifications are used for.", mandatory = True, ), + "enable_implicit_namespace_pkgs": attr.bool( + doc = """\ +(bool, optional): Override the global setting for generating __init__.py +files for namespace packages. If None, uses the repository-level setting. +""", + ), } return attrs diff --git a/python/private/pypi/generate_whl_library_build_bazel.bzl b/python/private/pypi/generate_whl_library_build_bazel.bzl index 3764e720c0..ee3d84c410 100644 --- a/python/private/pypi/generate_whl_library_build_bazel.bzl +++ b/python/private/pypi/generate_whl_library_build_bazel.bzl @@ -109,6 +109,8 @@ def generate_whl_library_build_bazel( kwargs["copy_executables"] = annotation.copy_executables kwargs["data_exclude"] = kwargs.get("data_exclude", []) + annotation.data_exclude_glob kwargs["srcs_exclude"] = annotation.srcs_exclude_glob + if hasattr(annotation, "enable_implicit_namespace_pkgs") and annotation.enable_implicit_namespace_pkgs != None: + kwargs["enable_implicit_namespace_pkgs"] = annotation.enable_implicit_namespace_pkgs if annotation.additive_build_content: additional_content.append(annotation.additive_build_content) if default_python_version: diff --git a/python/private/pypi/package_annotation.bzl b/python/private/pypi/package_annotation.bzl index 4a54703ac4..05e724399a 100644 --- a/python/private/pypi/package_annotation.bzl +++ b/python/private/pypi/package_annotation.bzl @@ -20,7 +20,8 @@ def package_annotation( copy_executables = {}, data = [], data_exclude_glob = [], - srcs_exclude_glob = []): + srcs_exclude_glob = [], + enable_implicit_namespace_pkgs = None): """Annotations to apply to the BUILD file content from package generated from a `pip_repository` rule. [cf]: https://github.com/bazelbuild/bazel-skylib/blob/main/docs/copy_file_doc.md @@ -35,6 +36,8 @@ def package_annotation( data_exclude_glob (list, optional): A list of exclude glob patterns to add as `data` to the generated `py_library` target. srcs_exclude_glob (list, optional): A list of labels to add as `srcs` to the generated `py_library` target. + enable_implicit_namespace_pkgs (bool, optional): Override the global setting for generating __init__.py + files for namespace packages. If None, uses the repository-level setting. Returns: str: A json encoded string of the provided content. @@ -46,4 +49,5 @@ def package_annotation( data = data, data_exclude_glob = data_exclude_glob, srcs_exclude_glob = srcs_exclude_glob, + enable_implicit_namespace_pkgs = enable_implicit_namespace_pkgs, )) diff --git a/tests/pypi/extension/extension_tests.bzl b/tests/pypi/extension/extension_tests.bzl index 0514e1d95b..4257db704e 100644 --- a/tests/pypi/extension/extension_tests.bzl +++ b/tests/pypi/extension/extension_tests.bzl @@ -49,6 +49,20 @@ simple==0.0.1 \ ], ) +def _whl_mod(*, hub_name, whl_name, additive_build_content = None, additive_build_content_file = None, copy_executables = {}, copy_files = {}, data = [], data_exclude_glob = [], srcs_exclude_glob = [], enable_implicit_namespace_pkgs = None): + return struct( + hub_name = hub_name, + whl_name = whl_name, + additive_build_content = additive_build_content, + additive_build_content_file = additive_build_content_file, + copy_executables = copy_executables, + copy_files = copy_files, + data = data, + data_exclude_glob = data_exclude_glob, + srcs_exclude_glob = srcs_exclude_glob, + enable_implicit_namespace_pkgs = enable_implicit_namespace_pkgs, + ) + def _mod(*, name, default = [], parse = [], override = [], whl_mods = [], is_root = True): return struct( name = name, @@ -234,6 +248,69 @@ def _test_build_pipstar_platform(env): _tests.append(_test_build_pipstar_platform) +def _test_whl_mods_with_namespace_pkgs(env): + pypi = _parse_modules( + env, + module_ctx = _mock_mctx( + _mod( + name = "rules_python", + parse = [ + _parse( + hub_name = "pypi", + python_version = "3.15", + requirements_lock = "requirements.txt", + ), + ], + whl_mods = [ + _whl_mod( + hub_name = "pypi", + whl_name = "simple", + additive_build_content = "# Custom build content", + enable_implicit_namespace_pkgs = True, + ), + ], + ), + ), + available_interpreters = { + "python_3_15_host": "unit_test_interpreter_target", + }, + minor_mapping = {"3.15": "3.15.19"}, + ) + + pypi.exposed_packages().contains_exactly({"pypi": ["simple"]}) + pypi.hub_group_map().contains_exactly({"pypi": {}}) + pypi.hub_whl_map().contains_exactly({"pypi": { + "simple": { + "pypi_315_simple": [ + whl_config_setting( + version = "3.15", + ), + ], + }, + }}) + pypi.whl_libraries().contains_exactly({ + "pypi_315_simple": { + "dep_template": "@pypi//{name}:{target}", + "python_interpreter_target": "unit_test_interpreter_target", + "requirement": "simple==0.0.1 --hash=sha256:deadbeef --hash=sha256:deadbaaf", + }, + }) + pypi.whl_mods().contains_exactly({ + "pypi": { + "simple": struct( + build_content = "# Custom build content", + copy_files = {}, + copy_executables = {}, + data = [], + data_exclude_glob = [], + srcs_exclude_glob = [], + enable_implicit_namespace_pkgs = True, + ), + }, + }) + +_tests.append(_test_whl_mods_with_namespace_pkgs) + def extension_test_suite(name): """Create the test suite. 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 225b296ebf..6d00e6a415 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 @@ -103,6 +103,7 @@ whl_library_targets_from_requires( "data_exclude_all", ], dep_template = "@pypi//{name}:{target}", + enable_implicit_namespace_pkgs = True, entry_points = { "foo": "bar.py", }, @@ -139,6 +140,7 @@ whl_library_targets_from_requires( data_exclude_glob = ["data_exclude_all"], srcs_exclude_glob = ["srcs_exclude_all"], additive_build_content = """# SOMETHING SPECIAL AT THE END""", + enable_implicit_namespace_pkgs = True, ), group_name = "qux", group_deps = ["foo", "fox", "qux"], @@ -211,6 +213,7 @@ whl_library_targets_from_requires( _tests.append(_test_all_with_loads) + def generate_whl_library_build_bazel_test_suite(name): """Create the test suite.