diff --git a/Help/command/add_custom_command.rst b/Help/command/add_custom_command.rst index cb0746b8f..9fbad4bf6 100644 --- a/Help/command/add_custom_command.rst +++ b/Help/command/add_custom_command.rst @@ -15,6 +15,7 @@ The first signature is for adding a custom command to produce an output:: [COMMAND command2 [ARGS] [args2...] ...] [MAIN_DEPENDENCY depend] [DEPENDS [depends...]] + [BYPRODUCTS [files...]] [IMPLICIT_DEPENDS depend1 [ depend2] ...] [WORKING_DIRECTORY dir] @@ -44,6 +45,27 @@ The options are: options are currently ignored when APPEND is given, but may be used in the future. +``BYPRODUCTS`` + Specify the files the command is expected to produce but whose + modification time may or may not be newer than the dependencies. + If a byproduct name is a relative path it will be interpreted + relative to the build tree directory corresponding to the + current source directory. + Each byproduct file will be marked with the :prop_sf:`GENERATED` + source file property automatically. + + Explicit specification of byproducts is supported by the + :generator:`Ninja` generator to tell the ``ninja`` build tool + how to regenerate byproducts when they are missing. It is + also useful when other build rules (e.g. custom commands) + depend on the byproducts. Ninja requires a build rule for any + generated file on which another rule depends even if there are + order-only dependencies to ensure the byproducts will be + available before their dependents build. + + The ``BYPRODUCTS`` option is ignored on non-Ninja generators + except to mark byproducts ``GENERATED``. + ``COMMAND`` Specify the command-line(s) to execute at build time. If more than one ``COMMAND`` is specified they will be executed in order, @@ -156,6 +178,7 @@ target is already built, the command will not execute. PRE_BUILD | PRE_LINK | POST_BUILD COMMAND command1 [ARGS] [args1...] [COMMAND command2 [ARGS] [args2...] ...] + [BYPRODUCTS [files...]] [WORKING_DIRECTORY dir] [COMMENT comment] [VERBATIM] [USES_TERMINAL]) diff --git a/Help/command/add_custom_target.rst b/Help/command/add_custom_target.rst index 8b7472df8..996d08e7d 100644 --- a/Help/command/add_custom_target.rst +++ b/Help/command/add_custom_target.rst @@ -8,6 +8,7 @@ Add a target with no output so it will always be built. add_custom_target(Name [ALL] [command1 [args1...]] [COMMAND command2 [args2...] ...] [DEPENDS depend depend depend ... ] + [BYPRODUCTS [files...]] [WORKING_DIRECTORY dir] [COMMENT comment] [VERBATIM] [USES_TERMINAL] @@ -28,6 +29,27 @@ The options are: target so that it will be run every time (the command cannot be called ``ALL``). +``BYPRODUCTS`` + Specify the files the command is expected to produce but whose + modification time may or may not be updated on subsequent builds. + If a byproduct name is a relative path it will be interpreted + relative to the build tree directory corresponding to the + current source directory. + Each byproduct file will be marked with the :prop_sf:`GENERATED` + source file property automatically. + + Explicit specification of byproducts is supported by the + :generator:`Ninja` generator to tell the ``ninja`` build tool + how to regenerate byproducts when they are missing. It is + also useful when other build rules (e.g. custom commands) + depend on the byproducts. Ninja requires a build rule for any + generated file on which another rule depends even if there are + order-only dependencies to ensure the byproducts will be + available before their dependents build. + + The ``BYPRODUCTS`` option is ignored on non-Ninja generators + except to mark byproducts ``GENERATED``. + ``COMMAND`` Specify the command-line(s) to execute at build time. If more than one ``COMMAND`` is specified they will be executed in order, diff --git a/Source/cmAddCustomCommandCommand.cxx b/Source/cmAddCustomCommandCommand.cxx index 410f97878..818b91082 100644 --- a/Source/cmAddCustomCommandCommand.cxx +++ b/Source/cmAddCustomCommandCommand.cxx @@ -32,7 +32,7 @@ bool cmAddCustomCommandCommand std::string source, target, main_dependency, working; std::string comment_buffer; const char* comment = 0; - std::vector depends, outputs, output; + std::vector depends, outputs, output, byproducts; bool verbatim = false; bool append = false; bool uses_terminal = false; @@ -57,6 +57,7 @@ bool cmAddCustomCommandCommand doing_main_dependency, doing_output, doing_outputs, + doing_byproducts, doing_comment, doing_working_directory, doing_nothing @@ -127,6 +128,10 @@ bool cmAddCustomCommandCommand { doing = doing_output; } + else if (copy == "BYPRODUCTS") + { + doing = doing_byproducts; + } else if (copy == "WORKING_DIRECTORY") { doing = doing_working_directory; @@ -150,6 +155,7 @@ bool cmAddCustomCommandCommand { case doing_output: case doing_outputs: + case doing_byproducts: if (!cmSystemTools::FileIsFullPath(copy.c_str())) { // This is an output to be generated, so it should be @@ -233,6 +239,9 @@ bool cmAddCustomCommandCommand case doing_outputs: outputs.push_back(filename); break; + case doing_byproducts: + byproducts.push_back(filename); + break; case doing_comment: comment_buffer = copy; comment = comment_buffer.c_str(); @@ -272,7 +281,9 @@ bool cmAddCustomCommandCommand } // Make sure the output names and locations are safe. - if(!this->CheckOutputs(output) || !this->CheckOutputs(outputs)) + if(!this->CheckOutputs(output) || + !this->CheckOutputs(outputs) || + !this->CheckOutputs(byproducts)) { return false; } @@ -314,7 +325,7 @@ bool cmAddCustomCommandCommand { // Source is empty, use the target. std::vector no_depends; - this->Makefile->AddCustomCommandToTarget(target, no_depends, + this->Makefile->AddCustomCommandToTarget(target, byproducts, no_depends, commandLines, cctype, comment, working.c_str(), escapeOldStyle, uses_terminal); @@ -322,8 +333,8 @@ bool cmAddCustomCommandCommand else if(target.empty()) { // Target is empty, use the output. - this->Makefile->AddCustomCommandToOutput(output, depends, - main_dependency, + this->Makefile->AddCustomCommandToOutput(output, byproducts, + depends, main_dependency, commandLines, comment, working.c_str(), false, escapeOldStyle, uses_terminal); @@ -351,6 +362,11 @@ bool cmAddCustomCommandCommand } } } + else if (!byproducts.empty()) + { + this->SetError("BYPRODUCTS may not be specified with SOURCE signatures"); + return false; + } else if (uses_terminal) { this->SetError("USES_TERMINAL may not be used with SOURCE signatures"); diff --git a/Source/cmAddCustomTargetCommand.cxx b/Source/cmAddCustomTargetCommand.cxx index fc4f8f198..09c8af554 100644 --- a/Source/cmAddCustomTargetCommand.cxx +++ b/Source/cmAddCustomTargetCommand.cxx @@ -45,7 +45,7 @@ bool cmAddCustomTargetCommand cmCustomCommandLines commandLines; // Accumulate dependencies. - std::vector depends; + std::vector depends, byproducts; std::string working_directory; bool verbatim = false; bool uses_terminal = false; @@ -57,6 +57,7 @@ bool cmAddCustomTargetCommand enum tdoing { doing_command, doing_depends, + doing_byproducts, doing_working_directory, doing_comment, doing_source, @@ -85,6 +86,10 @@ bool cmAddCustomTargetCommand { doing = doing_depends; } + else if(copy == "BYPRODUCTS") + { + doing = doing_byproducts; + } else if(copy == "WORKING_DIRECTORY") { doing = doing_working_directory; @@ -128,6 +133,19 @@ bool cmAddCustomTargetCommand case doing_command: currentLine.push_back(copy); break; + case doing_byproducts: + { + std::string filename; + if (!cmSystemTools::FileIsFullPath(copy.c_str())) + { + filename = this->Makefile->GetCurrentOutputDirectory(); + filename += "/"; + } + filename += copy; + cmSystemTools::ConvertToUnixSlashes(filename); + byproducts.push_back(filename); + } + break; case doing_depends: { std::string dep = copy; @@ -227,6 +245,12 @@ bool cmAddCustomTargetCommand cmSystemTools::CollapseFullPath(working_directory, build_dir); } + if (commandLines.empty() && !byproducts.empty()) + { + this->Makefile->IssueMessage(cmake::FATAL_ERROR, + "BYPRODUCTS may not be specified without any COMMAND"); + return true; + } if (commandLines.empty() && uses_terminal) { this->Makefile->IssueMessage(cmake::FATAL_ERROR, @@ -238,7 +262,8 @@ bool cmAddCustomTargetCommand bool escapeOldStyle = !verbatim; cmTarget* target = this->Makefile->AddUtilityCommand(targetName, excludeFromAll, - working_directory.c_str(), depends, + working_directory.c_str(), + byproducts, depends, commandLines, escapeOldStyle, comment, uses_terminal); diff --git a/Source/cmCPluginAPI.cxx b/Source/cmCPluginAPI.cxx index dd2a1b8f7..b304f28dd 100644 --- a/Source/cmCPluginAPI.cxx +++ b/Source/cmCPluginAPI.cxx @@ -353,10 +353,11 @@ void CCONV cmAddCustomCommandToTarget(void *arg, const char* target, } // Pass the call to the makefile instance. + std::vector no_byproducts; std::vector no_depends; const char* no_comment = 0; const char* no_working_dir = 0; - mf->AddCustomCommandToTarget(target, no_depends, commandLines, + mf->AddCustomCommandToTarget(target, no_byproducts, no_depends, commandLines, cctype, no_comment, no_working_dir); } diff --git a/Source/cmCustomCommand.cxx b/Source/cmCustomCommand.cxx index 45369cc2b..2afb029ab 100644 --- a/Source/cmCustomCommand.cxx +++ b/Source/cmCustomCommand.cxx @@ -28,6 +28,7 @@ cmCustomCommand::cmCustomCommand() //---------------------------------------------------------------------------- cmCustomCommand::cmCustomCommand(const cmCustomCommand& r): Outputs(r.Outputs), + Byproducts(r.Byproducts), Depends(r.Depends), CommandLines(r.CommandLines), HaveComment(r.HaveComment), @@ -49,6 +50,7 @@ cmCustomCommand& cmCustomCommand::operator=(cmCustomCommand const& r) } this->Outputs = r.Outputs; + this->Byproducts= r.Byproducts; this->Depends = r.Depends; this->CommandLines = r.CommandLines; this->HaveComment = r.HaveComment; @@ -66,11 +68,13 @@ cmCustomCommand& cmCustomCommand::operator=(cmCustomCommand const& r) //---------------------------------------------------------------------------- cmCustomCommand::cmCustomCommand(cmMakefile const* mf, const std::vector& outputs, + const std::vector& byproducts, const std::vector& depends, const cmCustomCommandLines& commandLines, const char* comment, const char* workingDirectory): Outputs(outputs), + Byproducts(byproducts), Depends(depends), CommandLines(commandLines), HaveComment(comment?true:false), @@ -99,6 +103,12 @@ const std::vector& cmCustomCommand::GetOutputs() const return this->Outputs; } +//---------------------------------------------------------------------------- +const std::vector& cmCustomCommand::GetByproducts() const +{ + return this->Byproducts; +} + //---------------------------------------------------------------------------- const std::vector& cmCustomCommand::GetDepends() const { diff --git a/Source/cmCustomCommand.h b/Source/cmCustomCommand.h index 283a0e462..0bfaef23b 100644 --- a/Source/cmCustomCommand.h +++ b/Source/cmCustomCommand.h @@ -32,6 +32,7 @@ public: /** Main constructor specifies all information for the command. */ cmCustomCommand(cmMakefile const* mf, const std::vector& outputs, + const std::vector& byproducts, const std::vector& depends, const cmCustomCommandLines& commandLines, const char* comment, @@ -42,6 +43,9 @@ public: /** Get the output file produced by the command. */ const std::vector& GetOutputs() const; + /** Get the extra files produced by the command. */ + const std::vector& GetByproducts() const; + /** Get the vector that holds the list of dependencies. */ const std::vector& GetDepends() const; @@ -86,6 +90,7 @@ public: private: std::vector Outputs; + std::vector Byproducts; std::vector Depends; cmCustomCommandLines CommandLines; bool HaveComment; diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx index 1bca6e68e..162d7a199 100644 --- a/Source/cmCustomCommandGenerator.cxx +++ b/Source/cmCustomCommandGenerator.cxx @@ -90,6 +90,12 @@ std::vector const& cmCustomCommandGenerator::GetOutputs() const return this->CC.GetOutputs(); } +//---------------------------------------------------------------------------- +std::vector const& cmCustomCommandGenerator::GetByproducts() const +{ + return this->CC.GetByproducts(); +} + //---------------------------------------------------------------------------- std::vector const& cmCustomCommandGenerator::GetDepends() const { diff --git a/Source/cmCustomCommandGenerator.h b/Source/cmCustomCommandGenerator.h index 0d8a0a4ed..b4ae0142b 100644 --- a/Source/cmCustomCommandGenerator.h +++ b/Source/cmCustomCommandGenerator.h @@ -42,6 +42,7 @@ public: const char* GetComment() const; std::string GetWorkingDirectory() const; std::vector const& GetOutputs() const; + std::vector const& GetByproducts() const; std::vector const& GetDepends() const; }; diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 4d49fe3da..b9c9b3b5e 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -2510,10 +2510,11 @@ cmTarget cmGlobalGenerator::CreateGlobalTarget( target.SetProperty("EXCLUDE_FROM_ALL","TRUE"); std::vector no_outputs; + std::vector no_byproducts; std::vector no_depends; // Store the custom command in the target. - cmCustomCommand cc(0, no_outputs, no_depends, *commandLines, 0, - workingDirectory); + cmCustomCommand cc(0, no_outputs, no_byproducts, no_depends, + *commandLines, 0, workingDirectory); cc.SetUsesTerminal(uses_terminal); target.AddPostBuildCommand(cc); target.SetProperty("EchoString", message); diff --git a/Source/cmGlobalVisualStudio8Generator.cxx b/Source/cmGlobalVisualStudio8Generator.cxx index 745515bcf..e6ce45d6e 100644 --- a/Source/cmGlobalVisualStudio8Generator.cxx +++ b/Source/cmGlobalVisualStudio8Generator.cxx @@ -341,9 +341,10 @@ bool cmGlobalVisualStudio8Generator::AddCheckTarget() // overwritten by the CreateVCProjBuildRule. // (this could be avoided with per-target source files) std::string no_main_dependency = ""; + std::vector no_byproducts; if(cmSourceFile* file = mf->AddCustomCommandToOutput( - stamps, listFiles, + stamps, no_byproducts, listFiles, no_main_dependency, commandLines, "Checking Build System", no_working_directory, true)) { diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index 5e7a898a7..b9f64e256 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -477,7 +477,9 @@ cmGlobalXCodeGenerator::AddExtraTargets(cmLocalGenerator* root, this->PostBuildMakeTarget(target.GetName(), "$(CONFIGURATION)"); cmCustomCommandLines commandLines; commandLines.push_back(makeHelper); + std::vector no_byproducts; lg->GetMakefile()->AddCustomCommandToTarget(target.GetName(), + no_byproducts, no_depends, commandLines, cmTarget::POST_BUILD, @@ -1366,6 +1368,7 @@ void cmGlobalXCodeGenerator::CreateCustomCommands(cmXCodeObject* buildPhases, cmd[0].push_back(str_link_file); cmCustomCommand command(this->CurrentMakefile, + std::vector(), std::vector(), std::vector(), cmd, diff --git a/Source/cmLocalNinjaGenerator.cxx b/Source/cmLocalNinjaGenerator.cxx index 3c39b6279..0b0d9714d 100644 --- a/Source/cmLocalNinjaGenerator.cxx +++ b/Source/cmLocalNinjaGenerator.cxx @@ -440,10 +440,18 @@ cmLocalNinjaGenerator::WriteCustomCommandBuildStatement( cmCustomCommandGenerator ccg(*cc, this->GetConfigName(), this->Makefile); const std::vector &outputs = ccg.GetOutputs(); - cmNinjaDeps ninjaOutputs(outputs.size()), ninjaDeps; + const std::vector &byproducts = ccg.GetByproducts(); + cmNinjaDeps ninjaOutputs(outputs.size()+byproducts.size()), ninjaDeps; +#if 0 +#error TODO: Once CC in an ExternalProject target must provide the \ + file of each imported target that has an add_dependencies pointing \ + at us. How to know which ExternalProject step actually provides it? +#endif std::transform(outputs.begin(), outputs.end(), ninjaOutputs.begin(), MapToNinjaPath()); + std::transform(byproducts.begin(), byproducts.end(), + ninjaOutputs.begin() + outputs.size(), MapToNinjaPath()); this->AppendCustomCommandDeps(ccg, ninjaDeps); for (cmNinjaDeps::iterator i = ninjaOutputs.begin(); i != ninjaOutputs.end(); diff --git a/Source/cmLocalVisualStudio6Generator.cxx b/Source/cmLocalVisualStudio6Generator.cxx index c14fb2b67..b9a507421 100644 --- a/Source/cmLocalVisualStudio6Generator.cxx +++ b/Source/cmLocalVisualStudio6Generator.cxx @@ -821,10 +821,12 @@ cmLocalVisualStudio6Generator::MaybeCreateOutputDir(cmTarget& target, command.push_back("make_directory"); command.push_back(outDir); std::vector no_output; + std::vector no_byproducts; std::vector no_depends; cmCustomCommandLines commands; commands.push_back(command); - pcc.reset(new cmCustomCommand(0, no_output, no_depends, commands, 0, 0)); + pcc.reset(new cmCustomCommand(0, no_output, no_byproducts, + no_depends, commands, 0, 0)); pcc->SetEscapeOldStyle(false); pcc->SetEscapeAllowMakeVars(true); return pcc; diff --git a/Source/cmLocalVisualStudioGenerator.cxx b/Source/cmLocalVisualStudioGenerator.cxx index 9680d43b1..f01aa6ba8 100644 --- a/Source/cmLocalVisualStudioGenerator.cxx +++ b/Source/cmLocalVisualStudioGenerator.cxx @@ -95,10 +95,12 @@ cmLocalVisualStudioGenerator::MaybeCreateImplibDir(cmTarget& target, command.push_back("make_directory"); command.push_back(impDir); std::vector no_output; + std::vector no_byproducts; std::vector no_depends; cmCustomCommandLines commands; commands.push_back(command); - pcc.reset(new cmCustomCommand(0, no_output, no_depends, commands, 0, 0)); + pcc.reset(new cmCustomCommand(0, no_output, no_byproducts, + no_depends, commands, 0, 0)); pcc->SetEscapeOldStyle(false); pcc->SetEscapeAllowMakeVars(true); return pcc; diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 7e5e4e70f..61807b2d5 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -880,13 +880,14 @@ void cmMakefile::ConfigureFinalPass() //---------------------------------------------------------------------------- void cmMakefile::AddCustomCommandToTarget(const std::string& target, + const std::vector& byproducts, const std::vector& depends, const cmCustomCommandLines& commandLines, cmTarget::CustomCommandType type, const char* comment, const char* workingDir, bool escapeOldStyle, - bool uses_terminal) const + bool uses_terminal) { // Find the target to which to add the custom command. cmTargets::iterator ti = this->Targets.find(target); @@ -936,9 +937,20 @@ cmMakefile::AddCustomCommandToTarget(const std::string& target, this->IssueMessage(cmake::FATAL_ERROR, e.str()); return; } + + // Always create the byproduct sources and mark them generated. + for(std::vector::const_iterator o = byproducts.begin(); + o != byproducts.end(); ++o) + { + if(cmSourceFile* out = this->GetOrCreateSource(*o, true)) + { + out->SetProperty("GENERATED", "1"); + } + } + // Add the command to the appropriate build step for the target. std::vector no_output; - cmCustomCommand cc(this, no_output, depends, + cmCustomCommand cc(this, no_output, byproducts, depends, commandLines, comment, workingDir); cc.SetEscapeOldStyle(escapeOldStyle); cc.SetEscapeAllowMakeVars(true); @@ -960,6 +972,7 @@ cmMakefile::AddCustomCommandToTarget(const std::string& target, //---------------------------------------------------------------------------- cmSourceFile* cmMakefile::AddCustomCommandToOutput(const std::vector& outputs, + const std::vector& byproducts, const std::vector& depends, const std::string& main_dependency, const cmCustomCommandLines& commandLines, @@ -1058,6 +1071,14 @@ cmMakefile::AddCustomCommandToOutput(const std::vector& outputs, out->SetProperty("GENERATED", "1"); } } + for(std::vector::const_iterator o = byproducts.begin(); + o != byproducts.end(); ++o) + { + if(cmSourceFile* out = this->GetOrCreateSource(*o, true)) + { + out->SetProperty("GENERATED", "1"); + } + } // Attach the custom command to the file. if(file) @@ -1070,8 +1091,8 @@ cmMakefile::AddCustomCommandToOutput(const std::vector& outputs, } cmCustomCommand* cc = - new cmCustomCommand(this, outputs, depends2, commandLines, - comment, workingDir); + new cmCustomCommand(this, outputs, byproducts, depends2, + commandLines, comment, workingDir); cc->SetEscapeOldStyle(escapeOldStyle); cc->SetEscapeAllowMakeVars(true); cc->SetUsesTerminal(uses_terminal); @@ -1128,7 +1149,9 @@ cmMakefile::AddCustomCommandToOutput(const std::string& output, { std::vector outputs; outputs.push_back(output); - return this->AddCustomCommandToOutput(outputs, depends, main_dependency, + std::vector no_byproducts; + return this->AddCustomCommandToOutput(outputs, no_byproducts, + depends, main_dependency, commandLines, comment, workingDir, replace, escapeOldStyle, uses_terminal); @@ -1150,7 +1173,9 @@ cmMakefile::AddCustomCommandOldStyle(const std::string& target, // In the old-style signature if the source and target were the // same then it added a post-build rule to the target. Preserve // this behavior. - this->AddCustomCommandToTarget(target, depends, commandLines, + std::vector no_byproducts; + this->AddCustomCommandToTarget(target, no_byproducts, + depends, commandLines, cmTarget::POST_BUILD, comment, 0); return; } @@ -1250,6 +1275,23 @@ cmMakefile::AddUtilityCommand(const std::string& utilityName, const cmCustomCommandLines& commandLines, bool escapeOldStyle, const char* comment, bool uses_terminal) +{ + std::vector no_byproducts; + return this->AddUtilityCommand(utilityName, excludeFromAll, workingDirectory, + no_byproducts, depends, commandLines, + escapeOldStyle, comment, uses_terminal); +} + +//---------------------------------------------------------------------------- +cmTarget* +cmMakefile::AddUtilityCommand(const std::string& utilityName, + bool excludeFromAll, + const char* workingDirectory, + const std::vector& byproducts, + const std::vector& depends, + const cmCustomCommandLines& commandLines, + bool escapeOldStyle, const char* comment, + bool uses_terminal) { // Create a target instance for this utility. cmTarget* target = this->AddNewTarget(cmTarget::UTILITY, utilityName); @@ -1270,10 +1312,12 @@ cmMakefile::AddUtilityCommand(const std::string& utilityName, force += cmake::GetCMakeFilesDirectory(); force += "/"; force += utilityName; + std::vector forced; + forced.push_back(force); std::string no_main_dependency = ""; bool no_replace = false; - this->AddCustomCommandToOutput(force, depends, - no_main_dependency, + this->AddCustomCommandToOutput(forced, byproducts, + depends, no_main_dependency, commandLines, comment, workingDirectory, no_replace, escapeOldStyle, uses_terminal); @@ -1289,6 +1333,16 @@ cmMakefile::AddUtilityCommand(const std::string& utilityName, cmSystemTools::Error("Could not get source file entry for ", force.c_str()); } + + // Always create the byproduct sources and mark them generated. + for(std::vector::const_iterator o = byproducts.begin(); + o != byproducts.end(); ++o) + { + if(cmSourceFile* out = this->GetOrCreateSource(*o, true)) + { + out->SetProperty("GENERATED", "1"); + } + } } return target; } diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index 73c299e0d..0458d5477 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -170,14 +170,16 @@ public: /** Add a custom command to the build. */ void AddCustomCommandToTarget(const std::string& target, + const std::vector& byproducts, const std::vector& depends, const cmCustomCommandLines& commandLines, cmTarget::CustomCommandType type, const char* comment, const char* workingDir, bool escapeOldStyle = true, - bool uses_terminal = false) const; + bool uses_terminal = false); cmSourceFile* AddCustomCommandToOutput( const std::vector& outputs, + const std::vector& byproducts, const std::vector& depends, const std::string& main_dependency, const cmCustomCommandLines& commandLines, @@ -242,6 +244,15 @@ public: bool escapeOldStyle = true, const char* comment = 0, bool uses_terminal = false); + cmTarget* AddUtilityCommand(const std::string& utilityName, + bool excludeFromAll, + const char* workingDirectory, + const std::vector& byproducts, + const std::vector& depends, + const cmCustomCommandLines& commandLines, + bool escapeOldStyle = true, + const char* comment = 0, + bool uses_terminal = false); /** * Add a link library to the build. diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx index 48c4a2dff..25931f3d6 100644 --- a/Source/cmNinjaNormalTargetGenerator.cxx +++ b/Source/cmNinjaNormalTargetGenerator.cxx @@ -256,7 +256,7 @@ cmNinjaNormalTargetGenerator /*deptype*/ "", rspfile, rspcontent, - /*restat*/ "", + /*restat*/ "$RESTAT", /*generator*/ false); } @@ -556,6 +556,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement() &postBuildCmdLines }; + cmNinjaDeps byproducts; for (unsigned i = 0; i != 3; ++i) { for (std::vector::const_iterator @@ -564,6 +565,9 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement() { cmCustomCommandGenerator ccg(*ci, cfgName, mf); localGen.AppendCustomCommandLines(ccg, *cmdLineLists[i]); + std::vector const& ccByproducts = ccg.GetByproducts(); + std::transform(ccByproducts.begin(), ccByproducts.end(), + std::back_inserter(byproducts), MapToNinjaPath()); } } @@ -611,6 +615,17 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement() this->GetLocalGenerator()->AppendTargetDepends(this->GetTarget(), orderOnlyDeps); + // Ninja should restat after linking if and only if there are byproducts. + vars["RESTAT"] = byproducts.empty()? "" : "1"; + + for (cmNinjaDeps::const_iterator oi = byproducts.begin(), + oe = byproducts.end(); + oi != oe; ++oi) + { + this->GetGlobalGenerator()->SeenCustomCommandOutput(*oi); + outputs.push_back(*oi); + } + // Write the build statement for this target. globalGen.WriteBuild(this->GetBuildFileStream(), comment.str(), diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx index 57fbcd3f4..796776290 100644 --- a/Source/cmNinjaTargetGenerator.cxx +++ b/Source/cmNinjaTargetGenerator.cxx @@ -538,8 +538,11 @@ cmNinjaTargetGenerator cmCustomCommandGenerator ccg(*cc, this->GetConfigName(), this->GetMakefile()); const std::vector& ccoutputs = ccg.GetOutputs(); + const std::vector& ccbyproducts= ccg.GetByproducts(); std::transform(ccoutputs.begin(), ccoutputs.end(), std::back_inserter(orderOnlyDeps), MapToNinjaPath()); + std::transform(ccbyproducts.begin(), ccbyproducts.end(), + std::back_inserter(orderOnlyDeps), MapToNinjaPath()); } if (!orderOnlyDeps.empty()) diff --git a/Source/cmNinjaUtilityTargetGenerator.cxx b/Source/cmNinjaUtilityTargetGenerator.cxx index c0a14eccb..42d6b46fd 100644 --- a/Source/cmNinjaUtilityTargetGenerator.cxx +++ b/Source/cmNinjaUtilityTargetGenerator.cxx @@ -27,8 +27,11 @@ cmNinjaUtilityTargetGenerator::~cmNinjaUtilityTargetGenerator() {} void cmNinjaUtilityTargetGenerator::Generate() { + std::string utilCommandName = cmake::GetCMakeFilesDirectoryPostSlash(); + utilCommandName += this->GetTargetName() + ".util"; + std::vector commands; - cmNinjaDeps deps, outputs; + cmNinjaDeps deps, outputs, util_outputs(1, utilCommandName); const std::vector *cmdLists[2] = { &this->GetTarget()->GetPreBuildCommands(), @@ -44,6 +47,9 @@ void cmNinjaUtilityTargetGenerator::Generate() this->GetMakefile()); this->GetLocalGenerator()->AppendCustomCommandDeps(ccg, deps); this->GetLocalGenerator()->AppendCustomCommandLines(ccg, commands); + std::vector const& ccByproducts = ccg.GetByproducts(); + std::transform(ccByproducts.begin(), ccByproducts.end(), + std::back_inserter(util_outputs), MapToNinjaPath()); if (ci->GetUsesTerminal()) uses_terminal = true; } @@ -64,8 +70,11 @@ void cmNinjaUtilityTargetGenerator::Generate() // Depend on all custom command outputs. const std::vector& ccOutputs = ccg.GetOutputs(); + const std::vector& ccByproducts = ccg.GetByproducts(); std::transform(ccOutputs.begin(), ccOutputs.end(), std::back_inserter(deps), MapToNinjaPath()); + std::transform(ccByproducts.begin(), ccByproducts.end(), + std::back_inserter(deps), MapToNinjaPath()); } } @@ -107,15 +116,19 @@ void cmNinjaUtilityTargetGenerator::Generate() if (command.find('$') != std::string::npos) return; - std::string utilCommandName = cmake::GetCMakeFilesDirectoryPostSlash(); - utilCommandName += this->GetTargetName() + ".util"; + for (cmNinjaDeps::const_iterator + oi = util_outputs.begin(), oe = util_outputs.end(); + oi != oe; ++oi) + { + this->GetGlobalGenerator()->SeenCustomCommandOutput(*oi); + } this->GetGlobalGenerator()->WriteCustomCommandBuild( command, desc, "Utility command for " + this->GetTargetName(), uses_terminal, - cmNinjaDeps(1, utilCommandName), + util_outputs, deps); this->GetGlobalGenerator()->WritePhonyBuild(this->GetBuildFileStream(), diff --git a/Source/cmQtAutoGenerators.cxx b/Source/cmQtAutoGenerators.cxx index 929cbc0be..8f9c091eb 100644 --- a/Source/cmQtAutoGenerators.cxx +++ b/Source/cmQtAutoGenerators.cxx @@ -438,7 +438,8 @@ bool cmQtAutoGenerators::InitializeAutogenTarget(cmTarget* target) // rejection in cmMakefile::AddCustomCommandToTarget because we know // PRE_BUILD will work for an OBJECT_LIBRARY in this specific case. std::vector no_output; - cmCustomCommand cc(makefile, no_output, depends, + std::vector no_byproducts; + cmCustomCommand cc(makefile, no_output, no_byproducts, depends, commandLines, autogenComment.c_str(), workingDirectory.c_str()); cc.SetEscapeOldStyle(false); @@ -451,7 +452,9 @@ bool cmQtAutoGenerators::InitializeAutogenTarget(cmTarget* target) cmTarget* autogenTarget = 0; if (!rcc_output.empty()) { - makefile->AddCustomCommandToOutput(rcc_output, depends, "", + std::vector no_byproducts; + makefile->AddCustomCommandToOutput(rcc_output, no_byproducts, + depends, "", commandLines, 0, workingDirectory.c_str(), false, false); diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 3e29683a0..ab28ecaf9 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -716,6 +716,8 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release ) list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/CustomCommand") + ADD_TEST_MACRO(CustomCommandByproducts CustomCommandByproducts) + ADD_TEST_MACRO(EmptyDepends ${CMAKE_CTEST_COMMAND}) add_test(CustomCommandWorkingDirectory ${CMAKE_CTEST_COMMAND} diff --git a/Tests/CustomCommand/CMakeLists.txt b/Tests/CustomCommand/CMakeLists.txt index e2600705d..915da0a7e 100644 --- a/Tests/CustomCommand/CMakeLists.txt +++ b/Tests/CustomCommand/CMakeLists.txt @@ -117,6 +117,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E echo " Copying doc1pre.txt to doc2post.txt." COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/doc1pre.txt ${PROJECT_BINARY_DIR}/doc2post.txt + BYPRODUCTS ${PROJECT_BINARY_DIR}/doc2post.txt COMMENT "Running TDocument post-build commands" ) diff --git a/Tests/CustomCommandByproducts/CMakeLists.txt b/Tests/CustomCommandByproducts/CMakeLists.txt new file mode 100644 index 000000000..c39a5366a --- /dev/null +++ b/Tests/CustomCommandByproducts/CMakeLists.txt @@ -0,0 +1,103 @@ +cmake_minimum_required(VERSION 3.1) +project(CustomCommandByproducts C) + +# Generate a byproduct in a rule that runs in the target consuming it. +add_custom_command( + OUTPUT timestamp1.txt + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/byproduct1.c.in byproduct1.c + BYPRODUCTS byproduct1.c + COMMAND ${CMAKE_COMMAND} -E touch timestamp1.txt + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/byproduct1.c.in + ) + +# Generate a byproduct in a rule that runs in a dependency of the consumer. +add_custom_command( + OUTPUT timestamp2.txt + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/byproduct2.c.in byproduct2.c + BYPRODUCTS byproduct2.c + COMMAND ${CMAKE_COMMAND} -E touch timestamp2.txt + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/byproduct2.c.in + ) +add_custom_target(Producer2 DEPENDS timestamp2.txt) + +# Generate a byproduct in a custom target. +add_custom_target(Producer3_4 + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/byproduct3.c.in byproduct3.c + BYPRODUCTS byproduct3.c + ) + +# Generate a byproduct in a custom target POST_BUILD command. +add_custom_command( + TARGET Producer3_4 POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/byproduct4.c.in byproduct4.c + BYPRODUCTS byproduct4.c + ) + +add_executable(ProducerExe ProducerExe.c) + +# Generate a byproduct in an executable POST_BUILD command. +add_custom_command( + TARGET ProducerExe POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/byproduct5.c.in byproduct5.c + BYPRODUCTS byproduct5.c + ) + +# Generate a byproduct in an executable PRE_LINK command. +add_custom_command( + TARGET ProducerExe PRE_LINK + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/byproduct6.c.in byproduct6.c + BYPRODUCTS byproduct6.c + ) + +# Generate a byproduct in an executable PRE_BUILD command. +add_custom_command( + TARGET ProducerExe PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/byproduct7.c.in byproduct7.c + BYPRODUCTS byproduct7.c + ) + +# Generate a byproduct in a custom command that consumes other byproducts. +add_custom_command(OUTPUT timestamp8.txt + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/byproduct8.c.in byproduct8.c + COMMAND ${CMAKE_COMMAND} -E touch timestamp8.txt + BYPRODUCTS byproduct8.c + DEPENDS + ${CMAKE_CURRENT_BINARY_DIR}/byproduct2.c + ${CMAKE_CURRENT_BINARY_DIR}/byproduct3.c + ${CMAKE_CURRENT_BINARY_DIR}/byproduct4.c + ${CMAKE_CURRENT_BINARY_DIR}/byproduct5.c + ${CMAKE_CURRENT_BINARY_DIR}/byproduct6.c + ${CMAKE_CURRENT_BINARY_DIR}/byproduct7.c + ${CMAKE_CURRENT_SOURCE_DIR}/byproduct8.c.in + ) + +# Add an executable consuming all the byproducts. +add_executable(CustomCommandByproducts + CustomCommandByproducts.c + byproduct1.c timestamp1.txt + byproduct2.c + byproduct3.c + byproduct4.c + byproduct5.c + byproduct6.c + byproduct7.c + byproduct8.c timestamp8.txt + ) +add_dependencies(CustomCommandByproducts Producer2) +add_dependencies(CustomCommandByproducts Producer3_4) +add_dependencies(CustomCommandByproducts ProducerExe) + +if(CMAKE_GENERATOR STREQUAL "Ninja") + add_custom_target(CheckNinja ALL + COMMENT "Checking build.ninja" + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/ninja-check.cmake + ) +endif() diff --git a/Tests/CustomCommandByproducts/CustomCommandByproducts.c b/Tests/CustomCommandByproducts/CustomCommandByproducts.c new file mode 100644 index 000000000..d9db9e6f7 --- /dev/null +++ b/Tests/CustomCommandByproducts/CustomCommandByproducts.c @@ -0,0 +1,21 @@ +extern int byproduct1(void); +extern int byproduct2(void); +extern int byproduct3(void); +extern int byproduct4(void); +extern int byproduct5(void); +extern int byproduct6(void); +extern int byproduct7(void); +extern int byproduct8(void); +int main(void) +{ + return ( + byproduct1() + + byproduct2() + + byproduct3() + + byproduct4() + + byproduct5() + + byproduct6() + + byproduct7() + + byproduct8() + + 0); +} diff --git a/Tests/CustomCommandByproducts/ProducerExe.c b/Tests/CustomCommandByproducts/ProducerExe.c new file mode 100644 index 000000000..78f2de106 --- /dev/null +++ b/Tests/CustomCommandByproducts/ProducerExe.c @@ -0,0 +1 @@ +int main(void) { return 0; } diff --git a/Tests/CustomCommandByproducts/byproduct1.c.in b/Tests/CustomCommandByproducts/byproduct1.c.in new file mode 100644 index 000000000..5c3cc2471 --- /dev/null +++ b/Tests/CustomCommandByproducts/byproduct1.c.in @@ -0,0 +1 @@ +int byproduct1(void) { return 0; } diff --git a/Tests/CustomCommandByproducts/byproduct2.c.in b/Tests/CustomCommandByproducts/byproduct2.c.in new file mode 100644 index 000000000..eeb69efef --- /dev/null +++ b/Tests/CustomCommandByproducts/byproduct2.c.in @@ -0,0 +1 @@ +int byproduct2(void) { return 0; } diff --git a/Tests/CustomCommandByproducts/byproduct3.c.in b/Tests/CustomCommandByproducts/byproduct3.c.in new file mode 100644 index 000000000..7d1531079 --- /dev/null +++ b/Tests/CustomCommandByproducts/byproduct3.c.in @@ -0,0 +1 @@ +int byproduct3(void) { return 0; } diff --git a/Tests/CustomCommandByproducts/byproduct4.c.in b/Tests/CustomCommandByproducts/byproduct4.c.in new file mode 100644 index 000000000..8b243ddfd --- /dev/null +++ b/Tests/CustomCommandByproducts/byproduct4.c.in @@ -0,0 +1 @@ +int byproduct4(void) { return 0; } diff --git a/Tests/CustomCommandByproducts/byproduct5.c.in b/Tests/CustomCommandByproducts/byproduct5.c.in new file mode 100644 index 000000000..47f599039 --- /dev/null +++ b/Tests/CustomCommandByproducts/byproduct5.c.in @@ -0,0 +1 @@ +int byproduct5(void) { return 0; } diff --git a/Tests/CustomCommandByproducts/byproduct6.c.in b/Tests/CustomCommandByproducts/byproduct6.c.in new file mode 100644 index 000000000..d70c14f90 --- /dev/null +++ b/Tests/CustomCommandByproducts/byproduct6.c.in @@ -0,0 +1 @@ +int byproduct6(void) { return 0; } diff --git a/Tests/CustomCommandByproducts/byproduct7.c.in b/Tests/CustomCommandByproducts/byproduct7.c.in new file mode 100644 index 000000000..0be5006c1 --- /dev/null +++ b/Tests/CustomCommandByproducts/byproduct7.c.in @@ -0,0 +1 @@ +int byproduct7(void) { return 0; } diff --git a/Tests/CustomCommandByproducts/byproduct8.c.in b/Tests/CustomCommandByproducts/byproduct8.c.in new file mode 100644 index 000000000..abefd62c6 --- /dev/null +++ b/Tests/CustomCommandByproducts/byproduct8.c.in @@ -0,0 +1 @@ +int byproduct8(void) { return 0; } diff --git a/Tests/CustomCommandByproducts/ninja-check.cmake b/Tests/CustomCommandByproducts/ninja-check.cmake new file mode 100644 index 000000000..2fc3cc217 --- /dev/null +++ b/Tests/CustomCommandByproducts/ninja-check.cmake @@ -0,0 +1,20 @@ +file(READ build.ninja build_ninja) +if("${build_ninja}" MATCHES [====[ +# Unknown Build Time Dependencies. +# Tell Ninja that they may appear as side effects of build rules +# otherwise ordered by order-only dependencies. + +((build [^:]*: phony [^\n]* +)*)# ========]====]) + set(phony "${CMAKE_MATCH_1}") + if(NOT phony) + message(STATUS "build.ninja correctly does not have extra phony rules") + else() + string(REGEX REPLACE "\n+$" "" phony "${phony}") + string(REGEX REPLACE "\n" "\n " phony " ${phony}") + message(FATAL_ERROR "build.ninja incorrectly has extra phony rules:\n" + "${phony}") + endif() +else() + message(FATAL_ERROR "build.ninja is incorrectly missing expected block") +endif() diff --git a/Tests/RunCMake/add_custom_command/RunCMakeTest.cmake b/Tests/RunCMake/add_custom_command/RunCMakeTest.cmake index d0f429af8..2f5c938af 100644 --- a/Tests/RunCMake/add_custom_command/RunCMakeTest.cmake +++ b/Tests/RunCMake/add_custom_command/RunCMakeTest.cmake @@ -6,4 +6,5 @@ run_cmake(BadArgument) run_cmake(NoArguments) run_cmake(NoOutputOrTarget) run_cmake(OutputAndTarget) +run_cmake(SourceByproducts) run_cmake(SourceUsesTerminal) diff --git a/Tests/RunCMake/add_custom_command/SourceByproducts-result.txt b/Tests/RunCMake/add_custom_command/SourceByproducts-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/add_custom_command/SourceByproducts-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/add_custom_command/SourceByproducts-stderr.txt b/Tests/RunCMake/add_custom_command/SourceByproducts-stderr.txt new file mode 100644 index 000000000..a9cd64c3f --- /dev/null +++ b/Tests/RunCMake/add_custom_command/SourceByproducts-stderr.txt @@ -0,0 +1,4 @@ +CMake Error at SourceByproducts.cmake:1 \(add_custom_command\): + add_custom_command BYPRODUCTS may not be specified with SOURCE signatures +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/add_custom_command/SourceByproducts.cmake b/Tests/RunCMake/add_custom_command/SourceByproducts.cmake new file mode 100644 index 000000000..824f41da7 --- /dev/null +++ b/Tests/RunCMake/add_custom_command/SourceByproducts.cmake @@ -0,0 +1 @@ +add_custom_command(SOURCE t TARGET t BYPRODUCTS b) diff --git a/Tests/RunCMake/add_custom_target/ByproductsNoCommand-result.txt b/Tests/RunCMake/add_custom_target/ByproductsNoCommand-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/add_custom_target/ByproductsNoCommand-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/add_custom_target/ByproductsNoCommand-stderr.txt b/Tests/RunCMake/add_custom_target/ByproductsNoCommand-stderr.txt new file mode 100644 index 000000000..6c80ca664 --- /dev/null +++ b/Tests/RunCMake/add_custom_target/ByproductsNoCommand-stderr.txt @@ -0,0 +1,4 @@ +CMake Error at ByproductsNoCommand.cmake:1 \(add_custom_target\): + BYPRODUCTS may not be specified without any COMMAND +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/add_custom_target/ByproductsNoCommand.cmake b/Tests/RunCMake/add_custom_target/ByproductsNoCommand.cmake new file mode 100644 index 000000000..6c142a270 --- /dev/null +++ b/Tests/RunCMake/add_custom_target/ByproductsNoCommand.cmake @@ -0,0 +1 @@ +add_custom_target(MyTarget BYPRODUCTS a b c d) diff --git a/Tests/RunCMake/add_custom_target/RunCMakeTest.cmake b/Tests/RunCMake/add_custom_target/RunCMakeTest.cmake index a612da98f..92c4a38fe 100644 --- a/Tests/RunCMake/add_custom_target/RunCMakeTest.cmake +++ b/Tests/RunCMake/add_custom_target/RunCMakeTest.cmake @@ -2,4 +2,5 @@ include(RunCMake) run_cmake(NoArguments) run_cmake(BadTargetName) +run_cmake(ByproductsNoCommand) run_cmake(UsesTerminalNoCommand)