Coverage Report

Created: 2026-04-29 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmGeneratorFileSets.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
#include "cmGeneratorFileSets.h"
4
5
#include <algorithm>
6
#include <iterator>
7
#include <map>
8
#include <unordered_set>
9
#include <utility>
10
#include <vector>
11
12
#include <cm/memory>
13
#include <cm/optional>
14
#include <cmext/algorithm>
15
16
#include "cmFileSet.h"
17
#include "cmFileSetMetadata.h"
18
#include "cmGenExContext.h"
19
#include "cmGenExEvaluation.h"
20
#include "cmGeneratorExpression.h"
21
#include "cmGeneratorExpressionDAGChecker.h"
22
#include "cmGeneratorExpressionNode.h"
23
#include "cmGeneratorFileSet.h"
24
#include "cmGeneratorTarget.h"
25
#include "cmLinkItem.h"
26
#include "cmList.h"
27
#include "cmListFileCache.h"
28
#include "cmMakefile.h"
29
#include "cmMessageType.h"
30
#include "cmSourceFile.h"
31
#include "cmStringAlgorithms.h"
32
#include "cmSystemTools.h"
33
#include "cmTarget.h"
34
#include "cmValue.h"
35
36
cmGeneratorFileSets::cmGeneratorFileSets(cmGeneratorTarget* target,
37
                                         cmLocalGenerator* lg)
38
0
  : Target(target)
39
0
  , LocalGenerator(lg)
40
0
{
41
0
  bool isFramework = target->IsFrameworkOnApple();
42
0
  auto issueMessage = [&target](cmFileSet const* fileSet) {
43
0
    target->Makefile->IssueMessage(
44
0
      MessageType::FATAL_ERROR,
45
0
      cmStrCat(R"(The file set ")", fileSet->GetName(), R"(",  of type ")",
46
0
               fileSet->GetType(),
47
0
               R"(", is incompatible with the "FRAMEWORK" target ")",
48
0
               target->GetName(), R"(".)"));
49
0
  };
50
51
0
  for (auto const& name : target->Target->GetAllPrivateFileSets()) {
52
0
    cmFileSet const* fileSet = target->Target->GetFileSet(name);
53
0
    if (isFramework &&
54
0
        !cm::FileSetMetadata::IsFrameworkSupported(fileSet->GetType())) {
55
0
      issueMessage(fileSet);
56
0
      continue;
57
0
    }
58
0
    auto entry = this->FileSets.emplace(
59
0
      name, cm::make_unique<cmGeneratorFileSet>(target, fileSet));
60
0
    auto const* genFileSet = entry.first->second.get();
61
0
    this->AllFileSets.push_back(genFileSet);
62
0
    this->SelfFileSets[genFileSet->GetType()].push_back(genFileSet);
63
0
  }
64
0
  for (auto const& name : target->Target->GetAllInterfaceFileSets()) {
65
0
    auto it = this->FileSets.find(name);
66
0
    cmGeneratorFileSet const* genFileSet = nullptr;
67
0
    if (it == this->FileSets.end()) {
68
0
      cmFileSet const* fileSet = target->Target->GetFileSet(name);
69
0
      if (isFramework &&
70
0
          !cm::FileSetMetadata::IsFrameworkSupported(fileSet->GetType())) {
71
0
        issueMessage(fileSet);
72
0
        continue;
73
0
      }
74
0
      auto entry = this->FileSets.emplace(
75
0
        name, cm::make_unique<cmGeneratorFileSet>(target, fileSet));
76
0
      genFileSet = entry.first->second.get();
77
0
      this->AllFileSets.push_back(genFileSet);
78
0
    } else {
79
0
      genFileSet = it->second.get();
80
0
    }
81
0
    this->InterfaceFileSets[genFileSet->GetType()].push_back(genFileSet);
82
0
  }
83
0
}
84
0
cmGeneratorFileSets::~cmGeneratorFileSets() = default;
85
86
std::vector<cm::string_view> cmGeneratorFileSets::GetFileSetTypes() const
87
0
{
88
0
  return cm::keys(this->SelfFileSets);
89
0
}
90
91
std::vector<cm::string_view> cmGeneratorFileSets::GetInterfaceFileSetTypes()
92
  const
93
0
{
94
0
  return cm::keys(this->InterfaceFileSets);
95
0
}
96
97
std::vector<cmGeneratorFileSet const*> const&
98
cmGeneratorFileSets::GetAllFileSets() const
99
0
{
100
0
  return this->AllFileSets;
101
0
}
102
103
namespace {
104
std::vector<cmGeneratorFileSet const*> NoFileSets;
105
}
106
107
std::vector<cmGeneratorFileSet const*> const& cmGeneratorFileSets::GetFileSets(
108
  cm::string_view type) const
109
0
{
110
0
  auto it = this->SelfFileSets.find(type);
111
0
  if (it != this->SelfFileSets.end()) {
112
0
    return it->second;
113
0
  }
114
0
  return NoFileSets;
115
0
}
116
std::vector<cmGeneratorFileSet const*> const&
117
cmGeneratorFileSets::GetInterfaceFileSets(cm::string_view type) const
118
0
{
119
0
  auto it = this->InterfaceFileSets.find(type);
120
0
  if (it != this->InterfaceFileSets.end()) {
121
0
    return it->second;
122
0
  }
123
0
  return NoFileSets;
124
0
}
125
126
cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSet(
127
  std::string const& name) const
128
0
{
129
0
  auto const it = this->FileSets.find(name);
130
0
  if (it != this->FileSets.end()) {
131
0
    return it->second.get();
132
0
  }
133
0
  return nullptr;
134
0
}
135
136
cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource(
137
  std::string const& config, std::string const& path) const
138
0
{
139
0
  using Lookup = cm::FileSetMetadata::FileSetLookup;
140
141
0
  this->BuildInfoCache(config);
142
143
0
  auto const& info = this->Configs[config];
144
145
0
  auto const it = info.FileSetCache.find(path);
146
0
  if (it != info.FileSetCache.end()) {
147
0
    return it->second;
148
0
  }
149
150
  // search in all the dependents
151
0
  auto const it2 = info.InterfaceFileSetCache.find(path);
152
0
  if (it2 != info.InterfaceFileSetCache.end() &&
153
0
      cm::FileSetMetadata::GetFileSetDescriptor(it2->second->GetType())
154
0
          .value_or(cm::FileSetMetadata::FileSetDescriptor{ Lookup::Target })
155
0
          .Lookup == Lookup::Dependencies) {
156
0
    return it2->second;
157
0
  }
158
159
0
  return nullptr;
160
0
}
161
cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource(
162
  std::string const& config, cmSourceFile const* sf) const
163
0
{
164
0
  return this->GetFileSetForSource(config, sf->GetFullPath());
165
0
}
166
167
std::vector<std::unique_ptr<cm::TargetPropertyEntry>>
168
cmGeneratorFileSets::GetSources(
169
  std::function<bool(cmGeneratorFileSet const*)> include,
170
  cm::GenEx::Context const& context, cmGeneratorTarget const* target,
171
  cmGeneratorExpressionDAGChecker* dagChecker) const
172
0
{
173
0
  std::vector<std::unique_ptr<TargetPropertyEntry>> entries;
174
175
0
  for (auto const& entry : this->FileSets) {
176
0
    auto const* fileSet = entry.second.get();
177
0
    if (include(fileSet)) {
178
0
      auto sources = fileSet->GetSources(context, target, dagChecker);
179
0
      std::move(sources.begin(), sources.end(), std::back_inserter(entries));
180
0
    }
181
0
  }
182
183
0
  return entries;
184
0
}
185
186
std::vector<std::unique_ptr<cm::TargetPropertyEntry>>
187
cmGeneratorFileSets::GetSources(
188
  cm::GenEx::Context const& context, cmGeneratorTarget const* target,
189
  cmGeneratorExpressionDAGChecker* dagChecker) const
190
0
{
191
0
  return this->GetSources(
192
0
    [](cmGeneratorFileSet const* fileSet) -> bool {
193
0
      return fileSet->IsForSelf();
194
0
    },
195
0
    context, target, dagChecker);
196
0
}
197
std::vector<std::unique_ptr<cm::TargetPropertyEntry>>
198
cmGeneratorFileSets::GetSources(
199
  std::string type, cm::GenEx::Context const& context,
200
  cmGeneratorTarget const* target,
201
  cmGeneratorExpressionDAGChecker* dagChecker) const
202
0
{
203
0
  return this->GetSources(
204
0
    [&type](cmGeneratorFileSet const* fileSet) -> bool {
205
0
      return fileSet->IsForSelf() && fileSet->GetType() == type;
206
0
    },
207
0
    context, target, dagChecker);
208
0
}
209
210
std::vector<std::unique_ptr<cm::TargetPropertyEntry>>
211
cmGeneratorFileSets::GetInterfaceSources(
212
  cm::GenEx::Context const& context, cmGeneratorTarget const* target,
213
  cmGeneratorExpressionDAGChecker* dagChecker) const
214
0
{
215
0
  return this->GetSources(
216
0
    [](cmGeneratorFileSet const* fileSet) -> bool {
217
0
      return fileSet->IsForInterface();
218
0
    },
219
0
    context, target, dagChecker);
220
0
}
221
std::vector<std::unique_ptr<cm::TargetPropertyEntry>>
222
cmGeneratorFileSets::GetInterfaceSources(
223
  std::string type, cm::GenEx::Context const& context,
224
  cmGeneratorTarget const* target,
225
  cmGeneratorExpressionDAGChecker* dagChecker) const
226
0
{
227
0
  return this->GetSources(
228
0
    [&type](cmGeneratorFileSet const* fileSet) -> bool {
229
0
      return fileSet->IsForInterface() && fileSet->GetType() == type;
230
0
    },
231
0
    context, target, dagChecker);
232
0
}
233
234
bool cmGeneratorFileSets::MaybeHaveInterfaceProperty(
235
  cm::string_view type, std::string const& prop,
236
  cm::GenEx::Evaluation* eval) const
237
0
{
238
0
  std::string const key =
239
0
    cmStrCat(type, "::", prop, '@', eval->Context.Config);
240
0
  auto i = this->MaybeInterfacePropertyExists.find(key);
241
0
  if (i == this->MaybeInterfacePropertyExists.end()) {
242
    // Insert an entry now in case there is a cycle.
243
0
    i = this->MaybeInterfacePropertyExists.emplace(key, false).first;
244
0
    bool& maybeInterfaceProp = i->second;
245
246
0
    for (auto const* fileSet : this->GetInterfaceFileSets(type)) {
247
      // If this file set itself has a non-empty property value, we are done.
248
0
      maybeInterfaceProp = !fileSet->GetProperty(prop).IsEmpty();
249
0
      if (maybeInterfaceProp) {
250
0
        break;
251
0
      }
252
0
    }
253
254
    // Otherwise, recurse to interface dependencies.
255
0
    if (!maybeInterfaceProp) {
256
0
      cmGeneratorTarget const* headTarget =
257
0
        eval->HeadTarget ? eval->HeadTarget : this->Target;
258
0
      if (cmLinkInterfaceLibraries const* iface =
259
0
            this->Target->GetLinkInterfaceLibraries(
260
0
              eval->Context.Config, headTarget,
261
0
              cmGeneratorTarget::UseTo::Compile)) {
262
0
        if (iface->HadHeadSensitiveCondition) {
263
          // With a different head target we may get to a library with
264
          // this interface property.
265
0
          maybeInterfaceProp = true;
266
0
        } else {
267
          // The transitive interface libraries do not depend on the
268
          // head target, so we can follow them.
269
0
          for (cmLinkItem const& lib : iface->Libraries) {
270
0
            if (lib.Target &&
271
0
                lib.Target->GetGeneratorFileSets()->MaybeHaveInterfaceProperty(
272
0
                  type, prop, eval)) {
273
0
              maybeInterfaceProp = true;
274
0
              break;
275
0
            }
276
0
          }
277
0
        }
278
0
      }
279
0
    }
280
0
  }
281
0
  return i->second;
282
0
}
283
284
std::string cmGeneratorFileSets::EvaluateInterfaceProperty(
285
  cm::string_view type, std::string const& prop, cm::GenEx::Evaluation* eval,
286
  cmGeneratorExpressionDAGChecker* dagCheckerParent) const
287
0
{
288
  // If the property does not appear transitively at all, we are done.
289
0
  if (!this->MaybeHaveInterfaceProperty(type, prop, eval)) {
290
0
    return std::string{};
291
0
  }
292
293
0
  cmList result;
294
0
  cmGeneratorExpressionDAGChecker dagChecker{
295
0
    this->Target,     prop,          nullptr,
296
0
    dagCheckerParent, eval->Context, eval->Backtrace,
297
0
  };
298
0
  switch (dagChecker.Check()) {
299
0
    case cmGeneratorExpressionDAGChecker::SELF_REFERENCE:
300
0
      dagChecker.ReportError(
301
0
        eval,
302
0
        cmStrCat("$<FILE_SET_PROPERTY:*,TARGET:", this->Target->GetName(), ',',
303
0
                 prop, '>'));
304
0
      return std::string{};
305
0
    case cmGeneratorExpressionDAGChecker::CYCLIC_REFERENCE:
306
      // No error. We just skip cyclic references.
307
0
    case cmGeneratorExpressionDAGChecker::ALREADY_SEEN:
308
      // No error. We have already seen this transitive property.
309
0
      return std::string{};
310
0
    case cmGeneratorExpressionDAGChecker::DAG:
311
0
      break;
312
0
  }
313
314
0
  cmGeneratorTarget const* headTarget =
315
0
    eval->HeadTarget ? eval->HeadTarget : this->Target;
316
317
0
  for (auto const* fileSet : this->GetInterfaceFileSets(type)) {
318
0
    if (cmValue p = fileSet->GetProperty(prop)) {
319
0
      result.append(cmGeneratorExpressionNode::EvaluateDependentExpression(
320
0
        *p, eval, headTarget, &dagChecker, this->Target));
321
0
    }
322
0
  }
323
324
0
  if (cmLinkInterfaceLibraries const* iface =
325
0
        this->Target->GetLinkInterfaceLibraries(
326
0
          eval->Context.Config, headTarget,
327
0
          cmGeneratorTarget::UseTo::Compile)) {
328
0
    eval->HadContextSensitiveCondition = eval->HadContextSensitiveCondition ||
329
0
      iface->HadContextSensitiveCondition;
330
0
    for (cmLinkItem const& lib : iface->Libraries) {
331
      // Broken code can have a target in its own link interface.
332
      // Don't follow such link interface entries so as not to create a
333
      // self-referencing loop.
334
0
      if (lib.Target && lib.Target != this->Target) {
335
        // Pretend $<FILE_SET__PROPERTY:fileSet,TARGET:lib.Target,prop>
336
        // appeared in the above property and hand-evaluate it as if it were
337
        // compiled.
338
        // Create a context as cmCompiledGeneratorExpression::Evaluate does.
339
0
        cm::GenEx::Evaluation libEval(
340
0
          eval->Context, eval->Quiet, headTarget, this->Target,
341
0
          eval->EvaluateForBuildsystem, eval->Backtrace);
342
0
        std::string libResult = cmGeneratorExpression::StripEmptyListElements(
343
0
          lib.Target->GetGeneratorFileSets()->EvaluateInterfaceProperty(
344
0
            type, prop, &libEval, &dagChecker));
345
0
        if (!libResult.empty()) {
346
0
          result.append(libResult);
347
0
        }
348
0
        eval->HadContextSensitiveCondition =
349
0
          eval->HadContextSensitiveCondition ||
350
0
          libEval.HadContextSensitiveCondition;
351
0
        eval->HadHeadSensitiveCondition =
352
0
          eval->HadHeadSensitiveCondition || libEval.HadHeadSensitiveCondition;
353
0
      }
354
0
    }
355
0
  }
356
357
0
  return result.to_string();
358
0
}
359
360
namespace {
361
void GetInterfaceFiles(cmGeneratorTarget const* target,
362
                       cm::GenEx::Context const& context,
363
                       std::unordered_set<cmGeneratorTarget const*>& targets,
364
                       std::map<std::string, cmGeneratorFileSet const*>& cache)
365
0
{
366
0
  namespace Metadata = cm::FileSetMetadata;
367
368
0
  for (auto const& type : Metadata::GetKnownTypes()) {
369
0
    auto fileSetDescriptor = Metadata::GetFileSetDescriptor(type);
370
0
    if (fileSetDescriptor &&
371
0
        fileSetDescriptor->Lookup == Metadata::FileSetLookup::Dependencies) {
372
0
      for (auto const* fileSet : target->GetInterfaceFileSets(type)) {
373
0
        auto files = fileSet->GetFiles(context, target);
374
375
0
        for (auto const& it : files.first) {
376
0
          for (auto const& filename : it.second) {
377
0
            auto collapsedFile = cmSystemTools::CollapseFullPath(filename);
378
0
            cache[collapsedFile] = fileSet;
379
0
          }
380
0
        }
381
0
      }
382
0
    }
383
0
  }
384
385
0
  if (cmLinkInterfaceLibraries const* iface =
386
0
        target->GetLinkInterfaceLibraries(context.Config, target,
387
0
                                          cmGeneratorTarget::UseTo::Compile)) {
388
0
    for (cmLinkItem const& lib : iface->Libraries) {
389
0
      if (lib.Target && lib.Target != target &&
390
0
          targets.insert(lib.Target).second) {
391
0
        GetInterfaceFiles(lib.Target, context, targets, cache);
392
0
      }
393
0
    }
394
0
  }
395
0
}
396
}
397
398
void cmGeneratorFileSets::BuildInfoCache(std::string const& config) const
399
0
{
400
0
  auto& info = this->Configs[config];
401
402
0
  if (info.BuiltCache) {
403
0
    return;
404
0
  }
405
406
0
  cm::GenEx::Context context(this->LocalGenerator, config);
407
408
0
  for (auto const& item : this->FileSets) {
409
0
    auto const* fileSet = item.second.get();
410
411
0
    auto files = fileSet->GetFiles(context, this->Target);
412
413
0
    for (auto const& it : files.first) {
414
0
      for (auto const& filename : it.second) {
415
0
        auto collapsedFile = cmSystemTools::CollapseFullPath(filename);
416
0
        info.FileSetCache[collapsedFile] = fileSet;
417
0
      }
418
0
    }
419
0
  }
420
421
  // retrieve all files inherited from dependent targets
422
0
  std::unordered_set<cmGeneratorTarget const*> targets;
423
424
0
  if (cmLinkImplementationLibraries const* impl =
425
0
        this->Target->GetLinkImplementationLibraries(
426
0
          config, cmGeneratorTarget::UseTo::Compile)) {
427
0
    for (cmLinkItem const& lib : impl->Libraries) {
428
0
      if (lib.Target) {
429
0
        GetInterfaceFiles(lib.Target, context, targets,
430
0
                          info.InterfaceFileSetCache);
431
0
      }
432
0
    }
433
0
  }
434
435
0
  info.BuiltCache = true;
436
0
}