From 7cf2bdffe2a95ea5e30e9c3166ef4398add8e6b9 Mon Sep 17 00:00:00 2001 From: Peter Collingbourne Date: Thu, 22 Sep 2011 17:04:03 +0100 Subject: Implement generator rules Introduce a rule attribute "generator" which, 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. A command line flag "-g" is introduced for the clean tool, which causes it to remove generator files. Fixes issue #102. --- configure.py | 3 ++- doc/manual.asciidoc | 26 ++++++++++++++++---------- misc/ninja_syntax.py | 5 ++++- src/clean.cc | 5 ++++- src/clean.h | 5 +++-- src/clean_test.cc | 22 ++++++++++++++++++++++ src/graph.cc | 4 +++- src/graph.h | 3 ++- src/ninja.cc | 23 +++++++++++++++++++++-- src/parsers.cc | 6 ++++++ 10 files changed, 83 insertions(+), 19 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..7e7063b 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -382,16 +382,16 @@ 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 -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. +`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..._+ 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 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. Ninja file reference -------------------- @@ -464,6 +464,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::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::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..5b96ca1 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -358,6 +358,23 @@ int CmdRules(State* state, int argc, char* argv[]) { } int CmdClean(State* state, int argc, char* argv[], const BuildConfig& config) { + bool generator = false; + + optind = 1; + int opt; + while ((opt = getopt(argc, argv, "g")) != -1) { + switch (opt) { + case 'g': + generator = true; + break; + default: + Usage(config); + return 1; + } + } + argv += optind; + argc -= optind; + Cleaner cleaner(state, config); if (argc >= 1) { @@ -381,7 +398,7 @@ int CmdClean(State* state, int argc, char* argv[], const BuildConfig& config) { } } else { - return cleaner.CleanAll(); + return cleaner.CleanAll(generator); } } @@ -479,8 +496,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. -- cgit v0.12 From 5a7151a8a6ab989a4016f03306b020df38f21732 Mon Sep 17 00:00:00 2001 From: Peter Collingbourne Date: Sat, 15 Oct 2011 19:55:33 +0100 Subject: Make the command line interface for the clean tool consistent Since we have started using command line flags for the clean tool, it is inconsistent to keep the "target" and "rule" prefixes. Replace them with a "-r" flag with the same semantics as "rule". --- doc/manual.asciidoc | 17 ++++++++--------- src/ninja.cc | 37 +++++++++++++++---------------------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index 7e7063b..ff197b1 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -383,15 +383,14 @@ 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, 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..._+ 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 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. +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. Ninja file reference -------------------- diff --git a/src/ninja.cc b/src/ninja.cc index 5b96ca1..a058e95 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -359,14 +359,18 @@ int CmdRules(State* state, int argc, char* argv[]) { int CmdClean(State* state, int argc, char* argv[], const BuildConfig& config) { bool generator = false; + bool clean_rules = false; optind = 1; int opt; - while ((opt = getopt(argc, argv, "g")) != -1) { + 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; @@ -375,29 +379,18 @@ int CmdClean(State* state, int argc, char* argv[], const BuildConfig& config) { argv += optind; argc -= optind; + if (clean_rules && argc == 0) { + Error("expected a rule to clean"); + return 1; + } + 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"); - return 1; - } - } else { + if (argc >= 1) { + if (clean_rules) + return cleaner.CleanRules(argc, argv); + else return cleaner.CleanTargets(argc, argv); - } - } - else { + } else { return cleaner.CleanAll(generator); } } -- cgit v0.12