Turn ProcessWrapperUtil helper functions into a ProcessWrapper class.

This will make it easier to carry the process-wrapper configuration around.
In particular, I want to add the ability to pass arbitrary extra flags to
the tool, and carrying them around explicitly and separately from the
process-wrapper path adds a lot of noise everywhere.

This also cleans up the way we gain access to the process-wrapper binary
and the checks to see if it is supported by homogenizing all logic into
a single place and removing OS-specific checks from a variety of places.

Part of https://github.com/bazelbuild/bazel/issues/10245.

RELNOTES: None.
PiperOrigin-RevId: 310908570
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContextTest.java b/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContextTest.java
index d75521e..416be8b 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContextTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContextTest.java
@@ -162,8 +162,8 @@
             environment,
             ImmutableMap.of("FOO", "BAR"),
             downloader,
-            null,
             1.0,
+            /*processWrapper=*/ null,
             new HashMap<>(),
             starlarkSemantics,
             repoRemoteExecutor);
diff --git a/src/test/java/com/google/devtools/build/lib/exec/local/BUILD b/src/test/java/com/google/devtools/build/lib/exec/local/BUILD
index fa14ab8..c6b9e86 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/local/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/exec/local/BUILD
@@ -21,6 +21,7 @@
         "//src/test/shell/integration:spend_cpu_time",
     ],
     deps = [
+        "//src/main/java/com/google/devtools/build/lib:runtime",
         "//src/main/java/com/google/devtools/build/lib/actions",
         "//src/main/java/com/google/devtools/build/lib/actions:localhost_capacity",
         "//src/main/java/com/google/devtools/build/lib/exec:bin_tools",
diff --git a/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java
index 6bde2b5..5a0158e 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java
@@ -47,6 +47,7 @@
 import com.google.devtools.build.lib.exec.SpawnRunner.ProgressStatus;
 import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionContext;
 import com.google.devtools.build.lib.exec.util.SpawnBuilder;
+import com.google.devtools.build.lib.runtime.ProcessWrapper;
 import com.google.devtools.build.lib.shell.JavaSubprocessFactory;
 import com.google.devtools.build.lib.shell.Subprocess;
 import com.google.devtools.build.lib.shell.SubprocessBuilder;
@@ -58,7 +59,6 @@
 import com.google.devtools.build.lib.unix.UnixFileSystem;
 import com.google.devtools.build.lib.util.NetUtil;
 import com.google.devtools.build.lib.util.OS;
-import com.google.devtools.build.lib.util.OsUtils;
 import com.google.devtools.build.lib.util.io.FileOutErr;
 import com.google.devtools.build.lib.vfs.DigestHashFunction;
 import com.google.devtools.build.lib.vfs.FileSystem;
@@ -99,30 +99,21 @@
 /** Unit tests for {@link LocalSpawnRunner}. */
 @RunWith(JUnit4.class)
 public class LocalSpawnRunnerTest {
-  private static final boolean USE_WRAPPER = true;
-  private static final boolean NO_WRAPPER = false;
 
   private static class TestedLocalSpawnRunner extends LocalSpawnRunner {
     public TestedLocalSpawnRunner(
         Path execRoot,
-        Path embeddedBin,
         LocalExecutionOptions localExecutionOptions,
         ResourceManager resourceManager,
-        boolean useProcessWrapper,
-        OS localOs,
+        ProcessWrapper processWrapper,
         LocalEnvProvider localEnvProvider) {
       super(
           execRoot,
           localExecutionOptions,
           resourceManager,
-          useProcessWrapper,
-          localOs,
           localEnvProvider,
-          useProcessWrapper
-              ? BinTools.forEmbeddedBin(
-                  embeddedBin,
-                  ImmutableList.of("process-wrapper" + OsUtils.executableExtension(localOs)))
-              : null,
+          /*binTools=*/ null,
+          processWrapper,
           Mockito.mock(RunfilesTreeUpdater.class));
     }
 
@@ -321,6 +312,10 @@
     return new InMemoryFileSystem();
   }
 
+  private static ProcessWrapper makeProcessWrapper(FileSystem fs) {
+    return new ProcessWrapper(fs.getPath("/process-wrapper"));
+  }
+
   /**
    * Enables real execution by default.
    *
@@ -351,11 +346,9 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
-            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
-            USE_WRAPPER,
-            OS.LINUX,
+            makeProcessWrapper(fs),
             LocalSpawnRunnerTest::keepLocalEnvUnchanged);
 
     FileOutErr fileOutErr = new FileOutErr(fs.getPath("/out/stdout"), fs.getPath("/out/stderr"));
@@ -372,11 +365,7 @@
     assertThat(captor.getValue().getArgv())
         .containsExactlyElementsIn(
             ImmutableList.of(
-                "/embedded_bin/process-wrapper",
-                "--timeout=123",
-                "--kill_delay=456",
-                "/bin/echo",
-                "Hi!"));
+                "/process-wrapper", "--timeout=123", "--kill_delay=456", "/bin/echo", "Hi!"));
     assertThat(captor.getValue().getEnv()).containsExactly("VARIABLE", "value");
     assertThat(captor.getValue().getTimeoutMillis()).isEqualTo(0);
     assertThat(captor.getValue().getStdout()).isEqualTo(StreamAction.REDIRECT);
@@ -408,11 +397,9 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             execRoot,
-            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
-            USE_WRAPPER,
-            OS.LINUX,
+            makeProcessWrapper(fs),
             LocalSpawnRunnerTest::keepLocalEnvUnchanged);
     ParamFileActionInput paramFileActionInput =
         new ParamFileActionInput(
@@ -462,11 +449,9 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
-            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
-            NO_WRAPPER,
-            OS.LINUX,
+            /*processWrapper=*/ null,
             LocalSpawnRunnerTest::keepLocalEnvUnchanged);
 
     FileOutErr fileOutErr = new FileOutErr(fs.getPath("/out/stdout"), fs.getPath("/out/stderr"));
@@ -507,11 +492,9 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
-            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
-            USE_WRAPPER,
-            OS.LINUX,
+            makeProcessWrapper(fs),
             LocalSpawnRunnerTest::keepLocalEnvUnchanged);
 
     assertThat(fs.getPath("/execroot").createDirectory()).isTrue();
@@ -528,11 +511,7 @@
         .containsExactlyElementsIn(
             ImmutableList.of(
                 // process-wrapper timeout grace_time stdout stderr
-                "/embedded_bin/process-wrapper",
-                "--timeout=0",
-                "--kill_delay=15",
-                "/bin/echo",
-                "Hi!"));
+                "/process-wrapper", "--timeout=0", "--kill_delay=15", "/bin/echo", "Hi!"));
     assertThat(captor.getValue().getEnv()).containsExactly("VARIABLE", "value");
     assertThat(captor.getValue().getStdout()).isEqualTo(StreamAction.REDIRECT);
     assertThat(captor.getValue().getStdoutFile()).isEqualTo(new File("/out/stdout"));
@@ -555,11 +534,9 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
-            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
-            USE_WRAPPER,
-            OS.LINUX,
+            makeProcessWrapper(fs),
             LocalSpawnRunnerTest::keepLocalEnvUnchanged);
 
     assertThat(fs.getPath("/out").createDirectory()).isTrue();
@@ -591,11 +568,9 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
-            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
-            USE_WRAPPER,
-            OS.LINUX,
+            makeProcessWrapper(fs),
             LocalSpawnRunnerTest::keepLocalEnvUnchanged);
 
     assertThat(fs.getPath("/execroot").createDirectory()).isTrue();
@@ -642,11 +617,9 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
-            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
-            USE_WRAPPER,
-            OS.LINUX,
+            makeProcessWrapper(fs),
             LocalSpawnRunnerTest::keepLocalEnvUnchanged);
 
     FileOutErr fileOutErr = new FileOutErr(fs.getPath("/out/stdout"), fs.getPath("/out/stderr"));
@@ -671,10 +644,9 @@
             tempDir,
             Options.getDefaults(LocalExecutionOptions.class),
             resourceManager,
-            /*useProcessWrapper=*/ false,
-            OS.LINUX,
             LocalEnvProvider.forCurrentOs(ImmutableMap.of()),
             /*binTools=*/ null,
+            /*processWrapper=*/ null,
             Mockito.mock(RunfilesTreeUpdater.class));
     FileOutErr fileOutErr =
         new FileOutErr(tempDir.getRelative("stdout"), tempDir.getRelative("stderr"));
@@ -755,11 +727,9 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
-            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
-            USE_WRAPPER,
-            OS.LINUX,
+            makeProcessWrapper(fs),
             LocalSpawnRunnerTest::keepLocalEnvUnchanged);
 
     FileOutErr fileOutErr = new FileOutErr(fs.getPath("/out/stdout"), fs.getPath("/out/stderr"));
@@ -782,11 +752,9 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
-            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
-            USE_WRAPPER,
-            OS.LINUX,
+            makeProcessWrapper(fs),
             LocalSpawnRunnerTest::keepLocalEnvUnchanged);
 
     FileOutErr fileOutErr = new FileOutErr(fs.getPath("/out/stdout"), fs.getPath("/out/stderr"));
@@ -813,11 +781,9 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
-            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
-            USE_WRAPPER,
-            OS.LINUX,
+            makeProcessWrapper(fs),
             localEnvProvider);
 
     FileOutErr fileOutErr = new FileOutErr(fs.getPath("/out/stdout"), fs.getPath("/out/stderr"));
@@ -833,51 +799,6 @@
             matches("^/execroot/tmp[0-9a-fA-F]+_[0-9a-fA-F]+/work$"));
   }
 
-  @Test
-  public void useCorrectExtensionOnWindows() throws Exception {
-    // TODO(#3536): Make this test work on Windows.
-    // The Command API implicitly absolutizes the path, and we get weird paths on Windows:
-    // T:\execroot\execroot\_bin\process-wrapper.exe
-    assumeTrue(OS.getCurrent() != OS.WINDOWS);
-
-    FileSystem fs = setupEnvironmentForFakeExecution();
-
-    SubprocessFactory factory = mock(SubprocessFactory.class);
-    ArgumentCaptor<SubprocessBuilder> captor = ArgumentCaptor.forClass(SubprocessBuilder.class);
-    when(factory.create(captor.capture())).thenReturn(new FinishedSubprocess(0));
-    SubprocessBuilder.setDefaultSubprocessFactory(factory);
-
-    LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
-    options.localSigkillGraceSeconds = 654;
-    LocalSpawnRunner runner =
-        new TestedLocalSpawnRunner(
-            fs.getPath("/execroot"),
-            fs.getPath("/embedded_bin"),
-            options,
-            resourceManager,
-            USE_WRAPPER,
-            OS.WINDOWS,
-            LocalSpawnRunnerTest::keepLocalEnvUnchanged);
-
-    FileOutErr fileOutErr = new FileOutErr(fs.getPath("/out/stdout"), fs.getPath("/out/stderr"));
-    SpawnExecutionContextForTesting policy = new SpawnExecutionContextForTesting(fileOutErr);
-    policy.timeoutMillis = 321 * 1000L;
-    assertThat(fs.getPath("/execroot").createDirectory()).isTrue();
-    SpawnResult result = runner.execAsync(SIMPLE_SPAWN, policy).get();
-    verify(factory).create(any(SubprocessBuilder.class));
-    assertThat(result.status()).isEqualTo(SpawnResult.Status.SUCCESS);
-
-    assertThat(captor.getValue().getArgv())
-        .containsExactlyElementsIn(
-            ImmutableList.of(
-                // process-wrapper timeout grace_time stdout stderr
-                "/embedded_bin/process-wrapper.exe",
-                "--timeout=321",
-                "--kill_delay=654",
-                "/bin/echo",
-                "Hi!"));
-  }
-
   /**
    * Copies the {@code process-wrapper} tool into the path under the temporary execRoot where the
    * {@link LocalSpawnRunner} expects to find it.
@@ -978,7 +899,8 @@
     Path embeddedBinaries = getTemporaryEmbeddedBin(fs);
     BinTools binTools = BinTools.forEmbeddedBin(embeddedBinaries,
         ImmutableList.of("process-wrapper"));
-    copyProcessWrapperIntoExecRoot(binTools.getEmbeddedPath("process-wrapper"));
+    Path processWrapperPath = binTools.getEmbeddedPath("process-wrapper");
+    copyProcessWrapperIntoExecRoot(processWrapperPath);
     Path cpuTimeSpenderPath = copyCpuTimeSpenderIntoExecRoot(execRoot);
 
     LocalSpawnRunner runner =
@@ -986,10 +908,9 @@
             execRoot,
             options,
             resourceManager,
-            USE_WRAPPER,
-            OS.LINUX,
             LocalSpawnRunnerTest::keepLocalEnvUnchanged,
             binTools,
+            new ProcessWrapper(processWrapperPath),
             Mockito.mock(RunfilesTreeUpdater.class));
 
     Spawn spawn =
@@ -1042,7 +963,8 @@
     Path embeddedBinaries = getTemporaryEmbeddedBin(fs);
     BinTools binTools = BinTools.forEmbeddedBin(embeddedBinaries,
         ImmutableList.of("process-wrapper"));
-    copyProcessWrapperIntoExecRoot(binTools.getEmbeddedPath("process-wrapper"));
+    Path processWrapperPath = binTools.getEmbeddedPath("process-wrapper");
+    copyProcessWrapperIntoExecRoot(processWrapperPath);
     Path cpuTimeSpenderPath = copyCpuTimeSpenderIntoExecRoot(execRoot);
 
     LocalSpawnRunner runner =
@@ -1050,10 +972,9 @@
             execRoot,
             options,
             resourceManager,
-            USE_WRAPPER,
-            OS.LINUX,
             LocalSpawnRunnerTest::keepLocalEnvUnchanged,
             binTools,
+            new ProcessWrapper(processWrapperPath),
             Mockito.mock(RunfilesTreeUpdater.class));
 
     Spawn spawn =
@@ -1102,11 +1023,9 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
-            fs.getPath("/embedded_bin"),
             Options.getDefaults(LocalExecutionOptions.class),
             resourceManager,
-            NO_WRAPPER,
-            OS.LINUX,
+            /*processWrapper=*/ null,
             LocalSpawnRunnerTest::keepLocalEnvUnchanged);
 
     FileOutErr fileOutErr = new FileOutErr(fs.getPath("/out/stdout"), fs.getPath("/out/stderr"));
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/ProcessWrapperUtilTest.java b/src/test/java/com/google/devtools/build/lib/runtime/ProcessWrapperTest.java
similarity index 72%
rename from src/test/java/com/google/devtools/build/lib/runtime/ProcessWrapperUtilTest.java
rename to src/test/java/com/google/devtools/build/lib/runtime/ProcessWrapperTest.java
index ff1fdd5..63b0746 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/ProcessWrapperUtilTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/ProcessWrapperTest.java
@@ -20,6 +20,7 @@
 import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import java.io.IOException;
 import java.time.Duration;
 import java.util.List;
 import org.junit.Before;
@@ -27,35 +28,41 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-/** Unit tests for {@link ProcessWrapperUtil}. */
+/** Unit tests for {@link ProcessWrapper}. */
 @RunWith(JUnit4.class)
-public final class ProcessWrapperUtilTest {
+public final class ProcessWrapperTest {
+
   private FileSystem testFS;
 
   @Before
-  public final void createFileSystem() {
+  public void setUp() {
     testFS = new InMemoryFileSystem();
   }
 
-  @Test
-  public void testProcessWrapperCommandLineBuilder_BuildsWithoutOptionalArguments() {
-    String processWrapperPath = "process-wrapper";
+  private ProcessWrapper getProcessWrapper(String path) throws IOException {
+    Path processWrapperPath = testFS.getPath(path);
+    processWrapperPath.getParentDirectory().createDirectoryAndParents();
+    processWrapperPath.getOutputStream().close();
+    return new ProcessWrapper(processWrapperPath);
+  }
 
+  @Test
+  public void testProcessWrapperCommandLineBuilder_BuildsWithoutOptionalArguments()
+      throws IOException {
     ImmutableList<String> commandArguments = ImmutableList.of("echo", "hello, world");
 
     ImmutableList<String> expectedCommandLine =
-        ImmutableList.<String>builder().add(processWrapperPath).addAll(commandArguments).build();
+        ImmutableList.<String>builder().add("/some/bin/path").addAll(commandArguments).build();
 
-    List<String> commandLine =
-        ProcessWrapperUtil.commandLineBuilder(processWrapperPath, commandArguments).build();
+    ProcessWrapper processWrapper = getProcessWrapper("/some/bin/path");
+    List<String> commandLine = processWrapper.commandLineBuilder(commandArguments).build();
 
     assertThat(commandLine).containsExactlyElementsIn(expectedCommandLine).inOrder();
   }
 
   @Test
-  public void testProcessWrapperCommandLineBuilder_BuildsWithOptionalArguments() {
-    String processWrapperPath = "process-wrapper";
-
+  public void testProcessWrapperCommandLineBuilder_BuildsWithOptionalArguments()
+      throws IOException {
     ImmutableList<String> commandArguments = ImmutableList.of("echo", "hello, world");
 
     Duration timeout = Duration.ofSeconds(10);
@@ -66,7 +73,7 @@
 
     ImmutableList<String> expectedCommandLine =
         ImmutableList.<String>builder()
-            .add(processWrapperPath)
+            .add("/path/to/process-wrapper")
             .add("--timeout=" + timeout.getSeconds())
             .add("--kill_delay=" + killDelay.getSeconds())
             .add("--stdout=" + stdoutPath)
@@ -75,8 +82,10 @@
             .addAll(commandArguments)
             .build();
 
+    ProcessWrapper processWrapper = getProcessWrapper("/path/to/process-wrapper");
     List<String> commandLine =
-        ProcessWrapperUtil.commandLineBuilder(processWrapperPath, commandArguments)
+        processWrapper
+            .commandLineBuilder(commandArguments)
             .setTimeout(timeout)
             .setKillDelay(killDelay)
             .setStdoutPath(stdoutPath)
diff --git a/src/test/java/com/google/devtools/build/lib/shell/CommandUsingProcessWrapperTest.java b/src/test/java/com/google/devtools/build/lib/shell/CommandUsingProcessWrapperTest.java
index a7c32c1..e6e87e8 100644
--- a/src/test/java/com/google/devtools/build/lib/shell/CommandUsingProcessWrapperTest.java
+++ b/src/test/java/com/google/devtools/build/lib/shell/CommandUsingProcessWrapperTest.java
@@ -17,7 +17,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.ImmutableList;
-import com.google.devtools.build.lib.runtime.ProcessWrapperUtil;
+import com.google.devtools.build.lib.runtime.ProcessWrapper;
 import com.google.devtools.build.lib.testutil.BlazeTestUtils;
 import com.google.devtools.build.lib.testutil.TestConstants;
 import com.google.devtools.build.lib.testutil.TestUtils;
@@ -43,8 +43,11 @@
     testFS = new UnixFileSystem(DigestHashFunction.getDefaultUnchecked());
   }
 
-  private String getProcessWrapperPath() {
-    return BlazeTestUtils.runfilesDir() + "/" + TestConstants.PROCESS_WRAPPER_PATH;
+  private ProcessWrapper getProcessWrapper() {
+    return new ProcessWrapper(
+        testFS
+            .getPath(BlazeTestUtils.runfilesDir())
+            .getRelative(TestConstants.PROCESS_WRAPPER_PATH));
   }
 
   private String getCpuTimeSpenderPath() {
@@ -66,8 +69,7 @@
   public void testProcessWrappedCommand_Echo() throws Exception {
     ImmutableList<String> commandArguments = ImmutableList.of("echo", "even drones can fly away");
 
-    List<String> fullCommandLine =
-        ProcessWrapperUtil.commandLineBuilder(getProcessWrapperPath(), commandArguments).build();
+    List<String> fullCommandLine = getProcessWrapper().commandLineBuilder(commandArguments).build();
 
     Command command = new Command(fullCommandLine.toArray(new String[0]));
     CommandResult commandResult = command.execute();
@@ -88,7 +90,8 @@
     Path statisticsFilePath = outputDir.getRelative("stats.out");
 
     List<String> fullCommandLine =
-        ProcessWrapperUtil.commandLineBuilder(getProcessWrapperPath(), commandArguments)
+        getProcessWrapper()
+            .commandLineBuilder(commandArguments)
             .setStatisticsPath(statisticsFilePath)
             .build();
 
diff --git a/src/test/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategyTest.java b/src/test/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategyTest.java
index ff66eb2..32154dc 100644
--- a/src/test/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategyTest.java
@@ -150,6 +150,7 @@
                 resourceManager,
                 (env, binTools1, fallbackTmpDir) -> ImmutableMap.copyOf(env),
                 binTools,
+                /*processWrapper=*/ null,
                 Mockito.mock(RunfilesTreeUpdater.class)));
     this.executor =
         new TestExecutorBuilder(fileSystem, directories, binTools)