feat(binary/test): add interpreter_args attribute (#2669)
Today, there's way to control what startup args are used for the
interpreter.
To fix, add an `interpreter_args` attribute. These are written into the
bootstrap.
This is only implemented for the bootstrap=script method
Fixes https://github.com/bazelbuild/rules_python/issues/2668
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c5bf986..dc24193 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -94,6 +94,8 @@
* (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 bcbff70..d190544 100644
--- a/python/private/py_executable.bzl
+++ b/python/private/py_executable.bzl
@@ -87,6 +87,21 @@
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 @@
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 19ff763..523210a 100644
--- a/python/private/stage1_bootstrap_template.sh
+++ b/python/private/stage1_bootstrap_template.sh
@@ -21,6 +21,11 @@
# 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 @@
"${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 8a64bf2..7a5c4b4 100644
--- a/tests/bootstrap_impls/BUILD.bazel
+++ b/tests/bootstrap_impls/BUILD.bazel
@@ -124,4 +124,13 @@
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 0000000..27744c6
--- /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()