Add support for build statement implicit outputs
authorBrad King <brad.king@kitware.com>
Mon, 13 Jul 2015 19:25:50 +0000 (15:25 -0400)
committerBrad King <brad.king@kitware.com>
Wed, 3 Feb 2016 14:37:17 +0000 (09:37 -0500)
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.

doc/manual.asciidoc
src/build_test.cc
src/graph.cc
src/graph.h
src/graph_test.cc
src/manifest_parser.cc
src/manifest_parser_test.cc

index ab5c945..4e73df3 100644 (file)
@@ -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
index 20fb664..7c6060d 100644 (file)
@@ -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) {
index 9e65675..3391305 100644 (file)
@@ -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,
                         ' ');
   }
 
index cf15123..add8d3d 100644 (file)
@@ -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;
 };
index 44be8a5..723e8ea 100644 (file)
@@ -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"
index d0fac59..0724e14 100644 (file)
@@ -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) {
index a18433a..2a7900b 100644 (file)
@@ -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"