diff options
author | Brad King <brad.king@kitware.com> | 2015-07-13 19:25:50 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2016-02-03 14:37:17 (GMT) |
commit | cc39240a10fb040fca80bf3669245f2f2d5736c5 (patch) | |
tree | 0b26230a0dd1dde9514dbbe7d6f06a3402c6e8b3 | |
parent | f1f3f494f5f67a4cd64b0cddaad472b070f6db07 (diff) | |
download | Ninja-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.asciidoc | 27 | ||||
-rw-r--r-- | src/build_test.cc | 16 | ||||
-rw-r--r-- | src/graph.cc | 3 | ||||
-rw-r--r-- | src/graph.h | 12 | ||||
-rw-r--r-- | src/graph_test.cc | 44 | ||||
-rw-r--r-- | src/manifest_parser.cc | 15 | ||||
-rw-r--r-- | 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 <<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" |