Coverage Report

Created: 2026-02-09 06:05

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