summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/manual.asciidoc27
-rw-r--r--src/build_test.cc16
-rw-r--r--src/graph.cc3
-rw-r--r--src/graph.h12
-rw-r--r--src/graph_test.cc44
-rw-r--r--src/manifest_parser.cc15
-rw-r--r--src/manifest_parser_test.cc34
7 files changed, 148 insertions, 3 deletions
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index ab5c945..4e73df3 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -689,6 +689,10 @@ A file is a series of declarations. A declaration can be one of:
Order-only dependencies may be tacked on the end with +||
_dependency1_ _dependency2_+. (See <<ref_dependencies,the reference on
dependency types>>.)
++
+Implicit outputs _(available since Ninja 1.7)_ may be added before
+the `:` with +| _output1_ _output2_+ and do not appear in `$out`.
+(See <<ref_outputs,the reference on output types>>.)
3. Variable declarations, which look like +_variable_ = _value_+.
@@ -872,6 +876,27 @@ interpretation of the command (such as the use of `&&` to chain
multiple commands), make the command execute the Windows shell by
prefixing the command with `cmd /c`.
+[[ref_outputs]]
+Build outputs
+~~~~~~~~~~~~~
+
+There are two types of build outputs which are subtly different.
+
+1. _Explicit outputs_, as listed in a build line. These are
+ available as the `$out` variable in the rule.
++
+This is the standard form of output to be used for e.g. the
+object file of a compile command.
+
+2. _Implicit outputs_, as listed in a build line with the syntax +|
+ _out1_ _out2_+ + before the `:` of a build line _(available since
+ Ninja 1.7)_. The semantics are identical to explicit outputs,
+ the only difference is that implicit outputs don't show up in the
+ `$out` variable.
++
+This is for expressing outputs that don't show up on the
+command line of the command.
+
[[ref_dependencies]]
Build dependencies
~~~~~~~~~~~~~~~~~~
@@ -883,7 +908,7 @@ There are three types of build dependencies which are subtly different.
cause the output to be rebuilt; if these file are missing and
Ninja doesn't know how to build them, the build is aborted.
+
-This is the standard form of dependency to be used for e.g. the
+This is the standard form of dependency to be used e.g. for the
source file of a compile command.
2. _Implicit dependencies_, either as picked up from
diff --git a/src/build_test.cc b/src/build_test.cc
index 20fb664..7c6060d 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -717,6 +717,22 @@ TEST_F(BuildTest, TwoOutputs) {
EXPECT_EQ("touch out1 out2", command_runner_.commands_ran_[0]);
}
+TEST_F(BuildTest, ImplicitOutput) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+" command = touch $out $out.imp\n"
+"build out | out.imp: touch in.txt\n"));
+ fs_.Create("in.txt", "");
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out.imp", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[0]);
+}
+
// Test case from
// https://github.com/ninja-build/ninja/issues/148
TEST_F(BuildTest, MultiOutIn) {
diff --git a/src/graph.cc b/src/graph.cc
index 9e65675..3391305 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -241,8 +241,9 @@ string EdgeEnv::LookupVariable(const string& var) {
edge_->inputs_.begin() + explicit_deps_count,
var == "in" ? ' ' : '\n');
} else if (var == "out") {
+ int explicit_outs_count = edge_->outputs_.size() - edge_->implicit_outs_;
return MakePathList(edge_->outputs_.begin(),
- edge_->outputs_.end(),
+ edge_->outputs_.begin() + explicit_outs_count,
' ');
}
diff --git a/src/graph.h b/src/graph.h
index cf15123..add8d3d 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -129,7 +129,7 @@ private:
struct Edge {
Edge() : rule_(NULL), pool_(NULL), env_(NULL),
outputs_ready_(false), deps_missing_(false),
- implicit_deps_(0), order_only_deps_(0) {}
+ implicit_deps_(0), order_only_deps_(0), implicit_outs_(0) {}
/// Return true if all inputs' in-edges are ready.
bool AllInputsReady() const;
@@ -181,6 +181,16 @@ struct Edge {
return index >= inputs_.size() - order_only_deps_;
}
+ // There are two types of outputs.
+ // 1) explicit outs, which show up as $out on the command line;
+ // 2) implicit outs, which the target generates but are not part of $out.
+ // These are stored in outputs_ in that order, and we keep a count of
+ // #2 to use when we need to access the various subsets.
+ int implicit_outs_;
+ bool is_implicit_out(size_t index) const {
+ return index >= outputs_.size() - implicit_outs_;
+ }
+
bool is_phony() const;
bool use_console() const;
};
diff --git a/src/graph_test.cc b/src/graph_test.cc
index 44be8a5..723e8ea 100644
--- a/src/graph_test.cc
+++ b/src/graph_test.cc
@@ -105,6 +105,50 @@ TEST_F(GraphTest, ExplicitImplicit) {
EXPECT_TRUE(GetNode("out.o")->dirty());
}
+TEST_F(GraphTest, ImplicitOutputParse) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build out | out.imp: cat in\n"));
+
+ Edge* edge = GetNode("out")->in_edge();
+ EXPECT_EQ(2, edge->outputs_.size());
+ EXPECT_EQ("out", edge->outputs_[0]->path());
+ EXPECT_EQ("out.imp", edge->outputs_[1]->path());
+ EXPECT_EQ(1, edge->implicit_outs_);
+ EXPECT_EQ(edge, GetNode("out.imp")->in_edge());
+}
+
+TEST_F(GraphTest, ImplicitOutputMissing) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build out | out.imp: cat in\n"));
+ fs_.Create("in", "");
+ fs_.Create("out", "");
+
+ Edge* edge = GetNode("out")->in_edge();
+ string err;
+ EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(GetNode("out")->dirty());
+ EXPECT_TRUE(GetNode("out.imp")->dirty());
+}
+
+TEST_F(GraphTest, ImplicitOutputOutOfDate) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build out | out.imp: cat in\n"));
+ fs_.Create("out.imp", "");
+ fs_.Tick();
+ fs_.Create("in", "");
+ fs_.Create("out", "");
+
+ Edge* edge = GetNode("out")->in_edge();
+ string err;
+ EXPECT_TRUE(scan_.RecomputeDirty(edge, &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(GetNode("out")->dirty());
+ EXPECT_TRUE(GetNode("out.imp")->dirty());
+}
+
TEST_F(GraphTest, PathWithCurrentDirectory) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule catdep\n"
diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc
index d0fac59..0724e14 100644
--- a/src/manifest_parser.cc
+++ b/src/manifest_parser.cc
@@ -247,6 +247,20 @@ bool ManifestParser::ParseEdge(string* err) {
} while (!out.empty());
}
+ // Add all implicit outs, counting how many as we go.
+ int implicit_outs = 0;
+ if (lexer_.PeekToken(Lexer::PIPE)) {
+ for (;;) {
+ EvalString out;
+ if (!lexer_.ReadPath(&out, err))
+ return err;
+ if (out.empty())
+ break;
+ outs.push_back(out);
+ ++implicit_outs;
+ }
+ }
+
if (!ExpectToken(Lexer::COLON, err))
return false;
@@ -350,6 +364,7 @@ bool ManifestParser::ParseEdge(string* err) {
delete edge;
return true;
}
+ edge->implicit_outs_ = implicit_outs;
edge->inputs_.reserve(ins.size());
for (vector<EvalString>::iterator i = ins.begin(); i != ins.end(); ++i) {
diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc
index a18433a..2a7900b 100644
--- a/src/manifest_parser_test.cc
+++ b/src/manifest_parser_test.cc
@@ -940,6 +940,40 @@ TEST_F(ParserTest, OrderOnly) {
ASSERT_TRUE(edge->is_order_only(1));
}
+TEST_F(ParserTest, ImplicitOutput) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(
+"rule cat\n"
+" command = cat $in > $out\n"
+"build foo | imp: cat bar\n"));
+
+ Edge* edge = state.LookupNode("imp")->in_edge();
+ ASSERT_EQ(edge->outputs_.size(), 2);
+ EXPECT_TRUE(edge->is_implicit_out(1));
+}
+
+TEST_F(ParserTest, ImplicitOutputEmpty) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(
+"rule cat\n"
+" command = cat $in > $out\n"
+"build foo | : cat bar\n"));
+
+ Edge* edge = state.LookupNode("foo")->in_edge();
+ ASSERT_EQ(edge->outputs_.size(), 1);
+ EXPECT_FALSE(edge->is_implicit_out(0));
+}
+
+TEST_F(ParserTest, NoExplicitOutput) {
+ ManifestParser parser(&state, NULL, kDupeEdgeActionWarn);
+ string err;
+ EXPECT_FALSE(parser.ParseTest(
+"rule cat\n"
+" command = cat $in > $out\n"
+"build | imp : cat bar\n", &err));
+ ASSERT_EQ("input:3: expected path\n"
+ "build | imp : cat bar\n"
+ " ^ near here", err);
+}
+
TEST_F(ParserTest, DefaultDefault) {
ASSERT_NO_FATAL_FAILURE(AssertParse(
"rule cat\n command = cat $in > $out\n"