Coverage Report

Created: 2026-04-29 07:01

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