diff options
Diffstat (limited to 'Source/cmGeneratorExpressionNode.cxx')
| -rw-r--r-- | Source/cmGeneratorExpressionNode.cxx | 677 | 
1 files changed, 669 insertions, 8 deletions
diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx index 396e9c9..45d5a83 100644 --- a/Source/cmGeneratorExpressionNode.cxx +++ b/Source/cmGeneratorExpressionNode.cxx @@ -7,10 +7,12 @@  #include <cerrno>  #include <cstdlib>  #include <cstring> +#include <functional>  #include <map>  #include <memory>  #include <set>  #include <sstream> +#include <unordered_map>  #include <utility>  #include <cm/iterator> @@ -24,6 +26,7 @@  #include "cmsys/String.h"  #include "cmAlgorithms.h" +#include "cmCMakePath.h"  #include "cmComputeLinkInformation.h"  #include "cmGeneratorExpression.h"  #include "cmGeneratorExpressionContext.h" @@ -413,7 +416,7 @@ static const struct TargetExistsNode : public cmGeneratorExpressionNode        return std::string();      } -    std::string targetName = parameters.front(); +    std::string const& targetName = parameters.front();      if (targetName.empty() ||          !cmGeneratorExpression::IsValidTargetName(targetName)) {        reportError(context, content->GetOriginalExpression(), @@ -445,7 +448,7 @@ static const struct TargetNameIfExistsNode : public cmGeneratorExpressionNode        return std::string();      } -    std::string targetName = parameters.front(); +    std::string const& targetName = parameters.front();      if (targetName.empty() ||          !cmGeneratorExpression::IsValidTargetName(targetName)) {        reportError(context, content->GetOriginalExpression(), @@ -599,6 +602,455 @@ static const struct UpperCaseNode : public cmGeneratorExpressionNode    }  } upperCaseNode; +namespace { +template <typename Container> +class Range : public cmRange<typename Container::const_iterator> +{ +private: +  using Base = cmRange<typename Container::const_iterator>; + +public: +  using const_iterator = typename Container::const_iterator; +  using value_type = typename Container::value_type; +  using size_type = typename Container::size_type; +  using difference_type = typename Container::difference_type; +  using const_reference = typename Container::const_reference; + +  Range(const Container& container) +    : Base(container.begin(), container.end()) +  { +  } + +  const_reference operator[](size_type pos) const +  { +    return *(this->begin() + pos); +  } + +  const_reference front() const { return *this->begin(); } +  const_reference back() const { return *std::prev(this->end()); } + +  Range& advance(difference_type amount) & +  { +    Base::advance(amount); +    return *this; +  } +  Range advance(difference_type amount) && +  { +    Base::advance(amount); +    return std::move(*this); +  } +}; + +using Arguments = Range<std::vector<std::string>>; + +bool CheckPathParametersEx(cmGeneratorExpressionContext* ctx, +                           const GeneratorExpressionContent* cnt, +                           cm::string_view option, std::size_t count, +                           int required = 1, bool exactly = true) +{ +  if (static_cast<int>(count) < required || +      (exactly && static_cast<int>(count) > required)) { +    reportError(ctx, cnt->GetOriginalExpression(), +                cmStrCat("$<PATH:", option, "> expression requires ", +                         (exactly ? "exactly" : "at least"), ' ', +                         (required == 1 ? "one parameter" : "two parameters"), +                         '.')); +    return false; +  } +  return true; +}; +bool CheckPathParameters(cmGeneratorExpressionContext* ctx, +                         const GeneratorExpressionContent* cnt, +                         cm::string_view option, const Arguments& args, +                         int required = 1) +{ +  return CheckPathParametersEx(ctx, cnt, option, args.size(), required); +}; +std::string ToString(bool isTrue) +{ +  return isTrue ? "1" : "0"; +}; +} + +static const struct PathNode : public cmGeneratorExpressionNode +{ +  PathNode() {} // NOLINT(modernize-use-equals-default) + +  int NumExpectedParameters() const override { return TwoOrMoreParameters; } + +  bool AcceptsArbitraryContentParameter() const override { return true; } + +  std::string Evaluate( +    const std::vector<std::string>& parameters, +    cmGeneratorExpressionContext* context, +    const GeneratorExpressionContent* content, +    cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override +  { +    static std::unordered_map< +      cm::string_view, +      std::function<std::string(cmGeneratorExpressionContext*, +                                const GeneratorExpressionContent*, +                                Arguments&)>> +      pathCommands{ +        { "GET_ROOT_NAME"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "GET_ROOT_NAME"_s, args) && +                !args.front().empty() +              ? cmCMakePath{ args.front() }.GetRootName().String() +              : std::string{}; +          } }, +        { "GET_ROOT_DIRECTORY"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "GET_ROOT_DIRECTORY"_s, +                                       args) && +                !args.front().empty() +              ? cmCMakePath{ args.front() }.GetRootDirectory().String() +              : std::string{}; +          } }, +        { "GET_ROOT_PATH"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "GET_ROOT_PATH"_s, args) && +                !args.front().empty() +              ? cmCMakePath{ args.front() }.GetRootPath().String() +              : std::string{}; +          } }, +        { "GET_FILENAME"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "GET_FILENAME"_s, args) && +                !args.front().empty() +              ? cmCMakePath{ args.front() }.GetFileName().String() +              : std::string{}; +          } }, +        { "GET_EXTENSION"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            bool lastOnly = args.front() == "LAST_ONLY"_s; +            if (lastOnly) { +              args.advance(1); +            } +            if (CheckPathParametersEx(ctx, cnt, +                                      lastOnly ? "GET_EXTENSION,LAST_ONLY"_s +                                               : "GET_EXTENSION"_s, +                                      args.size())) { +              if (args.front().empty()) { +                return std::string{}; +              } +              return lastOnly +                ? cmCMakePath{ args.front() }.GetExtension().String() +                : cmCMakePath{ args.front() }.GetWideExtension().String(); +            } +            return std::string{}; +          } }, +        { "GET_STEM"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            bool lastOnly = args.front() == "LAST_ONLY"_s; +            if (lastOnly) { +              args.advance(1); +            } +            if (CheckPathParametersEx( +                  ctx, cnt, lastOnly ? "GET_STEM,LAST_ONLY"_s : "GET_STEM"_s, +                  args.size())) { +              if (args.front().empty()) { +                return std::string{}; +              } +              return lastOnly +                ? cmCMakePath{ args.front() }.GetStem().String() +                : cmCMakePath{ args.front() }.GetNarrowStem().String(); +            } +            return std::string{}; +          } }, +        { "GET_RELATIVE_PART"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "GET_RELATIVE_PART"_s, +                                       args) && +                !args.front().empty() +              ? cmCMakePath{ args.front() }.GetRelativePath().String() +              : std::string{}; +          } }, +        { "GET_PARENT_PATH"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "GET_PARENT_PATH"_s, args) +              ? cmCMakePath{ args.front() }.GetParentPath().String() +              : std::string{}; +          } }, +        { "HAS_ROOT_NAME"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "HAS_ROOT_NAME"_s, args) +              ? ToString(cmCMakePath{ args.front() }.HasRootName()) +              : std::string{ "0" }; +          } }, +        { "HAS_ROOT_DIRECTORY"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "HAS_ROOT_DIRECTORY"_s, args) +              ? ToString(cmCMakePath{ args.front() }.HasRootDirectory()) +              : std::string{ "0" }; +          } }, +        { "HAS_ROOT_PATH"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "HAS_ROOT_PATH"_s, args) +              ? ToString(cmCMakePath{ args.front() }.HasRootPath()) +              : std::string{ "0" }; +          } }, +        { "HAS_FILENAME"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "HAS_FILENAME"_s, args) +              ? ToString(cmCMakePath{ args.front() }.HasFileName()) +              : std::string{ "0" }; +          } }, +        { "HAS_EXTENSION"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "HAS_EXTENSION"_s, args) && +                !args.front().empty() +              ? ToString(cmCMakePath{ args.front() }.HasExtension()) +              : std::string{ "0" }; +          } }, +        { "HAS_STEM"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "HAS_STEM"_s, args) +              ? ToString(cmCMakePath{ args.front() }.HasStem()) +              : std::string{ "0" }; +          } }, +        { "HAS_RELATIVE_PART"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "HAS_RELATIVE_PART"_s, args) +              ? ToString(cmCMakePath{ args.front() }.HasRelativePath()) +              : std::string{ "0" }; +          } }, +        { "HAS_PARENT_PATH"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "HAS_PARENT_PATH"_s, args) +              ? ToString(cmCMakePath{ args.front() }.HasParentPath()) +              : std::string{ "0" }; +          } }, +        { "IS_ABSOLUTE"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "IS_ABSOLUTE"_s, args) +              ? ToString(cmCMakePath{ args.front() }.IsAbsolute()) +              : std::string{ "0" }; +          } }, +        { "IS_RELATIVE"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "IS_RELATIVE"_s, args) +              ? ToString(cmCMakePath{ args.front() }.IsRelative()) +              : std::string{ "0" }; +          } }, +        { "IS_PREFIX"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            bool normalize = args.front() == "NORMALIZE"_s; +            if (normalize) { +              args.advance(1); +            } +            if (CheckPathParametersEx(ctx, cnt, +                                      normalize ? "IS_PREFIX,NORMALIZE"_s +                                                : "IS_PREFIX"_s, +                                      args.size(), 2)) { +              if (normalize) { +                return ToString(cmCMakePath{ args[0] }.Normal().IsPrefix( +                  cmCMakePath{ args[1] }.Normal())); +              } +              return ToString( +                cmCMakePath{ args[0] }.IsPrefix(cmCMakePath{ args[1] })); +            } +            return std::string{}; +          } }, +        { "CMAKE_PATH"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            bool normalize = args.front() == "NORMALIZE"_s; +            if (normalize) { +              args.advance(1); +            } +            if (CheckPathParametersEx(ctx, cnt, +                                      normalize ? "CMAKE_PATH,NORMALIZE"_s +                                                : "CMAKE_PATH"_s, +                                      args.size(), 1)) { +              auto path = +                cmCMakePath{ args.front(), cmCMakePath::auto_format }; +              return normalize ? path.Normal().GenericString() +                               : path.GenericString(); +            } +            return std::string{}; +          } }, +        { "APPEND"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            if (CheckPathParametersEx(ctx, cnt, "APPEND"_s, args.size(), 1, +                                      false)) { +              cmCMakePath path; +              for (const auto& p : args) { +                path /= p; +              } +              return path.String(); +            } +            return std::string{}; +          } }, +        { "REMOVE_FILENAME"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "REMOVE_FILENAME"_s, args) && +                !args.front().empty() +              ? cmCMakePath{ args.front() }.RemoveFileName().String() +              : std::string{}; +          } }, +        { "REPLACE_FILENAME"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "REPLACE_FILENAME"_s, args, 2) +              ? cmCMakePath{ args[0] } +                  .ReplaceFileName(cmCMakePath{ args[1] }) +                  .String() +              : std::string{}; +          } }, +        { "REMOVE_EXTENSION"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            bool lastOnly = args.front() == "LAST_ONLY"_s; +            if (lastOnly) { +              args.advance(1); +            } +            if (CheckPathParametersEx(ctx, cnt, +                                      lastOnly ? "REMOVE_EXTENSION,LAST_ONLY"_s +                                               : "REMOVE_EXTENSION"_s, +                                      args.size())) { +              if (args.front().empty()) { +                return std::string{}; +              } +              return lastOnly +                ? cmCMakePath{ args.front() }.RemoveExtension().String() +                : cmCMakePath{ args.front() }.RemoveWideExtension().String(); +            } +            return std::string{}; +          } }, +        { "REPLACE_EXTENSION"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            bool lastOnly = args.front() == "LAST_ONLY"_s; +            if (lastOnly) { +              args.advance(1); +            } +            if (CheckPathParametersEx(ctx, cnt, +                                      lastOnly +                                        ? "REPLACE_EXTENSION,LAST_ONLY"_s +                                        : "REPLACE_EXTENSION"_s, +                                      args.size(), 2)) { +              if (lastOnly) { +                return cmCMakePath{ args[0] } +                  .ReplaceExtension(cmCMakePath{ args[1] }) +                  .String(); +              } +              return cmCMakePath{ args[0] } +                .ReplaceWideExtension(cmCMakePath{ args[1] }) +                .String(); +            } +            return std::string{}; +          } }, +        { "NORMAL_PATH"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "NORMAL_PATH"_s, args) && +                !args.front().empty() +              ? cmCMakePath{ args.front() }.Normal().String() +              : std::string{}; +          } }, +        { "RELATIVE_PATH"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            return CheckPathParameters(ctx, cnt, "RELATIVE_PATH"_s, args, 2) +              ? cmCMakePath{ args[0] }.Relative(args[1]).String() +              : std::string{}; +          } }, +        { "ABSOLUTE_PATH"_s, +          [](cmGeneratorExpressionContext* ctx, +             const GeneratorExpressionContent* cnt, +             Arguments& args) -> std::string { +            bool normalize = args.front() == "NORMALIZE"_s; +            if (normalize) { +              args.advance(1); +            } +            if (CheckPathParametersEx(ctx, cnt, +                                      normalize ? "ABSOLUTE_PATH,NORMALIZE"_s +                                                : "ABSOLUTE_PATH"_s, +                                      args.size(), 2)) { +              auto path = cmCMakePath{ args[0] }.Absolute(args[1]); +              return normalize ? path.Normal().String() : path.String(); +            } +            return std::string{}; +          } } +      }; + +    if (cm::contains(pathCommands, parameters.front())) { +      auto args = Arguments{ parameters }.advance(1); +      return pathCommands[parameters.front()](context, content, args); +    } + +    reportError(context, content->GetOriginalExpression(), +                cmStrCat(parameters.front(), ": invalid option.")); +    return std::string{}; +  } +} pathNode; + +static const struct PathEqualNode : public cmGeneratorExpressionNode +{ +  PathEqualNode() {} // NOLINT(modernize-use-equals-default) + +  int NumExpectedParameters() const override { return 2; } + +  std::string Evaluate( +    const std::vector<std::string>& parameters, +    cmGeneratorExpressionContext* /*context*/, +    const GeneratorExpressionContent* /*content*/, +    cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override +  { +    return cmCMakePath{ parameters[0] } == cmCMakePath{ parameters[1] } ? "1" +                                                                        : "0"; +  } +} pathEqualNode; +  static const struct MakeCIdentifierNode : public cmGeneratorExpressionNode  {    MakeCIdentifierNode() {} // NOLINT(modernize-use-equals-default) @@ -1198,6 +1650,162 @@ static const struct LinkLanguageAndIdNode : public cmGeneratorExpressionNode    }  } linkLanguageAndIdNode; +static const struct LinkLibraryNode : public cmGeneratorExpressionNode +{ +  LinkLibraryNode() {} // NOLINT(modernize-use-equals-default) + +  int NumExpectedParameters() const override { return OneOrMoreParameters; } + +  std::string Evaluate( +    const std::vector<std::string>& parameters, +    cmGeneratorExpressionContext* context, +    const GeneratorExpressionContent* content, +    cmGeneratorExpressionDAGChecker* dagChecker) const override +  { +    using ForGenex = cmGeneratorExpressionDAGChecker::ForGenex; + +    if (!context->HeadTarget || !dagChecker || +        !dagChecker->EvaluatingLinkLibraries(nullptr, +                                             ForGenex::LINK_LIBRARY)) { +      reportError(context, content->GetOriginalExpression(), +                  "$<LINK_LIBRARY:...> may only be used with binary targets " +                  "to specify link libraries through 'LINK_LIBRARIES', " +                  "'INTERFACE_LINK_LIBRARIES', and " +                  "'INTERFACE_LINK_LIBRARIES_DIRECT' properties."); +      return std::string(); +    } + +    std::vector<std::string> list; +    cmExpandLists(parameters.begin(), parameters.end(), list); +    if (list.empty()) { +      reportError( +        context, content->GetOriginalExpression(), +        "$<LINK_LIBRARY:...> expects a feature name as first argument."); +      return std::string(); +    } +    if (list.size() == 1) { +      // no libraries specified, ignore this genex +      return std::string(); +    } + +    static cmsys::RegularExpression featureNameValidator("^[A-Za-z0-9_]+$"); +    auto const& feature = list.front(); +    if (!featureNameValidator.find(feature)) { +      reportError(context, content->GetOriginalExpression(), +                  cmStrCat("The feature name '", feature, +                           "' contains invalid characters.")); +      return std::string(); +    } + +    const auto LL_BEGIN = cmStrCat("<LINK_LIBRARY:", feature, '>'); +    const auto LL_END = cmStrCat("</LINK_LIBRARY:", feature, '>'); + +    // filter out $<LINK_LIBRARY:..> tags with same feature +    // and raise an error for any different feature +    cm::erase_if(list, [&](const std::string& item) -> bool { +      return item == LL_BEGIN || item == LL_END; +    }); +    auto it = +      std::find_if(list.cbegin() + 1, list.cend(), +                   [&feature](const std::string& item) -> bool { +                     return cmHasPrefix(item, "<LINK_LIBRARY:"_s) && +                       item.substr(14, item.find('>', 14) - 14) != feature; +                   }); +    if (it != list.cend()) { +      reportError( +        context, content->GetOriginalExpression(), +        "$<LINK_LIBRARY:...> with different features cannot be nested."); +      return std::string(); +    } +    // $<LINK_GROUP:...> must not appear as part of $<LINK_LIBRARY:...> +    it = std::find_if(list.cbegin() + 1, list.cend(), +                      [](const std::string& item) -> bool { +                        return cmHasPrefix(item, "<LINK_GROUP:"_s); +                      }); +    if (it != list.cend()) { +      reportError(context, content->GetOriginalExpression(), +                  "$<LINK_GROUP:...> cannot be nested inside a " +                  "$<LINK_LIBRARY:...> expression."); +      return std::string(); +    } + +    list.front() = LL_BEGIN; +    list.push_back(LL_END); + +    return cmJoin(list, ";"_s); +  } +} linkLibraryNode; + +static const struct LinkGroupNode : public cmGeneratorExpressionNode +{ +  LinkGroupNode() {} // NOLINT(modernize-use-equals-default) + +  int NumExpectedParameters() const override { return OneOrMoreParameters; } + +  std::string Evaluate( +    const std::vector<std::string>& parameters, +    cmGeneratorExpressionContext* context, +    const GeneratorExpressionContent* content, +    cmGeneratorExpressionDAGChecker* dagChecker) const override +  { +    using ForGenex = cmGeneratorExpressionDAGChecker::ForGenex; + +    if (!context->HeadTarget || !dagChecker || +        !dagChecker->EvaluatingLinkLibraries(nullptr, ForGenex::LINK_GROUP)) { +      reportError( +        context, content->GetOriginalExpression(), +        "$<LINK_GROUP:...> may only be used with binary targets " +        "to specify group of link libraries through 'LINK_LIBRARIES', " +        "'INTERFACE_LINK_LIBRARIES', and " +        "'INTERFACE_LINK_LIBRARIES_DIRECT' properties."); +      return std::string(); +    } + +    std::vector<std::string> list; +    cmExpandLists(parameters.begin(), parameters.end(), list); +    if (list.empty()) { +      reportError( +        context, content->GetOriginalExpression(), +        "$<LINK_GROUP:...> expects a feature name as first argument."); +      return std::string(); +    } +    // $<LINK_GROUP:..> cannot be nested +    if (std::find_if(list.cbegin(), list.cend(), +                     [](const std::string& item) -> bool { +                       return cmHasPrefix(item, "<LINK_GROUP"_s); +                     }) != list.cend()) { +      reportError(context, content->GetOriginalExpression(), +                  "$<LINK_GROUP:...> cannot be nested."); +      return std::string(); +    } +    if (list.size() == 1) { +      // no libraries specified, ignore this genex +      return std::string(); +    } + +    static cmsys::RegularExpression featureNameValidator("^[A-Za-z0-9_]+$"); +    auto const& feature = list.front(); +    if (!featureNameValidator.find(feature)) { +      reportError(context, content->GetOriginalExpression(), +                  cmStrCat("The feature name '", feature, +                           "' contains invalid characters.")); +      return std::string(); +    } + +    const auto LG_BEGIN = cmStrCat( +      "<LINK_GROUP:", feature, ':', +      cmJoin(cmRange<decltype(list.cbegin())>(list.cbegin() + 1, list.cend()), +             "|"_s), +      '>'); +    const auto LG_END = cmStrCat("</LINK_GROUP:", feature, '>'); + +    list.front() = LG_BEGIN; +    list.push_back(LG_END); + +    return cmJoin(list, ";"_s); +  } +} linkGroupNode; +  static const struct HostLinkNode : public cmGeneratorExpressionNode  {    HostLinkNode() {} // NOLINT(modernize-use-equals-default) @@ -1269,7 +1877,8 @@ static std::string getLinkedTargetsContent(  {    std::string result;    if (cmLinkImplementationLibraries const* impl = -        target->GetLinkImplementationLibraries(context->Config)) { +        target->GetLinkImplementationLibraries( +          context->Config, cmGeneratorTarget::LinkInterfaceFor::Usage)) {      for (cmLinkImplItem const& lib : impl->Libraries) {        if (lib.Target) {          // Pretend $<TARGET_PROPERTY:lib.Target,prop> appeared in our @@ -1612,7 +2221,7 @@ static const struct TargetObjectsNode : public cmGeneratorExpressionNode      const GeneratorExpressionContent* content,      cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override    { -    std::string tgtName = parameters.front(); +    std::string const& tgtName = parameters.front();      cmGeneratorTarget* gt = context->LG->FindGeneratorTargetToUse(tgtName);      if (!gt) {        std::ostringstream e; @@ -1638,7 +2247,7 @@ static const struct TargetObjectsNode : public cmGeneratorExpressionNode      {        std::string reason;        if (!context->EvaluateForBuildsystem && -          !gg->HasKnownObjectFileLocation(&reason)) { +          !gt->Target->HasKnownObjectFileLocation(&reason)) {          std::ostringstream e;          e << "The evaluation of the TARGET_OBJECTS generator expression "               "is only suitable for consumption by CMake (limited" @@ -1700,7 +2309,7 @@ static const struct TargetRuntimeDllsNode : public cmGeneratorExpressionNode      const GeneratorExpressionContent* content,      cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override    { -    std::string tgtName = parameters.front(); +    std::string const& tgtName = parameters.front();      cmGeneratorTarget* gt = context->LG->FindGeneratorTargetToUse(tgtName);      if (!gt) {        std::ostringstream e; @@ -1951,6 +2560,7 @@ class ArtifactPathTag;  class ArtifactPdbTag;  class ArtifactSonameTag;  class ArtifactBundleDirTag; +class ArtifactBundleDirNameTag;  class ArtifactBundleContentDirTag;  template <typename ArtifactT, typename ComponentT> @@ -2011,6 +2621,12 @@ struct TargetFilesystemArtifactDependency<ArtifactBundleDirTag,  {  };  template <> +struct TargetFilesystemArtifactDependency<ArtifactBundleDirNameTag, +                                          ArtifactPathTag> +  : TargetFilesystemArtifactDependencyCMP0112 +{ +}; +template <>  struct TargetFilesystemArtifactDependency<ArtifactBundleContentDirTag,                                            ArtifactPathTag>    : TargetFilesystemArtifactDependencyCMP0112 @@ -2138,6 +2754,41 @@ struct TargetFilesystemArtifactResultCreator<ArtifactBundleDirTag>  };  template <> +struct TargetFilesystemArtifactResultCreator<ArtifactBundleDirNameTag> +{ +  static std::string Create(cmGeneratorTarget* target, +                            cmGeneratorExpressionContext* context, +                            const GeneratorExpressionContent* content) +  { +    if (target->IsImported()) { +      ::reportError( +        context, content->GetOriginalExpression(), +        "TARGET_BUNDLE_DIR_NAME not allowed for IMPORTED targets."); +      return std::string(); +    } +    if (!target->IsBundleOnApple()) { +      ::reportError( +        context, content->GetOriginalExpression(), +        "TARGET_BUNDLE_DIR_NAME is allowed only for Bundle targets."); +      return std::string(); +    } + +    auto level = cmGeneratorTarget::BundleDirLevel; +    auto config = context->Config; +    if (target->IsAppBundleOnApple()) { +      return target->GetAppBundleDirectory(config, level); +    } +    if (target->IsFrameworkOnApple()) { +      return target->GetFrameworkDirectory(config, level); +    } +    if (target->IsCFBundleOnApple()) { +      return target->GetCFBundleDirectory(config, level); +    } +    return std::string(); +  } +}; + +template <>  struct TargetFilesystemArtifactResultCreator<ArtifactBundleContentDirTag>  {    static std::string Create(cmGeneratorTarget* target, @@ -2217,7 +2868,7 @@ protected:      cmGeneratorExpressionDAGChecker* dagChecker) const    {      // Lookup the referenced target. -    std::string name = parameters.front(); +    std::string const& name = parameters.front();      if (!cmGeneratorExpression::IsValidTargetName(name)) {        ::reportError(context, content->GetOriginalExpression(), @@ -2270,7 +2921,8 @@ struct TargetFilesystemArtifact : public TargetArtifactBase        return std::string();      }      // Not a dependent target if we are querying for ArtifactDirTag, -    // ArtifactNameTag, ArtifactBundleDirTag, and ArtifactBundleContentDirTag +    // ArtifactNameTag, ArtifactBundleDirTag, ArtifactBundleDirNameTag, +    // and ArtifactBundleContentDirTag      TargetFilesystemArtifactDependency<ArtifactT, ComponentT>::AddDependency(        target, context); @@ -2311,6 +2963,10 @@ static const TargetFilesystemArtifactNodeGroup<ArtifactPdbTag>  static const TargetFilesystemArtifact<ArtifactBundleDirTag, ArtifactPathTag>    targetBundleDirNode; +static const TargetFilesystemArtifact<ArtifactBundleDirNameTag, +                                      ArtifactNameTag> +  targetBundleDirNameNode; +  static const TargetFilesystemArtifact<ArtifactBundleContentDirTag,                                        ArtifactPathTag>    targetBundleContentDirNode; @@ -2636,6 +3292,7 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode(      { "TARGET_SONAME_FILE_DIR", &targetSoNameNodeGroup.FileDir },      { "TARGET_PDB_FILE_DIR", &targetPdbNodeGroup.FileDir },      { "TARGET_BUNDLE_DIR", &targetBundleDirNode }, +    { "TARGET_BUNDLE_DIR_NAME", &targetBundleDirNameNode },      { "TARGET_BUNDLE_CONTENT_DIR", &targetBundleContentDirNode },      { "STREQUAL", &strEqualNode },      { "EQUAL", &equalNode }, @@ -2644,6 +3301,8 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode(      { "REMOVE_DUPLICATES", &removeDuplicatesNode },      { "LOWER_CASE", &lowerCaseNode },      { "UPPER_CASE", &upperCaseNode }, +    { "PATH", &pathNode }, +    { "PATH_EQUAL", &pathEqualNode },      { "MAKE_C_IDENTIFIER", &makeCIdentifierNode },      { "BOOL", &boolNode },      { "IF", &ifNode }, @@ -2668,6 +3327,8 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode(      { "COMPILE_LANGUAGE", &languageNode },      { "LINK_LANG_AND_ID", &linkLanguageAndIdNode },      { "LINK_LANGUAGE", &linkLanguageNode }, +    { "LINK_LIBRARY", &linkLibraryNode }, +    { "LINK_GROUP", &linkGroupNode },      { "HOST_LINK", &hostLinkNode },      { "DEVICE_LINK", &deviceLinkNode },      { "SHELL_PATH", &shellPathNode }  | 
