Coverage Report

Created: 2026-02-09 06:05

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