Coverage Report

Created: 2026-06-15 07:03

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