Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
34cd838
Add support for REPLs
philsc Apr 1, 2025
d0dce4f
Merge remote-tracking branch 'upstream/main' into HEAD
philsc Apr 8, 2025
06cef67
Remove the macro-generator
philsc Apr 8, 2025
6c2dd35
revert some unnecessary changes
philsc Apr 8, 2025
fc9cdeb
Generate main.py so we can get independent of ipython
philsc Apr 8, 2025
a2be313
precommit
philsc Apr 8, 2025
f6dc039
fix a couple of comments
philsc Apr 8, 2025
b0b83ab
Delete unnecessary code
philsc Apr 8, 2025
402faf5
Undo bootstrap hook for now
philsc Apr 8, 2025
66d216d
prevent import leaks
philsc Apr 8, 2025
ac5d58c
simplify design a bit
philsc Apr 8, 2025
9c9c8b0
clean up some more
philsc Apr 8, 2025
1d3de79
run pre-commit
philsc Apr 8, 2025
d8b1125
add docstring
philsc Apr 8, 2025
e05f9af
clean up naming a tad
philsc Apr 8, 2025
9121abd
removed dead code
philsc Apr 8, 2025
8635890
Merge remote-tracking branch 'upstream/main' into HEAD
philsc Apr 20, 2025
8a00d32
Merge remote-tracking branch 'upstream/main' into HEAD
philsc Apr 28, 2025
d48433b
Incorporate some of the feedback
philsc Apr 28, 2025
2aa2a27
more feedback incorporated
philsc Apr 28, 2025
2c6f544
add tests
philsc Apr 28, 2025
200e5e8
precommit
philsc Apr 28, 2025
5f46437
add tests
philsc Apr 28, 2025
98064dc
precommit
philsc Apr 28, 2025
0ae8a81
Merge remote-tracking branch 'origin/main' into HEAD
philsc May 4, 2025
32c436e
Fix the missing banner on startup
philsc May 4, 2025
863e970
precommit
philsc May 4, 2025
b769fb1
add doc
philsc May 4, 2025
44dda2f
add some docs
philsc May 4, 2025
d9bed35
flesh out docs a bit
philsc May 5, 2025
f1a68ab
Merge branch 'main' into dev/philsc/repl
aignas May 15, 2025
520c980
docs changes
aignas May 15, 2025
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
2 changes: 2 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ build:rtd --enable_bzlmod
common --incompatible_python_disallow_native_rules

build --lockfile_mode=update

run:repl --//python/config_settings:bootstrap_impl=script //python/bin:repl
26 changes: 26 additions & 0 deletions python/bin/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
load("//python/private:interpreter.bzl", _interpreter_binary = "interpreter_binary")
load("//python:py_binary.bzl", "py_binary")

filegroup(
name = "distribution",
Expand All @@ -22,3 +23,28 @@ label_flag(
name = "python_src",
build_setting_default = "//python:none",
)

py_binary(
name = "repl",
srcs = ["repl.py"],
deps = [
":repl_dep",
":repl_lib_dep",
],
visibility = ["//visibility:public"],
)

# The user can modify this flag to make arbitrary libraries available for import
# on the REPL. Anything that exposes PyInfo can be used here.
label_flag(
name = "repl_dep",
build_setting_default = "//python:none",
)

# The user can modify this flag to make additional libraries available
# specifically for the purpose of interacting with the REPL. For example, point
# this at ipython in your .bazelrc file.
label_flag(
name = "repl_lib_dep",
build_setting_default = "//python:none",
)
28 changes: 28 additions & 0 deletions python/bin/repl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import os
from pathlib import Path

def start_repl():
# Simulate Python's behavior when a valid startup script is defined by the
# PYTHONSTARTUP variable. If this file path fails to load, print the error
# and revert to the default behavior.
if (startup_file := os.getenv("PYTHONSTARTUP")):
try:
source_code = Path(startup_file).read_text()
except Exception as error:
print(f"{type(error).__name__}: {error}")
else:
compiled_code = compile(source_code, filename=startup_file, mode="exec")
eval(compiled_code, {})

try:
# If the user has made ipython available somehow (e.g. via
# `repl_lib_dep`), then use it.
import IPython
IPython.start_ipython()
except ModuleNotFoundError:
# Fall back to the default shell.
import code
code.interact(local=dict(globals(), **locals()))

if __name__ == "__main__":
start_repl()
31 changes: 29 additions & 2 deletions python/private/py_library_macro.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,35 @@
"""Implementation of macro-half of py_library rule."""

load(":py_library_rule.bzl", py_library_rule = "py_library")
load(":py_binary_rule.bzl", py_binary_rule = "py_binary")

# The py_library's attributes we don't want to forward to auto-generated
# targets.
_LIBRARY_ONLY_ATTRS = [
"srcs",
"deps",
"data",
"imports",
]

# A wrapper macro is used to avoid any user-observable changes between a
# rule and macro. It also makes generator_function look as expected.
def py_library(**kwargs):
py_library_rule(**kwargs)
def py_library(name, **kwargs):
library_only_attrs = {
attr: kwargs.pop(attr, None)
for attr in _LIBRARY_ONLY_ATTRS
}
py_library_rule(
name = name,
**(library_only_attrs | kwargs)
)
py_binary_rule(
name = "%s.repl" % name,
srcs = [],
main_module = "python.bin.repl",
deps = [
":%s" % name,
"@rules_python//python/bin:repl",
],
**kwargs
)
3 changes: 3 additions & 0 deletions python/private/sentinel.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Label attributes with defaults cannot accept None, otherwise they fall
back to using the default. A sentinel allows detecting an intended None value.
"""

load("//python:py_info.bzl", "PyInfo")

SentinelInfo = provider(
doc = "Indicates this was the sentinel target.",
fields = [],
Expand All @@ -29,6 +31,7 @@ def _sentinel_impl(ctx):
SentinelInfo(),
# Also output ToolchainInfo to allow it to be used for noop toolchains
platform_common.ToolchainInfo(),
PyInfo(transitive_sources=depset()),
]

sentinel = rule(implementation = _sentinel_impl)
4 changes: 4 additions & 0 deletions python/private/stage1_bootstrap_template.sh
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ command=(
"$@"
)

# Point libedit/readline at the correct terminfo databases.
# https://github.com/astral-sh/python-build-standalone/blob/f0abfc9cb1f6a985fc5561cf5435f7f6e8a64e5b/docs/quirks.rst#backspace-key-doesnt-work-in-python-repl
export TERMINFO_DIRS=/etc/terminfo:/lib/terminfo:/usr/share/terminfo

# We use `exec` instead of a child process so that signals sent directly (e.g.
# using `kill`) to this process (the PID seen by the calling process) are
# received by the Python process. Otherwise, this process receives the signal
Expand Down
11 changes: 10 additions & 1 deletion python/private/stage2_bootstrap_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,16 @@ def main():
print_verbose("initial environ:", mapping=os.environ)
print_verbose("initial sys.path:", values=sys.path)

if bool(os.environ.get("RULES_PYTHON_BOOTSTRAP_REPL")):
global MAIN_PATH
global MAIN_MODULE
MAIN_PATH = ""
# TODO(philsc): Can we point at python.bin.repl instead? That would mean
# adding it as a dependency to all binaries.
MAIN_MODULE = "code"
# Prevent subprocesses from also entering the REPL.
del os.environ["RULES_PYTHON_BOOTSTRAP_REPL"]

main_rel_path = None
# todo: things happen to work because find_runfiles_root
# ends up using stage2_bootstrap, and ends up computing the proper
Expand Down Expand Up @@ -438,7 +448,6 @@ def main():
_run_py_path(main_filename, args=sys.argv[1:])
else:
_run_py_module(MAIN_MODULE)
sys.exit(0)


main()