Simplify BinTools: There is no need to create a "_bin" directory under the execRoot. If running the binary locally, we can execute from the install_base directly.

RELNOTES: None
PiperOrigin-RevId: 219193314
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
index a443070..e48494f 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
@@ -28,7 +28,6 @@
 import com.google.devtools.build.lib.actions.ActionInputPrefetcher;
 import com.google.devtools.build.lib.actions.ArtifactFactory;
 import com.google.devtools.build.lib.actions.BuildFailedException;
-import com.google.devtools.build.lib.actions.ExecException;
 import com.google.devtools.build.lib.actions.Executor;
 import com.google.devtools.build.lib.actions.ExecutorInitException;
 import com.google.devtools.build.lib.actions.LocalHostCapacity;
@@ -125,10 +124,10 @@
     this.runtime = env.getRuntime();
     this.request = request;
 
-    // Create tools before getting the strategies from the modules as some of them need tools to
-    // determine whether the host actually supports certain strategies (e.g. sandboxing).
-    try (SilentCloseable closeable = Profiler.instance().profile("createToolsSymlinks")) {
-      createToolsSymlinks();
+    try {
+      env.getExecRoot().createDirectoryAndParents();
+    } catch (IOException e) {
+      throw new ExecutorInitException("Execroot creation failed", e);
     }
 
     ExecutorBuilder builder = new ExecutorBuilder();
@@ -436,14 +435,6 @@
     }
   }
 
-  private void createToolsSymlinks() throws ExecutorInitException {
-    try {
-      env.getBlazeWorkspace().getBinTools().setupBuildTools(env.getWorkspaceName());
-    } catch (ExecException e) {
-      throw new ExecutorInitException("Tools symlink creation failed", e);
-    }
-  }
-
   private void createActionLogDirectory() throws ExecutorInitException {
     Path directory = env.getActionConsoleOutputDirectory();
     try {
diff --git a/src/main/java/com/google/devtools/build/lib/exec/BinTools.java b/src/main/java/com/google/devtools/build/lib/exec/BinTools.java
index a24ba60..fc384d2 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/BinTools.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/BinTools.java
@@ -15,20 +15,16 @@
 package com.google.devtools.build.lib.exec;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.hash.Hasher;
 import com.google.common.io.ByteStreams;
 import com.google.devtools.build.lib.actions.ActionInput;
-import com.google.devtools.build.lib.actions.EnvironmentalExecException;
-import com.google.devtools.build.lib.actions.ExecException;
 import com.google.devtools.build.lib.actions.FileArtifactValue;
 import com.google.devtools.build.lib.actions.cache.VirtualActionInput;
 import com.google.devtools.build.lib.analysis.BlazeDirectories;
 import com.google.devtools.build.lib.vfs.DigestHashFunction;
 import com.google.devtools.build.lib.vfs.Dirent;
-import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.Symlinks;
@@ -36,30 +32,23 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import javax.annotation.Nullable;
 
 /**
- * Initializes the <execRoot>/_bin/ directory that contains auxiliary tools used during action
- * execution (alarm, etc). The main purpose of this is to make sure that those tools are accessible
- * using relative paths from the execution root.
+ * Maintains a mapping between relative path (from the execution root) to {@link ActionInput}, for
+ * various auxiliary binaries used during action execution (alarm. etc).
  */
 public final class BinTools {
   private final Path embeddedBinariesRoot;
-  private final Path execrootParent;
   private final ImmutableList<String> embeddedTools;
-  private ImmutableMap<String, ActionInput> actionInputs;
-
-  private Path binDir;  // the working bin directory under execRoot
+  private final ImmutableMap<String, ActionInput> actionInputs;
 
   private BinTools(BlazeDirectories directories, ImmutableList<String> tools) {
-    this(
-        directories.getEmbeddedBinariesRoot(),
-        directories.getExecRoot().getParentDirectory(),
-        tools);
+    this(directories.getEmbeddedBinariesRoot(), tools);
   }
 
-  private BinTools(Path embeddedBinariesRoot, Path execrootParent, ImmutableList<String> tools) {
+  private BinTools(Path embeddedBinariesRoot, ImmutableList<String> tools) {
     this.embeddedBinariesRoot = embeddedBinariesRoot;
-    this.execrootParent = execrootParent;
     ImmutableList.Builder<String> builder = ImmutableList.builder();
     // Files under embedded_tools shouldn't be copied to under _bin dir
     // They won't be used during action execution time.
@@ -69,19 +58,17 @@
       }
     }
     this.embeddedTools = builder.build();
-    this.binDir = null;
-  }
 
-  private ImmutableMap<String, ActionInput> populateActionInputMap() {
     ImmutableMap.Builder<String, ActionInput> result = ImmutableMap.builder();
     for (String embeddedPath : embeddedTools) {
-      PathFragment execPath = getExecPath(embeddedPath);
-      Path path = binDir.getRelative(execPath.getBaseName());
+      Path path = getEmbeddedPath(embeddedPath);
+      PathFragment execPath =  PathFragment.create("_bin").getRelative(embeddedPath);
       result.put(embeddedPath, new PathActionInput(path, execPath));
     }
-    return result.build();
+    actionInputs = result.build();
   }
 
+
   /**
    * Creates an instance with the list of embedded tools obtained from scanning the directory
    * into which said binaries were extracted by the launcher.
@@ -97,8 +84,15 @@
    */
   @VisibleForTesting
   public static BinTools empty(BlazeDirectories directories) {
-    return new BinTools(directories, ImmutableList.<String>of())
-        .setBinDir(directories.getWorkspace().getBaseName());
+    return new BinTools(directories, ImmutableList.of());
+  }
+
+  /**
+   * Creates an instance for testing with the given embedded binaries root.
+   */
+  @VisibleForTesting
+  public static BinTools forEmbeddedBin(Path embeddedBinariesRoot, Iterable<String> tools) {
+    return new BinTools(embeddedBinariesRoot, ImmutableList.copyOf(tools));
   }
 
   /**
@@ -108,8 +102,7 @@
    */
   @VisibleForTesting
   public static BinTools forUnitTesting(BlazeDirectories directories, Iterable<String> tools) {
-    return new BinTools(directories, ImmutableList.copyOf(tools))
-        .setBinDir(directories.getWorkspace().getBaseName());
+    return new BinTools(directories, ImmutableList.copyOf(tools));
   }
 
   /**
@@ -119,10 +112,7 @@
    */
   @VisibleForTesting
   public static BinTools forUnitTesting(Path execroot, Iterable<String> tools) {
-    return new BinTools(
-        execroot.getRelative("/fake/embedded/tools"),
-        execroot.getParentDirectory(),
-        ImmutableList.copyOf(tools)).setBinDir(execroot.getBaseName());
+    return new BinTools(execroot.getRelative("/fake/embedded/tools"), ImmutableList.copyOf(tools));
   }
 
   /**
@@ -131,8 +121,8 @@
    */
   @VisibleForTesting
   public static BinTools forIntegrationTesting(
-      BlazeDirectories directories, Iterable<String> tools, String repositoryName) {
-    return new BinTools(directories, ImmutableList.copyOf(tools)).setBinDir(repositoryName);
+      BlazeDirectories directories, Iterable<String> tools) {
+    return new BinTools(directories, ImmutableList.copyOf(tools));
   }
 
   private static void scanDirectoryRecursively(
@@ -162,68 +152,15 @@
    * Returns an action input for the given embedded tool.
    */
   public ActionInput getActionInput(String embeddedPath) {
-    if (actionInputs == null) {
-      actionInputs = populateActionInputMap();
-    }
     return actionInputs.get(embeddedPath);
   }
 
-  public PathFragment getExecPath(String embedPath) {
+  @Nullable
+  public Path getEmbeddedPath(String embedPath) {
     if (!embeddedTools.contains(embedPath)) {
       return null;
     }
-    return PathFragment.create("_bin").getRelative(PathFragment.create(embedPath).getBaseName());
-  }
-
-  private BinTools setBinDir(String workspaceName) {
-    binDir = execrootParent.getRelative(workspaceName).getRelative("_bin");
-    return this;
-  }
-
-  /**
-   * Initializes the build tools not available at absolute paths. Note that
-   * these must be constant across all configurations.
-   */
-  public void setupBuildTools(String workspaceName) throws ExecException {
-    setBinDir(workspaceName);
-    try {
-      binDir.createDirectoryAndParents();
-    } catch (IOException e) {
-      throw new EnvironmentalExecException("could not create directory '" + binDir  + "'", e);
-    }
-
-    for (String embeddedPath : embeddedTools) {
-      setupTool(embeddedPath);
-    }
-  }
-
-  private void setupTool(String embeddedPath) throws ExecException {
-    Preconditions.checkNotNull(binDir);
-    Path sourcePath = embeddedBinariesRoot.getRelative(embeddedPath);
-    Path linkPath = binDir.getRelative(PathFragment.create(embeddedPath).getBaseName());
-    linkTool(sourcePath, linkPath);
-  }
-
-  private void linkTool(Path sourcePath, Path linkPath) throws ExecException {
-    if (linkPath.getFileSystem().supportsSymbolicLinksNatively(linkPath)) {
-      try {
-        if (!linkPath.isSymbolicLink()) {
-          // ensureSymbolicLink() does not handle the case where there is already
-          // a file with the same name, so we need to handle it here.
-          linkPath.delete();
-        }
-        FileSystemUtils.ensureSymbolicLink(linkPath, sourcePath);
-      } catch (IOException e) {
-        throw new EnvironmentalExecException("failed to link '" + sourcePath + "'", e);
-      }
-    } else {
-      // For file systems that do not support linking, copy.
-      try {
-        FileSystemUtils.copyTool(sourcePath, linkPath);
-      } catch (IOException e) {
-        throw new EnvironmentalExecException("failed to copy '" + sourcePath + "'" , e);
-      }
-    }
+    return embeddedBinariesRoot.getRelative(embedPath);
   }
 
   /** An ActionInput pointing at an absolute path. */
diff --git a/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java b/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java
index 0246c94..dc8711c 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java
@@ -85,7 +85,8 @@
       ImmutableMap<String, String> shellEnvironment,
       OutErr outErr)
       throws CommandException {
-    List<String> argv = getSpawnArgumentList(execRoot, binTools.getExecPath(BUILD_RUNFILES));
+    List<String> argv = getSpawnArgumentList(execRoot,
+        binTools.getEmbeddedPath(BUILD_RUNFILES).asFragment());
     Preconditions.checkNotNull(shellEnvironment);
     Command command =
         new CommandBuilder().addArgs(argv).setWorkingDir(execRoot).setEnv(shellEnvironment).build();
diff --git a/src/main/java/com/google/devtools/build/lib/exec/apple/XcodeLocalEnvProvider.java b/src/main/java/com/google/devtools/build/lib/exec/apple/XcodeLocalEnvProvider.java
index 646b50b..77d7fb0 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/apple/XcodeLocalEnvProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/apple/XcodeLocalEnvProvider.java
@@ -16,6 +16,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
+import com.google.devtools.build.lib.exec.BinTools;
 import com.google.devtools.build.lib.exec.local.LocalEnvProvider;
 import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
 import com.google.devtools.build.lib.rules.apple.DottedVersion;
@@ -64,7 +65,7 @@
 
   @Override
   public Map<String, String> rewriteLocalEnv(
-      Map<String, String> env, Path execRoot, String fallbackTmpDir) throws IOException {
+      Map<String, String> env, BinTools binTools, String fallbackTmpDir) throws IOException {
     boolean containsXcodeVersion = env.containsKey(AppleConfiguration.XCODE_VERSION_ENV_NAME);
     boolean containsAppleSdkVersion =
         env.containsKey(AppleConfiguration.APPLE_SDK_VERSION_ENV_NAME);
@@ -91,7 +92,7 @@
     String developerDir = "";
     if (containsXcodeVersion) {
       String version = env.get(AppleConfiguration.XCODE_VERSION_ENV_NAME);
-      developerDir = getDeveloperDir(execRoot, DottedVersion.fromString(version));
+      developerDir = getDeveloperDir(binTools, DottedVersion.fromString(version));
       newEnvBuilder.put("DEVELOPER_DIR", developerDir);
     }
     if (containsAppleSdkVersion) {
@@ -214,16 +215,16 @@
    * operation, always call {@link #getDeveloperDir(Path, DottedVersion)} instead, which does
    * caching.
    *
-   * @param execRoot the execution root path, used to locate the cache file
+   * @param binTools the {@link BinTools}, used to locate the cache file
    * @param version the xcode version number to look up
    * @return an absolute path to the root of the Xcode developer directory
    * @throws IOException if there is an issue with obtaining the path from the spawned process,
    *     either because there is no installed xcode with the given version, or there was an
    *     unexpected issue finding or running the tool
    */
-  private static String queryDeveloperDir(Path execRoot, DottedVersion version)
+  private static String queryDeveloperDir(BinTools binTools, DottedVersion version)
       throws IOException {
-    String xcodeLocatorPath = execRoot.getRelative("_bin/xcode-locator").getPathString();
+    String xcodeLocatorPath = binTools.getEmbeddedPath("xcode-locator").getPathString();
     try {
       CommandResult xcodeLocatorResult =
           new Command(new String[] {xcodeLocatorPath, version.toString()}).execute();
@@ -273,21 +274,21 @@
    * external sources in the system. Values are cached in-memory throughout the lifetime of the
    * Bazel server.
    *
-   * @param execRoot the execution root path, used to locate the cache file
+   * @param binTools the {@link BinTools} path, used to locate the cache file
    * @param version the xcode version number to look up
    * @return an absolute path to the root of the Xcode developer directory
    * @throws IOException if there is an issue with obtaining the path from the spawned process,
    *     either because there is no installed xcode with the given version, or there was an
    *     unexpected issue finding or running the tool
    */
-  private static String getDeveloperDir(Path execRoot, DottedVersion version)
+  private static String getDeveloperDir(BinTools binTools, DottedVersion version)
       throws IOException {
     try {
       return developerDirCache.computeIfAbsent(
           version.toString(),
           (key) -> {
             try {
-              String developerDir = queryDeveloperDir(execRoot, version);
+              String developerDir = queryDeveloperDir(binTools, version);
               log.info("Queried Xcode developer dir with key " + key + " and got " + developerDir);
               return developerDir;
             } catch (IOException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/exec/local/LocalEnvProvider.java b/src/main/java/com/google/devtools/build/lib/exec/local/LocalEnvProvider.java
index 5af7a19..7ae3e75 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/local/LocalEnvProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/local/LocalEnvProvider.java
@@ -13,7 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.lib.exec.local;
 
-import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.exec.BinTools;
 import java.io.IOException;
 import java.util.Map;
 
@@ -27,7 +27,7 @@
       new LocalEnvProvider() {
         @Override
         public Map<String, String> rewriteLocalEnv(
-            Map<String, String> env, Path execRoot, String fallbackTmpDir) {
+            Map<String, String> env, BinTools binTools, String fallbackTmpDir) {
           return env;
         }
       };
@@ -36,13 +36,12 @@
    * Rewrites a {@code Spawn}'s the environment if necessary.
    *
    * @param env the Spawn's environment to rewrite
-   * @param execRoot the path where the Spawn is executed
+   * @param binTools used to find built-in tool paths
    * @param fallbackTmpDir an absolute path to a temp directory that the Spawn could use. The
    *     particular implementation of {@link LocalEnvProvider} may choose to use some other path,
    *     typically the "TMPDIR" environment variable in the Bazel client's environment, but if
    *     that's unavailable, the implementation may decide to use this {@code fallbackTmpDir}.
    */
   Map<String, String> rewriteLocalEnv(
-      Map<String, String> env, Path execRoot, String fallbackTmpDir)
-      throws IOException;
+      Map<String, String> env, BinTools binTools, String fallbackTmpDir) throws IOException;
 }
diff --git a/src/main/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunner.java
index cd4075f..fa89b4b 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunner.java
@@ -32,6 +32,7 @@
 import com.google.devtools.build.lib.actions.Spawns;
 import com.google.devtools.build.lib.actions.cache.VirtualActionInput;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.exec.BinTools;
 import com.google.devtools.build.lib.exec.SpawnRunner;
 import com.google.devtools.build.lib.runtime.ProcessWrapperUtil;
 import com.google.devtools.build.lib.shell.AbnormalTerminationException;
@@ -76,13 +77,22 @@
   private final LocalExecutionOptions localExecutionOptions;
 
   private final boolean useProcessWrapper;
-  private final String processWrapper;
+  private final Path processWrapper;
 
   private final LocalEnvProvider localEnvProvider;
+  private final BinTools binTools;
 
   // TODO(b/62588075): Move this logic to ProcessWrapperUtil?
-  protected static Path getProcessWrapper(Path execRoot, OS localOs) {
-    return execRoot.getRelative("_bin/process-wrapper" + OsUtils.executableExtension(localOs));
+  private static Path getProcessWrapper(BinTools binTools, OS localOs) {
+    // We expect binTools to be null only under testing.
+    return binTools == null
+        ? null
+        : binTools.getEmbeddedPath("process-wrapper" + OsUtils.executableExtension(localOs));
+  }
+
+  private static boolean processWrapperExists(BinTools binTools) {
+    Path wrapper = getProcessWrapper(binTools, OS.getCurrent());
+    return wrapper != null && wrapper.exists();
   }
 
   public LocalSpawnRunner(
@@ -91,28 +101,32 @@
       ResourceManager resourceManager,
       boolean useProcessWrapper,
       OS localOs,
-      LocalEnvProvider localEnvProvider) {
+      LocalEnvProvider localEnvProvider,
+      BinTools binTools) {
     this.execRoot = execRoot;
-    this.processWrapper = getProcessWrapper(execRoot, localOs).getPathString();
+    this.processWrapper = getProcessWrapper(binTools, localOs);
     this.localExecutionOptions = Preconditions.checkNotNull(localExecutionOptions);
     this.hostName = NetUtil.getCachedShortHostName();
     this.resourceManager = resourceManager;
     this.useProcessWrapper = useProcessWrapper;
     this.localEnvProvider = localEnvProvider;
+    this.binTools = binTools;
   }
 
   public LocalSpawnRunner(
       Path execRoot,
       LocalExecutionOptions localExecutionOptions,
       ResourceManager resourceManager,
-      LocalEnvProvider localEnvProvider) {
+      LocalEnvProvider localEnvProvider,
+      BinTools binTools) {
     this(
         execRoot,
         localExecutionOptions,
         resourceManager,
-        OS.getCurrent() != OS.WINDOWS && getProcessWrapper(execRoot, OS.getCurrent()).exists(),
+        OS.getCurrent() != OS.WINDOWS && processWrapperExists(binTools),
         OS.getCurrent(),
-        localEnvProvider);
+        localEnvProvider,
+        binTools);
   }
 
   @Override
@@ -271,7 +285,7 @@
         commandTmpDir.createDirectory();
         Map<String, String> environment =
             localEnvProvider.rewriteLocalEnv(
-                spawn.getEnvironment(), execRoot, commandTmpDir.getPathString());
+                spawn.getEnvironment(), binTools, commandTmpDir.getPathString());
         if (useProcessWrapper) {
           // If the process wrapper is enabled, we use its timeout feature, which first interrupts
           // the subprocess and only kills it after a grace period so that the subprocess can output
@@ -281,7 +295,8 @@
           stdOut = ByteStreams.nullOutputStream();
           stdErr = ByteStreams.nullOutputStream();
           ProcessWrapperUtil.CommandLineBuilder commandLineBuilder =
-              ProcessWrapperUtil.commandLineBuilder(processWrapper, spawn.getArguments())
+              ProcessWrapperUtil.commandLineBuilder(processWrapper.getPathString(),
+                  spawn.getArguments())
                   .setStdoutPath(getPathOrDevNull(outErr.getOutputPath()))
                   .setStderrPath(getPathOrDevNull(outErr.getErrorPath()))
                   .setTimeout(context.getTimeout())
diff --git a/src/main/java/com/google/devtools/build/lib/exec/local/PosixLocalEnvProvider.java b/src/main/java/com/google/devtools/build/lib/exec/local/PosixLocalEnvProvider.java
index 7a8bbe9..2c154bc 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/local/PosixLocalEnvProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/local/PosixLocalEnvProvider.java
@@ -16,7 +16,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
-import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.exec.BinTools;
 import java.util.Map;
 
 /** {@link LocalEnvProvider} implementation for actions running on Unix-like platforms. */
@@ -41,7 +41,7 @@
    */
   @Override
   public Map<String, String> rewriteLocalEnv(
-      Map<String, String> env, Path execRoot, String fallbackTmpDir) {
+      Map<String, String> env, BinTools binTools, String fallbackTmpDir) {
     ImmutableMap.Builder<String, String> result = ImmutableMap.builder();
     result.putAll(Maps.filterKeys(env, k -> !k.equals("TMPDIR")));
     String p = clientEnv.get("TMPDIR");
diff --git a/src/main/java/com/google/devtools/build/lib/exec/local/WindowsLocalEnvProvider.java b/src/main/java/com/google/devtools/build/lib/exec/local/WindowsLocalEnvProvider.java
index 96ce78e..b1700f6 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/local/WindowsLocalEnvProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/local/WindowsLocalEnvProvider.java
@@ -16,7 +16,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
-import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.exec.BinTools;
 import java.util.Map;
 
 /** {@link LocalEnvProvider} implementation for actions running on Windows. */
@@ -48,7 +48,7 @@
    */
   @Override
   public Map<String, String> rewriteLocalEnv(
-      Map<String, String> env, Path execRoot, String fallbackTmpDir) {
+      Map<String, String> env, BinTools binTools, String fallbackTmpDir) {
     ImmutableMap.Builder<String, String> result = ImmutableMap.builder();
     result.putAll(Maps.filterKeys(env, k -> !k.equals("TMP") && !k.equals("TEMP")));
     String p = clientEnv.get("TMP");
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/ProcessWrapperUtil.java b/src/main/java/com/google/devtools/build/lib/runtime/ProcessWrapperUtil.java
index b830259..ef30e56 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/ProcessWrapperUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/ProcessWrapperUtil.java
@@ -16,7 +16,6 @@
 
 import com.google.devtools.build.lib.util.OsUtils;
 import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.vfs.PathFragment;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
@@ -37,8 +36,7 @@
 
   /** Returns the {@link Path} of the process wrapper binary, or null if it doesn't exist. */
   public static Path getProcessWrapper(CommandEnvironment cmdEnv) {
-    PathFragment execPath = cmdEnv.getBlazeWorkspace().getBinTools().getExecPath(PROCESS_WRAPPER);
-    return execPath != null ? cmdEnv.getExecRoot().getRelative(execPath) : null;
+    return cmdEnv.getBlazeWorkspace().getBinTools().getEmbeddedPath(PROCESS_WRAPPER);
   }
 
   /** Returns a new {@link CommandLineBuilder} for the process wrapper tool. */
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java
index 2d4eb24..fb8349b 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java
@@ -26,6 +26,7 @@
 import com.google.devtools.build.lib.actions.SpawnResult;
 import com.google.devtools.build.lib.actions.SpawnResult.Status;
 import com.google.devtools.build.lib.actions.UserExecException;
+import com.google.devtools.build.lib.exec.BinTools;
 import com.google.devtools.build.lib.exec.ExecutionOptions;
 import com.google.devtools.build.lib.exec.SpawnRunner;
 import com.google.devtools.build.lib.runtime.CommandEnvironment;
@@ -53,12 +54,14 @@
   private final SandboxOptions sandboxOptions;
   private final boolean verboseFailures;
   private final ImmutableSet<Path> inaccessiblePaths;
+  protected final BinTools binTools;
 
   public AbstractSandboxSpawnRunner(CommandEnvironment cmdEnv) {
     this.sandboxOptions = cmdEnv.getOptions().getOptions(SandboxOptions.class);
     this.verboseFailures = cmdEnv.getOptions().getOptions(ExecutionOptions.class).verboseFailures;
     this.inaccessiblePaths =
         sandboxOptions.getInaccessiblePaths(cmdEnv.getRuntime().getFileSystem());
+    this.binTools = cmdEnv.getBlazeWorkspace().getBinTools();
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java
index e4d3c95..22a35c3 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java
@@ -223,7 +223,7 @@
     sandboxExecRoot.createDirectory();
 
     Map<String, String> environment =
-        localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), execRoot, "/tmp");
+        localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), binTools, "/tmp");
 
     final HashSet<Path> writableDirs = new HashSet<>(alwaysWritableDirs);
     ImmutableSet<Path> extraWritableDirs = getWritableDirs(sandboxExecRoot, environment);
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/DockerSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/DockerSandboxedSpawnRunner.java
index a60b5dd..ac0c40d 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/DockerSandboxedSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/DockerSandboxedSpawnRunner.java
@@ -209,7 +209,7 @@
     sandboxExecRoot.createDirectory();
 
     Map<String, String> environment =
-        localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), execRoot, "/tmp");
+        localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), binTools, "/tmp");
 
     SandboxOutputs outputs = SandboxHelpers.getOutputs(spawn);
     Duration timeout = context.getTimeout();
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxUtil.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxUtil.java
index ca5dd15..77ba2bd 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxUtil.java
@@ -21,7 +21,6 @@
 import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.util.OsUtils;
 import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.vfs.PathFragment;
 import java.time.Duration;
 import java.util.List;
 import java.util.Map;
@@ -40,8 +39,7 @@
 
   /** Returns the path of the {@code linux-sandbox} binary, or null if it doesn't exist. */
   public static Path getLinuxSandbox(CommandEnvironment cmdEnv) {
-    PathFragment execPath = cmdEnv.getBlazeWorkspace().getBinTools().getExecPath(LINUX_SANDBOX);
-    return execPath != null ? cmdEnv.getExecRoot().getRelative(execPath) : null;
+    return cmdEnv.getBlazeWorkspace().getBinTools().getEmbeddedPath(LINUX_SANDBOX);
   }
 
   /** Returns a new command line builder for the {@code linux-sandbox} tool. */
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java
index 4e51707..c0d3cec 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java
@@ -147,7 +147,7 @@
     sandboxExecRoot.createDirectory();
 
     Map<String, String> environment =
-        localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), execRoot, "/tmp");
+        localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), binTools, "/tmp");
 
     ImmutableSet<Path> writableDirs = getWritableDirs(sandboxExecRoot, environment);
     SandboxOutputs outputs = SandboxHelpers.getOutputs(spawn);
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java
index 123f594..8401841 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java
@@ -80,7 +80,7 @@
     sandboxExecRoot.createDirectory();
 
     Map<String, String> environment =
-        localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), execRoot, "/tmp");
+        localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), binTools, "/tmp");
 
     Duration timeout = context.getTimeout();
     ProcessWrapperUtil.CommandLineBuilder commandLineBuilder =
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
index 0e0ba2d..7a62649 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
@@ -269,7 +269,8 @@
             env.getExecRoot(),
             localExecutionOptions,
             ResourceManager.instance(),
-            localEnvProvider);
+            localEnvProvider,
+            env.getBlazeWorkspace().getBinTools());
   }
 
   private static final class SandboxFallbackSpawnRunner implements SpawnRunner {
diff --git a/src/main/java/com/google/devtools/build/lib/standalone/StandaloneModule.java b/src/main/java/com/google/devtools/build/lib/standalone/StandaloneModule.java
index d07b8bd..4c8631d 100644
--- a/src/main/java/com/google/devtools/build/lib/standalone/StandaloneModule.java
+++ b/src/main/java/com/google/devtools/build/lib/standalone/StandaloneModule.java
@@ -89,6 +89,7 @@
             env.getExecRoot(),
             localExecutionOptions,
             ResourceManager.instance(),
-            localEnvProvider);
+            localEnvProvider,
+            env.getBlazeWorkspace().getBinTools());
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java
index 075ecfb..f912f04 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java
@@ -151,7 +151,8 @@
             localEnvProvider,
             env.getOptions()
                 .getOptions(SandboxOptions.class)
-                .symlinkedSandboxExpandsTreeArtifactsInRunfilesTree);
+                .symlinkedSandboxExpandsTreeArtifactsInRunfilesTree,
+            env.getBlazeWorkspace().getBinTools());
     builder.addActionContext(new WorkerSpawnStrategy(env.getExecRoot(), spawnRunner));
 
     builder.addStrategyByContext(SpawnActionContext.class, "standalone");
@@ -166,7 +167,8 @@
         env.getExecRoot(),
         localExecutionOptions,
         ResourceManager.instance(),
-        localEnvProvider);
+        localEnvProvider,
+        env.getBlazeWorkspace().getBinTools());
   }
 
   private static LocalEnvProvider createLocalEnvProvider(CommandEnvironment env) {
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
index 4d6b0c5..bbb2b0c 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
@@ -35,6 +35,7 @@
 import com.google.devtools.build.lib.actions.UserExecException;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.exec.BinTools;
 import com.google.devtools.build.lib.exec.SpawnRunner;
 import com.google.devtools.build.lib.exec.local.LocalEnvProvider;
 import com.google.devtools.build.lib.sandbox.SandboxHelpers;
@@ -78,6 +79,7 @@
   private final SpawnRunner fallbackRunner;
   private final LocalEnvProvider localEnvProvider;
   private final boolean sandboxUsesExpandedTreeArtifactsInRunfiles;
+  private final BinTools binTools;
 
   public WorkerSpawnRunner(
       Path execRoot,
@@ -86,7 +88,8 @@
       EventHandler reporter,
       SpawnRunner fallbackRunner,
       LocalEnvProvider localEnvProvider,
-      boolean sandboxUsesExpandedTreeArtifactsInRunfiles) {
+      boolean sandboxUsesExpandedTreeArtifactsInRunfiles,
+      BinTools binTools) {
     this.execRoot = execRoot;
     this.workers = Preconditions.checkNotNull(workers);
     this.extraFlags = extraFlags;
@@ -94,6 +97,7 @@
     this.fallbackRunner = fallbackRunner;
     this.localEnvProvider = localEnvProvider;
     this.sandboxUsesExpandedTreeArtifactsInRunfiles = sandboxUsesExpandedTreeArtifactsInRunfiles;
+    this.binTools = binTools;
   }
 
   @Override
@@ -132,7 +136,7 @@
     List<String> flagFiles = new ArrayList<>();
     ImmutableList<String> workerArgs = splitSpawnArgsIntoWorkerArgsAndFlagFiles(spawn, flagFiles);
     Map<String, String> env =
-        localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), execRoot, "/tmp");
+        localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), binTools, "/tmp");
 
     MetadataProvider inputFileCache = context.getMetadataProvider();
 
diff --git a/src/test/java/com/google/devtools/build/lib/exec/apple/XcodeLocalEnvProviderTest.java b/src/test/java/com/google/devtools/build/lib/exec/apple/XcodeLocalEnvProviderTest.java
index 7b6c8c3..38e6832 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/apple/XcodeLocalEnvProviderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/apple/XcodeLocalEnvProviderTest.java
@@ -18,6 +18,8 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.exec.BinTools;
 import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
 import com.google.devtools.build.lib.util.OS;
 import com.google.devtools.build.lib.vfs.DigestHashFunction;
@@ -42,7 +44,7 @@
               ImmutableMap.<String, String>of(
                   AppleConfiguration.APPLE_SDK_VERSION_ENV_NAME, "8.4",
                   AppleConfiguration.APPLE_SDK_PLATFORM_ENV_NAME, "iPhoneSimulator"),
-              fs.getPath("/tmp"),
+              BinTools.forUnitTesting(fs.getPath("/tmp"), ImmutableSet.of("xcode-locator")),
               "bazel");
       fail("action should fail due to being unable to resolve SDKROOT");
     } catch (IOException e) {
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 5b2daed..8ead6ea 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
@@ -20,7 +20,6 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.matches;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -40,6 +39,7 @@
 import com.google.devtools.build.lib.actions.ResourceSet;
 import com.google.devtools.build.lib.actions.Spawn;
 import com.google.devtools.build.lib.actions.SpawnResult;
+import com.google.devtools.build.lib.exec.BinTools;
 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;
@@ -53,6 +53,7 @@
 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;
@@ -91,6 +92,7 @@
   private static class TestedLocalSpawnRunner extends LocalSpawnRunner {
     public TestedLocalSpawnRunner(
         Path execRoot,
+        Path embeddedBin,
         LocalExecutionOptions localExecutionOptions,
         ResourceManager resourceManager,
         boolean useProcessWrapper,
@@ -102,7 +104,11 @@
           resourceManager,
           useProcessWrapper,
           localOs,
-          localEnvProvider);
+          localEnvProvider,
+          useProcessWrapper
+              ? BinTools.forEmbeddedBin(embeddedBin,
+                  ImmutableList.of("process-wrapper" + OsUtils.executableExtension(localOs)))
+              : null);
     }
 
     // Rigged to act on supplied filesystem (e.g. InMemoryFileSystem) for testing purposes
@@ -308,6 +314,7 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
+            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
             USE_WRAPPER,
@@ -328,7 +335,7 @@
     assertThat(captor.getValue().getArgv())
         .containsExactlyElementsIn(
             ImmutableList.of(
-                "/execroot/_bin/process-wrapper",
+                "/embedded_bin/process-wrapper",
                 "--timeout=123",
                 "--kill_delay=456",
                 "--stdout=/out/stdout",
@@ -361,7 +368,8 @@
     Path execRoot = fs.getPath("/execroot");
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
-            execRoot, options, resourceManager, USE_WRAPPER, OS.LINUX, LocalEnvProvider.UNMODIFIED);
+            execRoot, fs.getPath("/embedded_bin"), options, resourceManager, USE_WRAPPER, OS.LINUX,
+            LocalEnvProvider.UNMODIFIED);
     ParamFileActionInput paramFileActionInput =
         new ParamFileActionInput(
             PathFragment.create("some/dir/params"),
@@ -410,6 +418,7 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
+            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
             NO_WRAPPER,
@@ -454,6 +463,7 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
+            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
             USE_WRAPPER,
@@ -474,7 +484,7 @@
         .containsExactlyElementsIn(
             ImmutableList.of(
                 // process-wrapper timeout grace_time stdout stderr
-                "/execroot/_bin/process-wrapper",
+                "/embedded_bin/process-wrapper",
                 "--timeout=0",
                 "--kill_delay=15",
                 "--stdout=/out/stdout",
@@ -499,6 +509,7 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
+            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
             USE_WRAPPER,
@@ -534,6 +545,7 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
+            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
             USE_WRAPPER,
@@ -584,6 +596,7 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
+            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
             USE_WRAPPER,
@@ -615,6 +628,7 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
+            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
             USE_WRAPPER,
@@ -641,6 +655,7 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
+            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
             USE_WRAPPER,
@@ -671,6 +686,7 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
+            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
             USE_WRAPPER,
@@ -686,7 +702,7 @@
     verify(localEnvProvider)
         .rewriteLocalEnv(
             any(),
-            eq(fs.getPath("/execroot")),
+            any(),
             matches("^/execroot/tmp[0-9a-fA-F]+_[0-9a-fA-F]+/work$"));
   }
 
@@ -709,6 +725,7 @@
     LocalSpawnRunner runner =
         new TestedLocalSpawnRunner(
             fs.getPath("/execroot"),
+            fs.getPath("/embedded_bin"),
             options,
             resourceManager,
             USE_WRAPPER,
@@ -727,7 +744,7 @@
         .containsExactlyElementsIn(
             ImmutableList.of(
                 // process-wrapper timeout grace_time stdout stderr
-                "/execroot/_bin/process-wrapper.exe",
+                "/embedded_bin/process-wrapper.exe",
                 "--timeout=321",
                 "--kill_delay=654",
                 "--stdout=/out/stdout",
@@ -740,7 +757,7 @@
    * Copies the {@code process-wrapper} tool into the path under the temporary execRoot where the
    * {@link LocalSpawnRunner} expects to find it.
    */
-  private Path copyProcessWrapperIntoExecRoot(Path execRoot) throws IOException {
+  private void copyProcessWrapperIntoExecRoot(Path wrapperPath) throws IOException {
     File realProcessWrapperFile =
         new File(
             PathFragment.create(BlazeTestUtils.runfilesDir())
@@ -748,19 +765,14 @@
                 .getPathString());
     assertThat(realProcessWrapperFile.exists()).isTrue();
 
-    Path binDirectoryPath = execRoot.getRelative("_bin");
-    binDirectoryPath.createDirectory();
+    wrapperPath.createDirectoryAndParents();
+    File wrapperFile = wrapperPath.getPathFile();
 
-    Path execRootProcessWrapperPath = binDirectoryPath.getRelative("process-wrapper");
-    File execRootCpuTimeSpenderFile = execRootProcessWrapperPath.getPathFile();
+    wrapperPath.delete();
+    Files.copy(realProcessWrapperFile, wrapperFile);
+    assertThat(wrapperPath.exists()).isTrue();
 
-    assertThat(execRootProcessWrapperPath.exists()).isFalse();
-    Files.copy(realProcessWrapperFile, execRootCpuTimeSpenderFile);
-    assertThat(execRootProcessWrapperPath.exists()).isTrue();
-
-    execRootProcessWrapperPath.setExecutable(true);
-
-    return execRootProcessWrapperPath;
+    wrapperPath.setExecutable(true);
   }
 
   /**
@@ -787,23 +799,32 @@
     return execRootCpuTimeSpenderPath;
   }
 
-  /**
-   * Returns an execRoot {@link Path} inside a new temporary directory.
-   *
-   * <p>The temporary directory will be automatically deleted on exit.
-   */
-  private Path getTemporaryExecRoot(FileSystem fs) throws IOException {
+  private Path getTemporaryRoot(FileSystem fs, String name) throws IOException {
     File tempDirFile = TestUtils.makeTempDir();
     tempDirFile.deleteOnExit();
 
     Path tempDirPath = fs.getPath(tempDirFile.getPath());
     assertThat(tempDirPath.exists()).isTrue();
 
-    Path execRoot = tempDirPath.getRelative("execroot");
-    assertThat(execRoot.createDirectory()).isTrue();
-    assertThat(execRoot.exists()).isTrue();
+    Path root = tempDirPath.getRelative(name);
+    assertThat(root.createDirectory()).isTrue();
+    assertThat(root.exists()).isTrue();
 
-    return execRoot;
+    return root;
+  }
+
+  /**
+   * Returns an execRoot {@link Path} inside a new temporary directory.
+   *
+   * <p>The temporary directory will be automatically deleted on exit.
+   */
+  private Path getTemporaryExecRoot(FileSystem fs) throws IOException {
+    return getTemporaryRoot(fs, "execRoot");
+  }
+
+
+  private Path getTemporaryEmbeddedBin(FileSystem fs) throws  IOException {
+    return getTemporaryRoot(fs, "embedded_bin");
   }
 
   @Test
@@ -829,7 +850,10 @@
     Duration maximumSystemTimeToSpend = minimumSystemTimeToSpend.plus(Duration.ofSeconds(20));
 
     Path execRoot = getTemporaryExecRoot(fs);
-    copyProcessWrapperIntoExecRoot(execRoot);
+    Path embeddedBinaries = getTemporaryEmbeddedBin(fs);
+    BinTools binTools = BinTools.forEmbeddedBin(embeddedBinaries,
+        ImmutableList.of("process-wrapper"));
+    copyProcessWrapperIntoExecRoot(binTools.getEmbeddedPath("process-wrapper"));
     Path cpuTimeSpenderPath = copyCpuTimeSpenderIntoExecRoot(execRoot);
 
     LocalSpawnRunner runner =
@@ -839,7 +863,8 @@
             resourceManager,
             USE_WRAPPER,
             OS.LINUX,
-            LocalEnvProvider.UNMODIFIED);
+            LocalEnvProvider.UNMODIFIED,
+            binTools);
 
     Spawn spawn =
         new SpawnBuilder(
@@ -888,7 +913,10 @@
     Duration minimumSystemTimeToSpend = Duration.ZERO;
 
     Path execRoot = getTemporaryExecRoot(fs);
-    copyProcessWrapperIntoExecRoot(execRoot);
+    Path embeddedBinaries = getTemporaryEmbeddedBin(fs);
+    BinTools binTools = BinTools.forEmbeddedBin(embeddedBinaries,
+        ImmutableList.of("process-wrapper"));
+    copyProcessWrapperIntoExecRoot(binTools.getEmbeddedPath("process-wrapper"));
     Path cpuTimeSpenderPath = copyCpuTimeSpenderIntoExecRoot(execRoot);
 
     LocalSpawnRunner runner =
@@ -898,7 +926,8 @@
             resourceManager,
             USE_WRAPPER,
             OS.LINUX,
-            LocalEnvProvider.UNMODIFIED);
+            LocalEnvProvider.UNMODIFIED,
+            binTools);
 
     Spawn spawn =
         new SpawnBuilder(
diff --git a/src/test/java/com/google/devtools/build/lib/integration/util/IntegrationMock.java b/src/test/java/com/google/devtools/build/lib/integration/util/IntegrationMock.java
index 5a29dc6..ba07bcd 100644
--- a/src/test/java/com/google/devtools/build/lib/integration/util/IntegrationMock.java
+++ b/src/test/java/com/google/devtools/build/lib/integration/util/IntegrationMock.java
@@ -37,8 +37,7 @@
    * {@link BlazeDirectories#getEmbeddedBinariesRoot} that point to the runfiles tree
    * of the currently running test (as obtained from {@link BlazeTestUtils#runfilesDir}).
    */
-  public BinTools getIntegrationBinTools(
-      FileSystem fileSystem, BlazeDirectories directories, String workspaceName)
+  public BinTools getIntegrationBinTools(FileSystem fileSystem, BlazeDirectories directories)
       throws IOException {
     Path embeddedBinariesRoot = directories.getEmbeddedBinariesRoot();
     embeddedBinariesRoot.createDirectoryAndParents();
@@ -65,7 +64,6 @@
 
     return BinTools.forIntegrationTesting(
         directories,
-        TestConstants.EMBEDDED_TOOLS,
-        workspaceName);
+        TestConstants.EMBEDDED_TOOLS);
   }
 }
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 f9530ca..db31a40 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
@@ -40,6 +40,7 @@
 import com.google.devtools.build.lib.events.PrintingEventHandler;
 import com.google.devtools.build.lib.events.Reporter;
 import com.google.devtools.build.lib.exec.ActionContextProvider;
+import com.google.devtools.build.lib.exec.BinTools;
 import com.google.devtools.build.lib.exec.BlazeExecutor;
 import com.google.devtools.build.lib.exec.ExecutionOptions;
 import com.google.devtools.build.lib.exec.SingleBuildFileCache;
@@ -115,8 +116,7 @@
             /* defaultSystemJavabase= */ null,
             "mock-product-name");
     // This call implicitly symlinks the integration bin tools into the exec root.
-    IntegrationMock.get()
-        .getIntegrationBinTools(fileSystem, directories, TestConstants.WORKSPACE_NAME);
+    IntegrationMock.get().getIntegrationBinTools(fileSystem, directories);
     OptionsParser optionsParser = OptionsParser.newOptionsParser(ExecutionOptions.class);
     optionsParser.parse("--verbose_failures");
     LocalExecutionOptions localExecutionOptions = Options.getDefaults(LocalExecutionOptions.class);
@@ -145,7 +145,8 @@
                             execRoot,
                             localExecutionOptions,
                             resourceManager,
-                            LocalEnvProvider.UNMODIFIED)))),
+                            LocalEnvProvider.UNMODIFIED,
+                            BinTools.forIntegrationTesting(directories, ImmutableList.of()))))),
             ImmutableList.<ActionContextProvider>of());
 
     executor.getExecRoot().createDirectoryAndParents();