diff options
author | Brad King <brad.king@kitware.com> | 2009-08-11 09:54:56 -0400 |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2009-08-11 09:54:56 -0400 |
commit | d2e1f2b4d6d87c171464b5dc41b00b609c90bf26 (patch) | |
tree | d0108fae5ed9b014939c0f7baee8a6ad4d6daa42 | |
parent | 463b3f03bd848a345ab535d31be31d395fe66b13 (diff) | |
download | cmake-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.txt | 2 | ||||
-rw-r--r-- | Source/cmAddTestCommand.h | 23 | ||||
-rw-r--r-- | Source/cmGeneratorExpression.cxx | 196 | ||||
-rw-r--r-- | Source/cmGeneratorExpression.h | 55 | ||||
-rw-r--r-- | Source/cmTestGenerator.cxx | 9 | ||||
-rwxr-xr-x | bootstrap | 1 |
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. @@ -188,6 +188,7 @@ CMAKE_CXX_SOURCES="\ cmExportInstallFileGenerator \ cmInstallDirectoryGenerator \ cmGeneratedFileStream \ + cmGeneratorExpression \ cmGlobalGenerator \ cmLocalGenerator \ cmInstallGenerator \ |