Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmExportBuildCMakeConfigGenerator.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 "cmExportBuildCMakeConfigGenerator.h"
4
5
#include <algorithm>
6
#include <cstddef>
7
#include <functional>
8
#include <map>
9
#include <memory>
10
#include <set>
11
#include <sstream>
12
#include <utility>
13
#include <vector>
14
15
#include <cm/string_view>
16
#include <cmext/string_view>
17
18
#include "cmCryptoHash.h"
19
#include "cmExportSet.h"
20
#include "cmFileSet.h"
21
#include "cmGenExContext.h"
22
#include "cmGeneratedFileStream.h"
23
#include "cmGeneratorExpression.h"
24
#include "cmGeneratorTarget.h"
25
#include "cmLocalGenerator.h"
26
#include "cmMakefile.h"
27
#include "cmMessageType.h"
28
#include "cmOutputConverter.h"
29
#include "cmStateTypes.h"
30
#include "cmStringAlgorithms.h"
31
#include "cmSystemTools.h"
32
#include "cmTarget.h"
33
34
cmExportBuildCMakeConfigGenerator::cmExportBuildCMakeConfigGenerator()
35
0
{
36
0
  this->LG = nullptr;
37
0
  this->ExportSet = nullptr;
38
0
}
Unexecuted instantiation: cmExportBuildCMakeConfigGenerator::cmExportBuildCMakeConfigGenerator()
Unexecuted instantiation: cmExportBuildCMakeConfigGenerator::cmExportBuildCMakeConfigGenerator()
39
40
bool cmExportBuildCMakeConfigGenerator::GenerateMainFile(std::ostream& os)
41
0
{
42
0
  {
43
0
    std::string expectedTargets;
44
0
    std::string sep;
45
0
    bool generatedInterfaceRequired = false;
46
0
    auto visitor = [&](cmGeneratorTarget const* te) {
47
0
      expectedTargets += sep + this->Namespace + te->GetExportName();
48
0
      sep = " ";
49
50
0
      generatedInterfaceRequired |=
51
0
        this->GetExportTargetType(te) == cmStateEnums::INTERFACE_LIBRARY;
52
0
    };
53
54
0
    if (!this->CollectExports(visitor)) {
55
0
      return false;
56
0
    }
57
58
0
    if (generatedInterfaceRequired) {
59
0
      this->SetRequiredCMakeVersion(3, 0, 0);
60
0
    }
61
0
    this->GenerateExpectedTargetsCode(os, expectedTargets);
62
0
  }
63
64
  // Create all the imported targets.
65
0
  for (auto const& exp : this->Exports) {
66
0
    cmGeneratorTarget* gte = exp.Target;
67
0
    this->GenerateImportTargetCode(os, gte, this->GetExportTargetType(gte));
68
69
0
    gte->Target->AppendBuildInterfaceIncludes();
70
71
0
    ImportPropertyMap properties;
72
0
    if (!this->PopulateInterfaceProperties(gte, properties)) {
73
0
      return false;
74
0
    }
75
76
0
    this->PopulateInterfaceLinkLibrariesProperty(
77
0
      gte, cmGeneratorExpression::BuildInterface, properties);
78
79
0
    this->GenerateInterfaceProperties(gte, os, properties);
80
81
0
    this->GenerateTargetFileSets(gte, os);
82
0
  }
83
84
0
  std::string cxx_modules_name;
85
0
  if (this->ExportSet) {
86
0
    cxx_modules_name = this->ExportSet->GetName();
87
0
  } else {
88
0
    cmCryptoHash hasher(cmCryptoHash::AlgoSHA3_512);
89
0
    constexpr std::size_t HASH_TRUNCATION = 12;
90
0
    for (auto const& target : this->Targets) {
91
0
      hasher.Append(target.Name);
92
0
    }
93
0
    cxx_modules_name = hasher.FinalizeHex().substr(0, HASH_TRUNCATION);
94
0
  }
95
96
0
  this->GenerateCxxModuleInformation(cxx_modules_name, os);
97
98
  // Generate import file content for each configuration.
99
0
  for (std::string const& c : this->Configurations) {
100
0
    this->GenerateImportConfig(os, c);
101
0
  }
102
103
  // Generate import file content for each configuration.
104
0
  for (std::string const& c : this->Configurations) {
105
0
    this->GenerateImportCxxModuleConfigTargetInclusion(cxx_modules_name, c);
106
0
  }
107
108
0
  this->GenerateMissingTargetsCheckCode(os);
109
110
0
  return true;
111
0
}
112
113
void cmExportBuildCMakeConfigGenerator::GenerateImportTargetsConfig(
114
  std::ostream& os, std::string const& config, std::string const& suffix)
115
0
{
116
0
  for (auto const& exp : this->Exports) {
117
0
    cmGeneratorTarget* target = exp.Target;
118
119
    // Collect import properties for this target.
120
0
    ImportPropertyMap properties;
121
122
0
    if (this->GetExportTargetType(target) != cmStateEnums::INTERFACE_LIBRARY) {
123
0
      this->SetImportLocationProperty(config, suffix, target, properties);
124
0
    }
125
0
    if (!properties.empty()) {
126
      // Get the rest of the target details.
127
0
      if (this->GetExportTargetType(target) !=
128
0
          cmStateEnums::INTERFACE_LIBRARY) {
129
0
        this->SetImportDetailProperties(config, suffix, target, properties);
130
0
        this->SetImportLinkInterface(config, suffix,
131
0
                                     cmGeneratorExpression::BuildInterface,
132
0
                                     target, properties);
133
0
      }
134
135
      // TODO: PUBLIC_HEADER_LOCATION
136
      // This should wait until the build feature propagation stuff
137
      // is done.  Then this can be a propagated include directory.
138
      // this->GenerateImportProperty(config, te->HeaderGenerator,
139
      //                              properties);
140
141
      // Generate code in the export file.
142
0
      std::string importedXcFrameworkLocation = exp.XcFrameworkLocation;
143
0
      if (!importedXcFrameworkLocation.empty()) {
144
0
        importedXcFrameworkLocation = cmGeneratorExpression::Preprocess(
145
0
          importedXcFrameworkLocation,
146
0
          cmGeneratorExpression::PreprocessContext::BuildInterface);
147
0
        importedXcFrameworkLocation = cmGeneratorExpression::Evaluate(
148
0
          importedXcFrameworkLocation, exp.Target->GetLocalGenerator(), config,
149
0
          exp.Target, nullptr, exp.Target);
150
0
        if (!importedXcFrameworkLocation.empty() &&
151
0
            !cmSystemTools::FileIsFullPath(importedXcFrameworkLocation)) {
152
0
          importedXcFrameworkLocation =
153
0
            cmStrCat(this->LG->GetCurrentBinaryDirectory(), '/',
154
0
                     importedXcFrameworkLocation);
155
0
        }
156
0
      }
157
0
      this->GenerateImportPropertyCode(os, config, suffix, target, properties,
158
0
                                       importedXcFrameworkLocation);
159
0
    }
160
0
  }
161
0
}
162
163
namespace {
164
bool EntryIsContextSensitive(
165
  std::unique_ptr<cmCompiledGeneratorExpression> const& cge)
166
0
{
167
0
  return cge->GetHadContextSensitiveCondition();
168
0
}
169
}
170
171
std::string cmExportBuildCMakeConfigGenerator::GetFileSetDirectories(
172
  cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport const* /*te*/)
173
0
{
174
0
  std::vector<std::string> resultVector;
175
176
0
  auto configs =
177
0
    gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
178
0
  auto directoryEntries = fileSet->CompileDirectoryEntries();
179
180
0
  for (auto const& config : configs) {
181
0
    cm::GenEx::Context context(gte->LocalGenerator, config);
182
0
    auto directories =
183
0
      fileSet->EvaluateDirectoryEntries(directoryEntries, context, gte);
184
185
0
    bool const contextSensitive =
186
0
      std::any_of(directoryEntries.begin(), directoryEntries.end(),
187
0
                  EntryIsContextSensitive);
188
189
0
    auto const& type = fileSet->GetType();
190
    // C++ modules do not support interface file sets which are dependent upon
191
    // the configuration.
192
0
    if (contextSensitive && type == "CXX_MODULES"_s) {
193
0
      auto* mf = this->LG->GetMakefile();
194
0
      std::ostringstream e;
195
0
      e << "The \"" << gte->GetName() << "\" target's interface file set \""
196
0
        << fileSet->GetName() << "\" of type \"" << type
197
0
        << "\" contains context-sensitive base directory entries which is not "
198
0
           "supported.";
199
0
      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
200
0
      return std::string{};
201
0
    }
202
203
0
    for (auto const& directory : directories) {
204
0
      auto dest = cmOutputConverter::EscapeForCMake(
205
0
        directory, cmOutputConverter::WrapQuotes::NoWrap);
206
207
0
      if (contextSensitive && configs.size() != 1) {
208
0
        resultVector.push_back(
209
0
          cmStrCat("\"$<$<CONFIG:", config, ">:", dest, ">\""));
210
0
      } else {
211
0
        resultVector.emplace_back(cmStrCat('"', dest, '"'));
212
0
      }
213
0
    }
214
0
  }
215
216
0
  return cmJoin(resultVector, " ");
217
0
}
218
219
std::string cmExportBuildCMakeConfigGenerator::GetFileSetFiles(
220
  cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport const* /*te*/)
221
0
{
222
0
  std::vector<std::string> resultVector;
223
224
0
  auto configs =
225
0
    gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
226
227
0
  auto fileEntries = fileSet->CompileFileEntries();
228
0
  auto directoryEntries = fileSet->CompileDirectoryEntries();
229
230
0
  for (auto const& config : configs) {
231
0
    cm::GenEx::Context context(gte->LocalGenerator, config);
232
0
    auto directories =
233
0
      fileSet->EvaluateDirectoryEntries(directoryEntries, context, gte);
234
235
0
    std::map<std::string, std::vector<std::string>> files;
236
0
    for (auto const& entry : fileEntries) {
237
0
      fileSet->EvaluateFileEntry(directories, files, entry, context, gte);
238
0
    }
239
240
0
    bool const contextSensitive =
241
0
      std::any_of(directoryEntries.begin(), directoryEntries.end(),
242
0
                  EntryIsContextSensitive) ||
243
0
      std::any_of(fileEntries.begin(), fileEntries.end(),
244
0
                  EntryIsContextSensitive);
245
246
0
    auto const& type = fileSet->GetType();
247
    // C++ modules do not support interface file sets which are dependent upon
248
    // the configuration.
249
0
    if (contextSensitive && type == "CXX_MODULES"_s) {
250
0
      auto* mf = this->LG->GetMakefile();
251
0
      std::ostringstream e;
252
0
      e << "The \"" << gte->GetName() << "\" target's interface file set \""
253
0
        << fileSet->GetName() << "\" of type \"" << type
254
0
        << "\" contains context-sensitive file entries which is not "
255
0
           "supported.";
256
0
      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
257
0
      return std::string{};
258
0
    }
259
260
0
    for (auto const& it : files) {
261
0
      for (auto const& filename : it.second) {
262
0
        auto escapedFile = cmOutputConverter::EscapeForCMake(
263
0
          filename, cmOutputConverter::WrapQuotes::NoWrap);
264
0
        if (contextSensitive && configs.size() != 1) {
265
0
          resultVector.push_back(
266
0
            cmStrCat("\"$<$<CONFIG:", config, ">:", escapedFile, ">\""));
267
0
        } else {
268
0
          resultVector.emplace_back(cmStrCat('"', escapedFile, '"'));
269
0
        }
270
0
      }
271
0
    }
272
273
0
    if (!(contextSensitive && configs.size() != 1)) {
274
0
      break;
275
0
    }
276
0
  }
277
278
0
  return cmJoin(resultVector, " ");
279
0
}
280
281
void cmExportBuildCMakeConfigGenerator::GenerateCxxModuleConfigInformation(
282
  std::string const& name, std::ostream& os) const
283
0
{
284
0
  char const* opt = "";
285
0
  if (this->Configurations.size() > 1) {
286
    // With more than one configuration, each individual file is optional.
287
0
    opt = " OPTIONAL";
288
0
  }
289
290
  // Generate import file content for each configuration.
291
0
  for (std::string c : this->Configurations) {
292
0
    if (c.empty()) {
293
0
      c = "noconfig";
294
0
    }
295
0
    os << "include(\"${CMAKE_CURRENT_LIST_DIR}/cxx-modules-" << name << '-'
296
0
       << c << ".cmake\"" << opt << ")\n";
297
0
  }
298
0
}
299
300
bool cmExportBuildCMakeConfigGenerator::
301
  GenerateImportCxxModuleConfigTargetInclusion(std::string const& name,
302
                                               std::string config) const
303
0
{
304
0
  auto cxx_modules_dirname = this->GetCxxModulesDirectory();
305
0
  if (cxx_modules_dirname.empty()) {
306
0
    return true;
307
0
  }
308
309
0
  if (config.empty()) {
310
0
    config = "noconfig";
311
0
  }
312
313
0
  std::string fileName =
314
0
    cmStrCat(this->FileDir, '/', cxx_modules_dirname, "/cxx-modules-", name,
315
0
             '-', config, ".cmake");
316
317
0
  cmGeneratedFileStream os(fileName, true);
318
0
  if (!os) {
319
0
    std::string se = cmSystemTools::GetLastSystemError();
320
0
    std::ostringstream e;
321
0
    e << "cannot write to file \"" << fileName << "\": " << se;
322
0
    cmSystemTools::Error(e.str());
323
0
    return false;
324
0
  }
325
0
  os.SetCopyIfDifferent(true);
326
327
0
  for (auto const* tgt : this->ExportedTargets) {
328
    // Only targets with C++ module sources will have a
329
    // collator-generated install script.
330
0
    if (!tgt->HaveCxx20ModuleSources()) {
331
0
      continue;
332
0
    }
333
334
0
    os << "include(\"${CMAKE_CURRENT_LIST_DIR}/target-"
335
0
       << tgt->GetFilesystemExportName() << '-' << config << ".cmake\")\n";
336
0
  }
337
338
0
  return true;
339
0
}