Skip to content
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

Merged
merged 11 commits into from
Feb 19, 2020
Merged
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
2 changes: 2 additions & 0 deletions src/python/pants/backend/python/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
)
1 change: 1 addition & 0 deletions src/python/pants/backend/python/rules/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
47 changes: 47 additions & 0 deletions src/python/pants/backend/python/rules/repl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import logging
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.engine.addressable import Addresses
from pants.engine.legacy.graph import TransitiveHydratedTargets
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, ReplImplementation


logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class PythonRepl:
addresses: Addresses


@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)
return ReplBinary(
digest=repl_pex.directory_digest,
binary_name=repl_pex.output_filename,
)


def rules():
return [
UnionRule(ReplImplementation, PythonRepl),
run_python_repl,
]
2 changes: 2 additions & 0 deletions src/python/pants/rules/core/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions src/python/pants/rules/core/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
lint,
list_roots,
list_targets,
repl,
run,
strip_source_roots,
test,
Expand All @@ -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(),
Expand Down
85 changes: 85 additions & 0 deletions src/python/pants/rules/core/repl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# 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 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 = 'repl'
required_union_implementations = (ReplImplementation,)


class Repl(Goal):
subsystem_cls = ReplOptions


@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,
union_membership: UnionMembership,
global_options: GlobalOptions) -> Repl:

# 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()
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,
]
78 changes: 78 additions & 0 deletions src/python/pants/rules/core/repl_integration_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# 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,
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.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 ReplTest(GoalRuleTestBase):
goal_cls = Repl

def setUp(self):
super().setUp()
init_subsystems([download_pex_bin.DownloadedPexBin.Factory])

@classmethod
def rules(cls):
return (
*super().rules(),
*repl.rules(),
run_repl,
*pex.rules(),
download_pex_bin.download_pex_bin,
*pex_from_target_closure.rules(),
*prepare_chrooted_python_sources.rules(),
*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
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),
download_pex_bin.DownloadedPexBin.Factory.global_instance(),
]

output = self.execute_rule(args=["src/python:some_lib"], additional_params=additional_params)
assert output == "REPL exited successfully."