| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file Copyright.txt or https://cmake.org/licensing for details. */
- #include "cmOutputConverter.h"
- #include <algorithm>
- #include <cassert>
- #include <cctype>
- #include <set>
- #include <vector>
- #ifdef _WIN32
- # include <unordered_map>
- # include <utility>
- #endif
- #include "cmState.h"
- #include "cmStateDirectory.h"
- #include "cmStringAlgorithms.h"
- #include "cmSystemTools.h"
- #include "cmValue.h"
- namespace {
- bool PathEqOrSubDir(std::string const& a, std::string const& b)
- {
- return (cmSystemTools::ComparePath(a, b) ||
- cmSystemTools::IsSubDirectory(a, b));
- }
- }
- cmOutputConverter::cmOutputConverter(cmStateSnapshot const& snapshot)
- : StateSnapshot(snapshot)
- , LinkScriptShell(false)
- {
- assert(this->StateSnapshot.IsValid());
- this->ComputeRelativePathTopSource();
- this->ComputeRelativePathTopBinary();
- }
- void cmOutputConverter::ComputeRelativePathTopSource()
- {
- // Walk up the buildsystem directory tree to find the highest source
- // directory that contains the current source directory.
- cmStateSnapshot snapshot = this->StateSnapshot;
- for (cmStateSnapshot parent = snapshot.GetBuildsystemDirectoryParent();
- parent.IsValid(); parent = parent.GetBuildsystemDirectoryParent()) {
- if (cmSystemTools::IsSubDirectory(
- snapshot.GetDirectory().GetCurrentSource(),
- parent.GetDirectory().GetCurrentSource())) {
- snapshot = parent;
- }
- }
- this->RelativePathTopSource = snapshot.GetDirectory().GetCurrentSource();
- }
- void cmOutputConverter::ComputeRelativePathTopBinary()
- {
- // Walk up the buildsystem directory tree to find the highest binary
- // directory that contains the current binary directory.
- cmStateSnapshot snapshot = this->StateSnapshot;
- for (cmStateSnapshot parent = snapshot.GetBuildsystemDirectoryParent();
- parent.IsValid(); parent = parent.GetBuildsystemDirectoryParent()) {
- if (cmSystemTools::IsSubDirectory(
- snapshot.GetDirectory().GetCurrentBinary(),
- parent.GetDirectory().GetCurrentBinary())) {
- snapshot = parent;
- }
- }
- this->RelativePathTopBinary = snapshot.GetDirectory().GetCurrentBinary();
- }
- std::string const& cmOutputConverter::GetRelativePathTopSource() const
- {
- return this->RelativePathTopSource;
- }
- std::string const& cmOutputConverter::GetRelativePathTopBinary() const
- {
- return this->RelativePathTopBinary;
- }
- void cmOutputConverter::SetRelativePathTop(std::string const& topSource,
- std::string const& topBinary)
- {
- this->RelativePathTopSource = topSource;
- this->RelativePathTopBinary = topBinary;
- }
- std::string cmOutputConverter::MaybeRelativeTo(
- std::string const& local_path, std::string const& remote_path) const
- {
- bool bothInBinary =
- PathEqOrSubDir(local_path, this->RelativePathTopBinary) &&
- PathEqOrSubDir(remote_path, this->RelativePathTopBinary);
- bool bothInSource =
- PathEqOrSubDir(local_path, this->RelativePathTopSource) &&
- PathEqOrSubDir(remote_path, this->RelativePathTopSource);
- if (bothInBinary || bothInSource) {
- return cmSystemTools::ForceToRelativePath(local_path, remote_path);
- }
- return remote_path;
- }
- std::string cmOutputConverter::MaybeRelativeToTopBinDir(
- std::string const& path) const
- {
- return this->MaybeRelativeTo(this->GetState()->GetBinaryDirectory(), path);
- }
- std::string cmOutputConverter::MaybeRelativeToCurBinDir(
- std::string const& path) const
- {
- return this->MaybeRelativeTo(
- this->StateSnapshot.GetDirectory().GetCurrentBinary(), path);
- }
- std::string cmOutputConverter::ConvertToOutputForExisting(
- const std::string& remote, OutputFormat format) const
- {
- #ifdef _WIN32
- // Cache the Short Paths since we only convert the same few paths anyway and
- // calling `GetShortPathNameW` is really expensive.
- static std::unordered_map<std::string, std::string> shortPathCache{};
- // 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.
- if (this->GetState()->UseWindowsShell() &&
- remote.find_first_of(" #") != std::string::npos &&
- cmSystemTools::FileExists(remote)) {
- std::string shortPath = [&]() {
- auto cachedShortPathIt = shortPathCache.find(remote);
- if (cachedShortPathIt != shortPathCache.end()) {
- return cachedShortPathIt->second;
- }
- std::string tmp{};
- cmSystemTools::GetShortPath(remote, tmp);
- shortPathCache[remote] = tmp;
- return tmp;
- }();
- return this->ConvertToOutputFormat(shortPath, format);
- }
- #endif
- // Otherwise, perform standard conversion.
- return this->ConvertToOutputFormat(remote, format);
- }
- std::string cmOutputConverter::ConvertToOutputFormat(cm::string_view source,
- OutputFormat output) const
- {
- std::string result(source);
- // Convert it to an output path.
- if (output == SHELL || output == WATCOMQUOTE || output == NINJAMULTI) {
- result = this->ConvertDirectorySeparatorsForShell(source);
- result = this->EscapeForShell(result, true, false, output == WATCOMQUOTE,
- output == NINJAMULTI);
- } else if (output == RESPONSE) {
- result = this->EscapeForShell(result, false, false, false, false, true);
- }
- return result;
- }
- std::string cmOutputConverter::ConvertDirectorySeparatorsForShell(
- cm::string_view source) const
- {
- 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.
- if (this->GetState()->UseMSYSShell() && !this->LinkScriptShell) {
- if (result.size() > 2 && result[1] == ':') {
- result[1] = result[0];
- result[0] = '/';
- }
- }
- if (this->GetState()->UseWindowsShell()) {
- std::replace(result.begin(), result.end(), '/', '\\');
- }
- return result;
- }
- static bool cmOutputConverterIsShellOperator(cm::string_view str)
- {
- static std::set<cm::string_view> const shellOperators{
- "<", ">", "<<", ">>", "|", "||", "&&", "&>", "1>", "2>", "2>&1", "1>&2"
- };
- return (shellOperators.count(str) != 0);
- }
- std::string cmOutputConverter::EscapeForShell(cm::string_view str,
- bool makeVars, bool forEcho,
- bool useWatcomQuote,
- bool unescapeNinjaConfiguration,
- bool forResponse) const
- {
- // Do not escape shell operators.
- if (cmOutputConverterIsShellOperator(str)) {
- return std::string(str);
- }
- // Compute the flags for the target shell environment.
- int flags = 0;
- if (this->GetState()->UseWindowsVSIDE()) {
- flags |= Shell_Flag_VSIDE;
- } else if (!this->LinkScriptShell) {
- flags |= Shell_Flag_Make;
- }
- if (unescapeNinjaConfiguration) {
- flags |= Shell_Flag_UnescapeNinjaConfiguration;
- }
- if (makeVars) {
- flags |= Shell_Flag_AllowMakeVariables;
- }
- if (forEcho) {
- flags |= Shell_Flag_EchoWindows;
- }
- if (useWatcomQuote) {
- flags |= Shell_Flag_WatcomQuote;
- }
- if (forResponse) {
- flags |= Shell_Flag_IsResponse;
- }
- if (this->GetState()->UseWatcomWMake()) {
- flags |= Shell_Flag_WatcomWMake;
- }
- if (this->GetState()->UseMinGWMake()) {
- flags |= Shell_Flag_MinGWMake;
- }
- if (this->GetState()->UseNMake()) {
- flags |= Shell_Flag_NMake;
- }
- if (!this->GetState()->UseWindowsShell()) {
- flags |= Shell_Flag_IsUnix;
- }
- return Shell_GetArgument(str, flags);
- }
- std::string cmOutputConverter::EscapeForCMake(cm::string_view str,
- WrapQuotes wrapQuotes)
- {
- // Always double-quote the argument to take care of most escapes.
- std::string result = (wrapQuotes == WrapQuotes::Wrap) ? "\"" : "";
- for (const char c : str) {
- if (c == '"') {
- // Escape the double quote to avoid ending the argument.
- result += "\\\"";
- } else if (c == '$') {
- // Escape the dollar to avoid expanding variables.
- result += "\\$";
- } else if (c == '\\') {
- // Escape the backslash to avoid other escapes.
- result += "\\\\";
- } else {
- // Other characters will be parsed correctly.
- result += c;
- }
- }
- if (wrapQuotes == WrapQuotes::Wrap) {
- result += "\"";
- }
- return result;
- }
- std::string cmOutputConverter::EscapeWindowsShellArgument(cm::string_view arg,
- int shell_flags)
- {
- return Shell_GetArgument(arg, shell_flags);
- }
- cmOutputConverter::FortranFormat cmOutputConverter::GetFortranFormat(
- cm::string_view value)
- {
- FortranFormat format = FortranFormatNone;
- if (!value.empty()) {
- for (std::string const& fi : cmExpandedList(value)) {
- if (fi == "FIXED") {
- format = FortranFormatFixed;
- }
- if (fi == "FREE") {
- format = FortranFormatFree;
- }
- }
- }
- return format;
- }
- cmOutputConverter::FortranPreprocess cmOutputConverter::GetFortranPreprocess(
- cm::string_view value)
- {
- if (value.empty()) {
- return FortranPreprocess::Unset;
- }
- return cmIsOn(value) ? FortranPreprocess::Needed
- : FortranPreprocess::NotNeeded;
- }
- void cmOutputConverter::SetLinkScriptShell(bool linkScriptShell)
- {
- this->LinkScriptShell = linkScriptShell;
- }
- cmState* cmOutputConverter::GetState() const
- {
- return this->StateSnapshot.GetState();
- }
- /*
- 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 (^^).
- */
- /* Some helpers to identify character classes */
- static bool Shell_CharIsWhitespace(char c)
- {
- return ((c == ' ') || (c == '\t'));
- }
- static bool Shell_CharNeedsQuotesOnUnix(char c)
- {
- return ((c == '\'') || (c == '`') || (c == ';') || (c == '#') ||
- (c == '&') || (c == '$') || (c == '(') || (c == ')') || (c == '~') ||
- (c == '<') || (c == '>') || (c == '|') || (c == '*') || (c == '^') ||
- (c == '\\'));
- }
- static bool Shell_CharNeedsQuotesOnWindows(char c)
- {
- return ((c == '\'') || (c == '#') || (c == '&') || (c == '<') ||
- (c == '>') || (c == '|') || (c == '^'));
- }
- static bool Shell_CharIsMakeVariableName(char c)
- {
- return c && (c == '_' || isalpha((static_cast<int>(c))));
- }
- bool cmOutputConverter::Shell_CharNeedsQuotes(char c, int flags)
- {
- /* On Windows the built-in command shell echo never needs quotes. */
- if (!(flags & Shell_Flag_IsUnix) && (flags & Shell_Flag_EchoWindows)) {
- return false;
- }
- /* On all platforms quotes are needed to preserve whitespace. */
- if (Shell_CharIsWhitespace(c)) {
- return true;
- }
- /* Quote hyphens in response files */
- if (flags & Shell_Flag_IsResponse) {
- if (c == '-') {
- return true;
- }
- }
- if (flags & Shell_Flag_IsUnix) {
- /* On UNIX several special characters need quotes to preserve them. */
- if (Shell_CharNeedsQuotesOnUnix(c)) {
- return true;
- }
- } else {
- /* On Windows several special characters need quotes to preserve them. */
- if (Shell_CharNeedsQuotesOnWindows(c)) {
- return true;
- }
- }
- return false;
- }
- cm::string_view::iterator cmOutputConverter::Shell_SkipMakeVariables(
- cm::string_view::iterator c, cm::string_view::iterator end)
- {
- while ((c != end && (c + 1) != end) && (*c == '$' && *(c + 1) == '(')) {
- cm::string_view::iterator skip = c + 2;
- while ((skip != end) && Shell_CharIsMakeVariableName(*skip)) {
- ++skip;
- }
- if ((skip != end) && *skip == ')') {
- c = skip + 1;
- } else {
- break;
- }
- }
- 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
- bool cmOutputConverter::Shell_ArgumentNeedsQuotes(cm::string_view in,
- int flags)
- {
- /* The empty string needs quotes. */
- if (in.empty()) {
- return true;
- }
- /* Scan the string for characters that require quoting. */
- for (cm::string_view::iterator cit = in.begin(), cend = in.end();
- cit != cend; ++cit) {
- /* Look for $(MAKEVAR) syntax if requested. */
- if (flags & Shell_Flag_AllowMakeVariables) {
- #if KWSYS_SYSTEM_SHELL_QUOTE_MAKE_VARIABLES
- cm::string_view::iterator skip = Shell_SkipMakeVariables(cit, cend);
- if (skip != cit) {
- /* We need to quote make variable references to preserve the
- string with contents substituted in its place. */
- return true;
- }
- #else
- /* Skip over the make variable references if any are present. */
- cit = Shell_SkipMakeVariables(cit, cend);
- /* Stop if we have reached the end of the string. */
- if (cit == cend) {
- break;
- }
- #endif
- }
- /* Check whether this character needs quotes. */
- if (Shell_CharNeedsQuotes(*cit, flags)) {
- return true;
- }
- }
- /* On Windows some single character arguments need quotes. */
- if (flags & Shell_Flag_IsUnix && in.size() == 1) {
- char c = in[0];
- if ((c == '?') || (c == '&') || (c == '^') || (c == '|') || (c == '#')) {
- return true;
- }
- }
- return false;
- }
- std::string cmOutputConverter::Shell_GetArgument(cm::string_view in, int flags)
- {
- /* Output will be at least as long as input string. */
- std::string out;
- out.reserve(in.size());
- /* 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, flags);
- if (needQuotes) {
- /* Add the opening quote for this argument. */
- if (flags & Shell_Flag_WatcomQuote) {
- if (flags & Shell_Flag_IsUnix) {
- out += '"';
- }
- out += '\'';
- } else {
- out += '"';
- }
- }
- /* Scan the string for characters that require escaping or quoting. */
- for (cm::string_view::iterator cit = in.begin(), cend = in.end();
- cit != cend; ++cit) {
- /* Look for $(MAKEVAR) syntax if requested. */
- if (flags & Shell_Flag_AllowMakeVariables) {
- cm::string_view::iterator skip = Shell_SkipMakeVariables(cit, cend);
- if (skip != cit) {
- /* Copy to the end of the make variable references. */
- while (cit != skip) {
- out += *cit++;
- }
- /* The make variable reference eliminates any escaping needed
- for preceding backslashes. */
- windows_backslashes = 0;
- /* Stop if we have reached the end of the string. */
- if (cit == cend) {
- break;
- }
- }
- }
- /* Check whether this character needs escaping for the shell. */
- if (flags & Shell_Flag_IsUnix) {
- /* On Unix a few special characters need escaping even inside a
- quoted argument. */
- if (*cit == '\\' || *cit == '"' || *cit == '`' || *cit == '$') {
- /* This character needs a backslash to escape it. */
- out += '\\';
- }
- } else if (flags & Shell_Flag_EchoWindows) {
- /* On Windows the built-in command shell echo never needs escaping. */
- } else {
- /* On Windows only backslashes and double-quotes need escaping. */
- if (*cit == '\\') {
- /* Found a backslash. It may need to be escaped later. */
- ++windows_backslashes;
- } else if (*cit == '"') {
- /* Found a double-quote. Escape all immediately preceding
- backslashes. */
- while (windows_backslashes > 0) {
- --windows_backslashes;
- out += '\\';
- }
- /* Add the backslash to escape the double-quote. */
- out += '\\';
- } else {
- /* We encountered a normal character. This eliminates any
- escaping needed for preceding backslashes. */
- windows_backslashes = 0;
- }
- }
- /* Check whether this character needs escaping for a make tool. */
- if (*cit == '$') {
- if (flags & Shell_Flag_Make) {
- /* In Makefiles a dollar is written $$. The make tool will
- replace it with just $ before passing it to the shell. */
- out += "$$";
- } else if (flags & Shell_Flag_VSIDE) {
- /* 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. */
- out += "\"$\"";
- } else {
- /* Otherwise a dollar is written just $. */
- out += '$';
- }
- } else if (*cit == '#') {
- if ((flags & Shell_Flag_Make) && (flags & Shell_Flag_WatcomWMake)) {
- /* In Watcom WMake makefiles a pound is written $#. The make
- tool will replace it with just # before passing it to the
- shell. */
- out += "$#";
- } else {
- /* Otherwise a pound is written just #. */
- out += '#';
- }
- } else if (*cit == '%') {
- if ((flags & Shell_Flag_VSIDE) ||
- ((flags & Shell_Flag_Make) &&
- ((flags & Shell_Flag_MinGWMake) || (flags & Shell_Flag_NMake)))) {
- /* In the VS IDE, NMake, or MinGW make a percent is written %%. */
- out += "%%";
- } else {
- /* Otherwise a percent is written just %. */
- out += '%';
- }
- } else if (*cit == ';') {
- if (flags & Shell_Flag_VSIDE) {
- /* 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. */
- out += "\";\"";
- } else {
- /* Otherwise a semicolon is written just ;. */
- out += ';';
- }
- } else {
- /* Store this character. */
- out += *cit;
- }
- }
- if (needQuotes) {
- /* Add enough backslashes to escape any trailing ones. */
- while (windows_backslashes > 0) {
- --windows_backslashes;
- out += '\\';
- }
- /* Add the closing quote for this argument. */
- if (flags & Shell_Flag_WatcomQuote) {
- out += '\'';
- if (flags & Shell_Flag_IsUnix) {
- out += '"';
- }
- } else {
- out += '"';
- }
- }
- if (flags & Shell_Flag_UnescapeNinjaConfiguration) {
- std::string ninjaConfigReplace;
- if (flags & Shell_Flag_IsUnix) {
- ninjaConfigReplace += '\\';
- }
- ninjaConfigReplace += "$${CONFIGURATION}";
- cmSystemTools::ReplaceString(out, ninjaConfigReplace, "${CONFIGURATION}");
- }
- return out;
- }
|