diff options
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.java | 185 |
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) {} +} |