From 7a6c9d480d4ca7020f74e5f4dfbaf28c9ff2a6a4 Mon Sep 17 00:00:00 2001 From: Peter Collingbourne Date: Wed, 31 Aug 2011 04:55:35 +0100 Subject: [PATCH] Implement default target statements This introduces a new directive, the default target statement, which may be used to control the list of targets built by default (i.e. if no target is named on the command line). --- doc/manual.asciidoc | 40 ++++++++++++++++++++++++++++----- src/ninja.cc | 2 +- src/ninja.h | 3 +++ src/ninja_jumble.cc | 14 ++++++++++++ src/parsers.cc | 28 +++++++++++++++++++++++ src/parsers.h | 1 + src/parsers_test.cc | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 145 insertions(+), 8 deletions(-) diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index bb120a5..112aac5 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -271,6 +271,30 @@ printed when run, logged (see below), nor do they contribute to the command count printed as part of the build process. +Default target statements +~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, if no targets are specified on the command line, Ninja +will build every output that is not named as an input elsewhere. +You can override this behavior using a default target statement. +A default target statement causes Ninja to build only a given subset +of output files if none are specified on the command line. + +Default target statements begin with the `default` keyword, and have +the format +default _targets_+. A default target statement must appear +after the build statement that declares the target as an output file. +They are cumulative, so multiple statements may be used to extend +the list of default targets. For example: + +---------------- +default foo bar +default baz +---------------- + +This causes Ninja to build the `foo`, `bar` and `baz` targets by +default. + + The Ninja log ~~~~~~~~~~~~~ @@ -375,7 +399,9 @@ A file is a series of declarations. A declaration can be one of: 3. Variable declarations, which look like +_variable_ = _value_+. -4. References to more files, which look like +subninja _path_+ or +4. Default target statements, which look like +default _target1_ _target2_+. + +5. References to more files, which look like +subninja _path_+ or +include _path_+. The difference between these is explained below <>. @@ -499,8 +525,9 @@ lookup order for a variable referenced in a rule is: Variable expansion ~~~~~~~~~~~~~~~~~~ -Variables are expanded in two cases: in the right side of a `name = -value` statement and in paths in a `build` statement. +Variables are expanded in three cases: in the right side of a `name = +value` statement, in paths in a `build` statement and in paths in +a `default` statement. When a `name = value` statement is evaluated, its right-hand side is expanded once (according to the above scoping rules) immediately, and @@ -508,9 +535,10 @@ from then on `$name` expands to the static string as the result of the expansion. It is never the case that you'll need to "double-escape" a variable with some syntax like `$$foo`. -A `build` statement is first parsed as a space-separated list of -filenames and then each name is expanded. This means that spaces -within a variable will result in spaces in the expanded filename. +A `build` or `default` statement is first parsed as a space-separated +list of filenames and then each name is expanded. This means that +spaces within a variable will result in spaces in the expanded +filename. ---- spaced = foo bar diff --git a/src/ninja.cc b/src/ninja.cc index d4e697f..6bf7fbd 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -113,7 +113,7 @@ struct RealFileReader : public ManifestParser::FileReader { bool CollectTargetsFromArgs(State* state, int argc, char* argv[], vector* targets, string* err) { if (argc == 0) { - *targets = state->RootNodes(err); + *targets = state->DefaultNodes(err); if (!err->empty()) return false; } else { diff --git a/src/ninja.h b/src/ninja.h index 4953711..448a060 100644 --- a/src/ninja.h +++ b/src/ninja.h @@ -46,9 +46,11 @@ struct State { Node* LookupNode(const string& path); void AddIn(Edge* edge, const string& path); void AddOut(Edge* edge, const string& path); + bool AddDefault(const string& path, string* error); /// @return the root node(s) of the graph. (Root nodes have no output edges). /// @param error where to write the error message if somethings went wrong. vector RootNodes(string* error); + vector DefaultNodes(string* error); StatCache stat_cache_; /// All the rules used in the graph. @@ -56,6 +58,7 @@ struct State { /// All the edges of the graph. vector edges_; BindingEnv bindings_; + vector defaults_; struct BuildLog* build_log_; static const Rule kPhonyRule; diff --git a/src/ninja_jumble.cc b/src/ninja_jumble.cc index e5cdf29..faea156 100644 --- a/src/ninja_jumble.cc +++ b/src/ninja_jumble.cc @@ -82,6 +82,16 @@ void State::AddOut(Edge* edge, const string& path) { node->in_edge_ = edge; } +bool State::AddDefault(const string& path, string* err) { + Node* node = LookupNode(path); + if (!node) { + *err = "unknown target '" + path + "'"; + return false; + } + defaults_.push_back(node); + return true; +} + vector State::RootNodes(string* err) { vector root_nodes; // Search for nodes with no output. @@ -99,3 +109,7 @@ vector State::RootNodes(string* err) { assert(edges_.empty() || !root_nodes.empty()); return root_nodes; } + +vector State::DefaultNodes(string* err) { + return defaults_.empty() ? RootNodes(err) : defaults_; +} diff --git a/src/parsers.cc b/src/parsers.cc index 2235ba2..420a6cf 100644 --- a/src/parsers.cc +++ b/src/parsers.cc @@ -295,6 +295,9 @@ bool ManifestParser::Parse(const string& input, string* err) { } else if (len == 5 && memcmp(token.pos_, "build", 5) == 0) { if (!ParseEdge(err)) return false; + } else if (len == 7 && memcmp(token.pos_, "default", 7) == 0) { + if (!ParseDefaults(err)) + return false; } else if ((len == 7 && memcmp(token.pos_, "include", 7) == 0) || (len == 8 && memcmp(token.pos_, "subninja", 8) == 0)) { if (!ParseFileInclude(err)) @@ -416,6 +419,31 @@ bool ManifestParser::ParseLetValue(EvalString* eval, string* err) { return true; } +bool ManifestParser::ParseDefaults(string* err) { + if (!tokenizer_.ExpectIdent("default", err)) + return false; + + string target; + if (!tokenizer_.ReadIdent(&target)) + return tokenizer_.ErrorExpected("target name", err); + + do { + EvalString eval; + string eval_err; + if (!eval.Parse(target, &eval_err)) + return tokenizer_.Error(eval_err, err); + string path = eval.Evaluate(env_); + CanonicalizePath(&path); + if (!state_->AddDefault(path, &eval_err)) + return tokenizer_.Error(eval_err, err); + } while (tokenizer_.ReadIdent(&target)); + + if (!tokenizer_.Newline(err)) + return false; + + return true; +} + bool ManifestParser::ParseEdge(string* err) { vector ins, outs; diff --git a/src/parsers.h b/src/parsers.h index 6bee0ae..bdc88d2 100644 --- a/src/parsers.h +++ b/src/parsers.h @@ -126,6 +126,7 @@ struct ManifestParser { /// current env. bool ParseLet(string* key, string* val, string* err); bool ParseEdge(string* err); + bool ParseDefaults(string* err); /// Parse either a 'subninja' or 'include' line. bool ParseFileInclude(string* err); diff --git a/src/parsers_test.cc b/src/parsers_test.cc index 2c2bdf2..b5d253f 100644 --- a/src/parsers_test.cc +++ b/src/parsers_test.cc @@ -190,7 +190,8 @@ TEST_F(ParserTest, ReservedWords) { ASSERT_NO_FATAL_FAILURE(AssertParse( "rule build\n" " command = rule run $out\n" -"build subninja: build include foo.cc\n")); +"build subninja: build include default foo.cc\n" +"default subninja\n")); } TEST_F(ParserTest, Errors) { @@ -340,6 +341,35 @@ TEST_F(ParserTest, Errors) { &err)); EXPECT_EQ("line 4, col 1: expected variable after $", err); } + + { + State state; + ManifestParser parser(&state, NULL); + string err; + EXPECT_FALSE(parser.Parse("default\n", + &err)); + EXPECT_EQ("line 1, col 8: expected target name, got newline", err); + } + + { + State state; + ManifestParser parser(&state, NULL); + string err; + EXPECT_FALSE(parser.Parse("default nonexistent\n", + &err)); + EXPECT_EQ("line 1, col 9: unknown target 'nonexistent'", err); + } + + { + State state; + ManifestParser parser(&state, NULL); + string err; + EXPECT_FALSE(parser.Parse("rule r\n command = r\n" + "build b: r\n" + "default b:\n", + &err)); + EXPECT_EQ("line 4, col 10: expected newline, got ':'", err); + } } TEST_F(ParserTest, SubNinja) { @@ -404,6 +434,39 @@ TEST_F(ParserTest, OrderOnly) { ASSERT_TRUE(edge->is_order_only(1)); } +TEST_F(ParserTest, DefaultDefault) { + ASSERT_NO_FATAL_FAILURE(AssertParse( +"rule cat\n command = cat $in > $out\n" +"build a: cat foo\n" +"build b: cat foo\n" +"build c: cat foo\n" +"build d: cat foo\n")); + + string err; + EXPECT_EQ(4u, state.DefaultNodes(&err).size()); + EXPECT_EQ("", err); +} + +TEST_F(ParserTest, DefaultStatements) { + ASSERT_NO_FATAL_FAILURE(AssertParse( +"rule cat\n command = cat $in > $out\n" +"build a: cat foo\n" +"build b: cat foo\n" +"build c: cat foo\n" +"build d: cat foo\n" +"third = c\n" +"default a b\n" +"default $third\n")); + + string err; + std::vector nodes = state.DefaultNodes(&err); + EXPECT_EQ("", err); + ASSERT_EQ(3u, nodes.size()); + EXPECT_EQ("a", nodes[0]->file_->path_); + EXPECT_EQ("b", nodes[1]->file_->path_); + EXPECT_EQ("c", nodes[2]->file_->path_); +} + TEST(MakefileParser, Basic) { MakefileParser parser; string err; -- 2.7.4