Browse Source

ENH: Re-implemented command line argument shell quoting to support several platforms with one code base.

Brad King 19 years ago
parent
commit
dd332a00cc

+ 30 - 0
Source/cmCustomCommand.cxx

@@ -19,6 +19,8 @@
 //----------------------------------------------------------------------------
 cmCustomCommand::cmCustomCommand()
 {
+  this->EscapeOldStyle = true;
+  this->EscapeAllowMakeVars = false;
   this->Used = false;
 }
 
@@ -30,6 +32,8 @@ cmCustomCommand::cmCustomCommand(const cmCustomCommand& r):
   Comment(r.Comment),
   WorkingDirectory(r.WorkingDirectory)
 {
+  this->EscapeOldStyle = true;
+  this->EscapeAllowMakeVars = false;
   this->Used = false;
 }
 
@@ -45,6 +49,8 @@ cmCustomCommand::cmCustomCommand(const std::vector<std::string>& outputs,
   Comment(comment?comment:""),
   WorkingDirectory(workingDirectory?workingDirectory:"")
 {
+  this->EscapeOldStyle = true;
+  this->EscapeAllowMakeVars = false;
   this->Used = false;
 }
 
@@ -81,3 +87,27 @@ const char* cmCustomCommand::GetComment() const
 {
   return this->Comment.c_str();
 }
+
+//----------------------------------------------------------------------------
+bool cmCustomCommand::GetEscapeOldStyle() const
+{
+  return this->EscapeOldStyle;
+}
+
+//----------------------------------------------------------------------------
+void cmCustomCommand::SetEscapeOldStyle(bool b)
+{
+  this->EscapeOldStyle = b;
+}
+
+//----------------------------------------------------------------------------
+bool cmCustomCommand::GetEscapeAllowMakeVars() const
+{
+  return this->EscapeAllowMakeVars;
+}
+
+//----------------------------------------------------------------------------
+void cmCustomCommand::SetEscapeAllowMakeVars(bool b)
+{
+  this->EscapeAllowMakeVars = b;
+}

+ 11 - 0
Source/cmCustomCommand.h

@@ -53,6 +53,15 @@ public:
   /** Get the comment string for the command.  */
   const char* GetComment() const;
 
+  /** Set/Get whether old-style escaping should be used.  */
+  bool GetEscapeOldStyle() const;
+  void SetEscapeOldStyle(bool b);
+
+  /** Set/Get whether the build tool can replace variables in
+      arguments to the command.  */
+  bool GetEscapeAllowMakeVars() const;
+  void SetEscapeAllowMakeVars(bool b);
+
   /** set get the used status of the command */ 
   void SetUsed() { this->Used = true;}; 
   bool IsUsed() { return this->Used;};
@@ -63,6 +72,8 @@ private:
   cmCustomCommandLines CommandLines;
   std::string Comment;
   std::string WorkingDirectory;
+  bool EscapeAllowMakeVars;
+  bool EscapeOldStyle;
   bool Used;
 };
 

+ 13 - 3
Source/cmGlobalXCodeGenerator.cxx

@@ -962,7 +962,8 @@ cmGlobalXCodeGenerator::AddCommandsToBuildPhase(cmXCodeObject* buildphase,
     cmCustomCommand const& cc = *i; 
     if(!cc.GetCommandLines().empty())
       {
-      
+      bool escapeOldStyle = cc.GetEscapeOldStyle();
+      bool escapeAllowMakeVars = cc.GetEscapeAllowMakeVars();
       makefileStream << "\n#" << "Custom command rule: " << 
         cc.GetComment() << "\n";
       const std::vector<std::string>& outputs = cc.GetOutputs();
@@ -1037,8 +1038,17 @@ cmGlobalXCodeGenerator::AddCommandsToBuildPhase(cmXCodeObject* buildphase,
         for(unsigned int j=1; j < commandLine.size(); ++j)
           {
           cmd += " ";
-          cmd += (this->CurrentLocalGenerator
-                  ->EscapeForShell(commandLine[j].c_str()));
+          if(escapeOldStyle)
+            {
+            cmd += (this->CurrentLocalGenerator
+                    ->EscapeForShellOldStyle(commandLine[j].c_str()));
+            }
+          else
+            {
+            cmd += (this->CurrentLocalGenerator->
+                    EscapeForShell(commandLine[j].c_str(),
+                                   escapeAllowMakeVars));
+            }
           }
         makefileStream << "\t" << cmd.c_str() << "\n";
         }

+ 33 - 19
Source/cmLocalGenerator.cxx

@@ -39,6 +39,7 @@ cmLocalGenerator::cmLocalGenerator()
   this->ExcludeFromAll = false;
   this->Parent = 0;
   this->WindowsShell = false;
+  this->WindowsVSIDE = false;
   this->IgnoreLibPrefix = false;
   this->UseRelativePaths = false;
   this->Configured = false;
@@ -2243,12 +2244,9 @@ cmLocalGenerator
 }
 
 //----------------------------------------------------------------------------
-std::string cmLocalGenerator::EscapeForShell(const char* str)
+std::string cmLocalGenerator::EscapeForShellOldStyle(const char* str)
 {
   std::string result;
-  // Temporarily use old shell escaping code until a means of backward
-  // compatibility can be established in the new implementation.
-#if 1
   bool forceOn =  cmSystemTools::GetForceUnixPaths();
   if(forceOn && this->WindowsShell)
     {
@@ -2259,25 +2257,41 @@ std::string cmLocalGenerator::EscapeForShell(const char* str)
     {
     cmSystemTools::SetForceUnixPaths(true);
     }
-#else
+  return result;
+}
+
+//----------------------------------------------------------------------------
+std::string cmLocalGenerator::EscapeForShell(const char* str, bool makeVars)
+{
+  // Compute the flags for the target shell environment.
+  int flags = 0;
+  if(this->WindowsVSIDE)
+    {
+    flags |= cmsysSystem_Shell_Flag_VSIDE;
+    }
+  else
+    {
+    flags |= cmsysSystem_Shell_Flag_Make;
+    }
+  if(makeVars)
+    {
+    flags |= cmsysSystem_Shell_Flag_AllowMakeVariables;
+    }
+
+  // Compute the buffer size needed.
+  int size = (this->WindowsShell ?
+              cmsysSystem_Shell_GetArgumentSizeForWindows(str, flags) :
+              cmsysSystem_Shell_GetArgumentSizeForUnix(str, flags));
+
+  // Compute the shell argument itself.
+  std::vector<char> arg(size);
   if(this->WindowsShell)
     {
-    int size = cmsysSystem_Windows_ShellArgumentSize(str);
-    std::vector<char> arg(size);
-    cmsysSystem_Windows_ShellArgument(str, &arg[0]);
-    result = &arg[0];
+    cmsysSystem_Shell_GetArgumentForWindows(str, &arg[0], flags);
     }
   else
     {
-    for(const char* c = str; *c; ++c)
-      {
-      if(*c == '\\' || *c == '\'' || *c == '"' || *c == ';' || *c == ' ')
-        {
-        result += "\\";
-        }
-      result += *c;
-      }
+    cmsysSystem_Shell_GetArgumentForUnix(str, &arg[0], flags);
     }
-#endif
-  return result;
+  return std::string(&arg[0]);
 }

+ 7 - 2
Source/cmLocalGenerator.h

@@ -202,8 +202,12 @@ public:
   };
 
   /** Escape the given string to be used as a command line argument in
-      the native build system shell.  */
-  std::string EscapeForShell(const char* str);
+      the native build system shell.  Optionally allow the build
+      system to replace make variable references.  */
+  std::string EscapeForShell(const char* str, bool makeVars = false);
+
+  /** Backwards-compatibility version of EscapeForShell.  */
+  std::string EscapeForShellOldStyle(const char* str);
 
 protected:
 
@@ -267,6 +271,7 @@ protected:
   std::map<cmStdString, cmStdString> LanguageToIncludeFlags;
   std::map<cmStdString, cmStdString> UniqueObjectNamesMap;
   bool WindowsShell;
+  bool WindowsVSIDE;
   bool ForceUnixPath;
   bool UseRelativePaths;
   bool IgnoreLibPrefix;

+ 11 - 1
Source/cmLocalUnixMakefileGenerator3.cxx

@@ -899,6 +899,8 @@ cmLocalUnixMakefileGenerator3
     {
     dir = workingDir;
     }
+  bool escapeOldStyle = cc.GetEscapeOldStyle();
+  bool escapeAllowMakeVars = cc.GetEscapeAllowMakeVars();
 
   // Add each command line to the set of commands.
   std::vector<std::string> commands1;
@@ -927,7 +929,15 @@ cmLocalUnixMakefileGenerator3
       for(unsigned int j=1; j < commandLine.size(); ++j)
         {
         cmd += " ";
-        cmd += this->EscapeForShell(commandLine[j].c_str());
+        if(escapeOldStyle)
+          {
+          cmd += this->EscapeForShellOldStyle(commandLine[j].c_str());
+          }
+        else
+          {
+          cmd += this->EscapeForShell(commandLine[j].c_str(),
+                                      escapeAllowMakeVars);
+          }
         }
       commands1.push_back(cmd);
       }

+ 8 - 0
Source/cmLocalVisualStudio6Generator.cxx

@@ -461,6 +461,8 @@ void cmLocalVisualStudio6Generator
         std::string script =
           this->ConstructScript(command->GetCommandLines(), 
                                 command->GetWorkingDirectory(),
+                                command->GetEscapeOldStyle(),
+                                command->GetEscapeAllowMakeVars(),
                                 "\\\n\t");
         std::string comment =
           this->ConstructComment(*command,
@@ -775,6 +777,8 @@ cmLocalVisualStudio6Generator::CreateTargetRules(cmTarget &target,
       }
     customRuleCode += this->ConstructScript(cr->GetCommandLines(),
                                             cr->GetWorkingDirectory(),
+                                            cr->GetEscapeOldStyle(),
+                                            cr->GetEscapeAllowMakeVars(),
                                             prelink_newline);
     }
   for (std::vector<cmCustomCommand>::const_iterator cr =
@@ -787,6 +791,8 @@ cmLocalVisualStudio6Generator::CreateTargetRules(cmTarget &target,
       }
     customRuleCode += this->ConstructScript(cr->GetCommandLines(),
                                             cr->GetWorkingDirectory(),
+                                            cr->GetEscapeOldStyle(),
+                                            cr->GetEscapeAllowMakeVars(),
                                             prelink_newline);
     }
   if(prelink_total > 0)
@@ -814,6 +820,8 @@ cmLocalVisualStudio6Generator::CreateTargetRules(cmTarget &target,
       }
     customRuleCode += this->ConstructScript(cr->GetCommandLines(),
                                             cr->GetWorkingDirectory(),
+                                            cr->GetEscapeOldStyle(),
+                                            cr->GetEscapeAllowMakeVars(),
                                             postbuild_newline);
     }
   if(postbuild_total > 0)

+ 18 - 7
Source/cmLocalVisualStudio7Generator.cxx

@@ -1184,7 +1184,9 @@ void cmLocalVisualStudio7Generator
         // Construct the entire set of commands in one string.
         std::string script = 
           this->ConstructScript(command->GetCommandLines(),
-                                command->GetWorkingDirectory());
+                                command->GetWorkingDirectory(),
+                                command->GetEscapeOldStyle(),
+                                command->GetEscapeAllowMakeVars());
         std::string comment = this->ConstructComment(*command);
         const char* flags = compileFlags.size() ? compileFlags.c_str(): 0;
         this->WriteCustomRule(fout, source.c_str(), script.c_str(),
@@ -1391,8 +1393,11 @@ void cmLocalVisualStudio7Generator
       fout << "\nCommandLine=\"";
       init = true;
       }
-    std::string script = 
-      this->ConstructScript(cr->GetCommandLines(), cr->GetWorkingDirectory());
+    std::string script =
+      this->ConstructScript(cr->GetCommandLines(),
+                            cr->GetWorkingDirectory(),
+                            cr->GetEscapeOldStyle(),
+                            cr->GetEscapeAllowMakeVars());
     fout << this->EscapeForXML(script.c_str()).c_str();
     }
   if (init)
@@ -1413,8 +1418,11 @@ void cmLocalVisualStudio7Generator
       fout << "\nCommandLine=\"";
       init = true;
       }
-    std::string script = this->ConstructScript(cr->GetCommandLines(),
-                                               cr->GetWorkingDirectory());
+    std::string script =
+      this->ConstructScript(cr->GetCommandLines(),
+                            cr->GetWorkingDirectory(),
+                            cr->GetEscapeOldStyle(),
+                            cr->GetEscapeAllowMakeVars());
     fout << this->EscapeForXML(script.c_str()).c_str();
     }
   if (init)
@@ -1435,8 +1443,11 @@ void cmLocalVisualStudio7Generator
       fout << "\nCommandLine=\"";
       init = true;
       }
-    std::string script = 
-      this->ConstructScript(cr->GetCommandLines(), cr->GetWorkingDirectory());
+    std::string script =
+      this->ConstructScript(cr->GetCommandLines(),
+                            cr->GetWorkingDirectory(),
+                            cr->GetEscapeOldStyle(),
+                            cr->GetEscapeAllowMakeVars());
     fout << this->EscapeForXML(script.c_str()).c_str();
     }
   if (init)

+ 12 - 1
Source/cmLocalVisualStudioGenerator.cxx

@@ -24,6 +24,7 @@
 cmLocalVisualStudioGenerator::cmLocalVisualStudioGenerator()
 {
   this->WindowsShell = true;
+  this->WindowsVSIDE = true;
 }
 
 //----------------------------------------------------------------------------
@@ -110,6 +111,8 @@ std::string
 cmLocalVisualStudioGenerator
 ::ConstructScript(const cmCustomCommandLines& commandLines,
                   const char* workingDirectory,
+                  bool escapeOldStyle,
+                  bool escapeAllowMakeVars,
                   const char* newline)
 {
   // Store the script in a string.
@@ -146,7 +149,15 @@ cmLocalVisualStudioGenerator
     for(unsigned int j=1;j < commandLine.size(); ++j)
       {
       script += " ";
-      script += this->EscapeForShell(commandLine[j].c_str());
+      if(escapeOldStyle)
+        {
+        script += this->EscapeForShellOldStyle(commandLine[j].c_str());
+        }
+      else
+        {
+        script += this->EscapeForShell(commandLine[j].c_str(),
+                                       escapeAllowMakeVars);
+        }
       }
 
     // End the line.

+ 2 - 0
Source/cmLocalVisualStudioGenerator.h

@@ -39,6 +39,8 @@ protected:
   /** Construct a script from the given list of command lines.  */
   std::string ConstructScript(const cmCustomCommandLines& commandLines,
                               const char* workingDirectory,
+                              bool escapeOldStyle,
+                              bool escapeAllowMakeVars,
                               const char* newline = "\n");
 
   // Safe object file name generation.

+ 2 - 2
Source/kwsys/ProcessWin32.c

@@ -2089,7 +2089,7 @@ int kwsysProcessComputeCommandLength(kwsysProcess* cp,
       {
       /* Add the length of this argument.  It already includes room
          for a separating space or terminating null.  */
-      length += kwsysSystem_Windows_ShellArgumentSize(*arg);
+      length += kwsysSystem_Shell_GetArgumentSizeForWindows(*arg, 0);
       }
     }
 
@@ -2119,7 +2119,7 @@ void kwsysProcessComputeCommandLine(kwsysProcess* cp,
         }
 
       /* Add the current argument.  */
-      cmd = kwsysSystem_Windows_ShellArgument(*arg, cmd);
+      cmd = kwsysSystem_Shell_GetArgumentForWindows(*arg, cmd, 0);
       }
 
     /* Add the terminating null character to the command line.  */

+ 351 - 76
Source/kwsys/System.c

@@ -21,19 +21,164 @@
 #endif
 
 #include <string.h> /* strlen */
+#include <ctype.h>  /* isalpha */
+
+#include <stdio.h>
+
+/*
+
+Notes:
+
+Make variable replacements open a can of worms.  Sometimes they should
+be quoted and sometimes not.  Sometimes their replacement values are
+already quoted.
+
+VS variables cause problems.  In order to pass the referenced value
+with spaces the reference must be quoted.  If the variable value ends
+in a backslash then it will escape the ending quote!  In order to make
+the ending backslash appear we need this:
+
+  "$(InputDir)\"
+
+However if there is not a trailing backslash then this will put a
+quote in the value so we need:
+
+  "$(InputDir)"
+
+Make variable references are platform specific so we should probably
+just NOT quote them and let the listfile author deal with it.
+
+*/
+
+/*--------------------------------------------------------------------------*/
+static int kwsysSystem_Shell__CharIsWhitespace(char c)
+{
+  return ((c == ' ') || (c == '\t'));
+}
+
+/*--------------------------------------------------------------------------*/
+static int kwsysSystem_Shell__CharNeedsQuotesOnUnix(char c)
+{
+  return ((c == '\'') || (c == '`') || (c == ';') ||
+          (c == '&') || (c == '$') || (c == '(') || (c == ')'));
+}
+
+/*--------------------------------------------------------------------------*/
+static int kwsysSystem_Shell__CharNeedsQuotes(char c, int isUnix, int flags)
+{
+  /* On all platforms quotes are needed to preserve whitespace.  */
+  if(kwsysSystem_Shell__CharIsWhitespace(c))
+    {
+    return 1;
+    }
+
+  if(isUnix)
+    {
+    /* On UNIX several special characters need quotes to preserve them.  */
+    if(kwsysSystem_Shell__CharNeedsQuotesOnUnix(c))
+      {
+      return 1;
+      }
+    }
+  else
+    {
+    /* On Windows single-quotes must be escaped in some make
+       environments, such as in mingw32-make.  */
+    if(flags & kwsysSystem_Shell_Flag_Make)
+      {
+      if(c == '\'')
+        {
+        return 1;
+        }
+      }
+    }
+  return 0;
+}
 
 /*--------------------------------------------------------------------------*/
-static int kwsysSystemWindowsShellArgumentNeedsEscape(const char* in)
+static int kwsysSystem_Shell__CharIsMakeVariableName(char c)
 {
-  /* Scan the string for characters that need escaping.  Note that
-     single quotes seem to need escaping for some windows shell
-     environments (mingw32-make shell for example).  Single quotes do
-     not actually need backslash escapes but must be in a
-     double-quoted argument.  */
+  return c && (c == '_' || isalpha(((int)c)));
+}
+
+/*--------------------------------------------------------------------------*/
+static const char* kwsysSystem_Shell__SkipMakeVariables(const char* c)
+{
+  while(*c == '$' && *(c+1) == '(')
+    {
+    const char* skip = c+2;
+    while(kwsysSystem_Shell__CharIsMakeVariableName(*skip))
+      {
+      ++skip;
+      }
+    if(*skip == ')')
+      {
+      c = skip+1;
+      }
+    else
+      {
+      break;
+      }
+    }
+  return c;
+}
+
+/*
+Allowing make variable replacements opens a can of worms.  Sometimes
+they should be quoted and sometimes not.  Sometimes their replacement
+values are already quoted or contain escapes.
+
+Some Visual Studio variables cause problems.  In order to pass the
+referenced value with spaces the reference must be quoted.  If the
+variable value ends in a backslash then it will escape the ending
+quote!  In order to make the ending backslash appear we need this:
+
+  "$(InputDir)\"
+
+However if there is not a trailing backslash then this will put a
+quote in the value so we need:
+
+  "$(InputDir)"
+
+This macro decides whether we quote an argument just because it
+contains a make variable reference.  This should be replaced with a
+flag later when we understand applications of this better.
+*/
+#define KWSYS_SYSTEM_SHELL_QUOTE_MAKE_VARIABLES 0
+
+/*--------------------------------------------------------------------------*/
+static int kwsysSystem_Shell__ArgumentNeedsQuotes(const char* in, int isUnix,
+                                                  int flags)
+{
+  /* Scan the string for characters that require quoting.  */
   const char* c;
   for(c=in; *c; ++c)
     {
-    if(*c == ' ' || *c == '\t' || *c == '"' || *c == '\'')
+    /* Look for $(MAKEVAR) syntax if requested.  */
+    if(flags & kwsysSystem_Shell_Flag_AllowMakeVariables)
+      {
+#if KWSYS_SYSTEM_SHELL_QUOTE_MAKE_VARIABLES
+      const char* skip = kwsysSystem_Shell__SkipMakeVariables(c);
+      if(skip != c)
+        {
+        /* We need to quote make variable references to preserve the
+           string with contents substituted in its place.  */
+        return 1;
+        }
+#else
+      /* Skip over the make variable references if any are present.  */
+      c = kwsysSystem_Shell__SkipMakeVariables(c);
+
+      /* Stop if we have reached the end of the string.  */
+      if(!*c)
+        {
+        break;
+        }
+#endif
+      }
+
+    /* Check whether this character needs quotes.  */
+    if(kwsysSystem_Shell__CharNeedsQuotes(*c, isUnix, flags))
       {
       return 1;
       }
@@ -42,130 +187,260 @@ static int kwsysSystemWindowsShellArgumentNeedsEscape(const char* in)
 }
 
 /*--------------------------------------------------------------------------*/
-int kwsysSystem_Windows_ShellArgumentSize(const char* in)
+static int kwsysSystem_Shell__GetArgumentSize(const char* in,
+                                              int isUnix, int flags)
 {
   /* Start with the length of the original argument, plus one for
      either a terminating null or a separating space.  */
-  int length = (int)strlen(in) + 1;
+  int size = (int)strlen(in) + 1;
 
   /* String iterator.  */
   const char* c;
 
   /* Keep track of how many backslashes have been encountered in a row.  */
-  int backslashes = 0;
-
-  /* If nothing needs escaping, we do not need any extra length. */
-  if(!kwsysSystemWindowsShellArgumentNeedsEscape(in))
-    {
-    return length;
-    }
-
-  /* Add 2 for double quotes since spaces are present.  */
-  length += 2;
+  int windows_backslashes = 0;
 
-  /* Scan the string to find characters that need escaping.  */
+  /* Scan the string for characters that require escaping or quoting.  */
   for(c=in; *c; ++c)
     {
-    if(*c == '\\')
+    /* Look for $(MAKEVAR) syntax if requested.  */
+    if(flags & kwsysSystem_Shell_Flag_AllowMakeVariables)
       {
-      /* Found a backslash.  It may need to be escaped later.  */
-      ++backslashes;
+      /* Skip over the make variable references if any are present.  */
+      c = kwsysSystem_Shell__SkipMakeVariables(c);
+
+      /* Stop if we have reached the end of the string.  */
+      if(!*c)
+        {
+        break;
+        }
       }
-    else if(*c == '"')
+
+    /* Check whether this character needs escaping.  */
+    if(isUnix)
       {
-      /* Found a double-quote.  We need to escape it and all
-         immediately preceding backslashes.  */
-      length += backslashes + 1;
-      backslashes = 0;
+      /* On Unix a few special characters need escaping even inside a
+         quoted argument.  */
+      if(*c == '\\' || *c == '"' || *c == '`' || *c == '$')
+        {
+        /* This character needs a backslash to escape it.  */
+        ++size;
+        }
       }
     else
       {
-      /* Found another character.  This eliminates the possibility
-         that any immediately preceding backslashes will be
-         escaped.  */
-      backslashes = 0;
+      /* On Windows only backslashes and double-quotes need escaping.  */
+      if(*c == '\\')
+        {
+        /* Found a backslash.  It may need to be escaped later.  */
+        ++windows_backslashes;
+        }
+      else if(*c == '"')
+        {
+        /* Found a double-quote.  We need to escape it and all
+           immediately preceding backslashes.  */
+        size += windows_backslashes + 1;
+        windows_backslashes = 0;
+        }
+      else
+        {
+        /* Found another character.  This eliminates the possibility
+           that any immediately preceding backslashes will be
+           escaped.  */
+        windows_backslashes = 0;
+        }
+      }
+
+    /* The dollar sign needs special handling in some environments.  */
+    if(*c == '$')
+      {
+      if(flags & kwsysSystem_Shell_Flag_Make)
+        {
+        /* In Makefiles a dollar is written $$ so we need one extra
+           character.  */
+        ++size;
+        }
+      else if(flags & kwsysSystem_Shell_Flag_VSIDE)
+        {
+        /* In a VS IDE a dollar is written "$" so we need two extra
+           characters.  */
+        size += 2;
+        }
       }
     }
 
-  /* We need to escape all ending backslashes. */
-  length += backslashes;
+  /* Check whether the argument needs surrounding quotes.  */
+  if(kwsysSystem_Shell__ArgumentNeedsQuotes(in, isUnix, flags))
+    {
+    /* Surrounding quotes are needed.  Allocate space for them.  */
+    size += 2;
 
-  return length;
+    /* We must escape all ending backslashes when quoting on windows.  */
+    size += windows_backslashes;
+    }
+
+  return size;
 }
 
 /*--------------------------------------------------------------------------*/
-char* kwsysSystem_Windows_ShellArgument(const char* in, char* out)
+static char* kwsysSystem_Shell__GetArgument(const char* in, char* out,
+                                            int isUnix, int flags)
 {
   /* String iterator.  */
   const char* c;
 
   /* Keep track of how many backslashes have been encountered in a row.  */
-  int backslashes = 0;
+  int windows_backslashes = 0;
 
-  /* If nothing needs escaping, we can pass the argument verbatim. */
-  if(!kwsysSystemWindowsShellArgumentNeedsEscape(in))
+  /* Whether the argument must be quoted.  */
+  int needQuotes = kwsysSystem_Shell__ArgumentNeedsQuotes(in, isUnix, flags);
+  if(needQuotes)
     {
-    /* Just copy the string.  */
-    for(c=in; *c; ++c)
-      {
-      *out++ = *c;
-      }
-
-    /* Store a terminating null without incrementing.  */
-    *out = 0;
-    return out;
+    /* Add the opening quote for this argument.  */
+    *out++ = '"';
     }
 
-  /* Add the opening double-quote for this argument.  */
-  *out++ = '"';
-
-  /* Add the characters of the argument, possibly escaping them.  */
+  /* Scan the string for characters that require escaping or quoting.  */
   for(c=in; *c; ++c)
     {
-    if(*c == '\\')
+    /* Look for $(MAKEVAR) syntax if requested.  */
+    if(flags & kwsysSystem_Shell_Flag_AllowMakeVariables)
       {
-      /* Found a backslash.  It may need to be escaped later.  */
-      ++backslashes;
-      *out++ = '\\';
+      const char* skip = kwsysSystem_Shell__SkipMakeVariables(c);
+      if(skip != c)
+        {
+        /* Copy to the end of the make variable references.  */
+        while(c != skip)
+          {
+          *out++ = *c++;
+          }
+
+        /* Stop if we have reached the end of the string.  */
+        if(!*c)
+          {
+          break;
+          }
+        }
       }
-    else if(*c == '"')
+
+    /* Check whether this character needs escaping.  */
+    if(isUnix)
       {
-      /* Add enough backslashes to escape any that preceded the
-         double-quote.  */
-      while(backslashes > 0)
+      /* On Unix a few special characters need escaping even inside a
+         quoted argument.  */
+      if(*c == '\\' || *c == '"' || *c == '`' || *c == '$')
         {
-        --backslashes;
+        /* This character needs a backslash to escape it.  */
         *out++ = '\\';
         }
+      }
+    else
+      {
+      /* On Windows only backslashes and double-quotes need escaping.  */
+      if(*c == '\\')
+        {
+        /* Found a backslash.  It may need to be escaped later.  */
+        ++windows_backslashes;
+        }
+      else if(*c == '"')
+        {
+        /* Found a double-quote.  Escape all immediately preceding
+           backslashes.  */
+        while(windows_backslashes > 0)
+          {
+          --windows_backslashes;
+          *out++ = '\\';
+          }
 
-      /* Add the backslash to escape the double-quote.  */
-      *out++ = '\\';
+        /* Add the backslash to escape the double-quote.  */
+        *out++ = '\\';
+        }
+      else
+        {
+        /* We encountered a normal character.  This eliminates any
+           escaping needed for preceding backslashes.  */
+        windows_backslashes = 0;
+        }
+      }
 
-      /* Add the double-quote itself.  */
-      *out++ = '"';
+    /* The dollar sign needs special handling in some environments.  */
+    if(*c == '$')
+      {
+      if(flags & kwsysSystem_Shell_Flag_Make)
+        {
+        /* In Makefiles a dollar is written $$.  The make tool will
+           replace it with just $ before passing it to the shell.  */
+        *out++ = '$';
+        *out++ = '$';
+        }
+      else if(flags & kwsysSystem_Shell_Flag_VSIDE)
+        {
+        /* In a VS IDE a dollar is written "$".  If this is written in
+           an un-quoted argument it starts a quoted segment, inserts
+           the $ and ends the segment.  If it is written in a quoted
+           argument it ends quoting, inserts the $ and restarts
+           quoting.  Either way the $ is isolated from surrounding
+           text to avoid looking like a variable reference.  */
+        *out++ = '"';
+        *out++ = '$';
+        *out++ = '"';
+        }
+      else
+        {
+        /* Otherwise a dollar is written just $. */
+        *out++ = '$';
+        }
       }
     else
       {
-      /* We encountered a normal character.  This eliminates any
-         escaping needed for preceding backslashes.  Add the
-         character.  */
-      backslashes = 0;
+      /* Store this character.  */
       *out++ = *c;
       }
     }
 
-  /* Add enough backslashes to escape any trailing ones.  */
-  while(backslashes > 0)
+  if(needQuotes)
     {
-    --backslashes;
-    *out++ = '\\';
-    }
+    /* Add enough backslashes to escape any trailing ones.  */
+    while(windows_backslashes > 0)
+      {
+      --windows_backslashes;
+      *out++ = '\\';
+      }
 
-  /* Add the closing double-quote for this argument.  */
-  *out++ = '"';
+    /* Add the closing quote for this argument.  */
+    *out++ = '"';
+    }
 
   /* Store a terminating null without incrementing.  */
   *out = 0;
 
   return out;
 }
+
+/*--------------------------------------------------------------------------*/
+char* kwsysSystem_Shell_GetArgumentForWindows(const char* in,
+                                              char* out,
+                                              int flags)
+{
+  return kwsysSystem_Shell__GetArgument(in, out, 0, flags);
+}
+
+/*--------------------------------------------------------------------------*/
+char* kwsysSystem_Shell_GetArgumentForUnix(const char* in,
+                                           char* out,
+                                           int flags)
+{
+  return kwsysSystem_Shell__GetArgument(in, out, 1, flags);
+}
+
+/*--------------------------------------------------------------------------*/
+int kwsysSystem_Shell_GetArgumentSizeForWindows(const char* in, int flags)
+{
+  return kwsysSystem_Shell__GetArgumentSize(in, 0, flags);
+}
+
+/*--------------------------------------------------------------------------*/
+int kwsysSystem_Shell_GetArgumentSizeForUnix(const char* in, int flags)
+{
+  return kwsysSystem_Shell__GetArgumentSize(in, 1, flags);
+}

+ 58 - 12
Source/kwsys/System.h.in

@@ -24,8 +24,14 @@
 # define kwsys_ns(x) @KWSYS_NAMESPACE@##x
 # define kwsysEXPORT @KWSYS_NAMESPACE@_EXPORT
 #endif
-#define kwsysSystem_Windows_ShellArgument     kwsys_ns(System_Windows_ShellArgument)
-#define kwsysSystem_Windows_ShellArgumentSize kwsys_ns(System_Windows_ShellArgumentSize)
+#define kwsysSystem_Shell_GetArgumentForWindows      kwsys_ns(System_Shell_GetArgumentForWindows)
+#define kwsysSystem_Shell_GetArgumentForUnix         kwsys_ns(System_Shell_GetArgumentForUnix)
+#define kwsysSystem_Shell_GetArgumentSizeForWindows  kwsys_ns(System_Shell_GetArgumentSizeForWindows)
+#define kwsysSystem_Shell_GetArgumentSizeForUnix     kwsys_ns(System_Shell_GetArgumentSizeForUnix)
+#define kwsysSystem_Shell_Flag_e                     kwsys_ns(System_Shell_Flag_e)
+#define kwsysSystem_Shell_Flag_Make                  kwsys_ns(System_Shell_Flag_Make)
+#define kwsysSystem_Shell_Flag_VSIDE                 kwsys_ns(System_Shell_Flag_VSIDE)
+#define kwsysSystem_Shell_Flag_AllowMakeVariables    kwsys_ns(System_Shell_Flag_AllowMakeVariables)
 
 #if defined(__cplusplus)
 extern "C"
@@ -33,18 +39,52 @@ extern "C"
 #endif
 
 /**
- * Escape the given command line argument for use in a windows shell.
- * Returns a pointer to the end of the command line argument in the
- * given buffer.
+ * Transform the given command line argument for use in a Windows or
+ * Unix shell.  Returns a pointer to the end of the command line
+ * argument in the provided output buffer.  Flags may be passed to
+ * modify the generated quoting and escape sequences to work under
+ * alternative environments.
  */
-kwsysEXPORT char* kwsysSystem_Windows_ShellArgument(const char* in, char* out);
+kwsysEXPORT char* kwsysSystem_Shell_GetArgumentForWindows(const char* in,
+                                                          char* out,
+                                                          int flags);
+kwsysEXPORT char* kwsysSystem_Shell_GetArgumentForUnix(const char* in,
+                                                       char* out,
+                                                       int flags);
 
 /**
- * Compute the size of the buffer needed to store the result of
- * kwsysSystem_Windows_ShellArgument.  The return value includes space
- * for a null-terminator.
+ * Compute the size of the buffer required to store the output from
+ * kwsysSystem_Shell_GetArgumentForWindows or
+ * kwsysSystem_Shell_GetArgumentForUnix.  The flags passed must be
+ * identical between the two calls.
  */
-kwsysEXPORT int kwsysSystem_Windows_ShellArgumentSize(const char* in);
+kwsysEXPORT int kwsysSystem_Shell_GetArgumentSizeForWindows(const char* in,
+                                                            int flags);
+kwsysEXPORT int kwsysSystem_Shell_GetArgumentSizeForUnix(const char* in,
+                                                         int flags);
+
+/**
+ * Flags to pass to kwsysSystem_Shell_GetArgumentForWindows or
+ * kwsysSystem_Shell_GetArgumentForUnix.  These modify the generated
+ * quoting and escape sequences to work under alternative
+ * environments.
+ */
+enum kwsysSystem_Shell_Flag_e
+{
+  /** The target shell is in a makefile.  */
+  kwsysSystem_Shell_Flag_Make               = (1<<0),
+
+  /** The target shell is in a VS project file.  Do not use with
+      Shell_Flag_Make.  */
+  kwsysSystem_Shell_Flag_VSIDE              = (1<<1),
+
+  /** Make variable reference syntax $(MAKEVAR) should not be escaped
+      to allow a build tool to replace it.  Replacement values
+      containing spaces, quotes, backslashes, or other
+      non-alphanumeric characters that have significance to some makes
+      or shells produce undefined behavior.  */
+  kwsysSystem_Shell_Flag_AllowMakeVariables = (1<<2)
+};
 
 #if defined(__cplusplus)
 } /* extern "C" */
@@ -55,8 +95,14 @@ kwsysEXPORT int kwsysSystem_Windows_ShellArgumentSize(const char* in);
 #if !defined(KWSYS_NAMESPACE)
 # undef kwsys_ns
 # undef kwsysEXPORT
-# undef kwsysSystem_Windows_ShellArgument
-# undef kwsysSystem_Windows_ShellArgumentSize
+# undef kwsysSystem_Shell_GetArgumentForWindows
+# undef kwsysSystem_Shell_GetArgumentForUnix
+# undef kwsysSystem_Shell_GetArgumentSizeForWindows
+# undef kwsysSystem_Shell_GetArgumentSizeForUnix
+# undef kwsysSystem_Shell_Flag_e
+# undef kwsysSystem_Shell_Flag_Make
+# undef kwsysSystem_Shell_Flag_VSIDE
+# undef kwsysSystem_Shell_Flag_AllowMakeVariables
 #endif
 
 #endif