Coverage Report

Created: 2026-06-15 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmVariableWatchCommand.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 "cmVariableWatchCommand.h"
4
5
#include <limits>
6
#include <memory>
7
#include <utility>
8
9
#include "cmExecutionStatus.h"
10
#include "cmListFileCache.h"
11
#include "cmMakefile.h"
12
#include "cmMessageType.h"
13
#include "cmPolicies.h"
14
#include "cmStringAlgorithms.h"
15
#include "cmSystemTools.h"
16
#include "cmValue.h"
17
#include "cmVariableWatch.h"
18
#include "cmake.h"
19
20
class cmLocalGenerator;
21
22
namespace {
23
struct cmVariableWatchCallbackData
24
{
25
  bool InCallback;
26
  std::string Command;
27
};
28
29
void cmVariableWatchCommandVariableAccessed(
30
  std::string const& variable, cmVariableWatch::AccessType accessType,
31
  void* clientData, char const* newValue, cmMakefile const* mf)
32
0
{
33
0
  cmVariableWatchCallbackData* data =
34
0
    static_cast<cmVariableWatchCallbackData*>(clientData);
35
36
0
  if (data->InCallback) {
37
0
    return;
38
0
  }
39
0
  data->InCallback = true;
40
41
0
  auto accessString = cmVariableWatch::GetAccessAsString(accessType);
42
43
  /// Ultra bad!!
44
0
  cmMakefile* makefile = const_cast<cmMakefile*>(mf);
45
46
0
  std::string stack = *mf->GetProperty("LISTFILE_STACK");
47
0
  if (!data->Command.empty()) {
48
0
    cmValue const currentListFile =
49
0
      mf->GetDefinition("CMAKE_CURRENT_LIST_FILE");
50
0
    auto const fakeLineNo =
51
0
      std::numeric_limits<decltype(cmListFileArgument::Line)>::max();
52
53
0
    std::vector<cmListFileArgument> newLFFArgs{
54
0
      { variable, cmListFileArgument::Quoted, fakeLineNo },
55
0
      { accessString, cmListFileArgument::Quoted, fakeLineNo },
56
0
      { newValue ? newValue : "", cmListFileArgument::Quoted, fakeLineNo },
57
0
      { *currentListFile, cmListFileArgument::Quoted, fakeLineNo },
58
0
      { stack, cmListFileArgument::Quoted, fakeLineNo }
59
0
    };
60
0
    {
61
0
      cmPolicies::PolicyStatus cmp0219 =
62
0
        makefile->CheckCMP0219(data->Command, newLFFArgs);
63
0
      if (cmp0219 == cmPolicies::NEW) {
64
0
        for (cmListFileArgument& arg : newLFFArgs) {
65
0
          cmSystemTools::ReplaceString(arg.Value, "\\", "\\\\");
66
0
        }
67
0
      } else if (cmp0219 == cmPolicies::WARN) {
68
0
        makefile->IssueCMP0219Warning(data->Command, newLFFArgs);
69
0
      }
70
0
    }
71
72
0
    cmListFileFunction newLFF{ data->Command, fakeLineNo, fakeLineNo,
73
0
                               std::move(newLFFArgs) };
74
0
    cmExecutionStatus status(*makefile);
75
0
    if (!makefile->ExecuteCommand(newLFF, status)) {
76
0
      cmSystemTools::Error(
77
0
        cmStrCat("Error in cmake code at\nUnknown:0:\nA command failed "
78
0
                 "during the invocation of callback \"",
79
0
                 data->Command, "\"."));
80
0
    }
81
0
  } else {
82
0
    makefile->IssueMessage(
83
0
      MessageType::LOG,
84
0
      cmStrCat("Variable \"", variable, "\" was accessed using ", accessString,
85
0
               " with value \"", (newValue ? newValue : ""), "\"."));
86
0
  }
87
88
0
  data->InCallback = false;
89
0
}
90
91
void deleteVariableWatchCallbackData(void* client_data)
92
0
{
93
0
  cmVariableWatchCallbackData* data =
94
0
    static_cast<cmVariableWatchCallbackData*>(client_data);
95
0
  delete data;
96
0
}
97
98
/** This command does not really have a final pass but it needs to
99
    stay alive since it owns variable watch callback information. */
100
class FinalAction
101
{
102
public:
103
  /* NOLINTNEXTLINE(performance-unnecessary-value-param) */
104
  FinalAction(cmMakefile* makefile, std::string variable)
105
0
    : Action{ std::make_shared<Impl>(makefile, std::move(variable)) }
106
0
  {
107
0
  }
108
109
0
  void operator()(cmLocalGenerator&, cmListFileBacktrace const&) const {}
110
111
private:
112
  struct Impl
113
  {
114
    Impl(cmMakefile* makefile, std::string variable)
115
0
      : Makefile{ makefile }
116
0
      , Variable{ std::move(variable) }
117
0
    {
118
0
    }
119
120
    ~Impl()
121
0
    {
122
0
      this->Makefile->GetCMakeInstance()->GetVariableWatch()->RemoveWatch(
123
0
        this->Variable, cmVariableWatchCommandVariableAccessed);
124
0
    }
125
126
    cmMakefile* const Makefile;
127
    std::string const Variable;
128
  };
129
130
  std::shared_ptr<Impl const> Action;
131
};
132
} // anonymous namespace
133
134
bool cmVariableWatchCommand(std::vector<std::string> const& args,
135
                            cmExecutionStatus& status)
136
0
{
137
0
  if (args.empty()) {
138
0
    status.SetError("must be called with at least one argument.");
139
0
    return false;
140
0
  }
141
0
  std::string const& variable = args[0];
142
0
  std::string command;
143
0
  if (args.size() > 1) {
144
0
    command = args[1];
145
0
  }
146
0
  if (variable == "CMAKE_CURRENT_LIST_FILE") {
147
0
    status.SetError(cmStrCat("cannot be set on the variable: ", variable));
148
0
    return false;
149
0
  }
150
151
0
  auto* const data = new cmVariableWatchCallbackData;
152
153
0
  data->InCallback = false;
154
0
  data->Command = std::move(command);
155
156
0
  if (!status.GetMakefile().GetCMakeInstance()->GetVariableWatch()->AddWatch(
157
0
        variable, cmVariableWatchCommandVariableAccessed, data,
158
0
        deleteVariableWatchCallbackData)) {
159
0
    deleteVariableWatchCallbackData(data);
160
0
    return false;
161
0
  }
162
163
0
  status.GetMakefile().AddGeneratorAction(
164
0
    FinalAction{ &status.GetMakefile(), variable });
165
0
  return true;
166
0
}