Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmExportInstallCMakeConfigGenerator.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 "cmExportInstallCMakeConfigGenerator.h"
4
5
#include <algorithm>
6
#include <functional>
7
#include <map>
8
#include <memory>
9
#include <set>
10
#include <sstream>
11
#include <utility>
12
#include <vector>
13
14
#include <cm/string_view>
15
#include <cmext/string_view>
16
17
#include "cmExportFileGenerator.h"
18
#include "cmExportSet.h"
19
#include "cmFileSet.h"
20
#include "cmGenExContext.h"
21
#include "cmGeneratedFileStream.h"
22
#include "cmGeneratorExpression.h"
23
#include "cmGeneratorTarget.h"
24
#include "cmInstallExportGenerator.h"
25
#include "cmInstallFileSetGenerator.h"
26
#include "cmLocalGenerator.h"
27
#include "cmMakefile.h"
28
#include "cmMessageType.h"
29
#include "cmOutputConverter.h"
30
#include "cmStateTypes.h"
31
#include "cmStringAlgorithms.h"
32
#include "cmSystemTools.h"
33
#include "cmTargetExport.h"
34
#include "cmValue.h"
35
36
cmExportInstallCMakeConfigGenerator::cmExportInstallCMakeConfigGenerator(
37
  cmInstallExportGenerator* iegen)
38
0
  : cmExportInstallFileGenerator(iegen)
39
0
{
40
0
}
Unexecuted instantiation: cmExportInstallCMakeConfigGenerator::cmExportInstallCMakeConfigGenerator(cmInstallExportGenerator*)
Unexecuted instantiation: cmExportInstallCMakeConfigGenerator::cmExportInstallCMakeConfigGenerator(cmInstallExportGenerator*)
41
42
std::string cmExportInstallCMakeConfigGenerator::GetConfigImportFileGlob()
43
  const
44
0
{
45
0
  std::string glob = cmStrCat(this->FileBase, "-*", this->FileExt);
46
0
  return glob;
47
0
}
48
49
bool cmExportInstallCMakeConfigGenerator::GenerateMainFile(std::ostream& os)
50
0
{
51
0
  std::vector<cmTargetExport const*> allTargets;
52
0
  {
53
0
    std::string expectedTargets;
54
0
    std::string sep;
55
0
    auto visitor = [&](cmTargetExport const* te) {
56
0
      allTargets.push_back(te);
57
0
      expectedTargets += sep + this->Namespace + te->Target->GetExportName();
58
0
      sep = " ";
59
0
    };
60
61
0
    if (!this->CollectExports(visitor)) {
62
0
      return false;
63
0
    }
64
65
0
    this->GenerateExpectedTargetsCode(os, expectedTargets);
66
0
  }
67
68
  // Compute the relative import prefix for the file
69
0
  this->GenerateImportPrefix(os);
70
71
0
  bool requiresConfigFiles = false;
72
  // Create all the imported targets.
73
0
  for (cmTargetExport const* te : allTargets) {
74
0
    cmGeneratorTarget* gt = te->Target;
75
0
    cmStateEnums::TargetType targetType = this->GetExportTargetType(te);
76
77
0
    requiresConfigFiles =
78
0
      requiresConfigFiles || targetType != cmStateEnums::INTERFACE_LIBRARY;
79
80
0
    this->GenerateImportTargetCode(os, gt, targetType);
81
82
0
    ImportPropertyMap properties;
83
0
    if (!this->PopulateInterfaceProperties(te, properties)) {
84
0
      return false;
85
0
    }
86
87
0
    if (this->PopulateInterfaceLinkLibrariesProperty(
88
0
          gt, cmGeneratorExpression::InstallInterface, properties) &&
89
0
        !this->ExportOld) {
90
0
      this->SetRequiredCMakeVersion(2, 8, 12);
91
0
    }
92
0
    if (targetType == cmStateEnums::INTERFACE_LIBRARY) {
93
0
      this->SetRequiredCMakeVersion(3, 0, 0);
94
0
    }
95
0
    if (gt->GetProperty("INTERFACE_SOURCES")) {
96
      // We can only generate INTERFACE_SOURCES in CMake 3.3, but CMake 3.1
97
      // can consume them.
98
0
      this->SetRequiredCMakeVersion(3, 1, 0);
99
0
    }
100
101
0
    this->GenerateInterfaceProperties(gt, os, properties);
102
103
0
    this->GenerateTargetFileSets(gt, os, te);
104
0
  }
105
106
0
  this->LoadConfigFiles(os);
107
108
0
  bool result = true;
109
110
0
  std::string cxx_modules_name = this->GetExportSet()->GetName();
111
0
  this->GenerateCxxModuleInformation(cxx_modules_name, os);
112
0
  if (requiresConfigFiles) {
113
0
    for (std::string const& c : this->Configurations) {
114
0
      if (!this->GenerateImportCxxModuleConfigTargetInclusion(cxx_modules_name,
115
0
                                                              c)) {
116
0
        result = false;
117
0
      }
118
0
    }
119
0
  }
120
121
0
  this->CleanupTemporaryVariables(os);
122
0
  this->GenerateImportedFileCheckLoop(os);
123
124
  // Generate an import file for each configuration.
125
  // Don't do this if we only export INTERFACE_LIBRARY targets.
126
0
  if (requiresConfigFiles) {
127
0
    for (std::string const& c : this->Configurations) {
128
0
      if (!this->GenerateImportFileConfig(c)) {
129
0
        result = false;
130
0
      }
131
0
    }
132
0
  }
133
134
0
  this->GenerateMissingTargetsCheckCode(os);
135
136
0
  return result;
137
0
}
138
139
void cmExportInstallCMakeConfigGenerator::GenerateImportPrefix(
140
  std::ostream& os)
141
0
{
142
  // Set an _IMPORT_PREFIX variable for import location properties
143
  // to reference if they are relative to the install prefix.
144
0
  std::string installPrefix =
145
0
    this->IEGen->GetLocalGenerator()->GetMakefile()->GetSafeDefinition(
146
0
      "CMAKE_INSTALL_PREFIX");
147
0
  std::string const& expDest = this->IEGen->GetDestination();
148
0
  if (cmSystemTools::FileIsFullPath(expDest)) {
149
    // The export file is being installed to an absolute path so the
150
    // package is not relocatable.  Use the configured install prefix.
151
    /* clang-format off */
152
0
    os <<
153
0
      "# The installation prefix configured by this project.\n"
154
0
      "set(_IMPORT_PREFIX \"" << installPrefix << "\")\n"
155
0
      "\n";
156
    /* clang-format on */
157
0
  } else {
158
    // Add code to compute the installation prefix relative to the
159
    // import file location.
160
0
    std::string absDest = cmStrCat(installPrefix, '/', expDest);
161
0
    std::string absDestS = absDest + '/';
162
0
    os << "# Compute the installation prefix relative to this file.\n"
163
0
          "get_filename_component(_IMPORT_PREFIX"
164
0
          " \"${CMAKE_CURRENT_LIST_FILE}\" PATH)\n";
165
0
    if (cmHasLiteralPrefix(absDestS, "/lib/") ||
166
0
        cmHasLiteralPrefix(absDestS, "/lib64/") ||
167
0
        cmHasLiteralPrefix(absDestS, "/libx32/") ||
168
0
        cmHasLiteralPrefix(absDestS, "/usr/lib/") ||
169
0
        cmHasLiteralPrefix(absDestS, "/usr/lib64/") ||
170
0
        cmHasLiteralPrefix(absDestS, "/usr/libx32/")) {
171
      // Handle "/usr move" symlinks created by some Linux distros.
172
      /* clang-format off */
173
0
      os <<
174
0
        "# Use original install prefix when loaded through a\n"
175
0
        "# cross-prefix symbolic link such as /lib -> /usr/lib.\n"
176
0
        "get_filename_component(_realCurr \"${_IMPORT_PREFIX}\" REALPATH)\n"
177
0
        "get_filename_component(_realOrig \"" << absDest << "\" REALPATH)\n"
178
0
        "if(_realCurr STREQUAL _realOrig)\n"
179
0
        "  set(_IMPORT_PREFIX \"" << absDest << "\")\n"
180
0
        "endif()\n"
181
0
        "unset(_realOrig)\n"
182
0
        "unset(_realCurr)\n";
183
      /* clang-format on */
184
0
    }
185
0
    std::string dest = expDest;
186
0
    while (!dest.empty()) {
187
0
      os << "get_filename_component(_IMPORT_PREFIX \"${_IMPORT_PREFIX}\" "
188
0
            "PATH)\n";
189
0
      dest = cmSystemTools::GetFilenamePath(dest);
190
0
    }
191
0
    os << "if(_IMPORT_PREFIX STREQUAL \"/\")\n"
192
0
          "  set(_IMPORT_PREFIX \"\")\n"
193
0
          "endif()\n"
194
0
          "\n";
195
0
  }
196
0
}
197
198
void cmExportInstallCMakeConfigGenerator::CleanupTemporaryVariables(
199
  std::ostream& os)
200
0
{
201
  /* clang-format off */
202
0
  os << "# Cleanup temporary variables.\n"
203
0
        "set(_IMPORT_PREFIX)\n"
204
0
        "\n";
205
  /* clang-format on */
206
0
}
207
208
void cmExportInstallCMakeConfigGenerator::LoadConfigFiles(std::ostream& os)
209
0
{
210
  // Now load per-configuration properties for them.
211
  /* clang-format off */
212
0
  os << "# Load information for each installed configuration.\n"
213
0
        "file(GLOB _cmake_config_files \"${CMAKE_CURRENT_LIST_DIR}/"
214
0
        << this->GetConfigImportFileGlob() << "\")\n"
215
0
        "foreach(_cmake_config_file IN LISTS _cmake_config_files)\n"
216
0
        "  include(\"${_cmake_config_file}\")\n"
217
0
        "endforeach()\n"
218
0
        "unset(_cmake_config_file)\n"
219
0
        "unset(_cmake_config_files)\n"
220
0
        "\n";
221
  /* clang-format on */
222
0
}
223
224
void cmExportInstallCMakeConfigGenerator::GenerateImportConfig(
225
  std::ostream& os, std::string const& config)
226
0
{
227
  // Start with the import file header.
228
0
  this->GenerateImportHeaderCode(os, config);
229
230
  // Generate the per-config target information.
231
0
  this->cmExportFileGenerator::GenerateImportConfig(os, config);
232
233
  // End with the import file footer.
234
0
  this->GenerateImportFooterCode(os);
235
0
}
236
237
void cmExportInstallCMakeConfigGenerator::GenerateImportTargetsConfig(
238
  std::ostream& os, std::string const& config, std::string const& suffix)
239
0
{
240
  // Add each target in the set to the export.
241
0
  for (std::unique_ptr<cmTargetExport> const& te :
242
0
       this->GetExportSet()->GetTargetExports()) {
243
    // Collect import properties for this target.
244
0
    if (this->GetExportTargetType(te.get()) ==
245
0
        cmStateEnums::INTERFACE_LIBRARY) {
246
0
      continue;
247
0
    }
248
249
0
    ImportPropertyMap properties;
250
0
    std::set<std::string> importedLocations;
251
252
0
    this->PopulateImportProperties(config, suffix, te.get(), properties,
253
0
                                   importedLocations);
254
255
    // If any file location was set for the target add it to the
256
    // import file.
257
0
    if (!properties.empty()) {
258
0
      cmGeneratorTarget const* const gtgt = te->Target;
259
0
      std::string const importedXcFrameworkLocation =
260
0
        this->GetImportXcFrameworkLocation(config, te.get());
261
262
0
      this->SetImportLinkInterface(config, suffix,
263
0
                                   cmGeneratorExpression::InstallInterface,
264
0
                                   gtgt, properties);
265
266
0
      this->GenerateImportPropertyCode(os, config, suffix, gtgt, properties,
267
0
                                       importedXcFrameworkLocation);
268
0
      this->GenerateImportedFileChecksCode(
269
0
        os, gtgt, properties, importedLocations, importedXcFrameworkLocation);
270
0
    }
271
0
  }
272
0
}
273
274
namespace {
275
bool EntryIsContextSensitive(
276
  std::unique_ptr<cmCompiledGeneratorExpression> const& cge)
277
0
{
278
0
  return cge->GetHadContextSensitiveCondition();
279
0
}
280
}
281
282
std::string cmExportInstallCMakeConfigGenerator::GetFileSetDirectories(
283
  cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport const* te)
284
0
{
285
0
  std::vector<std::string> resultVector;
286
287
0
  auto configs =
288
0
    gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
289
290
0
  cmGeneratorExpression ge(*gte->Makefile->GetCMakeInstance());
291
0
  auto cge =
292
0
    ge.Parse(te->FileSetGenerators.at(fileSet->GetName())->GetDestination());
293
294
0
  for (auto const& config : configs) {
295
0
    auto unescapedDest = cge->Evaluate(gte->LocalGenerator, config, gte);
296
0
    auto dest = cmOutputConverter::EscapeForCMake(
297
0
      unescapedDest, cmOutputConverter::WrapQuotes::NoWrap);
298
0
    if (!cmSystemTools::FileIsFullPath(unescapedDest)) {
299
0
      dest = cmStrCat("${_IMPORT_PREFIX}/", dest);
300
0
    }
301
302
0
    auto const& type = fileSet->GetType();
303
    // C++ modules do not support interface file sets which are dependent upon
304
    // the configuration.
305
0
    if (cge->GetHadContextSensitiveCondition() && type == "CXX_MODULES"_s) {
306
0
      auto* mf = this->IEGen->GetLocalGenerator()->GetMakefile();
307
0
      std::ostringstream e;
308
0
      e << "The \"" << gte->GetName() << "\" target's interface file set \""
309
0
        << fileSet->GetName() << "\" of type \"" << type
310
0
        << "\" contains context-sensitive base file entries which is not "
311
0
           "supported.";
312
0
      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
313
0
      return std::string{};
314
0
    }
315
316
0
    if (cge->GetHadContextSensitiveCondition() && configs.size() != 1) {
317
0
      resultVector.push_back(
318
0
        cmStrCat("\"$<$<CONFIG:", config, ">:", dest, ">\""));
319
0
    } else {
320
0
      resultVector.emplace_back(cmStrCat('"', dest, '"'));
321
0
      break;
322
0
    }
323
0
  }
324
325
0
  return cmJoin(resultVector, " ");
326
0
}
327
328
std::string cmExportInstallCMakeConfigGenerator::GetFileSetFiles(
329
  cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport const* te)
330
0
{
331
0
  std::vector<std::string> resultVector;
332
333
0
  auto configs =
334
0
    gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
335
336
0
  auto fileEntries = fileSet->CompileFileEntries();
337
0
  auto directoryEntries = fileSet->CompileDirectoryEntries();
338
339
0
  cmGeneratorExpression destGe(*gte->Makefile->GetCMakeInstance());
340
0
  auto destCge = destGe.Parse(
341
0
    te->FileSetGenerators.at(fileSet->GetName())->GetDestination());
342
343
0
  for (auto const& config : configs) {
344
0
    cm::GenEx::Context context(gte->LocalGenerator, config);
345
0
    auto directories =
346
0
      fileSet->EvaluateDirectoryEntries(directoryEntries, context, gte);
347
348
0
    std::map<std::string, std::vector<std::string>> files;
349
0
    for (auto const& entry : fileEntries) {
350
0
      fileSet->EvaluateFileEntry(directories, files, entry, context, gte);
351
0
    }
352
0
    auto unescapedDest = destCge->Evaluate(gte->LocalGenerator, config, gte);
353
0
    auto dest =
354
0
      cmStrCat(cmOutputConverter::EscapeForCMake(
355
0
                 unescapedDest, cmOutputConverter::WrapQuotes::NoWrap),
356
0
               '/');
357
0
    if (!cmSystemTools::FileIsFullPath(unescapedDest)) {
358
0
      dest = cmStrCat("${_IMPORT_PREFIX}/", dest);
359
0
    }
360
361
0
    bool const contextSensitive = destCge->GetHadContextSensitiveCondition() ||
362
0
      std::any_of(directoryEntries.begin(), directoryEntries.end(),
363
0
                  EntryIsContextSensitive) ||
364
0
      std::any_of(fileEntries.begin(), fileEntries.end(),
365
0
                  EntryIsContextSensitive);
366
367
0
    auto const& type = fileSet->GetType();
368
    // C++ modules do not support interface file sets which are dependent upon
369
    // the configuration.
370
0
    if (contextSensitive && type == "CXX_MODULES"_s) {
371
0
      auto* mf = this->IEGen->GetLocalGenerator()->GetMakefile();
372
0
      std::ostringstream e;
373
0
      e << "The \"" << gte->GetName() << "\" target's interface file set \""
374
0
        << fileSet->GetName() << "\" of type \"" << type
375
0
        << "\" contains context-sensitive base file entries which is not "
376
0
           "supported.";
377
0
      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
378
0
      return std::string{};
379
0
    }
380
381
0
    for (auto const& it : files) {
382
0
      auto prefix = it.first.empty() ? "" : cmStrCat(it.first, '/');
383
0
      for (auto const& filename : it.second) {
384
0
        auto relFile =
385
0
          cmStrCat(prefix, cmSystemTools::GetFilenameNameView(filename));
386
0
        auto escapedFile =
387
0
          cmStrCat(dest,
388
0
                   cmOutputConverter::EscapeForCMake(
389
0
                     relFile, cmOutputConverter::WrapQuotes::NoWrap));
390
0
        if (contextSensitive && configs.size() != 1) {
391
0
          resultVector.push_back(
392
0
            cmStrCat("\"$<$<CONFIG:", config, ">:", escapedFile, ">\""));
393
0
        } else {
394
0
          resultVector.emplace_back(cmStrCat('"', escapedFile, '"'));
395
0
        }
396
0
      }
397
0
    }
398
399
0
    if (!(contextSensitive && configs.size() != 1)) {
400
0
      break;
401
0
    }
402
0
  }
403
404
0
  return cmJoin(resultVector, " ");
405
0
}
406
407
std::string cmExportInstallCMakeConfigGenerator::GetCxxModulesDirectory() const
408
0
{
409
0
  return IEGen->GetCxxModuleDirectory();
410
0
}
411
412
void cmExportInstallCMakeConfigGenerator::GenerateCxxModuleConfigInformation(
413
  std::string const& name, std::ostream& os) const
414
0
{
415
  // Now load per-configuration properties for them.
416
  /* clang-format off */
417
0
  os << "# Load information for each installed configuration.\n"
418
0
        "file(GLOB _cmake_cxx_module_includes \"${CMAKE_CURRENT_LIST_DIR}/cxx-modules-" << name << "-*.cmake\")\n"
419
0
        "foreach(_cmake_cxx_module_include IN LISTS _cmake_cxx_module_includes)\n"
420
0
        "  include(\"${_cmake_cxx_module_include}\")\n"
421
0
        "endforeach()\n"
422
0
        "unset(_cmake_cxx_module_include)\n"
423
0
        "unset(_cmake_cxx_module_includes)\n";
424
  /* clang-format on */
425
0
}
426
427
bool cmExportInstallCMakeConfigGenerator::
428
  GenerateImportCxxModuleConfigTargetInclusion(std::string const& name,
429
                                               std::string const& config)
430
0
{
431
0
  auto cxx_modules_dirname = this->GetCxxModulesDirectory();
432
0
  if (cxx_modules_dirname.empty()) {
433
0
    return true;
434
0
  }
435
436
0
  std::string filename_config = config;
437
0
  if (filename_config.empty()) {
438
0
    filename_config = "noconfig";
439
0
  }
440
441
0
  std::string const dest =
442
0
    cmStrCat(this->FileDir, '/', cxx_modules_dirname, '/');
443
0
  std::string fileName =
444
0
    cmStrCat(dest, "cxx-modules-", name, '-', filename_config, ".cmake");
445
446
0
  cmGeneratedFileStream os(fileName, true);
447
0
  if (!os) {
448
0
    std::string se = cmSystemTools::GetLastSystemError();
449
0
    std::ostringstream e;
450
0
    e << "cannot write to file \"" << fileName << "\": " << se;
451
0
    cmSystemTools::Error(e.str());
452
0
    return false;
453
0
  }
454
0
  os.SetCopyIfDifferent(true);
455
456
  // Record this per-config import file.
457
0
  this->ConfigCxxModuleFiles[config] = fileName;
458
459
0
  auto& prop_files = this->ConfigCxxModuleTargetFiles[config];
460
0
  for (auto const* tgt : this->ExportedTargets) {
461
    // Only targets with C++ module sources will have a
462
    // collator-generated install script.
463
0
    if (!tgt->HaveCxx20ModuleSources()) {
464
0
      continue;
465
0
    }
466
467
0
    auto prop_filename = cmStrCat("target-", tgt->GetFilesystemExportName(),
468
0
                                  '-', filename_config, ".cmake");
469
0
    prop_files.emplace_back(cmStrCat(dest, prop_filename));
470
0
    os << "include(\"${CMAKE_CURRENT_LIST_DIR}/" << prop_filename << "\")\n";
471
0
  }
472
473
0
  return true;
474
0
}