Coverage Report

Created: 2026-07-14 07:09

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 <functional>
6
#include <map>
7
#include <memory>
8
#include <set>
9
#include <unordered_map>
10
#include <utility>
11
12
#include <cm/optional>
13
#include <cm/string_view>
14
#include <cmext/algorithm>
15
#include <cmext/string_view>
16
17
#include "cmArgumentParser.h"
18
#include "cmArgumentParserTypes.h"
19
#include "cmCMakePath.h"
20
#include "cmDiagnostics.h"
21
#include "cmExecutionStatus.h"
22
#include "cmMakefile.h"
23
#include "cmSourceFile.h"
24
#include "cmSourceFileLocation.h"
25
#include "cmSourceGroup.h"
26
#include "cmStringAlgorithms.h"
27
#include "cmSystemTools.h"
28
#include "cmTarget.h"
29
30
namespace {
31
template <typename Args>
32
bool ProcessTree(Args const& args, cmExecutionStatus& status)
33
0
{
34
0
  cmMakefile& mf = status.GetMakefile();
35
36
0
  auto const& currentSourceDir = mf.GetCurrentSourceDirectory();
37
0
  std::vector<std::string> files;
38
0
  if (args.Files) {
39
0
    files.reserve(args.Files->size());
40
41
0
    for (auto const& file : *args.Files) {
42
0
      if (file.empty()) {
43
0
        continue;
44
0
      }
45
0
      std::string fullPath =
46
0
        cmSystemTools::CollapseFullPath(file, currentSourceDir);
47
0
      files.emplace_back(std::move(fullPath));
48
0
    }
49
0
  } else {
50
0
    std::vector<std::unique_ptr<cmSourceFile>> const& sources =
51
0
      mf.GetSourceFiles();
52
0
    for (auto const& src : sources) {
53
0
      if (!src->GetIsGenerated()) {
54
0
        files.push_back(cmSystemTools::CollapseFullPath(
55
0
          src->GetLocation().GetFullPath(), currentSourceDir));
56
0
      }
57
0
    }
58
0
  }
59
60
  // final checks
61
0
  cmCMakePath const root{ cmSystemTools::CollapseFullPath(*args.Tree) };
62
0
  cmCMakePath const prefix{
63
0
    cmCMakePath{ args.Prefix ? *args.Prefix : "" }.Normal()
64
0
  };
65
66
0
  auto it = files.begin();
67
0
  while (it != files.end()) {
68
0
    if (it->empty() || cmSystemTools::FileIsDirectory(*it) ||
69
0
        (it->back() == '/' || it->back() == '\\')) {
70
      // Ignore any empty files or directories
71
0
      it = files.erase(it);
72
0
      continue;
73
0
    }
74
75
0
    cmCMakePath file{ *it };
76
0
    if (!root.IsPrefix(file)) {
77
0
      status.SetError(cmStrCat('"', root.GenericString(),
78
0
                               "\" is not a prefix of file \"",
79
0
                               file.GenericString(), '"'));
80
0
      return false;
81
0
    }
82
83
    // source groups generation
84
0
    cmCMakePath sourceGroup = prefix / file.Relative(root);
85
0
    if (sourceGroup.HasParentPath()) {
86
0
      sourceGroup = sourceGroup.GetParentPath();
87
0
    }
88
0
    std::vector<std::string> tokenizedSG =
89
0
      cmTokenize(sourceGroup.GenericString(), '/', cmTokenizerMode::New);
90
91
0
    auto* sg = mf.GetOrCreateSourceGroup(tokenizedSG);
92
0
    if (!sg) {
93
0
      status.SetError(cmStrCat("could not create source group for file \"",
94
0
                               file.GenericString(), '"'));
95
0
      return false;
96
0
    }
97
0
    sg->AddGroupFile(file.GenericString());
98
99
0
    ++it;
100
0
  }
101
0
  return true;
102
0
}
103
} // namespace
104
105
bool cmSourceGroupCommand(std::vector<std::string> const& args,
106
                          cmExecutionStatus& status)
107
0
{
108
0
  if (args.empty()) {
109
0
    status.SetError("called with incorrect number of arguments");
110
0
    return false;
111
0
  }
112
113
0
  static cm::string_view const Keywords[]{ "TREE"_s, "PREFIX"_s, "FILES"_s,
114
0
                                           "REGULAR_EXPRESSION"_s };
115
0
  cmMakefile& mf = status.GetMakefile();
116
117
0
  if (args.size() == 2 && !cm::contains(Keywords, args[0]) &&
118
0
      !cm::contains(Keywords, args[1])) {
119
    // The pre-1.8 version of the command is being invoked.
120
0
    cmSourceGroup* sg = mf.GetOrCreateSourceGroup(args[0]);
121
0
    if (!sg) {
122
0
      status.SetError("Could not create or find source group.");
123
0
      return false;
124
0
    }
125
0
    sg->SetGroupRegex(args[1]);
126
0
    return true;
127
0
  }
128
129
0
  struct Arguments : public ArgumentParser::ParseResult
130
0
  {
131
0
    cm::optional<std::string> GroupName;
132
0
    cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Files;
133
0
    cm::optional<std::string> Regex;
134
0
    cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>>
135
0
      FileSets;
136
0
    cm::optional<ArgumentParser::NonEmpty<std::string>> Tree;
137
0
    cm::optional<ArgumentParser::MaybeEmpty<std::string>> Prefix;
138
139
0
    std::map<std::string, std::set<std::string>> FileSetsPerTarget;
140
0
  };
141
142
0
  auto unsupportedKeyword =
143
0
    [&mf](Arguments&, cm::string_view key,
144
0
          cm::string_view /*value */) -> ArgumentParser::Continue {
145
0
    mf.IssueDiagnostic(
146
0
      cmDiagnostics::CMD_AUTHOR,
147
0
      cmStrCat("keyword \"", key, "\" will be ignored in this context."));
148
0
    return ArgumentParser::Continue::Yes;
149
0
  };
150
  // to distinguish REGULAR_EXPRESSION without values from with an empty string
151
0
  auto handleRegex = [](Arguments& result,
152
0
                        cm::string_view value) -> ArgumentParser::Continue {
153
0
    if (value.data()) {
154
0
      result.Regex = std::string{ value };
155
0
    }
156
0
    return ArgumentParser::Continue::No;
157
0
  };
158
0
  auto handleTarget = [](Arguments& result,
159
0
                         cm::string_view value) -> ArgumentParser::Continue {
160
0
    if (!result.FileSets) {
161
0
      result.AddKeywordError(
162
0
        "TARGET", cmStrCat("FILE_SETS is required for TARGET ", value, '.'));
163
0
      return ArgumentParser::Continue::No;
164
0
    }
165
0
    if (!result.FileSets->empty()) {
166
0
      result.FileSetsPerTarget[std::string{ value }].insert(
167
0
        result.FileSets->begin(), result.FileSets->end());
168
0
    }
169
0
    result.FileSets.reset();
170
171
0
    return ArgumentParser::Continue::Yes;
172
0
  };
173
174
0
  std::vector<std::string> unexpectedArgs;
175
0
  auto parser =
176
0
    cmArgumentParser<Arguments>{}.Bind("FILES"_s, &Arguments::Files);
177
178
0
  if (cm::contains(args, "TREE")) {
179
    // this is the TREE syntax
180
0
    parser.Bind("TREE"_s, &Arguments::Tree)
181
0
      .Bind("PREFIX"_s, &Arguments::Prefix)
182
0
      .Bind("REGULAR_EXPRESSION"_s, unsupportedKeyword)
183
0
      .Bind("FILE_SETS"_s, unsupportedKeyword)
184
0
      .Bind("TARGET"_s, unsupportedKeyword);
185
0
  } else {
186
    // assume that first argument is the group name
187
0
    parser.Bind(0, &Arguments::GroupName)
188
0
      .Bind("REGULAR_EXPRESSION"_s, handleRegex, 0)
189
0
      .Bind("FILE_SETS"_s, &Arguments::FileSets)
190
0
      .Bind("TARGET"_s, handleTarget)
191
0
      .Bind("TREE"_s, unsupportedKeyword)
192
0
      .Bind("PREFIX"_s, unsupportedKeyword);
193
0
  }
194
195
0
  auto parsedArgs = parser.Parse(args, &unexpectedArgs);
196
197
  // do various checks for arguments consistency
198
0
  if (!parsedArgs.Check("", &unexpectedArgs, status)) {
199
0
    cmSystemTools::SetFatalErrorOccurred();
200
0
    return false;
201
0
  }
202
0
  if (!parsedArgs.Tree && !parsedArgs.GroupName) {
203
0
    status.SetError("missing source group name.");
204
0
    cmSystemTools::SetFatalErrorOccurred();
205
0
    return false;
206
0
  }
207
0
  if (!parsedArgs.Tree && parsedArgs.FileSets) {
208
0
    status.SetError(cmStrCat("TARGET is required for FILE_SETS ",
209
0
                             cmJoin(*parsedArgs.FileSets, ", "), '.'));
210
0
    return false;
211
0
  }
212
213
0
  if (parsedArgs.Tree) {
214
0
    if (!ProcessTree(parsedArgs, status)) {
215
0
      cmSystemTools::SetFatalErrorOccurred();
216
0
      return false;
217
0
    }
218
0
  } else {
219
0
    if (!parsedArgs.Files && !parsedArgs.Regex &&
220
0
        parsedArgs.FileSetsPerTarget.empty()) {
221
      // group is not created
222
0
      return true;
223
0
    }
224
225
0
    auto* sg = mf.GetOrCreateSourceGroup(*parsedArgs.GroupName);
226
0
    if (!sg) {
227
0
      status.SetError("could not create or find source group");
228
0
      cmSystemTools::SetFatalErrorOccurred();
229
0
      return false;
230
0
    }
231
232
0
    if (parsedArgs.Regex) {
233
0
      if (!sg->SetGroupRegex(*parsedArgs.Regex)) {
234
0
        status.SetError(cmStrCat("regular expression \"", *parsedArgs.Regex,
235
0
                                 "\" is invalid"));
236
0
        return false;
237
0
      }
238
0
    }
239
0
    if (parsedArgs.Files) {
240
0
      auto const& currentSourceDir = mf.GetCurrentSourceDirectory();
241
0
      for (auto const& file : *parsedArgs.Files) {
242
0
        sg->AddGroupFile(
243
0
          cmSystemTools::CollapseFullPath(file, currentSourceDir));
244
0
      }
245
0
    }
246
0
    for (auto& item : parsedArgs.FileSetsPerTarget) {
247
      // check validity of arguments
248
0
      auto it = mf.GetTargets().find(item.first);
249
0
      if (it == mf.GetTargets().end()) {
250
0
        mf.IssueDiagnostic(
251
0
          cmDiagnostics::CMD_AUTHOR,
252
0
          cmStrCat(
253
0
            "TARGET \"", item.first,
254
0
            "\" is not defined in this directory. It will be ignored."));
255
0
        continue;
256
0
      }
257
258
0
      cmTarget const& target = it->second;
259
0
      for (auto it2 = item.second.begin(); it2 != item.second.end();) {
260
0
        if (!target.GetFileSet(*it2)) {
261
0
          mf.IssueDiagnostic(cmDiagnostics::CMD_AUTHOR,
262
0
                             cmStrCat("FILE_SET \"", *it2,
263
0
                                      "\" is not known for TARGET \"",
264
0
                                      item.first, "\". It will ignored."));
265
0
          it2 = item.second.erase(it2);
266
0
        } else {
267
0
          ++it2;
268
0
        }
269
0
      }
270
0
      if (!item.second.empty()) {
271
0
        sg->AddGroupFileSets(item.first, item.second);
272
0
      }
273
0
    }
274
0
  }
275
276
0
  return true;
277
0
}