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.h
|
||||
cmGeneratedFileStream.cxx
|
||||
cmGeneratorExpression.cxx
|
||||
cmGeneratorExpression.h
|
||||
cmGlobalGenerator.cxx
|
||||
cmGlobalGenerator.h
|
||||
cmGlobalUnixMakefileGenerator3.cxx
|
||||
|
|
|
@ -79,6 +79,29 @@ public:
|
|||
"of the executable created at build time. "
|
||||
"If a CONFIGURATIONS option is given then the test will be executed "
|
||||
"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 "cmGeneratorExpression.h"
|
||||
#include "cmLocalGenerator.h"
|
||||
#include "cmMakefile.h"
|
||||
#include "cmSystemTools.h"
|
||||
|
@ -94,6 +95,10 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os,
|
|||
{
|
||||
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.
|
||||
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
|
||||
// be translated.
|
||||
std::string exe = command[0];
|
||||
cmMakefile* mf = this->Test->GetMakefile();
|
||||
cmTarget* target = mf->FindTargetToUse(exe.c_str());
|
||||
if(target && target->GetType() == cmTarget::EXECUTABLE)
|
||||
{
|
||||
|
@ -113,6 +117,7 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os,
|
|||
else
|
||||
{
|
||||
// Use the command name given.
|
||||
exe = ge.Process(exe.c_str());
|
||||
cmSystemTools::ConvertToUnixSlashes(exe);
|
||||
}
|
||||
|
||||
|
@ -122,7 +127,7 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os,
|
|||
for(std::vector<std::string>::const_iterator ci = command.begin()+1;
|
||||
ci != command.end(); ++ci)
|
||||
{
|
||||
os << " " << lg->EscapeForCMake(ci->c_str());
|
||||
os << " " << lg->EscapeForCMake(ge.Process(*ci));
|
||||
}
|
||||
|
||||
// Finish the test command.
|
||||
|
|
Loading…
Reference in New Issue