diff options
-rw-r--r-- | src/build.cc | 21 | ||||
-rw-r--r-- | src/build.h | 3 | ||||
-rw-r--r-- | src/build_test.cc | 59 |
3 files changed, 78 insertions, 5 deletions
diff --git a/src/build.cc b/src/build.cc index cd73487..44c1df6 100644 --- a/src/build.cc +++ b/src/build.cc @@ -521,16 +521,31 @@ Builder::Builder(State* state, const BuildConfig& config) } Builder::~Builder() { + Cleanup(); +} + +void Builder::Cleanup() { if (command_runner_.get()) { vector<Edge*> active_edges = command_runner_->GetActiveEdges(); command_runner_->Abort(); for (vector<Edge*>::iterator i = active_edges.begin(); i != active_edges.end(); ++i) { + bool has_depfile = !(*i)->rule_->depfile().empty(); for (vector<Node*>::iterator ni = (*i)->outputs_.begin(); - ni != (*i)->outputs_.end(); ++ni) - disk_interface_->RemoveFile((*ni)->path()); - if (!(*i)->rule_->depfile().empty()) + ni != (*i)->outputs_.end(); ++ni) { + // Only delete this output if it was actually modified. This is + // important for things like the generator where we don't want to + // delete the manifest file if we can avoid it. But if the rule + // uses a depfile, always delete. (Consider the case where we + // need to rebuild an output because of a modified header file + // mentioned in a depfile, and the command touches its depfile + // but is interrupted before it touches its output file.) + if (has_depfile || + (*ni)->mtime() != disk_interface_->Stat((*ni)->path())) + disk_interface_->RemoveFile((*ni)->path()); + } + if (has_depfile) disk_interface_->RemoveFile((*i)->EvaluateDepFile()); } } diff --git a/src/build.h b/src/build.h index 778d59d..179fca6 100644 --- a/src/build.h +++ b/src/build.h @@ -116,6 +116,9 @@ struct Builder { Builder(State* state, const BuildConfig& config); ~Builder(); + /// Clean up after interrupted commands by deleting output files. + void Cleanup(); + Node* AddTarget(const string& name, string* err); /// Add a target to the build, scanning dependencies. diff --git a/src/build_test.cc b/src/build_test.cc index 78bc918..ca74ab6 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -202,6 +202,8 @@ struct BuildTest : public StateTestWithBuiltinRules, virtual bool CanRunMore(); virtual bool StartCommand(Edge* edge); virtual Edge* WaitForCommand(ExitStatus* status, string* output); + virtual vector<Edge*> GetActiveEdges(); + virtual void Abort(); BuildConfig MakeConfig() { BuildConfig config; @@ -240,13 +242,15 @@ bool BuildTest::StartCommand(Edge* edge) { if (edge->rule().name() == "cat" || edge->rule().name() == "cat_rsp" || edge->rule().name() == "cc" || - edge->rule().name() == "touch") { + edge->rule().name() == "touch" || + edge->rule().name() == "touch-interrupt") { for (vector<Node*>::iterator out = edge->outputs_.begin(); out != edge->outputs_.end(); ++out) { fs_.Create((*out)->path(), now_, ""); } } else if (edge->rule().name() == "true" || - edge->rule().name() == "fail") { + edge->rule().name() == "fail" || + edge->rule().name() == "interrupt") { // Don't do anything. } else { printf("unknown command\n"); @@ -259,6 +263,12 @@ bool BuildTest::StartCommand(Edge* edge) { Edge* BuildTest::WaitForCommand(ExitStatus* status, string* /* output */) { if (Edge* edge = last_command_) { + if (edge->rule().name() == "interrupt" || + edge->rule().name() == "touch-interrupt") { + *status = ExitInterrupted; + return NULL; + } + if (edge->rule().name() == "fail") *status = ExitFailure; else @@ -270,6 +280,17 @@ Edge* BuildTest::WaitForCommand(ExitStatus* status, string* /* output */) { return NULL; } +vector<Edge*> BuildTest::GetActiveEdges() { + vector<Edge*> edges; + if (last_command_) + edges.push_back(last_command_); + return edges; +} + +void BuildTest::Abort() { + last_command_ = NULL; +} + TEST_F(BuildTest, NoWork) { string err; EXPECT_TRUE(builder_.AlreadyUpToDate()); @@ -941,3 +962,37 @@ TEST_F(BuildWithLogTest, RspFileCmdLineChange) { EXPECT_TRUE(builder_.Build(&err)); EXPECT_EQ(1, commands_ran_.size()); } + +TEST_F(BuildTest, InterruptCleanup) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"rule interrupt\n" +" command = interrupt\n" +"rule touch-interrupt\n" +" command = touch-interrupt\n" +"build out1: interrupt in1\n" +"build out2: touch-interrupt in2\n")); + + fs_.Create("out1", now_, ""); + fs_.Create("out2", now_, ""); + now_++; + fs_.Create("in1", now_, ""); + fs_.Create("in2", now_, ""); + + // An untouched output of an interrupted command should be retained. + string err; + EXPECT_TRUE(builder_.AddTarget("out1", &err)); + EXPECT_EQ("", err); + EXPECT_FALSE(builder_.Build(&err)); + EXPECT_EQ("interrupted by user", err); + builder_.Cleanup(); + EXPECT_EQ(now_-1, fs_.Stat("out1")); + err = ""; + + // A touched output of an interrupted command should be deleted. + EXPECT_TRUE(builder_.AddTarget("out2", &err)); + EXPECT_EQ("", err); + EXPECT_FALSE(builder_.Build(&err)); + EXPECT_EQ("interrupted by user", err); + builder_.Cleanup(); + EXPECT_EQ(0, fs_.Stat("out2")); +} |