From cc39240a10fb040fca80bf3669245f2f2d5736c5 Mon Sep 17 00:00:00 2001 From: Brad King Date: Mon, 13 Jul 2015 15:25:50 -0400 Subject: 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. --- doc/manual.asciidoc | 27 ++++++++++++++++++++++++++- src/build_test.cc | 16 ++++++++++++++++ src/graph.cc | 3 ++- src/graph.h | 12 +++++++++++- src/graph_test.cc | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/manifest_parser.cc | 15 +++++++++++++++ src/manifest_parser_test.cc | 34 ++++++++++++++++++++++++++++++++++ 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 <>.) ++ +Implicit outputs _(available since Ninja 1.7)_ may be added before +the `:` with +| _output1_ _output2_+ and do not appear in `$out`. +(See <>.) 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::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" -- cgit v0.12