cmOutputConverter.cxx 14.9 KB
Newer Older
1 2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
3 4
#include "cmOutputConverter.h"

5
#include <algorithm>
6
#include <assert.h>
7 8
#include <ctype.h>
#include <set>
9
#include <string.h>
10 11 12 13
#include <vector>

#include "cmState.h"
#include "cmSystemTools.h"
14

15
cmOutputConverter::cmOutputConverter(cmStateSnapshot const& snapshot)
16 17
  : StateSnapshot(snapshot)
  , LinkScriptShell(false)
18
{
19
  assert(this->StateSnapshot.IsValid());
20 21
}

22 23
std::string cmOutputConverter::ConvertToOutputForExisting(
  const std::string& remote, OutputFormat format) const
24 25 26 27
{
  // 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.
28 29
  if (this->GetState()->UseWindowsShell() &&
      remote.find(' ') != std::string::npos &&
30
      cmSystemTools::FileExists(remote)) {
31
    std::string tmp;
32
    if (cmSystemTools::GetShortPath(remote, tmp)) {
33 34
      return this->ConvertToOutputFormat(tmp, format);
    }
35
  }
36

37 38
  // Otherwise, perform standard conversion.
  return this->ConvertToOutputFormat(remote, format);
39 40 41
}

std::string cmOutputConverter::ConvertToOutputFormat(const std::string& source,
42
                                                     OutputFormat output) const
43 44 45
{
  std::string result = source;
  // Convert it to an output path.
46
  if (output == SHELL || output == WATCOMQUOTE) {
47
    result = this->ConvertDirectorySeparatorsForShell(source);
48
    result = this->EscapeForShell(result, true, false, output == WATCOMQUOTE);
49
  } else if (output == RESPONSE) {
50
    result = this->EscapeForShell(result, false, false, false);
51
  }
52 53 54
  return result;
}

55
std::string cmOutputConverter::ConvertDirectorySeparatorsForShell(
56
  const std::string& source) const
57 58 59 60 61
{
  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.
62 63
  if (this->GetState()->UseMSYSShell() && !this->LinkScriptShell) {
    if (result.size() > 2 && result[1] == ':') {
64 65 66
      result[1] = result[0];
      result[0] = '/';
    }
67 68
  }
  if (this->GetState()->UseWindowsShell()) {
69
    std::replace(result.begin(), result.end(), '/', '\\');
70
  }
71 72 73
  return result;
}

74
static bool cmOutputConverterIsShellOperator(cm::string_view str)
75
{
76
  static std::set<cm::string_view> const shellOperators{
77 78 79
    "<", ">", "<<", ">>", "|", "||", "&&", "&>", "1>", "2>", "2>&1", "1>&2"
  };
  return (shellOperators.count(str) != 0);
80 81 82
}

std::string cmOutputConverter::EscapeForShell(const std::string& str,
83 84
                                              bool makeVars, bool forEcho,
                                              bool useWatcomQuote) const
85 86
{
  // Do not escape shell operators.
87
  if (cmOutputConverterIsShellOperator(str)) {
88
    return str;
89
  }
90 91 92

  // Compute the flags for the target shell environment.
  int flags = 0;
93
  if (this->GetState()->UseWindowsVSIDE()) {
94
    flags |= Shell_Flag_VSIDE;
95
  } else if (!this->LinkScriptShell) {
96
    flags |= Shell_Flag_Make;
97 98
  }
  if (makeVars) {
99
    flags |= Shell_Flag_AllowMakeVariables;
100 101
  }
  if (forEcho) {
102
    flags |= Shell_Flag_EchoWindows;
103 104
  }
  if (useWatcomQuote) {
105
    flags |= Shell_Flag_WatcomQuote;
106 107
  }
  if (this->GetState()->UseWatcomWMake()) {
108
    flags |= Shell_Flag_WatcomWMake;
109 110
  }
  if (this->GetState()->UseMinGWMake()) {
111
    flags |= Shell_Flag_MinGWMake;
112 113
  }
  if (this->GetState()->UseNMake()) {
114
    flags |= Shell_Flag_NMake;
115
  }
116 117 118
  if (!this->GetState()->UseWindowsShell()) {
    flags |= Shell_Flag_IsUnix;
  }
119

120
  return Shell__GetArgument(str.c_str(), flags);
121 122
}

123
std::string cmOutputConverter::EscapeForCMake(cm::string_view str)
124 125 126
{
  // Always double-quote the argument to take care of most escapes.
  std::string result = "\"";
127 128
  for (const char c : str) {
    if (c == '"') {
129 130
      // Escape the double quote to avoid ending the argument.
      result += "\\\"";
131
    } else if (c == '$') {
132 133
      // Escape the dollar to avoid expanding variables.
      result += "\\$";
134
    } else if (c == '\\') {
135 136
      // Escape the backslash to avoid other escapes.
      result += "\\\\";
137
    } else {
138
      // Other characters will be parsed correctly.
139
      result += c;
140
    }
141
  }
142 143 144 145
  result += "\"";
  return result;
}

146 147
std::string cmOutputConverter::EscapeWindowsShellArgument(const char* arg,
                                                          int shell_flags)
148
{
149
  return Shell__GetArgument(arg, shell_flags);
150 151
}

152 153
cmOutputConverter::FortranFormat cmOutputConverter::GetFortranFormat(
  const char* value)
154 155
{
  FortranFormat format = FortranFormatNone;
156
  if (value && *value) {
157 158
    std::vector<std::string> fmt;
    cmSystemTools::ExpandListArgument(value, fmt);
159 160
    for (std::string const& fi : fmt) {
      if (fi == "FIXED") {
161
        format = FortranFormatFixed;
162
      }
163
      if (fi == "FREE") {
164 165 166
        format = FortranFormatFree;
      }
    }
167
  }
168 169 170 171 172 173 174 175 176 177 178 179
  return format;
}

void cmOutputConverter::SetLinkScriptShell(bool linkScriptShell)
{
  this->LinkScriptShell = linkScriptShell;
}

cmState* cmOutputConverter::GetState() const
{
  return this->StateSnapshot.GetState();
}
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214

/*

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 (^^).
*/

215
/* Some helpers to identify character classes */
216
static int Shell__CharIsWhitespace(char c)
217 218 219 220
{
  return ((c == ' ') || (c == '\t'));
}

221
static int Shell__CharNeedsQuotesOnUnix(char c)
222 223
{
  return ((c == '\'') || (c == '`') || (c == ';') || (c == '#') ||
224 225 226
          (c == '&') || (c == '$') || (c == '(') || (c == ')') || (c == '~') ||
          (c == '<') || (c == '>') || (c == '|') || (c == '*') || (c == '^') ||
          (c == '\\'));
227 228
}

229
static int Shell__CharNeedsQuotesOnWindows(char c)
230
{
231 232
  return ((c == '\'') || (c == '#') || (c == '&') || (c == '<') ||
          (c == '>') || (c == '|') || (c == '^'));
233 234
}

235
static int Shell__CharIsMakeVariableName(char c)
236 237 238 239
{
  return c && (c == '_' || isalpha((static_cast<int>(c))));
}

240
int cmOutputConverter::Shell__CharNeedsQuotes(char c, int flags)
241 242
{
  /* On Windows the built-in command shell echo never needs quotes.  */
243
  if (!(flags & Shell_Flag_IsUnix) && (flags & Shell_Flag_EchoWindows)) {
244
    return 0;
245
  }
246 247

  /* On all platforms quotes are needed to preserve whitespace.  */
248
  if (Shell__CharIsWhitespace(c)) {
249
    return 1;
250
  }
251

252
  if (flags & Shell_Flag_IsUnix) {
253
    /* On UNIX several special characters need quotes to preserve them.  */
254
    if (Shell__CharNeedsQuotesOnUnix(c)) {
255 256
      return 1;
    }
257
  } else {
258
    /* On Windows several special characters need quotes to preserve them.  */
259
    if (Shell__CharNeedsQuotesOnWindows(c)) {
260 261
      return 1;
    }
262
  }
263 264 265 266 267
  return 0;
}

const char* cmOutputConverter::Shell__SkipMakeVariables(const char* c)
{
268 269 270
  while (*c == '$' && *(c + 1) == '(') {
    const char* skip = c + 2;
    while (Shell__CharIsMakeVariableName(*skip)) {
271
      ++skip;
272 273 274 275
    }
    if (*skip == ')') {
      c = skip + 1;
    } else {
276 277
      break;
    }
278
  }
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
  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

305
int cmOutputConverter::Shell__ArgumentNeedsQuotes(const char* in, int flags)
306 307
{
  /* The empty string needs quotes.  */
308
  if (!*in) {
309
    return 1;
310
  }
311 312 313

  /* Scan the string for characters that require quoting.  */
  {
314 315 316 317
    const char* c;
    for (c = in; *c; ++c) {
      /* Look for $(MAKEVAR) syntax if requested.  */
      if (flags & Shell_Flag_AllowMakeVariables) {
318
#if KWSYS_SYSTEM_SHELL_QUOTE_MAKE_VARIABLES
319 320 321 322 323
        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;
324 325
        }
#else
326 327
        /* Skip over the make variable references if any are present.  */
        c = Shell__SkipMakeVariables(c);
328

329 330 331
        /* Stop if we have reached the end of the string.  */
        if (!*c) {
          break;
332 333 334 335
        }
#endif
      }

336
      /* Check whether this character needs quotes.  */
337
      if (Shell__CharNeedsQuotes(*c, flags)) {
338
        return 1;
339 340 341 342 343
      }
    }
  }

  /* On Windows some single character arguments need quotes.  */
344
  if (flags & Shell_Flag_IsUnix && *in && !*(in + 1)) {
345
    char c = *in;
346
    if ((c == '?') || (c == '&') || (c == '^') || (c == '|') || (c == '#')) {
347 348
      return 1;
    }
349
  }
350 351 352 353

  return 0;
}

354
std::string cmOutputConverter::Shell__GetArgument(const char* in, int flags)
355
{
356 357 358
  /* Output will be at least as long as input string.  */
  std::string out;
  out.reserve(strlen(in));
359 360 361 362 363 364 365 366

  /* 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.  */
367
  int needQuotes = Shell__ArgumentNeedsQuotes(in, flags);
368
  if (needQuotes) {
369
    /* Add the opening quote for this argument.  */
370
    if (flags & Shell_Flag_WatcomQuote) {
371
      if (flags & Shell_Flag_IsUnix) {
372
        out += '"';
373
      }
374
      out += '\'';
375
    } else {
376
      out += '"';
377
    }
378
  }
379 380

  /* Scan the string for characters that require escaping or quoting.  */
381
  for (c = in; *c; ++c) {
382
    /* Look for $(MAKEVAR) syntax if requested.  */
383
    if (flags & Shell_Flag_AllowMakeVariables) {
384
      const char* skip = Shell__SkipMakeVariables(c);
385
      if (skip != c) {
386
        /* Copy to the end of the make variable references.  */
387
        while (c != skip) {
388
          out += *c++;
389
        }
390 391 392 393 394 395

        /* The make variable reference eliminates any escaping needed
           for preceding backslashes.  */
        windows_backslashes = 0;

        /* Stop if we have reached the end of the string.  */
396
        if (!*c) {
397 398 399
          break;
        }
      }
400
    }
401 402

    /* Check whether this character needs escaping for the shell.  */
403
    if (flags & Shell_Flag_IsUnix) {
404 405
      /* On Unix a few special characters need escaping even inside a
         quoted argument.  */
406
      if (*c == '\\' || *c == '"' || *c == '`' || *c == '$') {
407
        /* This character needs a backslash to escape it.  */
408
        out += '\\';
409
      }
410
    } else if (flags & Shell_Flag_EchoWindows) {
411
      /* On Windows the built-in command shell echo never needs escaping.  */
412
    } else {
413
      /* On Windows only backslashes and double-quotes need escaping.  */
414
      if (*c == '\\') {
415 416
        /* Found a backslash.  It may need to be escaped later.  */
        ++windows_backslashes;
417
      } else if (*c == '"') {
418 419
        /* Found a double-quote.  Escape all immediately preceding
           backslashes.  */
420
        while (windows_backslashes > 0) {
421
          --windows_backslashes;
422
          out += '\\';
423
        }
424 425

        /* Add the backslash to escape the double-quote.  */
426
        out += '\\';
427
      } else {
428 429 430 431
        /* We encountered a normal character.  This eliminates any
           escaping needed for preceding backslashes.  */
        windows_backslashes = 0;
      }
432
    }
433 434

    /* Check whether this character needs escaping for a make tool.  */
435 436
    if (*c == '$') {
      if (flags & Shell_Flag_Make) {
437 438
        /* In Makefiles a dollar is written $$.  The make tool will
           replace it with just $ before passing it to the shell.  */
439
        out += "$$";
440
      } else if (flags & Shell_Flag_VSIDE) {
441 442 443 444 445 446
        /* 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.  */
447
        out += "\"$\"";
448
      } else {
449
        /* Otherwise a dollar is written just $. */
450
        out += '$';
451
      }
452 453
    } else if (*c == '#') {
      if ((flags & Shell_Flag_Make) && (flags & Shell_Flag_WatcomWMake)) {
454 455 456
        /* In Watcom WMake makefiles a pound is written $#.  The make
           tool will replace it with just # before passing it to the
           shell.  */
457
        out += "$#";
458
      } else {
459
        /* Otherwise a pound is written just #. */
460
        out += '#';
461
      }
462 463 464 465
    } else if (*c == '%') {
      if ((flags & Shell_Flag_VSIDE) ||
          ((flags & Shell_Flag_Make) &&
           ((flags & Shell_Flag_MinGWMake) || (flags & Shell_Flag_NMake)))) {
466
        /* In the VS IDE, NMake, or MinGW make a percent is written %%.  */
467
        out += "%%";
468
      } else {
469
        /* Otherwise a percent is written just %. */
470
        out += '%';
471
      }
472 473
    } else if (*c == ';') {
      if (flags & Shell_Flag_VSIDE) {
474 475 476 477 478
        /* 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.  */
479
        out += "\";\"";
480
      } else {
481
        /* Otherwise a semicolon is written just ;. */
482
        out += ';';
483
      }
484
    } else {
485
      /* Store this character.  */
486
      out += *c;
487
    }
488
  }
489

490
  if (needQuotes) {
491
    /* Add enough backslashes to escape any trailing ones.  */
492
    while (windows_backslashes > 0) {
493
      --windows_backslashes;
494
      out += '\\';
495
    }
496 497

    /* Add the closing quote for this argument.  */
498
    if (flags & Shell_Flag_WatcomQuote) {
499
      out += '\'';
500
      if (flags & Shell_Flag_IsUnix) {
501
        out += '"';
502
      }
503
    } else {
504
      out += '"';
505
    }
506
  }
507

508
  return out;
509
}