Преглед изворни кода

ENH: New format for warning and error messages

  - Add cmMakefile methods IssueError and IssueWarning
  - Maintain an explicit call stack in cmMakefile
  - Include context/call-stack info in messages
  - Nested errors now unwind the call stack
  - Use new mechanism for policy warnings and errors
  - Improve policy error message
  - Include cmExecutionStatus pointer in call stack
    so that errors deeper in the C++ stack under
    a command invocation will become errors for the
    command
Brad King пре 17 година
родитељ
комит
680104a490

+ 8 - 6
Source/cmAddCustomTargetCommand.cxx

@@ -18,7 +18,8 @@
 
 // cmAddCustomTargetCommand
 bool cmAddCustomTargetCommand
-::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+::InitialPass(std::vector<std::string> const& args,
+              cmExecutionStatus& status)
 {
   // This enum must be before an enum is used in a switch statment. 
   // If not there is an ICE on the itanium version of gcc we are running
@@ -45,9 +46,9 @@ bool cmAddCustomTargetCommand
     switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP_0001))
     {
       case cmPolicies::WARN:
-        cmSystemTools::Message(
+        this->Makefile->IssueWarning(
           this->Makefile->GetPolicies()->GetPolicyWarning
-            (cmPolicies::CMP_0001).c_str(),"Warning");
+            (cmPolicies::CMP_0001));
       case cmPolicies::OLD:
 //        if (this->Makefile->IsBWCompatibilityLessThan(2,2))
 //        {
@@ -60,10 +61,11 @@ bool cmAddCustomTargetCommand
         return false;
       case cmPolicies::REQUIRED_IF_USED:
       case cmPolicies::REQUIRED_ALWAYS:
-        this->SetError(
+        this->Makefile->IssueError(
           this->Makefile->GetPolicies()->GetRequiredPolicyError
-            (cmPolicies::CMP_0001).c_str());
-        return false;      
+            (cmPolicies::CMP_0001).c_str()
+          );
+        return false;
     }
   }
     

+ 8 - 1
Source/cmExecutionStatus.h

@@ -42,12 +42,19 @@ public:
   { return this->BreakInvoked; }
             
   virtual void Clear()
-  { this->ReturnInvoked = false; this->BreakInvoked = false; }
+    {
+    this->ReturnInvoked = false;
+    this->BreakInvoked = false;
+    this->NestedError = false;
+    }
+  virtual void SetNestedError(bool val) { this->NestedError = val; }
+  virtual bool GetNestedError() { return this->NestedError; }
 
                                         
 protected:
   bool ReturnInvoked;
   bool BreakInvoked;
+  bool NestedError;
 };
 
 #endif

+ 6 - 12
Source/cmFunctionCommand.cxx

@@ -86,7 +86,7 @@ public:
 
 bool cmFunctionHelperCommand::InvokeInitialPass
 (const std::vector<cmListFileArgument>& args,
- cmExecutionStatus &)
+ cmExecutionStatus & inStatus)
 {
   // Expand the argument list to the function.
   std::vector<std::string> expandedArgs;
@@ -157,18 +157,12 @@ bool cmFunctionHelperCommand::InvokeInitialPass
   for(unsigned int c = 0; c < this->Functions.size(); ++c)
     {
     cmExecutionStatus status;
-    if (!this->Makefile->ExecuteCommand(this->Functions[c],status))
+    if (!this->Makefile->ExecuteCommand(this->Functions[c],status) ||
+        status.GetNestedError())
       {
-      cmOStringStream error;
-      error << "Error in cmake code at\n"
-            << this->Functions[c].FilePath << ":" 
-            << this->Functions[c].Line << ":\n"
-            << "A command failed during the invocation of function \""
-            << this->Args[0].c_str() << "\".";
-      cmSystemTools::Error(error.str().c_str());
-
-      // pop scope on the makefile and return
-      this->Makefile->PopScope();
+      // The error message should have already included the call stack
+      // so we do not need to report an error here.
+      inStatus.SetNestedError(true);
       return false;
       }
     if (status.GetReturnInvoked())

+ 6 - 6
Source/cmListFileCache.cxx

@@ -152,15 +152,15 @@ bool cmListFile::ParseFile(const char* filename,
       switch (mf->GetPolicyStatus(cmPolicies::CMP_0000))
       {
         case cmPolicies::WARN:
-          cmSystemTools::Message(
-            mf->GetPolicies()->GetPolicyWarning
-              (cmPolicies::CMP_0000).c_str(),"Warning");
+          mf->IssueWarning(
+            mf->GetPolicies()->GetPolicyWarning(cmPolicies::CMP_0000)
+            );
         case cmPolicies::OLD:
           break; 
         default:
-          cmSystemTools::Error(
-            mf->GetPolicies()->GetRequiredPolicyError
-              (cmPolicies::CMP_0000).c_str());
+          mf->IssueError(
+            mf->GetPolicies()->GetRequiredPolicyError(cmPolicies::CMP_0000)
+            );
           return false;
       }
     }

+ 6 - 2
Source/cmListFileCache.h

@@ -50,14 +50,18 @@ struct cmListFileArgument
   long Line;
 };
 
-struct cmListFileFunction
+struct cmListFileContext
 {
   std::string Name;
-  std::vector<cmListFileArgument> Arguments;
   std::string FilePath;
   long Line;
 };
 
+struct cmListFileFunction: public cmListFileContext
+{
+  std::vector<cmListFileArgument> Arguments;
+};
+
 struct cmListFile
 {
   cmListFile() 

+ 5 - 17
Source/cmMacroCommand.cxx

@@ -237,24 +237,12 @@ bool cmMacroHelperCommand::InvokeInitialPass
       newLFF.Arguments.push_back(arg);
       }
     cmExecutionStatus status;
-    if(!this->Makefile->ExecuteCommand(newLFF,status))
+    if(!this->Makefile->ExecuteCommand(newLFF, status) ||
+       status.GetNestedError())
       {
-      if(args.size())
-        {
-        arg.FilePath = args[0].FilePath;
-        arg.Line = args[0].Line;
-        }
-      else
-        {
-        arg.FilePath =  "Unknown";
-        arg.Line = 0;
-        }
-      cmOStringStream error;
-      error << "Error in cmake code at\n"
-            << arg.FilePath << ":" << arg.Line << ":\n"
-            << "A command failed during the invocation of macro \""
-            << this->Args[0].c_str() << "\".";
-      cmSystemTools::Error(error.str().c_str());
+      // The error message should have already included the call stack
+      // so we do not need to report an error here.
+      inStatus.SetNestedError(true);
       return false;
       }
     if (status.GetReturnInvoked())

+ 163 - 49
Source/cmMakefile.cxx

@@ -280,6 +280,134 @@ bool cmMakefile::CommandExists(const char* name) const
   return this->GetCMakeInstance()->CommandExists(name);
 }
 
+//----------------------------------------------------------------------------
+// Helper function to print a block of text with every line following
+// a given prefix.
+void cmMakefilePrintPrefixed(std::ostream& os,  const char* prefix,
+                             std::string const& msg)
+{
+  bool newline = true;
+  for(const char* c = msg.c_str(); *c; ++c)
+    {
+    if(newline)
+      {
+      os << prefix;
+      newline = false;
+      }
+    os << *c;
+    if(*c == '\n')
+      {
+      newline = true;
+      }
+    }
+  if(!newline)
+    {
+    os << "\n";
+    }
+}
+
+//----------------------------------------------------------------------------
+void cmMakefile::IssueError(std::string const& msg) const
+{
+  this->IssueMessage(msg, true);
+}
+
+//----------------------------------------------------------------------------
+void cmMakefile::IssueWarning(std::string const& msg) const
+{
+  this->IssueMessage(msg, false);
+}
+
+//----------------------------------------------------------------------------
+void cmMakefile::IssueMessage(std::string const& text, bool isError) const
+{
+  cmOStringStream msg;
+
+  // Construct the message header.
+  if(isError)
+    {
+    msg << "CMake Error:";
+    }
+  else
+    {
+    msg << "CMake Warning:";
+    }
+
+  // Add the immediate context.
+  CallStackType::const_reverse_iterator i = this->CallStack.rbegin();
+  if(i != this->CallStack.rend())
+    {
+    if(isError)
+      {
+      i->Status->SetNestedError(true);
+      }
+    cmListFileContext const& lfc = *i->Context;
+    msg
+      << " at "
+      << this->LocalGenerator->Convert(lfc.FilePath.c_str(),
+                                       cmLocalGenerator::HOME)
+      << ":" << lfc.Line << " " << lfc.Name;
+    ++i;
+    }
+
+  // Add the message text.
+  msg << " {\n";
+  cmMakefilePrintPrefixed(msg, "  ", text);
+  msg << "}";
+
+  // Add the rest of the context.
+  if(i != this->CallStack.rend())
+    {
+    msg << " with call stack {\n";
+    while(i != this->CallStack.rend())
+      {
+      cmListFileContext const& lfc = *i->Context;
+      msg << "  "
+          << this->LocalGenerator->Convert(lfc.FilePath.c_str(),
+                                           cmLocalGenerator::HOME)
+          << ":" << lfc.Line << " " << lfc.Name << "\n";
+      ++i;
+      }
+    msg << "}\n";
+    }
+  else
+    {
+    msg << "\n";
+    }
+
+  // Output the message.
+  if(isError)
+    {
+    cmSystemTools::SetErrorOccured();
+    cmSystemTools::Message(msg.str().c_str(), "Error");
+    }
+  else
+    {
+    cmSystemTools::Message(msg.str().c_str(), "Warning");
+    }
+}
+
+//----------------------------------------------------------------------------
+// Helper class to make sure the call stack is valid.
+class cmMakefileCall
+{
+public:
+  cmMakefileCall(cmMakefile* mf,
+                 cmListFileContext const& lfc,
+                 cmExecutionStatus& status): Makefile(mf)
+    {
+    cmMakefile::CallStackEntry entry = {&lfc, &status};
+    this->Makefile->CallStack.push_back(entry);
+    }
+  ~cmMakefileCall()
+    {
+    this->Makefile->CallStack.pop_back();
+    }
+private:
+  cmMakefile* Makefile;
+};
+
+//----------------------------------------------------------------------------
 bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff,
                                 cmExecutionStatus &status)
 {
@@ -294,34 +422,30 @@ bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff,
   
   std::string name = lff.Name;
 
-  // execute the command
-  cmCommand *rm =
-    this->GetCMakeInstance()->GetCommand(name.c_str());
-  if(rm)
-    {
-    // const char* versionValue
-      // = this->GetDefinition("CMAKE_BACKWARDS_COMPATIBILITY");
-    // int major = 0;
-    // int minor = 0;
-    // if ( versionValue )
-      // {
-      // sscanf(versionValue, "%d.%d", &major, &minor);
-      // }
-    cmCommand* usedCommand = rm->Clone();
-    usedCommand->SetMakefile(this);
-    bool keepCommand = false;
-    if(usedCommand->GetEnabled() && !cmSystemTools::GetFatalErrorOccured()  &&
-       (!this->GetCMakeInstance()->GetScriptMode() ||
-        usedCommand->IsScriptable()))
-      {
-      if(!usedCommand->InvokeInitialPass(lff.Arguments,status))
+  // Place this call on the call stack.
+  cmMakefileCall stack_manager(this, lff, status);
+  static_cast<void>(stack_manager);
+
+  // Lookup the command prototype.
+  if(cmCommand* proto = this->GetCMakeInstance()->GetCommand(name.c_str()))
+    {
+    // Clone the prototype.
+    cmsys::auto_ptr<cmCommand> pcmd(proto->Clone());
+    pcmd->SetMakefile(this);
+
+    // Decide whether to invoke the command.
+    if(pcmd->GetEnabled() && !cmSystemTools::GetFatalErrorOccured()  &&
+       (!this->GetCMakeInstance()->GetScriptMode() || pcmd->IsScriptable()))
+      {
+      // Try invoking the command.
+      if(!pcmd->InvokeInitialPass(lff.Arguments,status) ||
+         status.GetNestedError())
         {
-        cmOStringStream error;
-        error << "Error in cmake code at\n"
-              << lff.FilePath << ":" << lff.Line << ":\n"
-              << usedCommand->GetError() << std::endl
-              << "   Called from: " << this->GetListFileStack().c_str();
-        cmSystemTools::Error(error.str().c_str());
+        if(!status.GetNestedError())
+          {
+          // The command invocation requested that we report an error.
+          this->IssueError(pcmd->GetError());
+          }
         result = false;
         if ( this->GetCMakeInstance()->GetScriptMode() )
           {
@@ -331,38 +455,28 @@ bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff,
       else
         {
         // use the command
-        keepCommand = true;
-        this->UsedCommands.push_back(usedCommand);
+        this->UsedCommands.push_back(pcmd.release());
         }
       }
     else if ( this->GetCMakeInstance()->GetScriptMode()
-              && !usedCommand->IsScriptable() )
+              && !pcmd->IsScriptable() )
       {
-      cmOStringStream error;
-      error << "Error in cmake code at\n"
-            << lff.FilePath << ":" << lff.Line << ":\n"
-            << "Command " << usedCommand->GetName()
-            << "() is not scriptable" << std::endl;
-      cmSystemTools::Error(error.str().c_str());
+      std::string error = "Command ";
+      error += pcmd->GetName();
+      error += "() is not scriptable";
+      this->IssueError(error);
       result = false;
       cmSystemTools::SetFatalErrorOccured();
       }
-    // if the Cloned command was not used
-    // then delete it
-    if(!keepCommand)
-      {
-      delete usedCommand;
-      }
     }
   else
     {
     if(!cmSystemTools::GetFatalErrorOccured())
       {
-      cmOStringStream error;
-      error << "Error in cmake code at\n"
-            << lff.FilePath << ":" << lff.Line << ":\n"
-            << "Unknown CMake command \"" << lff.Name.c_str() << "\".";
-      cmSystemTools::Error(error.str().c_str());
+      std::string error = "Unknown CMake command \"";
+      error += lff.Name;
+      error += "\".";
+      this->IssueError(error);
       result = false;
       cmSystemTools::SetFatalErrorOccured();
       }
@@ -3152,8 +3266,8 @@ bool cmMakefile::EnforceUniqueName(std::string const& name, std::string& msg,
       switch (this->GetPolicyStatus(cmPolicies::CMP_0002))
         {
         case cmPolicies::WARN:
-          msg = this->GetPolicies()->
-            GetPolicyWarning(cmPolicies::CMP_0002);
+          this->IssueWarning(this->GetPolicies()->
+                             GetPolicyWarning(cmPolicies::CMP_0002));
         case cmPolicies::OLD:
           return true;
         case cmPolicies::REQUIRED_IF_USED:

+ 17 - 0
Source/cmMakefile.h

@@ -41,6 +41,7 @@ class cmSourceFile;
 class cmTest;
 class cmVariableWatch;
 class cmake;
+class cmMakefileCall;
 
 /** \class cmMakefile
  * \brief Process the input CMakeLists.txt file.
@@ -783,6 +784,10 @@ public:
   void PopScope();
   void RaiseScope(const char *var, const char *value);
 
+  /** Issue messages with the given text plus context information.  */
+  void IssueWarning(std::string const& msg) const;
+  void IssueError(std::string const& msg) const;
+
 protected:
   // add link libraries and directories to the target
   void AddGlobalLinkInformation(const char* name, cmTarget& target);
@@ -876,6 +881,18 @@ private:
   // stack of list files being read 
   std::deque<cmStdString> ListFileStack;
 
+  // stack of commands being invoked.
+  struct CallStackEntry
+  {
+    cmListFileContext const* Context;
+    cmExecutionStatus* Status;
+  };
+  typedef std::deque<CallStackEntry> CallStackType;
+  CallStackType CallStack;
+  friend class cmMakefileCall;
+
+  void IssueMessage(std::string const& text, bool isError) const;
+
   cmTarget* FindBasicTarget(const char* name);
   std::vector<cmTarget*> ImportedTargetsOwned;
   std::map<cmStdString, cmTarget*> ImportedTargets;

+ 17 - 18
Source/cmPolicies.cxx

@@ -86,7 +86,7 @@ cmPolicies::cmPolicies()
   // define all the policies
   this->DefinePolicy(CMP_0000, "CMP_0000",
     "Missing a CMake version specification. You must have a cmake_policy "
-    "or cmake_minimum_required call.",
+    "call.",
     "CMake requires that projects specify what version of CMake they have "
     "been written to. The easiest way to do this is by placing a call to "
     "cmake_policy at the top of your CMakeLists file. For example: "
@@ -348,10 +348,11 @@ std::string cmPolicies::GetPolicyWarning(cmPolicies::PolicyID id)
 
   cmOStringStream msg;
   msg <<
-    "WARNING: Policy " << pos->second->IDString << " is not set: "
-    "" << pos->second->ShortDescription << " "
+    "Policy " << pos->second->IDString << " is not set: "
+    "" << pos->second->ShortDescription << "\n"
     "Run \"cmake --help-policy " << pos->second->IDString << "\" for "
-    "policy details.  Use the cmake_policy command to set the policy "
+    "policy details.\n"
+    "Use the cmake_policy command to set the policy "
     "and suppress this warning.";
   return msg.str();
 }
@@ -370,21 +371,19 @@ std::string cmPolicies::GetRequiredPolicyError(cmPolicies::PolicyID id)
   }
 
   cmOStringStream error;
-  error << 
-    "Error " <<
-    pos->second->IDString << ": " <<
-    pos->second->ShortDescription <<
-    " This behavior is required now. You can suppress this message by "
-    "specifying that your listfile is written to handle this new "
-    "behavior by adding either\n" <<
-    "cmake_policy (NEW " <<
-    pos->second->IDString << ")\n or \n. " <<
-    "cmake_policy (VERSION " << 
-    pos->second->GetVersionString() << " ) or later."
-    "Run cmake --help-policy " <<
-    pos->second->IDString << " for more information.";
+  error <<
+    "Policy " << pos->second->IDString << " is not set to NEW: "
+    "" << pos->second->ShortDescription << "\n"
+    "Run \"cmake --help-policy " << pos->second->IDString << "\" for "
+    "policy details.\n"
+    "CMake now requires this policy to be set to NEW by the project.  "
+    "The policy may be set explicitly using the code\n"
+    "  cmake_policy(SET " << pos->second->IDString << " NEW)\n"
+    "or by upgrading all policies with the code\n"
+    "  cmake_policy(VERSION " << pos->second->GetVersionString() <<
+    ") # or later\n"
+    "Run \"cmake --help-command cmake_policy\" for more information.";
   return error.str();
-  
 }
 
 ///! Get the default status for a policy