cmOutputConverter.cxx 19.1 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 14 15
#include <vector>

#include "cmAlgorithms.h"
#include "cmState.h"
#include "cmStateDirectory.h"
#include "cmSystemTools.h"
16

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

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

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

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

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

76 77 78 79 80 81
static bool cmOutputConverterNotAbove(const char* a, const char* b)
{
  return (cmSystemTools::ComparePath(a, b) ||
          cmSystemTools::IsSubDirectory(a, b));
}

82 83
bool cmOutputConverter::ContainedInDirectory(std::string const& local_path,
                                             std::string const& remote_path,
84
                                             cmStateDirectory const& directory)
85
{
86
  const std::string& relativePathTopBinary =
87
    directory.GetRelativePathTopBinary();
88
  const std::string& relativePathTopSource =
89
    directory.GetRelativePathTopSource();
90

91
  const bool bothInBinary =
92 93 94 95
    cmOutputConverterNotAbove(local_path.c_str(),
                              relativePathTopBinary.c_str()) &&
    cmOutputConverterNotAbove(remote_path.c_str(),
                              relativePathTopBinary.c_str());
96 97

  const bool bothInSource =
98 99 100 101
    cmOutputConverterNotAbove(local_path.c_str(),
                              relativePathTopSource.c_str()) &&
    cmOutputConverterNotAbove(remote_path.c_str(),
                              relativePathTopSource.c_str());
102

103 104 105 106 107 108 109 110
  return bothInSource || bothInBinary;
}

std::string cmOutputConverter::ConvertToRelativePath(
  std::string const& local_path, std::string const& remote_path) const
{
  if (!ContainedInDirectory(local_path, remote_path,
                            this->StateSnapshot.GetDirectory())) {
111 112 113 114 115 116 117 118 119 120
    return remote_path;
  }

  return this->ForceToRelativePath(local_path, remote_path);
}

std::string cmOutputConverter::ForceToRelativePath(
  std::string const& local_path, std::string const& remote_path)
{
  // The paths should never be quoted.
121 122
  assert(local_path.front() != '\"');
  assert(remote_path.front() != '\"');
123 124

  // The local path should never have a trailing slash.
125
  assert(local_path.empty() || local_path.back() != '/');
126 127

  // If the path is already relative then just return the path.
128
  if (!cmSystemTools::FileIsFullPath(remote_path)) {
129
    return remote_path;
130
  }
131 132 133

  // Identify the longest shared path component between the remote
  // path and the local path.
134 135
  std::vector<std::string> local;
  cmSystemTools::SplitPath(local_path, local);
136
  std::vector<std::string> remote;
137
  cmSystemTools::SplitPath(remote_path, remote);
138 139 140
  unsigned int common = 0;
  while (common < remote.size() && common < local.size() &&
         cmSystemTools::ComparePath(remote[common], local[common])) {
141
    ++common;
142
  }
143 144

  // If no part of the path is in common then return the full path.
145
  if (common == 0) {
146
    return remote_path;
147
  }
148 149

  // If the entire path is in common then just return a ".".
150
  if (common == remote.size() && common == local.size()) {
151
    return ".";
152
  }
153 154 155

  // If the entire path is in common except for a trailing slash then
  // just return a "./".
156 157
  if (common + 1 == remote.size() && remote[common].empty() &&
      common == local.size()) {
158
    return "./";
159
  }
160 161 162 163 164 165 166 167

  // 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.
168
  for (unsigned int i = common; i < local.size(); ++i) {
169
    relative += "..";
170
    if (i < local.size() - 1) {
171 172
      relative += "/";
    }
173
  }
174 175 176 177 178 179 180 181

  // 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.

182
  if (!relative.empty() && !remote.empty()) {
183
    relative += "/";
184
  }
185
  relative += cmJoin(cmMakeRange(remote).advance(common), "/");
186 187 188 189 190 191 192 193

  // Finally return the path.
  return relative;
}

static bool cmOutputConverterIsShellOperator(const std::string& str)
{
  static std::set<std::string> shellOperators;
194
  if (shellOperators.empty()) {
195 196 197 198 199 200 201 202 203 204 205 206
    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");
207
  }
208 209 210 211
  return shellOperators.count(str) > 0;
}

std::string cmOutputConverter::EscapeForShell(const std::string& str,
212 213
                                              bool makeVars, bool forEcho,
                                              bool useWatcomQuote) const
214 215
{
  // Do not escape shell operators.
216
  if (cmOutputConverterIsShellOperator(str)) {
217
    return str;
218
  }
219 220 221

  // Compute the flags for the target shell environment.
  int flags = 0;
222
  if (this->GetState()->UseWindowsVSIDE()) {
223
    flags |= Shell_Flag_VSIDE;
224
  } else if (!this->LinkScriptShell) {
225
    flags |= Shell_Flag_Make;
226 227
  }
  if (makeVars) {
228
    flags |= Shell_Flag_AllowMakeVariables;
229 230
  }
  if (forEcho) {
231
    flags |= Shell_Flag_EchoWindows;
232 233
  }
  if (useWatcomQuote) {
234
    flags |= Shell_Flag_WatcomQuote;
235 236
  }
  if (this->GetState()->UseWatcomWMake()) {
237
    flags |= Shell_Flag_WatcomWMake;
238 239
  }
  if (this->GetState()->UseMinGWMake()) {
240
    flags |= Shell_Flag_MinGWMake;
241 242
  }
  if (this->GetState()->UseNMake()) {
243
    flags |= Shell_Flag_NMake;
244
  }
245 246 247
  if (!this->GetState()->UseWindowsShell()) {
    flags |= Shell_Flag_IsUnix;
  }
248

249
  return Shell__GetArgument(str.c_str(), flags);
250 251 252 253 254 255
}

std::string cmOutputConverter::EscapeForCMake(const std::string& str)
{
  // Always double-quote the argument to take care of most escapes.
  std::string result = "\"";
256 257
  for (const char* c = str.c_str(); *c; ++c) {
    if (*c == '"') {
258 259
      // Escape the double quote to avoid ending the argument.
      result += "\\\"";
260
    } else if (*c == '$') {
261 262
      // Escape the dollar to avoid expanding variables.
      result += "\\$";
263
    } else if (*c == '\\') {
264 265
      // Escape the backslash to avoid other escapes.
      result += "\\\\";
266
    } else {
267 268 269
      // Other characters will be parsed correctly.
      result += *c;
    }
270
  }
271 272 273 274
  result += "\"";
  return result;
}

275 276
std::string cmOutputConverter::EscapeWindowsShellArgument(const char* arg,
                                                          int shell_flags)
277
{
278
  return Shell__GetArgument(arg, shell_flags);
279 280
}

281 282
cmOutputConverter::FortranFormat cmOutputConverter::GetFortranFormat(
  const char* value)
283 284
{
  FortranFormat format = FortranFormatNone;
285
  if (value && *value) {
286 287
    std::vector<std::string> fmt;
    cmSystemTools::ExpandListArgument(value, fmt);
288 289
    for (std::string const& fi : fmt) {
      if (fi == "FIXED") {
290
        format = FortranFormatFixed;
291
      }
292
      if (fi == "FREE") {
293 294 295
        format = FortranFormatFree;
      }
    }
296
  }
297 298 299 300 301 302 303 304 305 306 307 308
  return format;
}

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

cmState* cmOutputConverter::GetState() const
{
  return this->StateSnapshot.GetState();
}
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343

/*

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

344
/* Some helpers to identify character classes */
345
static int Shell__CharIsWhitespace(char c)
346 347 348 349
{
  return ((c == ' ') || (c == '\t'));
}

350
static int Shell__CharNeedsQuotesOnUnix(char c)
351 352
{
  return ((c == '\'') || (c == '`') || (c == ';') || (c == '#') ||
353 354 355
          (c == '&') || (c == '$') || (c == '(') || (c == ')') || (c == '~') ||
          (c == '<') || (c == '>') || (c == '|') || (c == '*') || (c == '^') ||
          (c == '\\'));
356 357
}

358
static int Shell__CharNeedsQuotesOnWindows(char c)
359
{
360 361
  return ((c == '\'') || (c == '#') || (c == '&') || (c == '<') ||
          (c == '>') || (c == '|') || (c == '^'));
362 363
}

364
static int Shell__CharIsMakeVariableName(char c)
365 366 367 368
{
  return c && (c == '_' || isalpha((static_cast<int>(c))));
}

369
int cmOutputConverter::Shell__CharNeedsQuotes(char c, int flags)
370 371
{
  /* On Windows the built-in command shell echo never needs quotes.  */
372
  if (!(flags & Shell_Flag_IsUnix) && (flags & Shell_Flag_EchoWindows)) {
373
    return 0;
374
  }
375 376

  /* On all platforms quotes are needed to preserve whitespace.  */
377
  if (Shell__CharIsWhitespace(c)) {
378
    return 1;
379
  }
380

381
  if (flags & Shell_Flag_IsUnix) {
382
    /* On UNIX several special characters need quotes to preserve them.  */
383
    if (Shell__CharNeedsQuotesOnUnix(c)) {
384 385
      return 1;
    }
386
  } else {
387
    /* On Windows several special characters need quotes to preserve them.  */
388
    if (Shell__CharNeedsQuotesOnWindows(c)) {
389 390
      return 1;
    }
391
  }
392 393 394 395 396
  return 0;
}

const char* cmOutputConverter::Shell__SkipMakeVariables(const char* c)
{
397 398 399
  while (*c == '$' && *(c + 1) == '(') {
    const char* skip = c + 2;
    while (Shell__CharIsMakeVariableName(*skip)) {
400
      ++skip;
401 402 403 404
    }
    if (*skip == ')') {
      c = skip + 1;
    } else {
405 406
      break;
    }
407
  }
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
  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

434
int cmOutputConverter::Shell__ArgumentNeedsQuotes(const char* in, int flags)
435 436
{
  /* The empty string needs quotes.  */
437
  if (!*in) {
438
    return 1;
439
  }
440 441 442

  /* Scan the string for characters that require quoting.  */
  {
443 444 445 446
    const char* c;
    for (c = in; *c; ++c) {
      /* Look for $(MAKEVAR) syntax if requested.  */
      if (flags & Shell_Flag_AllowMakeVariables) {
447
#if KWSYS_SYSTEM_SHELL_QUOTE_MAKE_VARIABLES
448 449 450 451 452
        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;
453 454
        }
#else
455 456
        /* Skip over the make variable references if any are present.  */
        c = Shell__SkipMakeVariables(c);
457

458 459 460
        /* Stop if we have reached the end of the string.  */
        if (!*c) {
          break;
461 462 463 464
        }
#endif
      }

465
      /* Check whether this character needs quotes.  */
466
      if (Shell__CharNeedsQuotes(*c, flags)) {
467
        return 1;
468 469 470 471 472
      }
    }
  }

  /* On Windows some single character arguments need quotes.  */
473
  if (flags & Shell_Flag_IsUnix && *in && !*(in + 1)) {
474
    char c = *in;
475
    if ((c == '?') || (c == '&') || (c == '^') || (c == '|') || (c == '#')) {
476 477
      return 1;
    }
478
  }
479 480 481 482

  return 0;
}

483
std::string cmOutputConverter::Shell__GetArgument(const char* in, int flags)
484
{
485 486 487
  /* Output will be at least as long as input string.  */
  std::string out;
  out.reserve(strlen(in));
488 489 490 491 492 493 494 495

  /* 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.  */
496
  int needQuotes = Shell__ArgumentNeedsQuotes(in, flags);
497
  if (needQuotes) {
498
    /* Add the opening quote for this argument.  */
499
    if (flags & Shell_Flag_WatcomQuote) {
500
      if (flags & Shell_Flag_IsUnix) {
501
        out += '"';
502
      }
503
      out += '\'';
504
    } else {
505
      out += '"';
506
    }
507
  }
508 509

  /* Scan the string for characters that require escaping or quoting.  */
510
  for (c = in; *c; ++c) {
511
    /* Look for $(MAKEVAR) syntax if requested.  */
512
    if (flags & Shell_Flag_AllowMakeVariables) {
513
      const char* skip = Shell__SkipMakeVariables(c);
514
      if (skip != c) {
515
        /* Copy to the end of the make variable references.  */
516
        while (c != skip) {
517
          out += *c++;
518
        }
519 520 521 522 523 524

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

        /* Stop if we have reached the end of the string.  */
525
        if (!*c) {
526 527 528
          break;
        }
      }
529
    }
530 531

    /* Check whether this character needs escaping for the shell.  */
532
    if (flags & Shell_Flag_IsUnix) {
533 534
      /* On Unix a few special characters need escaping even inside a
         quoted argument.  */
535
      if (*c == '\\' || *c == '"' || *c == '`' || *c == '$') {
536
        /* This character needs a backslash to escape it.  */
537
        out += '\\';
538
      }
539
    } else if (flags & Shell_Flag_EchoWindows) {
540
      /* On Windows the built-in command shell echo never needs escaping.  */
541
    } else {
542
      /* On Windows only backslashes and double-quotes need escaping.  */
543
      if (*c == '\\') {
544 545
        /* Found a backslash.  It may need to be escaped later.  */
        ++windows_backslashes;
546
      } else if (*c == '"') {
547 548
        /* Found a double-quote.  Escape all immediately preceding
           backslashes.  */
549
        while (windows_backslashes > 0) {
550
          --windows_backslashes;
551
          out += '\\';
552
        }
553 554

        /* Add the backslash to escape the double-quote.  */
555
        out += '\\';
556
      } else {
557 558 559 560
        /* We encountered a normal character.  This eliminates any
           escaping needed for preceding backslashes.  */
        windows_backslashes = 0;
      }
561
    }
562 563

    /* Check whether this character needs escaping for a make tool.  */
564 565
    if (*c == '$') {
      if (flags & Shell_Flag_Make) {
566 567
        /* In Makefiles a dollar is written $$.  The make tool will
           replace it with just $ before passing it to the shell.  */
568
        out += "$$";
569
      } else if (flags & Shell_Flag_VSIDE) {
570 571 572 573 574 575
        /* 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.  */
576
        out += "\"$\"";
577
      } else {
578
        /* Otherwise a dollar is written just $. */
579
        out += '$';
580
      }
581 582
    } else if (*c == '#') {
      if ((flags & Shell_Flag_Make) && (flags & Shell_Flag_WatcomWMake)) {
583 584 585
        /* In Watcom WMake makefiles a pound is written $#.  The make
           tool will replace it with just # before passing it to the
           shell.  */
586
        out += "$#";
587
      } else {
588
        /* Otherwise a pound is written just #. */
589
        out += '#';
590
      }
591 592 593 594
    } else if (*c == '%') {
      if ((flags & Shell_Flag_VSIDE) ||
          ((flags & Shell_Flag_Make) &&
           ((flags & Shell_Flag_MinGWMake) || (flags & Shell_Flag_NMake)))) {
595
        /* In the VS IDE, NMake, or MinGW make a percent is written %%.  */
596
        out += "%%";
597
      } else {
598
        /* Otherwise a percent is written just %. */
599
        out += '%';
600
      }
601 602
    } else if (*c == ';') {
      if (flags & Shell_Flag_VSIDE) {
603 604 605 606 607
        /* 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.  */
608
        out += "\";\"";
609
      } else {
610
        /* Otherwise a semicolon is written just ;. */
611
        out += ';';
612
      }
613
    } else {
614
      /* Store this character.  */
615
      out += *c;
616
    }
617
  }
618

619
  if (needQuotes) {
620
    /* Add enough backslashes to escape any trailing ones.  */
621
    while (windows_backslashes > 0) {
622
      --windows_backslashes;
623
      out += '\\';
624
    }
625 626

    /* Add the closing quote for this argument.  */
627
    if (flags & Shell_Flag_WatcomQuote) {
628
      out += '\'';
629
      if (flags & Shell_Flag_IsUnix) {
630
        out += '"';
631
      }
632
    } else {
633
      out += '"';
634
    }
635
  }
636

637
  return out;
638
}