diff --git a/python/features.bzl b/python/features.bzl index 99050a7ca5..8e295ec747 100644 --- a/python/features.bzl +++ b/python/features.bzl @@ -148,6 +148,45 @@ _LOADABLE_SYMBOLS = { ], } +def _major_minor(bazel_version): + # An empty version signifies a development build, which is treated as + # the latest version. + if not bazel_version: + return 99999, 99999 + + parts = bazel_version.split(".") + return int(parts[0]), int(parts[1]) + +def bazel_features(bazel_version): + """Return the features of a Bazel version that `rules_python` uses. + + The Bazel version is only available when a repository rule or a module + extension runs, so this is a function instead of fields of {obj}`features`, + e.g. `bazel_features(native.bazel_version).supports_whl_extraction`. + + :::{versionadded} VERSION_NEXT_FEATURE + ::: + + Args: + bazel_version: {type}`str` the Bazel version, e.g. + `native.bazel_version`. An empty string is a development build, + which is treated as the latest version. + + Returns: + {type}`struct` with: + * `supports_whl_extraction`: {type}`bool` whether + `repository_ctx.extract` can extract `.whl` files, which requires + Bazel 8.3.0 or later. + * `extract_needs_chmod`: {type}`bool` whether the files extracted by + `repository_ctx.extract` need their permissions fixed, which is the + case before Bazel 8.6.0. + """ + major, minor = _major_minor(bazel_version) + return struct( + supports_whl_extraction = major > 8 or (major == 8 and minor >= 3), + extract_needs_chmod = major < 8 or (major == 8 and minor < 6), + ) + features = struct( TYPEDEF = _features_typedef, # keep sorted diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel index dc0758efa5..1cdd28a847 100644 --- a/python/private/BUILD.bazel +++ b/python/private/BUILD.bazel @@ -907,10 +907,7 @@ bzl_library( bzl_library( name = "util", srcs = ["util.bzl"], - deps = [ - ":py_internal", - "@bazel_skylib//lib:types", - ], + deps = [":py_internal"], ) bzl_library( diff --git a/python/private/internal_config_repo.bzl b/python/private/internal_config_repo.bzl index 8f203bb89e..620be5b4d1 100644 --- a/python/private/internal_config_repo.bzl +++ b/python/private/internal_config_repo.bzl @@ -27,10 +27,8 @@ _ENABLE_DEPRECATION_WARNINGS_DEFAULT = "0" _CONFIG_TEMPLATE = """ config = struct( build_python_zip_default = {build_python_zip_default}, - supports_whl_extraction = {supports_whl_extraction}, enable_pystar = True, enable_deprecation_warnings = {enable_deprecation_warnings}, - extract_needs_chmod = {extract_needs_chmod}, bazel_8_or_later = {bazel_8_or_later}, bazel_9_or_later = {bazel_9_or_later}, bazel_10_or_later = {bazel_10_or_later}, @@ -81,20 +79,11 @@ def _internal_config_repo_impl(rctx): # An empty version signifies a development build, which is treated as # the latest version. if native.bazel_version: - version_parts = native.bazel_version.split(".") - bazel_major_version = int(version_parts[0]) - bazel_minor_version = int(version_parts[1]) + bazel_major_version = int(native.bazel_version.split(".")[0]) else: bazel_major_version = 99999 - bazel_minor_version = 99999 - supports_whl_extraction = False - extract_needs_chmod = bazel_major_version < 8 or (bazel_major_version == 8 and bazel_minor_version < 6) if bazel_major_version >= 8: - # Extracting .whl files requires Bazel 8.3.0 or later. - if bazel_major_version > 8 or bazel_minor_version >= 3: - supports_whl_extraction = True - builtin_py_info_symbol = "None" builtin_py_runtime_info_symbol = "None" builtin_py_cc_link_params_provider = "None" @@ -109,8 +98,6 @@ def _internal_config_repo_impl(rctx): enable_deprecation_warnings = _bool_from_environ(rctx, _ENABLE_DEPRECATION_WARNINGS_ENVVAR_NAME, _ENABLE_DEPRECATION_WARNINGS_DEFAULT), builtin_py_info_symbol = builtin_py_info_symbol, builtin_py_runtime_info_symbol = builtin_py_runtime_info_symbol, - supports_whl_extraction = str(supports_whl_extraction), - extract_needs_chmod = str(extract_needs_chmod), builtin_py_cc_link_params_provider = builtin_py_cc_link_params_provider, modules_using_explicit_initpy = str(explicit_init_py_modules), tool_deps_dev_only = str(rctx.attr.tool_deps_dev_only), diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel index f94d24594f..eece782ef3 100644 --- a/python/private/pypi/BUILD.bazel +++ b/python/private/pypi/BUILD.bazel @@ -265,8 +265,8 @@ bzl_library( srcs = ["patch_whl.bzl"], deps = [ ":parse_whl_name", + "//python:features", "//python/private:repo_utils", - "@rules_python_internal//:rules_python_config", ], ) @@ -371,7 +371,6 @@ bzl_library( deps = [ "//python/private:repo_utils", "//python/private:util", - "@bazel_skylib//lib:types", ], ) @@ -475,8 +474,8 @@ bzl_library( srcs = ["whl_extract.bzl"], deps = [ ":whl_metadata", + "//python:features", "//python/private:repo_utils", - "@rules_python_internal//:rules_python_config", ], ) diff --git a/python/private/pypi/patch_whl.bzl b/python/private/pypi/patch_whl.bzl index b257f2b4fe..f8c01f94b7 100644 --- a/python/private/pypi/patch_whl.bzl +++ b/python/private/pypi/patch_whl.bzl @@ -19,7 +19,7 @@ repack patched wheels. Instead, after extracting and patching, we fix the RECORD file natively in Starlark and repack using system zip. """ -load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config") +load("//python:features.bzl", "bazel_features") load("//python/private:repo_utils.bzl", "repo_utils") load(":parse_whl_name.bzl", "parse_whl_name") @@ -96,8 +96,8 @@ def patch_whl(rctx, *, whl_path, patches): repo_utils.extract( rctx, archive = whl_input, - supports_whl_extraction = rp_config.supports_whl_extraction, - extract_needs_chmod = rp_config.extract_needs_chmod, + supports_whl_extraction = bazel_features(native.bazel_version).supports_whl_extraction, + extract_needs_chmod = bazel_features(native.bazel_version).extract_needs_chmod, ) if not patches: diff --git a/python/private/pypi/pypi_repo_utils.bzl b/python/private/pypi/pypi_repo_utils.bzl index 8ec7bd1dbe..d8ad340340 100644 --- a/python/private/pypi/pypi_repo_utils.bzl +++ b/python/private/pypi/pypi_repo_utils.bzl @@ -14,7 +14,6 @@ "" -load("@bazel_skylib//lib:types.bzl", "types") load("//python/private:repo_utils.bzl", "repo_utils") load("//python/private:util.bzl", "is_importable_name") @@ -113,7 +112,11 @@ def _execute_prep(mrctx, *, python, srcs, **kwargs): environment = kwargs.pop("environment", {}) pythonpath = environment.get("PYTHONPATH", "") - if pythonpath and not types.is_string(pythonpath): + + # NOTE: `types.is_string` from `@bazel_skylib` is not used, so that this + # file can be loaded before the `WORKSPACE` defines `@bazel_skylib`, e.g. + # via `whl_library` from `python/repositories.bzl`. + if pythonpath and type(pythonpath) != "string": environment["PYTHONPATH"] = _construct_pypath(mrctx, entries = pythonpath) kwargs["environment"] = environment diff --git a/python/private/pypi/whl_extract.bzl b/python/private/pypi/whl_extract.bzl index d7195fd681..c2b4a1a19b 100644 --- a/python/private/pypi/whl_extract.bzl +++ b/python/private/pypi/whl_extract.bzl @@ -1,6 +1,6 @@ """A simple whl extractor.""" -load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config") +load("//python:features.bzl", "bazel_features") load("//python/private:repo_utils.bzl", "repo_utils") load(":whl_metadata.bzl", "find_whl_metadata") @@ -32,8 +32,8 @@ def whl_extract(rctx, *, whl_path, logger): rctx, archive = whl_path, output = install_dir_path, - supports_whl_extraction = rp_config.supports_whl_extraction, - extract_needs_chmod = rp_config.extract_needs_chmod, + supports_whl_extraction = bazel_features(native.bazel_version).supports_whl_extraction, + extract_needs_chmod = bazel_features(native.bazel_version).extract_needs_chmod, ) metadata_file = find_whl_metadata( diff --git a/python/private/util.bzl b/python/private/util.bzl index 31f317fedf..e96bd569e9 100644 --- a/python/private/util.bzl +++ b/python/private/util.bzl @@ -14,7 +14,6 @@ """Functionality shared by multiple pieces of code.""" -load("@bazel_skylib//lib:types.bzl", "types") load("//python/private:py_internal.bzl", "py_internal") def copy_propagating_kwargs(from_kwargs, into_kwargs = None): @@ -62,7 +61,11 @@ def add_tag(attrs, tag): # Preserve the input type: this allows a test verifying the underlying # rule can accept the tuple for the tags argument. - if types.is_tuple(tags): + # NOTE: `types.is_tuple` from `@bazel_skylib` is not used, so that + # this file can be loaded before the `WORKSPACE` defines + # `@bazel_skylib`, e.g. via `whl_library` from + # `python/repositories.bzl`. + if type(tags) == "tuple": attrs["tags"] = tags + (tag,) else: # List concatenation is necessary because the original value diff --git a/tests/bazel_features/BUILD.bazel b/tests/bazel_features/BUILD.bazel new file mode 100644 index 0000000000..2af882e20d --- /dev/null +++ b/tests/bazel_features/BUILD.bazel @@ -0,0 +1,3 @@ +load(":bazel_features_tests.bzl", "bazel_features_test_suite") + +bazel_features_test_suite(name = "bazel_features_tests") diff --git a/tests/bazel_features/bazel_features_tests.bzl b/tests/bazel_features/bazel_features_tests.bzl new file mode 100644 index 0000000000..f452d65817 --- /dev/null +++ b/tests/bazel_features/bazel_features_tests.bzl @@ -0,0 +1,35 @@ +"""Tests for the features of the Bazel versions.""" + +load("@rules_testing//lib:test_suite.bzl", "test_suite") +load("//python:features.bzl", "bazel_features") + +_tests = [] + +def _test_bazel_features(env): + for bazel_version, (supports_whl_extraction, extract_needs_chmod) in { + # A development build is treated as the latest version. + "": (True, False), + "7.4.1": (False, True), + "8.2.1": (False, True), + "8.3.0": (True, True), + "8.5.1": (True, True), + "8.6.0": (True, False), + "9.1.1": (True, False), + }.items(): + got = bazel_features(bazel_version) + env.expect.where(bazel_version = bazel_version).that_bool( + got.supports_whl_extraction, + ).equals(supports_whl_extraction) + env.expect.where(bazel_version = bazel_version).that_bool( + got.extract_needs_chmod, + ).equals(extract_needs_chmod) + +_tests.append(_test_bazel_features) + +def bazel_features_test_suite(name): + """Create the test suite. + + Args: + name: the name of the test suite + """ + test_suite(name = name, basic_tests = _tests)