From dd332a00cc0169221243a231cd2474b6521aa11a Mon Sep 17 00:00:00 2001 From: Brad King Date: Wed, 27 Sep 2006 13:43:46 -0400 Subject: ENH: Re-implemented command line argument shell quoting to support several platforms with one code base. --- Source/cmCustomCommand.cxx | 30 +++ Source/cmCustomCommand.h | 11 + Source/cmGlobalXCodeGenerator.cxx | 16 +- Source/cmLocalGenerator.cxx | 52 ++-- Source/cmLocalGenerator.h | 9 +- Source/cmLocalUnixMakefileGenerator3.cxx | 12 +- Source/cmLocalVisualStudio6Generator.cxx | 8 + Source/cmLocalVisualStudio7Generator.cxx | 25 +- Source/cmLocalVisualStudioGenerator.cxx | 13 +- Source/cmLocalVisualStudioGenerator.h | 2 + Source/kwsys/ProcessWin32.c | 4 +- Source/kwsys/System.c | 427 +++++++++++++++++++++++++------ Source/kwsys/System.h.in | 70 ++++- 13 files changed, 556 insertions(+), 123 deletions(-) diff --git a/Source/cmCustomCommand.cxx b/Source/cmCustomCommand.cxx index 162f10f..debc805 100644 --- a/Source/cmCustomCommand.cxx +++ b/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& 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; +} diff --git a/Source/cmCustomCommand.h b/Source/cmCustomCommand.h index 4fc300c..e493c5d 100644 --- a/Source/cmCustomCommand.h +++ b/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; }; diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index bfc8c8e..728e37e 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/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& 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"; } diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index 34295ae..a3b48b3 100644 --- a/Source/cmLocalGenerator.cxx +++ b/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 arg(size); if(this->WindowsShell) { - int size = cmsysSystem_Windows_ShellArgumentSize(str); - std::vector 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]); } diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h index 44aa79a..1f273b9 100644 --- a/Source/cmLocalGenerator.h +++ b/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 LanguageToIncludeFlags; std::map UniqueObjectNamesMap; bool WindowsShell; + bool WindowsVSIDE; bool ForceUnixPath; bool UseRelativePaths; bool IgnoreLibPrefix; diff --git a/Source/cmLocalUnixMakefileGenerator3.cxx b/Source/cmLocalUnixMakefileGenerator3.cxx index 0898daf..2eac5ab 100644 --- a/Source/cmLocalUnixMakefileGenerator3.cxx +++ b/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 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); } diff --git a/Source/cmLocalVisualStudio6Generator.cxx b/Source/cmLocalVisualStudio6Generator.cxx index 7a07d45..4295ce8 100644 --- a/Source/cmLocalVisualStudio6Generator.cxx +++ b/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::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) diff --git a/Source/cmLocalVisualStudio7Generator.cxx b/Source/cmLocalVisualStudio7Generator.cxx index 08d5158..2cdca06 100644 --- a/Source/cmLocalVisualStudio7Generator.cxx +++ b/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) diff --git a/Source/cmLocalVisualStudioGenerator.cxx b/Source/cmLocalVisualStudioGenerator.cxx index f874987..c342e28 100644 --- a/Source/cmLocalVisualStudioGenerator.cxx +++ b/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. diff --git a/Source/cmLocalVisualStudioGenerator.h b/Source/cmLocalVisualStudioGenerator.h index 38ca491..ca9cb6c 100644 --- a/Source/cmLocalVisualStudioGenerator.h +++ b/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. diff --git a/Source/kwsys/ProcessWin32.c b/Source/kwsys/ProcessWin32.c index 1fe6cb0..4f50d36 100644 --- a/Source/kwsys/ProcessWin32.c +++ b/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. */ diff --git a/Source/kwsys/System.c b/Source/kwsys/System.c index 3b8e5a5..414e69c 100644 --- a/Source/kwsys/System.c +++ b/Source/kwsys/System.c @@ -21,19 +21,164 @@ #endif #include /* strlen */ +#include /* isalpha */ + +#include + +/* + +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); +} diff --git a/Source/kwsys/System.h.in b/Source/kwsys/System.h.in index db4859e..b125473 100644 --- a/Source/kwsys/System.h.in +++ b/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 -- cgit v0.12