diff --git a/Source/cmAddCustomCommandCommand.cxx b/Source/cmAddCustomCommandCommand.cxx index a9f948ca9..4f6df9e84 100644 --- a/Source/cmAddCustomCommandCommand.cxx +++ b/Source/cmAddCustomCommandCommand.cxx @@ -32,9 +32,9 @@ bool cmAddCustomCommandCommand::InitialPass( return false; } - std::string source, target, comment, output, main_dependency, + std::string source, target, comment, main_dependency, working; - std::vector depends, outputs; + std::vector depends, outputs, output; // Accumulate one command line at a time. cmCustomCommandLine currentLine; @@ -155,7 +155,7 @@ bool cmAddCustomCommandCommand::InitialPass( source = copy; break; case doing_output: - output = filename; + output.push_back(filename); break; case doing_main_dependency: main_dependency = copy; @@ -204,34 +204,9 @@ bool cmAddCustomCommandCommand::InitialPass( return false; } - if ( !this->Makefile->CanIWriteThisFile(output.c_str()) ) + // Make sure the output names and locations are safe. + if(!this->CheckOutputs(output) || !this->CheckOutputs(outputs)) { - std::string e = "attempted to have a file: " + output + - " in a source directory as an output of custom command."; - this->SetError(e.c_str()); - cmSystemTools::SetFatalErrorOccured(); - return false; - } - std::vector::iterator oit; - for ( oit = outputs.begin(); oit != outputs.end(); ++ oit ) - { - if ( !this->Makefile->CanIWriteThisFile(oit->c_str()) ) - { - std::string e = "attempted to have a file: " + *oit + - " in a source directory as an output of custom command."; - this->SetError(e.c_str()); - cmSystemTools::SetFatalErrorOccured(); - return false; - } - } - - std::string::size_type pos = output.find_first_of("#<>"); - if(pos != output.npos) - { - cmOStringStream msg; - msg << "called with OUTPUT containing a \"" << output[pos] - << "\". This character is not allowed."; - this->SetError(msg.str().c_str()); return false; } @@ -247,7 +222,7 @@ bool cmAddCustomCommandCommand::InitialPass( else if(target.empty()) { // Target is empty, use the output. - this->Makefile->AddCustomCommandToOutput(output.c_str(), depends, + this->Makefile->AddCustomCommandToOutput(output, depends, main_dependency.c_str(), commandLines, comment.c_str(), working.c_str()); @@ -261,3 +236,36 @@ bool cmAddCustomCommandCommand::InitialPass( } return true; } + +//---------------------------------------------------------------------------- +bool +cmAddCustomCommandCommand +::CheckOutputs(const std::vector& outputs) +{ + for(std::vector::const_iterator o = outputs.begin(); + o != outputs.end(); ++o) + { + // Make sure the file will not be generated into the source + // directory during an out of source build. + if(!this->Makefile->CanIWriteThisFile(o->c_str())) + { + std::string e = "attempted to have a file \"" + *o + + "\" in a source directory as an output of custom command."; + this->SetError(e.c_str()); + cmSystemTools::SetFatalErrorOccured(); + return false; + } + + // Make sure the output file name has no invalid characters. + std::string::size_type pos = o->find_first_of("#<>"); + if(pos != o->npos) + { + cmOStringStream msg; + msg << "called with OUTPUT containing a \"" << (*o)[pos] + << "\". This character is not allowed."; + this->SetError(msg.str().c_str()); + return false; + } + } + return true; +} diff --git a/Source/cmAddCustomCommandCommand.h b/Source/cmAddCustomCommandCommand.h index 96f85f651..7a40c477e 100644 --- a/Source/cmAddCustomCommandCommand.h +++ b/Source/cmAddCustomCommandCommand.h @@ -66,7 +66,7 @@ public: "There are two main signatures for ADD_CUSTOM_COMMAND " "The first signature is for adding a custom command " "to produce an output.\n" - " ADD_CUSTOM_COMMAND(OUTPUT result\n" + " ADD_CUSTOM_COMMAND(OUTPUT output1 [output2 ...]\n" " COMMAND command1 [ARGS] [args1...]\n" " [COMMAND command2 [ARGS] [args2...] ...]\n" " [MAIN_DEPENDENCY depend]\n" @@ -106,6 +106,8 @@ public: } cmTypeMacro(cmAddCustomCommandCommand, cmCommand); +protected: + bool CheckOutputs(const std::vector& outputs); }; diff --git a/Source/cmCustomCommand.cxx b/Source/cmCustomCommand.cxx index b66568cda..162f10f30 100644 --- a/Source/cmCustomCommand.cxx +++ b/Source/cmCustomCommand.cxx @@ -24,7 +24,7 @@ cmCustomCommand::cmCustomCommand() //---------------------------------------------------------------------------- cmCustomCommand::cmCustomCommand(const cmCustomCommand& r): - Output(r.Output), + Outputs(r.Outputs), Depends(r.Depends), CommandLines(r.CommandLines), Comment(r.Comment), @@ -34,12 +34,12 @@ cmCustomCommand::cmCustomCommand(const cmCustomCommand& r): } //---------------------------------------------------------------------------- -cmCustomCommand::cmCustomCommand(const char* output, +cmCustomCommand::cmCustomCommand(const std::vector& outputs, const std::vector& depends, const cmCustomCommandLines& commandLines, const char* comment, const char* workingDirectory): - Output(output?output:""), + Outputs(outputs), Depends(depends), CommandLines(commandLines), Comment(comment?comment:""), @@ -49,9 +49,9 @@ cmCustomCommand::cmCustomCommand(const char* output, } //---------------------------------------------------------------------------- -const char* cmCustomCommand::GetOutput() const +const std::vector& cmCustomCommand::GetOutputs() const { - return this->Output.c_str(); + return this->Outputs; } //---------------------------------------------------------------------------- diff --git a/Source/cmCustomCommand.h b/Source/cmCustomCommand.h index a33f1234d..4fc300cee 100644 --- a/Source/cmCustomCommand.h +++ b/Source/cmCustomCommand.h @@ -32,14 +32,14 @@ public: cmCustomCommand(const cmCustomCommand& r); /** Main constructor specifies all information for the command. */ - cmCustomCommand(const char* output, + cmCustomCommand(const std::vector& outputs, const std::vector& depends, const cmCustomCommandLines& commandLines, const char* comment, const char* workingDirectory); /** Get the output file produced by the command. */ - const char* GetOutput() const; + const std::vector& GetOutputs() const; /** Get the working directory. */ const char* GetWorkingDirectory() const; @@ -58,7 +58,7 @@ public: bool IsUsed() { return this->Used;}; private: - std::string Output; + std::vector Outputs; std::vector Depends; cmCustomCommandLines CommandLines; std::string Comment; diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 9e9bbe422..57f2eb0ff 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -1428,9 +1428,10 @@ cmTarget cmGlobalGenerator::CreateGlobalTarget( target.SetType(cmTarget::GLOBAL_TARGET, name); target.SetInAll(false); - std::vector fileDepends; + std::vector no_outputs; + std::vector no_depends; // Store the custom command in the target. - cmCustomCommand cc(0, fileDepends, *commandLines, 0, 0); + cmCustomCommand cc(no_outputs, no_depends, *commandLines, 0, 0); target.GetPostBuildCommands().push_back(cc); target.SetProperty("EchoString", message); if ( depends_on_all ) @@ -1453,3 +1454,10 @@ void cmGlobalGenerator::AppendDirectoryForConfig(const char*, const char*, // this method to append the subdirectory for the given build // configuration. } + +//---------------------------------------------------------------------------- +void cmGlobalGenerator::CheckMultipleOutputs(cmMakefile*, bool) +{ + // Only certain generators need this check. They define this + // method. +} diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index bd21de0c6..d8e35ae38 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -175,6 +175,9 @@ public: configuration. This is valid during generation only. */ cmTargetManifest const& GetTargetManifest() { return this->TargetManifest; } + /** Support for multiple custom command outputs. */ + virtual void CheckMultipleOutputs(cmMakefile* mf, bool verbose); + virtual const char* GetAllTargetName() { return "ALL_BUILD"; } virtual const char* GetInstallTargetName() { return "INSTALL"; } virtual const char* GetPreinstallTargetName() { return 0; } diff --git a/Source/cmGlobalUnixMakefileGenerator3.cxx b/Source/cmGlobalUnixMakefileGenerator3.cxx index 048ff5090..4b17ded8e 100644 --- a/Source/cmGlobalUnixMakefileGenerator3.cxx +++ b/Source/cmGlobalUnixMakefileGenerator3.cxx @@ -98,6 +98,15 @@ void cmGlobalUnixMakefileGenerator3::GetDocumentation(cmDocumentationEntry& entr "default make target. A \"make install\" target is also provided."; } +//---------------------------------------------------------------------------- +void +cmGlobalUnixMakefileGenerator3 +::AddMultipleOutputPair(const char* depender, const char* dependee) +{ + MultipleOutputPairsType::value_type p(depender, dependee); + this->MultipleOutputPairs.insert(p); +} + //---------------------------------------------------------------------------- void cmGlobalUnixMakefileGenerator3::Generate() { @@ -298,8 +307,64 @@ void cmGlobalUnixMakefileGenerator3::WriteMainCMakefile() cmakefileStream << " )\n\n"; this->WriteMainCMakefileLanguageRules(cmakefileStream, this->LocalGenerators); + + if(!this->MultipleOutputPairs.empty()) + { + cmakefileStream + << "\n" + << "SET(CMAKE_MULTIPLE_OUTPUT_PAIRS\n"; + for(MultipleOutputPairsType::const_iterator pi = + this->MultipleOutputPairs.begin(); + pi != this->MultipleOutputPairs.end(); ++pi) + { + cmakefileStream << " \"" << pi->first << "\" \"" + << pi->second << "\"\n"; + } + cmakefileStream << " )\n\n"; + } } - + +//---------------------------------------------------------------------------- +void cmGlobalUnixMakefileGenerator3::CheckMultipleOutputs(cmMakefile* mf, + bool verbose) +{ + // Get the string listing the multiple output pairs. + const char* pairs_string = mf->GetDefinition("CMAKE_MULTIPLE_OUTPUT_PAIRS"); + if(!pairs_string) + { + return; + } + + // Convert the string to a list and preserve empty entries. + std::vector pairs; + cmSystemTools::ExpandListArgument(pairs_string, pairs, true); + for(std::vector::const_iterator i = pairs.begin(); + i != pairs.end(); ++i) + { + const std::string& depender = *i; + if(++i != pairs.end()) + { + const std::string& dependee = *i; + + // If the depender is missing then delete the dependee to make + // sure both will be regenerated. + if(cmSystemTools::FileExists(dependee.c_str()) && + !cmSystemTools::FileExists(depender.c_str())) + { + if(verbose) + { + cmOStringStream msg; + msg << "Deleting primary custom command output \"" << dependee + << "\" because another output \"" + << depender << "\" does not exist." << std::endl; + cmSystemTools::Stdout(msg.str().c_str()); + } + cmSystemTools::RemoveFile(dependee.c_str()); + } + } + } +} + void cmGlobalUnixMakefileGenerator3 ::WriteMainCMakefileLanguageRules(cmGeneratedFileStream& cmakefileStream, std::vector &lGenerators) diff --git a/Source/cmGlobalUnixMakefileGenerator3.h b/Source/cmGlobalUnixMakefileGenerator3.h index 60ca2da2d..3f8dbc23a 100644 --- a/Source/cmGlobalUnixMakefileGenerator3.h +++ b/Source/cmGlobalUnixMakefileGenerator3.h @@ -97,6 +97,19 @@ public: void WriteConvenienceRules(std::ostream& ruleFileStream, std::set &emitted); + /** In order to support parallel builds for custom commands with + multiple outputs the outputs are given a serial order, and only + the first output actually has the build rule. Other outputs + just depend on the first one. The check-build-system step must + remove a dependee if the depender is missing to make sure both + are regenerated properly. This method is used by the local + makefile generators to register such pairs. */ + void AddMultipleOutputPair(const char* depender, const char* dependee); + + /** Support for multiple custom command outputs. Called during + check-build-system step. */ + virtual void CheckMultipleOutputs(cmMakefile* mf, bool verbose); + protected: void WriteMainMakefile2(); void WriteMainCMakefile(); @@ -137,6 +150,9 @@ protected: // added later. If non-empty this variable holds a fake dependency // that can be added. std::string EmptyRuleHackDepends; + + typedef std::map MultipleOutputPairsType; + MultipleOutputPairsType MultipleOutputPairs; }; #endif diff --git a/Source/cmGlobalVisualStudio8Generator.cxx b/Source/cmGlobalVisualStudio8Generator.cxx index 969999d89..051f7180f 100644 --- a/Source/cmGlobalVisualStudio8Generator.cxx +++ b/Source/cmGlobalVisualStudio8Generator.cxx @@ -135,12 +135,12 @@ void cmGlobalVisualStudio8Generator::Generate() // file as the main dependency because it would get // overwritten by the AddVCProjBuildRule of the ALL_BUILD // target. - const char* no_comment = 0; const char* no_main_dependency = 0; const char* no_working_directory = 0; mf->AddCustomCommandToOutput( CMAKE_CHECK_BUILD_SYSTEM_TARGET ".vcproj.cmake", listFiles, - no_main_dependency, commandLines, no_comment, no_working_directory, true); + no_main_dependency, commandLines, "Checking Build System", + no_working_directory, true); if(cmSourceFile* file = mf->GetSource(CMAKE_CHECK_BUILD_SYSTEM_TARGET ".vcproj.cmake.rule")) { tgt->GetSourceFiles().push_back(file); diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index ffef3210f..d86bf2d49 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -887,6 +887,8 @@ cmGlobalXCodeGenerator::AddCommandsToBuildPhase(cmXCodeObject* buildphase, this->CreateString(makecmd.c_str())); return; } + + std::map multipleOutputPairs; std::string dir = this->CurrentMakefile->GetCurrentOutputDirectory(); dir += "/CMakeScripts"; @@ -916,10 +918,15 @@ cmGlobalXCodeGenerator::AddCommandsToBuildPhase(cmXCodeObject* buildphase, cmCustomCommand const& cc = *i; if(!cc.GetCommandLines().empty()) { - if(cc.GetOutput()[0]) + const std::vector& outputs = cc.GetOutputs(); + if(!outputs.empty()) { - makefileStream << "\\\n\t" << this-> - ConvertToRelativeForMake(cc.GetOutput()); + for(std::vector::const_iterator o = outputs.begin(); + o != outputs.end(); ++o) + { + makefileStream + << "\\\n\t" << this->ConvertToRelativeForMake(o->c_str()); + } } else { @@ -940,13 +947,29 @@ cmGlobalXCodeGenerator::AddCommandsToBuildPhase(cmXCodeObject* buildphase, makefileStream << "\n#" << "Custom command rule: " << cc.GetComment() << "\n"; - if(cc.GetOutput()[0]) + const std::vector& outputs = cc.GetOutputs(); + if(!outputs.empty()) { - makefileStream << this - ->ConvertToRelativeForMake(cc.GetOutput()) << ": "; + // There is at least one output. If there is more than one treat the + // first as the primary output and make the rest depend on it. + std::vector::const_iterator o = outputs.begin(); + std::string primary_output = + this->ConvertToRelativeForMake(o->c_str()); + for(++o; o != outputs.end(); ++o) + { + std::string current_output = + this->ConvertToRelativeForMake(o->c_str()); + makefileStream << current_output << ": " + << primary_output << "\n"; + multipleOutputPairs[current_output] = primary_output; + } + + // Start the rule for the primary output. + makefileStream << primary_output << ": "; } else { + // There are no outputs. Use the generated force rule name. makefileStream << tname[&cc] << ": "; } for(std::vector::const_iterator d = cc.GetDepends().begin(); @@ -1001,12 +1024,33 @@ cmGlobalXCodeGenerator::AddCommandsToBuildPhase(cmXCodeObject* buildphase, } } } + + // Add a rule to deal with multiple outputs of custom commands. + if(!multipleOutputPairs.empty()) + { + makefileStream << + "\n" + "cmake_check_multiple_outputs:\n"; + for(std::map::const_iterator o = + multipleOutputPairs.begin(); o != multipleOutputPairs.end(); ++o) + { + makefileStream << "\t@if [ ! -f " + << o->first << " ]; then rm -f " + << o->second << "; fi\n"; + } + } + std::string cdir = this->CurrentMakefile->GetCurrentOutputDirectory(); cdir = this->ConvertToRelativeForXCode(cdir.c_str()); std::string makecmd = "make -C "; makecmd += cdir; makecmd += " -f "; makecmd += this->ConvertToRelativeForMake(makefile.c_str()); + if(!multipleOutputPairs.empty()) + { + makecmd += " cmake_check_multiple_outputs"; + } + makecmd += " all"; cmSystemTools::ReplaceString(makecmd, "\\ ", "\\\\ "); buildphase->AddAttribute("shellScript", this->CreateString(makecmd.c_str())); } diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index 14205b42e..bd4b9b005 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -1668,6 +1668,37 @@ cmLocalGenerator::ConstructScript(const cmCustomCommandLines& commandLines, return script; } +//---------------------------------------------------------------------------- +std::string +cmLocalGenerator::ConstructComment(const cmCustomCommand& cc, + const char* default_comment) +{ + // Check for a comment provided with the command. + if(cc.GetComment() && *cc.GetComment()) + { + return cc.GetComment(); + } + + // Construct a reasonable default comment if possible. + if(!cc.GetOutputs().empty()) + { + std::string comment; + comment = "Generating "; + const char* sep = ""; + for(std::vector::const_iterator o = cc.GetOutputs().begin(); + o != cc.GetOutputs().end(); ++o) + { + comment += sep; + comment += this->Convert(o->c_str(), cmLocalGenerator::START_OUTPUT); + sep = ", "; + } + return comment; + } + + // Otherwise use the provided default. + return default_comment; +} + //---------------------------------------------------------------------------- std::string cmLocalGenerator::ConvertToOptionallyRelativeOutputPath(const char* remote) diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h index a7e07403d..41c2c4459 100644 --- a/Source/cmLocalGenerator.h +++ b/Source/cmLocalGenerator.h @@ -24,7 +24,7 @@ class cmGlobalGenerator; class cmTarget; class cmTargetManifest; class cmSourceFile; - +class cmCustomCommand; /** \class cmLocalGenerator * \brief Create required build files for a directory. @@ -211,6 +211,10 @@ protected: const char* workingDirectory, const char* newline = "\n"); + /** Construct a comment for a custom command. */ + std::string ConstructComment(const cmCustomCommand& cc, + const char* default_comment = ""); + /** Fill out these strings for the given target. Libraries to link, * flags, and linkflags. */ void GetTargetFlags(std::string& linkLibs, diff --git a/Source/cmLocalVisualStudio6Generator.cxx b/Source/cmLocalVisualStudio6Generator.cxx index ec5d7ea3d..b6fd4f8ea 100644 --- a/Source/cmLocalVisualStudio6Generator.cxx +++ b/Source/cmLocalVisualStudio6Generator.cxx @@ -215,6 +215,8 @@ void cmLocalVisualStudio6Generator::AddDSPBuildRule(cmTarget& tgt) std::string makefileIn = this->Makefile->GetStartDirectory(); makefileIn += "/"; makefileIn += "CMakeLists.txt"; + std::string comment = "Building Custom Rule "; + comment += makefileIn; std::string args; args = "-H"; args += @@ -246,10 +248,11 @@ void cmLocalVisualStudio6Generator::AddDSPBuildRule(cmTarget& tgt) cmCustomCommandLines commandLines; commandLines.push_back(commandLine); - const char* no_comment = 0; const char* no_working_directory = 0; - this->Makefile->AddCustomCommandToOutput(dspname.c_str(), listFiles, makefileIn.c_str(), - commandLines, no_comment, no_working_directory, true); + this->Makefile->AddCustomCommandToOutput(dspname.c_str(), listFiles, + makefileIn.c_str(), commandLines, + comment.c_str(), + no_working_directory, true); if(cmSourceFile* file = this->Makefile->GetSource(makefileIn.c_str())) { tgt.GetSourceFiles().push_back(file); @@ -443,12 +446,17 @@ void cmLocalVisualStudio6Generator::WriteGroup(const cmSourceGroup *sg, cmTarget this->ConstructScript(command->GetCommandLines(), command->GetWorkingDirectory(), "\\\n\t"); - const char* comment = command->GetComment(); + std::string comment = + this->ConstructComment(*command, + "Building Custom Rule $(InputPath)"); + if(comment == "") + { + comment = ""; + } const char* flags = compileFlags.size() ? compileFlags.c_str(): 0; - this->WriteCustomRule(fout, source.c_str(), script.c_str(), - (*comment?comment:"Custom Rule"), - command->GetDepends(), - command->GetOutput(), flags); + this->WriteCustomRule(fout, source.c_str(), script.c_str(), + comment.c_str(), command->GetDepends(), + command->GetOutputs(), flags); } else if(compileFlags.size()) { @@ -501,6 +509,7 @@ cmLocalVisualStudio6Generator strlen(target.GetName()) + 30)]; sprintf(output,"%s/%s_force_%i", this->Makefile->GetStartOutputDirectory(), target.GetName(), count); + std::string comment = this->ConstructComment(origCommand, ""); // Add the rule with the given dependencies and commands. const char* no_main_dependency = 0; @@ -508,7 +517,7 @@ cmLocalVisualStudio6Generator depends, no_main_dependency, origCommand.GetCommandLines(), - origCommand.GetComment(), + comment.c_str(), origCommand.GetWorkingDirectory()); // Replace the dependencies with the output of this rule so that the @@ -524,15 +533,17 @@ cmLocalVisualStudio6Generator delete [] output; } -void cmLocalVisualStudio6Generator::WriteCustomRule(std::ostream& fout, - const char* source, - const char* command, - const char* comment, - const std::vector& depends, - const char *output, - const char* flags - ) +void +cmLocalVisualStudio6Generator +::WriteCustomRule(std::ostream& fout, + const char* source, + const char* command, + const char* comment, + const std::vector& depends, + const std::vector& outputs, + const char* flags) { + // Write the rule for each configuration. std::vector::iterator i; for(i = this->Configurations.begin(); i != this->Configurations.end(); ++i) { @@ -561,18 +572,28 @@ void cmLocalVisualStudio6Generator::WriteCustomRule(std::ostream& fout, fout << "\n"; fout << "# PROP Ignore_Default_Tool 1\n"; - fout << "# Begin Custom Build - Building " << comment - << " $(InputPath)\n\n"; - if(output == 0) + fout << "# Begin Custom Build -"; + if(comment && *comment) + { + fout << " " << comment; + } + fout << "\n\n"; + if(outputs.empty()) { fout << source << "_force : \"$(SOURCE)\" \"$(INTDIR)\" \"$(OUTDIR)\"\n\t"; fout << command << "\n\n"; } - - // Write a rule for every output generated by this command. - fout << this->ConvertToOptionallyRelativeOutputPath(output) - << " : \"$(SOURCE)\" \"$(INTDIR)\" \"$(OUTDIR)\"\n\t"; - fout << command << "\n\n"; + else + { + for(std::vector::const_iterator o = outputs.begin(); + o != outputs.end(); ++o) + { + // Write a rule for every output generated by this command. + fout << this->ConvertToOptionallyRelativeOutputPath(o->c_str()) + << " : \"$(SOURCE)\" \"$(INTDIR)\" \"$(OUTDIR)\"\n\t"; + fout << command << "\n\n"; + } + } fout << "# End Custom Build\n\n"; } diff --git a/Source/cmLocalVisualStudio6Generator.h b/Source/cmLocalVisualStudio6Generator.h index a1450b3e0..cfa49acb4 100644 --- a/Source/cmLocalVisualStudio6Generator.h +++ b/Source/cmLocalVisualStudio6Generator.h @@ -85,7 +85,7 @@ private: const char* command, const char* comment, const std::vector& depends, - const char* output, + const std::vector& outputs, const char* flags); void AddUtilityCommandHack(cmTarget& target, int count, std::vector& depends, diff --git a/Source/cmLocalVisualStudio7Generator.cxx b/Source/cmLocalVisualStudio7Generator.cxx index 55b80efea..3c9942f08 100644 --- a/Source/cmLocalVisualStudio7Generator.cxx +++ b/Source/cmLocalVisualStudio7Generator.cxx @@ -170,6 +170,8 @@ void cmLocalVisualStudio7Generator::AddVCProjBuildRule(cmTarget& tgt) std::string makefileIn = this->Makefile->GetStartDirectory(); makefileIn += "/"; makefileIn += "CMakeLists.txt"; + std::string comment = "Building Custom Rule "; + comment += makefileIn; std::string args; args = "-H"; args += @@ -202,9 +204,10 @@ void cmLocalVisualStudio7Generator::AddVCProjBuildRule(cmTarget& tgt) cmCustomCommandLines commandLines; commandLines.push_back(commandLine); const char* no_working_directory = 0; - const char* no_comment = 0; - this->Makefile->AddCustomCommandToOutput(dspname.c_str(), listFiles, makefileIn.c_str(), - commandLines, no_comment, no_working_directory, true); + this->Makefile->AddCustomCommandToOutput(dspname.c_str(), listFiles, + makefileIn.c_str(), commandLines, + comment.c_str(), + no_working_directory, true); if(cmSourceFile* file = this->Makefile->GetSource(makefileIn.c_str())) { tgt.GetSourceFiles().push_back(file); @@ -1024,12 +1027,11 @@ void cmLocalVisualStudio7Generator::WriteGroup(const cmSourceGroup *sg, cmTarget // Construct the entire set of commands in one string. std::string script = this->ConstructScript(command->GetCommandLines(), command->GetWorkingDirectory()); - const char* comment = command->GetComment(); + std::string comment = this->ConstructComment(*command); const char* flags = compileFlags.size() ? compileFlags.c_str(): 0; this->WriteCustomRule(fout, source.c_str(), script.c_str(), - (*comment?comment:"Custom Rule"), - command->GetDepends(), - command->GetOutput(), flags); + comment.c_str(), command->GetDepends(), + command->GetOutputs(), flags); } else if(compileFlags.size() || additionalDeps.length()) { @@ -1093,9 +1095,10 @@ WriteCustomRule(std::ostream& fout, const char* command, const char* comment, const std::vector& depends, - const char *output, + const std::vector& outputs, const char* compileFlags) { + // Write the rule for each configuration. std::vector::iterator i; std::vector *configs = static_cast(this->GlobalGenerator)->GetConfigurations(); @@ -1112,11 +1115,8 @@ WriteCustomRule(std::ostream& fout, } fout << "\t\t\t\t\tEscapeForXML(command) << "\"\n" + << "\t\t\t\t\tDescription=\"" << this->EscapeForXML(comment) << "\"\n" + << "\t\t\t\t\tCommandLine=\"" << this->EscapeForXML(command) << "\"\n" << "\t\t\t\t\tAdditionalDependencies=\""; // Write out the dependencies for the rule. std::string temp; @@ -1130,13 +1130,21 @@ WriteCustomRule(std::ostream& fout, } fout << "\"\n"; fout << "\t\t\t\t\tOutputs=\""; - if(output == 0) + if(outputs.empty()) { fout << source << "_force"; } - - // Write a rule for the output generated by this command. - fout << this->ConvertToXMLOutputPathSingle(output); + else + { + // Write a rule for the output generated by this command. + const char* sep = ""; + for(std::vector::const_iterator o = outputs.begin(); + o != outputs.end(); ++o) + { + fout << sep << this->ConvertToXMLOutputPathSingle(o->c_str()); + sep = ";"; + } + } fout << "\"/>\n"; fout << "\t\t\t\t\n"; } diff --git a/Source/cmLocalVisualStudio7Generator.h b/Source/cmLocalVisualStudio7Generator.h index 387762bb5..d6cfe5aa4 100644 --- a/Source/cmLocalVisualStudio7Generator.h +++ b/Source/cmLocalVisualStudio7Generator.h @@ -108,7 +108,7 @@ private: const char* command, const char* comment, const std::vector& depends, - const char* output, + const std::vector& outputs, const char* extraFlags); void WriteGroup(const cmSourceGroup *sg, cmTarget target, std::ostream &fout, diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 7e434cf5b..3d3b2d18e 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -499,7 +499,7 @@ cmMakefile::AddCustomCommandToTarget(const char* target, if(ti != this->Targets.end()) { // Add the command to the appropriate build step for the target. - const char* no_output = 0; + std::vector no_output; cmCustomCommand cc(no_output, depends, commandLines, comment, workingDir); switch(type) { @@ -530,7 +530,7 @@ cmMakefile::AddCustomCommandToTarget(const char* target, //---------------------------------------------------------------------------- void -cmMakefile::AddCustomCommandToOutput(const char* output, +cmMakefile::AddCustomCommandToOutput(const std::vector& outputs, const std::vector& depends, const char* main_dependency, const cmCustomCommandLines& commandLines, @@ -538,6 +538,13 @@ cmMakefile::AddCustomCommandToOutput(const char* output, const char* workingDir, bool replace) { + // Make sure there is at least one output. + if(outputs.empty()) + { + cmSystemTools::Error("Attempt to add a custom rule with no output!"); + return; + } + // Choose a source file on which to store the custom command. cmSourceFile* file = 0; if(main_dependency && main_dependency[0]) @@ -572,8 +579,8 @@ cmMakefile::AddCustomCommandToOutput(const char* output, // Generate a rule file if the main dependency is not available. if(!file) { - // Construct a rule file associated with the output produced. - std::string outName = output; + // Construct a rule file associated with the first output produced. + std::string outName = outputs[0]; outName += ".rule"; // Check if the rule file already exists. @@ -584,7 +591,8 @@ cmMakefile::AddCustomCommandToOutput(const char* output, if(commandLines != file->GetCustomCommand()->GetCommandLines()) { cmSystemTools::Error("Attempt to add a custom rule to output \"", - output, "\" which already has a custom rule."); + outName.c_str(), + "\" which already has a custom rule."); } return; } @@ -593,10 +601,14 @@ cmMakefile::AddCustomCommandToOutput(const char* output, file = this->GetOrCreateSource(outName.c_str(), true); } - // Always create the output and mark it generated. - if(cmSourceFile* out = this->GetOrCreateSource(output, true)) + // Always create the output sources and mark them generated. + for(std::vector::const_iterator o = outputs.begin(); + o != outputs.end(); ++o) { - out->SetProperty("GENERATED", "1"); + if(cmSourceFile* out = this->GetOrCreateSource(o->c_str(), true)) + { + out->SetProperty("GENERATED", "1"); + } } // Construct a complete list of dependencies. @@ -610,11 +622,29 @@ cmMakefile::AddCustomCommandToOutput(const char* output, if(file) { cmCustomCommand* cc = - new cmCustomCommand(output, depends2, commandLines, comment, workingDir); + new cmCustomCommand(outputs, depends2, commandLines, + comment, workingDir); file->SetCustomCommand(cc); } } +//---------------------------------------------------------------------------- +void +cmMakefile::AddCustomCommandToOutput(const char* output, + const std::vector& depends, + const char* main_dependency, + const cmCustomCommandLines& commandLines, + const char* comment, + const char* workingDir, + bool replace) +{ + std::vector outputs; + outputs.push_back(output); + this->AddCustomCommandToOutput(outputs, depends, main_dependency, + commandLines, comment, workingDir, + replace); +} + //---------------------------------------------------------------------------- void cmMakefile::AddCustomCommandOldStyle(const char* target, @@ -735,7 +765,12 @@ void cmMakefile::AddUtilityCommand(const char* utilityName, bool all, target.SetInAll(all); target.SetMakefile(this); // Store the custom command in the target. - cmCustomCommand cc(output, depends, commandLines, 0, workingDirectory); + std::vector outputs; + if(output) + { + outputs.push_back(output); + } + cmCustomCommand cc(outputs, depends, commandLines, 0, workingDirectory); target.GetPostBuildCommands().push_back(cc); // Add the target to the set of targets. @@ -1149,14 +1184,20 @@ cmSourceFile *cmMakefile::GetSourceFileWithOutput(const char *cname) if ((*i)->GetCustomCommand()) { // is the output of the custom command match the source files name - out = (*i)->GetCustomCommand()->GetOutput(); - std::string::size_type pos = out.rfind(name); - // If the output matches exactly - if (pos != out.npos && - pos == out.size() - name.size() && - (pos ==0 || out[pos-1] == '/')) + const std::vector& outputs = + (*i)->GetCustomCommand()->GetOutputs(); + for(std::vector::const_iterator o = outputs.begin(); + o != outputs.end(); ++o) { - return *i; + out = *o; + std::string::size_type pos = out.rfind(name); + // If the output matches exactly + if (pos != out.npos && + pos == out.size() - name.size() && + (pos ==0 || out[pos-1] == '/')) + { + return *i; + } } } } diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index 189bce6f7..e59e90e73 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -143,6 +143,12 @@ public: const cmCustomCommandLines& commandLines, cmTarget::CustomCommandType type, const char* comment, const char* workingDir); + void AddCustomCommandToOutput(const std::vector& outputs, + const std::vector& depends, + const char* main_dependency, + const cmCustomCommandLines& commandLines, + const char* comment, const char* workingDir, + bool replace = false); void AddCustomCommandToOutput(const char* output, const std::vector& depends, const char* main_dependency, diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx index 1f7c17fb1..f2d3dc01f 100644 --- a/Source/cmMakefileTargetGenerator.cxx +++ b/Source/cmMakefileTargetGenerator.cxx @@ -18,6 +18,7 @@ #include "cmGeneratedFileStream.h" #include "cmGlobalGenerator.h" +#include "cmGlobalUnixMakefileGenerator3.h" #include "cmLocalUnixMakefileGenerator3.h" #include "cmMakefile.h" #include "cmSourceFile.h" @@ -608,10 +609,15 @@ void cmMakefileTargetGenerator::WriteCustomCommands() this->GenerateCustomRuleFile(*cc); if (clean) { - this->CleanFiles.push_back - (this->Convert(cc->GetOutput(), - cmLocalGenerator::START_OUTPUT, - cmLocalGenerator::UNCHANGED)); + const std::vector& outputs = cc->GetOutputs(); + for(std::vector::const_iterator o = outputs.begin(); + o != outputs.end(); ++o) + { + this->CleanFiles.push_back + (this->Convert(o->c_str(), + cmLocalGenerator::START_OUTPUT, + cmLocalGenerator::UNCHANGED)); + } } } } @@ -621,17 +627,15 @@ void cmMakefileTargetGenerator::WriteCustomCommands() void cmMakefileTargetGenerator ::GenerateCustomRuleFile(const cmCustomCommand& cc) { - // Convert the output name to a relative path if possible. - std::string output = this->Convert(cc.GetOutput(), - cmLocalGenerator::START_OUTPUT); - // Collect the commands. std::vector commands; - std::string preEcho = "Generating "; - preEcho += output; - this->LocalGenerator - ->AppendEcho(commands, preEcho.c_str(), - cmLocalUnixMakefileGenerator3::EchoGenerate); + std::string comment = this->LocalGenerator->ConstructComment(cc); + if(!comment.empty()) + { + this->LocalGenerator + ->AppendEcho(commands, comment.c_str(), + cmLocalUnixMakefileGenerator3::EchoGenerate); + } this->LocalGenerator->AppendCustomCommand(commands, cc); // Collect the dependencies. @@ -639,14 +643,30 @@ void cmMakefileTargetGenerator this->LocalGenerator->AppendCustomDepend(depends, cc); // Write the rule. - const char* comment = 0; - if(cc.GetComment() && *cc.GetComment()) - { - comment = cc.GetComment(); - } - this->LocalGenerator->WriteMakeRule(*this->BuildFileStream, comment, - cc.GetOutput(), depends, commands, + const std::vector& outputs = cc.GetOutputs(); + std::vector::const_iterator o = outputs.begin(); + this->LocalGenerator->WriteMakeRule(*this->BuildFileStream, 0, + o->c_str(), depends, commands, false); + + // If the rule has multiple outputs, add a rule for the extra + // outputs to just depend on the first output with no command. Also + // register the extra outputs as paired with the first output so + // that the check-build-system step will remove the primary output + // if any extra outputs are missing, forcing the rule to regenerate + // all outputs. + depends.clear(); + depends.push_back(*o); + commands.clear(); + cmGlobalUnixMakefileGenerator3* gg = + static_cast(this->GlobalGenerator); + for(++o; o != outputs.end(); ++o) + { + this->LocalGenerator->WriteMakeRule(*this->BuildFileStream, 0, + o->c_str(), depends, commands, + false); + gg->AddMultipleOutputPair(o->c_str(), depends[0].c_str()); + } } //---------------------------------------------------------------------------- diff --git a/Source/cmake.cxx b/Source/cmake.cxx index 1a0cfdca9..341f21068 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -1921,9 +1921,13 @@ int cmake::CheckBuildSystem() cmGlobalGenerator *ggd = this->CreateGlobalGenerator(genName); if (ggd) { + // Check the dependencies in case source files were removed. std::auto_ptr lgd(ggd->CreateLocalGenerator()); lgd->SetGlobalGenerator(ggd); lgd->CheckDependencies(mf, verbose, this->ClearBuildSystem); + + // Check for multiple output pairs. + ggd->CheckMultipleOutputs(mf, verbose); } // No need to rerun. diff --git a/Tests/CustomCommand/CMakeLists.txt b/Tests/CustomCommand/CMakeLists.txt index f62be0eaf..97c4d812a 100644 --- a/Tests/CustomCommand/CMakeLists.txt +++ b/Tests/CustomCommand/CMakeLists.txt @@ -48,11 +48,11 @@ ADD_EXECUTABLE(wrapper wrapper.cxx) # is instantiated for the output, with GENERATED 1 # at the end of the day this becomes a what in VS ? ADD_CUSTOM_COMMAND( - OUTPUT ${PROJECT_BINARY_DIR}/wrapped.c + OUTPUT ${PROJECT_BINARY_DIR}/wrapped.c ${PROJECT_BINARY_DIR}/wrapped_help.c DEPENDS wrapper MAIN_DEPENDENCY ${PROJECT_SOURCE_DIR}/wrapped.h COMMAND ${EXECUTABLE_OUTPUT_PATH}/${CMAKE_CFG_INTDIR}/wrapper - ARGS ${PROJECT_BINARY_DIR}/wrapped.c ${PROJECT_SOURCE_DIR}/wrapped.h + ${PROJECT_BINARY_DIR}/wrapped.c ${PROJECT_BINARY_DIR}/wrapped_help.c ) ################################################################ @@ -145,6 +145,7 @@ ADD_EXECUTABLE(CustomCommand ${PROJECT_BINARY_DIR}/foo.h ${PROJECT_BINARY_DIR}/foo.c ${PROJECT_BINARY_DIR}/wrapped.c + ${PROJECT_BINARY_DIR}/wrapped_help.c ${PROJECT_BINARY_DIR}/generated.c ) diff --git a/Tests/CustomCommand/wrapper.cxx b/Tests/CustomCommand/wrapper.cxx index 3a1149da5..f65eb9b50 100644 --- a/Tests/CustomCommand/wrapper.cxx +++ b/Tests/CustomCommand/wrapper.cxx @@ -2,14 +2,17 @@ int main(int argc, char *argv[]) { - if ( argc < 2 ) + if ( argc < 3 ) { - fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } FILE *fp = fopen(argv[1],"w"); - - fprintf(fp,"int wrapped() { return 5; }\n"); + fprintf(fp,"extern int wrapped_help(void);\n"); + fprintf(fp,"int wrapped(void) { return wrapped_help(); }\n"); + fclose(fp); + fp = fopen(argv[2],"w"); + fprintf(fp,"int wrapped_help(void) { return 5; }\n"); fclose(fp); return 0; }