Coverage Report

Created: 2026-06-15 07:03

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
std::unordered_set<cmGeneratorFileSet const*> const&
137
cmGeneratorFileSets::GetAllFileSetsForSource(std::string const& config,
138
                                             std::string const& path) const
139
0
{
140
0
  this->BuildInfoCache(config);
141
142
0
  auto const& info = this->Configs[config];
143
144
0
  auto const it = info.FileSetCache.find(path);
145
0
  if (it != info.FileSetCache.end()) {
146
0
    return it->second;
147
0
  }
148
149
  // search in all the dependents
150
0
  auto const it2 = info.InterfaceFileSetCache.find(path);
151
0
  if (it2 != info.InterfaceFileSetCache.end()) {
152
0
    return it2->second;
153
0
  }
154
155
0
  static std::unordered_set<cmGeneratorFileSet const*> emptySet;
156
0
  return emptySet;
157
0
}
158
std::unordered_set<cmGeneratorFileSet const*> const&
159
cmGeneratorFileSets::GetAllFileSetsForSource(std::string const& config,
160
                                             cmSourceFile const* sf) const
161
0
{
162
0
  return this->GetAllFileSetsForSource(config, sf->GetFullPath());
163
0
}
164
165
cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource(
166
  std::string const& config, std::string const& file) const
167
0
{
168
0
  auto const& fileSets = this->GetAllFileSetsForSource(config, file);
169
0
  if (fileSets.empty()) {
170
0
    return nullptr;
171
0
  }
172
0
  return *fileSets.begin();
173
0
}
174
cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource(
175
  std::string const& config, cmSourceFile const* sf) const
176
0
{
177
0
  return this->GetFileSetForSource(config, sf->GetFullPath());
178
0
}
179
180
std::vector<std::unique_ptr<cm::TargetPropertyEntry>>
181
cmGeneratorFileSets::GetSources(
182
  std::function<bool(cmGeneratorFileSet const*)> include,
183
  cm::GenEx::Context const& context, cmGeneratorTarget const* target,
184
  cmGeneratorExpressionDAGChecker* dagChecker) const
185
0
{
186
0
  std::vector<std::unique_ptr<TargetPropertyEntry>> entries;
187
188
0
  for (auto const& entry : this->FileSets) {
189
0
    auto const* fileSet = entry.second.get();
190
0
    if (include(fileSet)) {
191
0
      auto sources = fileSet->GetSources(context, target, dagChecker);
192
0
      std::move(sources.begin(), sources.end(), std::back_inserter(entries));
193
0
    }
194
0
  }
195
196
0
  return entries;
197
0
}
198
199
std::vector<std::unique_ptr<cm::TargetPropertyEntry>>
200
cmGeneratorFileSets::GetSources(
201
  cm::GenEx::Context const& context, cmGeneratorTarget const* target,
202
  cmGeneratorExpressionDAGChecker* dagChecker) const
203
0
{
204
0
  return this->GetSources(
205
0
    [](cmGeneratorFileSet const* fileSet) -> bool {
206
0
      return fileSet->IsForSelf();
207
0
    },
208
0
    context, target, dagChecker);
209
0
}
210
std::vector<std::unique_ptr<cm::TargetPropertyEntry>>
211
cmGeneratorFileSets::GetSources(
212
  std::string type, cm::GenEx::Context const& context,
213
  cmGeneratorTarget const* target,
214
  cmGeneratorExpressionDAGChecker* dagChecker) const
215
0
{
216
0
  return this->GetSources(
217
0
    [&type](cmGeneratorFileSet const* fileSet) -> bool {
218
0
      return fileSet->IsForSelf() && fileSet->GetType() == type;
219
0
    },
220
0
    context, target, dagChecker);
221
0
}
222
223
std::vector<std::unique_ptr<cm::TargetPropertyEntry>>
224
cmGeneratorFileSets::GetInterfaceSources(
225
  cm::GenEx::Context const& context, cmGeneratorTarget const* target,
226
  cmGeneratorExpressionDAGChecker* dagChecker) const
227
0
{
228
0
  return this->GetSources(
229
0
    [](cmGeneratorFileSet const* fileSet) -> bool {
230
0
      return fileSet->IsForInterface();
231
0
    },
232
0
    context, target, dagChecker);
233
0
}
234
std::vector<std::unique_ptr<cm::TargetPropertyEntry>>
235
cmGeneratorFileSets::GetInterfaceSources(
236
  std::string type, cm::GenEx::Context const& context,
237
  cmGeneratorTarget const* target,
238
  cmGeneratorExpressionDAGChecker* dagChecker) const
239
0
{
240
0
  return this->GetSources(
241
0
    [&type](cmGeneratorFileSet const* fileSet) -> bool {
242
0
      return fileSet->IsForInterface() && fileSet->GetType() == type;
243
0
    },
244
0
    context, target, dagChecker);
245
0
}
246
247
bool cmGeneratorFileSets::MaybeHaveInterfaceProperty(
248
  cm::string_view type, std::string const& prop,
249
  cm::GenEx::Evaluation* eval) const
250
0
{
251
0
  std::string const key =
252
0
    cmStrCat(type, "::", prop, '@', eval->Context.Config);
253
0
  auto i = this->MaybeInterfacePropertyExists.find(key);
254
0
  if (i == this->MaybeInterfacePropertyExists.end()) {
255
    // Insert an entry now in case there is a cycle.
256
0
    i = this->MaybeInterfacePropertyExists.emplace(key, false).first;
257
0
    bool& maybeInterfaceProp = i->second;
258
259
0
    for (auto const* fileSet : this->GetInterfaceFileSets(type)) {
260
      // If this file set itself has a non-empty property value, we are done.
261
0
      maybeInterfaceProp = !fileSet->GetProperty(prop).IsEmpty();
262
0
      if (maybeInterfaceProp) {
263
0
        break;
264
0
      }
265
0
    }
266
267
    // Otherwise, recurse to interface dependencies.
268
0
    if (!maybeInterfaceProp) {
269
0
      cmGeneratorTarget const* headTarget =
270
0
        eval->HeadTarget ? eval->HeadTarget : this->Target;
271
0
      if (cmLinkInterfaceLibraries const* iface =
272
0
            this->Target->GetLinkInterfaceLibraries(
273
0
              eval->Context.Config, headTarget,
274
0
              cmGeneratorTarget::UseTo::Compile)) {
275
0
        if (iface->HadHeadSensitiveCondition) {
276
          // With a different head target we may get to a library with
277
          // this interface property.
278
0
          maybeInterfaceProp = true;
279
0
        } else {
280
          // The transitive interface libraries do not depend on the
281
          // head target, so we can follow them.
282
0
          for (cmLinkItem const& lib : iface->Libraries) {
283
0
            if (lib.Target &&
284
0
                lib.Target->GetGeneratorFileSets()->MaybeHaveInterfaceProperty(
285
0
                  type, prop, eval)) {
286
0
              maybeInterfaceProp = true;
287
0
              break;
288
0
            }
289
0
          }
290
0
        }
291
0
      }
292
0
    }
293
0
  }
294
0
  return i->second;
295
0
}
296
297
std::string cmGeneratorFileSets::EvaluateInterfaceProperty(
298
  cm::string_view type, std::string const& prop, cm::GenEx::Evaluation* eval,
299
  cmGeneratorExpressionDAGChecker* dagCheckerParent) const
300
0
{
301
  // If the property does not appear transitively at all, we are done.
302
0
  if (!this->MaybeHaveInterfaceProperty(type, prop, eval)) {
303
0
    return std::string{};
304
0
  }
305
306
0
  cmList result;
307
0
  cmGeneratorExpressionDAGChecker dagChecker{
308
0
    this->Target,     prop,          nullptr,
309
0
    dagCheckerParent, eval->Context, eval->Backtrace,
310
0
  };
311
0
  switch (dagChecker.Check()) {
312
0
    case cmGeneratorExpressionDAGChecker::SELF_REFERENCE:
313
0
      dagChecker.ReportError(
314
0
        eval,
315
0
        cmStrCat("$<FILE_SET_PROPERTY:*,TARGET:", this->Target->GetName(), ',',
316
0
                 prop, '>'));
317
0
      return std::string{};
318
0
    case cmGeneratorExpressionDAGChecker::CYCLIC_REFERENCE:
319
      // No error. We just skip cyclic references.
320
0
    case cmGeneratorExpressionDAGChecker::ALREADY_SEEN:
321
      // No error. We have already seen this transitive property.
322
0
      return std::string{};
323
0
    case cmGeneratorExpressionDAGChecker::DAG:
324
0
      break;
325
0
  }
326
327
0
  cmGeneratorTarget const* headTarget =
328
0
    eval->HeadTarget ? eval->HeadTarget : this->Target;
329
330
0
  for (auto const* fileSet : this->GetInterfaceFileSets(type)) {
331
0
    if (cmValue p = fileSet->GetProperty(prop)) {
332
0
      result.append(cmGeneratorExpressionNode::EvaluateDependentExpression(
333
0
        *p, eval, headTarget, &dagChecker, this->Target));
334
0
    }
335
0
  }
336
337
0
  if (cmLinkInterfaceLibraries const* iface =
338
0
        this->Target->GetLinkInterfaceLibraries(
339
0
          eval->Context.Config, headTarget,
340
0
          cmGeneratorTarget::UseTo::Compile)) {
341
0
    eval->HadContextSensitiveCondition = eval->HadContextSensitiveCondition ||
342
0
      iface->HadContextSensitiveCondition;
343
0
    for (cmLinkItem const& lib : iface->Libraries) {
344
      // Broken code can have a target in its own link interface.
345
      // Don't follow such link interface entries so as not to create a
346
      // self-referencing loop.
347
0
      if (lib.Target && lib.Target != this->Target) {
348
        // Pretend $<FILE_SET__PROPERTY:fileSet,TARGET:lib.Target,prop>
349
        // appeared in the above property and hand-evaluate it as if it were
350
        // compiled.
351
        // Create a context as cmCompiledGeneratorExpression::Evaluate does.
352
0
        cm::GenEx::Evaluation libEval(
353
0
          eval->Context, eval->Quiet, headTarget, this->Target,
354
0
          eval->EvaluateForBuildsystem, eval->Backtrace);
355
0
        std::string libResult = cmGeneratorExpression::StripEmptyListElements(
356
0
          lib.Target->GetGeneratorFileSets()->EvaluateInterfaceProperty(
357
0
            type, prop, &libEval, &dagChecker));
358
0
        if (!libResult.empty()) {
359
0
          result.append(libResult);
360
0
        }
361
0
        eval->HadContextSensitiveCondition =
362
0
          eval->HadContextSensitiveCondition ||
363
0
          libEval.HadContextSensitiveCondition;
364
0
        eval->HadHeadSensitiveCondition =
365
0
          eval->HadHeadSensitiveCondition || libEval.HadHeadSensitiveCondition;
366
0
      }
367
0
    }
368
0
  }
369
370
0
  return result.to_string();
371
0
}
372
373
namespace {
374
void GetInterfaceFiles(
375
  cmGeneratorTarget const* target, cm::GenEx::Context const& context,
376
  std::unordered_set<cmGeneratorTarget const*>& targets,
377
  std::map<std::string, std::unordered_set<cmGeneratorFileSet const*>>& cache)
378
0
{
379
0
  namespace Metadata = cm::FileSetMetadata;
380
381
0
  for (auto const& type : Metadata::GetKnownTypes()) {
382
0
    auto fileSetDescriptor = Metadata::GetFileSetDescriptor(type);
383
0
    if (fileSetDescriptor &&
384
0
        fileSetDescriptor->Lookup == Metadata::FileSetLookup::Dependencies) {
385
0
      for (auto const* fileSet : target->GetInterfaceFileSets(type)) {
386
0
        auto files = fileSet->GetFiles(context, target);
387
388
0
        for (auto const& it : files.first) {
389
0
          for (auto const& filename : it.second) {
390
0
            auto collapsedFile = cmSystemTools::CollapseFullPath(filename);
391
0
            cache[collapsedFile].insert(fileSet);
392
0
          }
393
0
        }
394
0
      }
395
0
    }
396
0
  }
397
398
0
  if (cmLinkInterfaceLibraries const* iface =
399
0
        target->GetLinkInterfaceLibraries(context.Config, target,
400
0
                                          cmGeneratorTarget::UseTo::Compile)) {
401
0
    for (cmLinkItem const& lib : iface->Libraries) {
402
0
      if (lib.Target && lib.Target != target &&
403
0
          targets.insert(lib.Target).second) {
404
0
        GetInterfaceFiles(lib.Target, context, targets, cache);
405
0
      }
406
0
    }
407
0
  }
408
0
}
409
}
410
411
void cmGeneratorFileSets::BuildInfoCache(std::string const& config) const
412
0
{
413
0
  auto& info = this->Configs[config];
414
415
0
  if (info.BuiltCache) {
416
0
    return;
417
0
  }
418
419
0
  cm::GenEx::Context context(this->LocalGenerator, config);
420
421
0
  for (auto const& item : this->FileSets) {
422
0
    auto const* fileSet = item.second.get();
423
424
0
    auto files = fileSet->GetFiles(context, this->Target);
425
426
0
    for (auto const& it : files.first) {
427
0
      for (auto const& filename : it.second) {
428
0
        auto collapsedFile = cmSystemTools::CollapseFullPath(filename);
429
0
        info.FileSetCache[collapsedFile].insert(fileSet);
430
0
      }
431
0
    }
432
0
  }
433
434
  // retrieve all files inherited from dependent targets
435
0
  std::unordered_set<cmGeneratorTarget const*> targets;
436
437
0
  if (cmLinkImplementationLibraries const* impl =
438
0
        this->Target->GetLinkImplementationLibraries(
439
0
          config, cmGeneratorTarget::UseTo::Compile)) {
440
0
    for (cmLinkItem const& lib : impl->Libraries) {
441
0
      if (lib.Target) {
442
0
        GetInterfaceFiles(lib.Target, context, targets,
443
0
                          info.InterfaceFileSetCache);
444
0
      }
445
0
    }
446
0
  }
447
448
0
  info.BuiltCache = true;
449
0
}