From e119898ca335704f099fb32a87c949199dedf0f2 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Wed, 5 Feb 2020 14:57:41 -0800 Subject: [PATCH 01/11] Python repl --- src/python/pants/backend/python/register.py | 2 + src/python/pants/backend/python/rules/repl.py | 82 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/python/pants/backend/python/rules/repl.py diff --git a/src/python/pants/backend/python/register.py b/src/python/pants/backend/python/register.py index a24c86deb6b..8902c5e1d43 100644 --- a/src/python/pants/backend/python/register.py +++ b/src/python/pants/backend/python/register.py @@ -11,6 +11,7 @@ prepare_chrooted_python_sources, python_create_binary, python_test_runner, + repl, run_setup_py, ) from pants.backend.python.subsystems import python_native_code, subprocess_environment @@ -101,6 +102,7 @@ def rules(): *python_test_runner.rules(), *python_create_binary.rules(), *python_native_code.rules(), + *repl.rules(), *run_setup_py.rules(), *subprocess_environment.rules(), ) diff --git a/src/python/pants/backend/python/rules/repl.py b/src/python/pants/backend/python/rules/repl.py new file mode 100644 index 00000000000..7d57575d023 --- /dev/null +++ b/src/python/pants/backend/python/rules/repl.py @@ -0,0 +1,82 @@ +# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +import logging +from pathlib import Path + +from pants.backend.python.rules.pex import Pex +from pants.backend.python.rules.pex_from_target_closure import CreatePexFromTargetClosure +from pants.base.build_root import BuildRoot +from pants.engine.addressable import BuildFileAddresses +from pants.engine.console import Console +from pants.engine.fs import DirectoryToMaterialize, Workspace +from pants.engine.goal import Goal, GoalSubsystem +from pants.engine.interactive_runner import InteractiveProcessRequest, InteractiveRunner +from pants.engine.legacy.graph import HydratedTargets +from pants.engine.legacy.structs import PythonTargetAdaptor +from pants.engine.rules import goal_rule +from pants.engine.selectors import Get +from pants.option.global_options import GlobalOptions +from pants.util.contextutil import temporary_dir + + +logger = logging.getLogger(__name__) + + +class ReplOptions(GoalSubsystem): + """Opens a REPL.""" + name = 'repl2-python' + + +class Repl(Goal): + subsystem_cls = ReplOptions + + +@goal_rule +async def run_python_repl( + console: Console, + workspace: Workspace, + runner: InteractiveRunner, + build_file_addresses: BuildFileAddresses, + build_root: BuildRoot, + global_options: GlobalOptions) -> Repl: + + + hydrated_targets = await Get[HydratedTargets](BuildFileAddresses, build_file_addresses) + python_build_file_addresses = BuildFileAddresses( + ht.address for ht in hydrated_targets.dependencies if isinstance(ht.adaptor, PythonTargetAdaptor) + ) + + create_pex = CreatePexFromTargetClosure( + build_file_addresses=python_build_file_addresses, + output_filename="python-repl.pex", + entry_point=None, + ) + + 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(Path(tmpdir).relative_to(build_root.path)) + workspace.materialize_directory( + DirectoryToMaterialize(repl_pex.directory_digest, path_prefix=path_relative_to_build_root) + ) + + full_path = str(Path(tmpdir, repl_pex.output_filename)) + run_request = InteractiveProcessRequest( + argv=(full_path,), + run_in_workspace=True, + ) + result = runner.run_local_interactive_process(run_request) + exit_code = result.process_exit_code + + if exit_code == 0: + console.write_stdout("REPL exited successfully") + else: + console.write_stdout(f"REPL exited with error: {exit_code}") + return Repl(exit_code) + + +def rules(): + return [ + run_python_repl, + ] From 00d740ae23253638d19d803dc63e9c8241bdd02c Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Thu, 6 Feb 2020 11:27:47 -0800 Subject: [PATCH 02/11] Respond to PR comments --- src/python/pants/backend/python/rules/repl.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/python/pants/backend/python/rules/repl.py b/src/python/pants/backend/python/rules/repl.py index 7d57575d023..b4e09bcb87d 100644 --- a/src/python/pants/backend/python/rules/repl.py +++ b/src/python/pants/backend/python/rules/repl.py @@ -2,7 +2,7 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). import logging -from pathlib import Path +from pathlib import PurePath from pants.backend.python.rules.pex import Pex from pants.backend.python.rules.pex_from_target_closure import CreatePexFromTargetClosure @@ -12,7 +12,7 @@ from pants.engine.fs import DirectoryToMaterialize, Workspace from pants.engine.goal import Goal, GoalSubsystem from pants.engine.interactive_runner import InteractiveProcessRequest, InteractiveRunner -from pants.engine.legacy.graph import HydratedTargets +from pants.engine.legacy.graph import TransitiveHydratedTargets from pants.engine.legacy.structs import PythonTargetAdaptor from pants.engine.rules import goal_rule from pants.engine.selectors import Get @@ -37,14 +37,12 @@ async def run_python_repl( console: Console, workspace: Workspace, runner: InteractiveRunner, - build_file_addresses: BuildFileAddresses, + targets: TransitiveHydratedTargets, build_root: BuildRoot, global_options: GlobalOptions) -> Repl: - - hydrated_targets = await Get[HydratedTargets](BuildFileAddresses, build_file_addresses) python_build_file_addresses = BuildFileAddresses( - ht.address for ht in hydrated_targets.dependencies if isinstance(ht.adaptor, PythonTargetAdaptor) + ht.address for ht in targets.closure if isinstance(ht.adaptor, PythonTargetAdaptor) ) create_pex = CreatePexFromTargetClosure( @@ -56,12 +54,12 @@ async def run_python_repl( 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(Path(tmpdir).relative_to(build_root.path)) + path_relative_to_build_root = str(PurePath(tmpdir).relative_to(build_root.path)) workspace.materialize_directory( DirectoryToMaterialize(repl_pex.directory_digest, path_prefix=path_relative_to_build_root) ) - full_path = str(Path(tmpdir, repl_pex.output_filename)) + full_path = str(PurePath(tmpdir, repl_pex.output_filename)) run_request = InteractiveProcessRequest( argv=(full_path,), run_in_workspace=True, @@ -70,9 +68,9 @@ async def run_python_repl( exit_code = result.process_exit_code if exit_code == 0: - console.write_stdout("REPL exited successfully") + console.write_stdout("REPL exited successfully.") else: - console.write_stdout(f"REPL exited with error: {exit_code}") + console.write_stdout(f"REPL exited with error: {exit_code}.") return Repl(exit_code) From b067bd304769ce175b828f839bf5232b6b2a18e4 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Thu, 6 Feb 2020 17:01:14 -0800 Subject: [PATCH 03/11] Rename Repl -> PythonRepl --- src/python/pants/backend/python/rules/repl.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/python/pants/backend/python/rules/repl.py b/src/python/pants/backend/python/rules/repl.py index b4e09bcb87d..07c6b62a1a1 100644 --- a/src/python/pants/backend/python/rules/repl.py +++ b/src/python/pants/backend/python/rules/repl.py @@ -23,13 +23,13 @@ logger = logging.getLogger(__name__) -class ReplOptions(GoalSubsystem): +class PythonReplOptions(GoalSubsystem): """Opens a REPL.""" name = 'repl2-python' -class Repl(Goal): - subsystem_cls = ReplOptions +class PythonRepl(Goal): + subsystem_cls = PythonReplOptions @goal_rule @@ -39,7 +39,7 @@ async def run_python_repl( runner: InteractiveRunner, targets: TransitiveHydratedTargets, build_root: BuildRoot, - global_options: GlobalOptions) -> Repl: + global_options: GlobalOptions) -> PythonRepl: python_build_file_addresses = BuildFileAddresses( ht.address for ht in targets.closure if isinstance(ht.adaptor, PythonTargetAdaptor) @@ -71,7 +71,7 @@ async def run_python_repl( console.write_stdout("REPL exited successfully.") else: console.write_stdout(f"REPL exited with error: {exit_code}.") - return Repl(exit_code) + return PythonRepl(exit_code) def rules(): From 66107be45657357e9103bd3dc53beb8971e08ed2 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Fri, 7 Feb 2020 16:17:45 -0800 Subject: [PATCH 04/11] Avoid BuildFileAddresses --- src/python/pants/backend/python/rules/repl.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/python/pants/backend/python/rules/repl.py b/src/python/pants/backend/python/rules/repl.py index 07c6b62a1a1..c5d01231639 100644 --- a/src/python/pants/backend/python/rules/repl.py +++ b/src/python/pants/backend/python/rules/repl.py @@ -7,7 +7,7 @@ from pants.backend.python.rules.pex import Pex from pants.backend.python.rules.pex_from_target_closure import CreatePexFromTargetClosure from pants.base.build_root import BuildRoot -from pants.engine.addressable import BuildFileAddresses +from pants.engine.addressable import Addresses from pants.engine.console import Console from pants.engine.fs import DirectoryToMaterialize, Workspace from pants.engine.goal import Goal, GoalSubsystem @@ -41,12 +41,13 @@ async def run_python_repl( build_root: BuildRoot, global_options: GlobalOptions) -> PythonRepl: - python_build_file_addresses = BuildFileAddresses( - ht.address for ht in targets.closure if isinstance(ht.adaptor, PythonTargetAdaptor) + # note - when eric's changes are merged, the .to_address() won't be necessary + python_addresses = Addresses( + ht.address.to_address() for ht in targets.closure if isinstance(ht.adaptor, PythonTargetAdaptor) ) create_pex = CreatePexFromTargetClosure( - build_file_addresses=python_build_file_addresses, + addresses=python_addresses, output_filename="python-repl.pex", entry_point=None, ) From ba798365c07e704b17b5fb81155ea29dc260d599 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Mon, 10 Feb 2020 11:24:58 -0800 Subject: [PATCH 05/11] Add integration test --- src/python/pants/backend/python/rules/BUILD | 1 + .../python/rules/repl_integration_test.py | 67 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/python/pants/backend/python/rules/repl_integration_test.py diff --git a/src/python/pants/backend/python/rules/BUILD b/src/python/pants/backend/python/rules/BUILD index 3448593666c..63edb1dc6c1 100644 --- a/src/python/pants/backend/python/rules/BUILD +++ b/src/python/pants/backend/python/rules/BUILD @@ -64,6 +64,7 @@ python_tests( 'src/python/pants/testutil:interpreter_selection_utils', 'src/python/pants/testutil:test_base', 'src/python/pants/testutil/subsystem', + 'src/python/pants/testutil:goal_rule_test_base', ], tags = {'integration', 'partially_type_checked'}, timeout = 480, diff --git a/src/python/pants/backend/python/rules/repl_integration_test.py b/src/python/pants/backend/python/rules/repl_integration_test.py new file mode 100644 index 00000000000..a019825c2d9 --- /dev/null +++ b/src/python/pants/backend/python/rules/repl_integration_test.py @@ -0,0 +1,67 @@ +# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from pants.backend.python.rules import ( + download_pex_bin, + inject_init, + pex, + pex_from_target_closure, + prepare_chrooted_python_sources, + repl, +) +from pants.backend.python.rules.repl import PythonRepl +from pants.backend.python.subsystems import python_native_code, subprocess_environment +from pants.backend.python.targets.python_library import PythonLibrary +from pants.build_graph.build_file_aliases import BuildFileAliases +from pants.engine.fs import FileContent +from pants.engine.interactive_runner import InteractiveRunner +from pants.rules.core import strip_source_root +from pants.testutil.goal_rule_test_base import GoalRuleTestBase + + +class PythonReplTest(GoalRuleTestBase): + goal_cls = PythonRepl + + @classmethod + def rules(cls): + return ( + *super().rules(), + *repl.rules(), + *download_pex_bin.rules(), + *inject_init.rules(), + *pex.rules(), + *pex_from_target_closure.rules(), + *prepare_chrooted_python_sources.rules(), + *python_native_code.rules(), + *strip_source_root.rules(), + *subprocess_environment.rules(), + ) + + @classmethod + def alias_groups(cls) -> BuildFileAliases: + return BuildFileAliases( + targets={ + "python_library": PythonLibrary, + } + ) + + def test_repl_with_targets(self): + library_source = FileContent(path="some_lib.py", content=b"class SomeClass:\n pass\n") + self.create_library( + name="some_lib", + target_type="python_library", + path="src/python", + sources=["some_lib.py"] + ) + + self.create_file( + relpath="src/python/some_lib.py", + contents=library_source.content.decode(), + ) + + additional_params = [ + InteractiveRunner(self.scheduler), + ] + + output = self.execute_rule(args=["src/python:some_lib"], additional_params=additional_params) + assert output == "REPL exited successfully." From f8e666237bbd76905de1f41329e3a09047259bd2 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Tue, 11 Feb 2020 11:57:16 -0800 Subject: [PATCH 06/11] PR comments --- src/python/pants/backend/python/rules/repl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/python/pants/backend/python/rules/repl.py b/src/python/pants/backend/python/rules/repl.py index c5d01231639..45c0029cc4b 100644 --- a/src/python/pants/backend/python/rules/repl.py +++ b/src/python/pants/backend/python/rules/repl.py @@ -41,7 +41,8 @@ async def run_python_repl( build_root: BuildRoot, global_options: GlobalOptions) -> PythonRepl: - # note - when eric's changes are merged, the .to_address() won't be necessary + # NOTE - when Eric's changes pertaining to BuildFileAddresses are merged in https://github.com/pantsbuild/pants/pull/9100, the + # .to_address() call will no longer be necessary. python_addresses = Addresses( ht.address.to_address() for ht in targets.closure if isinstance(ht.adaptor, PythonTargetAdaptor) ) @@ -49,18 +50,17 @@ async def run_python_repl( create_pex = CreatePexFromTargetClosure( addresses=python_addresses, output_filename="python-repl.pex", - entry_point=None, ) 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)) + path_relative_to_build_root = PurePath(tmpdir).relative_to(build_root.path).as_posix() workspace.materialize_directory( DirectoryToMaterialize(repl_pex.directory_digest, path_prefix=path_relative_to_build_root) ) - full_path = str(PurePath(tmpdir, repl_pex.output_filename)) + full_path = PurePath(tmpdir, repl_pex.output_filename).as_posix() run_request = InteractiveProcessRequest( argv=(full_path,), run_in_workspace=True, From b1de414c392555d752de59bc45ffdd3e344944b8 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Tue, 11 Feb 2020 13:49:39 -0800 Subject: [PATCH 07/11] Fix tests with new BinaryTool commit --- src/python/pants/backend/python/rules/repl.py | 4 +--- .../backend/python/rules/repl_integration_test.py | 10 +++++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/python/pants/backend/python/rules/repl.py b/src/python/pants/backend/python/rules/repl.py index 45c0029cc4b..81aa7ef4567 100644 --- a/src/python/pants/backend/python/rules/repl.py +++ b/src/python/pants/backend/python/rules/repl.py @@ -41,10 +41,8 @@ async def run_python_repl( build_root: BuildRoot, global_options: GlobalOptions) -> PythonRepl: - # NOTE - when Eric's changes pertaining to BuildFileAddresses are merged in https://github.com/pantsbuild/pants/pull/9100, the - # .to_address() call will no longer be necessary. python_addresses = Addresses( - ht.address.to_address() for ht in targets.closure if isinstance(ht.adaptor, PythonTargetAdaptor) + ht.address for ht in targets.closure if isinstance(ht.adaptor, PythonTargetAdaptor) ) create_pex = CreatePexFromTargetClosure( diff --git a/src/python/pants/backend/python/rules/repl_integration_test.py b/src/python/pants/backend/python/rules/repl_integration_test.py index a019825c2d9..3d7077cd844 100644 --- a/src/python/pants/backend/python/rules/repl_integration_test.py +++ b/src/python/pants/backend/python/rules/repl_integration_test.py @@ -15,26 +15,33 @@ from pants.build_graph.build_file_aliases import BuildFileAliases from pants.engine.fs import FileContent from pants.engine.interactive_runner import InteractiveRunner +from pants.engine.rules import RootRule from pants.rules.core import strip_source_root from pants.testutil.goal_rule_test_base import GoalRuleTestBase +from pants.testutil.subsystem.util import init_subsystems class PythonReplTest(GoalRuleTestBase): goal_cls = PythonRepl + def setUp(self): + super().setUp() + init_subsystems([download_pex_bin.DownloadedPexBin.Factory]) + @classmethod def rules(cls): return ( *super().rules(), *repl.rules(), - *download_pex_bin.rules(), *inject_init.rules(), *pex.rules(), + download_pex_bin.download_pex_bin, *pex_from_target_closure.rules(), *prepare_chrooted_python_sources.rules(), *python_native_code.rules(), *strip_source_root.rules(), *subprocess_environment.rules(), + RootRule(download_pex_bin.DownloadedPexBin.Factory), ) @classmethod @@ -61,6 +68,7 @@ def test_repl_with_targets(self): additional_params = [ InteractiveRunner(self.scheduler), + download_pex_bin.DownloadedPexBin.Factory.global_instance(), ] output = self.execute_rule(args=["src/python:some_lib"], additional_params=additional_params) From f221db9b547e28a688b585b954a3ef24db9d6299 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Wed, 12 Feb 2020 15:05:19 -0800 Subject: [PATCH 08/11] Fix name of import --- .../pants/backend/python/rules/repl_integration_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/pants/backend/python/rules/repl_integration_test.py b/src/python/pants/backend/python/rules/repl_integration_test.py index 3d7077cd844..3d16bc28481 100644 --- a/src/python/pants/backend/python/rules/repl_integration_test.py +++ b/src/python/pants/backend/python/rules/repl_integration_test.py @@ -16,7 +16,7 @@ from pants.engine.fs import FileContent from pants.engine.interactive_runner import InteractiveRunner from pants.engine.rules import RootRule -from pants.rules.core import strip_source_root +from pants.rules.core import strip_source_roots from pants.testutil.goal_rule_test_base import GoalRuleTestBase from pants.testutil.subsystem.util import init_subsystems @@ -39,7 +39,7 @@ def rules(cls): *pex_from_target_closure.rules(), *prepare_chrooted_python_sources.rules(), *python_native_code.rules(), - *strip_source_root.rules(), + *strip_source_roots.rules(), *subprocess_environment.rules(), RootRule(download_pex_bin.DownloadedPexBin.Factory), ) From 63de9b0c910a4c5c30ca00edbba49ccc25bdc343 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Thu, 13 Feb 2020 17:23:15 -0800 Subject: [PATCH 09/11] Convert to a top-level `repl` rule --- src/python/pants/backend/python/rules/repl.py | 62 +++++---------- src/python/pants/rules/core/BUILD | 2 + src/python/pants/rules/core/register.py | 2 + src/python/pants/rules/core/repl.py | 77 +++++++++++++++++++ .../core}/repl_integration_test.py | 11 ++- 5 files changed, 106 insertions(+), 48 deletions(-) create mode 100644 src/python/pants/rules/core/repl.py rename src/python/pants/{backend/python/rules => rules/core}/repl_integration_test.py (90%) diff --git a/src/python/pants/backend/python/rules/repl.py b/src/python/pants/backend/python/rules/repl.py index 81aa7ef4567..d98e2d7f7c9 100644 --- a/src/python/pants/backend/python/rules/repl.py +++ b/src/python/pants/backend/python/rules/repl.py @@ -2,78 +2,52 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). import logging -from pathlib import PurePath +from dataclasses import dataclass from pants.backend.python.rules.pex import Pex from pants.backend.python.rules.pex_from_target_closure import CreatePexFromTargetClosure -from pants.base.build_root import BuildRoot from pants.engine.addressable import Addresses -from pants.engine.console import Console -from pants.engine.fs import DirectoryToMaterialize, Workspace -from pants.engine.goal import Goal, GoalSubsystem -from pants.engine.interactive_runner import InteractiveProcessRequest, InteractiveRunner from pants.engine.legacy.graph import TransitiveHydratedTargets from pants.engine.legacy.structs import PythonTargetAdaptor -from pants.engine.rules import goal_rule +from pants.engine.rules import UnionRule, rule from pants.engine.selectors import Get -from pants.option.global_options import GlobalOptions -from pants.util.contextutil import temporary_dir +from pants.rules.core.repl import ReplBinary, ReplDeterminer logger = logging.getLogger(__name__) -class PythonReplOptions(GoalSubsystem): - """Opens a REPL.""" - name = 'repl2-python' +@dataclass(frozen=True) +class PythonRepl: + addresses: Addresses -class PythonRepl(Goal): - subsystem_cls = PythonReplOptions +@rule +async def targets_to_python_repl(addresses: Addresses) -> PythonRepl: + return PythonRepl(addresses=addresses) -@goal_rule -async def run_python_repl( - console: Console, - workspace: Workspace, - runner: InteractiveRunner, - targets: TransitiveHydratedTargets, - build_root: BuildRoot, - global_options: GlobalOptions) -> PythonRepl: - +@rule +async def run_python_repl(repl: PythonRepl) -> ReplBinary: + targets = await Get[TransitiveHydratedTargets](Addresses, repl.addresses) python_addresses = Addresses( ht.address for ht in targets.closure if isinstance(ht.adaptor, PythonTargetAdaptor) ) - create_pex = CreatePexFromTargetClosure( addresses=python_addresses, output_filename="python-repl.pex", ) 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 = PurePath(tmpdir).relative_to(build_root.path).as_posix() - workspace.materialize_directory( - DirectoryToMaterialize(repl_pex.directory_digest, path_prefix=path_relative_to_build_root) - ) - - full_path = PurePath(tmpdir, repl_pex.output_filename).as_posix() - run_request = InteractiveProcessRequest( - argv=(full_path,), - run_in_workspace=True, - ) - result = runner.run_local_interactive_process(run_request) - exit_code = result.process_exit_code - - if exit_code == 0: - console.write_stdout("REPL exited successfully.") - else: - console.write_stdout(f"REPL exited with error: {exit_code}.") - return PythonRepl(exit_code) + return ReplBinary( + digest=repl_pex.directory_digest, + binary_name=repl_pex.output_filename, + ) def rules(): return [ + UnionRule(ReplDeterminer, PythonRepl), run_python_repl, + targets_to_python_repl, ] diff --git a/src/python/pants/rules/core/BUILD b/src/python/pants/rules/core/BUILD index 0ee75f29725..7eef7033e54 100644 --- a/src/python/pants/rules/core/BUILD +++ b/src/python/pants/rules/core/BUILD @@ -51,6 +51,8 @@ python_tests( sources=['*_integration_test.py'], dependencies=[ 'src/python/pants/testutil:int-test', + 'src/python/pants/backend/python/rules', + 'src/python/pants/testutil:goal_rule_test_base', 'examples/src/java/org/pantsbuild/example:hello_directory', 'examples/src/scala/org/pantsbuild/example:hello_directory', 'examples/src/resources/org/pantsbuild/example:hello_directory', diff --git a/src/python/pants/rules/core/register.py b/src/python/pants/rules/core/register.py index 4972842de0e..e0fa7d7c92c 100644 --- a/src/python/pants/rules/core/register.py +++ b/src/python/pants/rules/core/register.py @@ -11,6 +11,7 @@ lint, list_roots, list_targets, + repl, run, strip_source_roots, test, @@ -27,6 +28,7 @@ def rules(): *list_targets.rules(), *find_target_source_files.rules(), *filedeps.rules(), + *repl.rules(), *run.rules(), *strip_source_roots.rules(), *distdir.rules(), diff --git a/src/python/pants/rules/core/repl.py b/src/python/pants/rules/core/repl.py new file mode 100644 index 00000000000..3b7d6852c8f --- /dev/null +++ b/src/python/pants/rules/core/repl.py @@ -0,0 +1,77 @@ +# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from dataclasses import dataclass +from pathlib import PurePath + +from pants.base.build_root import BuildRoot +from pants.engine.addressable import Addresses +from pants.engine.console import Console +from pants.engine.fs import Digest, DirectoryToMaterialize, Workspace +from pants.engine.goal import Goal, GoalSubsystem +from pants.engine.interactive_runner import InteractiveProcessRequest, InteractiveRunner +from pants.engine.objects import union +from pants.engine.rules import goal_rule +from pants.engine.selectors import Get +from pants.option.global_options import GlobalOptions +from pants.util.contextutil import temporary_dir + + +class ReplOptions(GoalSubsystem): + """Opens a REPL.""" + name = 'repl2' + + +class Repl(Goal): + subsystem_cls = ReplOptions + + +@union +class ReplDeterminer: + pass + + +@dataclass(frozen=True) +class ReplBinary: + digest: Digest + binary_name: str + + +@goal_rule +async def run_repl( + console: Console, + workspace: Workspace, + runner: InteractiveRunner, + addresses: Addresses, + build_root: BuildRoot, + global_options: GlobalOptions) -> Repl: + + repl_binary = await Get[ReplBinary](Addresses, addresses) + + with temporary_dir(root_dir=global_options.pants_workdir, cleanup=False) as tmpdir: + path_relative_to_build_root = PurePath(tmpdir).relative_to(build_root.path).as_posix() + workspace.materialize_directory( + DirectoryToMaterialize(repl_binary.digest, path_prefix=path_relative_to_build_root) + ) + + full_path = PurePath(tmpdir, repl_binary.binary_name).as_posix() + run_request = InteractiveProcessRequest( + argv=(full_path,), + run_in_workspace=True, + ) + + result = runner.run_local_interactive_process(run_request) + exit_code = result.process_exit_code + + if exit_code == 0: + console.write_stdout("REPL exited successfully.") + else: + console.write_stdout(f"REPL exited with error: {exit_code}.") + + return Repl(exit_code) + + +def rules(): + return [ + run_repl, + ] diff --git a/src/python/pants/backend/python/rules/repl_integration_test.py b/src/python/pants/rules/core/repl_integration_test.py similarity index 90% rename from src/python/pants/backend/python/rules/repl_integration_test.py rename to src/python/pants/rules/core/repl_integration_test.py index 3d16bc28481..b2c81d7bdfc 100644 --- a/src/python/pants/backend/python/rules/repl_integration_test.py +++ b/src/python/pants/rules/core/repl_integration_test.py @@ -1,9 +1,9 @@ # Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). +import pants.engine.build_files as build_files from pants.backend.python.rules import ( download_pex_bin, - inject_init, pex, pex_from_target_closure, prepare_chrooted_python_sources, @@ -17,12 +17,13 @@ from pants.engine.interactive_runner import InteractiveRunner from pants.engine.rules import RootRule from pants.rules.core import strip_source_roots +from pants.rules.core.repl import Repl, run_repl from pants.testutil.goal_rule_test_base import GoalRuleTestBase from pants.testutil.subsystem.util import init_subsystems -class PythonReplTest(GoalRuleTestBase): - goal_cls = PythonRepl +class ReplTest(GoalRuleTestBase): + goal_cls = Repl def setUp(self): super().setUp() @@ -33,7 +34,7 @@ def rules(cls): return ( *super().rules(), *repl.rules(), - *inject_init.rules(), + run_repl, *pex.rules(), download_pex_bin.download_pex_bin, *pex_from_target_closure.rules(), @@ -41,7 +42,9 @@ def rules(cls): *python_native_code.rules(), *strip_source_roots.rules(), *subprocess_environment.rules(), + build_files.strip_address_origins, RootRule(download_pex_bin.DownloadedPexBin.Factory), + RootRule(PythonRepl), ) @classmethod From 701ec0bce378f58c2974e217d08f09e4a30a5abf Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Fri, 14 Feb 2020 13:23:45 -0800 Subject: [PATCH 10/11] Rename ReplDeterminer -> ReplImplementation, add docstring --- src/python/pants/backend/python/rules/repl.py | 4 ++-- src/python/pants/rules/core/repl.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/python/pants/backend/python/rules/repl.py b/src/python/pants/backend/python/rules/repl.py index d98e2d7f7c9..58eed1fb1c9 100644 --- a/src/python/pants/backend/python/rules/repl.py +++ b/src/python/pants/backend/python/rules/repl.py @@ -11,7 +11,7 @@ from pants.engine.legacy.structs import PythonTargetAdaptor from pants.engine.rules import UnionRule, rule from pants.engine.selectors import Get -from pants.rules.core.repl import ReplBinary, ReplDeterminer +from pants.rules.core.repl import ReplBinary, ReplImplementation logger = logging.getLogger(__name__) @@ -47,7 +47,7 @@ async def run_python_repl(repl: PythonRepl) -> ReplBinary: def rules(): return [ - UnionRule(ReplDeterminer, PythonRepl), + UnionRule(ReplImplementation, PythonRepl), run_python_repl, targets_to_python_repl, ] diff --git a/src/python/pants/rules/core/repl.py b/src/python/pants/rules/core/repl.py index 3b7d6852c8f..aa17ee20b97 100644 --- a/src/python/pants/rules/core/repl.py +++ b/src/python/pants/rules/core/repl.py @@ -27,8 +27,9 @@ class Repl(Goal): @union -class ReplDeterminer: - pass +class ReplImplementation: + """This type proxies from the top-level `repl` goal to a specific REPL implementation + for a specific language or languages.""" @dataclass(frozen=True) From 4d6b5f7433432043e60bf5423067d8001f6f0b5e Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Tue, 18 Feb 2020 13:32:29 -0800 Subject: [PATCH 11/11] Responses to PR comments --- src/python/pants/backend/python/rules/repl.py | 6 ----- src/python/pants/rules/core/repl.py | 25 ++++++++++++------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/python/pants/backend/python/rules/repl.py b/src/python/pants/backend/python/rules/repl.py index 58eed1fb1c9..d35a4a21eca 100644 --- a/src/python/pants/backend/python/rules/repl.py +++ b/src/python/pants/backend/python/rules/repl.py @@ -22,11 +22,6 @@ class PythonRepl: addresses: Addresses -@rule -async def targets_to_python_repl(addresses: Addresses) -> PythonRepl: - return PythonRepl(addresses=addresses) - - @rule async def run_python_repl(repl: PythonRepl) -> ReplBinary: targets = await Get[TransitiveHydratedTargets](Addresses, repl.addresses) @@ -49,5 +44,4 @@ def rules(): return [ UnionRule(ReplImplementation, PythonRepl), run_python_repl, - targets_to_python_repl, ] diff --git a/src/python/pants/rules/core/repl.py b/src/python/pants/rules/core/repl.py index aa17ee20b97..715abf9f339 100644 --- a/src/python/pants/rules/core/repl.py +++ b/src/python/pants/rules/core/repl.py @@ -11,27 +11,29 @@ from pants.engine.goal import Goal, GoalSubsystem from pants.engine.interactive_runner import InteractiveProcessRequest, InteractiveRunner from pants.engine.objects import union -from pants.engine.rules import goal_rule +from pants.engine.rules import UnionMembership, goal_rule from pants.engine.selectors import Get from pants.option.global_options import GlobalOptions from pants.util.contextutil import temporary_dir +@union +class ReplImplementation: + """This type proxies from the top-level `repl` goal to a specific REPL implementation + for a specific language or languages.""" + addresses: Addresses + + class ReplOptions(GoalSubsystem): """Opens a REPL.""" - name = 'repl2' + name = 'repl' + required_union_implementations = (ReplImplementation,) class Repl(Goal): subsystem_cls = ReplOptions -@union -class ReplImplementation: - """This type proxies from the top-level `repl` goal to a specific REPL implementation - for a specific language or languages.""" - - @dataclass(frozen=True) class ReplBinary: digest: Digest @@ -45,9 +47,14 @@ async def run_repl( runner: InteractiveRunner, addresses: Addresses, build_root: BuildRoot, + union_membership: UnionMembership, global_options: GlobalOptions) -> Repl: - repl_binary = await Get[ReplBinary](Addresses, addresses) + # Once #9142 is merged, we can guarantee that we will only even enter this `goal_rule` if there + # exists an implementer of the `ReplImplementation` union, so it's not a problem that this can + # in principle throw an exception. + repl_impl = next(iter(union_membership.union_rules[ReplImplementation])) + repl_binary = await Get[ReplBinary](ReplImplementation, repl_impl(addresses)) with temporary_dir(root_dir=global_options.pants_workdir, cleanup=False) as tmpdir: path_relative_to_build_root = PurePath(tmpdir).relative_to(build_root.path).as_posix()