Coverage Report

Created: 2026-04-29 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmBlockCommand.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
4
#include "cmBlockCommand.h"
5
6
#include <cstdint>
7
#include <utility>
8
9
#include <cm/memory>
10
#include <cm/optional>
11
#include <cm/string_view>
12
#include <cmext/enum_set>
13
#include <cmext/string_view>
14
15
#include "cmArgumentParser.h"
16
#include "cmArgumentParserTypes.h"
17
#include "cmExecutionStatus.h"
18
#include "cmFunctionBlocker.h"
19
#include "cmListFileCache.h"
20
#include "cmMakefile.h"
21
#include "cmStringAlgorithms.h"
22
#include "cmSystemTools.h"
23
24
namespace {
25
enum class ScopeType : std::uint8_t
26
{
27
  VARIABLES,
28
  POLICIES,
29
  DIAGNOSTICS,
30
};
31
using ScopeSet = cm::enum_set<ScopeType>;
32
33
class BlockScopePushPop
34
{
35
public:
36
  BlockScopePushPop(cmMakefile* m, ScopeSet const& scopes);
37
0
  ~BlockScopePushPop() = default;
38
39
  BlockScopePushPop(BlockScopePushPop const&) = delete;
40
  BlockScopePushPop& operator=(BlockScopePushPop const&) = delete;
41
42
private:
43
  std::unique_ptr<cmMakefile::PolicyPushPop> PolicyScope;
44
  std::unique_ptr<cmMakefile::VariablePushPop> VariableScope;
45
  std::unique_ptr<cmMakefile::DiagnosticPushPop> DiagnosticScope;
46
};
47
48
BlockScopePushPop::BlockScopePushPop(cmMakefile* mf, ScopeSet const& scopes)
49
0
{
50
0
  if (scopes.contains(ScopeType::POLICIES)) {
51
0
    this->PolicyScope = cm::make_unique<cmMakefile::PolicyPushPop>(mf);
52
0
  }
53
0
  if (scopes.contains(ScopeType::VARIABLES)) {
54
0
    this->VariableScope = cm::make_unique<cmMakefile::VariablePushPop>(mf);
55
0
  }
56
0
  if (scopes.contains(ScopeType::DIAGNOSTICS)) {
57
0
    this->DiagnosticScope = cm::make_unique<cmMakefile::DiagnosticPushPop>(mf);
58
0
  }
59
0
}
60
61
class cmBlockFunctionBlocker : public cmFunctionBlocker
62
{
63
public:
64
  cmBlockFunctionBlocker(cmMakefile* mf, ScopeSet const& scopes,
65
                         std::vector<std::string> variableNames);
66
  ~cmBlockFunctionBlocker() override;
67
68
0
  cm::string_view StartCommandName() const override { return "block"_s; }
69
0
  cm::string_view EndCommandName() const override { return "endblock"_s; }
70
71
0
  bool EndCommandSupportsArguments() const override { return false; }
72
73
  bool ArgumentsMatch(cmListFileFunction const& lff,
74
                      cmMakefile& mf) const override;
75
76
  bool Replay(std::vector<cmListFileFunction> functions,
77
              cmExecutionStatus& inStatus) override;
78
79
private:
80
  cmMakefile* Makefile;
81
  ScopeSet Scopes;
82
  BlockScopePushPop BlockScope;
83
  std::vector<std::string> VariableNames;
84
};
85
86
cmBlockFunctionBlocker::cmBlockFunctionBlocker(
87
  cmMakefile* const mf, ScopeSet const& scopes,
88
  std::vector<std::string> variableNames)
89
0
  : Makefile{ mf }
90
0
  , Scopes{ scopes }
91
0
  , BlockScope{ mf, scopes }
92
0
  , VariableNames{ std::move(variableNames) }
93
0
{
94
0
}
95
96
cmBlockFunctionBlocker::~cmBlockFunctionBlocker()
97
0
{
98
0
  if (this->Scopes.contains(ScopeType::VARIABLES)) {
99
0
    this->Makefile->RaiseScope(this->VariableNames);
100
0
  }
101
0
}
102
103
bool cmBlockFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
104
                                            cmMakefile&) const
105
0
{
106
  // no arguments expected for endblock()
107
  // but this method should not be called because EndCommandHasArguments()
108
  // returns false.
109
0
  return lff.Arguments().empty();
110
0
}
111
112
bool cmBlockFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
113
                                    cmExecutionStatus& inStatus)
114
0
{
115
0
  auto& mf = inStatus.GetMakefile();
116
117
  // Invoke all the functions that were collected in the block.
118
0
  for (cmListFileFunction const& fn : functions) {
119
0
    cmExecutionStatus status(mf);
120
0
    mf.ExecuteCommand(fn, status);
121
0
    if (status.GetReturnInvoked()) {
122
0
      mf.RaiseScope(status.GetReturnVariables());
123
0
      inStatus.SetReturnInvoked(status.GetReturnVariables());
124
0
      return true;
125
0
    }
126
0
    if (status.GetBreakInvoked()) {
127
0
      inStatus.SetBreakInvoked();
128
0
      return true;
129
0
    }
130
0
    if (status.GetContinueInvoked()) {
131
0
      inStatus.SetContinueInvoked();
132
0
      return true;
133
0
    }
134
0
    if (status.HasExitCode()) {
135
0
      inStatus.SetExitCode(status.GetExitCode());
136
0
      return true;
137
0
    }
138
0
    if (cmSystemTools::GetFatalErrorOccurred()) {
139
0
      return true;
140
0
    }
141
0
  }
142
0
  return true;
143
0
}
144
145
} // anonymous namespace
146
147
bool cmBlockCommand(std::vector<std::string> const& args,
148
                    cmExecutionStatus& status)
149
0
{
150
0
  struct Arguments : public ArgumentParser::ParseResult
151
0
  {
152
0
    cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> ScopeFor;
153
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Propagate;
154
0
  };
155
0
  static auto const parser = cmArgumentParser<Arguments>{}
156
0
                               .Bind("SCOPE_FOR"_s, &Arguments::ScopeFor)
157
0
                               .Bind("PROPAGATE"_s, &Arguments::Propagate);
158
0
  std::vector<std::string> unrecognizedArguments;
159
0
  auto parsedArgs = parser.Parse(args, &unrecognizedArguments);
160
161
0
  if (!unrecognizedArguments.empty()) {
162
0
    status.SetError(cmStrCat("called with unsupported argument \"",
163
0
                             unrecognizedArguments[0], '"'));
164
0
    cmSystemTools::SetFatalErrorOccurred();
165
0
    return false;
166
0
  }
167
168
0
  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
169
0
    cmSystemTools::SetFatalErrorOccurred();
170
0
    return true;
171
0
  }
172
173
0
  ScopeSet scopes;
174
175
0
  if (parsedArgs.ScopeFor) {
176
0
    for (auto const& scope : *parsedArgs.ScopeFor) {
177
0
      if (scope == "VARIABLES"_s) {
178
0
        scopes.insert(ScopeType::VARIABLES);
179
0
        continue;
180
0
      }
181
0
      if (scope == "POLICIES"_s) {
182
0
        scopes.insert(ScopeType::POLICIES);
183
0
        continue;
184
0
      }
185
0
      if (scope == "DIAGNOSTICS"_s) {
186
0
        scopes.insert(ScopeType::DIAGNOSTICS);
187
0
        continue;
188
0
      }
189
0
      status.SetError(cmStrCat("SCOPE_FOR unsupported scope \"", scope, '"'));
190
0
      cmSystemTools::SetFatalErrorOccurred();
191
0
      return false;
192
0
    }
193
0
  } else {
194
0
    scopes = { ScopeType::VARIABLES, ScopeType::POLICIES,
195
0
               ScopeType::DIAGNOSTICS };
196
0
  }
197
0
  if (!scopes.contains(ScopeType::VARIABLES) &&
198
0
      !parsedArgs.Propagate.empty()) {
199
0
    status.SetError(
200
0
      "PROPAGATE cannot be specified without a new scope for VARIABLES");
201
0
    cmSystemTools::SetFatalErrorOccurred();
202
0
    return false;
203
0
  }
204
205
  // create a function blocker
206
0
  auto fb = cm::make_unique<cmBlockFunctionBlocker>(
207
0
    &status.GetMakefile(), scopes, parsedArgs.Propagate);
208
0
  status.GetMakefile().AddFunctionBlocker(std::move(fb));
209
210
0
  return true;
211
0
}