Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmGeneratorExpressionEvaluationFile.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 "cmGeneratorExpressionEvaluationFile.h"
4
5
#include <memory>
6
#include <sstream>
7
#include <utility>
8
9
#include <cmext/string_view>
10
11
#include "cmsys/FStream.hxx"
12
13
#include "cmGenExContext.h"
14
#include "cmGeneratedFileStream.h"
15
#include "cmGlobalGenerator.h"
16
#include "cmListFileCache.h"
17
#include "cmLocalGenerator.h"
18
#include "cmMakefile.h"
19
#include "cmMessageType.h"
20
#include "cmSourceFile.h"
21
#include "cmStringAlgorithms.h"
22
#include "cmSystemTools.h"
23
24
cmGeneratorExpressionEvaluationFile::cmGeneratorExpressionEvaluationFile(
25
  std::string input, std::string target,
26
  std::unique_ptr<cmCompiledGeneratorExpression> outputFileExpr,
27
  std::unique_ptr<cmCompiledGeneratorExpression> condition,
28
  bool inputIsContent, std::string newLineCharacter, mode_t permissions,
29
  cmPolicies::PolicyStatus policyStatusCMP0070,
30
  cmPolicies::PolicyStatus policyStatusCMP0189)
31
0
  : Input(std::move(input))
32
0
  , Target(std::move(target))
33
0
  , OutputFileExpr(std::move(outputFileExpr))
34
0
  , Condition(std::move(condition))
35
0
  , InputIsContent(inputIsContent)
36
0
  , NewLineCharacter(std::move(newLineCharacter))
37
0
  , PolicyStatusCMP0070(policyStatusCMP0070)
38
0
  , PolicyStatusCMP0189(policyStatusCMP0189)
39
0
  , Permissions(permissions)
40
0
{
41
0
}
42
43
void cmGeneratorExpressionEvaluationFile::Generate(
44
  cmLocalGenerator* lg, std::string const& config, std::string const& lang,
45
  cmCompiledGeneratorExpression* inputExpression,
46
  std::map<std::string, std::string>& outputFiles, mode_t perm)
47
0
{
48
0
  cm::GenEx::Context context(lg, config, lang);
49
0
  context.SetCMP0189(this->PolicyStatusCMP0189);
50
0
  std::string rawCondition = this->Condition->GetInput();
51
0
  cmGeneratorTarget* target = lg->FindGeneratorTargetToUse(this->Target);
52
0
  if (!rawCondition.empty()) {
53
0
    std::string condResult =
54
0
      this->Condition->Evaluate(context, nullptr, target);
55
0
    if (condResult == "0") {
56
0
      return;
57
0
    }
58
0
    if (condResult != "1") {
59
0
      lg->IssueMessage(MessageType::FATAL_ERROR,
60
0
                       cmStrCat("Evaluation file condition \"", rawCondition,
61
0
                                "\" did not evaluate to valid content. Got \"",
62
0
                                condResult, "\"."));
63
0
      return;
64
0
    }
65
0
  }
66
67
0
  std::string const outputFileName = this->GetOutputFileName(context, target);
68
0
  std::string const& outputContent =
69
0
    inputExpression->Evaluate(context, nullptr, target);
70
71
0
  auto it = outputFiles.find(outputFileName);
72
73
0
  if (it != outputFiles.end()) {
74
0
    if (it->second == outputContent) {
75
0
      return;
76
0
    }
77
0
    lg->IssueMessage(
78
0
      MessageType::FATAL_ERROR,
79
0
      cmStrCat("Evaluation file to be written multiple times with different "
80
0
               "content. "
81
0
               "This is generally caused by the content evaluating the "
82
0
               "configuration type, language, or location of object files:\n ",
83
0
               outputFileName));
84
0
    return;
85
0
  }
86
87
0
  lg->GetMakefile()->AddCMakeOutputFile(outputFileName);
88
0
  this->Files.push_back(outputFileName);
89
0
  outputFiles[outputFileName] = outputContent;
90
91
  // If no new line character is specified, use the platform's native one,
92
  // as writing "\n" in text mode would.
93
0
  std::string newLine = this->NewLineCharacter;
94
0
  if (newLine.empty()) {
95
#ifdef _WIN32
96
    newLine = "\r\n";
97
#else
98
0
    newLine = "\n";
99
0
#endif
100
0
  }
101
0
  std::string fileContent;
102
0
  fileContent.reserve(outputContent.size());
103
0
  std::istringstream iss(outputContent);
104
0
  std::string line;
105
0
  bool hasNewLine = false;
106
0
  while (cmSystemTools::GetLineFromStream(iss, line, &hasNewLine)) {
107
0
    fileContent += line;
108
0
    if (hasNewLine || !this->NewLineCharacter.empty()) {
109
0
      fileContent += newLine;
110
0
    }
111
0
  }
112
113
0
  bool written = false;
114
0
  if (!cmGeneratedFileStream::WriteIfDifferent(outputFileName, fileContent,
115
0
                                               false, &written)) {
116
0
    lg->IssueMessage(MessageType::FATAL_ERROR,
117
0
                     "Could not open file for write in copy operation " +
118
0
                       outputFileName);
119
0
    return;
120
0
  }
121
0
  if (written && perm) {
122
0
    cmSystemTools::SetPermissions(outputFileName.c_str(), perm);
123
0
  }
124
0
}
125
126
void cmGeneratorExpressionEvaluationFile::CreateOutputFile(
127
  cmLocalGenerator* lg, std::string const& config)
128
0
{
129
0
  std::vector<std::string> enabledLanguages;
130
0
  cmGlobalGenerator* gg = lg->GetGlobalGenerator();
131
0
  cmGeneratorTarget* target = lg->FindGeneratorTargetToUse(this->Target);
132
0
  gg->GetEnabledLanguages(enabledLanguages);
133
134
0
  for (std::string const& lang : enabledLanguages) {
135
0
    cm::GenEx::Context context(lg, config, lang);
136
0
    context.SetCMP0189(this->PolicyStatusCMP0189);
137
0
    std::string const name = this->GetOutputFileName(context, target);
138
0
    cmSourceFile* sf = lg->GetMakefile()->GetOrCreateGeneratedSource(name);
139
140
    // Tell the build system generators that there is no build rule
141
    // to generate the file.
142
0
    sf->SetProperty("__CMAKE_GENERATED_BY_CMAKE", "1");
143
144
0
    gg->SetFilenameTargetDepends(
145
0
      sf, this->OutputFileExpr->GetSourceSensitiveTargets());
146
0
  }
147
0
}
148
149
void cmGeneratorExpressionEvaluationFile::Generate(cmLocalGenerator* lg)
150
0
{
151
0
  std::string inputContent;
152
0
  if (this->InputIsContent) {
153
0
    inputContent = this->Input;
154
0
  } else {
155
0
    std::string const inputFileName = this->GetInputFileName(lg);
156
0
    lg->GetMakefile()->AddCMakeDependFile(inputFileName);
157
0
    if (!this->Permissions) {
158
0
      cmSystemTools::GetPermissions(inputFileName.c_str(), this->Permissions);
159
0
    }
160
0
    cmsys::ifstream fin(inputFileName.c_str());
161
0
    if (!fin) {
162
0
      lg->IssueMessage(
163
0
        MessageType::FATAL_ERROR,
164
0
        cmStrCat("Evaluation file \"", inputFileName, "\" cannot be read."));
165
0
      return;
166
0
    }
167
168
0
    std::string line;
169
0
    std::string sep;
170
0
    while (cmSystemTools::GetLineFromStream(fin, line)) {
171
0
      inputContent = cmStrCat(std::move(inputContent), sep, line);
172
0
      sep = "\n";
173
0
    }
174
0
    inputContent += sep;
175
0
  }
176
177
0
  cmListFileBacktrace lfbt = this->OutputFileExpr->GetBacktrace();
178
0
  cmGeneratorExpression contentGE(*lg->GetCMakeInstance(), lfbt);
179
0
  std::unique_ptr<cmCompiledGeneratorExpression> inputExpression =
180
0
    contentGE.Parse(inputContent);
181
182
0
  std::map<std::string, std::string> outputFiles;
183
184
0
  std::vector<std::string> allConfigs =
185
0
    lg->GetMakefile()->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
186
187
0
  std::vector<std::string> enabledLanguages;
188
0
  cmGlobalGenerator* gg = lg->GetGlobalGenerator();
189
0
  gg->GetEnabledLanguages(enabledLanguages);
190
191
0
  for (std::string const& le : enabledLanguages) {
192
0
    for (std::string const& li : allConfigs) {
193
0
      this->Generate(lg, li, le, inputExpression.get(), outputFiles,
194
0
                     this->Permissions);
195
0
      if (cmSystemTools::GetFatalErrorOccurred()) {
196
0
        return;
197
0
      }
198
0
    }
199
0
  }
200
0
}
201
202
std::string cmGeneratorExpressionEvaluationFile::GetInputFileName(
203
  cmLocalGenerator const* lg)
204
0
{
205
0
  std::string inputFileName = this->Input;
206
207
0
  if (cmSystemTools::FileIsFullPath(inputFileName)) {
208
0
    inputFileName = cmSystemTools::CollapseFullPath(inputFileName);
209
0
  } else {
210
0
    inputFileName = this->FixRelativePath(inputFileName, PathForInput, lg);
211
0
  }
212
213
0
  return inputFileName;
214
0
}
215
216
std::string cmGeneratorExpressionEvaluationFile::GetOutputFileName(
217
  cm::GenEx::Context const& context, cmGeneratorTarget* target)
218
0
{
219
0
  std::string outputFileName =
220
0
    this->OutputFileExpr->Evaluate(context, nullptr, target);
221
222
0
  if (cmSystemTools::FileIsFullPath(outputFileName)) {
223
0
    outputFileName = cmSystemTools::CollapseFullPath(outputFileName);
224
0
  } else {
225
0
    outputFileName =
226
0
      this->FixRelativePath(outputFileName, PathForOutput, context.LG);
227
0
  }
228
229
0
  return outputFileName;
230
0
}
231
232
std::string cmGeneratorExpressionEvaluationFile::FixRelativePath(
233
  std::string const& relativePath, PathRole role, cmLocalGenerator const* lg)
234
0
{
235
0
  std::string resultPath;
236
0
  switch (this->PolicyStatusCMP0070) {
237
0
    case cmPolicies::WARN:
238
0
      lg->IssuePolicyWarning(
239
0
        cmPolicies::CMP0070, {},
240
0
        cmStrCat(
241
0
          "file(GENERATE) given relative "_s,
242
0
          (role == PathForInput ? "INPUT"_s : "OUTPUT"_s), " path:\n  "_s,
243
0
          relativePath,
244
0
          "\nThis is not defined behavior unless CMP0070 is set to NEW.  "
245
0
          "For compatibility with older versions of CMake, the previous "
246
0
          "undefined behavior will be used."_s));
247
0
      CM_FALLTHROUGH;
248
0
    case cmPolicies::OLD:
249
      // OLD behavior is to use the relative path unchanged,
250
      // which ends up being used relative to the working dir.
251
0
      resultPath = relativePath;
252
0
      break;
253
0
    case cmPolicies::NEW:
254
      // NEW behavior is to interpret the relative path with respect
255
      // to the current source or binary directory.
256
0
      switch (role) {
257
0
        case PathForInput:
258
0
          resultPath = cmSystemTools::CollapseFullPath(
259
0
            relativePath, lg->GetCurrentSourceDirectory());
260
0
          break;
261
0
        case PathForOutput:
262
0
          resultPath = cmSystemTools::CollapseFullPath(
263
0
            relativePath, lg->GetCurrentBinaryDirectory());
264
0
          break;
265
0
      }
266
0
      break;
267
0
  }
268
0
  return resultPath;
269
0
}