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:
parent
463b3f03bd
commit
d2e1f2b4d6
|
@ -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
|
||||||
|
|
|
@ -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\"."
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
};
|
|
@ -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.
|
||||||
|
|
|
@ -188,6 +188,7 @@ CMAKE_CXX_SOURCES="\
|
||||||
cmExportInstallFileGenerator \
|
cmExportInstallFileGenerator \
|
||||||
cmInstallDirectoryGenerator \
|
cmInstallDirectoryGenerator \
|
||||||
cmGeneratedFileStream \
|
cmGeneratedFileStream \
|
||||||
|
cmGeneratorExpression \
|
||||||
cmGlobalGenerator \
|
cmGlobalGenerator \
|
||||||
cmLocalGenerator \
|
cmLocalGenerator \
|
||||||
cmInstallGenerator \
|
cmInstallGenerator \
|
||||||
|
|
Loading…
Reference in New Issue