Introduce "generator expressions" to add_test()

This introduces a new syntax called "generator expressions" to the test
COMMAND option of the add_test(NAME) command mode.  These expressions
have a syntax like $<TARGET_FILE:mytarget> and are evaluated during
build system generation.  This syntax allows per-configuration target
output files to be referenced in test commands and arguments.
This commit is contained in:
Brad King 2009-08-11 09:54:56 -04:00
parent 463b3f03bd
commit d2e1f2b4d6
6 changed files with 284 additions and 2 deletions

View File

@ -159,6 +159,8 @@ SET(SRCS
cmFileTimeComparison.cxx cmFileTimeComparison.cxx
cmFileTimeComparison.h cmFileTimeComparison.h
cmGeneratedFileStream.cxx cmGeneratedFileStream.cxx
cmGeneratorExpression.cxx
cmGeneratorExpression.h
cmGlobalGenerator.cxx cmGlobalGenerator.cxx
cmGlobalGenerator.h cmGlobalGenerator.h
cmGlobalUnixMakefileGenerator3.cxx cmGlobalUnixMakefileGenerator3.cxx

View File

@ -79,6 +79,29 @@ public:
"of the executable created at build time. " "of the executable created at build time. "
"If a CONFIGURATIONS option is given then the test will be executed " "If a CONFIGURATIONS option is given then the test will be executed "
"only when testing under one of the named configurations." "only when testing under one of the named configurations."
"\n"
"Arguments after COMMAND may use \"generator expressions\" with the "
"syntax \"$<...>\". "
"These expressions are evaluted during build system generation and "
"produce information specific to each generated build configuration. "
"Valid expressions are:\n"
" $<CONFIGURATION> = configuration name\n"
" $<TARGET_FILE:tgt> = main file (.exe, .so.1.2, .a)\n"
" $<TARGET_LINKER_FILE:tgt> = file used to link (.a, .lib, .so)\n"
" $<TARGET_SONAME_FILE:tgt> = file with soname (.so.3)\n"
"where \"tgt\" is the name of a target. "
"Target file expressions produce a full path, but _DIR and _NAME "
"versions can produce the directory and file name components:\n"
" $<TARGET_FILE_DIR:tgt>/$<TARGET_FILE_NAME:tgt>\n"
" $<TARGET_LINKER_FILE_DIR:tgt>/$<TARGET_LINKER_FILE_NAME:tgt>\n"
" $<TARGET_SONAME_FILE_DIR:tgt>/$<TARGET_SONAME_FILE_NAME:tgt>\n"
"Example usage:\n"
" add_test(NAME mytest\n"
" COMMAND testDriver --config $<CONFIGURATION>\n"
" --exe $<TARGET_FILE:myexe>)\n"
"This creates a test \"mytest\" whose command runs a testDriver "
"tool passing the configuration name and the full path to the "
"executable file produced by target \"myexe\"."
; ;
} }

View File

@ -0,0 +1,196 @@
/*=========================================================================
Program: CMake - Cross-Platform Makefile Generator
Module: $RCSfile$
Language: C++
Date: $Date$
Version: $Revision$
Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved.
See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notices for more information.
=========================================================================*/
#include "cmGeneratorExpression.h"
#include "cmMakefile.h"
#include "cmTarget.h"
//----------------------------------------------------------------------------
cmGeneratorExpression::cmGeneratorExpression(
cmMakefile* mf, const char* config,
cmListFileBacktrace const& backtrace):
Makefile(mf), Config(config), Backtrace(backtrace)
{
this->TargetInfo.compile("^\\$<TARGET"
"(|_SONAME|_LINKER)" // File with what purpose?
"_FILE(|_NAME|_DIR):" // Filename component.
"([A-Za-z0-9_-]+)" // Target name.
">$");
}
//----------------------------------------------------------------------------
const char* cmGeneratorExpression::Process(std::string const& input)
{
return this->Process(input.c_str());
}
//----------------------------------------------------------------------------
const char* cmGeneratorExpression::Process(const char* input)
{
this->Data.clear();
// We construct and evaluate expressions directly in the output
// buffer. Each expression is replaced by its own output value
// after evaluation. A stack of barriers records the starting
// indices of open (pending) expressions.
for(const char* c = input; *c; ++c)
{
if(c[0] == '$' && c[1] == '<')
{
this->Barriers.push(this->Data.size());
this->Data.push_back('$');
this->Data.push_back('<');
c += 1;
}
else if(c[0] == '>' && !this->Barriers.empty())
{
this->Data.push_back('>');
if(!this->Evaluate()) { break; }
this->Barriers.pop();
}
else
{
this->Data.push_back(c[0]);
}
}
// Return a null-terminated output value.
this->Data.push_back('\0');
return &*this->Data.begin();
}
//----------------------------------------------------------------------------
bool cmGeneratorExpression::Evaluate()
{
// The top-most barrier points at the beginning of the expression.
size_t barrier = this->Barriers.top();
// Construct a null-terminated representation of the expression.
this->Data.push_back('\0');
const char* expr = &*(this->Data.begin()+barrier);
// Evaluate the expression.
std::string result;
if(this->Evaluate(expr, result))
{
// Success. Replace the expression with its evaluation result.
this->Data.erase(this->Data.begin()+barrier, this->Data.end());
this->Data.insert(this->Data.end(), result.begin(), result.end());
return true;
}
else
{
// Failure. Report the error message.
cmOStringStream e;
e << "Error evaluating generator expression:\n"
<< " " << expr << "\n"
<< result;
this->Makefile->GetCMakeInstance()
->IssueMessage(cmake::FATAL_ERROR, e.str().c_str(),
this->Backtrace);
return false;
}
}
//----------------------------------------------------------------------------
bool cmGeneratorExpression::Evaluate(const char* expr, std::string& result)
{
if(this->TargetInfo.find(expr))
{
if(!this->EvaluateTargetInfo(result))
{
return false;
}
}
else if(strcmp(expr, "$<CONFIGURATION>") == 0)
{
result = this->Config? this->Config : "";
}
else
{
result = "Expression syntax not recognized.";
return false;
}
return true;
}
//----------------------------------------------------------------------------
bool cmGeneratorExpression::EvaluateTargetInfo(std::string& result)
{
// Lookup the referenced target.
std::string name = this->TargetInfo.match(3);
cmTarget* target = this->Makefile->FindTargetToUse(name.c_str());
if(!target)
{
result = "No target \"" + name + "\"";
return false;
}
if(target->GetType() >= cmTarget::UTILITY &&
target->GetType() != cmTarget::UNKNOWN_LIBRARY)
{
result = "Target \"" + name + "\" is not an executable or library.";
return false;
}
// Lookup the target file with the given purpose.
std::string purpose = this->TargetInfo.match(1);
if(purpose == "")
{
// The target implementation file (.so.1.2, .dll, .exe, .a).
result = target->GetFullPath(this->Config, false, true);
}
else if(purpose == "_LINKER")
{
// The file used to link to the target (.so, .lib, .a).
if(!target->IsLinkable())
{
result = ("TARGET_LINKER_FILE is allowed only for libraries and "
"executables with ENABLE_EXPORTS.");
return false;
}
result = target->GetFullPath(this->Config, target->HasImportLibrary());
}
else if(purpose == "_SONAME")
{
// The target soname file (.so.1).
if(target->IsDLLPlatform())
{
result = "TARGET_SONAME_FILE is not allowed for DLL target platforms.";
return false;
}
if(target->GetType() != cmTarget::SHARED_LIBRARY)
{
result = "TARGET_SONAME_FILE is allowed only for SHARED libraries.";
return false;
}
result = target->GetDirectory(this->Config);
result += "/";
result += target->GetSOName(this->Config);
}
// Extract the requested portion of the full path.
std::string part = this->TargetInfo.match(2);
if(part == "_NAME")
{
result = cmSystemTools::GetFilenameName(result);
}
else if(part == "_DIR")
{
result = cmSystemTools::GetFilenamePath(result);
}
return true;
}

View File

@ -0,0 +1,55 @@
/*=========================================================================
Program: CMake - Cross-Platform Makefile Generator
Module: $RCSfile$
Language: C++
Date: $Date$
Version: $Revision$
Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved.
See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notices for more information.
=========================================================================*/
#include "cmStandardIncludes.h"
#include <stack>
#include <cmsys/RegularExpression.hxx>
class cmMakefile;
class cmListFileBacktrace;
/** \class cmGeneratorExpression
* \brief Evaluate generate-time query expression syntax.
*
* cmGeneratorExpression instances are used by build system generator
* implementations to evaluate the $<> generator expression syntax.
* Generator expressions are evaluated just before the generate step
* writes strings into the build system. They have knowledge of the
* build configuration which is not available at configure time.
*/
class cmGeneratorExpression
{
public:
/** Construct with an evaluation context and configuration. */
cmGeneratorExpression(cmMakefile* mf, const char* config,
cmListFileBacktrace const& backtrace);
/** Evaluate generator expressions in a string. */
const char* Process(std::string const& input);
const char* Process(const char* input);
private:
cmMakefile* Makefile;
const char* Config;
cmListFileBacktrace const& Backtrace;
std::vector<char> Data;
std::stack<size_t> Barriers;
cmsys::RegularExpression TargetInfo;
bool Evaluate();
bool Evaluate(const char* expr, std::string& result);
bool EvaluateTargetInfo(std::string& result);
};

View File

@ -16,6 +16,7 @@
=========================================================================*/ =========================================================================*/
#include "cmTestGenerator.h" #include "cmTestGenerator.h"
#include "cmGeneratorExpression.h"
#include "cmLocalGenerator.h" #include "cmLocalGenerator.h"
#include "cmMakefile.h" #include "cmMakefile.h"
#include "cmSystemTools.h" #include "cmSystemTools.h"
@ -94,6 +95,10 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os,
{ {
this->TestGenerated = true; this->TestGenerated = true;
// Set up generator expression evaluation context.
cmMakefile* mf = this->Test->GetMakefile();
cmGeneratorExpression ge(mf, config, this->Test->GetBacktrace());
// Start the test command. // Start the test command.
os << indent << "ADD_TEST(" << this->Test->GetName() << " "; os << indent << "ADD_TEST(" << this->Test->GetName() << " ";
@ -103,7 +108,6 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os,
// Check whether the command executable is a target whose name is to // Check whether the command executable is a target whose name is to
// be translated. // be translated.
std::string exe = command[0]; std::string exe = command[0];
cmMakefile* mf = this->Test->GetMakefile();
cmTarget* target = mf->FindTargetToUse(exe.c_str()); cmTarget* target = mf->FindTargetToUse(exe.c_str());
if(target && target->GetType() == cmTarget::EXECUTABLE) if(target && target->GetType() == cmTarget::EXECUTABLE)
{ {
@ -113,6 +117,7 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os,
else else
{ {
// Use the command name given. // Use the command name given.
exe = ge.Process(exe.c_str());
cmSystemTools::ConvertToUnixSlashes(exe); cmSystemTools::ConvertToUnixSlashes(exe);
} }
@ -122,7 +127,7 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os,
for(std::vector<std::string>::const_iterator ci = command.begin()+1; for(std::vector<std::string>::const_iterator ci = command.begin()+1;
ci != command.end(); ++ci) ci != command.end(); ++ci)
{ {
os << " " << lg->EscapeForCMake(ci->c_str()); os << " " << lg->EscapeForCMake(ge.Process(*ci));
} }
// Finish the test command. // Finish the test command.

View File

@ -188,6 +188,7 @@ CMAKE_CXX_SOURCES="\
cmExportInstallFileGenerator \ cmExportInstallFileGenerator \
cmInstallDirectoryGenerator \ cmInstallDirectoryGenerator \
cmGeneratedFileStream \ cmGeneratedFileStream \
cmGeneratorExpression \
cmGlobalGenerator \ cmGlobalGenerator \
cmLocalGenerator \ cmLocalGenerator \
cmInstallGenerator \ cmInstallGenerator \