Coverage Report

Created: 2026-06-15 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmInstrumentationCommand.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 "cmInstrumentationCommand.h"
4
5
#include <algorithm>
6
#include <cstdlib>
7
#include <functional>
8
#include <set>
9
#include <sstream>
10
11
#include <cmext/string_view>
12
13
#include <cm3p/json/reader.h>
14
#include <cm3p/json/value.h>
15
16
#include "cmsys/String.h"
17
18
#include "cmArgumentParser.h"
19
#include "cmArgumentParserTypes.h"
20
#include "cmExecutionStatus.h"
21
#include "cmInstrumentation.h"
22
#include "cmInstrumentationQuery.h"
23
#include "cmList.h"
24
#include "cmMakefile.h"
25
#include "cmMessageType.h"
26
#include "cmStringAlgorithms.h"
27
#include "cmValue.h"
28
#include "cmake.h"
29
30
namespace {
31
32
using Version = cmInstrumentationQuery::Version;
33
34
bool validateVersion(std::string const& key, std::string const& versionString,
35
                     int& version, cmExecutionStatus& status)
36
0
{
37
0
  if (!std::all_of(versionString.begin(), versionString.end(),
38
0
                   cmsysString_isdigit)) {
39
0
    status.SetError(cmStrCat("given a non-integer ", key, '.'));
40
0
    return false;
41
0
  }
42
0
  version = std::atoi(versionString.c_str());
43
0
  if (version != 1) {
44
0
    status.SetError(
45
0
      cmStrCat("given an unsupported ", key, " \"", versionString,
46
0
               "\" (the only currently supported version is 1)."));
47
0
    return false;
48
0
  }
49
0
  return true;
50
0
}
51
52
bool validateDataVersion(std::string const& versionString, Version& version,
53
                         cmExecutionStatus& status)
54
0
{
55
0
  char const* vStart = versionString.c_str();
56
0
  if (!std::all_of(versionString.begin(), versionString.end(), [](char c) {
57
0
        return cmsysString_isdigit(c) || c == '.';
58
0
      })) {
59
0
    status.SetError(
60
0
      cmStrCat("given a malformed DATA_VERSION \"", versionString,
61
0
               "\". A numeric major or major.minor version is required."));
62
63
0
    return false;
64
0
  }
65
66
0
  version.Major = std::atoi(vStart);
67
0
  version.Minor = 0;
68
0
  std::string::size_type pos = versionString.find('.');
69
0
  if (pos != std::string::npos) {
70
0
    vStart += pos + 1;
71
0
    version.Minor = std::atoi(vStart);
72
0
  }
73
74
0
  if (version.Major < 1 || version.Minor < 0) {
75
0
    status.SetError(
76
0
      cmStrCat("given a malformed DATA_VERSION \"", versionString,
77
0
               "\". A numeric major or major.minor version is required."));
78
0
    return false;
79
0
  }
80
81
0
  if (!cmInstrumentationQuery::ValidDataVersion(version)) {
82
0
    status.SetError(
83
0
      cmStrCat("given an unsupported DATA_VERSION \"", versionString,
84
0
               "\" (the maximum currently supported version is 1.1)."));
85
0
    return false;
86
0
  }
87
88
0
  return true;
89
0
}
90
91
template <typename E>
92
std::function<bool(std::string const&, E&)> EnumParser(
93
  std::vector<std::string> const toString)
94
0
{
95
0
  return [toString](std::string const& value, E& out) -> bool {
96
0
    for (size_t i = 0; i < toString.size(); ++i) {
97
0
      if (value == toString[i]) {
98
0
        out = (E)i;
99
0
        return true;
100
0
      }
101
0
    }
102
0
    return false;
103
0
  };
Unexecuted instantiation: cmInstrumentationCommand.cxx:(anonymous namespace)::EnumParser<cmInstrumentationQuery::Option>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, cmInstrumentationQuery::Option&)#1}::operator()(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, cmInstrumentationQuery::Option&) const
Unexecuted instantiation: cmInstrumentationCommand.cxx:(anonymous namespace)::EnumParser<cmInstrumentationQuery::Hook>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, cmInstrumentationQuery::Hook&)#1}::operator()(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, cmInstrumentationQuery::Hook&) const
104
0
}
Unexecuted instantiation: cmInstrumentationCommand.cxx:std::__1::function<bool (std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, cmInstrumentationQuery::Option&)> (anonymous namespace)::EnumParser<cmInstrumentationQuery::Option>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >)
Unexecuted instantiation: cmInstrumentationCommand.cxx:std::__1::function<bool (std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, cmInstrumentationQuery::Hook&)> (anonymous namespace)::EnumParser<cmInstrumentationQuery::Hook>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >)
105
}
106
107
bool cmInstrumentationCommand(std::vector<std::string> const& args,
108
                              cmExecutionStatus& status)
109
0
{
110
0
  if (args.empty()) {
111
0
    status.SetError("must be called with arguments.");
112
0
    return false;
113
0
  }
114
115
0
  if (status.GetMakefile().GetCMakeInstance()->GetInInitialCache()) {
116
0
    status.GetMakefile().IssueMessage(
117
0
      MessageType::FATAL_ERROR,
118
0
      "Cannot invoke cmake_instrumentation() within an initial cache file.");
119
0
    return false;
120
0
  }
121
122
0
  struct Arguments : public ArgumentParser::ParseResult
123
0
  {
124
0
    ArgumentParser::NonEmpty<std::string> ApiVersion;
125
0
    ArgumentParser::NonEmpty<std::string> DataVersion;
126
0
    ArgumentParser::NonEmpty<std::vector<std::string>> Options;
127
0
    ArgumentParser::NonEmpty<std::vector<std::string>> Hooks;
128
0
    ArgumentParser::NonEmpty<std::vector<std::vector<std::string>>> Callbacks;
129
0
    ArgumentParser::NonEmpty<std::vector<std::vector<std::string>>>
130
0
      CustomContent;
131
0
  };
132
133
0
  static auto const parser =
134
0
    cmArgumentParser<Arguments>{}
135
0
      .Bind("API_VERSION"_s, &Arguments::ApiVersion)
136
0
      .Bind("DATA_VERSION"_s, &Arguments::DataVersion)
137
0
      .Bind("OPTIONS"_s, &Arguments::Options)
138
0
      .Bind("HOOKS"_s, &Arguments::Hooks)
139
0
      .Bind("CALLBACK"_s, &Arguments::Callbacks)
140
0
      .Bind("CUSTOM_CONTENT"_s, &Arguments::CustomContent);
141
142
0
  std::vector<std::string> unparsedArguments;
143
0
  Arguments const arguments = parser.Parse(args, &unparsedArguments);
144
145
0
  if (arguments.MaybeReportError(status.GetMakefile())) {
146
0
    return true;
147
0
  }
148
0
  if (!unparsedArguments.empty()) {
149
0
    status.SetError("given unknown argument \"" + unparsedArguments.front() +
150
0
                    "\".");
151
0
    return false;
152
0
  }
153
0
  int apiVersion;
154
0
  Version dataVersion;
155
0
  if (!validateVersion("API_VERSION", arguments.ApiVersion, apiVersion,
156
0
                       status) ||
157
0
      !validateDataVersion(arguments.DataVersion, dataVersion, status)) {
158
0
    return false;
159
0
  }
160
161
0
  std::set<cmInstrumentationQuery::Option> options;
162
0
  auto optionParser = EnumParser<cmInstrumentationQuery::Option>(
163
0
    cmInstrumentationQuery::OptionString);
164
0
  for (auto const& arg : arguments.Options) {
165
0
    cmInstrumentationQuery::Option option;
166
0
    if (!optionParser(arg, option)) {
167
0
      status.SetError(
168
0
        cmStrCat("given invalid argument to OPTIONS \"", arg, '"'));
169
0
      return false;
170
0
    }
171
0
    options.insert(option);
172
0
  }
173
174
0
  std::set<cmInstrumentationQuery::Hook> hooks;
175
0
  auto hookParser = EnumParser<cmInstrumentationQuery::Hook>(
176
0
    cmInstrumentationQuery::HookString);
177
0
  for (auto const& arg : arguments.Hooks) {
178
0
    cmInstrumentationQuery::Hook hook;
179
0
    if (!hookParser(arg, hook)) {
180
0
      status.SetError(
181
0
        cmStrCat("given invalid argument to HOOKS \"", arg, '"'));
182
0
      return false;
183
0
    }
184
0
    hooks.insert(hook);
185
0
  }
186
187
  // Generate custom content
188
0
  cmInstrumentation* instrumentation =
189
0
    status.GetMakefile().GetCMakeInstance()->GetInstrumentation();
190
0
  for (auto const& content : arguments.CustomContent) {
191
0
    if (content.size() != 3) {
192
0
      status.SetError("CUSTOM_CONTENT expected 3 arguments");
193
0
      return false;
194
0
    }
195
0
    std::string const label = content[0];
196
0
    std::string const type = content[1];
197
0
    std::string const contentString = content[2];
198
0
    Json::Value value;
199
0
    if (type == "STRING") {
200
0
      value = contentString;
201
0
    } else if (type == "BOOL") {
202
0
      value = !cmValue(contentString).IsOff();
203
0
    } else if (type == "LIST") {
204
0
      value = Json::arrayValue;
205
0
      for (auto const& item : cmList(contentString)) {
206
0
        value.append(item);
207
0
      }
208
0
    } else if (type == "JSON") {
209
0
      Json::CharReaderBuilder builder;
210
0
      std::istringstream iss(contentString);
211
0
      if (!Json::parseFromStream(builder, iss, &value, nullptr)) {
212
0
        status.SetError(
213
0
          cmStrCat("failed to parse custom content as JSON: ", contentString));
214
0
        return false;
215
0
      }
216
0
    } else {
217
0
      status.SetError(
218
0
        cmStrCat("got an invalid type for CUSTOM_CONTENT: ", type));
219
0
      return false;
220
0
    }
221
0
    instrumentation->AddCustomContent(content.front(), value);
222
0
  }
223
224
  // Write query file
225
0
  instrumentation->WriteJSONQuery(dataVersion, options, hooks,
226
0
                                  arguments.Callbacks);
227
228
0
  return true;
229
0
}