summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvan Martin <martine@danga.com>2011-10-17 22:16:49 (GMT)
committerEvan Martin <martine@danga.com>2011-10-17 22:16:49 (GMT)
commitafbe2185a3bbd2453d6b1c27ee8f7c1cce6371a3 (patch)
tree5d370ccd781409fd459f31adfe9dd9c4989a8562
parent07c1f9b14b5f7071a5def7d09414ec852a4372ef (diff)
parent5a7151a8a6ab989a4016f03306b020df38f21732 (diff)
downloadNinja-afbe2185a3bbd2453d6b1c27ee8f7c1cce6371a3.zip
Ninja-afbe2185a3bbd2453d6b1c27ee8f7c1cce6371a3.tar.gz
Ninja-afbe2185a3bbd2453d6b1c27ee8f7c1cce6371a3.tar.bz2
Merge pull request #120 from pcc/generator
Implement generator rules
-rwxr-xr-xconfigure.py3
-rw-r--r--doc/manual.asciidoc23
-rw-r--r--misc/ninja_syntax.py5
-rw-r--r--src/clean.cc5
-rw-r--r--src/clean.h5
-rw-r--r--src/clean_test.cc22
-rw-r--r--src/graph.cc4
-rw-r--r--src/graph.h3
-rw-r--r--src/ninja.cc56
-rw-r--r--src/parsers.cc6
10 files changed, 94 insertions, 38 deletions
diff --git a/configure.py b/configure.py
index 99d9108..17afea4 100755
--- a/configure.py
+++ b/configure.py
@@ -234,7 +234,8 @@ n.newline()
n.comment('Regenerate build files if build script changes.')
n.rule('configure',
- command='./configure.py $configure_args')
+ command='./configure.py $configure_args',
+ generator=True)
n.build('build.ninja', 'configure',
implicit=['configure.py', 'misc/ninja_syntax.py'])
n.newline()
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index ed44bd2..ff197b1 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -382,16 +382,15 @@ than the _depth_ mode. It returns non-zero if an error occurs.
one. It can be used to know which rule name to pass to
+ninja -t targets rule _name_+.
-`clean`:: remove built files. If used like this +ninja -t clean+ it
-removes all the built files. If used like this
-+ninja -t clean _targets..._+ or like this
-+ninja -t clean target _targets..._+ it removes the given targets and
-recursively all files built for it. If used like this
-+ninja -t clean rule _rules_+ it removes all files built using the given
+`clean`:: remove built files. If used like this +ninja -t clean+ it removes
+all the built files, except for those created by the generator. If used
+like this +ninja -t clean -g+ it also removes built files created by the
+generator. If used like this +ninja -t clean _targets..._+ it removes the
+given targets and recursively all files built for it. If used like this
++ninja -t clean -r _rules_+ it removes all files built using the given
rules. The depfiles are not removed. Files created but not referenced in
-the graph are not removed. This tool takes in account the +-v+ and the
-+-n+ options (note that +-n+ implies +-v+). It returns non-zero if an
-error occurs.
+the graph are not removed. This tool takes in account the +-v+ and the +-n+
+options (note that +-n+ implies +-v+). It returns non-zero if an error occurs.
Ninja file reference
--------------------
@@ -464,6 +463,12 @@ aborting due to a missing input.
the full command or its description; if a command fails, the full command
line will always be printed before the command's output.
+`generator`:: if present, specifies that this rule is used to re-invoke
+ the generator program. Files built using `generator` rules are
+ treated specially in two ways: firstly, they will not be rebuilt
+ if the command line changes; and secondly, they are not cleaned
+ by default.
+
Additionally, the special `$in` and `$out` variables expand to the
space-separated list of files provided to the `build` line referencing
this `rule`.
diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py
index aa7e124..6e8a87c 100644
--- a/misc/ninja_syntax.py
+++ b/misc/ninja_syntax.py
@@ -28,13 +28,16 @@ class Writer(object):
value = ' '.join(value)
self._line('%s = %s' % (key, value), indent)
- def rule(self, name, command, description=None, depfile=None):
+ def rule(self, name, command, description=None, depfile=None,
+ generator=False):
self._line('rule %s' % name)
self.variable('command', command, indent=1)
if description:
self.variable('description', description, indent=1)
if depfile:
self.variable('depfile', depfile, indent=1)
+ if generator:
+ self.variable('generator', '1', indent=1)
def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
variables=None):
diff --git a/src/clean.cc b/src/clean.cc
index d22ac59..033fa96 100644
--- a/src/clean.cc
+++ b/src/clean.cc
@@ -96,7 +96,7 @@ void Cleaner::PrintFooter() {
printf("%d files.\n", cleaned_files_count_);
}
-int Cleaner::CleanAll() {
+int Cleaner::CleanAll(bool generator) {
Reset();
PrintHeader();
for (vector<Edge*>::iterator e = state_->edges_.begin();
@@ -104,6 +104,9 @@ int Cleaner::CleanAll() {
// Do not try to remove phony targets
if ((*e)->rule_ == &State::kPhonyRule)
continue;
+ // Do not remove generator's files unless generator specified.
+ if (!generator && (*e)->rule_->generator_)
+ continue;
for (vector<Node*>::iterator out_node = (*e)->outputs_.begin();
out_node != (*e)->outputs_.end(); ++out_node) {
Remove((*out_node)->file_->path_);
diff --git a/src/clean.h b/src/clean.h
index ab606f4..4d9b4e6 100644
--- a/src/clean.h
+++ b/src/clean.h
@@ -49,9 +49,10 @@ class Cleaner {
/// @return non-zero if an error occurs.
int CleanTargets(int target_count, char* targets[]);
- /// Clean all built files.
+ /// Clean all built files, except for files created by generator rules.
+ /// @param generator If set, also clean files created by generator rules.
/// @return non-zero if an error occurs.
- int CleanAll();
+ int CleanAll(bool generator = false);
/// Clean all the file built with the given rule @a rule.
/// @return non-zero if an error occurs.
diff --git a/src/clean_test.cc b/src/clean_test.cc
index f031dee..8606239 100644
--- a/src/clean_test.cc
+++ b/src/clean_test.cc
@@ -212,6 +212,28 @@ TEST_F(CleanTest, CleanRuleDryRun) {
EXPECT_EQ(0u, fs_.files_removed_.size());
}
+TEST_F(CleanTest, CleanRuleGenerator) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule regen\n"
+" command = cat $in > $out\n"
+" generator = 1\n"
+"build out1: cat in1\n"
+"build out2: regen in2\n"));
+ fs_.Create("out1", 1, "");
+ fs_.Create("out2", 1, "");
+
+ Cleaner cleaner(&state_, config_, &fs_);
+ EXPECT_EQ(0, cleaner.CleanAll());
+ EXPECT_EQ(1, cleaner.cleaned_files_count());
+ EXPECT_EQ(1u, fs_.files_removed_.size());
+
+ fs_.Create("out1", 1, "");
+
+ EXPECT_EQ(0, cleaner.CleanAll(/*generator=*/true));
+ EXPECT_EQ(2, cleaner.cleaned_files_count());
+ EXPECT_EQ(2u, fs_.files_removed_.size());
+}
+
TEST_F(CleanTest, CleanFailure) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"build dir: cat src1\n"));
diff --git a/src/graph.cc b/src/graph.cc
index 0f16519..e1441ea 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -93,8 +93,10 @@ bool Edge::RecomputeDirty(State* state, DiskInterface* disk_interface,
(*i)->dirty_ = true;
} else {
// May also be dirty due to the command changing since the last build.
+ // But if this is a generator rule, the command changing does not make us
+ // dirty.
BuildLog::LogEntry* entry;
- if (state->build_log_ &&
+ if (!rule_->generator_ && state->build_log_ &&
(entry = state->build_log_->LookupByOutput((*i)->file_->path_))) {
if (command != entry->command)
(*i)->dirty_ = true;
diff --git a/src/graph.h b/src/graph.h
index bf714e3..5b6bdf2 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -74,7 +74,7 @@ struct Node {
/// An invokable build command and associated metadata (description, etc.).
struct Rule {
- Rule(const string& name) : name_(name) { }
+ Rule(const string& name) : name_(name), generator_(false) { }
bool ParseCommand(const string& command, string* err) {
return command_.Parse(command, err);
@@ -83,6 +83,7 @@ struct Rule {
EvalString command_;
EvalString description_;
EvalString depfile_;
+ bool generator_;
};
struct State;
diff --git a/src/ninja.cc b/src/ninja.cc
index 5e8d2e4..a058e95 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -358,30 +358,40 @@ int CmdRules(State* state, int argc, char* argv[]) {
}
int CmdClean(State* state, int argc, char* argv[], const BuildConfig& config) {
- Cleaner cleaner(state, config);
- if (argc >= 1)
- {
- string mode = argv[0];
- if (mode == "target") {
- if (argc >= 2) {
- return cleaner.CleanTargets(argc - 1, &argv[1]);
- } else {
- Error("expected a target to clean");
- return 1;
- }
- } else if (mode == "rule") {
- if (argc >= 2) {
- return cleaner.CleanRules(argc - 1, &argv[1]);
- } else {
- Error("expected a rule to clean");
+ bool generator = false;
+ bool clean_rules = false;
+
+ optind = 1;
+ int opt;
+ while ((opt = getopt(argc, argv, "gr")) != -1) {
+ switch (opt) {
+ case 'g':
+ generator = true;
+ break;
+ case 'r':
+ clean_rules = true;
+ break;
+ default:
+ Usage(config);
return 1;
- }
- } else {
- return cleaner.CleanTargets(argc, argv);
}
}
- else {
- return cleaner.CleanAll();
+ argv += optind;
+ argc -= optind;
+
+ if (clean_rules && argc == 0) {
+ Error("expected a rule to clean");
+ return 1;
+ }
+
+ Cleaner cleaner(state, config);
+ if (argc >= 1) {
+ if (clean_rules)
+ return cleaner.CleanRules(argc, argv);
+ else
+ return cleaner.CleanTargets(argc, argv);
+ } else {
+ return cleaner.CleanAll(generator);
}
}
@@ -479,8 +489,10 @@ reload:
return CmdTargets(&state, argc, argv);
if (tool == "rules")
return CmdRules(&state, argc, argv);
+ // The clean tool uses getopt, and expects argv[0] to contain the name of
+ // the tool, i.e. "clean".
if (tool == "clean")
- return CmdClean(&state, argc, argv, config);
+ return CmdClean(&state, argc+1, argv-1, config);
Error("unknown tool '%s'", tool.c_str());
}
diff --git a/src/parsers.cc b/src/parsers.cc
index 9ed2938..c086109 100644
--- a/src/parsers.cc
+++ b/src/parsers.cc
@@ -383,6 +383,12 @@ bool ManifestParser::ParseRule(string* err) {
eval_target = &rule->depfile_;
} else if (key == "description") {
eval_target = &rule->description_;
+ } else if (key == "generator") {
+ rule->generator_ = true;
+ string dummy;
+ if (!tokenizer_.ReadToNewline(&dummy, err))
+ return false;
+ continue;
} else {
// Die on other keyvals for now; revisit if we want to add a
// scope here.