Skip to content

Commit

Permalink
Implement default target statements
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
pcc committed Aug 31, 2011
1 parent 6793057 commit 7a6c9d4
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 8 deletions.
40 changes: 34 additions & 6 deletions doc/manual.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~

Expand Down Expand Up @@ -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>>.
Expand Down Expand Up @@ -499,18 +525,20 @@ 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
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
Expand Down
2 changes: 1 addition & 1 deletion src/ninja.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions src/ninja.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,19 @@ 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.
map<string, const Rule*> rules_;
/// All the edges of the graph.
vector<Edge*> edges_;
BindingEnv bindings_;
vector<Node*> defaults_;
struct BuildLog* build_log_;

static const Rule kPhonyRule;
Expand Down
14 changes: 14 additions & 0 deletions src/ninja_jumble.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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_;
}
28 changes: 28 additions & 0 deletions src/parsers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions src/parsers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
65 changes: 64 additions & 1 deletion src/parsers_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 7a6c9d4

Please sign in to comment.