Commit d5719f22 authored by Brad King's avatar Brad King
Browse files

ENH: Added support for multiple outputs generated by a single custom command. ...

ENH: Added support for multiple outputs generated by a single custom command.  For Visual Studio generators the native tool provides support.  For Xcode and Makefile generators a simple trick is used.  The first output is considered primary and has the build rule attached.  Other outputs simply depend on the first output with no build rule.  During cmake_check_build_system CMake detects when a secondary output is missing and removes the primary output to make sure all outputs are regenerated.  This approach always builds the custom command at the right time and only once even during parallel builds.
parent b613cf0b
......@@ -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<std::string> depends, outputs;
std::vector<std::string> 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<std::string>::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<std::string>& outputs)
{
for(std::vector<std::string>::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;
}
......@@ -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<std::string>& outputs);
};
......
......@@ -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<std::string>& outputs,
const std::vector<std::string>& 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<std::string>& cmCustomCommand::GetOutputs() const
{
return this->Output.c_str();
return this->Outputs;
}
//----------------------------------------------------------------------------
......
......@@ -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<std::string>& outputs,
const std::vector<std::string>& 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<std::string>& 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<std::string> Outputs;
std::vector<std::string> Depends;
cmCustomCommandLines CommandLines;
std::string Comment;
......
......@@ -1428,9 +1428,10 @@ cmTarget cmGlobalGenerator::CreateGlobalTarget(
target.SetType(cmTarget::GLOBAL_TARGET, name);
target.SetInAll(false);
std::vector<std::string> fileDepends;
std::vector<std::string> no_outputs;
std::vector<std::string> 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.
}
......@@ -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; }
......
......@@ -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<std::string> pairs;
cmSystemTools::ExpandListArgument(pairs_string, pairs, true);
for(std::vector<std::string>::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<cmLocalGenerator *> &lGenerators)
......
......@@ -97,6 +97,19 @@ public:
void WriteConvenienceRules(std::ostream& ruleFileStream,
std::set<cmStdString> &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<cmStdString, cmStdString> MultipleOutputPairsType;
MultipleOutputPairsType MultipleOutputPairs;
};
#endif
......@@ -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);
......
......@@ -887,6 +887,8 @@ cmGlobalXCodeGenerator::AddCommandsToBuildPhase(cmXCodeObject* buildphase,
this->CreateString(makecmd.c_str()));
return;
}
std::map<cmStdString, cmStdString> 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<std::string>& outputs = cc.GetOutputs();
if(!outputs.empty())
{
makefileStream << "\\\n\t" << this->
ConvertToRelativeForMake(cc.GetOutput());
for(std::vector<std::string>::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<std::string>& 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<std::string>::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<std::string>::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<cmStdString, cmStdString>::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()));
}
......
......@@ -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<std::string>::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)
......
......@@ -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,
......
......@@ -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 == "<hack>")
{
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, "<hack>");
// 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<std::string>& 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<std::string>& depends,
const std::vector<std::string>& outputs,
const char* flags)
{
// Write the rule for each configuration.
std::vector<std::string>::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