file: Add GENERATE command to produce files at generate time

The idea is to write to a temp file which contains generator
expressions, and at generate time, evaluate the generator expressions,
and write the result to a file.

Because executables on Windows are limited in the length of command line
it is possible to use, it is common to write command line arguments to a
file instead and specify the file as a source of arguments.

This new FILE(GENERATE) subcommand allows the use of generator
expressions to create such files so that they can be used with
add_custom_command for example.
This commit is contained in:
Stephen Kelly 2013-01-02 17:10:04 +01:00 committed by Brad King
parent 272431a84f
commit b983a58bdf
27 changed files with 430 additions and 0 deletions

View File

@ -52,6 +52,7 @@
#include "cmFindProgramCommand.cxx" #include "cmFindProgramCommand.cxx"
#include "cmForEachCommand.cxx" #include "cmForEachCommand.cxx"
#include "cmFunctionCommand.cxx" #include "cmFunctionCommand.cxx"
#include "cmGeneratorExpressionEvaluationFile.cxx"
#include "cmGetCMakePropertyCommand.cxx" #include "cmGetCMakePropertyCommand.cxx"
#include "cmGetDirectoryPropertyCommand.cxx" #include "cmGetDirectoryPropertyCommand.cxx"
#include "cmGetFilenameComponentCommand.cxx" #include "cmGetFilenameComponentCommand.cxx"

View File

@ -167,6 +167,10 @@ bool cmFileCommand
{ {
return this->HandleTimestampCommand(args); return this->HandleTimestampCommand(args);
} }
else if ( subCommand == "GENERATE" )
{
return this->HandleGenerateCommand(args);
}
std::string e = "does not recognize sub-command "+subCommand; std::string e = "does not recognize sub-command "+subCommand;
this->SetError(e.c_str()); this->SetError(e.c_str());
@ -3249,6 +3253,80 @@ cmFileCommand::HandleUploadCommand(std::vector<std::string> const& args)
#endif #endif
} }
//----------------------------------------------------------------------------
void cmFileCommand::AddEvaluationFile(const std::string &inputName,
const std::string &outputExpr,
const std::string &condition,
bool inputIsContent
)
{
cmListFileBacktrace lfbt;
this->Makefile->GetBacktrace(lfbt);
cmGeneratorExpression outputGe(lfbt);
cmsys::auto_ptr<cmCompiledGeneratorExpression> outputCge
= outputGe.Parse(outputExpr);
cmGeneratorExpression conditionGe(lfbt);
cmsys::auto_ptr<cmCompiledGeneratorExpression> conditionCge
= conditionGe.Parse(condition);
this->Makefile->GetLocalGenerator()
->GetGlobalGenerator()->AddEvaluationFile(inputName,
outputCge,
this->Makefile,
conditionCge,
inputIsContent);
}
//----------------------------------------------------------------------------
bool cmFileCommand::HandleGenerateCommand(
std::vector<std::string> const& args)
{
if (args.size() < 5)
{
this->SetError("Incorrect arguments to GENERATE subcommand.");
return false;
}
if (args[1] != "OUTPUT")
{
this->SetError("Incorrect arguments to GENERATE subcommand.");
return false;
}
std::string condition;
if (args.size() > 5)
{
if (args[5] != "CONDITION")
{
this->SetError("Incorrect arguments to GENERATE subcommand.");
return false;
}
if (args.size() != 7)
{
this->SetError("Incorrect arguments to GENERATE subcommand.");
return false;
}
condition = args[6];
if (condition.empty())
{
this->SetError("CONDITION of sub-command GENERATE must not be empty if "
"specified.");
return false;
}
}
std::string output = args[2];
const bool inputIsContent = args[3] != "INPUT";
if (inputIsContent && args[3] != "CONTENT")
{
this->SetError("Incorrect arguments to GENERATE subcommand.");
return false;
}
std::string input = args[4];
this->AddEvaluationFile(input, output, condition, inputIsContent);
return true;
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
bool cmFileCommand::HandleTimestampCommand( bool cmFileCommand::HandleTimestampCommand(
std::vector<std::string> const& args) std::vector<std::string> const& args)

View File

@ -88,6 +88,9 @@ public:
" file(UPLOAD filename url [INACTIVITY_TIMEOUT timeout]\n" " file(UPLOAD filename url [INACTIVITY_TIMEOUT timeout]\n"
" [TIMEOUT timeout] [STATUS status] [LOG log] [SHOW_PROGRESS])\n" " [TIMEOUT timeout] [STATUS status] [LOG log] [SHOW_PROGRESS])\n"
" file(TIMESTAMP filename variable [<format string>] [UTC])\n" " file(TIMESTAMP filename variable [<format string>] [UTC])\n"
" file(GENERATE OUTPUT output_file\n"
" <INPUT input_file|CONTENT input_content>\n"
" CONDITION expression)\n"
"WRITE will write a message into a file called 'filename'. It " "WRITE will write a message into a file called 'filename'. It "
"overwrites the file if it already exists, and creates the file " "overwrites the file if it already exists, and creates the file "
"if it does not exist. (If the file is a build input, use " "if it does not exist. (If the file is a build input, use "
@ -231,6 +234,15 @@ public:
"it prints status messages, and NO_SOURCE_PERMISSIONS is default. " "it prints status messages, and NO_SOURCE_PERMISSIONS is default. "
"Installation scripts generated by the install() command use this " "Installation scripts generated by the install() command use this "
"signature (with some undocumented options for internal use)." "signature (with some undocumented options for internal use)."
"\n"
"GENERATE will write an <output_file> with content from an "
"<input_file>, or from <input_content>. The output is generated "
"conditionally based on the content of the <condition>. The file is "
"written at CMake generate-time and the input may contain generator "
"expressions. The <condition>, <output_file> and <input_file> may "
"also contain generator expressions. The <condition> must evaluate to "
"either '0' or '1'. The <output_file> must evaluate to a unique name "
"among all configurations and among all invocations of file(GENERATE)."
// Undocumented INSTALL options: // Undocumented INSTALL options:
// - RENAME <name> // - RENAME <name>
// - OPTIONAL // - OPTIONAL
@ -269,6 +281,13 @@ protected:
bool HandleUploadCommand(std::vector<std::string> const& args); bool HandleUploadCommand(std::vector<std::string> const& args);
bool HandleTimestampCommand(std::vector<std::string> const& args); bool HandleTimestampCommand(std::vector<std::string> const& args);
bool HandleGenerateCommand(std::vector<std::string> const& args);
private:
void AddEvaluationFile(const std::string &inputName,
const std::string &outputExpr,
const std::string &condition,
bool inputIsContent);
}; };

View File

@ -0,0 +1,151 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2013 Stephen Kelly <steveire@gmail.com>
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmGeneratorExpressionEvaluationFile.h"
#include "cmMakefile.h"
#include <assert.h>
//----------------------------------------------------------------------------
cmGeneratorExpressionEvaluationFile::cmGeneratorExpressionEvaluationFile(
const std::string &input,
cmsys::auto_ptr<cmCompiledGeneratorExpression> outputFileExpr,
cmMakefile *makefile,
cmsys::auto_ptr<cmCompiledGeneratorExpression> condition,
bool inputIsContent)
: Input(input),
OutputFileExpr(outputFileExpr),
Makefile(makefile),
Condition(condition),
InputIsContent(inputIsContent)
{
}
//----------------------------------------------------------------------------
void cmGeneratorExpressionEvaluationFile::Generate(const char *config,
cmCompiledGeneratorExpression* inputExpression,
std::map<std::string, std::string> &outputFiles)
{
std::string rawCondition = this->Condition->GetInput();
if (!rawCondition.empty())
{
std::string condResult = this->Condition->Evaluate(this->Makefile, config);
if (condResult == "0")
{
return;
}
if (condResult != "1")
{
cmOStringStream e;
e << "Evaluation file condition \"" << rawCondition << "\" did "
"not evaluate to valid content. Got \"" << condResult << "\".";
this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str().c_str());
return;
}
}
const std::string outputFileName
= this->OutputFileExpr->Evaluate(this->Makefile, config);
const std::string outputContent
= inputExpression->Evaluate(this->Makefile, config);
std::map<std::string, std::string>::iterator it
= outputFiles.find(outputFileName);
if(it != outputFiles.end())
{
if (it->second == outputContent)
{
return;
}
cmOStringStream e;
e << "Evaluation file to be written multiple times for different "
"configurations with different content:\n " << outputFileName;
this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str().c_str());
return;
}
this->Files.push_back(outputFileName);
outputFiles[outputFileName] = outputContent;
std::ofstream fout(outputFileName.c_str());
if(!fout)
{
cmOStringStream e;
e << "Evaluation file \"" << outputFileName << "\" cannot be written.";
this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str().c_str());
return;
}
fout << outputContent;
fout.close();
}
//----------------------------------------------------------------------------
void cmGeneratorExpressionEvaluationFile::Generate()
{
std::string inputContent;
if (this->InputIsContent)
{
inputContent = this->Input;
}
else
{
std::ifstream fin(this->Input.c_str());
if(!fin)
{
cmOStringStream e;
e << "Evaluation file \"" << this->Input << "\" cannot be read.";
this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str().c_str());
return;
}
std::string line;
std::string sep;
while(cmSystemTools::GetLineFromStream(fin, line))
{
inputContent += sep + line;
sep = "\n";
}
inputContent += sep;
}
cmListFileBacktrace lfbt = this->OutputFileExpr->GetBacktrace();
cmGeneratorExpression contentGE(lfbt);
cmsys::auto_ptr<cmCompiledGeneratorExpression> inputExpression
= contentGE.Parse(inputContent);
std::map<std::string, std::string> outputFiles;
std::vector<std::string> allConfigs;
this->Makefile->GetConfigurations(allConfigs);
if (allConfigs.empty())
{
this->Generate(0, inputExpression.get(), outputFiles);
}
else
{
for(std::vector<std::string>::const_iterator li = allConfigs.begin();
li != allConfigs.end(); ++li)
{
this->Generate(li->c_str(), inputExpression.get(), outputFiles);
if(cmSystemTools::GetFatalErrorOccured())
{
return;
}
}
}
}

View File

@ -0,0 +1,48 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2013 Stephen Kelly <steveire@gmail.com>
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#ifndef cmGeneratorExpressionEvaluationFile_h
#define cmGeneratorExpressionEvaluationFile_h
#include "cmStandardIncludes.h"
#include <cmsys/auto_ptr.hxx>
#include "cmGeneratorExpression.h"
//----------------------------------------------------------------------------
class cmGeneratorExpressionEvaluationFile
{
public:
cmGeneratorExpressionEvaluationFile(const std::string &input,
cmsys::auto_ptr<cmCompiledGeneratorExpression> outputFileExpr,
cmMakefile *makefile,
cmsys::auto_ptr<cmCompiledGeneratorExpression> condition,
bool inputIsContent);
void Generate();
std::vector<std::string> GetFiles() const { return this->Files; }
private:
void Generate(const char *config,
cmCompiledGeneratorExpression* inputExpression,
std::map<std::string, std::string> &outputFiles);
private:
const std::string Input;
const cmsys::auto_ptr<cmCompiledGeneratorExpression> OutputFileExpr;
cmMakefile *Makefile;
const cmsys::auto_ptr<cmCompiledGeneratorExpression> Condition;
std::vector<std::string> Files;
const bool InputIsContent;
};
#endif

View File

@ -26,6 +26,7 @@
#include "cmGeneratedFileStream.h" #include "cmGeneratedFileStream.h"
#include "cmGeneratorTarget.h" #include "cmGeneratorTarget.h"
#include "cmGeneratorExpression.h" #include "cmGeneratorExpression.h"
#include "cmGeneratorExpressionEvaluationFile.h"
#include <cmsys/Directory.hxx> #include <cmsys/Directory.hxx>
@ -69,6 +70,13 @@ cmGlobalGenerator::~cmGlobalGenerator()
{ {
delete this->LocalGenerators[i]; delete this->LocalGenerators[i];
} }
for(std::vector<cmGeneratorExpressionEvaluationFile*>::const_iterator
li = this->EvaluationFiles.begin();
li != this->EvaluationFiles.end();
++li)
{
delete *li;
}
this->LocalGenerators.clear(); this->LocalGenerators.clear();
if (this->ExtraGenerator) if (this->ExtraGenerator)
@ -981,6 +989,8 @@ void cmGlobalGenerator::Generate()
// Create per-target generator information. // Create per-target generator information.
this->CreateGeneratorTargets(); this->CreateGeneratorTargets();
this->ProcessEvaluationFiles();
// Compute the inter-target dependencies. // Compute the inter-target dependencies.
if(!this->ComputeTargetDepends()) if(!this->ComputeTargetDepends())
{ {
@ -2558,3 +2568,44 @@ std::string cmGlobalGenerator::EscapeJSON(const std::string& s) {
} }
return result; return result;
} }
//----------------------------------------------------------------------------
void cmGlobalGenerator::AddEvaluationFile(const std::string &inputFile,
cmsys::auto_ptr<cmCompiledGeneratorExpression> outputExpr,
cmMakefile *makefile,
cmsys::auto_ptr<cmCompiledGeneratorExpression> condition,
bool inputIsContent)
{
this->EvaluationFiles.push_back(
new cmGeneratorExpressionEvaluationFile(inputFile, outputExpr,
makefile, condition,
inputIsContent));
}
//----------------------------------------------------------------------------
void cmGlobalGenerator::ProcessEvaluationFiles()
{
std::set<std::string> generatedFiles;
for(std::vector<cmGeneratorExpressionEvaluationFile*>::const_iterator
li = this->EvaluationFiles.begin();
li != this->EvaluationFiles.end();
++li)
{
(*li)->Generate();
if (cmSystemTools::GetFatalErrorOccured())
{
return;
}
std::vector<std::string> files = (*li)->GetFiles();
for(std::vector<std::string>::const_iterator fi = files.begin();
fi != files.end(); ++fi)
{
if (!generatedFiles.insert(*fi).second)
{
cmSystemTools::Error("File to be generated by multiple different "
"commands: ", fi->c_str());
return;
}
}
}
}

View File

@ -20,9 +20,11 @@
#include "cmSystemTools.h" // for cmSystemTools::OutputOption #include "cmSystemTools.h" // for cmSystemTools::OutputOption
#include "cmExportSetMap.h" // For cmExportSetMap #include "cmExportSetMap.h" // For cmExportSetMap
#include "cmGeneratorTarget.h" #include "cmGeneratorTarget.h"
#include "cmGeneratorExpression.h"
class cmake; class cmake;
class cmGeneratorTarget; class cmGeneratorTarget;
class cmGeneratorExpressionEvaluationFile;
class cmMakefile; class cmMakefile;
class cmLocalGenerator; class cmLocalGenerator;
class cmExternalMakefileProjectGenerator; class cmExternalMakefileProjectGenerator;
@ -278,6 +280,14 @@ public:
static std::string EscapeJSON(const std::string& s); static std::string EscapeJSON(const std::string& s);
void AddEvaluationFile(const std::string &inputFile,
cmsys::auto_ptr<cmCompiledGeneratorExpression> outputName,
cmMakefile *makefile,
cmsys::auto_ptr<cmCompiledGeneratorExpression> condition,
bool inputIsContent);
void ProcessEvaluationFiles();
protected: protected:
typedef std::vector<cmLocalGenerator*> GeneratorVector; typedef std::vector<cmLocalGenerator*> GeneratorVector;
// for a project collect all its targets by following depend // for a project collect all its targets by following depend
@ -337,6 +347,7 @@ protected:
// All targets in the entire project. // All targets in the entire project.
std::map<cmStdString,cmTarget *> TotalTargets; std::map<cmStdString,cmTarget *> TotalTargets;
std::map<cmStdString,cmTarget *> ImportedTargets; std::map<cmStdString,cmTarget *> ImportedTargets;
std::vector<cmGeneratorExpressionEvaluationFile*> EvaluationFiles;
virtual const char* GetPredefinedTargetsFolder(); virtual const char* GetPredefinedTargetsFolder();
virtual bool UseFolderProperty(); virtual bool UseFolderProperty();

View File

@ -86,3 +86,5 @@ if("${CMAKE_TEST_GENERATOR}" MATCHES "Visual Studio [^6]")
add_RunCMake_test(include_external_msproject) add_RunCMake_test(include_external_msproject)
add_RunCMake_test(SolutionGlobalSections) add_RunCMake_test(SolutionGlobalSections)
endif() endif()
add_RunCMake_test(File_Generate)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,3 @@
CMake Error in CMakeLists.txt:
Evaluation file condition \"\$<1:Bad>\" did not evaluate to valid content.
Got \"Bad\".

View File

@ -0,0 +1,5 @@
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output.txt"
INPUT "${CMAKE_CURRENT_SOURCE_DIR}/input.txt"
CONDITION $<1:Bad>
)

View File

@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 2.8)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1 @@
CMake Error: File to be generated by multiple different commands: .*CommandConflict-build/output_.*.txt

View File

@ -0,0 +1,9 @@
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output_$<CONFIGURATION>.txt"
INPUT "${CMAKE_CURRENT_SOURCE_DIR}/input.txt"
CONDITION $<CONFIG:$<CONFIGURATION>>
)
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output_$<CONFIGURATION>.txt"
INPUT "${CMAKE_CURRENT_SOURCE_DIR}/input.txt"
CONDITION $<CONFIG:$<CONFIGURATION>>
)

View File

@ -0,0 +1,5 @@
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output.txt"
INPUT "${CMAKE_CURRENT_SOURCE_DIR}/input.txt"
CONDITION $<CONFIG:Debug>
)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,4 @@
CMake Error at EmptyCondition1.cmake:2 \(file\):
file Incorrect arguments to GENERATE subcommand.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,5 @@
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output.txt"
INPUT "${CMAKE_CURRENT_SOURCE_DIR}/input.txt"
CONDITION
)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,4 @@
CMake Error at EmptyCondition2.cmake:2 \(file\):
file CONDITION of sub-command GENERATE must not be empty if specified.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1,5 @@
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output.txt"
INPUT "${CMAKE_CURRENT_SOURCE_DIR}/input.txt"
CONDITION ""
)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,5 @@
CMake Error in CMakeLists.txt:
Evaluation file to be written multiple times for different configurations
with different content:
.*output.txt

View File

@ -0,0 +1,4 @@
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output.txt"
INPUT "${CMAKE_CURRENT_SOURCE_DIR}/input.txt"
)

View File

@ -0,0 +1,10 @@
include(RunCMake)
run_cmake(CommandConflict)
if("${RunCMake_GENERATOR}" MATCHES "Visual Studio" OR "${RunCMake_GENERATOR}" MATCHES "XCode" )
run_cmake(OutputConflict)
endif()
run_cmake(EmptyCondition1)
run_cmake(EmptyCondition2)
run_cmake(BadCondition)
run_cmake(DebugEvaluate)

View File

@ -0,0 +1 @@
Some $<$<CONFIG:Debug>:conflicting> $<$<NOT:$<CONFIG:Debug>>:content>