Coverage Report

Created: 2026-03-12 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmFileAPICommand.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 "cmFileAPICommand.h"
4
5
#include <algorithm>
6
#include <array>
7
#include <cstdlib>
8
#include <utility>
9
10
#include <cm/string_view>
11
#include <cmext/string_view>
12
13
#include "cmsys/String.h"
14
15
#include "cmArgumentParser.h"
16
#include "cmArgumentParserTypes.h"
17
#include "cmExecutionStatus.h"
18
#include "cmFileAPI.h"
19
#include "cmMakefile.h"
20
#include "cmRange.h"
21
#include "cmStringAlgorithms.h"
22
#include "cmSubcommandTable.h"
23
#include "cmake.h"
24
25
namespace {
26
27
std::string processObjectKindVersions(cmFileAPI& fileApi,
28
                                      cmFileAPI::ObjectKind objectKind,
29
                                      cm::string_view keyword,
30
                                      std::vector<std::string> const& versions)
31
0
{
32
  // The "versions" vector is empty only when the keyword was not present.
33
  // It is an error to provide the keyword with no versions after it, and that
34
  // is enforced by the argument parser before we get here.
35
0
  if (versions.empty()) {
36
0
    return {};
37
0
  }
38
39
  // The first supported version listed is what we use
40
0
  for (std::string const& ver : versions) {
41
0
    char const* vStart = ver.c_str();
42
0
    int majorVersion = std::atoi(vStart);
43
0
    int minorVersion = 0;
44
0
    std::string::size_type pos = ver.find('.');
45
0
    if (pos != std::string::npos) {
46
0
      vStart += pos + 1;
47
0
      minorVersion = std::atoi(vStart);
48
0
    }
49
0
    if (majorVersion < 1 || minorVersion < 0) {
50
0
      return cmStrCat("Given a malformed version \"", ver, "\" for ", keyword,
51
0
                      '.');
52
0
    }
53
0
    if (fileApi.AddProjectQuery(objectKind,
54
0
                                static_cast<unsigned>(majorVersion),
55
0
                                static_cast<unsigned>(minorVersion))) {
56
0
      return {};
57
0
    }
58
0
  }
59
0
  return cmStrCat("None of the specified ", keyword,
60
0
                  " versions is supported by this version of CMake.");
61
0
}
62
63
bool handleQueryCommand(std::vector<std::string> const& args,
64
                        cmExecutionStatus& status)
65
0
{
66
0
  if (args.empty()) {
67
0
    status.SetError("QUERY called without required arguments.");
68
0
    return false;
69
0
  }
70
71
0
  struct Arguments : public ArgumentParser::ParseResult
72
0
  {
73
0
    ArgumentParser::NonEmpty<std::string> ApiVersion;
74
0
    ArgumentParser::NonEmpty<std::vector<std::string>> CodeModelVersions;
75
0
    ArgumentParser::NonEmpty<std::vector<std::string>> CacheVersions;
76
0
    ArgumentParser::NonEmpty<std::vector<std::string>> CMakeFilesVersions;
77
0
    ArgumentParser::NonEmpty<std::vector<std::string>> ToolchainsVersions;
78
0
  };
79
80
0
  static auto const parser =
81
0
    cmArgumentParser<Arguments>{}
82
0
      .Bind("API_VERSION"_s, &Arguments::ApiVersion)
83
0
      .Bind("CODEMODEL"_s, &Arguments::CodeModelVersions)
84
0
      .Bind("CACHE"_s, &Arguments::CacheVersions)
85
0
      .Bind("CMAKEFILES"_s, &Arguments::CMakeFilesVersions)
86
0
      .Bind("TOOLCHAINS"_s, &Arguments::ToolchainsVersions);
87
88
0
  std::vector<std::string> unparsedArguments;
89
0
  Arguments const arguments =
90
0
    parser.Parse(cmMakeRange(args).advance(1), &unparsedArguments);
91
92
0
  if (arguments.MaybeReportError(status.GetMakefile())) {
93
0
    return true;
94
0
  }
95
0
  if (!unparsedArguments.empty()) {
96
0
    status.SetError(cmStrCat("QUERY given unknown argument \"",
97
0
                             unparsedArguments.front(), "\"."));
98
0
    return false;
99
0
  }
100
101
0
  if (!std::all_of(arguments.ApiVersion.begin(), arguments.ApiVersion.end(),
102
0
                   cmsysString_isdigit)) {
103
0
    status.SetError("QUERY given non-integer API_VERSION.");
104
0
    return false;
105
0
  }
106
0
  int const apiVersion = std::atoi(arguments.ApiVersion.c_str());
107
0
  if (apiVersion != 1) {
108
0
    status.SetError(
109
0
      cmStrCat("QUERY given unsupported API_VERSION \"", arguments.ApiVersion,
110
0
               "\" (the only currently supported version is 1)."));
111
0
    return false;
112
0
  }
113
114
0
  cmMakefile& mf = status.GetMakefile();
115
0
  cmake* cmi = mf.GetCMakeInstance();
116
0
  cmFileAPI* fileApi = cmi->GetFileAPI();
117
118
  // We want to check all keywords and report all errors, not just the first.
119
  // Record each result rather than short-circuiting on the first error.
120
121
  // NOTE: Double braces are needed here for compilers that don't implement the
122
  // CWG 1270 revision to C++11.
123
0
  std::array<std::string, 4> errors{
124
0
    { processObjectKindVersions(*fileApi, cmFileAPI::ObjectKind::CodeModel,
125
0
                                "CODEMODEL"_s, arguments.CodeModelVersions),
126
0
      processObjectKindVersions(*fileApi, cmFileAPI::ObjectKind::Cache,
127
0
                                "CACHE"_s, arguments.CacheVersions),
128
0
      processObjectKindVersions(*fileApi, cmFileAPI::ObjectKind::CMakeFiles,
129
0
                                "CMAKEFILES"_s, arguments.CMakeFilesVersions),
130
0
      processObjectKindVersions(*fileApi, cmFileAPI::ObjectKind::Toolchains,
131
0
                                "TOOLCHAINS"_s, arguments.ToolchainsVersions) }
132
0
  };
133
134
0
  if (!std::all_of(errors.begin(), errors.end(),
135
0
                   [](std::string const& s) -> bool { return s.empty(); })) {
136
0
    std::string message("QUERY given invalid arguments:");
137
0
    for (std::string const& s : errors) {
138
0
      if (!s.empty()) {
139
0
        message = cmStrCat(message, "\n  ", s);
140
0
      }
141
0
    }
142
0
    status.SetError(message);
143
0
    return false;
144
0
  }
145
146
0
  return true;
147
0
}
148
149
}
150
151
bool cmFileAPICommand(std::vector<std::string> const& args,
152
                      cmExecutionStatus& status)
153
0
{
154
0
  if (args.empty()) {
155
0
    status.SetError("must be called with arguments.");
156
0
    return false;
157
0
  }
158
159
  // clang-format off
160
0
  static cmSubcommandTable const subcommand{
161
0
    { "QUERY"_s, handleQueryCommand }
162
0
  };
163
  // clang-format on
164
165
0
  return subcommand(args[0], args, status);
166
0
}