This may cause the output's reverse dependencies to be removed from the
list of pending build actions.
+`rspfile`
+`rspfile_content`:: if present (both), Ninja will use a response file
+ for the given command, i.e. write the selected string (`rspfile_content`)
+ to the given file (`rspfile`) before calling the command and delete
+ the file after successful execution of the command.
++
+This is particularly useful on Windows OS, where the maximal length of
+a command line is limited and response files must be used instead.
++
+Use it like in the following example:
++
+----
+rule link
+ command = link.exe /OUT$out [usual link flags here] @$out.rsp
+ rspfile = $out.rsp
+ rspfile_content = $in
+
+build myapp.exe: link a.obj b.obj [possibly many other .obj files]
+----
+
Additionally, the special `$in` and `$out` variables expand to the
space-separated list of files provided to the `build` line referencing
this `rule`.
for (vector<Node*>::iterator ni = begin; ni != end; ++ni)
if ((*ni)->mtime() > most_recent_input)
most_recent_input = (*ni)->mtime();
- string command = (*ei)->EvaluateCommand();
-
+ string command = (*ei)->EvaluateCommand(true);
+
// Now, recompute the dirty state of each output.
bool all_outputs_clean = true;
for (vector<Node*>::iterator ni = (*ei)->outputs_.begin();
if (!disk_interface_->MakeDirs((*i)->path()))
return false;
}
+
+ // Create response file, if needed
+ // XXX: this may also block; do we care?
+ if (edge->HasRspFile()) {
+ if (!disk_interface_->WriteFile(edge->GetRspFile(), edge->GetRspFileContent()))
+ return false;
+ }
// start command computing and run it
if (!command_runner_->StartCommand(edge)) {
}
}
+ // delete the response file on success (if exists)
+ if (edge->HasRspFile())
+ disk_interface_->RemoveFile(edge->GetRspFile());
+
plan_.EdgeFinished(edge);
}
void BuildLog::RecordCommand(Edge* edge, int start_time, int end_time,
TimeStamp restat_mtime) {
- const string command = edge->EvaluateCommand();
+ string command = edge->EvaluateCommand(true);
for (vector<Node*>::iterator out = edge->outputs_.begin();
out != edge->outputs_.end(); ++out) {
const string& path = (*out)->path();
bool BuildTest::StartCommand(Edge* edge) {
assert(!last_command_);
commands_ran_.push_back(edge->EvaluateCommand());
- if (edge->rule().name() == "cat" || edge->rule_->name() == "cc" ||
+ if (edge->rule().name() == "cat" ||
+ edge->rule().name() == "cat_rsp" ||
+ edge->rule().name() == "cc" ||
edge->rule().name() == "touch") {
for (vector<Node*>::iterator out = edge->outputs_.begin();
out != edge->outputs_.end(); ++out) {
ASSERT_EQ(3u, commands_ran_.size());
}
+// Test that RSP files are created when & where appropriate and deleted after
+// succesful execution.
+TEST_F(BuildTest, RspFileSuccess)
+{
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+ "rule cat_rsp\n"
+ " command = cat $rspfile > $out\n"
+ " rspfile = $rspfile\n"
+ " rspfile_content = $long_command\n"
+ "build out1: cat in\n"
+ "build out2: cat_rsp in\n"
+ " rspfile = out2.rsp\n"
+ " long_command = Some very long command\n"));
+
+ fs_.Create("out1", now_, "");
+ fs_.Create("out2", now_, "");
+ fs_.Create("out3", now_, "");
+
+ now_++;
+
+ fs_.Create("in", now_, "");
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out1", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.AddTarget("out2", &err));
+ ASSERT_EQ("", err);
+
+ size_t files_created = fs_.files_created_.size();
+ size_t files_removed = fs_.files_removed_.size();
+
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ(2, commands_ran_.size()); // cat + cat_rsp
+
+ // The RSP file was created
+ ASSERT_EQ(files_created + 1, fs_.files_created_.size());
+ ASSERT_EQ(1, fs_.files_created_.count("out2.rsp"));
+
+ // The RSP file was removed
+ ASSERT_EQ(files_removed + 1, fs_.files_removed_.size());
+ ASSERT_EQ(1, fs_.files_removed_.count("out2.rsp"));
+}
+
+// Test that RSP file is created but not removed for commands, which fail
+TEST_F(BuildTest, RspFileFailure) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+ "rule fail\n"
+ " command = fail\n"
+ " rspfile = $rspfile\n"
+ " rspfile_content = $long_command\n"
+ "build out: fail in\n"
+ " rspfile = out.rsp\n"
+ " long_command = Another very long command\n"));
+
+ fs_.Create("out", now_, "");
+ now_++;
+ fs_.Create("in", now_, "");
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+
+ size_t files_created = fs_.files_created_.size();
+ size_t files_removed = fs_.files_removed_.size();
+
+ EXPECT_FALSE(builder_.Build(&err));
+ ASSERT_EQ("subcommand failed", err);
+ ASSERT_EQ(1, commands_ran_.size());
+
+ // The RSP file was created
+ ASSERT_EQ(files_created + 1, fs_.files_created_.size());
+ ASSERT_EQ(1, fs_.files_created_.count("out.rsp"));
+
+ // The RSP file was NOT removed
+ ASSERT_EQ(files_removed, fs_.files_removed_.size());
+ ASSERT_EQ(0, fs_.files_removed_.count("out.rsp"));
+
+ // The RSP file contains what it should
+ ASSERT_EQ("Another very long command", fs_.files_["out.rsp"].contents);
+}
+
+// Test that contens of the RSP file behaves like a regular part of
+// command line, i.e. triggers a rebuild if changed
+TEST_F(BuildWithLogTest, RspFileCmdLineChange) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+ "rule cat_rsp\n"
+ " command = cat $rspfile > $out\n"
+ " rspfile = $rspfile\n"
+ " rspfile_content = $long_command\n"
+ "build out: cat_rsp in\n"
+ " rspfile = out.rsp\n"
+ " long_command = Original very long command\n"));
+
+ fs_.Create("out", now_, "");
+ now_++;
+ fs_.Create("in", now_, "");
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+
+ // 1. Build for the 1st time (-> populate log)
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ(1, commands_ran_.size());
+
+ // 2. Build again (no change)
+ commands_ran_.clear();
+ state_.Reset();
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ EXPECT_EQ("", err);
+ ASSERT_TRUE(builder_.AlreadyUpToDate());
+
+ // 3. Alter the entry in the logfile
+ // (to simulate a change in the command line between 2 builds)
+ BuildLog::LogEntry * log_entry = build_log_.LookupByOutput("out");
+ ASSERT_TRUE(NULL != log_entry);
+ ASSERT_EQ("cat out.rsp > out;rspfile=Original very long command", log_entry->command);
+ log_entry->command = "cat out.rsp > out;rspfile=Altered very long command";
+ // Now expect the target to be rebuilt
+ commands_ran_.clear();
+ state_.Reset();
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ EXPECT_EQ("", err);
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ(1, commands_ran_.size());
+}
continue;
// Do not remove generator's files unless generator specified.
if (!generator && (*e)->rule().generator())
- continue;
+ continue;
for (vector<Node*>::iterator out_node = (*e)->outputs_.begin();
out_node != (*e)->outputs_.end(); ++out_node) {
Remove((*out_node)->path());
}
+ // Remove the depfile
if (!(*e)->rule().depfile().empty())
Remove((*e)->EvaluateDepFile());
+ // Remove the response file
+ if ((*e)->HasRspFile())
+ Remove((*e)->GetRspFile());
}
PrintFooter();
return status_;
void Cleaner::DoCleanTarget(Node* target) {
if (target->in_edge()) {
Remove(target->path());
+ if (target->in_edge()->HasRspFile())
+ Remove(target->in_edge()->GetRspFile());
for (vector<Node*>::iterator n = target->in_edge()->inputs_.begin();
n != target->in_edge()->inputs_.end();
++n) {
for (vector<Node*>::iterator out_node = (*e)->outputs_.begin();
out_node != (*e)->outputs_.end(); ++out_node) {
Remove((*out_node)->path());
+ if ((*e)->HasRspFile())
+ Remove((*e)->GetRspFile());
}
}
}
EXPECT_EQ(2u, fs_.files_removed_.size());
}
+TEST_F(CleanTest, CleanRspFile) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cc\n"
+" command = cc $in > $out\n"
+" rspfile = $rspfile\n"
+" rspfile_content=$in\n"
+"build out1: cc in1\n"
+" rspfile = cc1.rsp\n"
+" rspfile_content=$in\n"));
+ fs_.Create("out1", 1, "");
+ fs_.Create("cc1.rsp", 1, "");
+
+ Cleaner cleaner(&state_, config_, &fs_);
+ EXPECT_EQ(0, cleaner.CleanAll());
+ EXPECT_EQ(2, cleaner.cleaned_files_count());
+ EXPECT_EQ(2u, fs_.files_removed_.size());
+}
+
+TEST_F(CleanTest, CleanRsp) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cat_rsp \n"
+" command = cat $rspfile > $out\n"
+" rspfile = $rspfile\n"
+" rspfile_content = $in\n"
+"build in1: cat src1\n"
+"build out1: cat in1\n"
+"build in2: cat_rsp src2\n"
+" rspfile=in2.rsp\n"
+" rspfile_content=$in\n"
+"build out2: cat_rsp in2\n"
+" rspfile=out2.rsp\n"
+" rspfile_content=$in\n"));
+ fs_.Create("in1", 1, "");
+ fs_.Create("out1", 1, "");
+ fs_.Create("in2.rsp", 1, "");
+ fs_.Create("out2.rsp", 1, "");
+ fs_.Create("in2", 1, "");
+ fs_.Create("out2", 1, "");
+
+ Cleaner cleaner(&state_, config_, &fs_);
+ ASSERT_EQ(0, cleaner.cleaned_files_count());
+ ASSERT_EQ(0, cleaner.CleanTarget("out1"));
+ EXPECT_EQ(2, cleaner.cleaned_files_count());
+ ASSERT_EQ(0, cleaner.CleanTarget("in2"));
+ EXPECT_EQ(2, cleaner.cleaned_files_count());
+ ASSERT_EQ(0, cleaner.CleanRule("cat_rsp"));
+ EXPECT_EQ(2, cleaner.cleaned_files_count());
+
+ EXPECT_EQ(6u, fs_.files_removed_.size());
+
+ // Check they are removed.
+ EXPECT_EQ(0, fs_.Stat("in1"));
+ EXPECT_EQ(0, fs_.Stat("out1"));
+ EXPECT_EQ(0, fs_.Stat("in2"));
+ EXPECT_EQ(0, fs_.Stat("out2"));
+ EXPECT_EQ(0, fs_.Stat("in2.rsp"));
+ EXPECT_EQ(0, fs_.Stat("out2.rsp"));
+
+ fs_.files_removed_.clear();
+}
+
TEST_F(CleanTest, CleanFailure) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"build dir: cat src1\n"));
#endif
}
+bool RealDiskInterface::WriteFile(const string & path, const string & contents) {
+ FILE * fp = fopen(path.c_str(), "w");
+ if (fp == NULL) {
+ Error("WriteFile(%s): Unable to create file. %s", path.c_str(), strerror(errno));
+ return false;
+ }
+
+ if (fwrite(contents.data(), 1, contents.length(), fp) < contents.length()) {
+ Error("WriteFile(%s): Unable to write to the file. %s", path.c_str(), strerror(errno));
+ return false;
+ }
+
+ if (fclose(fp) == EOF) {
+ Error("WriteFile(%s): Unable to close the file. %s", path.c_str(), strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
bool RealDiskInterface::MakeDir(const string& path) {
if (::MakeDir(path) < 0) {
Error("mkdir(%s): %s", path.c_str(), strerror(errno));
/// Create a directory, returning false on failure.
virtual bool MakeDir(const string& path) = 0;
+ /// Create a file, with the specified name and contens
+ /// Returns true on success, false on failure
+ virtual bool WriteFile(const string & path, const string & contetns) = 0;
+
/// Read a file to a string. Fill in |err| on error.
virtual string ReadFile(const string& path, string* err) = 0;
virtual ~RealDiskInterface() {}
virtual TimeStamp Stat(const string& path);
virtual bool MakeDir(const string& path);
+ virtual bool WriteFile(const string & path, const string & contens);
virtual string ReadFile(const string& path, string* err);
virtual int RemoveFile(const string& path);
};
public DiskInterface {
// DiskInterface implementation.
virtual TimeStamp Stat(const string& path);
+ virtual bool WriteFile(const string& path, const string & contents) {
+ assert(false);
+ return true;
+ }
virtual bool MakeDir(const string& path) {
assert(false);
return false;
// date outputs, etc. Visit all outputs and determine whether they're dirty.
if (!dirty) {
BuildLog* build_log = state ? state->build_log_ : 0;
- string command = EvaluateCommand();
+ string command = EvaluateCommand(true);
for (vector<Node*>::iterator i = outputs_.begin();
i != outputs_.end(); ++i) {
return result;
}
-string Edge::EvaluateCommand() {
+string Edge::EvaluateCommand(bool incl_rsp_file) {
EdgeEnv env(this);
- return rule_->command().Evaluate(&env);
+ string command = rule_->command().Evaluate(&env);
+ if (incl_rsp_file && HasRspFile())
+ command += ";rspfile=" + GetRspFileContent();
+ return command;
}
string Edge::EvaluateDepFile() {
return rule_->description().Evaluate(&env);
}
+bool Edge::HasRspFile() {
+ return !rule_->rspfile_.empty();
+}
+
+string Edge::GetRspFile() {
+ EdgeEnv env(this);
+ return rule_->rspfile_.Evaluate(&env);
+}
+
+string Edge::GetRspFileContent() {
+ EdgeEnv env(this);
+ return rule_->rspfile_content_.Evaluate(&env);
+}
+
bool Edge::LoadDepFile(State* state, DiskInterface* disk_interface,
string* err) {
METRIC_RECORD("depfile load");
EvalString command_;
EvalString description_;
EvalString depfile_;
+ EvalString rspfile_;
+ EvalString rspfile_content_;
};
struct BuildLog;
/// Return true if all inputs' in-edges are ready.
bool AllInputsReady() const;
- string EvaluateCommand(); // XXX move to env, take env ptr
+ string EvaluateCommand(bool incl_rsp_file = false); // XXX move to env, take env ptr
string EvaluateDepFile();
string GetDescription();
+ bool HasRspFile();
+ string GetRspFile();
+ string GetRspFileContent();
bool LoadDepFile(State* state, DiskInterface* disk_interface, string* err);
void Dump();
rule->generator_ = true;
} else if (key == "restat") {
rule->restat_ = true;
+ } else if (key == "rspfile") {
+ rule->rspfile_ = value;
+ } else if (key == "rspfile_content") {
+ rule->rspfile_content_ = value;
} else {
// Die on other keyvals for now; revisit if we want to add a
// scope here.
}
}
+ if (rule->rspfile_.empty() != rule->rspfile_content_.empty())
+ return lexer_.Error("rspfile and rspfile_content need to be both specified", err);
+
if (rule->command_.empty())
return lexer_.Error("expected 'command =' line", err);
EXPECT_EQ("1", state.bindings_.LookupVariable("variable"));
}
+TEST_F(ParserTest, ResponseFiles) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(
+"rule cat_rsp\n"
+" command = cat $rspfile > $out\n"
+" rspfile = $rspfile\n"
+" rspfile_content = $in\n"
+"\n"
+"build out: cat_rsp in\n"
+" rspfile=out.rsp\n"));
+
+ ASSERT_EQ(2u, state.rules_.size());
+ const Rule* rule = state.rules_.begin()->second;
+ EXPECT_EQ("cat_rsp", rule->name());
+ EXPECT_EQ("[cat ][$rspfile][ > ][$out]", rule->command().Serialize());
+ EXPECT_EQ("[$rspfile]", rule->rspfile_.Serialize());
+ EXPECT_EQ("[$in]", rule->rspfile_content_.Serialize());
+}
+
TEST_F(ParserTest, Variables) {
ASSERT_NO_FATAL_FAILURE(AssertParse(
"l = one-letter-test\n"
const string& contents) {
files_[path].mtime = time;
files_[path].contents = contents;
+ files_created_.insert(path);
}
TimeStamp VirtualFileSystem::Stat(const string& path) {
return 0;
}
+bool VirtualFileSystem::WriteFile(const string& path, const string& contents) {
+ Create(path, 0, contents);
+ return true;
+}
+
bool VirtualFileSystem::MakeDir(const string& path) {
directories_made_.push_back(path);
return true; // success
// DiskInterface
virtual TimeStamp Stat(const string& path);
+ virtual bool WriteFile(const string& path, const string& contents);
virtual bool MakeDir(const string& path);
virtual string ReadFile(const string& path, string* err);
virtual int RemoveFile(const string& path);
typedef map<string, Entry> FileMap;
FileMap files_;
set<string> files_removed_;
+ set<string> files_created_;
};
struct ScopedTempDir {