aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java185
1 files changed, 185 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java
new file mode 100644
index 0000000000..3fd300eaaa
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java
@@ -0,0 +1,185 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.runtime.commands;
+
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.buildtool.BuildRequest;
+import com.google.devtools.build.lib.buildtool.OutputDirectoryLinksUtils;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.runtime.BlazeCommand;
+import com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.ShutdownBlazeServerException;
+import com.google.devtools.build.lib.runtime.BlazeRuntime;
+import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.shell.CommandException;
+import com.google.devtools.build.lib.util.CommandBuilder;
+import com.google.devtools.build.lib.util.ExitCode;
+import com.google.devtools.build.lib.util.ProcessUtils;
+import com.google.devtools.build.lib.util.ShellEscaper;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.OptionsProvider;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+/**
+ * Implements 'blaze clean'.
+ */
+@Command(name = "clean",
+ builds = true, // Does not, but people expect build options to be there
+ options = { CleanCommand.Options.class },
+ help = "resource:clean.txt",
+ shortDescription = "Removes output files and optionally stops the server.",
+ // TODO(bazel-team): Remove this - we inherit a huge number of unused options.
+ inherits = { BuildCommand.class })
+public final class CleanCommand implements BlazeCommand {
+
+ /**
+ * An interface for special options for the clean command.
+ */
+ public static class Options extends OptionsBase {
+ @Option(name = "clean_style",
+ defaultValue = "",
+ category = "clean",
+ help = "Can be either 'expunge' or 'expunge_async'.")
+ public String cleanStyle;
+
+ @Option(name = "expunge",
+ defaultValue = "false",
+ category = "clean",
+ expansion = "--clean_style=expunge",
+ help = "If specified, clean will remove the entire working tree for this Blaze " +
+ "instance, which includes all Blaze-created temporary and build output " +
+ "files, and it will stop the Blaze server if it is running.")
+ public boolean expunge;
+
+ @Option(name = "expunge_async",
+ defaultValue = "false",
+ category = "clean",
+ expansion = "--clean_style=expunge_async",
+ help = "If specified, clean will asynchronously remove the entire working tree for " +
+ "this Blaze instance, which includes all Blaze-created temporary and build " +
+ "output files, and it will stop the Blaze server if it is running. When this " +
+ "command completes, it will be safe to execute new commands in the same client, " +
+ "even though the deletion may continue in the background.")
+ public boolean expunge_async;
+ }
+
+ private static Logger LOG = Logger.getLogger(CleanCommand.class.getName());
+
+ @Override
+ public ExitCode exec(BlazeRuntime runtime, OptionsProvider options)
+ throws ShutdownBlazeServerException {
+ Options cleanOptions = options.getOptions(Options.class);
+ cleanOptions.expunge_async = cleanOptions.cleanStyle.equals("expunge_async");
+ cleanOptions.expunge = cleanOptions.cleanStyle.equals("expunge");
+
+ if (cleanOptions.expunge == false && cleanOptions.expunge_async == false &&
+ !cleanOptions.cleanStyle.isEmpty()) {
+ runtime.getReporter().handle(Event.error(
+ null, "Invalid clean_style value '" + cleanOptions.cleanStyle + "'"));
+ return ExitCode.COMMAND_LINE_ERROR;
+ }
+
+ String cleanBanner = cleanOptions.expunge_async ?
+ "Starting clean." :
+ "Starting clean (this may take a while). " +
+ "Consider using --expunge_async if the clean takes more than several minutes.";
+
+ runtime.getReporter().handle(Event.info(null/*location*/, cleanBanner));
+ try {
+ String symlinkPrefix =
+ options.getOptions(BuildRequest.BuildRequestOptions.class).symlinkPrefix;
+ actuallyClean(runtime, runtime.getOutputBase(), cleanOptions, symlinkPrefix);
+ return ExitCode.SUCCESS;
+ } catch (IOException e) {
+ runtime.getReporter().handle(Event.error(e.getMessage()));
+ return ExitCode.LOCAL_ENVIRONMENTAL_ERROR;
+ } catch (CommandException e) {
+ runtime.getReporter().handle(Event.error(e.getMessage()));
+ return ExitCode.RUN_FAILURE;
+ } catch (ExecException e) {
+ runtime.getReporter().handle(Event.error(e.getMessage()));
+ return ExitCode.RUN_FAILURE;
+ } catch (InterruptedException e) {
+ runtime.getReporter().handle(Event.error("clean interrupted"));
+ return ExitCode.INTERRUPTED;
+ }
+ }
+
+ private void actuallyClean(BlazeRuntime runtime,
+ Path outputBase, Options cleanOptions, String symlinkPrefix) throws IOException,
+ ShutdownBlazeServerException, CommandException, ExecException, InterruptedException {
+ if (runtime.getOutputService() != null) {
+ runtime.getOutputService().clean();
+ }
+ if (cleanOptions.expunge) {
+ LOG.info("Expunging...");
+ // Delete the big subdirectories with the important content first--this
+ // will take the most time. Then quickly delete the little locks, logs
+ // and links right before we exit. Once the lock file is gone there will
+ // be a small possibility of a server race if a client is waiting, but
+ // all significant files will be gone by then.
+ FileSystemUtils.deleteTreesBelow(outputBase);
+ FileSystemUtils.deleteTree(outputBase);
+ } else if (cleanOptions.expunge_async) {
+ LOG.info("Expunging asynchronously...");
+ String tempBaseName = outputBase.getBaseName() + "_tmp_" + ProcessUtils.getpid();
+
+ // Keeping tempOutputBase in the same directory ensures it remains in the
+ // same file system, and therefore the mv will be atomic and fast.
+ Path tempOutputBase = outputBase.getParentDirectory().getChild(tempBaseName);
+ outputBase.renameTo(tempOutputBase);
+ runtime.getReporter().handle(Event.info(
+ null, "Output base moved to " + tempOutputBase + " for deletion"));
+
+ // Daemonize the shell and use the double-fork idiom to ensure that the shell
+ // exits even while the "rm -rf" command continues.
+ String command = String.format("exec >&- 2>&- <&- && (/usr/bin/setsid /bin/rm -rf %s &)&",
+ ShellEscaper.escapeString(tempOutputBase.getPathString()));
+
+ LOG.info("Executing shell commmand " + ShellEscaper.escapeString(command));
+
+ // Doesn't throw iff command exited and was successful.
+ new CommandBuilder().addArg(command).useShell(true)
+ .setWorkingDir(tempOutputBase.getParentDirectory())
+ .build().execute();
+ } else {
+ LOG.info("Output cleaning...");
+ runtime.clearCaches();
+ for (String directory : new String[] {
+ BlazeDirectories.RELATIVE_OUTPUT_PATH, runtime.getWorkspaceName() }) {
+ Path child = outputBase.getChild(directory);
+ if (child.exists()) {
+ LOG.finest("Cleaning " + child);
+ FileSystemUtils.deleteTreesBelow(child);
+ }
+ }
+ }
+ // remove convenience links
+ OutputDirectoryLinksUtils.removeOutputDirectoryLinks(
+ runtime.getWorkspaceName(), runtime.getWorkspace(), runtime.getReporter(), symlinkPrefix);
+ // shutdown on expunge cleans
+ if (cleanOptions.expunge || cleanOptions.expunge_async) {
+ throw new ShutdownBlazeServerException(0);
+ }
+ }
+
+ @Override
+ public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) {}
+}