Coverage Report

Created: 2026-04-29 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmGeneratorTarget_HeaderSetVerification.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 <algorithm>
8
#include <array>
9
#include <initializer_list>
10
#include <map>
11
#include <ostream>
12
#include <set>
13
#include <string>
14
#include <utility>
15
#include <vector>
16
17
#include <cm/memory>
18
#include <cm/optional>
19
#include <cm/string_view>
20
21
#include "cmDiagnostics.h"
22
#include "cmFileSetMetadata.h"
23
#include "cmGenExContext.h"
24
#include "cmGeneratedFileStream.h"
25
#include "cmGeneratorExpression.h"
26
#include "cmGeneratorFileSet.h"
27
#include "cmGlobalGenerator.h"
28
#include "cmList.h"
29
#include "cmLocalGenerator.h"
30
#include "cmMakefile.h"
31
#include "cmMessageType.h"
32
#include "cmPolicies.h"
33
#include "cmSourceFile.h"
34
#include "cmSourceFileLocation.h"
35
#include "cmStateTypes.h"
36
#include "cmStringAlgorithms.h"
37
#include "cmSystemTools.h"
38
#include "cmTarget.h"
39
#include "cmTargetLinkLibraryType.h"
40
#include "cmValue.h"
41
42
bool cmGeneratorTarget::AddHeaderSetVerification()
43
0
{
44
0
  for (bool const isInterface : { false, true }) {
45
0
    if (!this->GetPropertyAsBool(isInterface ? "VERIFY_INTERFACE_HEADER_SETS"
46
0
                                             : "VERIFY_PRIVATE_HEADER_SETS")) {
47
0
      continue;
48
0
    }
49
50
0
    if (this->GetType() != cmStateEnums::STATIC_LIBRARY &&
51
0
        this->GetType() != cmStateEnums::SHARED_LIBRARY &&
52
0
        (this->GetType() != cmStateEnums::MODULE_LIBRARY || isInterface) &&
53
0
        this->GetType() != cmStateEnums::UNKNOWN_LIBRARY &&
54
0
        this->GetType() != cmStateEnums::OBJECT_LIBRARY &&
55
0
        this->GetType() != cmStateEnums::INTERFACE_LIBRARY &&
56
0
        this->GetType() != cmStateEnums::EXECUTABLE) {
57
0
      continue;
58
0
    }
59
60
0
    char const* headerSetsProperty = isInterface
61
0
      ? "INTERFACE_HEADER_SETS_TO_VERIFY"
62
0
      : "PRIVATE_HEADER_SETS_TO_VERIFY";
63
64
0
    auto verifyValue = this->GetProperty(headerSetsProperty);
65
0
    bool const all = verifyValue.IsEmpty();
66
0
    std::set<std::string> verifySet;
67
0
    if (!all) {
68
0
      cmList verifyList{ verifyValue };
69
0
      verifySet.insert(verifyList.begin(), verifyList.end());
70
0
    }
71
72
0
    cmTarget* verifyTarget = nullptr;
73
0
    std::string const verifyTargetName =
74
0
      cmStrCat(this->GetName(),
75
0
               isInterface ? "_verify_interface_header_sets"
76
0
                           : "_verify_private_header_sets");
77
78
0
    char const* allVerifyTargetName = isInterface
79
0
      ? "all_verify_interface_header_sets"
80
0
      : "all_verify_private_header_sets";
81
0
    cmTarget* allVerifyTarget =
82
0
      this->GlobalGenerator->GetMakefiles().front()->FindTargetToUse(
83
0
        allVerifyTargetName, { cmStateEnums::TargetDomain::NATIVE });
84
85
0
    auto fileSetEntries = isInterface
86
0
      ? this->GetInterfaceFileSets(cm::FileSetMetadata::HEADERS)
87
0
      : this->GetFileSets(cm::FileSetMetadata::HEADERS);
88
89
0
    std::set<cmGeneratorFileSet const*> fileSets;
90
0
    for (auto const& fileSet : fileSetEntries) {
91
0
      if (all || verifySet.count(fileSet->GetName())) {
92
0
        fileSets.insert(fileSet);
93
0
        verifySet.erase(fileSet->GetName());
94
0
      }
95
0
    }
96
97
0
    if (isInterface) {
98
0
      cmPolicies::PolicyStatus const cmp0209 = this->GetPolicyStatusCMP0209();
99
0
      if (cmp0209 != cmPolicies::NEW &&
100
0
          this->GetType() == cmStateEnums::EXECUTABLE &&
101
0
          !this->GetPropertyAsBool("ENABLE_EXPORTS")) {
102
0
        if (cmp0209 == cmPolicies::WARN && !fileSets.empty()) {
103
0
          this->Makefile->IssueDiagnostic(
104
0
            cmDiagnostics::CMD_AUTHOR,
105
0
            cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0209),
106
0
                     "\n"
107
0
                     "Executable target \"",
108
0
                     this->GetName(),
109
0
                     "\" has interface header file sets, but it does not "
110
0
                     "enable exports. Those headers would be verified under "
111
0
                     "CMP0209 NEW behavior.\n"));
112
0
        }
113
0
        continue;
114
0
      }
115
0
    }
116
117
0
    if (!verifySet.empty()) {
118
0
      this->Makefile->IssueMessage(
119
0
        MessageType::FATAL_ERROR,
120
0
        cmStrCat("Property ", headerSetsProperty, " of target \"",
121
0
                 this->GetName(),
122
0
                 "\" contained the following header sets that are nonexistent "
123
0
                 "or not ",
124
0
                 isInterface ? "INTERFACE" : "PRIVATE", ":\n  ",
125
0
                 cmJoin(verifySet, "\n  ")));
126
0
      return false;
127
0
    }
128
129
0
    cm::optional<cm::optional<std::string>> defaultLanguage;
130
131
    // First, collect all verification stubs before creating the target,
132
    // so we know whether to create an OBJECT library or not.
133
0
    std::vector<std::string> stubSources;
134
0
    for (auto const* fileSet : fileSets) {
135
0
      auto const& dirCges = fileSet->CompileDirectoryEntries();
136
0
      auto const& fileCges = fileSet->CompileFileEntries();
137
138
0
      static auto const contextSensitive =
139
0
        [](std::unique_ptr<cmCompiledGeneratorExpression> const& cge) {
140
0
          return cge->GetHadContextSensitiveCondition();
141
0
        };
142
0
      bool dirCgesContextSensitive = false;
143
0
      bool fileCgesContextSensitive = false;
144
145
0
      std::vector<std::string> dirs;
146
0
      std::map<std::string, std::vector<std::string>> filesPerDir;
147
0
      bool first = true;
148
0
      for (auto const& config : this->Makefile->GetGeneratorConfigs(
149
0
             cmMakefile::GeneratorConfigQuery::IncludeEmptyConfig)) {
150
0
        cm::GenEx::Context context(this->LocalGenerator, config);
151
0
        if (first || dirCgesContextSensitive) {
152
0
          dirs = fileSet->EvaluateDirectoryEntries(dirCges, context, this);
153
0
          dirCgesContextSensitive =
154
0
            std::any_of(dirCges.begin(), dirCges.end(), contextSensitive);
155
0
        }
156
0
        if (first || fileCgesContextSensitive) {
157
0
          filesPerDir.clear();
158
0
          for (auto const& fileCge : fileCges) {
159
0
            fileSet->EvaluateFileEntry(dirs, filesPerDir, fileCge, context,
160
0
                                       this);
161
0
            if (fileCge->GetHadContextSensitiveCondition()) {
162
0
              fileCgesContextSensitive = true;
163
0
            }
164
0
          }
165
0
        }
166
167
0
        for (auto const& files : filesPerDir) {
168
0
          for (auto const& file : files.second) {
169
0
            cm::optional<std::string> filenameOpt =
170
0
              this->GenerateHeaderSetVerificationFile(
171
0
                *this->Makefile->GetOrCreateSource(file), files.first,
172
0
                verifyTargetName, defaultLanguage);
173
0
            if (!filenameOpt) {
174
0
              continue;
175
0
            }
176
0
            std::string filename = *filenameOpt;
177
178
0
            if (fileCgesContextSensitive) {
179
0
              filename = cmStrCat("$<$<CONFIG:", config, ">:", filename, '>');
180
0
            }
181
0
            stubSources.emplace_back(std::move(filename));
182
0
          }
183
0
        }
184
185
0
        if (!dirCgesContextSensitive && !fileCgesContextSensitive) {
186
0
          break;
187
0
        }
188
0
        first = false;
189
0
      }
190
0
    }
191
192
0
    if (stubSources.empty()) {
193
      // No headers to verify. Create a utility target so the target
194
      // name always exists (e.g. for build system dependencies) without
195
      // needing a placeholder source. This avoids warnings from tools
196
      // like Xcode's libtool about empty static libraries.
197
0
      verifyTarget =
198
0
        this->Makefile->AddNewUtilityTarget(verifyTargetName, true);
199
0
    } else {
200
      // Create an OBJECT library to compile the verification stubs.
201
0
      {
202
0
        cmMakefile::PolicyPushPop polScope(this->Makefile);
203
0
        this->Makefile->SetPolicy(cmPolicies::CMP0119, cmPolicies::NEW);
204
0
        verifyTarget = this->Makefile->AddLibrary(
205
0
          verifyTargetName, cmStateEnums::OBJECT_LIBRARY, {}, true);
206
0
      }
207
208
0
      if (isInterface) {
209
        // Link to the original target so that we pick up its
210
        // interface compile options just like a consumer would.
211
        // This also ensures any generated headers in the original
212
        // target will be created.
213
0
        verifyTarget->AddLinkLibrary(
214
0
          *this->Makefile, this->GetName(),
215
0
          cmTargetLinkLibraryType::GENERAL_LibraryType);
216
0
      } else {
217
        // For private file sets, we need to simulate compiling the
218
        // same way as the original target. That includes linking to
219
        // the same things so we pick up the same transitive
220
        // properties. For the <LANG>_... properties, we don't care if
221
        // we set them for languages this target won't eventually use.
222
        // Copy language-standard properties for all supported
223
        // languages. We don't care if we set properties for languages
224
        // this target won't eventually use.
225
0
        static std::array<std::string, 19> const propertiesToCopy{ {
226
0
          "COMPILE_DEFINITIONS",    "COMPILE_FEATURES",
227
0
          "COMPILE_FLAGS",          "COMPILE_OPTIONS",
228
0
          "DEFINE_SYMBOL",          "INCLUDE_DIRECTORIES",
229
0
          "LINK_LIBRARIES",         "C_STANDARD",
230
0
          "C_STANDARD_REQUIRED",    "C_EXTENSIONS",
231
0
          "CXX_STANDARD",           "CXX_STANDARD_REQUIRED",
232
0
          "CXX_EXTENSIONS",         "OBJC_STANDARD",
233
0
          "OBJC_STANDARD_REQUIRED", "OBJC_EXTENSIONS",
234
0
          "OBJCXX_STANDARD",        "OBJCXX_STANDARD_REQUIRED",
235
0
          "OBJCXX_EXTENSIONS",
236
0
        } };
237
0
        for (std::string const& prop : propertiesToCopy) {
238
0
          cmValue propValue = this->Target->GetProperty(prop);
239
0
          if (propValue.IsSet()) {
240
0
            verifyTarget->SetProperty(prop, propValue);
241
0
          }
242
0
        }
243
        // The original target might have generated headers. Since
244
        // we only link to the original target for compilation,
245
        // there's nothing to force such generation to happen yet.
246
        // Our verify target must depend on the original target to
247
        // ensure such generated files will be created.
248
0
        verifyTarget->AddUtility(this->GetName(), false, this->Makefile);
249
0
        verifyTarget->AddCodegenDependency(this->GetName());
250
0
      }
251
252
0
      verifyTarget->SetProperty("AUTOMOC", "OFF");
253
0
      verifyTarget->SetProperty("AUTORCC", "OFF");
254
0
      verifyTarget->SetProperty("AUTOUIC", "OFF");
255
0
      verifyTarget->SetProperty("DISABLE_PRECOMPILE_HEADERS", "ON");
256
0
      verifyTarget->SetProperty("UNITY_BUILD", "OFF");
257
0
      verifyTarget->SetProperty("CXX_SCAN_FOR_MODULES", "OFF");
258
259
0
      if (isInterface) {
260
0
        verifyTarget->FinalizeTargetConfiguration(
261
0
          this->Makefile->GetCompileDefinitionsEntries());
262
0
      } else {
263
        // Private verification only needs to add the directory scope
264
        // definitions here
265
0
        for (auto const& def :
266
0
             this->Makefile->GetCompileDefinitionsEntries()) {
267
0
          verifyTarget->InsertCompileDefinition(def);
268
0
        }
269
0
      }
270
271
0
      for (auto const& source : stubSources) {
272
0
        verifyTarget->AddSource(source);
273
0
      }
274
0
    }
275
0
    if (!allVerifyTarget) {
276
0
      allVerifyTarget =
277
0
        this->GlobalGenerator->GetMakefiles().front()->AddNewUtilityTarget(
278
0
          allVerifyTargetName, true);
279
0
    }
280
0
    allVerifyTarget->AddUtility(verifyTargetName, false);
281
282
0
    this->LocalGenerator->AddGeneratorTarget(
283
0
      cm::make_unique<cmGeneratorTarget>(verifyTarget, this->LocalGenerator));
284
0
  }
285
0
  return true;
286
0
}
287
288
cm::optional<std::string> cmGeneratorTarget::GenerateHeaderSetVerificationFile(
289
  cmSourceFile& source, std::string const& dir,
290
  std::string const& verifyTargetName,
291
  cm::optional<cm::optional<std::string>>& defaultLanguage) const
292
0
{
293
0
  if (source.GetPropertyAsBool("SKIP_LINTING")) {
294
0
    return cm::nullopt;
295
0
  }
296
297
0
  cm::optional<std::string> language =
298
0
    this->ResolveHeaderLanguage(source, defaultLanguage);
299
0
  if (!language) {
300
0
    return cm::nullopt;
301
0
  }
302
303
0
  std::string headerFilename = dir;
304
0
  if (!headerFilename.empty()) {
305
0
    headerFilename += '/';
306
0
  }
307
0
  headerFilename += source.GetLocation().GetName();
308
309
0
  return this->GenerateStubForLanguage(*language, headerFilename,
310
0
                                       verifyTargetName, source);
311
0
}
312
313
cm::optional<std::string> cmGeneratorTarget::ResolveHeaderLanguage(
314
  cmSourceFile& source,
315
  cm::optional<cm::optional<std::string>>& defaultLanguage) const
316
0
{
317
0
  static std::array<cm::string_view, 4> const supportedLangs{ {
318
0
    "C",
319
0
    "CXX",
320
0
    "OBJC",
321
0
    "OBJCXX",
322
0
  } };
323
0
  auto isSupported = [](cm::string_view lang) -> bool {
324
0
    return std::find(supportedLangs.begin(), supportedLangs.end(), lang) !=
325
0
      supportedLangs.end();
326
0
  };
327
328
  // If the source has an explicit language, validate and return it.
329
0
  std::string language = source.GetOrDetermineLanguage();
330
0
  if (!language.empty()) {
331
0
    if (!isSupported(language)) {
332
0
      return cm::nullopt;
333
0
    }
334
0
    return cm::optional<std::string>(std::move(language));
335
0
  }
336
337
  /*
338
    Compute and cache the default language for unlanguaged headers.
339
    The lattice join is run once per file set, not once per header.
340
    Lattice:   OBJCXX
341
              /      \
342
            CXX      OBJC
343
              \      /
344
                 C
345
  */
346
0
  if (!defaultLanguage) {
347
0
    std::set<std::string> langs;
348
0
    for (AllConfigSource const& tgtSource : this->GetAllConfigSources()) {
349
0
      std::string const& lang = tgtSource.Source->GetOrDetermineLanguage();
350
0
      if (isSupported(lang)) {
351
0
        langs.insert(lang);
352
0
      }
353
0
    }
354
0
    if (langs.empty()) {
355
0
      std::vector<std::string> languagesVector;
356
0
      this->GlobalGenerator->GetEnabledLanguages(languagesVector);
357
0
      for (std::string const& lang : languagesVector) {
358
0
        if (isSupported(lang)) {
359
0
          langs.insert(lang);
360
0
        }
361
0
      }
362
0
    }
363
364
0
    cm::optional<std::string> resolved;
365
0
    if (langs.count("OBJCXX") || (langs.count("CXX") && langs.count("OBJC"))) {
366
0
      resolved = "OBJCXX"; // promote
367
0
    } else if (langs.count("CXX")) {
368
0
      resolved = "CXX";
369
0
    } else if (langs.count("OBJC")) {
370
0
      resolved = "OBJC";
371
0
    } else if (langs.count("C")) {
372
0
      resolved = "C";
373
0
    }
374
0
    defaultLanguage = resolved;
375
0
  }
376
377
0
  return *defaultLanguage;
378
0
}
379
380
cm::optional<std::string> cmGeneratorTarget::GenerateStubForLanguage(
381
  std::string const& language, std::string const& headerFilename,
382
  std::string const& verifyTargetName, cmSourceFile& source) const
383
0
{
384
0
  static std::array<std::pair<cm::string_view, cm::string_view>, 4> const
385
0
    langToExt = { {
386
0
      { "C", ".c" },
387
0
      { "CXX", ".cxx" },
388
0
      { "OBJC", ".m" },
389
0
      { "OBJCXX", ".mm" },
390
0
    } };
391
392
  // NOLINTNEXTLINE(readability-qualified-auto)
393
0
  auto const it =
394
0
    std::find_if(langToExt.begin(), langToExt.end(),
395
0
                 [&](std::pair<cm::string_view, cm::string_view> const& p) {
396
0
                   return p.first == language;
397
0
                 });
398
0
  if (it == langToExt.end()) {
399
0
    return cm::nullopt;
400
0
  }
401
402
0
  std::string filename =
403
0
    cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(), '/',
404
0
             verifyTargetName, '/', headerFilename, it->second);
405
406
0
  cmSourceFile* verificationSource =
407
0
    this->Makefile->GetOrCreateSource(filename);
408
0
  source.SetSpecialSourceType(
409
0
    cmSourceFile::SpecialSourceType::HeaderSetVerificationSource);
410
0
  verificationSource->SetProperty("LANGUAGE", language);
411
412
0
  cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(filename));
413
414
0
  cmGeneratedFileStream fout(filename);
415
0
  fout.SetCopyIfDifferent(true);
416
  // The IWYU "associated" pragma tells include-what-you-use to
417
  // consider the headerFile as part of the entire language
418
  // unit within include-what-you-use and as a result allows
419
  // one to get IWYU advice for headers.
420
  // Also suppress clang-tidy include checks in generated code.
421
0
  fout
422
0
    << "/* NOLINTNEXTLINE(misc-header-include-cycle,misc-include-cleaner) */\n"
423
0
    << "#include <" << headerFilename << "> /* IWYU pragma: associated */\n";
424
0
  fout.close();
425
426
0
  return cm::optional<std::string>(std::move(filename));
427
0
}