688353577d8575ab2807b8039f76cc22efc6520d
[platform/upstream/cmake.git] / Source / cmOutputConverter.cxx
1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 #include "cmOutputConverter.h"
4
5 #include <algorithm>
6 #include <cassert>
7 #include <cctype>
8 #include <set>
9 #include <vector>
10
11 #ifdef _WIN32
12 #  include <unordered_map>
13 #  include <utility>
14 #endif
15
16 #include "cmState.h"
17 #include "cmStateDirectory.h"
18 #include "cmStringAlgorithms.h"
19 #include "cmSystemTools.h"
20 #include "cmValue.h"
21
22 namespace {
23 bool PathEqOrSubDir(std::string const& a, std::string const& b)
24 {
25   return (cmSystemTools::ComparePath(a, b) ||
26           cmSystemTools::IsSubDirectory(a, b));
27 }
28 }
29
30 cmOutputConverter::cmOutputConverter(cmStateSnapshot const& snapshot)
31   : StateSnapshot(snapshot)
32 {
33   assert(this->StateSnapshot.IsValid());
34   this->ComputeRelativePathTopSource();
35   this->ComputeRelativePathTopBinary();
36   this->ComputeRelativePathTopRelation();
37 }
38
39 void cmOutputConverter::ComputeRelativePathTopSource()
40 {
41   // Walk up the buildsystem directory tree to find the highest source
42   // directory that contains the current source directory.
43   cmStateSnapshot snapshot = this->StateSnapshot;
44   for (cmStateSnapshot parent = snapshot.GetBuildsystemDirectoryParent();
45        parent.IsValid(); parent = parent.GetBuildsystemDirectoryParent()) {
46     if (cmSystemTools::IsSubDirectory(
47           snapshot.GetDirectory().GetCurrentSource(),
48           parent.GetDirectory().GetCurrentSource())) {
49       snapshot = parent;
50     }
51   }
52   this->RelativePathTopSource = snapshot.GetDirectory().GetCurrentSource();
53 }
54
55 void cmOutputConverter::ComputeRelativePathTopBinary()
56 {
57   // Walk up the buildsystem directory tree to find the highest binary
58   // directory that contains the current binary directory.
59   cmStateSnapshot snapshot = this->StateSnapshot;
60   for (cmStateSnapshot parent = snapshot.GetBuildsystemDirectoryParent();
61        parent.IsValid(); parent = parent.GetBuildsystemDirectoryParent()) {
62     if (cmSystemTools::IsSubDirectory(
63           snapshot.GetDirectory().GetCurrentBinary(),
64           parent.GetDirectory().GetCurrentBinary())) {
65       snapshot = parent;
66     }
67   }
68
69   this->RelativePathTopBinary = snapshot.GetDirectory().GetCurrentBinary();
70 }
71
72 void cmOutputConverter::ComputeRelativePathTopRelation()
73 {
74   if (cmSystemTools::ComparePath(this->RelativePathTopSource,
75                                  this->RelativePathTopBinary)) {
76     this->RelativePathTopRelation = TopRelation::InSource;
77   } else if (cmSystemTools::IsSubDirectory(this->RelativePathTopBinary,
78                                            this->RelativePathTopSource)) {
79     this->RelativePathTopRelation = TopRelation::BinInSrc;
80   } else if (cmSystemTools::IsSubDirectory(this->RelativePathTopSource,
81                                            this->RelativePathTopBinary)) {
82     this->RelativePathTopRelation = TopRelation::SrcInBin;
83   } else {
84     this->RelativePathTopRelation = TopRelation::Separate;
85   }
86 }
87
88 std::string const& cmOutputConverter::GetRelativePathTopSource() const
89 {
90   return this->RelativePathTopSource;
91 }
92
93 std::string const& cmOutputConverter::GetRelativePathTopBinary() const
94 {
95   return this->RelativePathTopBinary;
96 }
97
98 void cmOutputConverter::SetRelativePathTop(std::string const& topSource,
99                                            std::string const& topBinary)
100 {
101   this->RelativePathTopSource = topSource;
102   this->RelativePathTopBinary = topBinary;
103   this->ComputeRelativePathTopRelation();
104 }
105
106 std::string cmOutputConverter::MaybeRelativeTo(
107   std::string const& local_path, std::string const& remote_path) const
108 {
109   bool localInBinary = PathEqOrSubDir(local_path, this->RelativePathTopBinary);
110   bool remoteInBinary =
111     PathEqOrSubDir(remote_path, this->RelativePathTopBinary);
112
113   bool localInSource = PathEqOrSubDir(local_path, this->RelativePathTopSource);
114   bool remoteInSource =
115     PathEqOrSubDir(remote_path, this->RelativePathTopSource);
116
117   switch (this->RelativePathTopRelation) {
118     case TopRelation::Separate:
119       // Checks are independent.
120       break;
121     case TopRelation::BinInSrc:
122       localInSource = localInSource && !localInBinary;
123       remoteInSource = remoteInSource && !remoteInBinary;
124       break;
125     case TopRelation::SrcInBin:
126       localInBinary = localInBinary && !localInSource;
127       remoteInBinary = remoteInBinary && !remoteInSource;
128       break;
129     case TopRelation::InSource:
130       // Checks are identical.
131       break;
132   };
133
134   bool const bothInBinary = localInBinary && remoteInBinary;
135   bool const bothInSource = localInSource && remoteInSource;
136
137   if (bothInBinary || bothInSource) {
138     return cmSystemTools::ForceToRelativePath(local_path, remote_path);
139   }
140   return remote_path;
141 }
142
143 std::string cmOutputConverter::MaybeRelativeToTopBinDir(
144   std::string const& path) const
145 {
146   return this->MaybeRelativeTo(this->GetState()->GetBinaryDirectory(), path);
147 }
148
149 std::string cmOutputConverter::MaybeRelativeToCurBinDir(
150   std::string const& path) const
151 {
152   return this->MaybeRelativeTo(
153     this->StateSnapshot.GetDirectory().GetCurrentBinary(), path);
154 }
155
156 std::string cmOutputConverter::ConvertToOutputForExisting(
157   const std::string& remote, OutputFormat format) const
158 {
159 #ifdef _WIN32
160   // Cache the Short Paths since we only convert the same few paths anyway and
161   // calling `GetShortPathNameW` is really expensive.
162   static std::unordered_map<std::string, std::string> shortPathCache{};
163
164   // If this is a windows shell, the result has a space, and the path
165   // already exists, we can use a short-path to reference it without a
166   // space.
167   if (this->GetState()->UseWindowsShell() &&
168       remote.find_first_of(" #") != std::string::npos &&
169       cmSystemTools::FileExists(remote)) {
170
171     std::string shortPath = [&]() {
172       auto cachedShortPathIt = shortPathCache.find(remote);
173
174       if (cachedShortPathIt != shortPathCache.end()) {
175         return cachedShortPathIt->second;
176       }
177
178       std::string tmp{};
179       cmSystemTools::GetShortPath(remote, tmp);
180       shortPathCache[remote] = tmp;
181       return tmp;
182     }();
183
184     return this->ConvertToOutputFormat(shortPath, format);
185   }
186 #endif
187
188   // Otherwise, perform standard conversion.
189   return this->ConvertToOutputFormat(remote, format);
190 }
191
192 std::string cmOutputConverter::ConvertToOutputFormat(cm::string_view source,
193                                                      OutputFormat output) const
194 {
195   std::string result(source);
196   // Convert it to an output path.
197   if (output == SHELL || output == WATCOMQUOTE || output == NINJAMULTI) {
198     result = this->ConvertDirectorySeparatorsForShell(source);
199     result = this->EscapeForShell(result, true, false, output == WATCOMQUOTE,
200                                   output == NINJAMULTI);
201   } else if (output == RESPONSE) {
202     result = this->EscapeForShell(result, false, false, false, false, true);
203   }
204   return result;
205 }
206
207 std::string cmOutputConverter::ConvertDirectorySeparatorsForShell(
208   cm::string_view source) const
209 {
210   std::string result(source);
211   // For the MSYS shell convert drive letters to posix paths, so
212   // that c:/some/path becomes /c/some/path.  This is needed to
213   // avoid problems with the shell path translation.
214   if (this->GetState()->UseMSYSShell() && !this->LinkScriptShell) {
215     if (result.size() > 2 && result[1] == ':') {
216       result[1] = result[0];
217       result[0] = '/';
218     }
219   }
220   if (this->GetState()->UseWindowsShell()) {
221     std::replace(result.begin(), result.end(), '/', '\\');
222   }
223   return result;
224 }
225
226 static bool cmOutputConverterIsShellOperator(cm::string_view str)
227 {
228   static std::set<cm::string_view> const shellOperators{
229     "<", ">", "<<", ">>", "|", "||", "&&", "&>", "1>", "2>", "2>&1", "1>&2"
230   };
231   return (shellOperators.count(str) != 0);
232 }
233
234 std::string cmOutputConverter::EscapeForShell(cm::string_view str,
235                                               bool makeVars, bool forEcho,
236                                               bool useWatcomQuote,
237                                               bool unescapeNinjaConfiguration,
238                                               bool forResponse) const
239 {
240   // Do not escape shell operators.
241   if (cmOutputConverterIsShellOperator(str)) {
242     return std::string(str);
243   }
244
245   // Compute the flags for the target shell environment.
246   int flags = 0;
247   if (this->GetState()->UseWindowsVSIDE()) {
248     flags |= Shell_Flag_VSIDE;
249   } else if (!this->LinkScriptShell) {
250     flags |= Shell_Flag_Make;
251   }
252   if (unescapeNinjaConfiguration) {
253     flags |= Shell_Flag_UnescapeNinjaConfiguration;
254   }
255   if (makeVars) {
256     flags |= Shell_Flag_AllowMakeVariables;
257   }
258   if (forEcho) {
259     flags |= Shell_Flag_EchoWindows;
260   }
261   if (useWatcomQuote) {
262     flags |= Shell_Flag_WatcomQuote;
263   }
264   if (forResponse) {
265     flags |= Shell_Flag_IsResponse;
266   }
267   if (this->GetState()->UseWatcomWMake()) {
268     flags |= Shell_Flag_WatcomWMake;
269   }
270   if (this->GetState()->UseMinGWMake()) {
271     flags |= Shell_Flag_MinGWMake;
272   }
273   if (this->GetState()->UseNMake()) {
274     flags |= Shell_Flag_NMake;
275   }
276   if (!this->GetState()->UseWindowsShell()) {
277     flags |= Shell_Flag_IsUnix;
278   }
279
280   return Shell_GetArgument(str, flags);
281 }
282
283 std::string cmOutputConverter::EscapeForCMake(cm::string_view str,
284                                               WrapQuotes wrapQuotes)
285 {
286   // Always double-quote the argument to take care of most escapes.
287   std::string result = (wrapQuotes == WrapQuotes::Wrap) ? "\"" : "";
288   for (const char c : str) {
289     if (c == '"') {
290       // Escape the double quote to avoid ending the argument.
291       result += "\\\"";
292     } else if (c == '$') {
293       // Escape the dollar to avoid expanding variables.
294       result += "\\$";
295     } else if (c == '\\') {
296       // Escape the backslash to avoid other escapes.
297       result += "\\\\";
298     } else {
299       // Other characters will be parsed correctly.
300       result += c;
301     }
302   }
303   if (wrapQuotes == WrapQuotes::Wrap) {
304     result += "\"";
305   }
306   return result;
307 }
308
309 std::string cmOutputConverter::EscapeWindowsShellArgument(cm::string_view arg,
310                                                           int shell_flags)
311 {
312   return Shell_GetArgument(arg, shell_flags);
313 }
314
315 cmOutputConverter::FortranFormat cmOutputConverter::GetFortranFormat(
316   cm::string_view value)
317 {
318   FortranFormat format = FortranFormatNone;
319   if (!value.empty()) {
320     for (std::string const& fi : cmExpandedList(value)) {
321       if (fi == "FIXED") {
322         format = FortranFormatFixed;
323       }
324       if (fi == "FREE") {
325         format = FortranFormatFree;
326       }
327     }
328   }
329   return format;
330 }
331
332 cmOutputConverter::FortranPreprocess cmOutputConverter::GetFortranPreprocess(
333   cm::string_view value)
334 {
335   if (value.empty()) {
336     return FortranPreprocess::Unset;
337   }
338
339   return cmIsOn(value) ? FortranPreprocess::Needed
340                        : FortranPreprocess::NotNeeded;
341 }
342
343 void cmOutputConverter::SetLinkScriptShell(bool linkScriptShell)
344 {
345   this->LinkScriptShell = linkScriptShell;
346 }
347
348 cmState* cmOutputConverter::GetState() const
349 {
350   return this->StateSnapshot.GetState();
351 }
352
353 /*
354
355 Notes:
356
357 Make variable replacements open a can of worms.  Sometimes they should
358 be quoted and sometimes not.  Sometimes their replacement values are
359 already quoted.
360
361 VS variables cause problems.  In order to pass the referenced value
362 with spaces the reference must be quoted.  If the variable value ends
363 in a backslash then it will escape the ending quote!  In order to make
364 the ending backslash appear we need this:
365
366   "$(InputDir)\"
367
368 However if there is not a trailing backslash then this will put a
369 quote in the value so we need:
370
371   "$(InputDir)"
372
373 Make variable references are platform specific so we should probably
374 just NOT quote them and let the listfile author deal with it.
375
376 */
377
378 /*
379 TODO: For windows echo:
380
381 To display a pipe (|) or redirection character (< or >) when using the
382 echo command, use a caret character immediately before the pipe or
383 redirection character (for example, ^>, ^<, or ^| ). If you need to
384 use the caret character itself (^), use two in a row (^^).
385 */
386
387 /* Some helpers to identify character classes */
388 static bool Shell_CharIsWhitespace(char c)
389 {
390   return ((c == ' ') || (c == '\t'));
391 }
392
393 static bool Shell_CharNeedsQuotesOnUnix(char c)
394 {
395   return ((c == '\'') || (c == '`') || (c == ';') || (c == '#') ||
396           (c == '&') || (c == '$') || (c == '(') || (c == ')') || (c == '~') ||
397           (c == '<') || (c == '>') || (c == '|') || (c == '*') || (c == '^') ||
398           (c == '\\'));
399 }
400
401 static bool Shell_CharNeedsQuotesOnWindows(char c)
402 {
403   return ((c == '\'') || (c == '#') || (c == '&') || (c == '<') ||
404           (c == '>') || (c == '|') || (c == '^'));
405 }
406
407 static bool Shell_CharIsMakeVariableName(char c)
408 {
409   return c && (c == '_' || isalpha((static_cast<int>(c))));
410 }
411
412 bool cmOutputConverter::Shell_CharNeedsQuotes(char c, int flags)
413 {
414   /* On Windows the built-in command shell echo never needs quotes.  */
415   if (!(flags & Shell_Flag_IsUnix) && (flags & Shell_Flag_EchoWindows)) {
416     return false;
417   }
418
419   /* On all platforms quotes are needed to preserve whitespace.  */
420   if (Shell_CharIsWhitespace(c)) {
421     return true;
422   }
423
424   /* Quote hyphens in response files */
425   if (flags & Shell_Flag_IsResponse) {
426     if (c == '-') {
427       return true;
428     }
429   }
430
431   if (flags & Shell_Flag_IsUnix) {
432     /* On UNIX several special characters need quotes to preserve them.  */
433     if (Shell_CharNeedsQuotesOnUnix(c)) {
434       return true;
435     }
436   } else {
437     /* On Windows several special characters need quotes to preserve them.  */
438     if (Shell_CharNeedsQuotesOnWindows(c)) {
439       return true;
440     }
441   }
442   return false;
443 }
444
445 cm::string_view::iterator cmOutputConverter::Shell_SkipMakeVariables(
446   cm::string_view::iterator c, cm::string_view::iterator end)
447 {
448   while ((c != end && (c + 1) != end) && (*c == '$' && *(c + 1) == '(')) {
449     cm::string_view::iterator skip = c + 2;
450     while ((skip != end) && Shell_CharIsMakeVariableName(*skip)) {
451       ++skip;
452     }
453     if ((skip != end) && *skip == ')') {
454       c = skip + 1;
455     } else {
456       break;
457     }
458   }
459   return c;
460 }
461
462 /*
463 Allowing make variable replacements opens a can of worms.  Sometimes
464 they should be quoted and sometimes not.  Sometimes their replacement
465 values are already quoted or contain escapes.
466
467 Some Visual Studio variables cause problems.  In order to pass the
468 referenced value with spaces the reference must be quoted.  If the
469 variable value ends in a backslash then it will escape the ending
470 quote!  In order to make the ending backslash appear we need this:
471
472   "$(InputDir)\"
473
474 However if there is not a trailing backslash then this will put a
475 quote in the value so we need:
476
477   "$(InputDir)"
478
479 This macro decides whether we quote an argument just because it
480 contains a make variable reference.  This should be replaced with a
481 flag later when we understand applications of this better.
482 */
483 #define KWSYS_SYSTEM_SHELL_QUOTE_MAKE_VARIABLES 0
484
485 bool cmOutputConverter::Shell_ArgumentNeedsQuotes(cm::string_view in,
486                                                   int flags)
487 {
488   /* The empty string needs quotes.  */
489   if (in.empty()) {
490     return true;
491   }
492
493   /* Scan the string for characters that require quoting.  */
494   for (cm::string_view::iterator cit = in.begin(), cend = in.end();
495        cit != cend; ++cit) {
496     /* Look for $(MAKEVAR) syntax if requested.  */
497     if (flags & Shell_Flag_AllowMakeVariables) {
498 #if KWSYS_SYSTEM_SHELL_QUOTE_MAKE_VARIABLES
499       cm::string_view::iterator skip = Shell_SkipMakeVariables(cit, cend);
500       if (skip != cit) {
501         /* We need to quote make variable references to preserve the
502            string with contents substituted in its place.  */
503         return true;
504       }
505 #else
506       /* Skip over the make variable references if any are present.  */
507       cit = Shell_SkipMakeVariables(cit, cend);
508
509       /* Stop if we have reached the end of the string.  */
510       if (cit == cend) {
511         break;
512       }
513 #endif
514     }
515
516     /* Check whether this character needs quotes.  */
517     if (Shell_CharNeedsQuotes(*cit, flags)) {
518       return true;
519     }
520   }
521
522   /* On Windows some single character arguments need quotes.  */
523   if (flags & Shell_Flag_IsUnix && in.size() == 1) {
524     char c = in[0];
525     if ((c == '?') || (c == '&') || (c == '^') || (c == '|') || (c == '#')) {
526       return true;
527     }
528   }
529
530   return false;
531 }
532
533 std::string cmOutputConverter::Shell_GetArgument(cm::string_view in, int flags)
534 {
535   /* Output will be at least as long as input string.  */
536   std::string out;
537   out.reserve(in.size());
538
539   /* Keep track of how many backslashes have been encountered in a row.  */
540   int windows_backslashes = 0;
541
542   /* Whether the argument must be quoted.  */
543   int needQuotes = Shell_ArgumentNeedsQuotes(in, flags);
544   if (needQuotes) {
545     /* Add the opening quote for this argument.  */
546     if (flags & Shell_Flag_WatcomQuote) {
547       if (flags & Shell_Flag_IsUnix) {
548         out += '"';
549       }
550       out += '\'';
551     } else {
552       out += '"';
553     }
554   }
555
556   /* Scan the string for characters that require escaping or quoting.  */
557   for (cm::string_view::iterator cit = in.begin(), cend = in.end();
558        cit != cend; ++cit) {
559     /* Look for $(MAKEVAR) syntax if requested.  */
560     if (flags & Shell_Flag_AllowMakeVariables) {
561       cm::string_view::iterator skip = Shell_SkipMakeVariables(cit, cend);
562       if (skip != cit) {
563         /* Copy to the end of the make variable references.  */
564         while (cit != skip) {
565           out += *cit++;
566         }
567
568         /* The make variable reference eliminates any escaping needed
569            for preceding backslashes.  */
570         windows_backslashes = 0;
571
572         /* Stop if we have reached the end of the string.  */
573         if (cit == cend) {
574           break;
575         }
576       }
577     }
578
579     /* Check whether this character needs escaping for the shell.  */
580     if (flags & Shell_Flag_IsUnix) {
581       /* On Unix a few special characters need escaping even inside a
582          quoted argument.  */
583       if (*cit == '\\' || *cit == '"' || *cit == '`' || *cit == '$') {
584         /* This character needs a backslash to escape it.  */
585         out += '\\';
586       }
587     } else if (flags & Shell_Flag_EchoWindows) {
588       /* On Windows the built-in command shell echo never needs escaping.  */
589     } else {
590       /* On Windows only backslashes and double-quotes need escaping.  */
591       if (*cit == '\\') {
592         /* Found a backslash.  It may need to be escaped later.  */
593         ++windows_backslashes;
594       } else if (*cit == '"') {
595         /* Found a double-quote.  Escape all immediately preceding
596            backslashes.  */
597         while (windows_backslashes > 0) {
598           --windows_backslashes;
599           out += '\\';
600         }
601
602         /* Add the backslash to escape the double-quote.  */
603         out += '\\';
604       } else {
605         /* We encountered a normal character.  This eliminates any
606            escaping needed for preceding backslashes.  */
607         windows_backslashes = 0;
608       }
609     }
610
611     /* Check whether this character needs escaping for a make tool.  */
612     if (*cit == '$') {
613       if (flags & Shell_Flag_Make) {
614         /* In Makefiles a dollar is written $$.  The make tool will
615            replace it with just $ before passing it to the shell.  */
616         out += "$$";
617       } else if (flags & Shell_Flag_VSIDE) {
618         /* In a VS IDE a dollar is written "$".  If this is written in
619            an un-quoted argument it starts a quoted segment, inserts
620            the $ and ends the segment.  If it is written in a quoted
621            argument it ends quoting, inserts the $ and restarts
622            quoting.  Either way the $ is isolated from surrounding
623            text to avoid looking like a variable reference.  */
624         out += "\"$\"";
625       } else {
626         /* Otherwise a dollar is written just $. */
627         out += '$';
628       }
629     } else if (*cit == '#') {
630       if ((flags & Shell_Flag_Make) && (flags & Shell_Flag_WatcomWMake)) {
631         /* In Watcom WMake makefiles a pound is written $#.  The make
632            tool will replace it with just # before passing it to the
633            shell.  */
634         out += "$#";
635       } else {
636         /* Otherwise a pound is written just #. */
637         out += '#';
638       }
639     } else if (*cit == '%') {
640       if ((flags & Shell_Flag_VSIDE) ||
641           ((flags & Shell_Flag_Make) &&
642            ((flags & Shell_Flag_MinGWMake) || (flags & Shell_Flag_NMake)))) {
643         /* In the VS IDE, NMake, or MinGW make a percent is written %%.  */
644         out += "%%";
645       } else {
646         /* Otherwise a percent is written just %. */
647         out += '%';
648       }
649     } else if (*cit == ';') {
650       if (flags & Shell_Flag_VSIDE) {
651         /* In a VS IDE a semicolon is written ";".  If this is written
652            in an un-quoted argument it starts a quoted segment,
653            inserts the ; and ends the segment.  If it is written in a
654            quoted argument it ends quoting, inserts the ; and restarts
655            quoting.  Either way the ; is isolated.  */
656         out += "\";\"";
657       } else {
658         /* Otherwise a semicolon is written just ;. */
659         out += ';';
660       }
661     } else {
662       /* Store this character.  */
663       out += *cit;
664     }
665   }
666
667   if (needQuotes) {
668     /* Add enough backslashes to escape any trailing ones.  */
669     while (windows_backslashes > 0) {
670       --windows_backslashes;
671       out += '\\';
672     }
673
674     /* Add the closing quote for this argument.  */
675     if (flags & Shell_Flag_WatcomQuote) {
676       out += '\'';
677       if (flags & Shell_Flag_IsUnix) {
678         out += '"';
679       }
680     } else {
681       out += '"';
682     }
683   }
684
685   if (flags & Shell_Flag_UnescapeNinjaConfiguration) {
686     std::string ninjaConfigReplace;
687     if (flags & Shell_Flag_IsUnix) {
688       ninjaConfigReplace += '\\';
689     }
690     ninjaConfigReplace += "$${CONFIGURATION}";
691     cmSystemTools::ReplaceString(out, ninjaConfigReplace, "${CONFIGURATION}");
692   }
693
694   return out;
695 }