"bazel clean": prevent creation of command.log Open files cannot be deleted on Windows, thus `bazel clean --expunge` fails when it attemps to delete the `command.log` that stdout/stderr is tee'd into, and so does BlazeCommandDispatcher when it attemps to delete the `command.log` just before dispatching to the command implementation (not just `clean` but any command). This change: - closes `command.log` before we attempt to delete it - marks CleanCommand (through the Command annotation) as one that should not write to the command.log, thus we don't create a new instance of the file This change allows `bazel clean --expunge` to delete everything in the output base, with the exception of `jvm.log`. Unfortunately that file is opened by the C++ bazel client process, so we have to close it there prior to sending the clean to the bazel server. See https://github.com/bazelbuild/bazel/issues/1586 -- MOS_MIGRATED_REVID=137278553
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java index 7d7f300..87adbd7 100644 --- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java +++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
@@ -370,20 +370,21 @@ return exitCausingException.getExitCode().getNumericExitCode(); } - if (env.getRuntime().writeCommandLog()) { - try { - Path commandLog = getCommandLogPath(env.getOutputBase()); + try { + Path commandLog = getCommandLogPath(env.getOutputBase()); - // Unlink old command log from previous build, if present, so scripts - // reading it don't conflate it with the command log we're about to write. - commandLog.delete(); + // Unlink old command log from previous build, if present, so scripts + // reading it don't conflate it with the command log we're about to write. + closeSilently(logOutputStream); + logOutputStream = null; + commandLog.delete(); + if (env.getRuntime().writeCommandLog() && commandAnnotation.writeCommandLog()) { logOutputStream = commandLog.getOutputStream(); outErr = tee(outErr, OutErr.create(logOutputStream, logOutputStream)); - } catch (IOException ioException) { - LoggingUtil.logToRemote( - Level.WARNING, "Unable to delete or open command.log", ioException); } + } catch (IOException ioException) { + LoggingUtil.logToRemote(Level.WARNING, "Unable to delete or open command.log", ioException); } ExitCode result = checkCwdInWorkspace(env, commandAnnotation, commandName, outErr);
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/Command.java b/src/main/java/com/google/devtools/build/lib/runtime/Command.java index c415f0a..4db5627 100644 --- a/src/main/java/com/google/devtools/build/lib/runtime/Command.java +++ b/src/main/java/com/google/devtools/build/lib/runtime/Command.java
@@ -15,7 +15,6 @@ import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionsBase; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -89,6 +88,13 @@ boolean binaryStdErr() default false; /** + * Returns true if this command may want to write to the command.log. + * + * <p>The clean command would typically set this to false so it can delete the command.log. + */ + boolean writeCommandLog() default true; + + /** * The help message for this command. If the value starts with "resource:", * the remainder is interpreted as the name of a text file resource (in the * .jar file that provides the Command implementation class).
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 index 3b1c68f..961400e 100644 --- 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
@@ -38,16 +38,17 @@ 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 }) +/** Implements 'blaze clean'. */ +@Command( + name = "clean", + builds = true, // Does not, but people expect build options to be there + writeCommandLog = false, // Do not create a command.log, otherwise we couldn't delete it. + 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.