diff options
26 files changed, 588 insertions, 18 deletions
diff --git a/Modules/CMakeDetermineCompilerABI.cmake b/Modules/CMakeDetermineCompilerABI.cmake index d6df305..81ec71c 100644 --- a/Modules/CMakeDetermineCompilerABI.cmake +++ b/Modules/CMakeDetermineCompilerABI.cmake @@ -24,10 +24,18 @@ FUNCTION(CMAKE_DETERMINE_COMPILER_ABI lang src) # Compile the ABI identification source. SET(BIN "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeDetermineCompilerABI_${lang}.bin") + SET(CMAKE_FLAGS ) + IF(DEFINED CMAKE_${lang}_VERBOSE_FLAG) + SET(CMAKE_FLAGS "-DCMAKE_EXE_LINKER_FLAGS=${CMAKE_${lang}_VERBOSE_FLAG}") + ENDIF() TRY_COMPILE(CMAKE_DETERMINE_${lang}_ABI_COMPILED ${CMAKE_BINARY_DIR} ${src} - CMAKE_FLAGS "-DCMAKE_EXE_LINKER_FLAGS=${CMAKE_${lang}_VERBOSE_FLAG}" + CMAKE_FLAGS "${CMAKE_FLAGS}" "-DCMAKE_${lang}_STANDARD_LIBRARIES=" + # We need ignore these warnings because some platforms need + # CMAKE_${lang}_STANDARD_LIBRARIES to link properly and we + # don't care when we are just determining the ABI. + "--no-warn-unused-cli" OUTPUT_VARIABLE OUTPUT COPY_FILE "${BIN}" ) @@ -58,10 +66,16 @@ FUNCTION(CMAKE_DETERMINE_COMPILER_ABI lang src) # Parse implicit linker information for this language, if available. SET(implicit_dirs "") SET(implicit_libs "") + SET(MULTI_ARCH FALSE) + IF(DEFINED CMAKE_OSX_ARCHITECTURES) + IF( "${CMAKE_OSX_ARCHITECTURES}" MATCHES ";" ) + SET(MULTI_ARCH TRUE) + ENDIF() + ENDIF() IF(CMAKE_${lang}_VERBOSE_FLAG # Implicit link information cannot be used explicitly for # multiple OS X architectures, so we skip it. - AND NOT "${CMAKE_OSX_ARCHITECTURES}" MATCHES ";" + AND NOT MULTI_ARCH # Skip this with Xcode for now. AND NOT "${CMAKE_GENERATOR}" MATCHES Xcode) CMAKE_PARSE_IMPLICIT_LINK_INFO("${OUTPUT}" implicit_libs implicit_dirs log) diff --git a/Modules/CMakeDetermineCompilerId.cmake b/Modules/CMakeDetermineCompilerId.cmake index bf78a5b..9a3884a 100644 --- a/Modules/CMakeDetermineCompilerId.cmake +++ b/Modules/CMakeDetermineCompilerId.cmake @@ -243,7 +243,9 @@ FUNCTION(CMAKE_DETERMINE_COMPILER_ID_CHECK lang file) # ENDIF("${CMAKE_EXECUTABLE_MAGIC}" MATCHES "feedface") ENDIF(NOT CMAKE_EXECUTABLE_FORMAT) - + IF(NOT DEFINED CMAKE_EXECUTABLE_FORMAT) + SET(CMAKE_EXECUTABLE_FORMAT) + ENDIF() # Return the information extracted. SET(CMAKE_${lang}_COMPILER_ID "${CMAKE_${lang}_COMPILER_ID}" PARENT_SCOPE) SET(CMAKE_${lang}_PLATFORM_ID "${CMAKE_${lang}_PLATFORM_ID}" PARENT_SCOPE) diff --git a/Source/QtDialog/CMakeSetupDialog.cxx b/Source/QtDialog/CMakeSetupDialog.cxx index 29fcfc0..779c14e 100644 --- a/Source/QtDialog/CMakeSetupDialog.cxx +++ b/Source/QtDialog/CMakeSetupDialog.cxx @@ -118,8 +118,15 @@ CMakeSetupDialog::CMakeSetupDialog() this, SLOT(doInstallForCommandLine())); #endif QMenu* OptionsMenu = this->menuBar()->addMenu(tr("&Options")); - this->SuppressDevWarningsAction = OptionsMenu->addAction(tr("&Suppress dev Warnings (-Wno-dev)")); + this->SuppressDevWarningsAction = + OptionsMenu->addAction(tr("&Suppress dev Warnings (-Wno-dev)")); this->SuppressDevWarningsAction->setCheckable(true); + this->WarnUninitializedAction = + OptionsMenu->addAction(tr("&Warn Uninitialized (--warn-uninitialized)")); + this->WarnUninitializedAction->setCheckable(true); + this->WarnUnusedAction = + OptionsMenu->addAction(tr("&Warn Unused (--warn-unused-vars)")); + this->WarnUnusedAction->setCheckable(true); QAction* debugAction = OptionsMenu->addAction(tr("&Debug Output")); debugAction->setCheckable(true); @@ -245,6 +252,13 @@ void CMakeSetupDialog::initialize() QObject::connect(this->SuppressDevWarningsAction, SIGNAL(triggered(bool)), this->CMakeThread->cmakeInstance(), SLOT(setSuppressDevWarnings(bool))); + QObject::connect(this->WarnUninitializedAction, SIGNAL(triggered(bool)), + this->CMakeThread->cmakeInstance(), + SLOT(setWarnUninitializedMode(bool))); + QObject::connect(this->WarnUnusedAction, SIGNAL(triggered(bool)), + this->CMakeThread->cmakeInstance(), + SLOT(setWarnUnusedMode(bool))); + if(!this->SourceDirectory->text().isEmpty() || !this->BinaryDirectory->lineEdit()->text().isEmpty()) { diff --git a/Source/QtDialog/CMakeSetupDialog.h b/Source/QtDialog/CMakeSetupDialog.h index 0e3caec..c4d029a 100644 --- a/Source/QtDialog/CMakeSetupDialog.h +++ b/Source/QtDialog/CMakeSetupDialog.h @@ -93,6 +93,8 @@ protected: QAction* ConfigureAction; QAction* GenerateAction; QAction* SuppressDevWarningsAction; + QAction* WarnUninitializedAction; + QAction* WarnUnusedAction; QAction* InstallForCommandLineAction; State CurrentState; diff --git a/Source/QtDialog/QCMake.cxx b/Source/QtDialog/QCMake.cxx index dc31fad..48871c2 100644 --- a/Source/QtDialog/QCMake.cxx +++ b/Source/QtDialog/QCMake.cxx @@ -28,6 +28,8 @@ QCMake::QCMake(QObject* p) : QObject(p) { this->SuppressDevWarnings = false; + this->WarnUninitializedMode = false; + this->WarnUnusedMode = false; qRegisterMetaType<QCMakeProperty>(); qRegisterMetaType<QCMakePropertyList>(); @@ -164,6 +166,9 @@ void QCMake::configure() this->CMakeInstance->CreateGlobalGenerator(this->Generator.toAscii().data())); this->CMakeInstance->LoadCache(); this->CMakeInstance->SetSuppressDevWarnings(this->SuppressDevWarnings); + std::cerr << "set warn uninitialized " << this->WarnUninitializedMode << "\n"; + this->CMakeInstance->SetWarnUninitialized(this->WarnUninitializedMode); + this->CMakeInstance->SetWarnUnused(this->WarnUnusedMode); this->CMakeInstance->PreLoadCMakeFiles(); cmSystemTools::ResetErrorOccuredFlag(); @@ -244,6 +249,8 @@ void QCMake::setProperties(const QCMakePropertyList& newProps) // add some new properites foreach(QCMakeProperty s, props) { + this->CMakeInstance->WatchUnusedCli(s.Key.toAscii().data()); + if(s.Type == QCMakeProperty::BOOL) { this->CMakeInstance->AddCacheEntry(s.Key.toAscii().data(), @@ -417,3 +424,13 @@ void QCMake::setSuppressDevWarnings(bool value) { this->SuppressDevWarnings = value; } + +void QCMake::setWarnUninitializedMode(bool value) +{ + this->WarnUninitializedMode = value; +} + +void QCMake::setWarnUnusedMode(bool value) +{ + this->WarnUnusedMode = value; +} diff --git a/Source/QtDialog/QCMake.h b/Source/QtDialog/QCMake.h index bbfb3d7..0d10823 100644 --- a/Source/QtDialog/QCMake.h +++ b/Source/QtDialog/QCMake.h @@ -88,6 +88,10 @@ public slots: void setDebugOutput(bool); /// set whether to do suppress dev warnings void setSuppressDevWarnings(bool value); + /// set whether to run cmake with warnings about uninitialized variables + void setWarnUninitializedMode(bool value); + /// set whether to run cmake with warnings about unused variables + void setWarnUnusedMode(bool value); public: /// get the list of cache properties @@ -133,6 +137,9 @@ protected: static void errorCallback(const char* msg, const char* title, bool&, void* cd); bool SuppressDevWarnings; + bool WarnUninitializedMode; + bool WarnUnusedMode; + bool WarnUnusedAllMode; QString SourceDirectory; QString BinaryDirectory; QString Generator; diff --git a/Source/cmCommandArgumentParserHelper.cxx b/Source/cmCommandArgumentParserHelper.cxx index 234c37e..a781767 100644 --- a/Source/cmCommandArgumentParserHelper.cxx +++ b/Source/cmCommandArgumentParserHelper.cxx @@ -20,6 +20,8 @@ int cmCommandArgument_yyparse( yyscan_t yyscanner ); // cmCommandArgumentParserHelper::cmCommandArgumentParserHelper() { + this->WarnUninitialized = false; + this->CheckSystemVars = false; this->FileLine = -1; this->FileName = 0; this->RemoveEmpty = true; @@ -119,10 +121,32 @@ char* cmCommandArgumentParserHelper::ExpandVariable(const char* var) cmOStringStream ostr; ostr << this->FileLine; return this->AddString(ostr.str().c_str()); - } + } const char* value = this->Makefile->GetDefinition(var); if(!value && !this->RemoveEmpty) { + // check to see if we need to print a warning + // if strict mode is on and the variable has + // not been "cleared"/initialized with a set(foo ) call + if(this->WarnUninitialized && !this->Makefile->VariableInitialized(var)) + { + if (this->CheckSystemVars || + cmSystemTools::IsSubDirectory(this->FileName, + this->Makefile->GetHomeDirectory()) || + cmSystemTools::IsSubDirectory(this->FileName, + this->Makefile->GetHomeOutputDirectory())) + { + cmOStringStream msg; + cmListFileBacktrace bt; + cmListFileContext lfc; + lfc.FilePath = this->FileName; + lfc.Line = this->FileLine; + bt.push_back(lfc); + msg << "uninitialized variable \'" << var << "\'"; + this->Makefile->GetCMakeInstance()->IssueMessage(cmake::AUTHOR_WARNING, + msg.str().c_str(), bt); + } + } return 0; } if (this->EscapeQuotes && value) @@ -319,6 +343,8 @@ void cmCommandArgumentParserHelper::Error(const char* str) void cmCommandArgumentParserHelper::SetMakefile(const cmMakefile* mf) { this->Makefile = mf; + this->WarnUninitialized = mf->GetCMakeInstance()->GetWarnUninitialized(); + this->CheckSystemVars = mf->GetCMakeInstance()->GetCheckSystemVars(); } void cmCommandArgumentParserHelper::SetResult(const char* value) diff --git a/Source/cmCommandArgumentParserHelper.h b/Source/cmCommandArgumentParserHelper.h index 62cbc80..a211e95 100644 --- a/Source/cmCommandArgumentParserHelper.h +++ b/Source/cmCommandArgumentParserHelper.h @@ -96,6 +96,8 @@ private: const cmMakefile* Makefile; std::string Result; const char* FileName; + bool WarnUninitialized; + bool CheckSystemVars; long FileLine; bool EscapeQuotes; std::string ErrorString; diff --git a/Source/cmCoreTryCompile.cxx b/Source/cmCoreTryCompile.cxx index b8a0c95..30356c8 100644 --- a/Source/cmCoreTryCompile.cxx +++ b/Source/cmCoreTryCompile.cxx @@ -245,10 +245,8 @@ int cmCoreTryCompile::TryCompileCode(std::vector<std::string> const& argv) CMAKE_TRY_COMPILE_OSX_ARCHITECTURE first to i386 and then to ppc to have the tests run for each specific architecture. Since cmLocalGenerator doesn't allow building for "the other" - architecture only via CMAKE_OSX_ARCHITECTURES,use to CMAKE_DO_TRY_COMPILE - to enforce it for this case here. + architecture only via CMAKE_OSX_ARCHITECTURES. */ - cmakeFlags.push_back("-DCMAKE_DO_TRY_COMPILE=TRUE"); if(this->Makefile->GetDefinition("CMAKE_TRY_COMPILE_OSX_ARCHITECTURES")!=0) { std::string flag="-DCMAKE_OSX_ARCHITECTURES="; diff --git a/Source/cmDefinitions.cxx b/Source/cmDefinitions.cxx index 2d40718..9d28700 100644 --- a/Source/cmDefinitions.cxx +++ b/Source/cmDefinitions.cxx @@ -85,6 +85,22 @@ const char* cmDefinitions::Set(const char* key, const char* value) } //---------------------------------------------------------------------------- +std::set<cmStdString> cmDefinitions::LocalKeys() const +{ + std::set<cmStdString> keys; + // Consider local definitions. + for(MapType::const_iterator mi = this->Map.begin(); + mi != this->Map.end(); ++mi) + { + if (mi->second.Exists) + { + keys.insert(mi->first); + } + } + return keys; +} + +//---------------------------------------------------------------------------- cmDefinitions cmDefinitions::Closure() const { return cmDefinitions(ClosureTag(), this); diff --git a/Source/cmDefinitions.h b/Source/cmDefinitions.h index e385e88..4834d84 100644 --- a/Source/cmDefinitions.h +++ b/Source/cmDefinitions.h @@ -40,6 +40,9 @@ public: /** Set (or unset if null) a value associated with a key. */ const char* Set(const char* key, const char* value); + /** Get the set of all local keys. */ + std::set<cmStdString> LocalKeys() const; + /** Compute the closure of all defined keys with values. This flattens the scope. The result has no parent. */ cmDefinitions Closure() const; diff --git a/Source/cmFunctionCommand.cxx b/Source/cmFunctionCommand.cxx index b05642e..ec4fd16 100644 --- a/Source/cmFunctionCommand.cxx +++ b/Source/cmFunctionCommand.cxx @@ -113,6 +113,7 @@ bool cmFunctionHelperCommand::InvokeInitialPass cmOStringStream strStream; strStream << expandedArgs.size(); this->Makefile->AddDefinition("ARGC",strStream.str().c_str()); + this->Makefile->MarkVariableAsUsed("ARGC"); // set the values for ARGV0 ARGV1 ... for (unsigned int t = 0; t < expandedArgs.size(); ++t) @@ -121,6 +122,7 @@ bool cmFunctionHelperCommand::InvokeInitialPass tmpStream << "ARGV" << t; this->Makefile->AddDefinition(tmpStream.str().c_str(), expandedArgs[t].c_str()); + this->Makefile->MarkVariableAsUsed(tmpStream.str().c_str()); } // define the formal arguments @@ -153,7 +155,9 @@ bool cmFunctionHelperCommand::InvokeInitialPass cnt ++; } this->Makefile->AddDefinition("ARGV", argvDef.c_str()); + this->Makefile->MarkVariableAsUsed("ARGV"); this->Makefile->AddDefinition("ARGN", argnDef.c_str()); + this->Makefile->MarkVariableAsUsed("ARGN"); // Invoke all the functions that were collected in the block. // for each function diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index c6d05b0..e4c169a 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -896,6 +896,8 @@ void cmGlobalGenerator::Generate() } this->CMakeInstance->UpdateProgress("Generating done", -1); + + this->CMakeInstance->RunCheckForUnusedVariables("generation"); } //---------------------------------------------------------------------------- diff --git a/Source/cmMacroCommand.cxx b/Source/cmMacroCommand.cxx index 497949a..774f32b 100644 --- a/Source/cmMacroCommand.cxx +++ b/Source/cmMacroCommand.cxx @@ -31,6 +31,7 @@ public: // we must copy when we clone newC->Args = this->Args; newC->Functions = this->Functions; + newC->FilePath = this->FilePath; newC->Policies = this->Policies; return newC; } @@ -78,6 +79,7 @@ public: std::vector<std::string> Args; std::vector<cmListFileFunction> Functions; cmPolicies::PolicyMap Policies; + std::string FilePath; }; @@ -121,7 +123,10 @@ bool cmMacroHelperCommand::InvokeInitialPass std::string argnDef; bool argnDefInitialized = false; bool argvDefInitialized = false; - + if( this->Functions.size()) + { + this->FilePath = this->Functions[0].FilePath; + } // Invoke all the functions that were collected in the block. cmListFileFunction newLFF; // for each function @@ -135,10 +140,13 @@ bool cmMacroHelperCommand::InvokeInitialPass newLFF.Line = this->Functions[c].Line; // for each argument of the current function - for (std::vector<cmListFileArgument>::const_iterator k = + for (std::vector<cmListFileArgument>::iterator k = this->Functions[c].Arguments.begin(); k != this->Functions[c].Arguments.end(); ++k) { + // Set the FilePath on the arguments to match the function since it is + // not stored and the original values may be freed + k->FilePath = this->FilePath.c_str(); tmps = k->Value; // replace formal arguments for (unsigned int j = 1; j < this->Args.size(); ++j) diff --git a/Source/cmMacroCommand.h b/Source/cmMacroCommand.h index 3457da2..93e10b2 100644 --- a/Source/cmMacroCommand.h +++ b/Source/cmMacroCommand.h @@ -112,7 +112,6 @@ public: "policies inside macros." ; } - cmTypeMacro(cmMacroCommand, cmCommand); }; diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index c64053a..a97ec10 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -43,12 +43,22 @@ class cmMakefile::Internals { public: std::stack<cmDefinitions, std::list<cmDefinitions> > VarStack; + std::stack<std::set<cmStdString> > VarInitStack; + std::stack<std::set<cmStdString> > VarUsageStack; }; // default is not to be building executables cmMakefile::cmMakefile(): Internal(new Internals) { - this->Internal->VarStack.push(cmDefinitions()); + const cmDefinitions& defs = cmDefinitions(); + const std::set<cmStdString> globalKeys = defs.LocalKeys(); + this->Internal->VarStack.push(defs); + this->Internal->VarInitStack.push(globalKeys); + this->Internal->VarUsageStack.push(globalKeys); + + // Initialize these first since AddDefaultDefinitions calls AddDefinition + this->WarnUnused = false; + this->CheckSystemVars = false; // Setup the default include file regular expression (match everything). this->IncludeFileRegularExpression = "^.*$"; @@ -91,6 +101,8 @@ cmMakefile::cmMakefile(): Internal(new Internals) cmMakefile::cmMakefile(const cmMakefile& mf): Internal(new Internals) { this->Internal->VarStack.push(mf.Internal->VarStack.top().Closure()); + this->Internal->VarInitStack.push(mf.Internal->VarInitStack.top()); + this->Internal->VarUsageStack.push(mf.Internal->VarUsageStack.top()); this->Prefix = mf.Prefix; this->AuxSourceDirectories = mf.AuxSourceDirectories; @@ -128,8 +140,10 @@ cmMakefile::cmMakefile(const cmMakefile& mf): Internal(new Internals) this->SubDirectoryOrder = mf.SubDirectoryOrder; this->Properties = mf.Properties; this->PreOrder = mf.PreOrder; - this->ListFileStack = mf.ListFileStack; + this->WarnUnused = mf.WarnUnused; this->Initialize(); + this->CheckSystemVars = mf.CheckSystemVars; + this->ListFileStack = mf.ListFileStack; } //---------------------------------------------------------------------------- @@ -570,6 +584,7 @@ bool cmMakefile::ReadListFile(const char* filename_in, std::string currentFile = this->GetSafeDefinition("CMAKE_CURRENT_LIST_FILE"); this->AddDefinition("CMAKE_PARENT_LIST_FILE", filename_in); + this->MarkVariableAsUsed("CMAKE_PARENT_LIST_FILE"); const char* external = 0; std::string external_abs; @@ -610,6 +625,7 @@ bool cmMakefile::ReadListFile(const char* filename_in, } this->AddDefinition("CMAKE_CURRENT_LIST_FILE", filenametoread); + this->MarkVariableAsUsed("CMAKE_CURRENT_LIST_FILE"); // try to see if the list file is the top most // list file for a project, and if it is, then it @@ -642,7 +658,9 @@ bool cmMakefile::ReadListFile(const char* filename_in, *fullPath = ""; } this->AddDefinition("CMAKE_PARENT_LIST_FILE", currentParentFile.c_str()); + this->MarkVariableAsUsed("CMAKE_PARENT_LIST_FILE"); this->AddDefinition("CMAKE_CURRENT_LIST_FILE", currentFile.c_str()); + this->MarkVariableAsUsed("CMAKE_CURRENT_LIST_FILE"); return false; } // add this list file to the list of dependencies @@ -682,11 +700,16 @@ bool cmMakefile::ReadListFile(const char* filename_in, } this->AddDefinition("CMAKE_PARENT_LIST_FILE", currentParentFile.c_str()); + this->MarkVariableAsUsed("CMAKE_PARENT_LIST_FILE"); this->AddDefinition("CMAKE_CURRENT_LIST_FILE", currentFile.c_str()); + this->MarkVariableAsUsed("CMAKE_CURRENT_LIST_FILE"); // pop the listfile off the stack this->ListFileStack.pop_back(); + // Check for unused variables + this->CheckForUnusedVariables(); + return true; } @@ -752,6 +775,8 @@ void cmMakefile::SetLocalGenerator(cmLocalGenerator* lg) this->AddSourceGroup("Resources", "\\.plist$"); #endif + this->WarnUnused = this->GetCMakeInstance()->GetWarnUnused(); + this->CheckSystemVars = this->GetCMakeInstance()->GetCheckSystemVars(); } bool cmMakefile::NeedBackwardsCompatibility(unsigned int major, @@ -1611,6 +1636,13 @@ void cmMakefile::AddDefinition(const char* name, const char* value) #endif this->Internal->VarStack.top().Set(name, value); + if (this->Internal->VarUsageStack.size() && + this->VariableInitialized(name)) + { + this->CheckForUnused("changing definition", name); + this->Internal->VarUsageStack.top().erase(name); + } + this->Internal->VarInitStack.top().insert(name); #ifdef CMAKE_BUILD_WITH_CMAKE cmVariableWatch* vv = this->GetVariableWatch(); @@ -1675,6 +1707,13 @@ void cmMakefile::AddCacheDefinition(const char* name, const char* value, void cmMakefile::AddDefinition(const char* name, bool value) { this->Internal->VarStack.top().Set(name, value? "ON" : "OFF"); + if (this->Internal->VarUsageStack.size() && + this->VariableInitialized(name)) + { + this->CheckForUnused("changing definition", name); + this->Internal->VarUsageStack.top().erase(name); + } + this->Internal->VarInitStack.top().insert(name); #ifdef CMAKE_BUILD_WITH_CMAKE cmVariableWatch* vv = this->GetVariableWatch(); if ( vv ) @@ -1685,9 +1724,90 @@ void cmMakefile::AddDefinition(const char* name, bool value) #endif } +void cmMakefile::CheckForUnusedVariables() const +{ + const cmDefinitions& defs = this->Internal->VarStack.top(); + const std::set<cmStdString>& locals = defs.LocalKeys(); + std::set<cmStdString>::const_iterator it = locals.begin(); + for (; it != locals.end(); ++it) + { + this->CheckForUnused("out of scope", it->c_str()); + } +} + +void cmMakefile::MarkVariableAsUsed(const char* var) +{ + this->Internal->VarUsageStack.top().insert(var); +} + +bool cmMakefile::VariableInitialized(const char* var) const +{ + if(this->Internal->VarInitStack.top().find(var) != + this->Internal->VarInitStack.top().end()) + { + return true; + } + return false; +} + +bool cmMakefile::VariableUsed(const char* var) const +{ + if(this->Internal->VarUsageStack.top().find(var) != + this->Internal->VarUsageStack.top().end()) + { + return true; + } + return false; +} + +void cmMakefile::CheckForUnused(const char* reason, const char* name) const +{ + if (this->WarnUnused && !this->VariableUsed(name)) + { + cmStdString path; + cmListFileBacktrace bt; + if (this->CallStack.size()) + { + const cmListFileContext* file = this->CallStack.back().Context; + bt.push_back(*file); + path = file->FilePath.c_str(); + } + else + { + path = this->GetStartDirectory(); + path += "/CMakeLists.txt"; + cmListFileContext lfc; + lfc.FilePath = path; + lfc.Line = 0; + bt.push_back(lfc); + } + if (this->CheckSystemVars || + cmSystemTools::IsSubDirectory(path.c_str(), + this->GetHomeDirectory()) || + (cmSystemTools::IsSubDirectory(path.c_str(), + this->GetHomeOutputDirectory()) && + !cmSystemTools::IsSubDirectory(path.c_str(), + cmake::GetCMakeFilesDirectory()))) + { + cmOStringStream msg; + msg << "unused variable (" << reason << ") \'" << name << "\'"; + this->GetCMakeInstance()->IssueMessage(cmake::AUTHOR_WARNING, + msg.str().c_str(), + bt); + } + } +} + void cmMakefile::RemoveDefinition(const char* name) { this->Internal->VarStack.top().Set(name, 0); + if (this->Internal->VarUsageStack.size() && + this->VariableInitialized(name)) + { + this->CheckForUnused("unsetting", name); + this->Internal->VarUsageStack.top().erase(name); + } + this->Internal->VarInitStack.top().insert(name); #ifdef CMAKE_BUILD_WITH_CMAKE cmVariableWatch* vv = this->GetVariableWatch(); if ( vv ) @@ -2066,6 +2186,10 @@ const char* cmMakefile::GetDefinition(const char* name) const RecordPropertyAccess(name,cmProperty::VARIABLE); } #endif + if (this->WarnUnused) + { + this->Internal->VarUsageStack.top().insert(name); + } const char* def = this->Internal->VarStack.top().Get(name); if(!def) { @@ -2703,6 +2827,11 @@ int cmMakefile::TryCompile(const char *srcdir, const char *bindir, // if cmake args were provided then pass them in if (cmakeArgs) { + // FIXME: Workaround to ignore unused CLI variables until the + // 'ArgumentExpansion' test succeeds with CMAKE_STRICT on + cm.SetWarnUnusedCli(false); + //cm.SetArgs(*cmakeArgs, true); + cm.SetCacheArgs(*cmakeArgs); } // to save time we pass the EnableLanguage info directly @@ -3321,12 +3450,48 @@ std::string cmMakefile::GetListFileStack() void cmMakefile::PushScope() { cmDefinitions* parent = &this->Internal->VarStack.top(); + const std::set<cmStdString>& init = this->Internal->VarInitStack.top(); + const std::set<cmStdString>& usage = this->Internal->VarUsageStack.top(); this->Internal->VarStack.push(cmDefinitions(parent)); + this->Internal->VarInitStack.push(init); + this->Internal->VarUsageStack.push(usage); } void cmMakefile::PopScope() { + cmDefinitions* current = &this->Internal->VarStack.top(); + std::set<cmStdString> init = this->Internal->VarInitStack.top(); + std::set<cmStdString> usage = this->Internal->VarUsageStack.top(); + const std::set<cmStdString>& locals = current->LocalKeys(); + // Remove initialization and usage information for variables in the local + // scope. + std::set<cmStdString>::const_iterator it = locals.begin(); + for (; it != locals.end(); ++it) + { + init.erase(*it); + if (!this->VariableUsed(it->c_str())) + { + this->CheckForUnused("out of scope", it->c_str()); + } + else + { + usage.erase(*it); + } + } this->Internal->VarStack.pop(); + this->Internal->VarInitStack.pop(); + this->Internal->VarUsageStack.pop(); + // Push initialization and usage up to the parent scope. + it = init.begin(); + for (; it != init.end(); ++it) + { + this->Internal->VarInitStack.top().insert(*it); + } + it = usage.begin(); + for (; it != usage.end(); ++it) + { + this->Internal->VarUsageStack.top().insert(*it); + } } void cmMakefile::RaiseScope(const char *var, const char *varDef) @@ -3351,7 +3516,14 @@ void cmMakefile::RaiseScope(const char *var, const char *varDef) // directory's scope was initialized by the closure of the parent // scope, so we do not need to localize the definition first. cmMakefile* parent = plg->GetMakefile(); - parent->Internal->VarStack.top().Set(var, varDef); + if (varDef) + { + parent->AddDefinition(var, varDef); + } + else + { + parent->RemoveDefinition(var); + } } else { diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index 8b8a3f8..837a352 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -61,6 +61,14 @@ public: unsigned int GetCacheMajorVersion(); unsigned int GetCacheMinorVersion(); + /* Check for unused variables in this scope */ + void CheckForUnusedVariables() const; + /* Mark a variable as used */ + void MarkVariableAsUsed(const char* var); + /* return true if a variable has been initialized */ + bool VariableInitialized(const char* ) const; + /* return true if a variable has been used */ + bool VariableUsed(const char* ) const; /** Return whether compatibility features needed for a version of the cache or lower should be enabled. */ bool NeedCacheCompatibility(int major, int minor); @@ -836,7 +844,10 @@ public: protected: // add link libraries and directories to the target void AddGlobalLinkInformation(const char* name, cmTarget& target); - + + // Check for a an unused variable + void CheckForUnused(const char* reason, const char* name) const; + std::string Prefix; std::vector<std::string> AuxSourceDirectories; // @@ -929,6 +940,10 @@ private: // should this makefile be processed before or after processing the parent bool PreOrder; + // Unused variable flags + bool WarnUnused; + bool CheckSystemVars; + // stack of list files being read std::deque<cmStdString> ListFileStack; diff --git a/Source/cmStringCommand.cxx b/Source/cmStringCommand.cxx index 19f5c0f..bab08fe 100644 --- a/Source/cmStringCommand.cxx +++ b/Source/cmStringCommand.cxx @@ -482,6 +482,7 @@ void cmStringCommand::ClearMatches(cmMakefile* mf) char name[128]; sprintf(name, "CMAKE_MATCH_%d", i); mf->AddDefinition(name, ""); + mf->MarkVariableAsUsed(name); } } @@ -493,6 +494,7 @@ void cmStringCommand::StoreMatches(cmMakefile* mf,cmsys::RegularExpression& re) char name[128]; sprintf(name, "CMAKE_MATCH_%d", i); mf->AddDefinition(name, re.match(i).c_str()); + mf->MarkVariableAsUsed(name); } } diff --git a/Source/cmake.cxx b/Source/cmake.cxx index 1e3b018..3aa4aa0 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -137,9 +137,20 @@ void cmNeedBackwardsCompatibility(const std::string& variable, #endif } +void cmWarnUnusedCliWarning(const std::string& variable, + int, void* ctx, const char*, const cmMakefile*) +{ + cmake* cm = reinterpret_cast<cmake*>(ctx); + cm->MarkCliAsUsed(variable); +} + cmake::cmake() { this->Trace = false; + this->WarnUninitialized = false; + this->WarnUnused = false; + this->WarnUnusedCli = true; + this->CheckSystemVars = false; this->SuppressDevWarnings = false; this->DoSuppressDevWarnings = false; this->DebugOutput = false; @@ -367,6 +378,10 @@ bool cmake::SetCacheArgs(const std::vector<std::string>& args) { this->CacheManager->AddCacheEntry(var.c_str(), value.c_str(), "No help, variable specified on the command line.", type); + if(this->WarnUnusedCli) + { + this->WatchUnusedCli(var.c_str()); + } } else { @@ -509,9 +524,10 @@ void cmake::ReadListFile(const char *path) } // Parse the args -void cmake::SetArgs(const std::vector<std::string>& args) +void cmake::SetArgs(const std::vector<std::string>& args, + bool directoriesSetBefore) { - bool directoriesSet = false; + bool directoriesSet = directoriesSetBefore; for(unsigned int i=1; i < args.size(); ++i) { std::string arg = args[i]; @@ -613,6 +629,28 @@ void cmake::SetArgs(const std::vector<std::string>& args) std::cout << "Running with trace output on.\n"; this->SetTrace(true); } + else if(arg.find("--warn-uninitialized",0) == 0) + { + std::cout << "Warn about uninitialized values.\n"; + this->SetWarnUninitialized(true); + } + else if(arg.find("--warn-unused-vars",0) == 0) + { + std::cout << "Finding unused variables.\n"; + this->SetWarnUnused(true); + } + else if(arg.find("--no-warn-unused-cli",0) == 0) + { + std::cout << "Not searching for unused variables given on the " << + "command line.\n"; + this->SetWarnUnusedCli(false); + } + else if(arg.find("--check-system-vars",0) == 0) + { + std::cout << "Also check system files when warning about unused and " << + "uninitialized variables.\n"; + this->SetCheckSystemVars(true); + } else if(arg.find("-G",0) == 0) { std::string value = arg.substr(2); @@ -2272,6 +2310,11 @@ int cmake::Run(const std::vector<std::string>& args, bool noconfigure) std::string oldstartoutputdir = this->GetStartOutputDirectory(); this->SetStartDirectory(this->GetHomeDirectory()); this->SetStartOutputDirectory(this->GetHomeOutputDirectory()); + const bool warncli = this->WarnUnusedCli; + if (!this->ScriptMode) + { + this->WarnUnusedCli = false; + } int ret = this->Configure(); if (ret || this->ScriptMode) { @@ -2293,6 +2336,7 @@ int cmake::Run(const std::vector<std::string>& args, bool noconfigure) #endif return ret; } + this->WarnUnusedCli = warncli; ret = this->Generate(); std::string message = "Build files have been written to: "; message += this->GetHomeOutputDirectory(); @@ -2835,6 +2879,11 @@ const char* cmake::GetCPackCommand() return this->CPackCommand.c_str(); } +void cmake::MarkCliAsUsed(const std::string& variable) +{ + this->UsedCliVariables[variable] = true; +} + void cmake::GenerateGraphViz(const char* fileName) const { cmGeneratedFileStream str(fileName); @@ -4477,3 +4526,32 @@ int cmake::Build(const std::string& dir, config.c_str(), clean, false, 0, true, 0, nativeOptions); } + +void cmake::WatchUnusedCli(const char* var) +{ +#ifdef CMAKE_BUILD_WITH_CMAKE + this->VariableWatch->AddWatch(var, cmWarnUnusedCliWarning, this); + this->UsedCliVariables[var] = false; +#endif +} + +void cmake::RunCheckForUnusedVariables(const std::string& reason) const +{ +#ifdef CMAKE_BUILD_WITH_CMAKE + if(this->WarnUnusedCli) + { + std::map<std::string, bool>::const_iterator it; + for(it = this->UsedCliVariables.begin(); + it != this->UsedCliVariables.end(); ++it) + { + if(!it->second) + { + std::string message = "CMake Warning: The variable, '" + it->first + + "', given on the command line, was not used during the " + reason + + "."; + cmSystemTools::Message(message.c_str()); + } + } + } +#endif +} diff --git a/Source/cmake.h b/Source/cmake.h index 8312795..b132164 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -212,7 +212,8 @@ class cmake bool CommandExists(const char* name) const; ///! Parse command line arguments - void SetArgs(const std::vector<std::string>&); + void SetArgs(const std::vector<std::string>&, + bool directoriesSetBefore = false); ///! Is this cmake running as a result of a TRY_COMPILE command bool GetIsInTryCompile() { return this->InTryCompile; } @@ -306,6 +307,17 @@ class cmake // Do we want trace output during the cmake run. bool GetTrace() { return this->Trace;} void SetTrace(bool b) { this->Trace = b;} + bool GetWarnUninitialized() { return this->WarnUninitialized;} + void SetWarnUninitialized(bool b) { this->WarnUninitialized = b;} + bool GetWarnUnused() { return this->WarnUnused;} + void SetWarnUnused(bool b) { this->WarnUnused = b;} + bool GetWarnUnusedCli() { return this->WarnUnusedCli;} + void SetWarnUnusedCli(bool b) { this->WarnUnusedCli = b;} + bool GetCheckSystemVars() { return this->CheckSystemVars;} + void SetCheckSystemVars(bool b) { this->CheckSystemVars = b;} + + void MarkCliAsUsed(const std::string& variable); + // Define a property void DefineProperty(const char *name, cmProperty::ScopeType scope, const char *ShortDescription, @@ -351,6 +363,9 @@ class cmake const std::string& config, const std::vector<std::string>& nativeOptions, bool clean); + + void WatchUnusedCli(const char* var); + void RunCheckForUnusedVariables(const std::string& reason) const; protected: void InitializeProperties(); int HandleDeleteCacheVariables(const char* var); @@ -443,6 +458,11 @@ private: bool ScriptMode; bool DebugOutput; bool Trace; + bool WarnUninitialized; + bool WarnUnused; + bool WarnUnusedCli; + bool CheckSystemVars; + std::map<std::string, bool> UsedCliVariables; std::string CMakeEditCommand; std::string CMakeCommand; std::string CXXEnvironment; diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx index ddff71d..a51673c 100644 --- a/Source/cmakemain.cxx +++ b/Source/cmakemain.cxx @@ -120,6 +120,17 @@ static const char * cmDocumentationOptions[][3] = {"--trace", "Put cmake in trace mode.", "Print a trace of all calls made and from where with " "message(send_error ) calls."}, + {"--warn-uninitialized", "Warn about uninitialized values.", + "Print a warning when an uninitialized variable is used."}, + {"--warn-unused-vars", "Warn about unused variables.", + "Find variables that are declared or set, but not used."}, + {"--no-warn-unused-cli", "Don't warn about command line options.", + "Don't find variables that are declared on the command line, but not " + "used."}, + {"--check-system-vars", "Find problems with variable usage in system " + "files.", "Normally, unused and uninitialized variables are searched for " + "only in CMAKE_SOURCE_DIR and CMAKE_BINARY_DIR. This flag tells CMake to " + "warn about other files as well."}, {"--help-command cmd [file]", "Print help for a single command and exit.", "Full documentation specific to the given command is displayed. " "If a file is specified, the documentation is written into and the output " diff --git a/Tests/ArgumentExpansion/CMakeLists.txt b/Tests/ArgumentExpansion/CMakeLists.txt new file mode 100644 index 0000000..6201706 --- /dev/null +++ b/Tests/ArgumentExpansion/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 2.8) + +project(ArgumentExpansion) + +function (argument_tester expected expected_len) + list(LENGTH ARGN argn_len) + list(LENGTH ${expected} expected_received_len) + + if (NOT ${expected_received_len} EQUAL ${expected_len}) + message(STATUS "Unexpected: Expanding expected values isn't working") + endif (NOT ${expected_received_len} EQUAL ${expected_len}) + + if (${argn_len} EQUAL ${expected_len}) + set(i 0) + while (i LESS ${argn_len}) + list(GET ARGN ${i} argn_value) + list(GET ${expected} ${i} expected_value) + + if (NOT ${argn_value} STREQUAL ${expected_value}) + message(STATUS "Unexpected: Argument ${i} doesn't match") + message(STATUS " Expected: ${expected_value}") + message(STATUS " Received: ${argn_value}") + endif (NOT ${argn_value} STREQUAL ${expected_value}) + + math(EXPR i "${i} + 1") + endwhile (i LESS ${argn_len}) + else (${argn_len} EQUAL ${expected_len}) + message(STATUS "Unexpected: Lengths of arguments don't match") + message(STATUS " Expected: ${expected_len}") + message(STATUS " Received: ${argn_len}") + endif (${argn_len} EQUAL ${expected_len}) +endfunction (argument_tester expected) + +set(empty_test) +message(STATUS "Test: Empty arguments") +argument_tester(empty_test 0 ${empty_test}) + +set(single_arg_test + "single arg") +message(STATUS "Test: Single argument") +argument_tester(single_arg_test 1 ${single_arg_test}) + +set(multiple_arg_test + "first arg" + "second arg") +message(STATUS "Test: Multiple arguments") +argument_tester(multiple_arg_test 2 ${multiple_arg_test}) + +set(nested_list_arg_test + "${multiple_arg_test}" + "first arg" + "second arg") +message(STATUS "Test: Nested list argument") +argument_tester(nested_list_arg_test 3 ${nested_list_arg_test}) + +set(semicolon_arg_test + "pre\;post") +message(STATUS "Test: Semicolon argument") +argument_tester(semicolon_arg_test 1 ${semicolon_arg_test}) diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 5e88b5c..5471052 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -371,6 +371,21 @@ ${CMake_BINARY_DIR}/bin/cmake -DVERSION=master -P ${CMake_SOURCE_DIR}/Utilities/ ) LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/CustComDepend") + ADD_TEST(ArgumentExpansion ${CMAKE_CTEST_COMMAND} + --build-and-test + "${CMake_SOURCE_DIR}/Tests/ArgumentExpansion" + "${CMake_BINARY_DIR}/Tests/ArgumentExpansion" + --build-generator ${CMAKE_TEST_GENERATOR} + --build-project ArgumentExpansion + --build-makeprogram ${CMAKE_TEST_MAKEPROGRAM} + --build-exe-dir "${CMake_BINARY_DIR}/Tests/ArgumentExpansion/bin" + ) + IF(CMAKE_STRICT) + SET_TESTS_PROPERTIES(ArgumentExpansion PROPERTIES + FAIL_REGULAR_EXPRESSION "Unexpected: ") + ENDIF(CMAKE_STRICT) + LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/ArgumentExpansion") + ADD_TEST(CustomCommand ${CMAKE_CTEST_COMMAND} --build-and-test "${CMake_SOURCE_DIR}/Tests/CustomCommand" @@ -1077,6 +1092,77 @@ ${CMake_BINARY_DIR}/bin/cmake -DVERSION=master -P ${CMake_SOURCE_DIR}/Utilities/ LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/BundleGeneratorTest") ENDIF(APPLE AND CTEST_TEST_CPACK) + ADD_TEST(WarnUnusedUnusedViaSet ${CMAKE_CTEST_COMMAND} + --build-and-test + "${CMake_SOURCE_DIR}/Tests/VariableUnusedViaSet" + "${CMake_BINARY_DIR}/Tests/WarnUnusedUnusedViaSet" + --build-generator ${CMAKE_TEST_GENERATOR} + --build-makeprogram ${CMAKE_TEST_MAKEPROGRAM} + --build-noclean + --build-project WarnUnusedUnusedViaSet + --build-options "--warn-unused-vars") + SET_TESTS_PROPERTIES(WarnUnusedUnusedViaSet PROPERTIES + PASS_REGULAR_EXPRESSION "unused variable \\(changing definition\\) 'UNUSED_VARIABLE'") + SET_TESTS_PROPERTIES(WarnUnusedUnusedViaSet PROPERTIES + FAIL_REGULAR_EXPRESSION "unused variable \\(unsetting\\) 'UNUSED_VARIABLE'") + LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/WarnUnusedUnusedViaSet") + + ADD_TEST(WarnUnusedUnusedViaUnset ${CMAKE_CTEST_COMMAND} + --build-and-test + "${CMake_SOURCE_DIR}/Tests/VariableUnusedViaUnset" + "${CMake_BINARY_DIR}/Tests/WarnUnusedUnusedViaUnset" + --build-generator ${CMAKE_TEST_GENERATOR} + --build-makeprogram ${CMAKE_TEST_MAKEPROGRAM} + --build-noclean + --build-project WarnUnusedUnusedViaUnset + --build-options "--warn-unused-vars") + SET_TESTS_PROPERTIES(WarnUnusedUnusedViaUnset PROPERTIES + PASS_REGULAR_EXPRESSION "CMake Warning .*:7 \\(set\\):") + SET_TESTS_PROPERTIES(WarnUnusedUnusedViaUnset PROPERTIES + FAIL_REGULAR_EXPRESSION "CMake Warning .*:5 \\(set\\):") + LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/WarnUnusedUnusedViaUnset") + + ADD_TEST(WarnUnusedCliUnused ${CMAKE_CTEST_COMMAND} + --build-and-test + "${CMake_SOURCE_DIR}/Tests/VariableUsage" + "${CMake_BINARY_DIR}/Tests/WarnUnusedCliUnused" + --build-generator ${CMAKE_TEST_GENERATOR} + --build-makeprogram ${CMAKE_TEST_MAKEPROGRAM} + --build-noclean + --build-project WarnUnusedCliUnused + --build-options "-DUNUSED_CLI_VARIABLE=Unused") + SET_TESTS_PROPERTIES(WarnUnusedCliUnused PROPERTIES + PASS_REGULAR_EXPRESSION "CMake Warning: The variable, 'UNUSED_CLI_VARIABLE'") + LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/WarnUnusedCliUnused") + + ADD_TEST(WarnUnusedCliUsed ${CMAKE_CTEST_COMMAND} + --build-and-test + "${CMake_SOURCE_DIR}/Tests/VariableUsage" + "${CMake_BINARY_DIR}/Tests/WarnUnusedCliUsed" + --build-generator ${CMAKE_TEST_GENERATOR} + --build-makeprogram ${CMAKE_TEST_MAKEPROGRAM} + --build-noclean + --build-project WarnUnusedCliUsed + --build-options "-DUSED_VARIABLE=Usage proven") + SET_TESTS_PROPERTIES(WarnUnusedCliUsed PROPERTIES + PASS_REGULAR_EXPRESSION "Usage proven") + SET_TESTS_PROPERTIES(WarnUnusedCliUsed PROPERTIES + FAIL_REGULAR_EXPRESSION "CMake Warning: The variable, 'USED_VARIABLE'") + LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/WarnUnusedCliUsed") + + ADD_TEST(WarnUninitialized ${CMAKE_CTEST_COMMAND} + --build-and-test + "${CMake_SOURCE_DIR}/Tests/VariableUsage" + "${CMake_BINARY_DIR}/Tests/WarnUninitialized" + --build-generator ${CMAKE_TEST_GENERATOR} + --build-makeprogram ${CMAKE_TEST_MAKEPROGRAM} + --build-noclean + --build-project WarnUninitialized + --build-options "--warn-uninitialized") + SET_TESTS_PROPERTIES(WarnUninitialized PROPERTIES + PASS_REGULAR_EXPRESSION "uninitialized variable 'USED_VARIABLE'") + LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/WarnUninitialized") + # Make sure CTest can handle a test with no newline in output. ADD_TEST(CTest.NoNewline ${CMAKE_CMAKE_COMMAND} -E echo_append "This line has no newline!") diff --git a/Tests/VariableUnusedViaSet/CMakeLists.txt b/Tests/VariableUnusedViaSet/CMakeLists.txt new file mode 100644 index 0000000..0123ab2 --- /dev/null +++ b/Tests/VariableUnusedViaSet/CMakeLists.txt @@ -0,0 +1,4 @@ +set(UNUSED_VARIABLE) +# Warning should occur here +set(UNUSED_VARIABLE "Usage") +message(STATUS "${UNUSED_VARIABLE}") diff --git a/Tests/VariableUnusedViaUnset/CMakeLists.txt b/Tests/VariableUnusedViaUnset/CMakeLists.txt new file mode 100644 index 0000000..4b4031d --- /dev/null +++ b/Tests/VariableUnusedViaUnset/CMakeLists.txt @@ -0,0 +1,8 @@ +# NOTE: Changing lines in here changes the test results since the first +# instance shouldn't warn, but the second should and they have the same message + +# A warning should NOT be issued for this line: +set(UNUSED_VARIABLE) +# Warning should occur here: +set(UNUSED_VARIABLE) +message(STATUS "${UNUSED_VARIABLE}") diff --git a/Tests/VariableUsage/CMakeLists.txt b/Tests/VariableUsage/CMakeLists.txt new file mode 100644 index 0000000..4da1f56 --- /dev/null +++ b/Tests/VariableUsage/CMakeLists.txt @@ -0,0 +1 @@ +message(STATUS "${USED_VARIABLE}") |