cmOutputConverter.cxx 19.2 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
std::string cmOutputConverter::ConvertToRelativePath(
  std::string const& local_path, std::string const& remote_path) const
{
82 83 84 85 86
  const std::string relativePathTopBinary =
    this->StateSnapshot.GetDirectory().GetRelativePathTopBinary();
  const std::string relativePathTopSource =
    this->StateSnapshot.GetDirectory().GetRelativePathTopSource();

87
  const bool bothInBinary =
88 89 90 91
    cmOutputConverterNotAbove(local_path.c_str(),
                              relativePathTopBinary.c_str()) &&
    cmOutputConverterNotAbove(remote_path.c_str(),
                              relativePathTopBinary.c_str());
92 93

  const bool bothInSource =
94 95 96 97
    cmOutputConverterNotAbove(local_path.c_str(),
                              relativePathTopSource.c_str()) &&
    cmOutputConverterNotAbove(remote_path.c_str(),
                              relativePathTopSource.c_str());
98 99

  if (!(bothInSource || bothInBinary)) {
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
    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;
119
  }
120 121 122

  // Identify the longest shared path component between the remote
  // path and the local path.
123 124
  std::vector<std::string> local;
  cmSystemTools::SplitPath(local_path, local);
125
  std::vector<std::string> remote;
126
  cmSystemTools::SplitPath(remote_path, remote);
127 128 129
  unsigned int common = 0;
  while (common < remote.size() && common < local.size() &&
         cmSystemTools::ComparePath(remote[common], local[common])) {
130
    ++common;
131
  }
132 133

  // If no part of the path is in common then return the full path.
134
  if (common == 0) {
135
    return remote_path;
136
  }
137 138

  // If the entire path is in common then just return a ".".
139
  if (common == remote.size() && common == local.size()) {
140
    return ".";
141
  }
142 143 144

  // If the entire path is in common except for a trailing slash then
  // just return a "./".
145 146
  if (common + 1 == remote.size() && remote[common].empty() &&
      common == local.size()) {
147
    return "./";
148
  }
149 150 151 152 153 154 155 156

  // 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.
157
  for (unsigned int i = common; i < local.size(); ++i) {
158
    relative += "..";
159
    if (i < local.size() - 1) {
160 161
      relative += "/";
    }
162
  }
163 164 165 166 167 168 169 170

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

171
  if (!relative.empty() && !remote.empty()) {
172
    relative += "/";
173
  }
174
  relative += cmJoin(cmMakeRange(remote).advance(common), "/");
175 176 177 178 179 180 181 182

  // Finally return the path.
  return relative;
}

static bool cmOutputConverterIsShellOperator(const std::string& str)
{
  static std::set<std::string> shellOperators;
183
  if (shellOperators.empty()) {
184 185 186 187 188 189 190 191 192 193 194 195
    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");
196
  }
197 198 199 200
  return shellOperators.count(str) > 0;
}

std::string cmOutputConverter::EscapeForShell(const std::string& str,
201 202
                                              bool makeVars, bool forEcho,
                                              bool useWatcomQuote) const
203 204
{
  // Do not escape shell operators.
205
  if (cmOutputConverterIsShellOperator(str)) {
206
    return str;
207
  }
208 209 210

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

235 236 237
  return this->GetState()->UseWindowsShell()
    ? Shell_GetArgumentForWindows(str.c_str(), flags)
    : Shell_GetArgumentForUnix(str.c_str(), flags);
238 239 240 241 242 243
}

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

263 264
std::string cmOutputConverter::EscapeWindowsShellArgument(const char* arg,
                                                          int shell_flags)
265
{
266
  return Shell_GetArgumentForWindows(arg, shell_flags);
267 268
}

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

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

cmState* cmOutputConverter::GetState() const
{
  return this->StateSnapshot.GetState();
}
298 299 300 301 302 303 304 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

/*

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 == '#') ||
341 342 343
          (c == '&') || (c == '$') || (c == '(') || (c == ')') || (c == '~') ||
          (c == '<') || (c == '>') || (c == '|') || (c == '*') || (c == '^') ||
          (c == '\\'));
344 345 346 347
}

int cmOutputConverter::Shell__CharNeedsQuotesOnWindows(char c)
{
348 349
  return ((c == '\'') || (c == '#') || (c == '&') || (c == '<') ||
          (c == '>') || (c == '|') || (c == '^'));
350 351 352 353 354
}

int cmOutputConverter::Shell__CharNeedsQuotes(char c, int isUnix, int flags)
{
  /* On Windows the built-in command shell echo never needs quotes.  */
355
  if (!isUnix && (flags & Shell_Flag_EchoWindows)) {
356
    return 0;
357
  }
358 359

  /* On all platforms quotes are needed to preserve whitespace.  */
360
  if (Shell__CharIsWhitespace(c)) {
361
    return 1;
362
  }
363

364
  if (isUnix) {
365
    /* On UNIX several special characters need quotes to preserve them.  */
366
    if (Shell__CharNeedsQuotesOnUnix(c)) {
367 368
      return 1;
    }
369
  } else {
370
    /* On Windows several special characters need quotes to preserve them.  */
371
    if (Shell__CharNeedsQuotesOnWindows(c)) {
372 373
      return 1;
    }
374
  }
375 376 377 378 379 380 381 382 383 384
  return 0;
}

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

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

422 423
int cmOutputConverter::Shell__ArgumentNeedsQuotes(const char* in, int isUnix,
                                                  int flags)
424 425
{
  /* The empty string needs quotes.  */
426
  if (!*in) {
427
    return 1;
428
  }
429 430 431

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

447 448 449
        /* Stop if we have reached the end of the string.  */
        if (!*c) {
          break;
450 451 452 453
        }
#endif
      }

454 455 456
      /* Check whether this character needs quotes.  */
      if (Shell__CharNeedsQuotes(*c, isUnix, flags)) {
        return 1;
457 458 459 460 461
      }
    }
  }

  /* On Windows some single character arguments need quotes.  */
462
  if (!isUnix && *in && !*(in + 1)) {
463
    char c = *in;
464
    if ((c == '?') || (c == '&') || (c == '^') || (c == '|') || (c == '#')) {
465 466
      return 1;
    }
467
  }
468 469 470 471

  return 0;
}

472 473
std::string cmOutputConverter::Shell__GetArgument(const char* in, int isUnix,
                                                  int flags)
474
{
475
  std::ostringstream out;
476 477 478 479 480 481 482 483 484

  /* 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);
485
  if (needQuotes) {
486
    /* Add the opening quote for this argument.  */
487 488
    if (flags & Shell_Flag_WatcomQuote) {
      if (isUnix) {
489
        out << '"';
490
      }
491
      out << '\'';
492
    } else {
493
      out << '"';
494
    }
495
  }
496 497

  /* Scan the string for characters that require escaping or quoting.  */
498
  for (c = in; *c; ++c) {
499
    /* Look for $(MAKEVAR) syntax if requested.  */
500
    if (flags & Shell_Flag_AllowMakeVariables) {
501
      const char* skip = Shell__SkipMakeVariables(c);
502
      if (skip != c) {
503
        /* Copy to the end of the make variable references.  */
504
        while (c != skip) {
505
          out << *c++;
506
        }
507 508 509 510 511 512

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

        /* Stop if we have reached the end of the string.  */
513
        if (!*c) {
514 515 516
          break;
        }
      }
517
    }
518 519

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

        /* Add the backslash to escape the double-quote.  */
543
        out << '\\';
544
      } else {
545 546 547 548
        /* We encountered a normal character.  This eliminates any
           escaping needed for preceding backslashes.  */
        windows_backslashes = 0;
      }
549
    }
550 551

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

607
  if (needQuotes) {
608
    /* Add enough backslashes to escape any trailing ones.  */
609
    while (windows_backslashes > 0) {
610
      --windows_backslashes;
611
      out << '\\';
612
    }
613 614

    /* Add the closing quote for this argument.  */
615
    if (flags & Shell_Flag_WatcomQuote) {
616
      out << '\'';
617
      if (isUnix) {
618
        out << '"';
619
      }
620
    } else {
621
      out << '"';
622
    }
623
  }
624

625
  return out.str();
626 627
}

628 629
std::string cmOutputConverter::Shell_GetArgumentForWindows(const char* in,
                                                           int flags)
630
{
631
  return Shell__GetArgument(in, 0, flags);
632 633
}

634 635
std::string cmOutputConverter::Shell_GetArgumentForUnix(const char* in,
                                                        int flags)
636
{
637
  return Shell__GetArgument(in, 1, flags);
638
}