Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions news/native-bootstrap-imports.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(bootstrap) Bazel 7 native Python targets can run with both the Python and
shell bootstraps without crashing on unexpanded launcher placeholders. Their
declared import roots and repository imports are available when the launcher
falls back to executing the main file directly.
33 changes: 32 additions & 1 deletion python/private/python_bootstrap_template.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import subprocess
_STAGE2_BOOTSTRAP_SENTINEL = "%stage2" + "_bootstrap%"
# runfiles-root-relative path
STAGE2_BOOTSTRAP="%stage2_bootstrap%"
_USING_STAGE2_BOOTSTRAP_FALLBACK = False

# NOTE: The fallback logic from stage2_bootstrap to main is only present
# as a courtesy for an older, unsupported, configuration. It can be removed
Expand All @@ -49,6 +50,7 @@ if STAGE2_BOOTSTRAP == _STAGE2_BOOTSTRAP_SENTINEL:
_main = "%main%"
if _main != _MAIN_SENTINEL and _main:
STAGE2_BOOTSTRAP = _main
_USING_STAGE2_BOOTSTRAP_FALLBACK = True
else:
STAGE2_BOOTSTRAP = ""

Expand Down Expand Up @@ -98,7 +100,13 @@ else:
RUNTIME_VENV_SYMLINKS = """
%runtime_venv_symlinks%
""".strip().split("\n")
RUNTIME_VENV_SYMLINKS = dict(line.split("|") for line in RUNTIME_VENV_SYMLINKS if line)
# Native rules leave this newer placeholder unchanged. They have no runtime
# venv symlinks; only parse entries when the placeholder was expanded.
_RUNTIME_VENV_SYMLINKS_SENTINEL = "%runtime_venv" + "_symlinks%"
if RUNTIME_VENV_SYMLINKS == [_RUNTIME_VENV_SYMLINKS_SENTINEL]:
RUNTIME_VENV_SYMLINKS = {}
else:
RUNTIME_VENV_SYMLINKS = dict(line.split("|") for line in RUNTIME_VENV_SYMLINKS if line)

ADDITIONAL_INTERPRETER_ARGS = os.environ.get("RULES_PYTHON_ADDITIONAL_INTERPRETER_ARGS", "")
EXTRACT_ROOT = os.environ.get("RULES_PYTHON_EXTRACT_ROOT")
Expand Down Expand Up @@ -614,6 +622,29 @@ def main():

print_verbose("runfiles root:", runfiles_root)

if _USING_STAGE2_BOOTSTRAP_FALLBACK:
# Native rules expand %imports% and %import_all%, but do not generate
# stage 2 to set up sys.path. Preserve the native launcher's import order
# when executing the main file directly; leave regular stage-2 launches
# unchanged. These paths are runfiles-relative, not checkout-relative.
imports = "%imports%"
python_paths = [runfiles_root]
python_paths.extend(join(runfiles_root, path) for path in imports.split(":") if path)
if "%import_all%" == "True":
python_paths.extend(
join(runfiles_root, repo) for repo in sorted(os.listdir(runfiles_root))
if os.path.isdir(join(runfiles_root, repo)))
else:
python_paths.append(join(runfiles_root, WORKSPACE_NAME))
python_paths = [get_windows_path_with_unc_prefix(path) for path in python_paths]
existing_pythonpath = os.environ.get("PYTHONPATH")
if existing_pythonpath:
python_paths.extend(existing_pythonpath.split(os.pathsep))
new_env["PYTHONPATH"] = os.pathsep.join(dict.fromkeys(python_paths))
if IS_WINDOWS:
new_env["PYTHONPATH"] = new_env["PYTHONPATH"].replace("/", os.sep)
print_verbose("native fallback PYTHONPATH:", new_env["PYTHONPATH"])

if os.environ.get("RULES_PYTHON_TESTING_TELL_RUNFILES_ROOT"):
new_env["RULES_PYTHON_TESTING_RUNFILES_ROOT"] = runfiles_root

Expand Down
50 changes: 50 additions & 0 deletions python/private/stage1_bootstrap_template.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ function _symlink() {
# runfiles-root-relative path
STAGE2_BOOTSTRAP="%stage2_bootstrap%"

# Like the Python bootstrap, fall back to the main file for native rules,
# which expand %main% but leave newer stage-2 placeholders unchanged.
_STAGE2_BOOTSTRAP_SENTINEL="%stage2""_bootstrap%"
_INTERPRETER_ARGS_SENTINEL="%interpreter""_args%"
USING_STAGE2_BOOTSTRAP_FALLBACK=0
if [[ "$STAGE2_BOOTSTRAP" == "$_STAGE2_BOOTSTRAP_SENTINEL" ]]; then
_MAIN_SENTINEL="%main""%"
main="%main%"
if [[ "$main" == "$_MAIN_SENTINEL" || -z "$main" ]]; then
echo >&2 "ERROR: %stage2_bootstrap% (or %main%) was not substituted."
exit 1
fi
# Native external targets use workspace/../repo/main.py. The workspace
# directory need not exist, so remove that prefix before accessing the file.
STAGE2_BOOTSTRAP="${main#"%workspace_name%/../"}"
USING_STAGE2_BOOTSTRAP_FALLBACK=1
fi

# runfiles-root-relative path to python interpreter to use.
# This is the `bin/python3` path in the binary's venv.
PYTHON_BINARY='%python_binary%'
Expand Down Expand Up @@ -60,6 +78,11 @@ declare -a INTERPRETER_ARGS_FROM_TARGET=(
%interpreter_args%
)

if [[ "$USING_STAGE2_BOOTSTRAP_FALLBACK" == "1" &&
"${INTERPRETER_ARGS_FROM_TARGET[*]}" == "$_INTERPRETER_ARGS_SENTINEL" ]]; then
INTERPRETER_ARGS_FROM_TARGET=()
fi

if [[ "$IS_ZIPFILE" == "1" ]]; then
# NOTE: Macs have an old version of mktemp, so we must use only the
# minimal functionality of it.
Expand Down Expand Up @@ -309,6 +332,33 @@ fi

export RUNFILES_DIR

if [[ "$USING_STAGE2_BOOTSTRAP_FALLBACK" == "1" ]]; then
# Restore native import order when there is no stage 2 to set up sys.path.
# Declared imports are used as provided, without workspace-specific paths.
declare -a python_paths=("$RUNFILES_DIR")
imports="%imports%"
IFS=: read -r -a import_paths <<< "$imports"
for import_path in "${import_paths[@]}"; do
python_paths+=("$RUNFILES_DIR/$import_path")
done

if [[ "%import_all%" == "True" ]]; then
for repo_root in "$RUNFILES_DIR"/*; do
if [[ -d "$repo_root" ]]; then
python_paths+=("$repo_root")
fi
done
else
python_paths+=("$RUNFILES_DIR/%workspace_name%")
fi

python_path="$(IFS=:; echo "${python_paths[*]}")"
if [[ -n "${PYTHONPATH:-}" ]]; then
python_path+=":$PYTHONPATH"
fi
interpreter_env+=("PYTHONPATH=$python_path")
fi

if command -v env >/dev/null 2>&1; then
ENV_CMD="env"
else
Expand Down
17 changes: 17 additions & 0 deletions tests/bootstrap_impls/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@ load("//tests/support:support.bzl", "SUPPORTS_BOOTSTRAP_SCRIPT")
load("//tests/support/pytest_test:pytest_test.bzl", "pytest_test")
load(":venv_relative_path_tests.bzl", "relative_path_test_suite")

py_test(
name = "native_bootstrap_test",
srcs = ["native_bootstrap_test.py"],
data = ["//python/private:python_bootstrap_template.txt"],
deps = ["//python/runfiles"],
)

py_test(
name = "native_bootstrap_script_test",
srcs = ["native_bootstrap_test.py"],
data = ["//python/private:stage1_bootstrap_template"],
env = {"BOOTSTRAP": "script"},
main = "native_bootstrap_test.py",
target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT,
deps = ["//python/runfiles"],
)

py_reconfig_binary(
name = "bootstrap_script_zipapp_bin",
srcs = ["bin.py"],
Expand Down
Loading
Loading