summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2015-07-13 19:25:50 (GMT)
committerBrad King <brad.king@kitware.com>2016-02-03 14:37:17 (GMT)
commitcc39240a10fb040fca80bf3669245f2f2d5736c5 (patch)
tree0b26230a0dd1dde9514dbbe7d6f06a3402c6e8b3
parentf1f3f494f5f67a4cd64b0cddaad472b070f6db07 (diff)
downloadNinja-cc39240a10fb040fca80bf3669245f2f2d5736c5.zip
Ninja-cc39240a10fb040fca80bf3669245f2f2d5736c5.tar.gz
Ninja-cc39240a10fb040fca80bf3669245f2f2d5736c5.tar.bz2
Add support for build statement implicit outputs
Some build rules produce outputs that are not mentioned on the command line but that should be part of the build graph. Such outputs should not be named in the `$out` variable. Extend the build statement syntax to support specification of implicit outputs using the syntax `| out1 out2` after the explicit outputs and before the `:`. For example, compilation of a Fortran source file `foo.f90` that defines `MODULE FOO` may now be specified as: rule fc command = f95 -c $in -o $out build foo.o | foo.mod: fc foo.f90 The `foo.mod` file is an implicit output generated by the compiler based on the content of the source file and not mentioned on the command line.
-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"