Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmMacroCommand.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 "cmMacroCommand.h"
4
5
#include <functional>
6
#include <utility>
7
8
#include <cm/memory>
9
#include <cm/string_view>
10
#include <cmext/algorithm>
11
#include <cmext/string_view>
12
13
#include "cmExecutionStatus.h"
14
#include "cmFunctionBlocker.h"
15
#include "cmList.h"
16
#include "cmListFileCache.h"
17
#include "cmMakefile.h"
18
#include "cmPolicies.h"
19
#include "cmRange.h"
20
#include "cmState.h"
21
#include "cmStringAlgorithms.h"
22
#include "cmSystemTools.h"
23
24
namespace {
25
26
// define the class for macro commands
27
class cmMacroHelperCommand
28
{
29
public:
30
  /**
31
   * This is called when the command is first encountered in
32
   * the CMakeLists.txt file.
33
   */
34
  bool operator()(std::vector<cmListFileArgument> const& args,
35
                  cmExecutionStatus& inStatus) const;
36
37
  std::vector<std::string> Args;
38
  std::vector<cmListFileFunction> Functions;
39
  cmPolicies::PolicyMap Policies;
40
  std::string FilePath;
41
};
42
43
bool cmMacroHelperCommand::operator()(
44
  std::vector<cmListFileArgument> const& args,
45
  cmExecutionStatus& inStatus) const
46
0
{
47
0
  cmMakefile& makefile = inStatus.GetMakefile();
48
49
  // Expand the argument list to the macro.
50
0
  std::vector<std::string> expandedArgs;
51
0
  makefile.ExpandArguments(args, expandedArgs);
52
53
  // make sure the number of arguments passed is at least the number
54
  // required by the signature
55
0
  if (expandedArgs.size() < this->Args.size() - 1) {
56
0
    std::string errorMsg =
57
0
      cmStrCat("Macro invoked with incorrect arguments for macro named: ",
58
0
               this->Args[0]);
59
0
    inStatus.SetError(errorMsg);
60
0
    return false;
61
0
  }
62
63
0
  cmMakefile::MacroPushPop macroScope(&makefile, this->FilePath,
64
0
                                      this->Policies);
65
66
  // set the value of argc
67
0
  std::string argcDef = std::to_string(expandedArgs.size());
68
69
0
  auto expIt = expandedArgs.begin() + (this->Args.size() - 1);
70
0
  std::string expandedArgn =
71
0
    cmList::to_string(cmMakeRange(expIt, expandedArgs.end()));
72
0
  std::string expandedArgv = cmList::to_string(expandedArgs);
73
0
  std::vector<std::string> variables;
74
0
  variables.reserve(this->Args.size() - 1);
75
0
  for (unsigned int j = 1; j < this->Args.size(); ++j) {
76
0
    variables.emplace_back(cmStrCat("${", this->Args[j], '}'));
77
0
  }
78
0
  std::vector<std::string> argVs;
79
0
  argVs.reserve(expandedArgs.size());
80
0
  for (unsigned int j = 0; j < expandedArgs.size(); ++j) {
81
0
    argVs.emplace_back(cmStrCat("${ARGV", j, '}'));
82
0
  }
83
  // Invoke all the functions that were collected in the block.
84
  // for each function
85
0
  for (cmListFileFunction const& func : this->Functions) {
86
    // Replace the formal arguments and then invoke the command.
87
0
    std::vector<cmListFileArgument> newLFFArgs;
88
0
    newLFFArgs.reserve(func.Arguments().size());
89
90
    // for each argument of the current function
91
0
    for (cmListFileArgument const& k : func.Arguments()) {
92
0
      cmListFileArgument arg;
93
0
      arg.Value = k.Value;
94
0
      if (k.Delim != cmListFileArgument::Bracket) {
95
        // replace formal arguments
96
0
        for (unsigned int j = 0; j < variables.size(); ++j) {
97
0
          cmSystemTools::ReplaceString(arg.Value, variables[j],
98
0
                                       expandedArgs[j]);
99
0
        }
100
        // replace argc
101
0
        cmSystemTools::ReplaceString(arg.Value, "${ARGC}", argcDef);
102
103
0
        cmSystemTools::ReplaceString(arg.Value, "${ARGN}", expandedArgn);
104
0
        cmSystemTools::ReplaceString(arg.Value, "${ARGV}", expandedArgv);
105
106
        // if the current argument of the current function has ${ARGV in it
107
        // then try replacing ARGV values
108
0
        if (arg.Value.find("${ARGV") != std::string::npos) {
109
0
          for (unsigned int t = 0; t < expandedArgs.size(); ++t) {
110
0
            cmSystemTools::ReplaceString(arg.Value, argVs[t], expandedArgs[t]);
111
0
          }
112
0
        }
113
0
      }
114
0
      arg.Delim = k.Delim;
115
0
      arg.Line = k.Line;
116
0
      newLFFArgs.push_back(std::move(arg));
117
0
    }
118
0
    cmListFileFunction newLFF{ func.OriginalName(), func.Line(),
119
0
                               func.LineEnd(), std::move(newLFFArgs) };
120
0
    cmExecutionStatus status(makefile);
121
0
    if (!makefile.ExecuteCommand(newLFF, status) || status.GetNestedError()) {
122
      // The error message should have already included the call stack
123
      // so we do not need to report an error here.
124
0
      macroScope.Quiet();
125
0
      inStatus.SetNestedError();
126
0
      return false;
127
0
    }
128
0
    if (status.GetReturnInvoked()) {
129
0
      inStatus.SetReturnInvoked(status.GetReturnVariables());
130
0
      return true;
131
0
    }
132
0
    if (status.GetBreakInvoked()) {
133
0
      inStatus.SetBreakInvoked();
134
0
      return true;
135
0
    }
136
0
    if (status.HasExitCode()) {
137
0
      inStatus.SetExitCode(status.GetExitCode());
138
0
      return true;
139
0
    }
140
0
  }
141
0
  return true;
142
0
}
143
144
class cmMacroFunctionBlocker : public cmFunctionBlocker
145
{
146
public:
147
0
  cm::string_view StartCommandName() const override { return "macro"_s; }
148
0
  cm::string_view EndCommandName() const override { return "endmacro"_s; }
149
150
  bool ArgumentsMatch(cmListFileFunction const&,
151
                      cmMakefile& mf) const override;
152
153
  bool Replay(std::vector<cmListFileFunction> functions,
154
              cmExecutionStatus& status) override;
155
156
  std::vector<std::string> Args;
157
};
158
159
bool cmMacroFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
160
                                            cmMakefile& mf) const
161
0
{
162
0
  std::vector<std::string> expandedArguments;
163
0
  mf.ExpandArguments(lff.Arguments(), expandedArguments);
164
0
  return expandedArguments.empty() || expandedArguments[0] == this->Args[0];
165
0
}
166
167
bool cmMacroFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
168
                                    cmExecutionStatus& status)
169
0
{
170
0
  cmMakefile& mf = status.GetMakefile();
171
0
  mf.AppendProperty("MACROS", this->Args[0]);
172
  // create a new command and add it to cmake
173
0
  cmMacroHelperCommand f;
174
0
  f.Args = this->Args;
175
0
  f.Functions = std::move(functions);
176
0
  f.FilePath = this->GetStartingContext().FilePath;
177
0
  mf.RecordPolicies(f.Policies);
178
0
  return mf.GetState()->AddScriptedCommand(
179
0
    this->Args[0],
180
0
    BT<cmState::Command>(std::move(f),
181
0
                         mf.GetBacktrace().Push(this->GetStartingContext())),
182
0
    mf);
183
0
}
184
}
185
186
bool cmMacroCommand(std::vector<std::string> const& args,
187
                    cmExecutionStatus& status)
188
0
{
189
0
  if (args.empty()) {
190
0
    status.SetError("called with incorrect number of arguments");
191
0
    return false;
192
0
  }
193
194
  // create a function blocker
195
0
  {
196
0
    auto fb = cm::make_unique<cmMacroFunctionBlocker>();
197
0
    cm::append(fb->Args, args);
198
0
    status.GetMakefile().AddFunctionBlocker(std::move(fb));
199
0
  }
200
0
  return true;
201
0
}