Repository: pantsbuild/pants

Issue:
Allow setting/passing through env vars to `experimental_shell_command`
**Is your feature request related to a problem? Please describe.**

Some of the `tools` I'd like to use in my `experimental_shell_command` targets rely on environment variables being set. The env vars vary on a per-user basis so I can't hard-code them in the shell `command` - instead I'd like to pass them through in the same way I can pass through vars for `pytest` and `docker` runs.

**Describe the solution you'd like**

A new `extra_env_vars` field on the `experimental_shell_command` target, that supports the pass-through syntax of specifying a var name without a value (i.e. `extra_env_vars=["HOME"]`).


Discussion:

Resolving patch:
diff --git a/src/python/pants/backend/shell/shell_command.py b/src/python/pants/backend/shell/shell_command.py
--- a/src/python/pants/backend/shell/shell_command.py
+++ b/src/python/pants/backend/shell/shell_command.py
@@ -14,6 +14,7 @@
 from pants.backend.shell.shell_setup import ShellSetup
 from pants.backend.shell.target_types import (
     ShellCommandCommandField,
+    ShellCommandExtraEnvVarsField,
     ShellCommandLogOutputField,
     ShellCommandOutputsField,
     ShellCommandRunWorkdirField,
@@ -115,6 +116,7 @@ async def prepare_shell_command_process(
     timeout = shell_command.get(ShellCommandTimeoutField).value
     tools = shell_command.get(ShellCommandToolsField, default_raw_value=()).value
     outputs = shell_command.get(ShellCommandOutputsField).value or ()
+    extra_env_vars = shell_command.get(ShellCommandExtraEnvVarsField).value or ()
 
     if not command:
         raise ValueError(
@@ -162,6 +164,9 @@ async def prepare_shell_command_process(
                     rationale=f"execute `{shell_command.alias}` {shell_command.address}",
                 )
 
+    extra_env = await Get(Environment, EnvironmentRequest(extra_env_vars))
+    command_env.update(extra_env)
+
     transitive_targets = await Get(
         TransitiveTargets,
         TransitiveTargetsRequest([shell_command.address]),
diff --git a/src/python/pants/backend/shell/target_types.py b/src/python/pants/backend/shell/target_types.py
--- a/src/python/pants/backend/shell/target_types.py
+++ b/src/python/pants/backend/shell/target_types.py
@@ -301,6 +301,17 @@ class ShellCommandToolsField(StringSequenceField):
     )
 
 
+class ShellCommandExtraEnvVarsField(StringSequenceField):
+    alias = "extra_env_vars"
+    help = softwrap(
+        """
+        Additional environment variables to include in the shell process.
+        Entries are strings in the form `ENV_VAR=value` to use explicitly; or just
+        `ENV_VAR` to copy the value of a variable in Pants's own environment.
+        """
+    )
+
+
 class ShellCommandLogOutputField(BoolField):
     alias = "log_output"
     default = False
@@ -324,6 +335,7 @@ class ShellCommandTarget(Target):
         ShellCommandSourcesField,
         ShellCommandTimeoutField,
         ShellCommandToolsField,
+        ShellCommandExtraEnvVarsField,
     )
     help = softwrap(
         """


Test patch:
diff --git a/src/python/pants/backend/shell/shell_command_test.py b/src/python/pants/backend/shell/shell_command_test.py
--- a/src/python/pants/backend/shell/shell_command_test.py
+++ b/src/python/pants/backend/shell/shell_command_test.py
@@ -404,3 +404,32 @@ def test_shell_command_boot_script(rule_runner: RuleRunner) -> None:
     assert sorted(res.env["TOOLS"].split()) == tools
     for tool in tools:
         assert res.env[tool].endswith(f"/{tool.replace('_', '.')}")
+
+
+def test_shell_command_extra_env_vars(caplog, rule_runner: RuleRunner) -> None:
+    caplog.set_level(logging.INFO)
+    caplog.clear()
+    rule_runner.set_options([], env={"FOO": "foo"}, env_inherit={"PATH"})
+    rule_runner.write_files(
+        {
+            "src/BUILD": dedent(
+                """\
+                experimental_shell_command(
+                  name="extra-env-test",
+                  tools=["echo"],
+                  extra_env_vars=["FOO", "HELLO=world", "BAR"],
+                  command='echo FOO="$FOO" HELLO="$HELLO" BAR="$BAR"',
+                  log_output=True,
+                )
+                """
+            )
+        }
+    )
+
+    assert_shell_command_result(
+        rule_runner,
+        Address("src", target_name="extra-env-test"),
+        expected_contents={},
+    )
+
+    assert_logged(caplog, [(logging.INFO, "FOO=foo HELLO=world BAR=\n")])