/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 | } |