Coverage Report

Created: 2026-10-28 06:38

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
35.4k
{
37
35.4k
  return dagChecker && dagChecker->IsComputingLinkLibraries();
38
35.4k
}
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
    { "COMPILE_WRAPPERS"_s,
52
      { "INTERFACE_COMPILE_WRAPPERS"_s, UseTo::Compile } },
53
    { "INCLUDE_DIRECTORIES"_s,
54
      { "INTERFACE_INCLUDE_DIRECTORIES"_s, UseTo::Compile } },
55
    { "LINK_DEPENDS"_s, { "INTERFACE_LINK_DEPENDS"_s, UseTo::Link } },
56
    { "LINK_DIRECTORIES"_s, { "INTERFACE_LINK_DIRECTORIES"_s, UseTo::Link } },
57
    { "LINK_LIBRARIES"_s, { "INTERFACE_LINK_LIBRARIES"_s, UseTo::Link } },
58
    { "LINK_OPTIONS"_s, { "INTERFACE_LINK_OPTIONS"_s, UseTo::Link } },
59
    { "LINK_WRAPPERS"_s, { "INTERFACE_LINK_WRAPPERS"_s, UseTo::Link } },
60
    { "PRECOMPILE_HEADERS"_s,
61
      { "INTERFACE_PRECOMPILE_HEADERS"_s, UseTo::Compile } },
62
    { "SOURCES"_s, { "INTERFACE_SOURCES"_s, UseTo::Compile } },
63
    { "SYSTEM_INCLUDE_DIRECTORIES"_s,
64
      { "INTERFACE_SYSTEM_INCLUDE_DIRECTORIES"_s, UseTo::Compile } },
65
  };
66
67
bool cmGeneratorTarget::MaybeHaveInterfaceProperty(std::string const& prop,
68
                                                   cm::GenEx::Evaluation* eval,
69
                                                   UseTo usage) const
70
13.4k
{
71
13.4k
  std::string const key = cmStrCat(prop, '@', eval->Context.Config);
72
13.4k
  auto i = this->MaybeInterfacePropertyExists.find(key);
73
13.4k
  if (i == this->MaybeInterfacePropertyExists.end()) {
74
    // Insert an entry now in case there is a cycle.
75
8.33k
    i = this->MaybeInterfacePropertyExists.emplace(key, false).first;
76
8.33k
    bool& maybeInterfaceProp = i->second;
77
78
    // If this target itself has a non-empty property value, we are done.
79
8.33k
    maybeInterfaceProp = cmNonempty(this->GetProperty(prop));
80
81
    // Otherwise, recurse to interface dependencies.
82
8.33k
    if (!maybeInterfaceProp) {
83
6.40k
      cmGeneratorTarget const* headTarget =
84
6.40k
        eval->HeadTarget ? eval->HeadTarget : this;
85
6.40k
      if (cmLinkInterfaceLibraries const* iface =
86
6.40k
            this->GetLinkInterfaceLibraries(eval->Context.Config, headTarget,
87
6.40k
                                            usage)) {
88
6.40k
        if (iface->HadHeadSensitiveCondition) {
89
          // With a different head target we may get to a library with
90
          // this interface property.
91
146
          maybeInterfaceProp = true;
92
6.25k
        } else {
93
          // The transitive interface libraries do not depend on the
94
          // head target, so we can follow them.
95
17.9k
          for (cmLinkItem const& lib : iface->Libraries) {
96
17.9k
            if (lib.Target &&
97
3.75k
                lib.Target->MaybeHaveInterfaceProperty(prop, eval, usage)) {
98
1.39k
              maybeInterfaceProp = true;
99
1.39k
              break;
100
1.39k
            }
101
17.9k
          }
102
6.25k
        }
103
6.40k
      }
104
6.40k
    }
105
8.33k
  }
106
13.4k
  return i->second;
107
13.4k
}
108
109
std::string cmGeneratorTarget::EvaluateInterfaceProperty(
110
  std::string const& prop, cm::GenEx::Evaluation* eval,
111
  cmGeneratorExpressionDAGChecker* dagCheckerParent, UseTo usage) const
112
9.68k
{
113
9.68k
  std::string result;
114
115
  // If the property does not appear transitively at all, we are done.
116
9.68k
  if (!this->MaybeHaveInterfaceProperty(prop, eval, usage)) {
117
3.69k
    return result;
118
3.69k
  }
119
120
  // Evaluate $<TARGET_PROPERTY:this,prop> as if it were compiled.  This is
121
  // a subset of TargetPropertyNode::Evaluate without stringify/parse steps
122
  // but sufficient for transitive interface properties.
123
5.99k
  cmGeneratorExpressionDAGChecker dagChecker{
124
5.99k
    this, prop, nullptr, dagCheckerParent, eval->Context, eval->Backtrace,
125
5.99k
  };
126
5.99k
  switch (dagChecker.Check()) {
127
0
    case cmGeneratorExpressionDAGChecker::SELF_REFERENCE:
128
0
      dagChecker.ReportError(
129
0
        eval, cmStrCat("$<TARGET_PROPERTY:", this->GetName(), ',', prop, '>'));
130
0
      return result;
131
31
    case cmGeneratorExpressionDAGChecker::CYCLIC_REFERENCE:
132
      // No error. We just skip cyclic references.
133
583
    case cmGeneratorExpressionDAGChecker::ALREADY_SEEN:
134
      // No error. We have already seen this transitive property.
135
583
      return result;
136
5.40k
    case cmGeneratorExpressionDAGChecker::DAG:
137
5.40k
      break;
138
5.99k
  }
139
140
5.40k
  cmGeneratorTarget const* headTarget =
141
5.40k
    eval->HeadTarget ? eval->HeadTarget : this;
142
143
5.40k
  if (cmValue p = this->GetProperty(prop)) {
144
3.69k
    result = cmGeneratorExpressionNode::EvaluateDependentExpression(
145
3.69k
      *p, eval, headTarget, &dagChecker, this);
146
3.69k
  }
147
148
5.40k
  if (cmLinkInterfaceLibraries const* iface = this->GetLinkInterfaceLibraries(
149
5.40k
        eval->Context.Config, headTarget, usage)) {
150
5.40k
    eval->HadContextSensitiveCondition = eval->HadContextSensitiveCondition ||
151
5.12k
      iface->HadContextSensitiveCondition;
152
5.88k
    for (cmLinkItem const& lib : iface->Libraries) {
153
      // Broken code can have a target in its own link interface.
154
      // Don't follow such link interface entries so as not to create a
155
      // self-referencing loop.
156
5.88k
      if (lib.Target && lib.Target != this) {
157
        // Pretend $<TARGET_PROPERTY:lib.Target,prop> appeared in the
158
        // above property and hand-evaluate it as if it were compiled.
159
        // Create a context as cmCompiledGeneratorExpression::Evaluate does.
160
3.91k
        cm::GenEx::Evaluation libEval(eval->Context, eval->Quiet, headTarget,
161
3.91k
                                      this, eval->EvaluateForBuildsystem,
162
3.91k
                                      eval->Backtrace);
163
3.91k
        std::string libResult = cmGeneratorExpression::StripEmptyListElements(
164
3.91k
          lib.Target->EvaluateInterfaceProperty(prop, &libEval, &dagChecker,
165
3.91k
                                                usage));
166
3.91k
        if (!libResult.empty()) {
167
1.89k
          if (result.empty()) {
168
1.22k
            result = std::move(libResult);
169
1.22k
          } else {
170
670
            result.reserve(result.size() + 1 + libResult.size());
171
670
            result += ";";
172
670
            result += libResult;
173
670
          }
174
1.89k
        }
175
3.91k
        eval->HadContextSensitiveCondition =
176
3.91k
          eval->HadContextSensitiveCondition ||
177
1.57k
          libEval.HadContextSensitiveCondition;
178
3.91k
        eval->HadHeadSensitiveCondition =
179
3.91k
          eval->HadHeadSensitiveCondition || libEval.HadHeadSensitiveCondition;
180
3.91k
      }
181
5.88k
    }
182
5.40k
  }
183
184
5.40k
  return result;
185
5.99k
}
186
187
cm::optional<cmGeneratorTarget::TransitiveProperty>
188
cmGeneratorTarget::IsTransitiveProperty(
189
  cm::string_view prop, cm::GenEx::Context const& context,
190
  cmGeneratorExpressionDAGChecker const* dagChecker) const
191
47.4k
{
192
47.4k
  cm::optional<TransitiveProperty> result;
193
47.4k
  static cm::string_view const kINTERFACE_ = "INTERFACE_"_s;
194
47.4k
  PropertyFor const propertyFor = cmHasPrefix(prop, kINTERFACE_)
195
47.4k
    ? PropertyFor::Interface
196
47.4k
    : PropertyFor::Build;
197
47.4k
  if (propertyFor == PropertyFor::Interface) {
198
13.8k
    prop = prop.substr(kINTERFACE_.length());
199
13.8k
  }
200
47.4k
  auto i = BuiltinTransitiveProperties.find(prop);
201
47.4k
  if (i != BuiltinTransitiveProperties.end() &&
202
      // Look up CMP0189 in the context where evaluation occurs,
203
      // not where the target was created.
204
39.1k
      context.GetCMP0189() != cmPolicies::NEW && prop == "LINK_LIBRARIES"_s) {
205
27.1k
    i = BuiltinTransitiveProperties.end();
206
27.1k
  }
207
47.4k
  if (i != BuiltinTransitiveProperties.end()) {
208
12.0k
    result = i->second;
209
12.0k
    if (result->Usage != cmGeneratorTarget::UseTo::Compile) {
210
4.34k
      cmPolicies::PolicyStatus cmp0166 =
211
4.34k
        context.LG->GetPolicyStatus(cmPolicies::CMP0166);
212
4.34k
      if ((cmp0166 == cmPolicies::WARN || cmp0166 == cmPolicies::OLD) &&
213
0
          (prop == "LINK_DIRECTORIES"_s || prop == "LINK_DEPENDS"_s ||
214
0
           prop == "LINK_OPTIONS"_s)) {
215
0
        result->Usage = cmGeneratorTarget::UseTo::Compile;
216
0
      }
217
4.34k
    }
218
35.4k
  } else if (!ComputingLinkLibraries(dagChecker)) {
219
    // Honor TRANSITIVE_COMPILE_PROPERTIES and TRANSITIVE_LINK_PROPERTIES
220
    // from the link closure when we are not evaluating the closure itself.
221
13.3k
    CustomTransitiveProperties const& ctp =
222
13.3k
      this->GetCustomTransitiveProperties(context.Config, propertyFor);
223
13.3k
    auto ci = ctp.find(std::string(prop));
224
13.3k
    if (ci != ctp.end()) {
225
382
      result = ci->second;
226
382
    }
227
13.3k
  }
228
47.4k
  return result;
229
47.4k
}
230
231
cmGeneratorTarget::CustomTransitiveProperty::CustomTransitiveProperty(
232
  std::string interfaceName, UseTo usage)
233
809
  : CustomTransitiveProperty(
234
809
      cm::make_unique<std::string>(std::move(interfaceName)), usage)
235
809
{
236
809
}
237
cmGeneratorTarget::CustomTransitiveProperty::CustomTransitiveProperty(
238
  std::unique_ptr<std::string> interfaceNameBuf, UseTo usage)
239
809
  : TransitiveProperty{ *interfaceNameBuf, usage }
240
809
  , InterfaceNameBuf(std::move(interfaceNameBuf))
241
809
{
242
809
}
243
244
void cmGeneratorTarget::CustomTransitiveProperties::Add(cmValue props,
245
                                                        UseTo usage)
246
14.1k
{
247
14.1k
  if (props) {
248
809
    cmList propsList(*props);
249
809
    for (std::string p : propsList) {
250
809
      std::string ip;
251
809
      static cm::string_view const kINTERFACE_ = "INTERFACE_"_s;
252
809
      if (cmHasPrefix(p, kINTERFACE_)) {
253
0
        ip = std::move(p);
254
0
        p = ip.substr(kINTERFACE_.length());
255
809
      } else {
256
809
        ip = cmStrCat(kINTERFACE_, p);
257
809
      }
258
809
      this->emplace(std::move(p),
259
809
                    CustomTransitiveProperty(std::move(ip), usage));
260
809
    }
261
809
  }
262
14.1k
}
263
264
cmGeneratorTarget::CustomTransitiveProperties const&
265
cmGeneratorTarget::GetCustomTransitiveProperties(std::string const& config,
266
                                                 PropertyFor propertyFor) const
267
13.3k
{
268
13.3k
  std::map<std::string, CustomTransitiveProperties>& ctpm =
269
13.3k
    propertyFor == PropertyFor::Build
270
13.3k
    ? this->CustomTransitiveBuildPropertiesMap
271
13.3k
    : this->CustomTransitiveInterfacePropertiesMap;
272
13.3k
  auto i = ctpm.find(config);
273
13.3k
  if (i == ctpm.end()) {
274
4.32k
    CustomTransitiveProperties ctp;
275
4.32k
    auto addTransitiveProperties = [this, &config, propertyFor,
276
8.65k
                                    &ctp](std::string const& tp, UseTo usage) {
277
      // Add transitive properties named by the target itself.
278
8.65k
      ctp.Add(this->GetProperty(tp), usage);
279
      // Add transitive properties named by the target's link dependencies.
280
8.65k
      if (propertyFor == PropertyFor::Build) {
281
6.03k
        for (cmGeneratorTarget const* gt :
282
6.03k
             this->GetLinkImplementationClosure(config, usage)) {
283
5.13k
          ctp.Add(gt->GetProperty(tp), usage);
284
5.13k
        }
285
6.03k
      } else {
286
        // The set of custom transitive INTERFACE_ properties does not
287
        // depend on the consumer.  Use the target as its own head.
288
2.61k
        cmGeneratorTarget const* headTarget = this;
289
2.61k
        for (cmGeneratorTarget const* gt :
290
2.61k
             this->GetLinkInterfaceClosure(config, headTarget, usage)) {
291
371
          ctp.Add(gt->GetProperty(tp), usage);
292
371
        }
293
2.61k
      }
294
8.65k
    };
295
4.32k
    addTransitiveProperties("TRANSITIVE_LINK_PROPERTIES", UseTo::Link);
296
4.32k
    addTransitiveProperties("TRANSITIVE_COMPILE_PROPERTIES", UseTo::Compile);
297
4.32k
    i = ctpm.emplace(config, std::move(ctp)).first;
298
4.32k
  }
299
13.3k
  return i->second;
300
13.3k
}