aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/sandbox
diff options
context:
space:
mode:
authorGravatar jmmv <jmmv@google.com>2018-03-19 19:04:20 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-03-19 19:06:31 -0700
commit56d1b1c3122a3d1ec111baab339631b8c42c2c31 (patch)
tree67b4ae9a560c454a9da82d3e1153710f4a10a542 /src/main/java/com/google/devtools/build/lib/sandbox
parentc1dacd3683fe9184d299cbbe0bd9231ad6feb224 (diff)
Plumb support for mounting a sandboxfs instance during a build.
This introduces user-facing options to enable the experimental sandboxfs support and, when enabled, mounts a sandboxfs instance throughout the build. The sandboxfs' process handle is passed to the SandboxActionContextProvider so that the SpawnRunners can later consume it. Note that this does NOT yet provide sandboxfs support for the builds as the SpawnRunners are untouched. RELNOTES: None. PiperOrigin-RevId: 189678732
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/sandbox')
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfsProcess.java7
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java149
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java24
4 files changed, 152 insertions, 34 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfsProcess.java b/src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfsProcess.java
index 70a355f378..37656258aa 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfsProcess.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfsProcess.java
@@ -99,13 +99,16 @@ final class RealSandboxfsProcess implements SandboxfsProcess {
* read-only throughout the lifetime of this instance. Writable subdirectories can later be
* mapped via {@link #map(List)}.
*
- * @param binary path to the sandboxfs binary
+ * @param binary path to the sandboxfs binary. This is a {@link PathFragment} and not a
+ * {@link Path} because we want to support "bare" (non-absolute) names for the location of
+ * the sandboxfs binary; such names are automatically looked for in the {@code PATH}.
* @param mountPoint directory on which to mount the sandboxfs instance
* @param logFile path to the file that will receive all sandboxfs logging output
* @return a new handle that represents the running process
* @throws IOException if there is a problem starting the process
*/
- static SandboxfsProcess mount(Path binary, Path mountPoint, Path logFile) throws IOException {
+ static SandboxfsProcess mount(PathFragment binary, Path mountPoint, Path logFile)
+ throws IOException {
log.info("Mounting sandboxfs (" + binary + ") onto " + mountPoint);
// TODO(jmmv): Before starting a sandboxfs serving instance, we must query the current version
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java
index cd36827512..9067ba42e5 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java
@@ -35,6 +35,7 @@ import com.google.devtools.common.options.OptionsProvider;
import java.io.IOException;
import java.time.Duration;
import java.util.Optional;
+import javax.annotation.Nullable;
/**
* Provides the sandboxed spawn strategy.
@@ -46,7 +47,8 @@ final class SandboxActionContextProvider extends ActionContextProvider {
this.contexts = contexts;
}
- public static SandboxActionContextProvider create(CommandEnvironment cmdEnv, Path sandboxBase)
+ public static SandboxActionContextProvider create(CommandEnvironment cmdEnv, Path sandboxBase,
+ @Nullable SandboxfsProcess process)
throws IOException {
ImmutableList.Builder<ActionContext> contexts = ImmutableList.builder();
@@ -72,6 +74,7 @@ final class SandboxActionContextProvider extends ActionContextProvider {
// This is the preferred sandboxing strategy on Linux.
if (LinuxSandboxedSpawnRunner.isSupported(cmdEnv)) {
+ // TODO(jmmv): Inject process into spawn runner.
SpawnRunner spawnRunner =
withFallback(
cmdEnv,
@@ -81,6 +84,7 @@ final class SandboxActionContextProvider extends ActionContextProvider {
// This is the preferred sandboxing strategy on macOS.
if (DarwinSandboxedSpawnRunner.isSupported(cmdEnv)) {
+ // TODO(jmmv): Inject process into spawn runner.
SpawnRunner spawnRunner =
withFallback(
cmdEnv,
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 bdb1269c4b..d0f6c75da3 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
@@ -14,76 +14,163 @@
package com.google.devtools.build.lib.sandbox;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
import com.google.common.collect.ImmutableList;
-import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.common.eventbus.Subscribe;
import com.google.devtools.build.lib.buildtool.BuildRequest;
+import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
+import com.google.devtools.build.lib.buildtool.buildevent.BuildInterruptedEvent;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.exec.ActionContextProvider;
import com.google.devtools.build.lib.exec.ExecutorBuilder;
import com.google.devtools.build.lib.runtime.BlazeModule;
import com.google.devtools.build.lib.runtime.Command;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
+import com.google.devtools.build.lib.util.AbruptExitException;
+import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.vfs.FileSystem;
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.common.options.OptionsBase;
import java.io.IOException;
+import javax.annotation.Nullable;
/**
* This module provides the Sandbox spawn strategy.
*/
public final class SandboxModule extends BlazeModule {
- private Path sandboxBase;
+
+ /** Environment for the running command. */
+ private @Nullable CommandEnvironment env;
+
+ /** Path to the location of the sandboxes. */
+ private @Nullable Path sandboxBase;
+
+ /** Instance of the sandboxfs process in use, if enabled. */
+ private @Nullable SandboxfsProcess sandboxfsProcess;
+
+ /**
+ * Whether to remove the sandbox worker directories after a build or not. Useful for debugging
+ * to inspect the state of files on failures.
+ */
private boolean shouldCleanupSandboxBase;
@Override
public Iterable<Class<? extends OptionsBase>> getCommandOptions(Command command) {
return "build".equals(command.name())
- ? ImmutableList.<Class<? extends OptionsBase>>of(SandboxOptions.class)
- : ImmutableList.<Class<? extends OptionsBase>>of();
+ ? ImmutableList.of(SandboxOptions.class)
+ : ImmutableList.of();
+ }
+
+ /** Computes the path to the sandbox base tree for the given running command. */
+ private static Path computeSandboxBase(SandboxOptions options, CommandEnvironment env) {
+ if (options.sandboxBase.isEmpty()) {
+ return env.getOutputBase().getRelative("sandbox");
+ } else {
+ String dirName = String.format("%s-sandbox.%s", env.getRuntime().getProductName(),
+ Fingerprint.md5Digest(env.getOutputBase().toString()));
+ FileSystem fileSystem = env.getRuntime().getFileSystem();
+ return fileSystem.getPath(options.sandboxBase).getRelative(dirName);
+ }
+ }
+
+ @Override
+ public void beforeCommand(CommandEnvironment env) {
+ // We can't assert that env is null because the Blaze runtime does not guarantee that
+ // afterCommand() will be called if the command fails due to, e.g. a syntax error.
+ this.env = env;
+ env.getEventBus().register(this);
+
+ // Don't attempt cleanup unless the executor is initialized.
+ sandboxfsProcess = null;
+ shouldCleanupSandboxBase = false;
}
@Override
public void executorInit(
CommandEnvironment cmdEnv, BuildRequest request, ExecutorBuilder builder) {
- BlazeDirectories blazeDirs = cmdEnv.getDirectories();
- String productName = cmdEnv.getRuntime().getProductName();
- SandboxOptions sandboxOptions = request.getOptions(SandboxOptions.class);
- FileSystem fs = cmdEnv.getRuntime().getFileSystem();
+ checkNotNull(env, "env not initialized; was beforeCommand called?");
- if (sandboxOptions.sandboxBase.isEmpty()) {
- sandboxBase = blazeDirs.getOutputBase().getRelative(productName + "-sandbox");
- } else {
- String dirName =
- productName + "-sandbox." + Fingerprint.md5Digest(blazeDirs.getOutputBase().toString());
- sandboxBase = fs.getPath(sandboxOptions.sandboxBase).getRelative(dirName);
- }
+ SandboxOptions options = env.getOptions().getOptions(SandboxOptions.class);
+ checkNotNull(options, "We were told to initialize the executor but the SandboxOptions are "
+ + "not present; were they registered for all build commands?");
- // Do not remove the sandbox base when --sandbox_debug was specified so that people can check
- // out the contents of the generated sandbox directories.
- shouldCleanupSandboxBase = !sandboxOptions.sandboxDebug;
+ sandboxBase = computeSandboxBase(options, env);
+ ActionContextProvider provider;
try {
- FileSystemUtils.createDirectoryAndParents(sandboxBase);
- builder.addActionContextProvider(SandboxActionContextProvider.create(cmdEnv, sandboxBase));
+ sandboxBase.createDirectoryAndParents();
+
+ if (options.useSandboxfs) {
+ Path mountPoint = sandboxBase.getRelative("sandboxfs");
+ mountPoint.createDirectory();
+ Path logFile = sandboxBase.getRelative("sandboxfs.log");
+
+ env.getReporter().handle(Event.info("Mounting sandboxfs instance on " + mountPoint));
+ sandboxfsProcess = RealSandboxfsProcess.mount(
+ PathFragment.create(options.sandboxfsPath), mountPoint, logFile);
+ provider = SandboxActionContextProvider.create(cmdEnv, sandboxBase, sandboxfsProcess);
+ } else {
+ provider = SandboxActionContextProvider.create(cmdEnv, sandboxBase, null);
+ }
} catch (IOException e) {
- throw new IllegalStateException(e);
+ env.getBlazeModuleEnvironment().exit(
+ new AbruptExitException(
+ "Failed to initialize sandbox: " + e,
+ ExitCode.LOCAL_ENVIRONMENTAL_ERROR));
+ return;
}
+ builder.addActionContextProvider(provider);
builder.addActionContextConsumer(new SandboxActionContextConsumer(cmdEnv));
+
+ // Do not remove the sandbox base when --sandbox_debug was specified so that people can check
+ // out the contents of the generated sandbox directories.
+ shouldCleanupSandboxBase = !options.sandboxDebug;
+ }
+
+ private void unmountSandboxfs(String reason) {
+ if (sandboxfsProcess != null) {
+ checkNotNull(env, "env not initialized; was beforeCommand called?");
+ env.getReporter().handle(Event.info(reason));
+ // TODO(jmmv): This can be incredibly slow. Either fix sandboxfs or do it in the background.
+ sandboxfsProcess.destroy();
+ sandboxfsProcess = null;
+ }
+ }
+
+ @Subscribe
+ public void buildComplete(BuildCompleteEvent event) {
+ unmountSandboxfs("Build complete; unmounting sandboxfs...");
+ }
+
+ @Subscribe
+ public void buildInterrupted(BuildInterruptedEvent event) {
+ unmountSandboxfs("Build interrupted; unmounting sandboxfs...");
}
@Override
public void afterCommand() {
- super.afterCommand();
-
- if (sandboxBase != null) {
- if (shouldCleanupSandboxBase) {
- try {
- FileSystemUtils.deleteTree(sandboxBase);
- } catch (IOException e) {
- // Nothing we can do at this point.
- }
+ checkNotNull(env, "env not initialized; was beforeCommand called?");
+
+ if (shouldCleanupSandboxBase) {
+ try {
+ FileSystemUtils.deleteTree(sandboxBase);
+ } catch (IOException e) {
+ env.getReporter().handle(Event.warn("Failed to delete sandbox base " + sandboxBase
+ + ": " + e));
}
- sandboxBase = null;
+ shouldCleanupSandboxBase = false;
}
+
+ checkState(sandboxfsProcess == null, "sandboxfs instance should have been shut down at this "
+ + "point; were the buildComplete/buildInterrupted events sent?");
+ sandboxBase = null;
+
+ env.getEventBus().unregister(this);
+ env = null;
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
index 6b5db9b449..faaaaa3f90 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
@@ -179,6 +179,30 @@ public class SandboxOptions extends OptionsBase {
)
public List<ImmutableMap.Entry<String, String>> sandboxAdditionalMounts;
+ @Option(
+ name = "experimental_use_sandboxfs",
+ defaultValue = "false",
+ category = "config",
+ documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "Use sandboxfs to create the actions' execroot directories instead of building a symlink "
+ + "tree."
+ )
+ public boolean useSandboxfs;
+
+ @Option(
+ name = "experimental_sandboxfs_path",
+ defaultValue = "sandboxfs",
+ category = "config",
+ documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "Path to the sandboxfs binary to use when --experimental_use_sandboxfs is true. If a "
+ + "bare name, use the first binary of that name found in the PATH."
+ )
+ public String sandboxfsPath;
+
public ImmutableSet<Path> getInaccessiblePaths(FileSystem fs) {
List<Path> inaccessiblePaths = new ArrayList<>();
for (String path : sandboxBlockPath) {