Add a REPL
How to add a new implementation to the
repl goal opens up an interactive Read-Eval-Print Loop that runs in the foreground.
Typically, the REPL is loaded with the transitive closure of the files and targets that the user provided, so that users may import their code and resources in the REPL.
This guide walks through adding a simple
replimplementation for Bash that runs
/bin/bashand ensures all relevant files are included in the temporary directory. See here for the final implementation.
1. Install your REPL
There are several ways for Pants to install your REPL. See Installing tools.
In this example, we simply find the program
bash on the user's machine, but often you will want to install a tool like Ammonite or iPython instead.
You may want to also add options for your REPL implementation, such as allowing users to change the version of the tool. See Options and subsystems.
2. Set up a subclass of
ReplImplementation and define the class property
name: str with the name of your REPL, e.g.
"ipython". Users can then set the option
--repl-shell to this option to choose your REPL implementation.
from pants.core.goals.repl import ReplImplementation class BashRepl(ReplImplementation): name = "bash"
Then, register your new
ReplImplementation with a
UnionRule so that Pants knows your REPL implementation exists:
from pants.engine.rules import collect_rules from pants.engine.unions import UnionRule ... def rules(): return [ *collect_rules(), UnionRule(ReplImplementation, BashRepl), ]
3. Create a rule for your REPL logic
Your rule should take as a parameter the
ReplImplementation from Step 2, which has a field
targets: Targets containing the transitive closure of the targets/files provided by the user. You can then filter these targets to only include the
Sources you care about.
Your rule should return
ReplRequest, which has the fields
args: Iterable[str], and
extra_env: Optional[Mapping[str, str]].
ReplRequest will get converted into an
InteractiveProcess that will run in the foreground.
The process will run in a temporary directory in the build root, which means that the script/program can access files that would normally need to be declared by adding a
resources target to the
The process's environment will not be hermetic, meaning that it will inherit the environment used by the
./pants process. Any values you set in
extra_env will add or update the specified environment variables.
from dataclasses import dataclass from pants.core.goals.repl import ReplRequest from pants.core.target_types import FileSourceField, ResourceSourceField from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest from pants.engine.rules import Get, rule from pants.engine.target import SourcesField from pants.util.logging import LogLevel ... @rule(level=LogLevel.DEBUG) async def create_bash_repl_request(repl: BashRepl) -> ReplRequest: # First, we find the `bash` program. bash_program_paths = await Get( BinaryPaths, BinaryPathRequest(binary_name="bash", search_path=("/bin", "/usr/bin")), ) if not bash_program_paths.first_path: raise EnvironmentError("Could not find the `bash` program on /bin or /usr/bin.") bash_program = bash_program_paths.first_path # `repl.targets` already includes the transitive closure of the input targets. We filter out # irrelevant soures. sources = await Get( SourceFiles, SourceFilesRequest( (tgt.get(SourcesField) for tgt in repl.targets), for_sources_types=(BashSourceField, FileSourceField, ResourceSourceField), ), ) return ReplRequest( digest=sources.snapshot.digest, args=(bash_program.exe,) )
If you use any relative paths in
extra_env, you should call
repl.in_chroot("./example_relative_path") on the values. This ensures that you run on the correct file in the temporary directory created by Pants.
Finally, update your plugin's
register.py to activate this file's rules.
from bash import repl def rules(): return [*repl.rules()]
Now, when you run
./pants repl --shell=bash ::, your new REPL should be used.
Updated over 1 year ago