Skip to content

Commit c5137cd

Browse files
Implement 'dynout' feature to inform ninja about dynamic outputs
Co-authored-by: Hampus Adolfsson <[email protected]>
1 parent ca1cbd3 commit c5137cd

19 files changed

+740
-62
lines changed

doc/manual.asciidoc

+7
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,13 @@ keys.
900900
stored as `.ninja_deps` in the `builddir`, see <<ref_toplevel,the
901901
discussion of `builddir`>>.
902902

903+
`dynout`:: path to an optional _dynout file_ that contains extra _implicit
904+
outputs_ generated by the rule. This allows ninja to dynamically discover
905+
output files whose presence is decided during the build, so that for
906+
subsequent builds the edge is re-run if some dynamic output is missing, and
907+
dynamic outputs are cleaned when using the `-t clean` tool. The dynout file
908+
syntax expects one path per line.
909+
903910
`msvc_deps_prefix`:: _(Available since Ninja 1.5.)_ defines the string
904911
which should be stripped from msvc's /showIncludes output. Only
905912
needed when `deps = msvc` and no English Visual Studio version is used.

src/build.cc

+77-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "depfile_parser.h"
3535
#include "deps_log.h"
3636
#include "disk_interface.h"
37+
#include "dynout_parser.h"
3738
#include "explanations.h"
3839
#include "graph.h"
3940
#include "metrics.h"
@@ -698,6 +699,7 @@ void Builder::Cleanup() {
698699
for (vector<Edge*>::iterator e = active_edges.begin();
699700
e != active_edges.end(); ++e) {
700701
string depfile = (*e)->GetUnescapedDepfile();
702+
string dynout = (*e)->GetUnescapedDynout();
701703
for (vector<Node*>::iterator o = (*e)->outputs_.begin();
702704
o != (*e)->outputs_.end(); ++o) {
703705
// Only delete this output if it was actually modified. This is
@@ -716,6 +718,8 @@ void Builder::Cleanup() {
716718
}
717719
if (!depfile.empty())
718720
disk_interface_->RemoveFile(depfile);
721+
if (!dynout.empty())
722+
disk_interface_->RemoveFile(dynout);
719723
}
720724
}
721725

@@ -949,6 +953,18 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
949953
}
950954
}
951955

956+
int outputs_count = 0;
957+
string extract_err;
958+
std::string dynout_file = edge->GetUnescapedDynout();
959+
if (!ExtractDynouts(edge, dynout_file, &deps_nodes, &outputs_count,
960+
&extract_err) &&
961+
result->success()) {
962+
if (!result->output.empty())
963+
result->output.append("\n");
964+
result->output.append(extract_err);
965+
result->status = ExitFailure;
966+
}
967+
952968
int64_t start_time_millis, end_time_millis;
953969
RunningEdgeMap::iterator it = running_edges_.find(edge);
954970
start_time_millis = it->second;
@@ -1008,21 +1024,23 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
10081024
disk_interface_->RemoveFile(rspfile);
10091025

10101026
if (scan_.build_log()) {
1011-
if (!scan_.build_log()->RecordCommand(edge, start_time_millis,
1012-
end_time_millis, record_mtime)) {
1027+
std::vector<Node*> dynouts(deps_nodes.end() - outputs_count,
1028+
deps_nodes.end());
1029+
if (!scan_.build_log()->RecordCommand(
1030+
edge, start_time_millis, end_time_millis, record_mtime, dynouts)) {
10131031
*err = string("Error writing to build log: ") + strerror(errno);
10141032
return false;
10151033
}
10161034
}
10171035

1018-
if (!deps_type.empty() && !config_.dry_run) {
1036+
if ((!deps_type.empty() || !dynout_file.empty()) && !config_.dry_run) {
10191037
assert(!edge->outputs_.empty() && "should have been rejected by parser");
10201038
for (std::vector<Node*>::const_iterator o = edge->outputs_.begin();
10211039
o != edge->outputs_.end(); ++o) {
10221040
TimeStamp deps_mtime = disk_interface_->Stat((*o)->path(), err);
10231041
if (deps_mtime == -1)
10241042
return false;
1025-
if (!scan_.deps_log()->RecordDeps(*o, deps_mtime, deps_nodes, 0)) {
1043+
if (!scan_.deps_log()->RecordDeps(*o, deps_mtime, deps_nodes, outputs_count)) {
10261044
*err = std::string("Error writing to deps log: ") + strerror(errno);
10271045
return false;
10281046
}
@@ -1109,3 +1127,58 @@ bool Builder::LoadDyndeps(Node* node, string* err) {
11091127

11101128
return true;
11111129
}
1130+
1131+
bool Builder::ExtractDynouts(Edge* edge, const std::string& dynout_file,
1132+
std::vector<Node*>* nodes, int* outputs_count,
1133+
std::string* err) {
1134+
if (dynout_file.empty()) {
1135+
return true;
1136+
}
1137+
1138+
// Read depfile content. Treat a missing depfile as empty.
1139+
std::string content;
1140+
switch (disk_interface_->ReadFile(dynout_file, &content, err)) {
1141+
case DiskInterface::Okay:
1142+
break;
1143+
case DiskInterface::NotFound:
1144+
if (err != NULL) {
1145+
err->clear();
1146+
}
1147+
break;
1148+
case DiskInterface::OtherError:
1149+
if (err != NULL) {
1150+
*err = "loading '" + dynout_file + "': " + *err;
1151+
}
1152+
return false;
1153+
}
1154+
1155+
std::vector<StringPiece> output_paths;
1156+
std::string parse_err;
1157+
if (!DynoutParser::Parse(content, output_paths, &parse_err)) {
1158+
if (err != NULL) {
1159+
*err = parse_err;
1160+
}
1161+
return false;
1162+
}
1163+
1164+
int start_size = nodes->size();
1165+
1166+
for (const StringPiece &p : output_paths) {
1167+
uint64_t slash_bits;
1168+
std::string canonical = p.AsString();
1169+
CanonicalizePath(&canonical, &slash_bits);
1170+
Node* new_node = state_->GetNode(canonical, slash_bits);
1171+
nodes->push_back(new_node);
1172+
}
1173+
*outputs_count = (int) nodes->size() - start_size;
1174+
1175+
if (!g_keep_dynout) {
1176+
if (disk_interface_->RemoveFile(dynout_file) < 0) {
1177+
if (err != NULL) {
1178+
*err = std::string("deleting dynout: ") + strerror(errno) + std::string("\n");
1179+
}
1180+
return false;
1181+
}
1182+
}
1183+
return true;
1184+
}

src/build.h

+4
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ struct Builder {
233233
const std::string& deps_prefix,
234234
std::vector<Node*>* deps_nodes, std::string* err);
235235

236+
bool ExtractDynouts(Edge* edge, const std::string& dynout_file,
237+
std::vector<Node*>* nodes, int* outputs_count,
238+
std::string* err);
239+
236240
/// Map of running edge to time the edge started running.
237241
typedef std::map<const Edge*, int> RunningEdgeMap;
238242
RunningEdgeMap running_edges_;

src/build_log.cc

+25-22
Original file line numberDiff line numberDiff line change
@@ -142,33 +142,36 @@ bool BuildLog::OpenForWrite(const string& path, const BuildLogUser& user,
142142
}
143143

144144
bool BuildLog::RecordCommand(Edge* edge, int start_time, int end_time,
145-
TimeStamp mtime) {
145+
TimeStamp mtime,
146+
const std::vector<Node*> &extra_outputs) {
146147
string command = edge->EvaluateCommand(true);
147148
uint64_t command_hash = LogEntry::HashCommand(command);
148-
for (vector<Node*>::iterator out = edge->outputs_.begin();
149-
out != edge->outputs_.end(); ++out) {
150-
const string& path = (*out)->path();
151-
Entries::iterator i = entries_.find(path);
152-
LogEntry* log_entry;
153-
if (i != entries_.end()) {
154-
log_entry = i->second;
155-
} else {
156-
log_entry = new LogEntry(path);
157-
entries_.insert(Entries::value_type(log_entry->output, log_entry));
158-
}
159-
log_entry->command_hash = command_hash;
160-
log_entry->start_time = start_time;
161-
log_entry->end_time = end_time;
162-
log_entry->mtime = mtime;
163149

164-
if (!OpenForWriteIfNeeded()) {
165-
return false;
166-
}
167-
if (log_file_) {
168-
if (!WriteEntry(log_file_, *log_entry))
150+
for (const vector<Node*> outputs : { edge->outputs_, extra_outputs }) {
151+
for (const Node *out : outputs) {
152+
const string& path = out->path();
153+
Entries::iterator i = entries_.find(path);
154+
LogEntry* log_entry;
155+
if (i != entries_.end()) {
156+
log_entry = i->second;
157+
} else {
158+
log_entry = new LogEntry(path);
159+
entries_.insert(Entries::value_type(log_entry->output, log_entry));
160+
}
161+
log_entry->command_hash = command_hash;
162+
log_entry->start_time = start_time;
163+
log_entry->end_time = end_time;
164+
log_entry->mtime = mtime;
165+
166+
if (!OpenForWriteIfNeeded()) {
169167
return false;
170-
if (fflush(log_file_) != 0) {
168+
}
169+
if (log_file_) {
170+
if (!WriteEntry(log_file_, *log_entry))
171171
return false;
172+
if (fflush(log_file_) != 0) {
173+
return false;
174+
}
172175
}
173176
}
174177
}

src/build_log.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
struct DiskInterface;
2727
struct Edge;
28+
struct Node;
2829

2930
/// Can answer questions about the manifest for the BuildLog.
3031
struct BuildLogUser {
@@ -49,7 +50,8 @@ struct BuildLog {
4950
bool OpenForWrite(const std::string& path, const BuildLogUser& user,
5051
std::string* err);
5152
bool RecordCommand(Edge* edge, int start_time, int end_time,
52-
TimeStamp mtime = 0);
53+
TimeStamp mtime = 0,
54+
const std::vector<Node*>& extra_outputs = {});
5355
void Close();
5456

5557
/// Load the on-disk log.

src/build_log_test.cc

+25
Original file line numberDiff line numberDiff line change
@@ -365,4 +365,29 @@ TEST_F(BuildLogRecompactTest, Recompact) {
365365
ASSERT_FALSE(log2.LookupByOutput("out2"));
366366
}
367367

368+
// Make sure the build log can record extra outputs not part of the edge (e.g.
369+
// from a dynout file).
370+
TEST_F(BuildLogTest, ExtraOutputs) {
371+
AssertParse(&state_,
372+
"build out: cat mid\n");
373+
374+
BuildLog log;
375+
376+
// The build log must handle overlap between the extra outputs and the edge
377+
// outputs, and only record each output once
378+
Node *out_node = state_.LookupNode("out");
379+
ASSERT_TRUE(out_node);
380+
Node *second_out = state_.GetNode("out.bis", 0);
381+
std::vector<Node*> extra_outputs = { out_node, second_out };
382+
383+
log.RecordCommand(state_.edges_[0], 15, 18, 0, extra_outputs);
384+
385+
ASSERT_EQ(2u, log.entries().size());
386+
BuildLog::LogEntry* e1 = log.LookupByOutput("out.bis");
387+
ASSERT_TRUE(e1);
388+
ASSERT_EQ(15, e1->start_time);
389+
ASSERT_EQ("out.bis", e1->output);
390+
}
391+
392+
368393
} // anonymous namespace

0 commit comments

Comments
 (0)