Coverage Report

Created: 2026-03-12 06:35

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
bool validateVersion(std::string const& key, std::string const& versionString,
33
                     int& version, cmExecutionStatus& status)
34
0
{
35
0
  if (!std::all_of(versionString.begin(), versionString.end(),
36
0
                   cmsysString_isdigit)) {
37
0
    status.SetError(cmStrCat("given a non-integer ", key, '.'));
38
0
    return false;
39
0
  }
40
0
  version = std::atoi(versionString.c_str());
41
0
  if (version != 1) {
42
0
    status.SetError(
43
0
      cmStrCat("given an unsupported ", key, " \"", versionString,
44
0
               "\" (the only currently supported version is 1)."));
45
0
    return false;
46
0
  }
47
0
  return true;
48
0
}
49
50
template <typename E>
51
std::function<bool(std::string const&, E&)> EnumParser(
52
  std::vector<std::string> const toString)
53
0
{
54
0
  return [toString](std::string const& value, E& out) -> bool {
55
0
    for (size_t i = 0; i < toString.size(); ++i) {
56
0
      if (value == toString[i]) {
57
0
        out = (E)i;
58
0
        return true;
59
0
      }
60
0
    }
61
0
    return false;
62
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
63
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> > > >)
64
}
65
66
bool cmInstrumentationCommand(std::vector<std::string> const& args,
67
                              cmExecutionStatus& status)
68
0
{
69
0
  if (args.empty()) {
70
0
    status.SetError("must be called with arguments.");
71
0
    return false;
72
0
  }
73
74
0
  if (status.GetMakefile().GetCMakeInstance()->GetInInitialCache()) {
75
0
    status.GetMakefile().IssueMessage(
76
0
      MessageType::FATAL_ERROR,
77
0
      "Cannot invoke cmake_instrumentation() within an initial cache file.");
78
0
    return false;
79
0
  }
80
81
0
  struct Arguments : public ArgumentParser::ParseResult
82
0
  {
83
0
    ArgumentParser::NonEmpty<std::string> ApiVersion;
84
0
    ArgumentParser::NonEmpty<std::string> DataVersion;
85
0
    ArgumentParser::NonEmpty<std::vector<std::string>> Options;
86
0
    ArgumentParser::NonEmpty<std::vector<std::string>> Hooks;
87
0
    ArgumentParser::NonEmpty<std::vector<std::vector<std::string>>> Callbacks;
88
0
    ArgumentParser::NonEmpty<std::vector<std::vector<std::string>>>
89
0
      CustomContent;
90
0
  };
91
92
0
  static auto const parser =
93
0
    cmArgumentParser<Arguments>{}
94
0
      .Bind("API_VERSION"_s, &Arguments::ApiVersion)
95
0
      .Bind("DATA_VERSION"_s, &Arguments::DataVersion)
96
0
      .Bind("OPTIONS"_s, &Arguments::Options)
97
0
      .Bind("HOOKS"_s, &Arguments::Hooks)
98
0
      .Bind("CALLBACK"_s, &Arguments::Callbacks)
99
0
      .Bind("CUSTOM_CONTENT"_s, &Arguments::CustomContent);
100
101
0
  std::vector<std::string> unparsedArguments;
102
0
  Arguments const arguments = parser.Parse(args, &unparsedArguments);
103
104
0
  if (arguments.MaybeReportError(status.GetMakefile())) {
105
0
    return true;
106
0
  }
107
0
  if (!unparsedArguments.empty()) {
108
0
    status.SetError("given unknown argument \"" + unparsedArguments.front() +
109
0
                    "\".");
110
0
    return false;
111
0
  }
112
0
  int apiVersion;
113
0
  int dataVersion;
114
0
  if (!validateVersion("API_VERSION", arguments.ApiVersion, apiVersion,
115
0
                       status) ||
116
0
      !validateVersion("DATA_VERSION", arguments.DataVersion, dataVersion,
117
0
                       status)) {
118
0
    return false;
119
0
  }
120
121
0
  std::set<cmInstrumentationQuery::Option> options;
122
0
  auto optionParser = EnumParser<cmInstrumentationQuery::Option>(
123
0
    cmInstrumentationQuery::OptionString);
124
0
  for (auto const& arg : arguments.Options) {
125
0
    cmInstrumentationQuery::Option option;
126
0
    if (!optionParser(arg, option)) {
127
0
      status.SetError(
128
0
        cmStrCat("given invalid argument to OPTIONS \"", arg, '"'));
129
0
      return false;
130
0
    }
131
0
    options.insert(option);
132
0
  }
133
134
0
  std::set<cmInstrumentationQuery::Hook> hooks;
135
0
  auto hookParser = EnumParser<cmInstrumentationQuery::Hook>(
136
0
    cmInstrumentationQuery::HookString);
137
0
  for (auto const& arg : arguments.Hooks) {
138
0
    cmInstrumentationQuery::Hook hook;
139
0
    if (!hookParser(arg, hook)) {
140
0
      status.SetError(
141
0
        cmStrCat("given invalid argument to HOOKS \"", arg, '"'));
142
0
      return false;
143
0
    }
144
0
    hooks.insert(hook);
145
0
  }
146
147
  // Generate custom content
148
0
  cmInstrumentation* instrumentation =
149
0
    status.GetMakefile().GetCMakeInstance()->GetInstrumentation();
150
0
  for (auto const& content : arguments.CustomContent) {
151
0
    if (content.size() != 3) {
152
0
      status.SetError("CUSTOM_CONTENT expected 3 arguments");
153
0
      return false;
154
0
    }
155
0
    std::string const label = content[0];
156
0
    std::string const type = content[1];
157
0
    std::string const contentString = content[2];
158
0
    Json::Value value;
159
0
    if (type == "STRING") {
160
0
      value = contentString;
161
0
    } else if (type == "BOOL") {
162
0
      value = !cmValue(contentString).IsOff();
163
0
    } else if (type == "LIST") {
164
0
      value = Json::arrayValue;
165
0
      for (auto const& item : cmList(contentString)) {
166
0
        value.append(item);
167
0
      }
168
0
    } else if (type == "JSON") {
169
0
      Json::CharReaderBuilder builder;
170
0
      std::istringstream iss(contentString);
171
0
      if (!Json::parseFromStream(builder, iss, &value, nullptr)) {
172
0
        status.SetError(
173
0
          cmStrCat("failed to parse custom content as JSON: ", contentString));
174
0
        return false;
175
0
      }
176
0
    } else {
177
0
      status.SetError(
178
0
        cmStrCat("got an invalid type for CUSTOM_CONTENT: ", type));
179
0
      return false;
180
0
    }
181
0
    instrumentation->AddCustomContent(content.front(), value);
182
0
  }
183
184
  // Write query file
185
0
  instrumentation->WriteJSONQuery(options, hooks, arguments.Callbacks);
186
187
0
  return true;
188
0
}