diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2965d256..359d0676 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,6 +21,5 @@ jobs: uses: bazel-contrib/.github/.github/workflows/bazel.yaml@e76b9e5a1d91256e06b097c4470a22263969600b with: folders: '[".", "e2e/smoke"]' - # TODO: make the root folder work with bzlmod - # MAYBE: allow usage from Bazel 6 as well - exclude: '[{"bazelversion": "6.4.0"}, {"folder": ".", "bzlmodEnabled": true}]' + # TODO(alex): switch the root folder to bzlmod + exclude: '[{"folder": ".", "bazelversion": "6.4.0"}, {"folder": ".", "bzlmodEnabled": true}]' diff --git a/e2e/smoke/.bazelrc b/e2e/smoke/.bazelrc index e69de29b..6a0a12d4 100644 --- a/e2e/smoke/.bazelrc +++ b/e2e/smoke/.bazelrc @@ -0,0 +1,2 @@ +# Under Bazel 7 or later, this flag is ignored as the PyRuntimeInfo gives this information. +common --@aspect_rules_py//py:interpreter_version=3.9.18 diff --git a/py/BUILD.bazel b/py/BUILD.bazel index 03d69b38..0aab4cbe 100644 --- a/py/BUILD.bazel +++ b/py/BUILD.bazel @@ -1,8 +1,18 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("@bazel_skylib//rules:common_settings.bzl", "string_flag") # For stardoc to reference the files exports_files(["defs.bzl"]) +# For Bazel 6.x compatibility, since +# PyRuntimeInfo shipped only with Bazel 7 +# Users can set, e.g. --@aspect_rules_py//py:interpreter_version=3.9.18 +string_flag( + name = "interpreter_version", + build_setting_default = "", + visibility = ["//visibility:public"], +) + bzl_library( name = "repositories", srcs = ["repositories.bzl"], diff --git a/py/private/BUILD.bazel b/py/private/BUILD.bazel index 973c3ebd..568af5a7 100644 --- a/py/private/BUILD.bazel +++ b/py/private/BUILD.bazel @@ -62,7 +62,10 @@ bzl_library( name = "py_semantics", srcs = ["py_semantics.bzl"], visibility = ["//py:__subpackages__"], - deps = ["//py/private/toolchain:types"], + deps = [ + "//py/private/toolchain:types", + "@bazel_skylib//rules:common_settings", + ], ) bzl_library( diff --git a/py/private/py_binary.bzl b/py/private/py_binary.bzl index 99de2fdc..8467c269 100644 --- a/py/private/py_binary.bzl +++ b/py/private/py_binary.bzl @@ -74,15 +74,15 @@ def _py_binary_rule_impl(ctx): "{{ARG_PYTHON}}": to_rlocation_path(ctx, py_toolchain.python), "{{ARG_VENV_NAME}}": ".{}.venv".format(ctx.attr.name), "{{ARG_VENV_PYTHON_VERSION}}": "{}.{}.{}".format( - py_toolchain.toolchain.interpreter_version_info.major, - py_toolchain.toolchain.interpreter_version_info.minor, - py_toolchain.toolchain.interpreter_version_info.micro, + py_toolchain.interpreter_version_info.major, + py_toolchain.interpreter_version_info.minor, + py_toolchain.interpreter_version_info.micro, ), "{{ARG_PTH_FILE}}": to_rlocation_path(ctx, site_packages_pth_file), "{{ENTRYPOINT}}": to_rlocation_path(ctx, ctx.file.main), "{{PYTHON_ENV}}": "\n".join(_dict_to_exports(env)).strip(), "{{EXEC_PYTHON_BIN}}": "python{}".format( - py_toolchain.toolchain.interpreter_version_info.major, + py_toolchain.interpreter_version_info.major, ), }, is_executable = True, @@ -146,6 +146,10 @@ _attrs = dict({ "_runfiles_lib": attr.label( default = "@bazel_tools//tools/bash/runfiles", ), + # NB: this is read by _resolve_toolchain in py_semantics. + "_interpreter_version_flag": attr.label( + default = "//py:interpreter_version", + ), }) _attrs.update(**_py_library.attrs) diff --git a/py/private/py_semantics.bzl b/py/private/py_semantics.bzl index 71f19702..81b135c6 100644 --- a/py/private/py_semantics.bzl +++ b/py/private/py_semantics.bzl @@ -1,8 +1,32 @@ """Functions to determine which Python toolchain to use""" load("//py/private/toolchain:types.bzl", "PY_TOOLCHAIN") +load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") -_INTERPRETER_FLAGS = ["-B", "-I"] +_INTERPRETER_FLAGS = [ + # -B Don't write .pyc files on import. See also PYTHONDONTWRITEBYTECODE. + "-B", + # -I Run Python in isolated mode. This also implies -E and -s. + # In isolated mode sys.path contains neither the script's directory nor the user's site-packages directory. + # All PYTHON* environment variables are ignored, too. + # Further restrictions may be imposed to prevent the user from injecting malicious code. + "-I", +] + +_MUST_SET_INTERPRETER_VERSION_FLAG = """\ +ERROR: Prior to Bazel 7.x, the python interpreter version must be explicitly provided. + +For example in `.bazelrc` with Bazel 6.4, add + + common --@aspect_rules_py//py:interpreter_version=3.9.18 + +Bazel 6.3 and earlier didn't handle the `common` verb for custom flags. +Repeat the flag to avoid discarding the analysis cache: + + build --@aspect_rules_py//py:interpreter_version=3.9.18 + fetch --@aspect_rules_py//py:interpreter_version=3.9.18 + query --@aspect_rules_py//py:interpreter_version=3.9.18 +""" def _resolve_toolchain(ctx): """Resolves the Python toolchain to a simple struct. @@ -36,10 +60,27 @@ def _resolve_toolchain(ctx): files = depset([]) uses_interpreter_path = True + # Bazel 7 has this field on the PyRuntimeInfo + if "interpreter_version_info" in dir(py3_toolchain): + interpreter_version_info = py3_toolchain.interpreter_version_info + elif ctx.attr._interpreter_version_flag[BuildSettingInfo].value: + # Back-compat for Bazel 6. + # Same code as rules_python: + # https://github.com/bazelbuild/rules_python/blob/76f1c76f60ccb536d3b3e2c9f023d8063f40bcd5/python/repositories.bzl#L109 + major, minor, micro = ctx.attr._interpreter_version_flag[BuildSettingInfo].value.split(".") + interpreter_version_info = struct( + major = major, + minor = minor, + micro = micro, + ) + else: + fail(_MUST_SET_INTERPRETER_VERSION_FLAG) + return struct( toolchain = py3_toolchain, files = files, python = interpreter, + interpreter_version_info = interpreter_version_info, uses_interpreter_path = uses_interpreter_path, flags = _INTERPRETER_FLAGS, ) diff --git a/py/private/py_unpacked_wheel.bzl b/py/private/py_unpacked_wheel.bzl index d21b4dce..5e902fc3 100644 --- a/py/private/py_unpacked_wheel.bzl +++ b/py/private/py_unpacked_wheel.bzl @@ -21,9 +21,9 @@ def _py_unpacked_wheel_impl(ctx): py_toolchain.python, "--python-version", "{}.{}.{}".format( - py_toolchain.toolchain.interpreter_version_info.major, - py_toolchain.toolchain.interpreter_version_info.minor, - py_toolchain.toolchain.interpreter_version_info.micro, + py_toolchain.interpreter_version_info.major, + py_toolchain.interpreter_version_info.minor, + py_toolchain.interpreter_version_info.micro, ), "--package-name", ctx.attr.py_package_name, @@ -44,8 +44,8 @@ def _py_unpacked_wheel_impl(ctx): unpack_directory.basename, "lib", "python{}.{}".format( - py_toolchain.toolchain.interpreter_version_info.major, - py_toolchain.toolchain.interpreter_version_info.minor, + py_toolchain.interpreter_version_info.major, + py_toolchain.interpreter_version_info.minor, ), "site-packages", ) @@ -74,6 +74,10 @@ _attrs = { "py_package_name": attr.string( mandatory = True, ), + # NB: this is read by _resolve_toolchain in py_semantics. + "_interpreter_version_flag": attr.label( + default = "//py:interpreter_version", + ), } py_unpacked_wheel = rule( diff --git a/py/private/py_venv.bzl b/py/private/py_venv.bzl index 4341c095..bbde295c 100644 --- a/py/private/py_venv.bzl +++ b/py/private/py_venv.bzl @@ -48,13 +48,13 @@ def _py_venv_rule_imp(ctx): "{{ARG_PYTHON}}": to_rlocation_path(ctx, py_toolchain.python), "{{ARG_VENV_LOCATION}}": paths.join(ctx.attr.location, ctx.attr.venv_name), "{{ARG_VENV_PYTHON_VERSION}}": "{}.{}.{}".format( - py_toolchain.toolchain.interpreter_version_info.major, - py_toolchain.toolchain.interpreter_version_info.minor, - py_toolchain.toolchain.interpreter_version_info.micro, + py_toolchain.interpreter_version_info.major, + py_toolchain.interpreter_version_info.minor, + py_toolchain.interpreter_version_info.micro, ), "{{ARG_PTH_FILE}}": to_rlocation_path(ctx, site_packages_pth_file), "{{EXEC_PYTHON_BIN}}": "python{}".format( - py_toolchain.toolchain.interpreter_version_info.major, + py_toolchain.interpreter_version_info.major, ), }, is_executable = True, @@ -115,6 +115,10 @@ _py_venv = rule( "_runfiles_lib": attr.label( default = "@bazel_tools//tools/bash/runfiles", ), + # NB: this is read by _resolve_toolchain in py_semantics. + "_interpreter_version_flag": attr.label( + default = "//py:interpreter_version", + ), }, toolchains = [ SH_TOOLCHAIN, diff --git a/py/private/toolchain/autodetecting.bzl b/py/private/toolchain/autodetecting.bzl index 3e5bcf8a..58fe3dde 100644 --- a/py/private/toolchain/autodetecting.bzl +++ b/py/private/toolchain/autodetecting.bzl @@ -1,5 +1,4 @@ # buildifier: disable=module-docstring -load("//py/private:py_semantics.bzl", _py_semantics = "semantics") def _autodetecting_py_wrapper_impl(rctx): which_python = rctx.which("python3") @@ -8,7 +7,7 @@ def _autodetecting_py_wrapper_impl(rctx): # Check if `which_python` ends up being the final binary, or it's actually a wrapper itself. exec_result = rctx.execute( - [which_python] + _py_semantics.interpreter_flags + ["-c", "import sys; import os; print(os.path.realpath(sys.executable))"], + [which_python] + ["-B", "-I", "-c", "import sys; import os; print(os.path.realpath(sys.executable))"], ) if exec_result.return_code == 0: