cmOutputConverter.cxx 19.4 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 90 91 92 93
  const std::string relativePathTopBinary =
    this->StateSnapshot.GetDirectory().GetRelativePathTopBinary();
  const std::string relativePathTopSource =
    this->StateSnapshot.GetDirectory().GetRelativePathTopSource();

94
  const bool bothInBinary =
95 96 97 98
    cmOutputConverterNotAbove(local_path.c_str(),
                              relativePathTopBinary.c_str()) &&
    cmOutputConverterNotAbove(remote_path.c_str(),
                              relativePathTopBinary.c_str());
99 100

  const bool bothInSource =
101 102 103 104
    cmOutputConverterNotAbove(local_path.c_str(),
                              relativePathTopSource.c_str()) &&
    cmOutputConverterNotAbove(remote_path.c_str(),
                              relativePathTopSource.c_str());
105 106

  if (!(bothInSource || bothInBinary)) {
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
    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;
126
  }
127 128 129

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

  // If no part of the path is in common then return the full path.
141
  if (common == 0) {
142
    return remote_path;
143
  }
144 145

  // If the entire path is in common then just return a ".".
146
  if (common == remote.size() && common == local.size()) {
147
    return ".";
148
  }
149 150 151

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

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

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

178
  if (!relative.empty() && !remote.empty()) {
179
    relative += "/";
180
  }
181
  relative += cmJoin(cmMakeRange(remote).advance(common), "/");
182 183 184 185 186 187 188 189

  // Finally return the path.
  return relative;
}

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

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

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

242 243 244
  return this->GetState()->UseWindowsShell()
    ? Shell_GetArgumentForWindows(str.c_str(), flags)
    : Shell_GetArgumentForUnix(str.c_str(), flags);
245 246 247 248 249 250
}

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

270 271
std::string cmOutputConverter::EscapeWindowsShellArgument(const char* arg,
                                                          int shell_flags)
272
{
273
  return Shell_GetArgumentForWindows(arg, shell_flags);
274 275
}

276 277
cmOutputConverter::FortranFormat cmOutputConverter::GetFortranFormat(
  const char* value)
278 279
{
  FortranFormat format = FortranFormatNone;
280
  if (value && *value) {
281 282
    std::vector<std::string> fmt;
    cmSystemTools::ExpandListArgument(value, fmt);
283 284 285
    for (std::vector<std::string>::iterator fi = fmt.begin(); fi != fmt.end();
         ++fi) {
      if (*fi == "FIXED") {
286
        format = FortranFormatFixed;
287 288
      }
      if (*fi == "FREE") {
289 290 291
        format = FortranFormatFree;
      }
    }
292
  }
293 294 295 296 297 298 299 300 301 302 303 304
  return format;
}

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

cmState* cmOutputConverter::GetState() const
{
  return this->StateSnapshot.GetState();
}
305 306 307 308 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 344 345 346 347

/*

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 == '#') ||
348 349 350
          (c == '&') || (c == '$') || (c == '(') || (c == ')') || (c == '~') ||
          (c == '<') || (c == '>') || (c == '|') || (c == '*') || (c == '^') ||
          (c == '\\'));
351 352 353 354
}

int cmOutputConverter::Shell__CharNeedsQuotesOnWindows(char c)
{
355 356
  return ((c == '\'') || (c == '#') || (c == '&') || (c == '<') ||
          (c == '>') || (c == '|') || (c == '^'));
357 358 359 360 361
}

int cmOutputConverter::Shell__CharNeedsQuotes(char c, int isUnix, int flags)
{
  /* On Windows the built-in command shell echo never needs quotes.  */
362
  if (!isUnix && (flags & Shell_Flag_EchoWindows)) {
363
    return 0;
364
  }
365 366

  /* On all platforms quotes are needed to preserve whitespace.  */
367
  if (Shell__CharIsWhitespace(c)) {
368
    return 1;
369
  }
370

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

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

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

429 430
int cmOutputConverter::Shell__ArgumentNeedsQuotes(const char* in, int isUnix,
                                                  int flags)
431 432
{
  /* The empty string needs quotes.  */
433
  if (!*in) {
434
    return 1;
435
  }
436 437 438

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

454 455 456
        /* Stop if we have reached the end of the string.  */
        if (!*c) {
          break;
457 458 459 460
        }
#endif
      }

461 462 463
      /* Check whether this character needs quotes.  */
      if (Shell__CharNeedsQuotes(*c, isUnix, flags)) {
        return 1;
464 465 466 467 468
      }
    }
  }

  /* On Windows some single character arguments need quotes.  */
469
  if (!isUnix && *in && !*(in + 1)) {
470
    char c = *in;
471
    if ((c == '?') || (c == '&') || (c == '^') || (c == '|') || (c == '#')) {
472 473
      return 1;
    }
474
  }
475 476 477 478

  return 0;
}

479 480
std::string cmOutputConverter::Shell__GetArgument(const char* in, int isUnix,
                                                  int flags)
481
{
482
  std::ostringstream out;
483 484 485 486 487 488 489 490 491

  /* 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);
492
  if (needQuotes) {
493
    /* Add the opening quote for this argument.  */
494 495
    if (flags & Shell_Flag_WatcomQuote) {
      if (isUnix) {
496
        out << '"';
497
      }
498
      out << '\'';
499
    } else {
500
      out << '"';
501
    }
502
  }
503 504

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

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

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

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

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

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

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

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

632
  return out.str();
633 634
}

635 636
std::string cmOutputConverter::Shell_GetArgumentForWindows(const char* in,
                                                           int flags)
637
{
638
  return Shell__GetArgument(in, 0, flags);
639 640
}

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