diff options
author | Evan Martin <martine@danga.com> | 2011-10-17 22:16:49 (GMT) |
---|---|---|
committer | Evan Martin <martine@danga.com> | 2011-10-17 22:16:49 (GMT) |
commit | afbe2185a3bbd2453d6b1c27ee8f7c1cce6371a3 (patch) | |
tree | 5d370ccd781409fd459f31adfe9dd9c4989a8562 | |
parent | 07c1f9b14b5f7071a5def7d09414ec852a4372ef (diff) | |
parent | 5a7151a8a6ab989a4016f03306b020df38f21732 (diff) | |
download | Ninja-afbe2185a3bbd2453d6b1c27ee8f7c1cce6371a3.zip Ninja-afbe2185a3bbd2453d6b1c27ee8f7c1cce6371a3.tar.gz Ninja-afbe2185a3bbd2453d6b1c27ee8f7c1cce6371a3.tar.bz2 |
Merge pull request #120 from pcc/generator
Implement generator rules
-rwxr-xr-x | configure.py | 3 | ||||
-rw-r--r-- | doc/manual.asciidoc | 23 | ||||
-rw-r--r-- | misc/ninja_syntax.py | 5 | ||||
-rw-r--r-- | src/clean.cc | 5 | ||||
-rw-r--r-- | src/clean.h | 5 | ||||
-rw-r--r-- | src/clean_test.cc | 22 | ||||
-rw-r--r-- | src/graph.cc | 4 | ||||
-rw-r--r-- | src/graph.h | 3 | ||||
-rw-r--r-- | src/ninja.cc | 56 | ||||
-rw-r--r-- | src/parsers.cc | 6 |
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. |