summaryrefslogtreecommitdiffstats
path: root/Source/cmOutputConverter.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'Source/cmOutputConverter.cxx')
-rw-r--r--Source/cmOutputConverter.cxx726
1 files changed, 726 insertions, 0 deletions
diff --git a/Source/cmOutputConverter.cxx b/Source/cmOutputConverter.cxx
new file mode 100644
index 0000000..411184c
--- /dev/null
+++ b/Source/cmOutputConverter.cxx
@@ -0,0 +1,726 @@
+/*============================================================================
+ CMake - Cross Platform Makefile Generator
+ Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+
+ Distributed under the OSI-approved BSD License (the "License");
+ see accompanying file Copyright.txt for details.
+
+ This software is distributed WITHOUT ANY WARRANTY; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the License for more information.
+============================================================================*/
+#include "cmOutputConverter.h"
+
+#include "cmAlgorithms.h"
+#include "cmake.h"
+
+#include <assert.h>
+#include <sstream>
+
+#include <ctype.h> /* isalpha */
+#include <string.h> /* strlen */
+
+cmOutputConverter::cmOutputConverter(cmState::Snapshot snapshot)
+ : StateSnapshot(snapshot)
+ , LinkScriptShell(false)
+{
+ assert(this->StateSnapshot.IsValid());
+}
+
+std::string cmOutputConverter::ConvertToOutputForExisting(
+ const std::string& remote, OutputFormat format) const
+{
+ // If this is a windows shell, the result has a space, and the path
+ // already exists, we can use a short-path to reference it without a
+ // space.
+ if (this->GetState()->UseWindowsShell() &&
+ remote.find(' ') != std::string::npos &&
+ cmSystemTools::FileExists(remote.c_str())) {
+ std::string tmp;
+ if (cmSystemTools::GetShortPath(remote, tmp)) {
+ return this->ConvertToOutputFormat(tmp, format);
+ }
+ }
+
+ // Otherwise, perform standard conversion.
+ return this->ConvertToOutputFormat(remote, format);
+}
+
+std::string cmOutputConverter::ConvertToOutputForExisting(
+ RelativeRoot remote, OutputFormat format) const
+{
+ // The relative root must have a path (i.e. not FULL or NONE)
+ assert(remote != FULL);
+ assert(remote != NONE);
+
+ const char* remotePath = this->GetRelativeRootPath(remote);
+ assert(remotePath != CM_NULLPTR);
+
+ return this->ConvertToOutputForExisting(remotePath, format);
+}
+
+const char* cmOutputConverter::GetRelativeRootPath(RelativeRoot relroot) const
+{
+ switch (relroot) {
+ case HOME:
+ return this->GetState()->GetSourceDirectory();
+ case START:
+ return this->StateSnapshot.GetDirectory().GetCurrentSource();
+ case HOME_OUTPUT:
+ return this->GetState()->GetBinaryDirectory();
+ case START_OUTPUT:
+ return this->StateSnapshot.GetDirectory().GetCurrentBinary();
+ default:
+ break;
+ }
+ return CM_NULLPTR;
+}
+
+std::string cmOutputConverter::Convert(const std::string& source,
+ RelativeRoot relative,
+ OutputFormat output) const
+{
+ // Convert the path to a relative path.
+ std::string result = source;
+
+ switch (relative) {
+ case HOME:
+ result = this->ConvertToRelativePath(
+ this->GetState()->GetSourceDirectoryComponents(), result);
+ break;
+ case START:
+ result = this->ConvertToRelativePath(
+ this->StateSnapshot.GetDirectory().GetCurrentSourceComponents(),
+ result);
+ break;
+ case HOME_OUTPUT:
+ result = this->ConvertToRelativePath(
+ this->GetState()->GetBinaryDirectoryComponents(), result);
+ break;
+ case START_OUTPUT:
+ result = this->ConvertToRelativePath(
+ this->StateSnapshot.GetDirectory().GetCurrentBinaryComponents(),
+ result);
+ break;
+ case FULL:
+ result = cmSystemTools::CollapseFullPath(result);
+ break;
+ case NONE:
+ break;
+ }
+ return this->ConvertToOutputFormat(result, output);
+}
+
+std::string cmOutputConverter::ConvertToOutputFormat(const std::string& source,
+ OutputFormat output) const
+{
+ std::string result = source;
+ // Convert it to an output path.
+ if (output == MAKERULE) {
+ result = cmSystemTools::ConvertToOutputPath(result.c_str());
+ } else if (output == SHELL || output == WATCOMQUOTE) {
+ result = this->ConvertDirectorySeparatorsForShell(source);
+ result = this->EscapeForShell(result, true, false, output == WATCOMQUOTE);
+ } else if (output == RESPONSE) {
+ result = this->EscapeForShell(result, false, false, false);
+ }
+ return result;
+}
+
+std::string cmOutputConverter::ConvertDirectorySeparatorsForShell(
+ const std::string& source) const
+{
+ std::string result = source;
+ // For the MSYS shell convert drive letters to posix paths, so
+ // that c:/some/path becomes /c/some/path. This is needed to
+ // avoid problems with the shell path translation.
+ if (this->GetState()->UseMSYSShell() && !this->LinkScriptShell) {
+ if (result.size() > 2 && result[1] == ':') {
+ result[1] = result[0];
+ result[0] = '/';
+ }
+ }
+ if (this->GetState()->UseWindowsShell()) {
+ std::replace(result.begin(), result.end(), '/', '\\');
+ }
+ return result;
+}
+
+std::string cmOutputConverter::Convert(RelativeRoot remote,
+ const std::string& local,
+ OutputFormat output) const
+{
+ // The relative root must have a path (i.e. not FULL or NONE)
+ assert(remote != FULL);
+ assert(remote != NONE);
+
+ const char* remotePath = this->GetRelativeRootPath(remote);
+ assert(remotePath != CM_NULLPTR);
+
+ if (local.empty()) {
+ return this->ConvertToOutputFormat(remotePath, output);
+ }
+
+ std::vector<std::string> components;
+ cmSystemTools::SplitPath(local, components);
+ std::string result = this->ConvertToRelativePath(components, remotePath);
+ return this->ConvertToOutputFormat(result, output);
+}
+
+static bool cmOutputConverterNotAbove(const char* a, const char* b)
+{
+ return (cmSystemTools::ComparePath(a, b) ||
+ cmSystemTools::IsSubDirectory(a, b));
+}
+
+std::string cmOutputConverter::ConvertToRelativePath(
+ const std::vector<std::string>& local, const std::string& in_remote,
+ bool force) const
+{
+ // The path should never be quoted.
+ assert(in_remote[0] != '\"');
+
+ // The local path should never have a trailing slash.
+ assert(!local.empty() && !(local[local.size() - 1] == ""));
+
+ // If the path is already relative then just return the path.
+ if (!cmSystemTools::FileIsFullPath(in_remote.c_str())) {
+ return in_remote;
+ }
+
+ if (!force) {
+ // Skip conversion if the path and local are not both in the source
+ // or both in the binary tree.
+ std::string local_path = cmSystemTools::JoinPath(local);
+ if (!((cmOutputConverterNotAbove(
+ local_path.c_str(),
+ this->StateSnapshot.GetDirectory().GetRelativePathTopBinary()) &&
+ cmOutputConverterNotAbove(
+ in_remote.c_str(),
+ this->StateSnapshot.GetDirectory().GetRelativePathTopBinary())) ||
+ (cmOutputConverterNotAbove(
+ local_path.c_str(),
+ this->StateSnapshot.GetDirectory().GetRelativePathTopSource()) &&
+ cmOutputConverterNotAbove(in_remote.c_str(),
+ this->StateSnapshot.GetDirectory()
+ .GetRelativePathTopSource())))) {
+ return in_remote;
+ }
+ }
+
+ // Identify the longest shared path component between the remote
+ // path and the local path.
+ std::vector<std::string> remote;
+ cmSystemTools::SplitPath(in_remote, remote);
+ unsigned int common = 0;
+ while (common < remote.size() && common < local.size() &&
+ cmSystemTools::ComparePath(remote[common], local[common])) {
+ ++common;
+ }
+
+ // If no part of the path is in common then return the full path.
+ if (common == 0) {
+ return in_remote;
+ }
+
+ // If the entire path is in common then just return a ".".
+ if (common == remote.size() && common == local.size()) {
+ return ".";
+ }
+
+ // If the entire path is in common except for a trailing slash then
+ // just return a "./".
+ if (common + 1 == remote.size() && remote[common].empty() &&
+ common == local.size()) {
+ return "./";
+ }
+
+ // Construct the relative path.
+ std::string relative;
+
+ // First add enough ../ to get up to the level of the shared portion
+ // of the path. Leave off the trailing slash. Note that the last
+ // component of local will never be empty because local should never
+ // have a trailing slash.
+ for (unsigned int i = common; i < local.size(); ++i) {
+ relative += "..";
+ if (i < local.size() - 1) {
+ relative += "/";
+ }
+ }
+
+ // Now add the portion of the destination path that is not included
+ // in the shared portion of the path. Add a slash the first time
+ // only if there was already something in the path. If there was a
+ // trailing slash in the input then the last iteration of the loop
+ // will add a slash followed by an empty string which will preserve
+ // the trailing slash in the output.
+
+ if (!relative.empty() && !remote.empty()) {
+ relative += "/";
+ }
+ relative += cmJoin(cmMakeRange(remote).advance(common), "/");
+
+ // Finally return the path.
+ return relative;
+}
+
+static bool cmOutputConverterIsShellOperator(const std::string& str)
+{
+ static std::set<std::string> shellOperators;
+ if (shellOperators.empty()) {
+ shellOperators.insert("<");
+ shellOperators.insert(">");
+ shellOperators.insert("<<");
+ shellOperators.insert(">>");
+ shellOperators.insert("|");
+ shellOperators.insert("||");
+ shellOperators.insert("&&");
+ shellOperators.insert("&>");
+ shellOperators.insert("1>");
+ shellOperators.insert("2>");
+ shellOperators.insert("2>&1");
+ shellOperators.insert("1>&2");
+ }
+ return shellOperators.count(str) > 0;
+}
+
+std::string cmOutputConverter::EscapeForShell(const std::string& str,
+ bool makeVars, bool forEcho,
+ bool useWatcomQuote) const
+{
+ // Do not escape shell operators.
+ if (cmOutputConverterIsShellOperator(str)) {
+ return str;
+ }
+
+ // Compute the flags for the target shell environment.
+ int flags = 0;
+ if (this->GetState()->UseWindowsVSIDE()) {
+ flags |= Shell_Flag_VSIDE;
+ } else if (!this->LinkScriptShell) {
+ flags |= Shell_Flag_Make;
+ }
+ if (makeVars) {
+ flags |= Shell_Flag_AllowMakeVariables;
+ }
+ if (forEcho) {
+ flags |= Shell_Flag_EchoWindows;
+ }
+ if (useWatcomQuote) {
+ flags |= Shell_Flag_WatcomQuote;
+ }
+ if (this->GetState()->UseWatcomWMake()) {
+ flags |= Shell_Flag_WatcomWMake;
+ }
+ if (this->GetState()->UseMinGWMake()) {
+ flags |= Shell_Flag_MinGWMake;
+ }
+ if (this->GetState()->UseNMake()) {
+ flags |= Shell_Flag_NMake;
+ }
+
+ return this->GetState()->UseWindowsShell()
+ ? Shell_GetArgumentForWindows(str.c_str(), flags)
+ : Shell_GetArgumentForUnix(str.c_str(), flags);
+}
+
+std::string cmOutputConverter::EscapeForCMake(const std::string& str)
+{
+ // Always double-quote the argument to take care of most escapes.
+ std::string result = "\"";
+ for (const char* c = str.c_str(); *c; ++c) {
+ if (*c == '"') {
+ // Escape the double quote to avoid ending the argument.
+ result += "\\\"";
+ } else if (*c == '$') {
+ // Escape the dollar to avoid expanding variables.
+ result += "\\$";
+ } else if (*c == '\\') {
+ // Escape the backslash to avoid other escapes.
+ result += "\\\\";
+ } else {
+ // Other characters will be parsed correctly.
+ result += *c;
+ }
+ }
+ result += "\"";
+ return result;
+}
+
+std::string cmOutputConverter::EscapeWindowsShellArgument(const char* arg,
+ int shell_flags)
+{
+ return Shell_GetArgumentForWindows(arg, shell_flags);
+}
+
+cmOutputConverter::FortranFormat cmOutputConverter::GetFortranFormat(
+ const char* value)
+{
+ FortranFormat format = FortranFormatNone;
+ if (value && *value) {
+ std::vector<std::string> fmt;
+ cmSystemTools::ExpandListArgument(value, fmt);
+ for (std::vector<std::string>::iterator fi = fmt.begin(); fi != fmt.end();
+ ++fi) {
+ if (*fi == "FIXED") {
+ format = FortranFormatFixed;
+ }
+ if (*fi == "FREE") {
+ format = FortranFormatFree;
+ }
+ }
+ }
+ return format;
+}
+
+void cmOutputConverter::SetLinkScriptShell(bool linkScriptShell)
+{
+ this->LinkScriptShell = linkScriptShell;
+}
+
+cmState* cmOutputConverter::GetState() const
+{
+ return this->StateSnapshot.GetState();
+}
+
+/*
+
+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.
+
+*/
+
+/*
+TODO: For windows echo:
+
+To display a pipe (|) or redirection character (< or >) when using the
+echo command, use a caret character immediately before the pipe or
+redirection character (for example, ^>, ^<, or ^| ). If you need to
+use the caret character itself (^), use two in a row (^^).
+*/
+
+int cmOutputConverter::Shell__CharIsWhitespace(char c)
+{
+ return ((c == ' ') || (c == '\t'));
+}
+
+int cmOutputConverter::Shell__CharNeedsQuotesOnUnix(char c)
+{
+ return ((c == '\'') || (c == '`') || (c == ';') || (c == '#') ||
+ (c == '&') || (c == '$') || (c == '(') || (c == ')') || (c == '~') ||
+ (c == '<') || (c == '>') || (c == '|') || (c == '*') || (c == '^') ||
+ (c == '\\'));
+}
+
+int cmOutputConverter::Shell__CharNeedsQuotesOnWindows(char c)
+{
+ return ((c == '\'') || (c == '#') || (c == '&') || (c == '<') ||
+ (c == '>') || (c == '|') || (c == '^'));
+}
+
+int cmOutputConverter::Shell__CharNeedsQuotes(char c, int isUnix, int flags)
+{
+ /* On Windows the built-in command shell echo never needs quotes. */
+ if (!isUnix && (flags & Shell_Flag_EchoWindows)) {
+ return 0;
+ }
+
+ /* On all platforms quotes are needed to preserve whitespace. */
+ if (Shell__CharIsWhitespace(c)) {
+ return 1;
+ }
+
+ if (isUnix) {
+ /* On UNIX several special characters need quotes to preserve them. */
+ if (Shell__CharNeedsQuotesOnUnix(c)) {
+ return 1;
+ }
+ } else {
+ /* On Windows several special characters need quotes to preserve them. */
+ if (Shell__CharNeedsQuotesOnWindows(c)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int cmOutputConverter::Shell__CharIsMakeVariableName(char c)
+{
+ return c && (c == '_' || isalpha(((int)c)));
+}
+
+const char* cmOutputConverter::Shell__SkipMakeVariables(const char* c)
+{
+ while (*c == '$' && *(c + 1) == '(') {
+ const char* skip = c + 2;
+ while (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
+
+int cmOutputConverter::Shell__ArgumentNeedsQuotes(const char* in, int isUnix,
+ int flags)
+{
+ /* The empty string needs quotes. */
+ if (!*in) {
+ return 1;
+ }
+
+ /* Scan the string for characters that require quoting. */
+ {
+ const char* c;
+ for (c = in; *c; ++c) {
+ /* Look for $(MAKEVAR) syntax if requested. */
+ if (flags & Shell_Flag_AllowMakeVariables) {
+#if KWSYS_SYSTEM_SHELL_QUOTE_MAKE_VARIABLES
+ const char* skip = 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 = Shell__SkipMakeVariables(c);
+
+ /* Stop if we have reached the end of the string. */
+ if (!*c) {
+ break;
+ }
+#endif
+ }
+
+ /* Check whether this character needs quotes. */
+ if (Shell__CharNeedsQuotes(*c, isUnix, flags)) {
+ return 1;
+ }
+ }
+ }
+
+ /* On Windows some single character arguments need quotes. */
+ if (!isUnix && *in && !*(in + 1)) {
+ char c = *in;
+ if ((c == '?') || (c == '&') || (c == '^') || (c == '|') || (c == '#')) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+std::string cmOutputConverter::Shell__GetArgument(const char* in, int isUnix,
+ int flags)
+{
+ std::ostringstream out;
+
+ /* String iterator. */
+ const char* c;
+
+ /* Keep track of how many backslashes have been encountered in a row. */
+ int windows_backslashes = 0;
+
+ /* Whether the argument must be quoted. */
+ int needQuotes = Shell__ArgumentNeedsQuotes(in, isUnix, flags);
+ if (needQuotes) {
+ /* Add the opening quote for this argument. */
+ if (flags & Shell_Flag_WatcomQuote) {
+ if (isUnix) {
+ out << '"';
+ }
+ out << '\'';
+ } else {
+ out << '"';
+ }
+ }
+
+ /* Scan the string for characters that require escaping or quoting. */
+ for (c = in; *c; ++c) {
+ /* Look for $(MAKEVAR) syntax if requested. */
+ if (flags & Shell_Flag_AllowMakeVariables) {
+ const char* skip = Shell__SkipMakeVariables(c);
+ if (skip != c) {
+ /* Copy to the end of the make variable references. */
+ while (c != skip) {
+ out << *c++;
+ }
+
+ /* The make variable reference eliminates any escaping needed
+ for preceding backslashes. */
+ windows_backslashes = 0;
+
+ /* Stop if we have reached the end of the string. */
+ if (!*c) {
+ break;
+ }
+ }
+ }
+
+ /* Check whether this character needs escaping for the shell. */
+ if (isUnix) {
+ /* 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. */
+ out << '\\';
+ }
+ } else if (flags & Shell_Flag_EchoWindows) {
+ /* On Windows the built-in command shell echo never needs escaping. */
+ } 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 << '\\';
+ } else {
+ /* We encountered a normal character. This eliminates any
+ escaping needed for preceding backslashes. */
+ windows_backslashes = 0;
+ }
+ }
+
+ /* Check whether this character needs escaping for a make tool. */
+ if (*c == '$') {
+ if (flags & Shell_Flag_Make) {
+ /* In Makefiles a dollar is written $$. The make tool will
+ replace it with just $ before passing it to the shell. */
+ out << "$$";
+ } else if (flags & 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 << "\"$\"";
+ } else {
+ /* Otherwise a dollar is written just $. */
+ out << '$';
+ }
+ } else if (*c == '#') {
+ if ((flags & Shell_Flag_Make) && (flags & Shell_Flag_WatcomWMake)) {
+ /* In Watcom WMake makefiles a pound is written $#. The make
+ tool will replace it with just # before passing it to the
+ shell. */
+ out << "$#";
+ } else {
+ /* Otherwise a pound is written just #. */
+ out << '#';
+ }
+ } else if (*c == '%') {
+ if ((flags & Shell_Flag_VSIDE) ||
+ ((flags & Shell_Flag_Make) &&
+ ((flags & Shell_Flag_MinGWMake) || (flags & Shell_Flag_NMake)))) {
+ /* In the VS IDE, NMake, or MinGW make a percent is written %%. */
+ out << "%%";
+ } else {
+ /* Otherwise a percent is written just %. */
+ out << '%';
+ }
+ } else if (*c == ';') {
+ if (flags & Shell_Flag_VSIDE) {
+ /* In a VS IDE a semicolon 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. */
+ out << "\";\"";
+ } else {
+ /* Otherwise a semicolon is written just ;. */
+ out << ';';
+ }
+ } else {
+ /* Store this character. */
+ out << *c;
+ }
+ }
+
+ if (needQuotes) {
+ /* Add enough backslashes to escape any trailing ones. */
+ while (windows_backslashes > 0) {
+ --windows_backslashes;
+ out << '\\';
+ }
+
+ /* Add the closing quote for this argument. */
+ if (flags & Shell_Flag_WatcomQuote) {
+ out << '\'';
+ if (isUnix) {
+ out << '"';
+ }
+ } else {
+ out << '"';
+ }
+ }
+
+ return out.str();
+}
+
+std::string cmOutputConverter::Shell_GetArgumentForWindows(const char* in,
+ int flags)
+{
+ return Shell__GetArgument(in, 0, flags);
+}
+
+std::string cmOutputConverter::Shell_GetArgumentForUnix(const char* in,
+ int flags)
+{
+ return Shell__GetArgument(in, 1, flags);
+}