summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Modules/CMakeDetermineCompilerABI.cmake18
-rw-r--r--Modules/CMakeDetermineCompilerId.cmake4
-rw-r--r--Source/CursesDialog/cmCursesMainForm.cxx1
-rw-r--r--Source/QtDialog/CMakeSetupDialog.cxx16
-rw-r--r--Source/QtDialog/CMakeSetupDialog.h2
-rw-r--r--Source/QtDialog/QCMake.cxx18
-rw-r--r--Source/QtDialog/QCMake.h7
-rw-r--r--Source/cmCommandArgumentParserHelper.cxx28
-rw-r--r--Source/cmCommandArgumentParserHelper.h2
-rw-r--r--Source/cmCoreTryCompile.cxx4
-rw-r--r--Source/cmDefinitions.cxx16
-rw-r--r--Source/cmDefinitions.h3
-rw-r--r--Source/cmFunctionCommand.cxx4
-rw-r--r--Source/cmGlobalGenerator.cxx2
-rw-r--r--Source/cmMacroCommand.cxx12
-rw-r--r--Source/cmMacroCommand.h1
-rw-r--r--Source/cmMakefile.cxx202
-rw-r--r--Source/cmMakefile.h17
-rw-r--r--Source/cmStringCommand.cxx2
-rw-r--r--Source/cmake.cxx89
-rw-r--r--Source/cmake.h23
-rw-r--r--Source/cmakemain.cxx11
-rw-r--r--Tests/ArgumentExpansion/CMakeLists.txt60
-rw-r--r--Tests/CMakeLists.txt84
-rw-r--r--Tests/VariableUnusedViaSet/CMakeLists.txt4
-rw-r--r--Tests/VariableUnusedViaUnset/CMakeLists.txt8
-rw-r--r--Tests/VariableUsage/CMakeLists.txt1
27 files changed, 621 insertions, 18 deletions
diff --git a/Modules/CMakeDetermineCompilerABI.cmake b/Modules/CMakeDetermineCompilerABI.cmake
index ed0b6fb..7f918e6 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/CursesDialog/cmCursesMainForm.cxx b/Source/CursesDialog/cmCursesMainForm.cxx
index c93f353..e1876b9 100644
--- a/Source/CursesDialog/cmCursesMainForm.cxx
+++ b/Source/CursesDialog/cmCursesMainForm.cxx
@@ -789,6 +789,7 @@ void cmCursesMainForm::RemoveEntry(const char* value)
const char* val = (*it)->GetValue();
if ( val && !strcmp(value, val) )
{
+ this->CMakeInstance->UnwatchUnusedCli(value);
this->Entries->erase(it);
break;
}
diff --git a/Source/QtDialog/CMakeSetupDialog.cxx b/Source/QtDialog/CMakeSetupDialog.cxx
index 408dbac..c8c4bfa 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);
@@ -247,6 +254,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 1934795..5121759 100644
--- a/Source/QtDialog/CMakeSetupDialog.h
+++ b/Source/QtDialog/CMakeSetupDialog.h
@@ -97,6 +97,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..a40a175 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,8 @@ void QCMake::configure()
this->CMakeInstance->CreateGlobalGenerator(this->Generator.toAscii().data()));
this->CMakeInstance->LoadCache();
this->CMakeInstance->SetSuppressDevWarnings(this->SuppressDevWarnings);
+ this->CMakeInstance->SetWarnUninitialized(this->WarnUninitializedMode);
+ this->CMakeInstance->SetWarnUnused(this->WarnUnusedMode);
this->CMakeInstance->PreLoadCMakeFiles();
cmSystemTools::ResetErrorOccuredFlag();
@@ -238,12 +242,16 @@ void QCMake::setProperties(const QCMakePropertyList& newProps)
// remove some properites
foreach(QString s, toremove)
{
+ this->CMakeInstance->UnwatchUnusedCli(s.toAscii().data());
+
cachem->RemoveCacheEntry(s.toAscii().data());
}
// 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 +425,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 e1cb0bb..96a214e 100644
--- a/Source/cmCoreTryCompile.cxx
+++ b/Source/cmCoreTryCompile.cxx
@@ -251,10 +251,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 6c8938e..d47fb6f 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -903,6 +903,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 e443b86..e14e44d 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -44,12 +44,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 = "^.*$";
@@ -92,6 +102,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;
@@ -129,8 +141,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;
}
//----------------------------------------------------------------------------
@@ -571,6 +585,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;
@@ -611,8 +626,10 @@ bool cmMakefile::ReadListFile(const char* filename_in,
}
this->AddDefinition("CMAKE_CURRENT_LIST_FILE", filenametoread);
+ this->MarkVariableAsUsed("CMAKE_CURRENT_LIST_FILE");
this->AddDefinition("CMAKE_CURRENT_LIST_DIR",
cmSystemTools::GetFilenamePath(filenametoread).c_str());
+ this->MarkVariableAsUsed("CMAKE_CURRENT_LIST_DIR");
// try to see if the list file is the top most
// list file for a project, and if it is, then it
@@ -645,9 +662,12 @@ 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");
this->AddDefinition("CMAKE_CURRENT_LIST_DIR",
cmSystemTools::GetFilenamePath(currentFile).c_str());
+ this->MarkVariableAsUsed("CMAKE_CURRENT_LIST_DIR");
return false;
}
// add this list file to the list of dependencies
@@ -687,13 +707,19 @@ 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");
this->AddDefinition("CMAKE_CURRENT_LIST_DIR",
cmSystemTools::GetFilenamePath(currentFile).c_str());
+ this->MarkVariableAsUsed("CMAKE_CURRENT_LIST_DIR");
// pop the listfile off the stack
this->ListFileStack.pop_back();
+ // Check for unused variables
+ this->CheckForUnusedVariables();
+
return true;
}
@@ -759,6 +785,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,
@@ -1627,6 +1655,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();
@@ -1691,6 +1726,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 )
@@ -1701,9 +1743,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 )
@@ -2055,6 +2178,7 @@ const char* cmMakefile::GetRequiredDefinition(const char* name) const
bool cmMakefile::IsDefinitionSet(const char* name) const
{
const char* def = this->Internal->VarStack.top().Get(name);
+ this->Internal->VarUsageStack.top().insert(name);
if(!def)
{
def = this->GetCacheManager()->GetCacheValue(name);
@@ -2082,6 +2206,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)
{
@@ -2733,6 +2861,31 @@ 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 in try-compile.
+ //
+ // Ideally we should use SetArgs to honor options like --warn-unused-vars.
+ // However, there is a subtle problem when certain arguments are passed to
+ // a macro wrapping around try_compile or try_run that does not escape
+ // semicolons in its parameters but just passes ${ARGV} or ${ARGN}. In
+ // this case a list argument like "-DVAR=a;b" gets split into multiple
+ // cmake arguments "-DVAR=a" and "b". Currently SetCacheArgs ignores
+ // argument "b" and uses just "-DVAR=a", leading to a subtle bug in that
+ // the try_compile or try_run does not get the proper value of VAR. If we
+ // call SetArgs here then it would treat "b" as the source directory and
+ // cause an error such as "The source directory .../CMakeFiles/CMakeTmp/b
+ // does not exist", thus breaking the try_compile or try_run completely.
+ //
+ // Strictly speaking the bug is in the wrapper macro because the CMake
+ // language has always flattened nested lists and the macro should escape
+ // the semicolons in its arguments before forwarding them. However, this
+ // bug is so subtle that projects typically work anyway, usually because
+ // the value VAR=a is sufficient for the try_compile or try_run to get the
+ // correct result. Calling SetArgs here would break such projects that
+ // previously built. Instead we work around the issue by never reporting
+ // unused arguments and ignoring options such as --warn-unused-vars.
+ cm.SetWarnUnusedCli(false);
+ //cm.SetArgs(*cmakeArgs, true);
+
cm.SetCacheArgs(*cmakeArgs);
}
// to save time we pass the EnableLanguage info directly
@@ -3416,12 +3569,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)
@@ -3446,7 +3635,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 2b4414d..949fcf5 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 1b49837..bab0aaf 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -138,9 +138,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,10 @@ 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
{
@@ -4255,3 +4303,40 @@ 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::UnwatchUnusedCli(const char* var)
+{
+#ifdef CMAKE_BUILD_WITH_CMAKE
+ this->VariableWatch->RemoveWatch(var, cmWarnUnusedCliWarning);
+ this->UsedCliVariables[var] = true;
+#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 +
+ "', specified manually, was not used during the " + reason +
+ ".";
+ cmSystemTools::Message(message.c_str());
+ }
+ }
+ }
+#endif
+}
diff --git a/Source/cmake.h b/Source/cmake.h
index 435d38b..1bb42d3 100644
--- a/Source/cmake.h
+++ b/Source/cmake.h
@@ -214,7 +214,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; }
@@ -308,6 +309,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,
@@ -353,6 +365,10 @@ class cmake
const std::string& config,
const std::vector<std::string>& nativeOptions,
bool clean);
+
+ void UnwatchUnusedCli(const char* var);
+ void WatchUnusedCli(const char* var);
+ void RunCheckForUnusedVariables(const std::string& reason) const;
protected:
void InitializeProperties();
int HandleDeleteCacheVariables(const char* var);
@@ -445,6 +461,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..a24636f
--- /dev/null
+++ b/Tests/ArgumentExpansion/CMakeLists.txt
@@ -0,0 +1,60 @@
+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 ()
+
+ 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 flattens")
+argument_tester(nested_list_arg_test 4 ${nested_list_arg_test})
+
+set(semicolon_arg_test
+ "pre\;post")
+set(semicolon_arg_test_flat "pre;post")
+message(STATUS "Test: Semicolon argument flattens")
+argument_tester(semicolon_arg_test_flat 2 ${semicolon_arg_test})
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index adef084..959413d 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -391,6 +391,19 @@ ${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"
+ )
+ SET_TESTS_PROPERTIES(ArgumentExpansion PROPERTIES
+ FAIL_REGULAR_EXPRESSION "Unexpected: ")
+ LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/ArgumentExpansion")
+
ADD_TEST(CustomCommand ${CMAKE_CTEST_COMMAND}
--build-and-test
"${CMake_SOURCE_DIR}/Tests/CustomCommand"
@@ -1139,6 +1152,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")
+
ADD_TEST(TestsWorkingDirectory ${CMAKE_CTEST_COMMAND}
--build-and-test
"${CMake_SOURCE_DIR}/Tests/TestsWorkingDirectory"
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}")