Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmSourceGroupCommand.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 "cmSourceGroupCommand.h"
4
5
#include <cstddef>
6
#include <map>
7
#include <memory>
8
#include <set>
9
#include <utility>
10
11
#include <cmext/algorithm>
12
13
#include "cmExecutionStatus.h"
14
#include "cmMakefile.h"
15
#include "cmSourceFile.h"
16
#include "cmSourceFileLocation.h"
17
#include "cmSourceGroup.h"
18
#include "cmStringAlgorithms.h"
19
#include "cmSystemTools.h"
20
21
namespace {
22
23
using ParsedArguments = std::map<std::string, std::vector<std::string>>;
24
using ExpectedOptions = std::vector<std::string>;
25
26
std::string const kTreeOptionName = "TREE";
27
std::string const kPrefixOptionName = "PREFIX";
28
std::string const kFilesOptionName = "FILES";
29
std::string const kRegexOptionName = "REGULAR_EXPRESSION";
30
std::string const kSourceGroupOptionName = "<sg_name>";
31
32
std::set<std::string> getSourceGroupFilesPaths(
33
  std::string const& root, std::vector<std::string> const& files)
34
0
{
35
0
  std::set<std::string> ret;
36
0
  std::string::size_type const rootLength = root.length();
37
38
0
  for (std::string const& file : files) {
39
0
    ret.insert(file.substr(rootLength + 1)); // +1 to also omnit last '/'
40
0
  }
41
42
0
  return ret;
43
0
}
44
45
bool rootIsPrefix(std::string const& root,
46
                  std::vector<std::string> const& files, std::string& error)
47
0
{
48
0
  for (std::string const& file : files) {
49
0
    if (!cmHasPrefix(file, root)) {
50
0
      error = cmStrCat("ROOT: ", root, " is not a prefix of file: ", file);
51
0
      return false;
52
0
    }
53
0
  }
54
55
0
  return true;
56
0
}
57
58
std::vector<std::string> prepareFilesPathsForTree(
59
  std::vector<std::string> const& filesPaths,
60
  std::string const& currentSourceDir)
61
0
{
62
0
  std::vector<std::string> prepared;
63
0
  prepared.reserve(filesPaths.size());
64
65
0
  for (auto const& filePath : filesPaths) {
66
0
    std::string fullPath =
67
0
      cmSystemTools::CollapseFullPath(filePath, currentSourceDir);
68
    // If provided file path is actually not a directory, silently ignore it.
69
0
    if (cmSystemTools::FileIsDirectory(fullPath)) {
70
0
      continue;
71
0
    }
72
73
    // Handle directory that doesn't exist yet.
74
0
    if (!fullPath.empty() &&
75
0
        (fullPath.back() == '/' || fullPath.back() == '\\')) {
76
0
      continue;
77
0
    }
78
79
0
    prepared.emplace_back(std::move(fullPath));
80
0
  }
81
82
0
  return prepared;
83
0
}
84
85
bool addFilesToItsSourceGroups(std::string const& root,
86
                               std::set<std::string> const& sgFilesPaths,
87
                               std::string const& prefix, cmMakefile& makefile,
88
                               std::string& errorMsg)
89
0
{
90
0
  cmSourceGroup* sg;
91
92
0
  for (std::string const& sgFilesPath : sgFilesPaths) {
93
0
    std::vector<std::string> tokenizedPath = cmTokenize(
94
0
      prefix.empty() ? sgFilesPath : cmStrCat(prefix, '/', sgFilesPath),
95
0
      R"(\/)", cmTokenizerMode::New);
96
97
0
    if (tokenizedPath.empty()) {
98
0
      continue;
99
0
    }
100
0
    tokenizedPath.pop_back();
101
102
0
    if (tokenizedPath.empty()) {
103
0
      tokenizedPath.emplace_back();
104
0
    }
105
106
0
    sg = makefile.GetOrCreateSourceGroup(tokenizedPath);
107
108
0
    if (!sg) {
109
0
      errorMsg = "Could not create source group for file: " + sgFilesPath;
110
0
      return false;
111
0
    }
112
0
    std::string const fullPath =
113
0
      cmSystemTools::CollapseFullPath(sgFilesPath, root);
114
0
    sg->AddGroupFile(fullPath);
115
0
  }
116
117
0
  return true;
118
0
}
119
120
ExpectedOptions getExpectedOptions()
121
0
{
122
0
  ExpectedOptions options;
123
124
0
  options.push_back(kTreeOptionName);
125
0
  options.push_back(kPrefixOptionName);
126
0
  options.push_back(kFilesOptionName);
127
0
  options.push_back(kRegexOptionName);
128
129
0
  return options;
130
0
}
131
132
bool isExpectedOption(std::string const& argument,
133
                      ExpectedOptions const& expectedOptions)
134
0
{
135
0
  return cm::contains(expectedOptions, argument);
136
0
}
137
138
void parseArguments(std::vector<std::string> const& args,
139
                    ParsedArguments& parsedArguments)
140
0
{
141
0
  ExpectedOptions const expectedOptions = getExpectedOptions();
142
0
  size_t i = 0;
143
144
  // at this point we know that args vector is not empty
145
146
  // if first argument is not one of expected options it's source group name
147
0
  if (!isExpectedOption(args[0], expectedOptions)) {
148
    // get source group name and go to next argument
149
0
    parsedArguments[kSourceGroupOptionName].push_back(args[0]);
150
0
    ++i;
151
0
  }
152
153
0
  for (; i < args.size();) {
154
    // get current option and increment index to go to next argument
155
0
    std::string const& currentOption = args[i++];
156
157
    // create current option entry in parsed arguments
158
0
    std::vector<std::string>& currentOptionArguments =
159
0
      parsedArguments[currentOption];
160
161
    // collect option arguments while we won't find another expected option
162
0
    while (i < args.size() && !isExpectedOption(args[i], expectedOptions)) {
163
0
      currentOptionArguments.push_back(args[i++]);
164
0
    }
165
0
  }
166
0
}
167
168
} // namespace
169
170
static bool checkArgumentsPreconditions(ParsedArguments const& parsedArguments,
171
                                        std::string& errorMsg);
172
173
static bool processTree(cmMakefile& mf, ParsedArguments& parsedArguments,
174
                        std::string& errorMsg);
175
176
static bool checkSingleParameterArgumentPreconditions(
177
  std::string const& argument, ParsedArguments const& parsedArguments,
178
  std::string& errorMsg);
179
180
bool cmSourceGroupCommand(std::vector<std::string> const& args,
181
                          cmExecutionStatus& status)
182
0
{
183
0
  if (args.empty()) {
184
0
    status.SetError("called with incorrect number of arguments");
185
0
    return false;
186
0
  }
187
188
0
  cmMakefile& mf = status.GetMakefile();
189
190
  // If only two arguments are given, the pre-1.8 version of the
191
  // command is being invoked.
192
0
  bool isShortTreeSyntax =
193
0
    ((args.size() == 2) && (args[0] == kTreeOptionName) &&
194
0
     cmSystemTools::FileIsDirectory(args[1]));
195
0
  if (args.size() == 2 && args[1] != kFilesOptionName && !isShortTreeSyntax) {
196
0
    cmSourceGroup* sg = mf.GetOrCreateSourceGroup(args[0]);
197
198
0
    if (!sg) {
199
0
      status.SetError("Could not create or find source group");
200
0
      return false;
201
0
    }
202
203
0
    sg->SetGroupRegex(args[1].c_str());
204
0
    return true;
205
0
  }
206
207
0
  ParsedArguments parsedArguments;
208
0
  std::string errorMsg;
209
210
0
  parseArguments(args, parsedArguments);
211
212
0
  if (!checkArgumentsPreconditions(parsedArguments, errorMsg)) {
213
0
    return false;
214
0
  }
215
216
0
  if (parsedArguments.find(kTreeOptionName) != parsedArguments.end()) {
217
0
    if (!processTree(mf, parsedArguments, errorMsg)) {
218
0
      status.SetError(errorMsg);
219
0
      return false;
220
0
    }
221
0
  } else {
222
0
    if (parsedArguments.find(kSourceGroupOptionName) ==
223
0
        parsedArguments.end()) {
224
0
      status.SetError("Missing source group name.");
225
0
      return false;
226
0
    }
227
228
0
    cmSourceGroup* sg = mf.GetOrCreateSourceGroup(args[0]);
229
230
0
    if (!sg) {
231
0
      status.SetError("Could not create or find source group");
232
0
      return false;
233
0
    }
234
235
    // handle regex
236
0
    if (parsedArguments.find(kRegexOptionName) != parsedArguments.end()) {
237
0
      std::string const& sgRegex = parsedArguments[kRegexOptionName].front();
238
0
      sg->SetGroupRegex(sgRegex.c_str());
239
0
    }
240
241
    // handle files
242
0
    std::vector<std::string> const& filesArguments =
243
0
      parsedArguments[kFilesOptionName];
244
0
    for (auto const& filesArg : filesArguments) {
245
0
      std::string src = filesArg;
246
0
      src =
247
0
        cmSystemTools::CollapseFullPath(src, mf.GetCurrentSourceDirectory());
248
0
      sg->AddGroupFile(src);
249
0
    }
250
0
  }
251
252
0
  return true;
253
0
}
254
255
static bool checkArgumentsPreconditions(ParsedArguments const& parsedArguments,
256
                                        std::string& errorMsg)
257
0
{
258
0
  return checkSingleParameterArgumentPreconditions(
259
0
           kPrefixOptionName, parsedArguments, errorMsg) &&
260
0
    checkSingleParameterArgumentPreconditions(kTreeOptionName, parsedArguments,
261
0
                                              errorMsg) &&
262
0
    checkSingleParameterArgumentPreconditions(kRegexOptionName,
263
0
                                              parsedArguments, errorMsg);
264
0
}
265
266
static bool processTree(cmMakefile& mf, ParsedArguments& parsedArguments,
267
                        std::string& errorMsg)
268
0
{
269
0
  std::string const root =
270
0
    cmSystemTools::CollapseFullPath(parsedArguments[kTreeOptionName].front());
271
0
  std::string prefix = parsedArguments[kPrefixOptionName].empty()
272
0
    ? ""
273
0
    : parsedArguments[kPrefixOptionName].front();
274
275
0
  std::vector<std::string> files;
276
0
  auto filesArgIt = parsedArguments.find(kFilesOptionName);
277
0
  if (filesArgIt != parsedArguments.end()) {
278
0
    files = filesArgIt->second;
279
0
  } else {
280
0
    std::vector<std::unique_ptr<cmSourceFile>> const& srcFiles =
281
0
      mf.GetSourceFiles();
282
0
    for (auto const& srcFile : srcFiles) {
283
0
      if (!srcFile->GetIsGenerated()) {
284
0
        files.push_back(srcFile->GetLocation().GetFullPath());
285
0
      }
286
0
    }
287
0
  }
288
289
0
  std::vector<std::string> const filesVector =
290
0
    prepareFilesPathsForTree(files, mf.GetCurrentSourceDirectory());
291
292
0
  if (!rootIsPrefix(root, filesVector, errorMsg)) {
293
0
    return false;
294
0
  }
295
296
0
  std::set<std::string> sourceGroupPaths =
297
0
    getSourceGroupFilesPaths(root, filesVector);
298
299
0
  return addFilesToItsSourceGroups(root, sourceGroupPaths, prefix, mf,
300
0
                                   errorMsg);
301
0
}
302
303
static bool checkSingleParameterArgumentPreconditions(
304
  std::string const& argument, ParsedArguments const& parsedArguments,
305
  std::string& errorMsg)
306
0
{
307
0
  auto foundArgument = parsedArguments.find(argument);
308
0
  if (foundArgument != parsedArguments.end()) {
309
0
    std::vector<std::string> const& optionArguments = foundArgument->second;
310
311
0
    if (optionArguments.empty()) {
312
0
      errorMsg = argument + " argument given without an argument.";
313
0
      return false;
314
0
    }
315
0
    if (optionArguments.size() > 1) {
316
0
      errorMsg = "too many arguments passed to " + argument + ".";
317
0
      return false;
318
0
    }
319
0
  }
320
321
0
  return true;
322
0
}