-
-
Notifications
You must be signed in to change notification settings - Fork 649
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v2 repl-python goal #9077
v2 repl-python goal #9077
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exciting!
Could you please add V2 integration tests, e.g. GoalRuleTestBase
-style? python_test_runner_integration_test.py
and isort/rules_integration_test.py
might be good examples.
|
||
class ReplOptions(GoalSubsystem): | ||
"""Opens a REPL.""" | ||
name = 'repl2-python' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Medium term, I like the proposal to rename this to python
. For now, this is a good name because it makes clear that this is still a WIP and subject to changes.
exit_code = result.process_exit_code | ||
|
||
if exit_code == 0: | ||
console.write_stdout("REPL exited successfully") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: probably good to use periods with both these messages.
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
import logging | ||
from pathlib import Path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Semi-nit: it looks like PurePath
would work for everything in this file and I recommend changing to it. PurePath
is a superclass of Path
and has all the same methods as Path
, except for methods that actually inspect the real filesystem. For example, there is no PurePath.exists()
or PurePath.is_file()
.
Why use PurePath
? It ensures we don't accidentally leak into the filesystem when we didn't intend to and it better expresses the intent of the code, that you're solely doing path manipulation and not actually reading from the filesystem.
global_options: GlobalOptions) -> Repl: | ||
|
||
|
||
hydrated_targets = await Get[HydratedTargets](BuildFileAddresses, build_file_addresses) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead, you can directly request HydratedTargets
in the rule. It looks like you aren't using build_file_addresses
anywhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I think we need TransitiveHydratedTargets
?
fb12be7
to
7ccb304
Compare
Working on the integration test has revealed that there's room for improvement with the current test infrastructure for python tests. Once this PR is merged I want to refactor some of this code into a common Python v2 test class of some kind. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay! Thank you for getting this working! Neat how little code it takes.
For the tests, what are your thoughts on somehow feeding stdin to the REPL to confirm that it's truly interactive? I think we discussed the stdin being something like calling exit()
, which should cause the REPL to close right away. Add a timeout so that if the input doesn't work, we don't hang the test.
build_root: BuildRoot, | ||
global_options: GlobalOptions) -> PythonRepl: | ||
|
||
# note - when eric's changes are merged, the .to_address() won't be necessary |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hopefully tomorrow! Close to getting this working..finishing last parts of getting V1 to still work, which still needs to use BuildFileAddress
.
create_pex = CreatePexFromTargetClosure( | ||
addresses=python_addresses, | ||
output_filename="python-repl.pex", | ||
entry_point=None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default is None
. I think it's fine to leave this off.
repl_pex = await Get[Pex](CreatePexFromTargetClosure, create_pex) | ||
|
||
with temporary_dir(root_dir=global_options.pants_workdir, cleanup=False) as tmpdir: | ||
path_relative_to_build_root = str(PurePath(tmpdir).relative_to(build_root.path)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@codealchemy started using as_posix()
instead. It does the same thing, minus not working on Windows, and I think is nicer to read:
path_relative_to_build_root = str(PurePath(tmpdir).relative_to(build_root.path)) | |
path_relative_to_build_root = PurePath(tmpdir).relative_to(build_root.path).as_posix() |
DirectoryToMaterialize(repl_pex.directory_digest, path_prefix=path_relative_to_build_root) | ||
) | ||
|
||
full_path = str(PurePath(tmpdir, repl_pex.output_filename)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto with using as_posix
exit_code = result.process_exit_code | ||
|
||
if exit_code == 0: | ||
console.write_stdout("REPL exited successfully.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this is worth keeping, although it's fine in the short term. Logging exceptions seems helpful, but presumably it will be clear to the user once the REPL stops running.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like the idea of having a specific line of output with --v2-ui
turned on, kind of like the current SUCCESS
or FAILURE
output, that signals the pants run completed? But that hopefully can be decoupled from the implementation of individual rules.
Yeah, I think this would be a good thing to have, it will just require a little bit more work to build. I think I will try to get that chunk of work added to this PR tomorrow. |
1ee1a65
to
a59f988
Compare
exit_code = result.process_exit_code | ||
|
||
if exit_code == 0: | ||
console.write_stdout("REPL exited successfully.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like the idea of having a specific line of output with --v2-ui
turned on, kind of like the current SUCCESS
or FAILURE
output, that signals the pants run completed? But that hopefully can be decoupled from the implementation of individual rules.
download_pex_bin.DownloadedPexBin.Factory.global_instance(), | ||
] | ||
|
||
output = self.execute_rule(args=["src/python:some_lib"], additional_params=additional_params) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to mock input being sent to the repl? This was one of the biggest difficulties about testing the v1 repl. Is it possible to mock stdin being sent to interactive processes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not currently. #9108 is an issue I assigned myself to implement this work, but I don't think it's trivial to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as for the line of output, yeah, it would be nice if all v2 REPLs could use the same bit of code to output some "pants run complete" message. Whether or not that will be possible depends on whether we build some kind of new hierarchy level grouping all possible repl
goals, cf. #9104
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I was thinking rather that the v2 ui itself could output that sort of message for all goals, as opposed to v2 repls specifically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see what you mean. That's not a bad idea actually, although it's not a change i want to try to make in this commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to mock input being sent to the repl? This was one of the biggest difficulties about testing the v1 repl. Is it possible to mock stdin being sent to interactive processes?
Piping input to an integration test works fine (under pantsd too!): see
pants/tests/python/pants_test/backend/python/tasks/test_python_repl_integration.py
Lines 9 to 19 in 93c3567
@ensure_daemon | |
def test_run_repl(self): | |
# Run a repl on a library target. Avoid some known-to-choke-on interpreters. | |
command = ['repl', | |
'--python-setup-interpreter-constraints=CPython>=2.7,<3', | |
'testprojects/src/python/interpreter_selection:echo_interpreter_version_lib', | |
'--quiet'] | |
program = 'from interpreter_selection.echo_interpreter_version import say_hello; say_hello()' | |
pants_run = self.run_pants(command=command, stdin_data=program) | |
output_lines = pants_run.stdout_data.rstrip().split('\n') | |
self.assertIn('echo_interpreter_version loaded successfully.', output_lines) |
e17a5d7
to
dfe94af
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay! I replied to #9104: I think that there is a pretty straightforward way to avoid "goal per language", and it would be good to consider it before landing this.
No worries. I left some comments on that issue; I think there's still a bit of discussion to be had over exactly what v2 repl goals ought to look like. In the meantime, in the interest of getting this patch merged, would be okay to rename this goal from |
name = 'repl2-python' | ||
|
||
|
||
class PythonRepl(Goal): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that a reasonable amount of decoupling to begin heading in the direction of #9104 would be to install the repl
goal in the core with a union, and to then fill the union in the python backend.
7d95714
to
2c8ffa9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! It's neat how little code it took to get this working.
src/python/pants/rules/core/repl.py
Outdated
@union | ||
class ReplDeterminer: | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe ReplImplementation
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, to be clear, possibly in a followup this will be an option like ./pants repl2 --implementation=ipython
? For now, we're punting on the specific option because there's only one possible implementation so it's unambigous? But this gives the infrastructure for more down the road?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that's what I had in mind with this type.
5a1676d
to
93514f9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for putting in the time to generalize this! It looks like it needs a minor tweak to actually use the union: once it's green feel free to land.
src/python/pants/rules/core/repl.py
Outdated
|
||
class ReplOptions(GoalSubsystem): | ||
"""Opens a REPL.""" | ||
name = 'repl2' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does registration fail when this has the name repl
? It feels like it shouldn't...
For example: we were able to install v2 test
in the appropriate place... the only reason we couldn't for fmt
/lint
was that they had recursive options on them.
src/python/pants/rules/core/repl.py
Outdated
|
||
|
||
@union | ||
class ReplImplementation: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing is actually using this union, so this is likely to still cause "isolated backend" test failures.
I think that to actually bring the union into play, you'll need to construct an instance of the union member. That would look like taking UnionMembership
, finding "a matching repl instance" (because this hasn't been fully generalized to inspect the target types, you could do something like "taking the first one"), constructing an instance of it (PythonRepl
in this case), which will allow you to remove the targets_to_python_repl
rule.
Constructing an instance of a union member might look like:
# TODO: We're only expecting one of these right now: to further generalize this, we'll want
# to finish #4535.
repl_impl = next(union_membership.union_rules[ReplImplementation])
repl_binary = await Get[ReplBinary](ReplImplementation, repl_impl(addresses))
93514f9
to
09f46d3
Compare
09f46d3
to
fee2497
Compare
fee2497
to
4d6b5f7
Compare
This commit adds pants support for a v2 Python REPL goal, called
repl2-python
, which spawns a Python repl as a PEX build with the dependencies of the specified targets.As per the discussion summarized in #9104, this goal is consistent with the plan of attack of implementing v2 repl support generally as a set of many goals, one per supported repl, that all know how to filter source files to only the language(s) they can interpret.