Skip to content

Commit 5eb139f

Browse files
chowderrickeylev
andauthored
fix: only delete first sys.path entry in the stage-2 bootstrap if PYTHONSAFEPATH is unset or unsupported (#2418)
Unnconditionally deleting the first `sys.path` entry on the stage-2 bootstrap incorrecly removes a valid search path on Python 3.11 and above, since `PYTHONSAFEPATH` is already unconditionally set in stage-1. It should be deleted only if it is unset or unsupported. Fixes #2318 --------- Co-authored-by: Richard Levasseur <richardlev@gmail.com> Co-authored-by: Richard Levasseur <rlevasseur@google.com>
1 parent 096a04f commit 5eb139f

File tree

6 files changed

+102
-18
lines changed

6 files changed

+102
-18
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ Other changes:
100100
* (repositories): Add libs/python3.lib and pythonXY.dll to the `libpython` target
101101
defined by a repository template. This enables stable ABI builds of Python extensions
102102
on Windows (by defining Py_LIMITED_API).
103+
* (rules) `py_test` and `py_binary` targets no longer incorrectly remove the
104+
first `sys.path` entry when using {obj}`--bootstrap_impl=script`
103105

104106
{#v0-0-0-added}
105107
### Added

python/private/stage2_bootstrap_template.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44

55
import sys
66

7-
# The Python interpreter unconditionally prepends the directory containing this
7+
# By default the Python interpreter prepends the directory containing this
88
# script (following symlinks) to the import path. This is the cause of #9239,
9-
# and is a special case of #7091. We therefore explicitly delete that entry.
10-
# TODO(#7091): Remove this hack when no longer necessary.
11-
# TODO: Use sys.flags.safe_path to determine whether this removal should be
12-
# performed
13-
del sys.path[0]
9+
# and is a special case of #7091.
10+
#
11+
# Python 3.11 introduced an PYTHONSAFEPATH (-P) option that disables this
12+
# behaviour, which we set in the stage 1 bootstrap.
13+
# So the prepended entry needs to be removed only if the above option is either
14+
# unset or not supported by the interpreter.
15+
# NOTE: This can be removed when Python 3.10 and below is no longer supported
16+
if not getattr(sys.flags, "safe_path", False):
17+
del sys.path[0]
1418

1519
import contextlib
1620
import os

tests/bootstrap_impls/BUILD.bazel

+6-12
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,10 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
15-
load("//python/private:util.bzl", "IS_BAZEL_7_OR_HIGHER") # buildifier: disable=bzl-visibility
1614
load("//tests/support:sh_py_run_test.bzl", "py_reconfig_test", "sh_py_run_test")
15+
load("//tests/support:support.bzl", "SUPPORTS_BOOTSTRAP_SCRIPT")
1716
load(":venv_relative_path_tests.bzl", "relative_path_test_suite")
1817

19-
_SUPPORTS_BOOTSTRAP_SCRIPT = select({
20-
"@platforms//os:windows": ["@platforms//:incompatible"],
21-
"//conditions:default": [],
22-
}) if IS_BAZEL_7_OR_HIGHER else ["@platforms//:incompatible"]
23-
2418
sh_py_run_test(
2519
name = "run_binary_zip_no_test",
2620
build_python_zip = "no",
@@ -41,7 +35,7 @@ sh_py_run_test(
4135
build_python_zip = "yes",
4236
py_src = "bin.py",
4337
sh_src = "run_binary_zip_yes_test.sh",
44-
target_compatible_with = _SUPPORTS_BOOTSTRAP_SCRIPT,
38+
target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT,
4539
)
4640

4741
sh_py_run_test(
@@ -50,7 +44,7 @@ sh_py_run_test(
5044
build_python_zip = "no",
5145
py_src = "bin.py",
5246
sh_src = "run_binary_zip_no_test.sh",
53-
target_compatible_with = _SUPPORTS_BOOTSTRAP_SCRIPT,
47+
target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT,
5448
)
5549

5650
py_reconfig_test(
@@ -60,7 +54,7 @@ py_reconfig_test(
6054
env = {"BOOTSTRAP": "script"},
6155
imports = ["./USER_IMPORT/site-packages"],
6256
main = "sys_path_order_test.py",
63-
target_compatible_with = _SUPPORTS_BOOTSTRAP_SCRIPT,
57+
target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT,
6458
)
6559

6660
py_reconfig_test(
@@ -77,7 +71,7 @@ sh_py_run_test(
7771
bootstrap_impl = "script",
7872
py_src = "bin.py",
7973
sh_src = "inherit_pythonsafepath_env_test.sh",
80-
target_compatible_with = _SUPPORTS_BOOTSTRAP_SCRIPT,
74+
target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT,
8175
)
8276

8377
sh_py_run_test(
@@ -86,7 +80,7 @@ sh_py_run_test(
8680
imports = ["./MARKER"],
8781
py_src = "call_sys_exe.py",
8882
sh_src = "sys_executable_inherits_sys_path_test.sh",
89-
target_compatible_with = _SUPPORTS_BOOTSTRAP_SCRIPT,
83+
target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT,
9084
)
9185

9286
relative_path_test_suite(name = "relative_path_tests")

tests/no_unsafe_paths/BUILD.bazel

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright 2024 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
load("//tests/support:sh_py_run_test.bzl", "py_reconfig_test")
15+
load("//tests/support:support.bzl", "SUPPORTS_BOOTSTRAP_SCRIPT")
16+
17+
py_reconfig_test(
18+
name = "no_unsafe_paths_3.10_test",
19+
srcs = ["test.py"],
20+
bootstrap_impl = "script",
21+
main = "test.py",
22+
python_version = "3.10",
23+
target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT,
24+
)
25+
26+
py_reconfig_test(
27+
name = "no_unsafe_paths_3.11_test",
28+
srcs = ["test.py"],
29+
bootstrap_impl = "script",
30+
main = "test.py",
31+
python_version = "3.11",
32+
target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT,
33+
)

tests/no_unsafe_paths/test.py

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright 2024 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
import sys
17+
import unittest
18+
19+
20+
class NoUnsafePathsTest(unittest.TestCase):
21+
def test_no_unsafe_paths_in_search_path(self):
22+
# Based on sys.path documentation, the first item added is the zip
23+
# archive
24+
# (see: https://docs.python.org/3/library/sys_path_init.html)
25+
#
26+
# We can use this as a marker to verify that during bootstrapping,
27+
# (1) no unexpected paths were prepended, and (2) no paths were
28+
# accidentally dropped.
29+
#
30+
major, minor, *_ = sys.version_info
31+
archive = f"python{major}{minor}.zip"
32+
33+
# < Python 3.11 behaviour
34+
if (major, minor) < (3, 11):
35+
# Because of https://github.com/bazelbuild/rules_python/blob/0.39.0/python/private/stage2_bootstrap_template.py#L415-L436
36+
self.assertEqual(os.path.dirname(sys.argv[0]), sys.path[0])
37+
self.assertEqual(os.path.basename(sys.path[1]), archive)
38+
# >= Python 3.11 behaviour
39+
else:
40+
self.assertEqual(os.path.basename(sys.path[0]), archive)
41+
42+
43+
if __name__ == '__main__':
44+
unittest.main()

tests/support/support.bzl

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
# rules_testing or as config_setting values, which don't support Label in some
2020
# places.
2121

22+
load("//python/private:util.bzl", "IS_BAZEL_7_OR_HIGHER") # buildifier: disable=bzl-visibility
23+
2224
MAC = Label("//tests/support:mac")
2325
MAC_X86_64 = Label("//tests/support:mac_x86_64")
2426
LINUX = Label("//tests/support:linux")
@@ -39,3 +41,8 @@ PRECOMPILE_SOURCE_RETENTION = str(Label("//python/config_settings:precompile_sou
3941
PYC_COLLECTION = str(Label("//python/config_settings:pyc_collection"))
4042
PYTHON_VERSION = str(Label("//python/config_settings:python_version"))
4143
VISIBLE_FOR_TESTING = str(Label("//python/private:visible_for_testing"))
44+
45+
SUPPORTS_BOOTSTRAP_SCRIPT = select({
46+
"@platforms//os:windows": ["@platforms//:incompatible"],
47+
"//conditions:default": [],
48+
}) if IS_BAZEL_7_OR_HIGHER else ["@platforms//:incompatible"]

0 commit comments

Comments
 (0)