cmOutputConverter.cxx 19.6 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 5
#include "cmOutputConverter.h"

#include "cmAlgorithms.h"
6
#include "cmSystemTools.h"
7

8
#include <algorithm>
9
#include <assert.h>
10 11
#include <ctype.h>
#include <set>
12
#include <sstream>
13 14

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

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

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

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

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

73 74 75 76 77 78
static bool cmOutputConverterNotAbove(const char* a, const char* b)
{
  return (cmSystemTools::ComparePath(a, b) ||
          cmSystemTools::IsSubDirectory(a, b));
}

79 80 81 82 83 84
std::string cmOutputConverter::ConvertToRelativePath(
  std::string const& local_path, std::string const& remote_path) const
{
  // The paths should never be quoted.
  assert(local_path[0] != '\"');
  assert(remote_path[0] != '\"');
85 86

  // The local path should never have a trailing slash.
87
  assert(local_path.empty() || local_path[local_path.size() - 1] != '/');
88 89

  // If the path is already relative then just return the path.
90 91
  if (!cmSystemTools::FileIsFullPath(remote_path.c_str())) {
    return remote_path;
92
  }
93

94 95 96 97 98
  const std::string relativePathTopBinary =
    this->StateSnapshot.GetDirectory().GetRelativePathTopBinary();
  const std::string relativePathTopSource =
    this->StateSnapshot.GetDirectory().GetRelativePathTopSource();

99
  const bool bothInBinary =
100 101 102 103
    cmOutputConverterNotAbove(local_path.c_str(),
                              relativePathTopBinary.c_str()) &&
    cmOutputConverterNotAbove(remote_path.c_str(),
                              relativePathTopBinary.c_str());
104 105

  const bool bothInSource =
106 107 108 109
    cmOutputConverterNotAbove(local_path.c_str(),
                              relativePathTopSource.c_str()) &&
    cmOutputConverterNotAbove(remote_path.c_str(),
                              relativePathTopSource.c_str());
110 111

  if (!(bothInSource || bothInBinary)) {
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
    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.
  assert(local_path[0] != '\"');
  assert(remote_path[0] != '\"');

  // The local path should never have a trailing slash.
  assert(local_path.empty() || local_path[local_path.size() - 1] != '/');

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

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

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

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

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

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

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

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

  // Finally return the path.
  return relative;
}

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

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

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

247 248 249
  return this->GetState()->UseWindowsShell()
    ? Shell_GetArgumentForWindows(str.c_str(), flags)
    : Shell_GetArgumentForUnix(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_GetArgumentForWindows(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 290
    for (std::vector<std::string>::iterator fi = fmt.begin(); fi != fmt.end();
         ++fi) {
      if (*fi == "FIXED") {
291
        format = FortranFormatFixed;
292 293
      }
      if (*fi == "FREE") {
294 295 296
        format = FortranFormatFree;
      }
    }
297
  }
298 299 300 301 302 303 304 305 306 307 308 309
  return format;
}

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

cmState* cmOutputConverter::GetState() const
{
  return this->StateSnapshot.GetState();
}
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 344 345 346 347 348 349 350 351 352

/*

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 == '#') ||
353 354 355
          (c == '&') || (c == '$') || (c == '(') || (c == ')') || (c == '~') ||
          (c == '<') || (c == '>') || (c == '|') || (c == '*') || (c == '^') ||
          (c == '\\'));
356 357 358 359
}

int cmOutputConverter::Shell__CharNeedsQuotesOnWindows(char c)
{
360 361
  return ((c == '\'') || (c == '#') || (c == '&') || (c == '<') ||
          (c == '>') || (c == '|') || (c == '^'));
362 363 364 365 366
}

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

  /* On all platforms quotes are needed to preserve whitespace.  */
372
  if (Shell__CharIsWhitespace(c)) {
373
    return 1;
374
  }
375

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

int cmOutputConverter::Shell__CharIsMakeVariableName(char c)
{
  return c && (c == '_' || isalpha(((int)c)));
}

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 435
int cmOutputConverter::Shell__ArgumentNeedsQuotes(const char* in, int isUnix,
                                                  int flags)
436 437
{
  /* The empty string needs quotes.  */
438
  if (!*in) {
439
    return 1;
440
  }
441 442 443

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

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

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

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

  return 0;
}

484 485
std::string cmOutputConverter::Shell__GetArgument(const char* in, int isUnix,
                                                  int flags)
486
{
487
  std::ostringstream out;
488 489 490 491 492 493 494 495 496

  /* 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);
497
  if (needQuotes) {
498
    /* Add the opening quote for this argument.  */
499 500
    if (flags & Shell_Flag_WatcomQuote) {
      if (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 (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 (isUnix) {
630
        out << '"';
631
      }
632
    } else {
633
      out << '"';
634
    }
635
  }
636

637
  return out.str();
638 639
}

640 641
std::string cmOutputConverter::Shell_GetArgumentForWindows(const char* in,
                                                           int flags)
642
{
643
  return Shell__GetArgument(in, 0, flags);
644 645
}

646 647
std::string cmOutputConverter::Shell_GetArgumentForUnix(const char* in,
                                                        int flags)
648
{
649
  return Shell__GetArgument(in, 1, flags);
650
}