Skip to content

Commit

Permalink
Adds fields to capture stdout/stderr to experimental_run_in_sandbox (
Browse files Browse the repository at this point in the history
…pantsbuild#18165)

This adds `stdout=` and `stderr=` fields to `experimental_run_in_sandbox`, allowing users to include those streams in the output from such a target.

Closes pantsbuild#18087.
  • Loading branch information
Christopher Neugebauer authored Feb 6, 2023
1 parent ceaa203 commit cc5a436
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 5 deletions.
14 changes: 14 additions & 0 deletions src/python/pants/backend/shell/target_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,18 @@ class RunInSandboxArgumentsField(StringSequenceField):
help = f"Extra arguments to pass into the `{RunInSandboxRunnableField.alias}` field."


class RunInSandboxStdoutFilenameField(StringField):
alias = "stdout"
default = None
help = "A filename to capture the contents of `stdout` to, relative to the value of `workdir`."


class RunInSandboxStderrFilenameField(StringField):
alias = "stderr"
default = None
help = "A filename to capture the contents of `stdout` to, relative to the value of `workdir`."


class ShellCommandTimeoutField(IntField):
alias = "timeout"
default = 30
Expand Down Expand Up @@ -532,6 +544,8 @@ class ShellRunInSandboxTarget(Target):
ShellCommandExtraEnvVarsField,
ShellCommandWorkdirField,
ShellCommandOutputRootDirField,
RunInSandboxStdoutFilenameField,
RunInSandboxStderrFilenameField,
EnvironmentField,
)
help = softwrap(
Expand Down
31 changes: 26 additions & 5 deletions src/python/pants/backend/shell/util_rules/adhoc_process_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import shlex
from dataclasses import dataclass
from textwrap import dedent # noqa: PNT20
from typing import Union
from typing import Mapping, Union

from pants.backend.shell.subsystems.shell_setup import ShellSetup
from pants.backend.shell.target_types import (
Expand All @@ -34,7 +34,15 @@
)
from pants.engine.addresses import Addresses, UnparsedAddressInputs
from pants.engine.env_vars import EnvironmentVars, EnvironmentVarsRequest
from pants.engine.fs import EMPTY_DIGEST, CreateDigest, Digest, Directory, MergeDigests, Snapshot
from pants.engine.fs import (
EMPTY_DIGEST,
CreateDigest,
Digest,
Directory,
FileContent,
MergeDigests,
Snapshot,
)
from pants.engine.internals.native_engine import RemovePrefix
from pants.engine.process import Process
from pants.engine.rules import Get, MultiGet, collect_rules, rule, rule_helper
Expand Down Expand Up @@ -166,16 +174,29 @@ def _parse_outputs_from_command(

@rule_helper
async def _adjust_root_output_directory(
digest: Digest, address: Address, working_directory: str, root_output_directory: str
digest: Digest,
address: Address,
working_directory: str,
root_output_directory: str,
extra_files: Mapping[str, bytes] = FrozenDict(),
) -> Digest:

working_directory = _parse_working_directory(working_directory, address)
new_root = _parse_working_directory(root_output_directory, working_directory)

if new_root == "":
return digest
else:
return await Get(Digest, RemovePrefix(digest, new_root))

if extra_files:
extra_digest = await Get(
Digest,
CreateDigest(
FileContent(f"{new_root}/{name}", content) for name, content in extra_files.items()
),
)
digest = await Get(Digest, MergeDigests((digest, extra_digest)))

return await Get(Digest, RemovePrefix(digest, new_root))


def _shell_tool_safe_env_name(tool_name: str) -> str:
Expand Down
9 changes: 9 additions & 0 deletions src/python/pants/backend/shell/util_rules/run_in_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
RunInSandboxArgumentsField,
RunInSandboxRunnableField,
RunInSandboxSourcesField,
RunInSandboxStderrFilenameField,
RunInSandboxStdoutFilenameField,
ShellCommandLogOutputField,
ShellCommandOutputRootDirField,
ShellCommandWorkdirField,
Expand Down Expand Up @@ -132,11 +134,18 @@ async def run_in_sandbox_request(
if result.stderr:
logger.warning(result.stderr.decode())

extras = (
(shell_command[RunInSandboxStdoutFilenameField].value, result.stdout),
(shell_command[RunInSandboxStderrFilenameField].value, result.stderr),
)
extra_contents = {i: j for i, j in extras if i}

adjusted = await _adjust_root_output_directory(
result.output_digest,
shell_command.address,
working_directory,
root_output_directory,
extra_files=extra_contents,
)
output = await Get(Snapshot, Digest, adjusted)

Expand Down
38 changes: 38 additions & 0 deletions src/python/pants/backend/shell/util_rules/shell_command_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,44 @@ def test_run_runnable_in_sandbox_with_workdir(rule_runner: RuleRunner) -> None:
)


def test_run_in_sandbox_capture_stdout_err(rule_runner: RuleRunner) -> None:
rule_runner.write_files(
{
"src/fruitcake.py": dedent(
"""\
import sys
print("fruitcake")
print("inconceivable", file=sys.stderr)
"""
),
"src/BUILD": dedent(
"""\
python_source(
source="fruitcake.py",
name="fruitcake",
)
experimental_run_in_sandbox(
name="run_fruitcake",
runnable=":fruitcake",
stdout="stdout",
stderr="stderr",
)
"""
),
}
)

assert_run_in_sandbox_result(
rule_runner,
Address("src", target_name="run_fruitcake"),
expected_contents={
"stderr": "inconceivable\n",
"stdout": "fruitcake\n",
},
)


def test_relative_directories(rule_runner: RuleRunner) -> None:
rule_runner.write_files(
{
Expand Down

0 comments on commit cc5a436

Please sign in to comment.