Skip to content

Commit

Permalink
v2 ./pants run (#8573)
Browse files Browse the repository at this point in the history
## Problem

The v2 engine doesn't implement the run goal yet.

## Solution

Implement the run goal for python targets in the v2 engine.
  • Loading branch information
gshuflin authored Nov 6, 2019
1 parent d40f13f commit 7f4ef60
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def create_python_binary(python_binary_adaptor: PythonBinaryAdaptor,
)

pex = yield Get(Pex, CreatePex, create_requirements_pex)
yield CreatedBinary(digest=pex.directory_digest)
yield CreatedBinary(digest=pex.directory_digest, binary_name=pex.output_filename)


def rules():
Expand Down
1 change: 1 addition & 0 deletions src/python/pants/rules/core/binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class BinaryTarget:
@dataclass(frozen=True)
class CreatedBinary:
digest: Digest
binary_name: str


@console_rule
Expand Down
57 changes: 40 additions & 17 deletions src/python/pants/rules/core/run.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,58 @@
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from pathlib import Path

from pants.build_graph.address import Address
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
from pants.engine.interactive_runner import InteractiveProcessRequest, InteractiveRunner
from pants.engine.rules import console_rule
from pants.engine.selectors import Get
from pants.rules.core.binary import CreatedBinary
from pants.util.contextutil import temporary_dir


class Run(Goal):
"""Runs a runnable target."""
name = 'v2-run'
name = 'run'


#TODO(gregs) - the `run` rule should really only accept a single target, but we don't have the infrastructure
# yet to support a console rule that can request one and only one target (rather than BuildFileAddresses plural
# or Specs plural).
@console_rule
def run(console: Console, runner: InteractiveRunner, build_file_addresses: BuildFileAddresses) -> Run:
console.write_stdout("Running the `run` goal\n")

request = InteractiveProcessRequest(
argv=["/usr/bin/python"],
env=("TEST_ENV", "TEST"),
run_in_workspace=False,
)

try:
res = runner.run_local_interactive_process(request)
print(f"Subprocess exited with result: {res.process_exit_code}")
yield Run(res.process_exit_code)
except Exception as e:
print(f"Exception when running local interactive process: {e}")
yield Run(-1)
def run(console: Console, workspace: Workspace, runner: InteractiveRunner, addresses: BuildFileAddresses) -> Run:
bfa = addresses.dependencies[0]
target = bfa.to_address()
binary = yield Get(CreatedBinary, Address, target)

with temporary_dir(cleanup=True) as tmpdir:
dirs_to_materialize = (DirectoryToMaterialize(path=str(tmpdir), directory_digest=binary.digest),)
workspace.materialize_directories(dirs_to_materialize)

console.write_stdout(f"Running target: {target}\n")
full_path = str(Path(tmpdir, binary.binary_name))
run_request = InteractiveProcessRequest(
argv=[full_path],
run_in_workspace=True,
)

try:
result = runner.run_local_interactive_process(run_request)
exit_code = result.process_exit_code
if result.process_exit_code == 0:
console.write_stdout(f"{target} ran successfully.\n")
else:
console.write_stderr(f"{target} failed with code {result.process_exit_code}!\n")

except Exception as e:
console.write_stderr(f"Exception when attempting to run {target} : {e}\n")
exit_code = -1

yield Run(exit_code)


def rules():
Expand Down
14 changes: 14 additions & 0 deletions tests/python/pants_test/rules/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ python_tests(
]
)

python_tests(
name='run',
source='test_run.py',
dependencies=[
'src/python/pants/build_graph',
'src/python/pants/engine/legacy:graph',
'src/python/pants/engine/legacy:structs',
'src/python/pants/rules/core',
'tests/python/pants_test:test_base',
'tests/python/pants_test:console_rule_test_base',
'tests/python/pants_test/engine:util',
]
)

python_tests(
name='fmt_integration',
source='test_fmt_integration.py',
Expand Down
64 changes: 64 additions & 0 deletions tests/python/pants_test/rules/test_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from pants.build_graph.address import Address, BuildFileAddress
from pants.engine.addressable import BuildFileAddresses
from pants.engine.fs import Digest, FileContent, InputFilesContent, Workspace
from pants.engine.interactive_runner import InteractiveRunner
from pants.rules.core import run
from pants.rules.core.binary import CreatedBinary
from pants_test.console_rule_test_base import ConsoleRuleTestBase
from pants_test.engine.util import MockConsole, run_rule


class RunTest(ConsoleRuleTestBase):
goal_cls = run.Run

def create_mock_binary(self, program_text: bytes) -> CreatedBinary:
input_files_content = InputFilesContent((
FileContent(path='program.py', content=program_text, is_executable=True),
))
digest, = self.scheduler.product_request(Digest, [input_files_content])
return CreatedBinary(
binary_name='program.py',
digest=digest,
)

def single_target_run(self, *, console: MockConsole, program_text: bytes, spec: str):
workspace = Workspace(self.scheduler)
interactive_runner = InteractiveRunner(self.scheduler)
address = Address.parse(spec)
bfa = BuildFileAddress(
build_file=None,
target_name=address.target_name,
rel_path=f'{address.spec_path}/BUILD'
)
build_file_addresses = BuildFileAddresses((bfa,))
res = run_rule(run.run, console, workspace, interactive_runner, build_file_addresses, {
(CreatedBinary, Address): lambda _: self.create_mock_binary(program_text)
})
return res

def test_normal_run(self) -> None:
console = MockConsole(use_colors=False)
program_text = b'#!/usr/bin/python\nprint("hello")'
res = self.single_target_run(
console=console,
program_text=program_text,
spec='some/addr'
)
self.assertEqual(res.exit_code, 0)
self.assertEquals(console.stdout.getvalue(), "Running target: some/addr:addr\nsome/addr:addr ran successfully.\n")
self.assertEquals(console.stderr.getvalue(), "")

def test_failed_run(self) -> None:
console = MockConsole(use_colors=False)
program_text = b'#!/usr/bin/python\nraise RuntimeError("foo")'
res = self.single_target_run(
console=console,
program_text=program_text,
spec='some/addr'
)
self.assertEqual(res.exit_code, 1)
self.assertEquals(console.stdout.getvalue(), "Running target: some/addr:addr\n")
self.assertEquals(console.stderr.getvalue(), "some/addr:addr failed with code 1!\n")

0 comments on commit 7f4ef60

Please sign in to comment.