diff --git a/CHANGELOG.md b/CHANGELOG.md index c5bf986216..dc2419360c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,8 @@ Unreleased changes template. * (rules) APIs for creating custom rules based on the core py_binary, py_test, and py_library rules ([#1647](https://github.com/bazelbuild/rules_python/issues/1647)) +* (rules) Added {obj}`interpreter_args` attribute to `py_binary` and `py_test`, + which allows pass arguments to the interpreter before the regular args. {#v0-0-0-removed} ### Removed diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index bcbff70bec..d1905448a6 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -87,6 +87,21 @@ EXECUTABLE_ATTRS = dicts.add( IMPORTS_ATTRS, COVERAGE_ATTRS, { + "interpreter_args": lambda: attrb.StringList( + doc = """ +Arguments that are only applicable to the interpreter. + +The args an interpreter supports are specific to the interpreter. For +CPython, see https://docs.python.org/3/using/cmdline.html. + +:::{note} +Only supported for {obj}`--bootstrap_impl=script`. Ignored otherwise. +::: + +:::{versionadded} VERSION_NEXT_FEATURE +::: +""", + ), "legacy_create_init": lambda: attrb.Int( default = -1, values = [-1, 0, 1], @@ -658,6 +673,10 @@ def _create_stage1_bootstrap( python_binary_actual = venv.interpreter_actual_path if venv else "" subs = { + "%interpreter_args%": "\n".join([ + '"{}"'.format(v) + for v in ctx.attr.interpreter_args + ]), "%is_zipfile%": "1" if is_for_zip else "0", "%python_binary%": python_binary_path, "%python_binary_actual%": python_binary_actual, diff --git a/python/private/stage1_bootstrap_template.sh b/python/private/stage1_bootstrap_template.sh index 19ff763094..523210ad14 100644 --- a/python/private/stage1_bootstrap_template.sh +++ b/python/private/stage1_bootstrap_template.sh @@ -21,6 +21,11 @@ IS_ZIPFILE="%is_zipfile%" # 0 or 1 RECREATE_VENV_AT_RUNTIME="%recreate_venv_at_runtime%" +# array of strings +declare -a INTERPRETER_ARGS_FROM_TARGET=( +%interpreter_args% +) + if [[ "$IS_ZIPFILE" == "1" ]]; then # NOTE: Macs have an old version of mktemp, so we must use only the # minimal functionality of it. @@ -222,6 +227,7 @@ command=( "${interpreter_env[@]}" "$python_exe" "${interpreter_args[@]}" + "${INTERPRETER_ARGS_FROM_TARGET[@]}" "$stage2_bootstrap" "$@" ) diff --git a/tests/bootstrap_impls/BUILD.bazel b/tests/bootstrap_impls/BUILD.bazel index 8a64bf2b5b..7a5c4b46c6 100644 --- a/tests/bootstrap_impls/BUILD.bazel +++ b/tests/bootstrap_impls/BUILD.bazel @@ -124,4 +124,13 @@ sh_py_run_test( target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT, ) +py_reconfig_test( + name = "interpreter_args_test", + srcs = ["interpreter_args_test.py"], + bootstrap_impl = "script", + interpreter_args = ["-XSPECIAL=1"], + main = "interpreter_args_test.py", + target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT, +) + relative_path_test_suite(name = "relative_path_tests") diff --git a/tests/bootstrap_impls/interpreter_args_test.py b/tests/bootstrap_impls/interpreter_args_test.py new file mode 100644 index 0000000000..27744c647f --- /dev/null +++ b/tests/bootstrap_impls/interpreter_args_test.py @@ -0,0 +1,25 @@ +# Copyright 2025 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. + +import sys +import unittest + + +class InterpreterArgsTest(unittest.TestCase): + def test_interpreter_args(self): + self.assertEqual(sys._xoptions, {"SPECIAL": "1"}) + + +if __name__ == "__main__": + unittest.main()