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 <sstream>
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 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.c_str())) {
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 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 121 122 123 124 125 126 127 128 129
    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;
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 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
int cmOutputConverter::Shell__CharNeedsQuotes(char c, int flags)
365 366
{
  /* On Windows the built-in command shell echo never needs quotes.  */
367
  if (!(flags & Shell_Flag_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 (flags & Shell_Flag_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
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
  std::ostringstream out;
486 487 488 489 490 491 492 493

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

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

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

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

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

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

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

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

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

635
  return out.str();
636
}