summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Collingbourne <peter@pcc.me.uk>2011-08-31 03:55:35 (GMT)
committerPeter Collingbourne <peter@pcc.me.uk>2011-08-31 16:51:05 (GMT)
commit7a6c9d480d4ca7020f74e5f4dfbaf28c9ff2a6a4 (patch)
tree20aff5ee6b1061c0f53c51805419225a1367b2bd
parent679305757e68861885f1e5c819625d2f0bca6c0b (diff)
downloadNinja-7a6c9d480d4ca7020f74e5f4dfbaf28c9ff2a6a4.zip
Ninja-7a6c9d480d4ca7020f74e5f4dfbaf28c9ff2a6a4.tar.gz
Ninja-7a6c9d480d4ca7020f74e5f4dfbaf28c9ff2a6a4.tar.bz2
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).
-rw-r--r--doc/manual.asciidoc40
-rw-r--r--src/ninja.cc2
-rw-r--r--src/ninja.h3
-rw-r--r--src/ninja_jumble.cc14
-rw-r--r--src/parsers.cc28
-rw-r--r--src/parsers.h1
-rw-r--r--src/parsers_test.cc65
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
<<ref_scope,in the discussion about scoping>>.
@@ -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<Node*>* 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<Node*> RootNodes(string* error);
+ vector<Node*> 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<Edge*> edges_;
BindingEnv bindings_;
+ vector<Node*> 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<Node*> State::RootNodes(string* err) {
vector<Node*> root_nodes;
// Search for nodes with no output.
@@ -99,3 +109,7 @@ vector<Node*> State::RootNodes(string* err) {
assert(edges_.empty() || !root_nodes.empty());
return root_nodes;
}
+
+vector<Node*> 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<string> 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<Node*> 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;