From 5c6af5e6f89cfce51d0c27b6812a6566db7f984e Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 15 Mar 2025 17:39:16 -0700 Subject: [PATCH 1/2] wip: basic interpreter_args impl --- CHANGELOG.md | 2 ++ python/private/py_executable.bzl | 19 +++++++++++++++ python/private/stage1_bootstrap_template.sh | 6 +++++ tests/bootstrap_impls/BUILD.bazel | 9 ++++++++ .../bootstrap_impls/interpreter_args_test.py | 23 +++++++++++++++++++ 5 files changed, 59 insertions(+) create mode 100644 tests/bootstrap_impls/interpreter_args_test.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9029794ffc..b03a6e80b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,8 @@ Unreleased changes template. * (pypi) Direct HTTP urls for wheels and sdists are now supported when using {obj}`experimental_index_url` (bazel downloader). Partially fixes [#2363](https://github.com/bazelbuild/rules_python/issues/2363). +* (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 f85f242bba..d6a1a5a45e 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], @@ -664,6 +679,10 @@ def _create_stage1_bootstrap( "%recreate_venv_at_runtime%": str(int(venv.recreate_venv_at_runtime)) if venv else "0", "%target%": str(ctx.label), "%workspace_name%": ctx.workspace_name, + "%interpreter_args%": "\n".join([ + '"{}"'.format(v) + for v in ctx.attr.interpreter_args + ]), } if stage2_bootstrap: 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..985d3e195f --- /dev/null +++ b/tests/bootstrap_impls/interpreter_args_test.py @@ -0,0 +1,23 @@ +# 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() From 422dfd893946274136cf4be8c19bf5ad050b12fe Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 16 Mar 2025 19:29:17 -0700 Subject: [PATCH 2/2] fix style --- python/private/py_executable.bzl | 8 ++++---- tests/bootstrap_impls/interpreter_args_test.py | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 27a6bda4c6..d1905448a6 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -673,16 +673,16 @@ 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, "%recreate_venv_at_runtime%": str(int(venv.recreate_venv_at_runtime)) if venv else "0", "%target%": str(ctx.label), "%workspace_name%": ctx.workspace_name, - "%interpreter_args%": "\n".join([ - '"{}"'.format(v) - for v in ctx.attr.interpreter_args - ]), } if stage2_bootstrap: diff --git a/tests/bootstrap_impls/interpreter_args_test.py b/tests/bootstrap_impls/interpreter_args_test.py index 985d3e195f..27744c647f 100644 --- a/tests/bootstrap_impls/interpreter_args_test.py +++ b/tests/bootstrap_impls/interpreter_args_test.py @@ -15,9 +15,11 @@ import sys import unittest + class InterpreterArgsTest(unittest.TestCase): def test_interpreter_args(self): self.assertEqual(sys._xoptions, {"SPECIAL": "1"}) + if __name__ == "__main__": unittest.main()