Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmGeneratorTarget_TransitiveProperty.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
/* clang-format off */
4
#include "cmGeneratorTarget.h"
5
/* clang-format on */
6
7
#include <map>
8
#include <string>
9
#include <unordered_map>
10
#include <utility>
11
#include <vector>
12
13
#include <cm/memory>
14
#include <cm/optional>
15
#include <cm/string_view>
16
#include <cmext/string_view>
17
18
#include "cmGenExContext.h"
19
#include "cmGenExEvaluation.h"
20
#include "cmGeneratorExpression.h"
21
#include "cmGeneratorExpressionDAGChecker.h"
22
#include "cmGeneratorExpressionNode.h"
23
#include "cmLinkItem.h"
24
#include "cmList.h"
25
#include "cmListFileCache.h"
26
#include "cmLocalGenerator.h"
27
#include "cmPolicies.h"
28
#include "cmStringAlgorithms.h"
29
#include "cmValue.h"
30
31
namespace {
32
using UseTo = cmGeneratorTarget::UseTo;
33
using TransitiveProperty = cmGeneratorTarget::TransitiveProperty;
34
35
bool ComputingLinkLibraries(cmGeneratorExpressionDAGChecker const* dagChecker)
36
0
{
37
0
  return dagChecker && dagChecker->IsComputingLinkLibraries();
38
0
}
39
}
40
41
std::map<cm::string_view, TransitiveProperty> const
42
  cmGeneratorTarget::BuiltinTransitiveProperties = {
43
    { "AUTOMOC_MACRO_NAMES"_s,
44
      { "INTERFACE_AUTOMOC_MACRO_NAMES"_s, UseTo::Compile } },
45
    { "AUTOUIC_OPTIONS"_s, { "INTERFACE_AUTOUIC_OPTIONS"_s, UseTo::Compile } },
46
    { "COMPILE_DEFINITIONS"_s,
47
      { "INTERFACE_COMPILE_DEFINITIONS"_s, UseTo::Compile } },
48
    { "COMPILE_FEATURES"_s,
49
      { "INTERFACE_COMPILE_FEATURES"_s, UseTo::Compile } },
50
    { "COMPILE_OPTIONS"_s, { "INTERFACE_COMPILE_OPTIONS"_s, UseTo::Compile } },
51
    { "INCLUDE_DIRECTORIES"_s,
52
      { "INTERFACE_INCLUDE_DIRECTORIES"_s, UseTo::Compile } },
53
    { "LINK_DEPENDS"_s, { "INTERFACE_LINK_DEPENDS"_s, UseTo::Link } },
54
    { "LINK_DIRECTORIES"_s, { "INTERFACE_LINK_DIRECTORIES"_s, UseTo::Link } },
55
    { "LINK_LIBRARIES"_s, { "INTERFACE_LINK_LIBRARIES"_s, UseTo::Link } },
56
    { "LINK_OPTIONS"_s, { "INTERFACE_LINK_OPTIONS"_s, UseTo::Link } },
57
    { "PRECOMPILE_HEADERS"_s,
58
      { "INTERFACE_PRECOMPILE_HEADERS"_s, UseTo::Compile } },
59
    { "SOURCES"_s, { "INTERFACE_SOURCES"_s, UseTo::Compile } },
60
    { "SYSTEM_INCLUDE_DIRECTORIES"_s,
61
      { "INTERFACE_SYSTEM_INCLUDE_DIRECTORIES"_s, UseTo::Compile } },
62
  };
63
64
bool cmGeneratorTarget::MaybeHaveInterfaceProperty(std::string const& prop,
65
                                                   cm::GenEx::Evaluation* eval,
66
                                                   UseTo usage) const
67
0
{
68
0
  std::string const key = cmStrCat(prop, '@', eval->Context.Config);
69
0
  auto i = this->MaybeInterfacePropertyExists.find(key);
70
0
  if (i == this->MaybeInterfacePropertyExists.end()) {
71
    // Insert an entry now in case there is a cycle.
72
0
    i = this->MaybeInterfacePropertyExists.emplace(key, false).first;
73
0
    bool& maybeInterfaceProp = i->second;
74
75
    // If this target itself has a non-empty property value, we are done.
76
0
    maybeInterfaceProp = cmNonempty(this->GetProperty(prop));
77
78
    // Otherwise, recurse to interface dependencies.
79
0
    if (!maybeInterfaceProp) {
80
0
      cmGeneratorTarget const* headTarget =
81
0
        eval->HeadTarget ? eval->HeadTarget : this;
82
0
      if (cmLinkInterfaceLibraries const* iface =
83
0
            this->GetLinkInterfaceLibraries(eval->Context.Config, headTarget,
84
0
                                            usage)) {
85
0
        if (iface->HadHeadSensitiveCondition) {
86
          // With a different head target we may get to a library with
87
          // this interface property.
88
0
          maybeInterfaceProp = true;
89
0
        } else {
90
          // The transitive interface libraries do not depend on the
91
          // head target, so we can follow them.
92
0
          for (cmLinkItem const& lib : iface->Libraries) {
93
0
            if (lib.Target &&
94
0
                lib.Target->MaybeHaveInterfaceProperty(prop, eval, usage)) {
95
0
              maybeInterfaceProp = true;
96
0
              break;
97
0
            }
98
0
          }
99
0
        }
100
0
      }
101
0
    }
102
0
  }
103
0
  return i->second;
104
0
}
105
106
std::string cmGeneratorTarget::EvaluateInterfaceProperty(
107
  std::string const& prop, cm::GenEx::Evaluation* eval,
108
  cmGeneratorExpressionDAGChecker* dagCheckerParent, UseTo usage) const
109
0
{
110
0
  std::string result;
111
112
  // If the property does not appear transitively at all, we are done.
113
0
  if (!this->MaybeHaveInterfaceProperty(prop, eval, usage)) {
114
0
    return result;
115
0
  }
116
117
  // Evaluate $<TARGET_PROPERTY:this,prop> as if it were compiled.  This is
118
  // a subset of TargetPropertyNode::Evaluate without stringify/parse steps
119
  // but sufficient for transitive interface properties.
120
0
  cmGeneratorExpressionDAGChecker dagChecker{
121
0
    this, prop, nullptr, dagCheckerParent, eval->Context, eval->Backtrace,
122
0
  };
123
0
  switch (dagChecker.Check()) {
124
0
    case cmGeneratorExpressionDAGChecker::SELF_REFERENCE:
125
0
      dagChecker.ReportError(
126
0
        eval, cmStrCat("$<TARGET_PROPERTY:", this->GetName(), ',', prop, '>'));
127
0
      return result;
128
0
    case cmGeneratorExpressionDAGChecker::CYCLIC_REFERENCE:
129
      // No error. We just skip cyclic references.
130
0
    case cmGeneratorExpressionDAGChecker::ALREADY_SEEN:
131
      // No error. We have already seen this transitive property.
132
0
      return result;
133
0
    case cmGeneratorExpressionDAGChecker::DAG:
134
0
      break;
135
0
  }
136
137
0
  cmGeneratorTarget const* headTarget =
138
0
    eval->HeadTarget ? eval->HeadTarget : this;
139
140
0
  if (cmValue p = this->GetProperty(prop)) {
141
0
    result = cmGeneratorExpressionNode::EvaluateDependentExpression(
142
0
      *p, eval, headTarget, &dagChecker, this);
143
0
  }
144
145
0
  if (cmLinkInterfaceLibraries const* iface = this->GetLinkInterfaceLibraries(
146
0
        eval->Context.Config, headTarget, usage)) {
147
0
    eval->HadContextSensitiveCondition = eval->HadContextSensitiveCondition ||
148
0
      iface->HadContextSensitiveCondition;
149
0
    for (cmLinkItem const& lib : iface->Libraries) {
150
      // Broken code can have a target in its own link interface.
151
      // Don't follow such link interface entries so as not to create a
152
      // self-referencing loop.
153
0
      if (lib.Target && lib.Target != this) {
154
        // Pretend $<TARGET_PROPERTY:lib.Target,prop> appeared in the
155
        // above property and hand-evaluate it as if it were compiled.
156
        // Create a context as cmCompiledGeneratorExpression::Evaluate does.
157
0
        cm::GenEx::Evaluation libEval(eval->Context, eval->Quiet, headTarget,
158
0
                                      this, eval->EvaluateForBuildsystem,
159
0
                                      eval->Backtrace);
160
0
        std::string libResult = cmGeneratorExpression::StripEmptyListElements(
161
0
          lib.Target->EvaluateInterfaceProperty(prop, &libEval, &dagChecker,
162
0
                                                usage));
163
0
        if (!libResult.empty()) {
164
0
          if (result.empty()) {
165
0
            result = std::move(libResult);
166
0
          } else {
167
0
            result.reserve(result.size() + 1 + libResult.size());
168
0
            result += ";";
169
0
            result += libResult;
170
0
          }
171
0
        }
172
0
        eval->HadContextSensitiveCondition =
173
0
          eval->HadContextSensitiveCondition ||
174
0
          libEval.HadContextSensitiveCondition;
175
0
        eval->HadHeadSensitiveCondition =
176
0
          eval->HadHeadSensitiveCondition || libEval.HadHeadSensitiveCondition;
177
0
      }
178
0
    }
179
0
  }
180
181
0
  return result;
182
0
}
183
184
cm::optional<cmGeneratorTarget::TransitiveProperty>
185
cmGeneratorTarget::IsTransitiveProperty(
186
  cm::string_view prop, cm::GenEx::Context const& context,
187
  cmGeneratorExpressionDAGChecker const* dagChecker) const
188
0
{
189
0
  cm::optional<TransitiveProperty> result;
190
0
  static cm::string_view const kINTERFACE_ = "INTERFACE_"_s;
191
0
  PropertyFor const propertyFor = cmHasPrefix(prop, kINTERFACE_)
192
0
    ? PropertyFor::Interface
193
0
    : PropertyFor::Build;
194
0
  if (propertyFor == PropertyFor::Interface) {
195
0
    prop = prop.substr(kINTERFACE_.length());
196
0
  }
197
0
  auto i = BuiltinTransitiveProperties.find(prop);
198
0
  if (i != BuiltinTransitiveProperties.end() &&
199
      // Look up CMP0189 in the context where evaluation occurs,
200
      // not where the target was created.
201
0
      context.GetCMP0189() != cmPolicies::NEW && prop == "LINK_LIBRARIES"_s) {
202
0
    i = BuiltinTransitiveProperties.end();
203
0
  }
204
0
  if (i != BuiltinTransitiveProperties.end()) {
205
0
    result = i->second;
206
0
    if (result->Usage != cmGeneratorTarget::UseTo::Compile) {
207
0
      cmPolicies::PolicyStatus cmp0166 =
208
0
        context.LG->GetPolicyStatus(cmPolicies::CMP0166);
209
0
      if ((cmp0166 == cmPolicies::WARN || cmp0166 == cmPolicies::OLD) &&
210
0
          (prop == "LINK_DIRECTORIES"_s || prop == "LINK_DEPENDS"_s ||
211
0
           prop == "LINK_OPTIONS"_s)) {
212
0
        result->Usage = cmGeneratorTarget::UseTo::Compile;
213
0
      }
214
0
    }
215
0
  } else if (!ComputingLinkLibraries(dagChecker)) {
216
    // Honor TRANSITIVE_COMPILE_PROPERTIES and TRANSITIVE_LINK_PROPERTIES
217
    // from the link closure when we are not evaluating the closure itself.
218
0
    CustomTransitiveProperties const& ctp =
219
0
      this->GetCustomTransitiveProperties(context.Config, propertyFor);
220
0
    auto ci = ctp.find(std::string(prop));
221
0
    if (ci != ctp.end()) {
222
0
      result = ci->second;
223
0
    }
224
0
  }
225
0
  return result;
226
0
}
227
228
cmGeneratorTarget::CustomTransitiveProperty::CustomTransitiveProperty(
229
  std::string interfaceName, UseTo usage)
230
0
  : CustomTransitiveProperty(
231
0
      cm::make_unique<std::string>(std::move(interfaceName)), usage)
232
0
{
233
0
}
234
cmGeneratorTarget::CustomTransitiveProperty::CustomTransitiveProperty(
235
  std::unique_ptr<std::string> interfaceNameBuf, UseTo usage)
236
0
  : TransitiveProperty{ *interfaceNameBuf, usage }
237
0
  , InterfaceNameBuf(std::move(interfaceNameBuf))
238
0
{
239
0
}
240
241
void cmGeneratorTarget::CustomTransitiveProperties::Add(cmValue props,
242
                                                        UseTo usage)
243
0
{
244
0
  if (props) {
245
0
    cmList propsList(*props);
246
0
    for (std::string p : propsList) {
247
0
      std::string ip;
248
0
      static cm::string_view const kINTERFACE_ = "INTERFACE_"_s;
249
0
      if (cmHasPrefix(p, kINTERFACE_)) {
250
0
        ip = std::move(p);
251
0
        p = ip.substr(kINTERFACE_.length());
252
0
      } else {
253
0
        ip = cmStrCat(kINTERFACE_, p);
254
0
      }
255
0
      this->emplace(std::move(p),
256
0
                    CustomTransitiveProperty(std::move(ip), usage));
257
0
    }
258
0
  }
259
0
}
260
261
cmGeneratorTarget::CustomTransitiveProperties const&
262
cmGeneratorTarget::GetCustomTransitiveProperties(std::string const& config,
263
                                                 PropertyFor propertyFor) const
264
0
{
265
0
  std::map<std::string, CustomTransitiveProperties>& ctpm =
266
0
    propertyFor == PropertyFor::Build
267
0
    ? this->CustomTransitiveBuildPropertiesMap
268
0
    : this->CustomTransitiveInterfacePropertiesMap;
269
0
  auto i = ctpm.find(config);
270
0
  if (i == ctpm.end()) {
271
0
    CustomTransitiveProperties ctp;
272
0
    auto addTransitiveProperties = [this, &config, propertyFor,
273
0
                                    &ctp](std::string const& tp, UseTo usage) {
274
      // Add transitive properties named by the target itself.
275
0
      ctp.Add(this->GetProperty(tp), usage);
276
      // Add transitive properties named by the target's link dependencies.
277
0
      if (propertyFor == PropertyFor::Build) {
278
0
        for (cmGeneratorTarget const* gt :
279
0
             this->GetLinkImplementationClosure(config, usage)) {
280
0
          ctp.Add(gt->GetProperty(tp), usage);
281
0
        }
282
0
      } else {
283
        // The set of custom transitive INTERFACE_ properties does not
284
        // depend on the consumer.  Use the target as its own head.
285
0
        cmGeneratorTarget const* headTarget = this;
286
0
        for (cmGeneratorTarget const* gt :
287
0
             this->GetLinkInterfaceClosure(config, headTarget, usage)) {
288
0
          ctp.Add(gt->GetProperty(tp), usage);
289
0
        }
290
0
      }
291
0
    };
292
0
    addTransitiveProperties("TRANSITIVE_LINK_PROPERTIES", UseTo::Link);
293
0
    addTransitiveProperties("TRANSITIVE_COMPILE_PROPERTIES", UseTo::Compile);
294
0
    i = ctpm.emplace(config, std::move(ctp)).first;
295
0
  }
296
0
  return i->second;
297
0
}