summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2009-08-11 09:54:56 -0400
committerBrad King <brad.king@kitware.com>2009-08-11 09:54:56 -0400
commitd2e1f2b4d6d87c171464b5dc41b00b609c90bf26 (patch)
treed0108fae5ed9b014939c0f7baee8a6ad4d6daa42
parent463b3f03bd848a345ab535d31be31d395fe66b13 (diff)
downloadcmake-d2e1f2b4d6d87c171464b5dc41b00b609c90bf26.tar.gz
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.
-rw-r--r--Source/CMakeLists.txt2
-rw-r--r--Source/cmAddTestCommand.h23
-rw-r--r--Source/cmGeneratorExpression.cxx196
-rw-r--r--Source/cmGeneratorExpression.h55
-rw-r--r--Source/cmTestGenerator.cxx9
-rwxr-xr-xbootstrap1
6 files changed, 284 insertions, 2 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 6474cb0dfa..01d397684a 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -159,6 +159,8 @@ SET(SRCS
cmFileTimeComparison.cxx
cmFileTimeComparison.h
cmGeneratedFileStream.cxx
+ cmGeneratorExpression.cxx
+ cmGeneratorExpression.h
cmGlobalGenerator.cxx
cmGlobalGenerator.h
cmGlobalUnixMakefileGenerator3.cxx
diff --git a/Source/cmAddTestCommand.h b/Source/cmAddTestCommand.h
index a971fd777c..0b9ca3c3d1 100644
--- a/Source/cmAddTestCommand.h
+++ b/Source/cmAddTestCommand.h
@@ -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\"."
;
}
diff --git a/Source/cmGeneratorExpression.cxx b/Source/cmGeneratorExpression.cxx
new file mode 100644
index 0000000000..5c409f705f
--- /dev/null
+++ b/Source/cmGeneratorExpression.cxx
@@ -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;
+}
diff --git a/Source/cmGeneratorExpression.h b/Source/cmGeneratorExpression.h
new file mode 100644
index 0000000000..05a697fc38
--- /dev/null
+++ b/Source/cmGeneratorExpression.h
@@ -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);
+};
diff --git a/Source/cmTestGenerator.cxx b/Source/cmTestGenerator.cxx
index 2c98910556..866ffd3fca 100644
--- a/Source/cmTestGenerator.cxx
+++ b/Source/cmTestGenerator.cxx
@@ -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.
diff --git a/bootstrap b/bootstrap
index 4710eca0a2..0c21c770f7 100755
--- a/bootstrap
+++ b/bootstrap
@@ -188,6 +188,7 @@ CMAKE_CXX_SOURCES="\
cmExportInstallFileGenerator \
cmInstallDirectoryGenerator \
cmGeneratedFileStream \
+ cmGeneratorExpression \
cmGlobalGenerator \
cmLocalGenerator \
cmInstallGenerator \