/src/CMake/Source/cmCMakeLanguageCommand.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 "cmCMakeLanguageCommand.h" |
4 | | |
5 | | #include <algorithm> |
6 | | #include <array> |
7 | | #include <cstddef> |
8 | | #include <initializer_list> |
9 | | #include <map> |
10 | | #include <memory> |
11 | | #include <set> |
12 | | #include <string> |
13 | | #include <unordered_map> |
14 | | #include <utility> |
15 | | #include <vector> |
16 | | |
17 | | #include <cm/optional> |
18 | | #include <cm/string_view> |
19 | | #include <cmext/string_view> |
20 | | |
21 | | #include "cmsys/RegularExpression.hxx" |
22 | | |
23 | | #include "cmArgumentParser.h" |
24 | | #include "cmArgumentParserTypes.h" |
25 | | #include "cmDependencyProvider.h" |
26 | | #include "cmExecutionStatus.h" |
27 | | #include "cmExperimental.h" |
28 | | #include "cmGeneratorTarget.h" |
29 | | #include "cmGetPropertyCommand.h" |
30 | | #include "cmGlobalGenerator.h" |
31 | | #include "cmLinkItem.h" |
32 | | #include "cmListFileCache.h" |
33 | | #include "cmLocalGenerator.h" |
34 | | #include "cmMakefile.h" |
35 | | #include "cmMessageType.h" // IWYU pragma: keep |
36 | | #include "cmPropertyMap.h" |
37 | | #include "cmRange.h" |
38 | | #include "cmState.h" |
39 | | #include "cmStateSnapshot.h" |
40 | | #include "cmStateTypes.h" |
41 | | #include "cmStringAlgorithms.h" |
42 | | #include "cmSystemTools.h" |
43 | | #include "cmTarget.h" |
44 | | #include "cmTargetPropertyComputer.h" |
45 | | #include "cmTargetPropertyHelper.h" |
46 | | #include "cmValue.h" |
47 | | #include "cmake.h" |
48 | | |
49 | | namespace cm { |
50 | | enum class TargetType; |
51 | | } |
52 | | |
53 | | namespace { |
54 | | |
55 | | bool FatalError(cmExecutionStatus& status, std::string const& error) |
56 | 0 | { |
57 | 0 | status.SetError(error); |
58 | 0 | cmSystemTools::SetFatalErrorOccurred(); |
59 | 0 | return false; |
60 | 0 | } |
61 | | |
62 | | std::array<cm::static_string_view, 14> InvalidCommands{ |
63 | | { // clang-format off |
64 | | "function"_s, "endfunction"_s, |
65 | | "macro"_s, "endmacro"_s, |
66 | | "if"_s, "elseif"_s, "else"_s, "endif"_s, |
67 | | "while"_s, "endwhile"_s, |
68 | | "foreach"_s, "endforeach"_s, |
69 | | "block"_s, "endblock"_s |
70 | | } // clang-format on |
71 | | }; |
72 | | |
73 | | std::array<cm::static_string_view, 1> InvalidDeferCommands{ |
74 | | { |
75 | | // clang-format off |
76 | | "return"_s, |
77 | | } // clang-format on |
78 | | }; |
79 | | |
80 | | struct Defer |
81 | | { |
82 | | std::string Id; |
83 | | std::string IdVar; |
84 | | cmMakefile* Directory = nullptr; |
85 | | }; |
86 | | |
87 | | bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args, |
88 | | std::string const& callCommand, |
89 | | size_t startArg, cm::optional<Defer> defer, |
90 | | cmExecutionStatus& status) |
91 | 0 | { |
92 | | // ensure specified command is valid |
93 | | // start/end flow control commands are not allowed |
94 | 0 | auto cmd = cmSystemTools::LowerCase(callCommand); |
95 | 0 | if (std::find(InvalidCommands.cbegin(), InvalidCommands.cend(), cmd) != |
96 | 0 | InvalidCommands.cend()) { |
97 | 0 | return FatalError(status, |
98 | 0 | cmStrCat("invalid command specified: "_s, callCommand)); |
99 | 0 | } |
100 | 0 | if (defer && |
101 | 0 | std::find(InvalidDeferCommands.cbegin(), InvalidDeferCommands.cend(), |
102 | 0 | cmd) != InvalidDeferCommands.cend()) { |
103 | 0 | return FatalError(status, |
104 | 0 | cmStrCat("invalid command specified: "_s, callCommand)); |
105 | 0 | } |
106 | | |
107 | 0 | cmMakefile& makefile = status.GetMakefile(); |
108 | 0 | cmListFileContext context = makefile.GetBacktrace().Top(); |
109 | |
|
110 | 0 | std::vector<cmListFileArgument> funcArgs; |
111 | 0 | funcArgs.reserve(args.size() - startArg); |
112 | | |
113 | | // The rest of the arguments are passed to the function call above |
114 | 0 | for (size_t i = startArg; i < args.size(); ++i) { |
115 | 0 | funcArgs.emplace_back(args[i].Value, args[i].Delim, context.Line); |
116 | 0 | } |
117 | 0 | cmListFileFunction func{ callCommand, context.Line, context.Line, |
118 | 0 | std::move(funcArgs) }; |
119 | |
|
120 | 0 | if (defer) { |
121 | 0 | if (defer->Id.empty()) { |
122 | 0 | defer->Id = makefile.NewDeferId(); |
123 | 0 | } |
124 | 0 | if (!defer->IdVar.empty()) { |
125 | 0 | makefile.AddDefinition(defer->IdVar, defer->Id); |
126 | 0 | } |
127 | 0 | cmMakefile* deferMakefile = |
128 | 0 | defer->Directory ? defer->Directory : &makefile; |
129 | 0 | if (!deferMakefile->DeferCall(defer->Id, context.FilePath, func)) { |
130 | 0 | return FatalError( |
131 | 0 | status, |
132 | 0 | cmStrCat("DEFER CALL may not be scheduled in directory:\n "_s, |
133 | 0 | deferMakefile->GetCurrentBinaryDirectory(), |
134 | 0 | "\nat this time."_s)); |
135 | 0 | } |
136 | 0 | return true; |
137 | 0 | } |
138 | 0 | return makefile.ExecuteCommand(func, status); |
139 | 0 | } |
140 | | |
141 | | bool cmCMakeLanguageCommandDEFER(Defer const& defer, |
142 | | std::vector<std::string> const& args, |
143 | | size_t arg, cmExecutionStatus& status) |
144 | 0 | { |
145 | 0 | cmMakefile* deferMakefile = |
146 | 0 | defer.Directory ? defer.Directory : &status.GetMakefile(); |
147 | 0 | if (args[arg] == "CANCEL_CALL"_s) { |
148 | 0 | ++arg; // Consume CANCEL_CALL. |
149 | 0 | auto ids = cmMakeRange(args).advance(arg); |
150 | 0 | for (std::string const& id : ids) { |
151 | 0 | if (id[0] >= 'A' && id[0] <= 'Z') { |
152 | 0 | return FatalError( |
153 | 0 | status, cmStrCat("DEFER CANCEL_CALL unknown argument:\n "_s, id)); |
154 | 0 | } |
155 | 0 | if (!deferMakefile->DeferCancelCall(id)) { |
156 | 0 | return FatalError( |
157 | 0 | status, |
158 | 0 | cmStrCat("DEFER CANCEL_CALL may not update directory:\n "_s, |
159 | 0 | deferMakefile->GetCurrentBinaryDirectory(), |
160 | 0 | "\nat this time."_s)); |
161 | 0 | } |
162 | 0 | } |
163 | 0 | return true; |
164 | 0 | } |
165 | 0 | if (args[arg] == "GET_CALL_IDS"_s) { |
166 | 0 | ++arg; // Consume GET_CALL_IDS. |
167 | 0 | if (arg == args.size()) { |
168 | 0 | return FatalError(status, "DEFER GET_CALL_IDS missing output variable"); |
169 | 0 | } |
170 | 0 | std::string const& var = args[arg++]; |
171 | 0 | if (arg != args.size()) { |
172 | 0 | return FatalError(status, "DEFER GET_CALL_IDS given too many arguments"); |
173 | 0 | } |
174 | 0 | cm::optional<std::string> ids = deferMakefile->DeferGetCallIds(); |
175 | 0 | if (!ids) { |
176 | 0 | return FatalError( |
177 | 0 | status, |
178 | 0 | cmStrCat("DEFER GET_CALL_IDS may not access directory:\n "_s, |
179 | 0 | deferMakefile->GetCurrentBinaryDirectory(), |
180 | 0 | "\nat this time."_s)); |
181 | 0 | } |
182 | 0 | status.GetMakefile().AddDefinition(var, *ids); |
183 | 0 | return true; |
184 | 0 | } |
185 | 0 | if (args[arg] == "GET_CALL"_s) { |
186 | 0 | ++arg; // Consume GET_CALL. |
187 | 0 | if (arg == args.size()) { |
188 | 0 | return FatalError(status, "DEFER GET_CALL missing id"); |
189 | 0 | } |
190 | 0 | std::string const& id = args[arg++]; |
191 | 0 | if (arg == args.size()) { |
192 | 0 | return FatalError(status, "DEFER GET_CALL missing output variable"); |
193 | 0 | } |
194 | 0 | std::string const& var = args[arg++]; |
195 | 0 | if (arg != args.size()) { |
196 | 0 | return FatalError(status, "DEFER GET_CALL given too many arguments"); |
197 | 0 | } |
198 | 0 | if (id.empty()) { |
199 | 0 | return FatalError(status, "DEFER GET_CALL id may not be empty"); |
200 | 0 | } |
201 | 0 | if (id[0] >= 'A' && id[0] <= 'Z') { |
202 | 0 | return FatalError(status, |
203 | 0 | cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id)); |
204 | 0 | } |
205 | 0 | cm::optional<std::string> call = deferMakefile->DeferGetCall(id); |
206 | 0 | if (!call) { |
207 | 0 | return FatalError( |
208 | 0 | status, |
209 | 0 | cmStrCat("DEFER GET_CALL may not access directory:\n "_s, |
210 | 0 | deferMakefile->GetCurrentBinaryDirectory(), |
211 | 0 | "\nat this time."_s)); |
212 | 0 | } |
213 | 0 | status.GetMakefile().AddDefinition(var, *call); |
214 | 0 | return true; |
215 | 0 | } |
216 | 0 | return FatalError(status, |
217 | 0 | cmStrCat("DEFER operation unknown: "_s, args[arg])); |
218 | 0 | } |
219 | | |
220 | | bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args, |
221 | | cmExecutionStatus& status) |
222 | 0 | { |
223 | 0 | cmMakefile& makefile = status.GetMakefile(); |
224 | 0 | cmListFileContext context = makefile.GetBacktrace().Top(); |
225 | 0 | std::vector<std::string> expandedArgs; |
226 | 0 | makefile.ExpandArguments(args, expandedArgs); |
227 | |
|
228 | 0 | if (expandedArgs.size() < 2) { |
229 | 0 | return FatalError(status, "called with incorrect number of arguments"); |
230 | 0 | } |
231 | | |
232 | 0 | if (expandedArgs[1] != "CODE") { |
233 | 0 | auto code_iter = |
234 | 0 | std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE"); |
235 | 0 | if (code_iter == expandedArgs.end()) { |
236 | 0 | return FatalError(status, "called without CODE argument"); |
237 | 0 | } |
238 | 0 | return FatalError( |
239 | 0 | status, |
240 | 0 | "called with unsupported arguments between EVAL and CODE arguments"); |
241 | 0 | } |
242 | | |
243 | 0 | std::string const code = |
244 | 0 | cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " "); |
245 | 0 | return makefile.ReadListFileAsString( |
246 | 0 | code, cmStrCat(context.FilePath, ':', context.Line, ":EVAL")); |
247 | 0 | } |
248 | | |
249 | | bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER( |
250 | | std::vector<std::string> const& args, cmExecutionStatus& status) |
251 | 0 | { |
252 | 0 | cmState* state = status.GetMakefile().GetState(); |
253 | 0 | if (!state->InTopLevelIncludes()) { |
254 | 0 | return FatalError( |
255 | 0 | status, |
256 | 0 | "Dependency providers can only be set as part of the first call to " |
257 | 0 | "project(). More specifically, cmake_language(SET_DEPENDENCY_PROVIDER) " |
258 | 0 | "can only be called while the first project() command processes files " |
259 | 0 | "listed in CMAKE_PROJECT_TOP_LEVEL_INCLUDES."); |
260 | 0 | } |
261 | | |
262 | 0 | struct SetProviderArgs |
263 | 0 | { |
264 | 0 | std::string Command; |
265 | 0 | ArgumentParser::NonEmpty<std::vector<std::string>> Methods; |
266 | 0 | }; |
267 | |
|
268 | 0 | auto const ArgsParser = |
269 | 0 | cmArgumentParser<SetProviderArgs>() |
270 | 0 | .Bind("SET_DEPENDENCY_PROVIDER"_s, &SetProviderArgs::Command) |
271 | 0 | .Bind("SUPPORTED_METHODS"_s, &SetProviderArgs::Methods); |
272 | |
|
273 | 0 | std::vector<std::string> unparsed; |
274 | 0 | auto parsedArgs = ArgsParser.Parse(args, &unparsed); |
275 | |
|
276 | 0 | if (!unparsed.empty()) { |
277 | 0 | return FatalError( |
278 | 0 | status, cmStrCat("Unrecognized keyword: \"", unparsed.front(), '"')); |
279 | 0 | } |
280 | | |
281 | | // We store the command that FetchContent_MakeAvailable() can call in a |
282 | | // global (but considered internal) property. If the provider doesn't |
283 | | // support this method, we set this property to an empty string instead. |
284 | | // This simplifies the logic in FetchContent_MakeAvailable() and doesn't |
285 | | // require us to define a new internal command or sub-command. |
286 | 0 | std::string fcmasProperty = "__FETCHCONTENT_MAKEAVAILABLE_SERIAL_PROVIDER"; |
287 | |
|
288 | 0 | if (parsedArgs.Command.empty()) { |
289 | 0 | if (!parsedArgs.Methods.empty()) { |
290 | 0 | return FatalError(status, |
291 | 0 | "Must specify a non-empty command name when provider " |
292 | 0 | "methods are given"); |
293 | 0 | } |
294 | 0 | state->ClearDependencyProvider(); |
295 | 0 | state->SetGlobalProperty(fcmasProperty, ""); |
296 | 0 | return true; |
297 | 0 | } |
298 | | |
299 | 0 | cmState::Command command = state->GetCommand(parsedArgs.Command); |
300 | 0 | if (!command) { |
301 | 0 | return FatalError(status, |
302 | 0 | cmStrCat("Command \"", parsedArgs.Command, |
303 | 0 | "\" is not a defined command")); |
304 | 0 | } |
305 | | |
306 | 0 | if (parsedArgs.Methods.empty()) { |
307 | 0 | return FatalError(status, "Must specify at least one provider method"); |
308 | 0 | } |
309 | | |
310 | 0 | bool supportsFetchContentMakeAvailableSerial = false; |
311 | 0 | std::vector<cmDependencyProvider::Method> methods; |
312 | 0 | for (auto const& method : parsedArgs.Methods) { |
313 | 0 | if (method == "FIND_PACKAGE") { |
314 | 0 | methods.emplace_back(cmDependencyProvider::Method::FindPackage); |
315 | 0 | } else if (method == "FETCHCONTENT_MAKEAVAILABLE_SERIAL") { |
316 | 0 | supportsFetchContentMakeAvailableSerial = true; |
317 | 0 | methods.emplace_back( |
318 | 0 | cmDependencyProvider::Method::FetchContentMakeAvailableSerial); |
319 | 0 | } else { |
320 | 0 | return FatalError( |
321 | 0 | status, |
322 | 0 | cmStrCat("Unknown dependency provider method \"", method, '"')); |
323 | 0 | } |
324 | 0 | } |
325 | | |
326 | 0 | state->SetDependencyProvider({ parsedArgs.Command, methods }); |
327 | 0 | state->SetGlobalProperty( |
328 | 0 | fcmasProperty, |
329 | 0 | supportsFetchContentMakeAvailableSerial ? parsedArgs.Command : ""); |
330 | |
|
331 | 0 | return true; |
332 | 0 | } |
333 | | |
334 | | bool cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL( |
335 | | std::vector<cmListFileArgument> const& args, cmExecutionStatus& status) |
336 | 0 | { |
337 | 0 | cmMakefile& makefile = status.GetMakefile(); |
338 | 0 | std::vector<std::string> expandedArgs; |
339 | 0 | makefile.ExpandArguments(args, expandedArgs); |
340 | |
|
341 | 0 | if (args.size() < 2 || expandedArgs.size() > 2) { |
342 | 0 | return FatalError( |
343 | 0 | status, |
344 | 0 | "sub-command GET_MESSAGE_LOG_LEVEL expects exactly one argument"); |
345 | 0 | } |
346 | | |
347 | 0 | Message::LogLevel logLevel = makefile.GetCurrentLogLevel(); |
348 | 0 | std::string outputValue = cmake::LogLevelToString(logLevel); |
349 | |
|
350 | 0 | std::string const& outputVariable = expandedArgs[1]; |
351 | 0 | makefile.AddDefinition(outputVariable, outputValue); |
352 | 0 | return true; |
353 | 0 | } |
354 | | |
355 | | bool cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED( |
356 | | std::vector<cmListFileArgument> const& args, cmExecutionStatus& status) |
357 | 0 | { |
358 | 0 | cmMakefile& makefile = status.GetMakefile(); |
359 | 0 | std::vector<std::string> expandedArgs; |
360 | 0 | makefile.ExpandArguments(args, expandedArgs); |
361 | |
|
362 | 0 | if (expandedArgs.size() != 3) { |
363 | 0 | return FatalError(status, |
364 | 0 | "sub-command GET_EXPERIMENTAL_FEATURE_ENABLED expects " |
365 | 0 | "exactly two arguments"); |
366 | 0 | } |
367 | | |
368 | 0 | auto const& featureName = expandedArgs[1]; |
369 | 0 | auto const& variableName = expandedArgs[2]; |
370 | |
|
371 | 0 | if (auto feature = cmExperimental::FeatureByName(featureName)) { |
372 | 0 | if (cmExperimental::HasSupportEnabled(makefile, *feature)) { |
373 | 0 | makefile.AddDefinition(variableName, "TRUE"); |
374 | 0 | } else { |
375 | 0 | makefile.AddDefinition(variableName, "FALSE"); |
376 | 0 | } |
377 | 0 | } else { |
378 | 0 | return FatalError(status, |
379 | 0 | cmStrCat("Experimental feature name \"", featureName, |
380 | 0 | "\" does not exist.")); |
381 | 0 | } |
382 | | |
383 | 0 | return true; |
384 | 0 | } |
385 | | |
386 | | struct PrintTargetsArgs : public ArgumentParser::ParseResult |
387 | | { |
388 | | cm::optional<std::string> Regex; |
389 | | bool ImportedOnly = false; |
390 | | bool NoImported = false; |
391 | | bool IgnoreCase = false; |
392 | | }; |
393 | | |
394 | | // Lists every target that currently exists, optionally filtered by a |
395 | | // name REGEX and by imported state. "Currently exists" means anything |
396 | | // CMake has defined up to this call: targets in the current directory, |
397 | | // its ancestors, and any already-processed subdirectories. Walking the |
398 | | // global generator's makefiles captures exactly that set; a name-keyed |
399 | | // map sorts the output and dedupes imported targets, which are inherited |
400 | | // into child makefiles and would otherwise be seen many times. |
401 | | bool cmCMakeLanguageCommandPRINT_TARGETS( |
402 | | std::vector<cmListFileArgument> const& args, cmExecutionStatus& status) |
403 | 0 | { |
404 | 0 | cmMakefile& makefile = status.GetMakefile(); |
405 | 0 | std::vector<std::string> expandedArgs; |
406 | 0 | makefile.ExpandArguments(args, expandedArgs); |
407 | | |
408 | | // Drop the leading "PRINT_TARGETS" subcommand keyword. |
409 | 0 | std::vector<std::string> body(expandedArgs.begin() + 1, expandedArgs.end()); |
410 | |
|
411 | 0 | auto const ArgsParser = |
412 | 0 | cmArgumentParser<PrintTargetsArgs>() |
413 | 0 | .Bind("REGEX"_s, &PrintTargetsArgs::Regex) |
414 | 0 | .Bind("IMPORTED_ONLY"_s, &PrintTargetsArgs::ImportedOnly) |
415 | 0 | .Bind("NO_IMPORTED"_s, &PrintTargetsArgs::NoImported) |
416 | 0 | .Bind("IGNORE_CASE"_s, &PrintTargetsArgs::IgnoreCase); |
417 | |
|
418 | 0 | std::vector<std::string> unparsed; |
419 | 0 | auto parsedArgs = ArgsParser.Parse(body, &unparsed); |
420 | |
|
421 | 0 | if (!unparsed.empty()) { |
422 | 0 | return FatalError( |
423 | 0 | status, |
424 | 0 | cmStrCat( |
425 | 0 | "Unknown argument(s) given to cmake_language(PRINT_TARGETS) call: \"", |
426 | 0 | cmJoin(unparsed, "\" \""), "\".")); |
427 | 0 | } |
428 | 0 | if (parsedArgs.MaybeReportError(makefile)) { |
429 | 0 | cmSystemTools::SetFatalErrorOccurred(); |
430 | 0 | return true; |
431 | 0 | } |
432 | | |
433 | 0 | if (parsedArgs.ImportedOnly && parsedArgs.NoImported) { |
434 | 0 | return FatalError(status, |
435 | 0 | "IMPORTED_ONLY and NO_IMPORTED keywords are mutually " |
436 | 0 | "exclusive in cmake_language(PRINT_TARGETS) call."); |
437 | 0 | } |
438 | | |
439 | 0 | if (parsedArgs.IgnoreCase && !parsedArgs.Regex) { |
440 | 0 | return FatalError(status, |
441 | 0 | "IGNORE_CASE keyword in cmake_language(PRINT_TARGETS) " |
442 | 0 | "call is only valid with REGEX."); |
443 | 0 | } |
444 | | |
445 | | // Compile the optional REGEX up front so a bad pattern fails fast. With |
446 | | // IGNORE_CASE the pattern and the candidate names are both lower-cased. |
447 | 0 | cm::optional<cmsys::RegularExpression> regex; |
448 | 0 | if (parsedArgs.Regex) { |
449 | 0 | cmsys::RegularExpression re; |
450 | 0 | std::string const pat = parsedArgs.IgnoreCase |
451 | 0 | ? cmSystemTools::LowerCase(*parsedArgs.Regex) |
452 | 0 | : *parsedArgs.Regex; |
453 | 0 | if (!re.compile(pat)) { |
454 | 0 | return FatalError(status, |
455 | 0 | cmStrCat("REGEX regular expression \"", |
456 | 0 | *parsedArgs.Regex, "\" cannot compile.")); |
457 | 0 | } |
458 | 0 | regex = std::move(re); |
459 | 0 | } |
460 | | |
461 | 0 | bool const includeNormal = !parsedArgs.ImportedOnly; |
462 | 0 | bool const includeImported = !parsedArgs.NoImported; |
463 | |
|
464 | 0 | struct TargetInfo |
465 | 0 | { |
466 | 0 | cm::TargetType Type; |
467 | 0 | bool Imported; |
468 | 0 | }; |
469 | 0 | std::map<std::string, TargetInfo> targets; |
470 | 0 | for (auto const& mf : makefile.GetGlobalGenerator()->GetMakefiles()) { |
471 | 0 | if (includeNormal) { |
472 | 0 | for (auto const& ti : mf->GetTargets()) { |
473 | 0 | cmTarget const& t = ti.second; |
474 | 0 | targets.insert({ t.GetName(), { t.GetType(), false } }); |
475 | 0 | } |
476 | 0 | } |
477 | 0 | if (includeImported) { |
478 | 0 | for (cmTarget const* t : mf->GetImportedTargets()) { |
479 | 0 | targets.insert({ t->GetName(), { t->GetType(), true } }); |
480 | 0 | } |
481 | 0 | } |
482 | 0 | } |
483 | | |
484 | | // Build the body first so the header is suppressed when a REGEX filters |
485 | | // everything out (matches cmake_language(PRINT_VARIABLES) behavior). |
486 | 0 | std::string lines; |
487 | 0 | bool anyMatched = false; |
488 | 0 | for (auto const& t : targets) { |
489 | 0 | if (regex) { |
490 | 0 | std::string const subj = |
491 | 0 | parsedArgs.IgnoreCase ? cmSystemTools::LowerCase(t.first) : t.first; |
492 | 0 | if (!regex->find(subj)) { |
493 | 0 | continue; |
494 | 0 | } |
495 | 0 | } |
496 | 0 | lines += |
497 | 0 | cmStrCat(" ", t.first, " (", cmState::GetTargetTypeName(t.second.Type), |
498 | 0 | t.second.Imported ? ", IMPORTED" : "", ")\n"); |
499 | 0 | anyMatched = true; |
500 | 0 | } |
501 | |
|
502 | 0 | if (anyMatched) { |
503 | | // The message opens with a banner line. |
504 | 0 | std::string out = "Printing targets...\n"; |
505 | | // The header reflects the imported-filter mode and any REGEX in effect. |
506 | 0 | char const* label = "All targets"; |
507 | 0 | if (parsedArgs.ImportedOnly) { |
508 | 0 | label = "Imported targets"; |
509 | 0 | } else if (parsedArgs.NoImported) { |
510 | 0 | label = "Non-imported targets"; |
511 | 0 | } |
512 | 0 | out += cmStrCat(" ", label); |
513 | 0 | if (parsedArgs.Regex) { |
514 | 0 | out += cmStrCat( |
515 | 0 | " matching REGEX '", *parsedArgs.Regex, "' (", |
516 | 0 | parsedArgs.IgnoreCase ? "case insensitive" : "case sensitive", ")"); |
517 | 0 | } |
518 | 0 | out += cmStrCat(":\n", lines); |
519 | 0 | makefile.DisplayStatus(out, -1); |
520 | 0 | } |
521 | |
|
522 | 0 | if (!anyMatched && parsedArgs.Regex) { |
523 | 0 | makefile.IssueMessage( |
524 | 0 | MessageType::WARNING, |
525 | 0 | cmStrCat("No targets matching REGEX '", *parsedArgs.Regex, "' (", |
526 | 0 | parsedArgs.IgnoreCase ? "case insensitive" : "case sensitive", |
527 | 0 | ") in cmake_language(PRINT_TARGETS ...).")); |
528 | 0 | } |
529 | 0 | return true; |
530 | 0 | } |
531 | | |
532 | | struct PrintVariablesArgs : public ArgumentParser::ParseResult |
533 | | { |
534 | | bool All = false; |
535 | | ArgumentParser::NonEmpty<std::vector<std::string>> Named; |
536 | | cm::optional<std::string> NameRegex; |
537 | | cm::optional<std::string> ValueRegex; |
538 | | bool IgnoreCase = false; |
539 | | // Internal: set by the deprecated cmake_print_variables() module wrapper to |
540 | | // request the historical flush single-line output (no banner, undefined |
541 | | // names printed as name=""). Not part of the public interface. |
542 | | bool CmakePrintVariables = false; |
543 | | }; |
544 | | |
545 | | // Banner opening a cmake_language(PRINT_VARIABLES) status message. Suppressed |
546 | | // in the deprecated cmake_print_variables() (legacy) path. |
547 | | cm::static_string_view const PrintVariablesBanner = |
548 | | "Printing variables...\n"_s; |
549 | | |
550 | | // NAMED mode. By default (matching cmake_language(PRINT_PROPERTIES)) this |
551 | | // emits a " Named variables:" header followed by one `name = "value"` entry |
552 | | // per line; a name that is not defined prints as `name = <NOTFOUND>` in the |
553 | | // order given. When `legacy` is set (the deprecated cmake_print_variables() |
554 | | // wrapper) it instead emits the historical flush single line |
555 | | // "name=\"value\" ; ..." with no header and undefined names as name="". |
556 | | // Values go through GetDefinition so users see the same |
557 | | // in-scope/cache-fallback value they'd get from ${var}. |
558 | | void PrintVariablesNamed(cmMakefile& makefile, |
559 | | std::vector<std::string> const& names, bool legacy) |
560 | 0 | { |
561 | 0 | if (legacy) { |
562 | 0 | std::string msg; |
563 | 0 | bool first = true; |
564 | 0 | for (std::string const& name : names) { |
565 | 0 | if (!first) { |
566 | 0 | msg += " ; "; |
567 | 0 | } |
568 | 0 | first = false; |
569 | 0 | cmValue v = makefile.GetDefinition(name); |
570 | 0 | msg += cmStrCat(name, "=\"", v ? *v : std::string(), "\""); |
571 | 0 | } |
572 | 0 | makefile.DisplayStatus(msg, -1); |
573 | 0 | return; |
574 | 0 | } |
575 | | |
576 | 0 | std::string out = cmStrCat(PrintVariablesBanner, " Named variables:\n"); |
577 | 0 | for (std::string const& name : names) { |
578 | 0 | cmValue v = makefile.GetDefinition(name); |
579 | 0 | if (v) { |
580 | 0 | out += cmStrCat(" ", name, " = \"", *v, "\"\n"); |
581 | 0 | } else { |
582 | 0 | out += cmStrCat(" ", name, " = <NOTFOUND>\n"); |
583 | 0 | } |
584 | 0 | } |
585 | 0 | makefile.DisplayStatus(out, -1); |
586 | 0 | } |
587 | | |
588 | | // ALL mode: enumerate every regular variable in scope and |
589 | | // every cache entry. A name with both a regular variable and a cache |
590 | | // entry is printed on two distinct lines so the user sees the full |
591 | | // picture, including the value the cache holds even when a regular |
592 | | // variable shadows it. Values are read via the snapshot and cache APIs |
593 | | // directly to avoid firing variable-watch callbacks (which would |
594 | | // otherwise trip CMP0218 when CMAKE_WARN_DEPRECATED or |
595 | | // CMAKE_ERROR_DEPRECATED are in scope). |
596 | | bool PrintVariablesAll(cmMakefile& makefile, PrintVariablesArgs const& parsed, |
597 | | cmExecutionStatus& status) |
598 | 0 | { |
599 | 0 | cm::optional<cmsys::RegularExpression> nameRegex; |
600 | 0 | cm::optional<cmsys::RegularExpression> valueRegex; |
601 | 0 | if (parsed.NameRegex) { |
602 | 0 | cmsys::RegularExpression re; |
603 | 0 | std::string const pat = parsed.IgnoreCase |
604 | 0 | ? cmSystemTools::LowerCase(*parsed.NameRegex) |
605 | 0 | : *parsed.NameRegex; |
606 | 0 | if (!re.compile(pat)) { |
607 | 0 | return FatalError(status, |
608 | 0 | cmStrCat("NAME_REGEX regular expression \"", |
609 | 0 | *parsed.NameRegex, "\" cannot compile.")); |
610 | 0 | } |
611 | 0 | nameRegex = std::move(re); |
612 | 0 | } |
613 | 0 | if (parsed.ValueRegex) { |
614 | 0 | cmsys::RegularExpression re; |
615 | 0 | std::string const pat = parsed.IgnoreCase |
616 | 0 | ? cmSystemTools::LowerCase(*parsed.ValueRegex) |
617 | 0 | : *parsed.ValueRegex; |
618 | 0 | if (!re.compile(pat)) { |
619 | 0 | return FatalError(status, |
620 | 0 | cmStrCat("VALUE_REGEX regular expression \"", |
621 | 0 | *parsed.ValueRegex, "\" cannot compile.")); |
622 | 0 | } |
623 | 0 | valueRegex = std::move(re); |
624 | 0 | } |
625 | | |
626 | 0 | auto matches = [&](std::string const& name, std::string const& value) { |
627 | 0 | if (nameRegex) { |
628 | 0 | std::string const subj = |
629 | 0 | parsed.IgnoreCase ? cmSystemTools::LowerCase(name) : name; |
630 | 0 | if (!nameRegex->find(subj)) { |
631 | 0 | return false; |
632 | 0 | } |
633 | 0 | } |
634 | 0 | if (valueRegex) { |
635 | 0 | std::string const subj = |
636 | 0 | parsed.IgnoreCase ? cmSystemTools::LowerCase(value) : value; |
637 | 0 | if (!valueRegex->find(subj)) { |
638 | 0 | return false; |
639 | 0 | } |
640 | 0 | } |
641 | 0 | return true; |
642 | 0 | }; |
643 | |
|
644 | 0 | cmStateSnapshot const snapshot = makefile.GetStateSnapshot(); |
645 | 0 | cmState* state = makefile.GetState(); |
646 | | |
647 | | // GetDefinitions() already unions ClosureKeys() with the cache keys and |
648 | | // sorts the result; just dedupe so a name that lives in both lists isn't |
649 | | // visited twice. |
650 | 0 | auto names = makefile.GetDefinitions(); |
651 | 0 | names.erase(std::unique(names.begin(), names.end()), names.end()); |
652 | | |
653 | | // Build the body first so nothing is printed when a regex filters |
654 | | // everything out; the warning below covers that case. |
655 | 0 | std::string body; |
656 | 0 | bool anyMatched = false; |
657 | 0 | for (std::string const& name : names) { |
658 | 0 | cmValue regular = snapshot.GetDefinition(name); |
659 | 0 | if (regular && matches(name, *regular)) { |
660 | 0 | body += cmStrCat(" ", name, " = \"", *regular, "\"\n"); |
661 | 0 | anyMatched = true; |
662 | 0 | } |
663 | 0 | cmValue cached = state->GetInitializedCacheValue(name); |
664 | 0 | if (cached && matches(name, *cached)) { |
665 | 0 | auto const type = state->GetCacheEntryType(name); |
666 | 0 | body += cmStrCat(" CACHE{", name, "}"); |
667 | 0 | if (type != cmStateEnums::UNINITIALIZED) { |
668 | 0 | body += cmStrCat(":", cmState::CacheEntryTypeToString(type)); |
669 | 0 | } |
670 | 0 | body += cmStrCat(" = \"", *cached, "\"\n"); |
671 | 0 | anyMatched = true; |
672 | 0 | } |
673 | 0 | } |
674 | |
|
675 | 0 | if (anyMatched) { |
676 | 0 | cmValue listFile = snapshot.GetDefinition("CMAKE_CURRENT_LIST_FILE"); |
677 | 0 | std::string out = |
678 | 0 | cmStrCat(PrintVariablesBanner, " Variables in scope at '", |
679 | 0 | listFile ? *listFile : std::string("<unknown>"), "'"); |
680 | 0 | if (parsed.NameRegex || parsed.ValueRegex) { |
681 | 0 | out += " matching"; |
682 | 0 | if (parsed.NameRegex) { |
683 | 0 | out += cmStrCat(" name '", *parsed.NameRegex, "'"); |
684 | 0 | } |
685 | 0 | if (parsed.NameRegex && parsed.ValueRegex) { |
686 | 0 | out += " and"; |
687 | 0 | } |
688 | 0 | if (parsed.ValueRegex) { |
689 | 0 | out += cmStrCat(" value '", *parsed.ValueRegex, "'"); |
690 | 0 | } |
691 | 0 | out += parsed.IgnoreCase ? " (case insensitive)" : " (case sensitive)"; |
692 | 0 | } |
693 | 0 | out += cmStrCat(":\n", body); |
694 | 0 | makefile.DisplayStatus(out, -1); |
695 | 0 | } |
696 | |
|
697 | 0 | if (!anyMatched && (parsed.NameRegex || parsed.ValueRegex)) { |
698 | 0 | std::string msg = "No variables in scope matching"; |
699 | 0 | if (parsed.NameRegex) { |
700 | 0 | msg += cmStrCat(" name '", *parsed.NameRegex, "'"); |
701 | 0 | } |
702 | 0 | if (parsed.NameRegex && parsed.ValueRegex) { |
703 | 0 | msg += " and"; |
704 | 0 | } |
705 | 0 | if (parsed.ValueRegex) { |
706 | 0 | msg += cmStrCat(" value '", *parsed.ValueRegex, "'"); |
707 | 0 | } |
708 | 0 | msg += parsed.IgnoreCase ? " (case insensitive)" : " (case sensitive)"; |
709 | 0 | msg += " in cmake_language(PRINT_VARIABLES ...)."; |
710 | 0 | makefile.IssueMessage(MessageType::WARNING, msg); |
711 | 0 | } |
712 | 0 | return true; |
713 | 0 | } |
714 | | |
715 | | bool cmCMakeLanguageCommandPRINT_VARIABLES( |
716 | | std::vector<cmListFileArgument> const& args, cmExecutionStatus& status) |
717 | 0 | { |
718 | 0 | cmMakefile& makefile = status.GetMakefile(); |
719 | 0 | std::vector<std::string> expandedArgs; |
720 | 0 | makefile.ExpandArguments(args, expandedArgs); |
721 | | |
722 | | // Drop the leading "PRINT_VARIABLES" subcommand keyword. |
723 | 0 | std::vector<std::string> body(expandedArgs.begin() + 1, expandedArgs.end()); |
724 | |
|
725 | 0 | auto const ArgsParser = |
726 | 0 | cmArgumentParser<PrintVariablesArgs>() |
727 | 0 | .Bind("ALL"_s, &PrintVariablesArgs::All) |
728 | 0 | .Bind("NAMED"_s, &PrintVariablesArgs::Named) |
729 | 0 | .Bind("NAME_REGEX"_s, &PrintVariablesArgs::NameRegex) |
730 | 0 | .Bind("VALUE_REGEX"_s, &PrintVariablesArgs::ValueRegex) |
731 | 0 | .Bind("IGNORE_CASE"_s, &PrintVariablesArgs::IgnoreCase) |
732 | 0 | .Bind("__CMAKE_PRINT_VARIABLES"_s, |
733 | 0 | &PrintVariablesArgs::CmakePrintVariables); |
734 | |
|
735 | 0 | std::vector<std::string> unparsed; |
736 | 0 | auto parsedArgs = ArgsParser.Parse(body, &unparsed); |
737 | | |
738 | | // No bareword form: every token must belong to ALL, NAMED, or a filter. |
739 | 0 | if (!unparsed.empty()) { |
740 | 0 | return FatalError( |
741 | 0 | status, |
742 | 0 | cmStrCat("Unknown argument(s) given to cmake_language(PRINT_VARIABLES)" |
743 | 0 | ": \"", |
744 | 0 | cmJoin(unparsed, "\" \""), "\".")); |
745 | 0 | } |
746 | | |
747 | 0 | if (parsedArgs.MaybeReportError(makefile)) { |
748 | 0 | cmSystemTools::SetFatalErrorOccurred(); |
749 | 0 | return true; |
750 | 0 | } |
751 | | |
752 | 0 | bool const hasNamed = !parsedArgs.Named.empty(); |
753 | 0 | bool const hasFilters = |
754 | 0 | parsedArgs.NameRegex || parsedArgs.ValueRegex || parsedArgs.IgnoreCase; |
755 | | |
756 | | // ALL and NAMED are mutually exclusive modes. |
757 | 0 | if (parsedArgs.All && hasNamed) { |
758 | 0 | return FatalError(status, |
759 | 0 | "ALL and NAMED keywords in " |
760 | 0 | "cmake_language(PRINT_VARIABLES) call " |
761 | 0 | "are mutually exclusive."); |
762 | 0 | } |
763 | | |
764 | | // The filter keywords narrow an enumeration, so they only make sense with |
765 | | // ALL (explicit or implicit), never with NAMED. |
766 | 0 | if (hasNamed && hasFilters) { |
767 | 0 | return FatalError(status, |
768 | 0 | "NAME_REGEX, VALUE_REGEX, and IGNORE_CASE in " |
769 | 0 | "cmake_language(PRINT_VARIABLES) call " |
770 | 0 | "are only valid with ALL, not NAMED."); |
771 | 0 | } |
772 | | |
773 | | // __CMAKE_PRINT_VARIABLES selects the legacy single-line NAMED output; it is |
774 | | // meaningless when enumerating variables (ALL, explicit or implicit). |
775 | 0 | if (!hasNamed && parsedArgs.CmakePrintVariables) { |
776 | 0 | return FatalError(status, |
777 | 0 | "__CMAKE_PRINT_VARIABLES in " |
778 | 0 | "cmake_language(PRINT_VARIABLES) call is only valid " |
779 | 0 | "with NAMED."); |
780 | 0 | } |
781 | | |
782 | | // Default to ALL when neither mode keyword is given. |
783 | 0 | bool const all = parsedArgs.All || !hasNamed; |
784 | 0 | if (all) { |
785 | 0 | return PrintVariablesAll(makefile, parsedArgs, status); |
786 | 0 | } |
787 | | |
788 | 0 | PrintVariablesNamed(makefile, parsedArgs.Named, |
789 | 0 | parsedArgs.CmakePrintVariables); |
790 | 0 | return true; |
791 | 0 | } |
792 | | // Walks every target reachable from `root` through any linkage - PUBLIC, |
793 | | // INTERFACE, PRIVATE, and `$<LINK_ONLY:>`-wrapped deps. At each level we |
794 | | // union the link implementation and link interface, each queried with both |
795 | | // UseTo::Compile and UseTo::Link, so deps that only appear under LINK_ONLY |
796 | | // (which evaluates to empty under UseTo::Compile) still come through. |
797 | | std::vector<cmGeneratorTarget const*> CollectDependentTargets( |
798 | | cmGeneratorTarget const* root, std::string const& config) |
799 | 0 | { |
800 | 0 | std::vector<cmGeneratorTarget const*> deps; |
801 | 0 | std::set<cmGeneratorTarget const*> visited; |
802 | 0 | visited.insert(root); |
803 | 0 | std::vector<cmGeneratorTarget const*> queue; |
804 | 0 | queue.push_back(root); |
805 | 0 | while (!queue.empty()) { |
806 | 0 | cmGeneratorTarget const* cur = queue.back(); |
807 | 0 | queue.pop_back(); |
808 | 0 | auto visit = [&](std::vector<cmLinkItem> const& libs) { |
809 | 0 | for (cmLinkItem const& item : libs) { |
810 | 0 | cmGeneratorTarget const* dep = item.Target; |
811 | 0 | if (!dep || !visited.insert(dep).second) { |
812 | 0 | continue; |
813 | 0 | } |
814 | 0 | deps.push_back(dep); |
815 | 0 | queue.push_back(dep); |
816 | 0 | } |
817 | 0 | }; |
818 | 0 | for (auto useTo : { cmGeneratorTarget::UseTo::Compile, |
819 | 0 | cmGeneratorTarget::UseTo::Link }) { |
820 | 0 | if (cmLinkImplementationLibraries const* impl = |
821 | 0 | cur->GetLinkImplementationLibraries(config, useTo)) { |
822 | 0 | visit(impl->Libraries); |
823 | 0 | } |
824 | 0 | if (cmLinkInterfaceLibraries const* iface = |
825 | 0 | cur->GetLinkInterfaceLibraries(config, root, useTo)) { |
826 | 0 | visit(iface->Libraries); |
827 | 0 | } |
828 | 0 | } |
829 | 0 | } |
830 | 0 | return deps; |
831 | 0 | } |
832 | | |
833 | | // Append one property line to `out`: |
834 | | // " <entityName>.<propertyName> = \"<value>\"" |
835 | | // A null `value` writes "<NOTFOUND>" instead of "= \"...\"". |
836 | | void WritePropertyLine(std::string& out, std::string const& entityName, |
837 | | std::string const& propertyName, cmValue value) |
838 | 0 | { |
839 | 0 | out += cmStrCat(" ", entityName, ".", propertyName); |
840 | 0 | if (value) { |
841 | 0 | out += cmStrCat(" = \"", *value, "\""); |
842 | 0 | } else { |
843 | 0 | out += " = <NOTFOUND>"; |
844 | 0 | } |
845 | 0 | out += "\n"; |
846 | 0 | } |
847 | | |
848 | | enum class BlockKind |
849 | | { |
850 | | Explicit, |
851 | | All, |
852 | | }; |
853 | | |
854 | | enum class HeaderSuffix |
855 | | { |
856 | | None, |
857 | | Reachable, |
858 | | }; |
859 | | |
860 | | void EmitBlockHeader( |
861 | | std::string& out, std::string const& entityName, |
862 | | std::string const& entityType, BlockKind kind, HeaderSuffix suffix, |
863 | | cm::optional<std::string> const& nameRegexStr = cm::nullopt, |
864 | | cm::optional<std::string> const& valueRegexStr = cm::nullopt) |
865 | 0 | { |
866 | 0 | out += |
867 | 0 | cmStrCat(" ", (kind == BlockKind::All ? "All properties" : "Properties"), |
868 | 0 | " for ", entityType, " ", entityName); |
869 | 0 | if (suffix == HeaderSuffix::Reachable) { |
870 | 0 | out += " (and all reachable)"; |
871 | 0 | } |
872 | 0 | if (kind == BlockKind::All && (nameRegexStr || valueRegexStr)) { |
873 | 0 | out += " matching"; |
874 | 0 | if (nameRegexStr) { |
875 | 0 | out += cmStrCat(" name '", *nameRegexStr, "'"); |
876 | 0 | } |
877 | 0 | if (nameRegexStr && valueRegexStr) { |
878 | 0 | out += " and"; |
879 | 0 | } |
880 | 0 | if (valueRegexStr) { |
881 | 0 | out += cmStrCat(" value '", *valueRegexStr, "'"); |
882 | 0 | } |
883 | 0 | } |
884 | 0 | out += ":\n"; |
885 | 0 | } |
886 | | |
887 | | // Build the "no properties matched" warning text used when the regex filter |
888 | | // wipes everything out for a target's ALL block. Returned without any |
889 | | // "Warning:" prefix - callers feed it to IssueMessage, which adds the |
890 | | // standard "CMake Warning at <file>:<line>" framing. |
891 | | std::string EmptyMatchWarningMessage( |
892 | | std::string const& entityName, std::string const& entityType, |
893 | | cm::optional<std::string> const& nameRegexStr, |
894 | | cm::optional<std::string> const& valueRegexStr) |
895 | 0 | { |
896 | 0 | std::string msg = |
897 | 0 | cmStrCat("No properties for ", entityType, " ", entityName, " matching"); |
898 | 0 | if (nameRegexStr) { |
899 | 0 | msg += cmStrCat(" name '", *nameRegexStr, "'"); |
900 | 0 | } |
901 | 0 | if (nameRegexStr && valueRegexStr) { |
902 | 0 | msg += " and"; |
903 | 0 | } |
904 | 0 | if (valueRegexStr) { |
905 | 0 | msg += cmStrCat(" value '", *valueRegexStr, "'"); |
906 | 0 | } |
907 | 0 | msg += " in cmake_language(PRINT_PROPERTIES ...)."; |
908 | 0 | return msg; |
909 | 0 | } |
910 | | |
911 | | // Entity kinds supported by cmake_language(PRINT_PROPERTIES). |
912 | | enum class EntityKind |
913 | | { |
914 | | Target, |
915 | | Source, |
916 | | Test, |
917 | | Directory, |
918 | | Cache |
919 | | }; |
920 | | |
921 | | // Display name used in PRINT_PROPERTIES output ("TARGET", "SOURCE", etc.). |
922 | | char const* EntityTypeName(EntityKind kind) |
923 | 0 | { |
924 | 0 | switch (kind) { |
925 | 0 | case EntityKind::Target: |
926 | 0 | return "TARGET"; |
927 | 0 | case EntityKind::Source: |
928 | 0 | return "SOURCE"; |
929 | 0 | case EntityKind::Test: |
930 | 0 | return "TEST"; |
931 | 0 | case EntityKind::Directory: |
932 | 0 | return "DIRECTORY"; |
933 | 0 | case EntityKind::Cache: |
934 | 0 | return "CACHE"; |
935 | 0 | } |
936 | 0 | return ""; |
937 | 0 | } |
938 | | |
939 | | // Dispatch a property lookup to the appropriate per-entity helper. |
940 | | bool GetPropertyHelper(cmExecutionStatus& status, EntityKind kind, |
941 | | std::string const& name, |
942 | | std::string const& propertyName, cmValue& out) |
943 | 0 | { |
944 | 0 | switch (kind) { |
945 | 0 | case EntityKind::Target: |
946 | 0 | return GetPropertyCommand::LookupTargetProperty(status, name, |
947 | 0 | propertyName, out); |
948 | 0 | case EntityKind::Source: |
949 | 0 | return GetPropertyCommand::LookupSourceProperty(status, name, |
950 | 0 | propertyName, out); |
951 | 0 | case EntityKind::Test: |
952 | 0 | return GetPropertyCommand::LookupTestProperty(status, name, propertyName, |
953 | 0 | out); |
954 | 0 | case EntityKind::Directory: |
955 | 0 | return GetPropertyCommand::LookupDirectoryProperty(status, name, |
956 | 0 | propertyName, out); |
957 | 0 | case EntityKind::Cache: |
958 | 0 | return GetPropertyCommand::LookupCacheProperty(status, name, |
959 | 0 | propertyName, out); |
960 | 0 | } |
961 | 0 | return false; |
962 | 0 | } |
963 | | |
964 | | // Emit one ALL-mode block: prints all properties matching the specified |
965 | | // regexes for a head target (`targets[0]`) and optionally any reachable |
966 | | // dependencies (`targets[1]` and on). |
967 | | bool EmitAllPropertiesBlock(std::string& out, std::string const& headerName, |
968 | | std::vector<cmTarget*> const& targets, |
969 | | HeaderSuffix suffix, |
970 | | cm::optional<cmsys::RegularExpression>& nameRegex, |
971 | | cm::optional<cmsys::RegularExpression>& valueRegex, |
972 | | cm::optional<std::string> const& nameRegexStr, |
973 | | cm::optional<std::string> const& valueRegexStr) |
974 | 0 | { |
975 | 0 | std::string groupBuf; |
976 | 0 | bool groupEmitted = false; |
977 | 0 | for (cmTarget* t : targets) { |
978 | 0 | std::string const& rowName = |
979 | 0 | (t == targets.front()) ? headerName : t->GetName(); |
980 | 0 | for (auto const& kv : t->GetExtendedProperties().GetList()) { |
981 | 0 | if (nameRegex && !nameRegex->find(kv.first)) { |
982 | 0 | continue; |
983 | 0 | } |
984 | 0 | if (valueRegex && !valueRegex->find(kv.second)) { |
985 | 0 | continue; |
986 | 0 | } |
987 | 0 | WritePropertyLine(groupBuf, rowName, kv.first, cmValue(kv.second)); |
988 | 0 | groupEmitted = true; |
989 | 0 | } |
990 | 0 | } |
991 | 0 | if (!groupEmitted) { |
992 | 0 | return false; |
993 | 0 | } |
994 | 0 | EmitBlockHeader(out, headerName, "TARGET", BlockKind::All, suffix, |
995 | 0 | nameRegexStr, valueRegexStr); |
996 | 0 | out += groupBuf; |
997 | 0 | return true; |
998 | 0 | } |
999 | | |
1000 | | // Outcome of emitting a NAMED-mode block. |
1001 | | enum class NamedBlockResult |
1002 | | { |
1003 | | Failed, // a property lookup hard-failed |
1004 | | Emitted, // at least one property line was written |
1005 | | Empty, // every requested property was skipped; header suppressed |
1006 | | }; |
1007 | | |
1008 | | // Emit one block of user-specified properties for a head target (`targets[0]`) |
1009 | | // and optionally any reachable dependencies (`targets[1]` and on). |
1010 | | NamedBlockResult EmitNamedPropertiesBlock( |
1011 | | std::string& out, std::string const& headerName, |
1012 | | std::vector<cmTarget*> const& targets, |
1013 | | std::vector<std::string> const& namedProperties, HeaderSuffix suffix, |
1014 | | cmMakefile& mf) |
1015 | 0 | { |
1016 | 0 | bool headerWritten = false; |
1017 | 0 | for (cmTarget* t : targets) { |
1018 | 0 | std::string const& rowName = |
1019 | 0 | (t == targets.front()) ? headerName : t->GetName(); |
1020 | 0 | for (std::string const& propName : namedProperties) { |
1021 | | // A computed location property may not be read from a non-imported |
1022 | | // target; skip it with a warning rather than triggering the getter's |
1023 | | // fatal error. |
1024 | 0 | if (!t->IsImported() && |
1025 | 0 | cmTargetPropertyComputer::IsComputedLocationProperty(t->GetType(), |
1026 | 0 | propName)) { |
1027 | 0 | mf.IssueMessage( |
1028 | 0 | MessageType::WARNING, |
1029 | 0 | cmStrCat("The ", propName, |
1030 | 0 | " property may not be read from non-imported target \"", |
1031 | 0 | rowName, "\"; skipping.")); |
1032 | 0 | continue; |
1033 | 0 | } |
1034 | 0 | cmValue v; |
1035 | 0 | if (cmGetTargetProperty(rowName, t, propName, mf, v) != |
1036 | 0 | cmGetTargetPropertyResult::Success) { |
1037 | 0 | return NamedBlockResult::Failed; |
1038 | 0 | } |
1039 | 0 | if (!headerWritten) { |
1040 | 0 | EmitBlockHeader(out, headerName, "TARGET", BlockKind::Explicit, |
1041 | 0 | suffix); |
1042 | 0 | headerWritten = true; |
1043 | 0 | } |
1044 | 0 | WritePropertyLine(out, rowName, propName, v); |
1045 | 0 | } |
1046 | 0 | } |
1047 | 0 | return headerWritten ? NamedBlockResult::Emitted : NamedBlockResult::Empty; |
1048 | 0 | } |
1049 | | |
1050 | | bool PrintPropertiesConfigureTime( |
1051 | | cmExecutionStatus& status, std::vector<std::string> const& namedProperties, |
1052 | | bool all, std::vector<std::string> const& entityNames, EntityKind kind, |
1053 | | cm::optional<std::string> const& propertyNameRegexStr, |
1054 | | cm::optional<std::string> const& propertyValueRegexStr, |
1055 | | cm::optional<cmsys::RegularExpression>& propertyNameRegex, |
1056 | | cm::optional<cmsys::RegularExpression>& propertyValueRegex, |
1057 | | std::string const& messagePrefix) |
1058 | 0 | { |
1059 | 0 | cmMakefile& makefile = status.GetMakefile(); |
1060 | 0 | bool const hasRegex = propertyNameRegexStr || propertyValueRegexStr; |
1061 | 0 | std::string out = messagePrefix; |
1062 | 0 | bool anyEmitted = false; |
1063 | 0 | for (auto const& entityName : entityNames) { |
1064 | 0 | if (kind == EntityKind::Target) { |
1065 | 0 | cmTarget* target = makefile.FindTargetToUse(entityName); |
1066 | 0 | if (!target) { |
1067 | 0 | out += cmStrCat("\n No such TARGET \"", entityName, "\" !\n\n"); |
1068 | 0 | anyEmitted = true; |
1069 | 0 | continue; |
1070 | 0 | } |
1071 | 0 | if (all) { |
1072 | 0 | bool const emitted = EmitAllPropertiesBlock( |
1073 | 0 | out, entityName, { target }, HeaderSuffix::None, propertyNameRegex, |
1074 | 0 | propertyValueRegex, propertyNameRegexStr, propertyValueRegexStr); |
1075 | 0 | if (emitted) { |
1076 | 0 | anyEmitted = true; |
1077 | 0 | } else if (hasRegex) { |
1078 | 0 | makefile.IssueMessage(MessageType::WARNING, |
1079 | 0 | EmptyMatchWarningMessage( |
1080 | 0 | entityName, "TARGET", propertyNameRegexStr, |
1081 | 0 | propertyValueRegexStr)); |
1082 | 0 | } |
1083 | 0 | } else { |
1084 | 0 | NamedBlockResult const result = EmitNamedPropertiesBlock( |
1085 | 0 | out, entityName, { target }, namedProperties, HeaderSuffix::None, |
1086 | 0 | makefile); |
1087 | 0 | if (result == NamedBlockResult::Failed) { |
1088 | 0 | status.SetError(cmStrCat( |
1089 | 0 | "failed to retrieve properties for TARGET \"", entityName, "\"")); |
1090 | 0 | return false; |
1091 | 0 | } |
1092 | 0 | if (result == NamedBlockResult::Emitted) { |
1093 | 0 | anyEmitted = true; |
1094 | 0 | } |
1095 | 0 | } |
1096 | 0 | } else { |
1097 | 0 | EmitBlockHeader(out, entityName, EntityTypeName(kind), |
1098 | 0 | BlockKind::Explicit, HeaderSuffix::None); |
1099 | 0 | for (auto const& propertyName : namedProperties) { |
1100 | 0 | cmValue v; |
1101 | 0 | if (!GetPropertyHelper(status, kind, entityName, propertyName, v)) { |
1102 | 0 | return false; |
1103 | 0 | } |
1104 | 0 | WritePropertyLine(out, entityName, propertyName, v); |
1105 | 0 | } |
1106 | 0 | anyEmitted = true; |
1107 | 0 | } |
1108 | 0 | } |
1109 | 0 | if (anyEmitted) { |
1110 | 0 | makefile.DisplayStatus(out, -1); |
1111 | 0 | } |
1112 | 0 | return true; |
1113 | 0 | } |
1114 | | |
1115 | | bool PrintTargetPropertiesDeferred( |
1116 | | std::vector<std::string> targetNames, |
1117 | | std::vector<std::string> namedProperties, bool all, |
1118 | | cm::optional<std::string> propertyNameRegexStr, |
1119 | | cm::optional<std::string> propertyValueRegexStr, |
1120 | | cm::optional<cmsys::RegularExpression> propertyNameRegex, |
1121 | | cm::optional<cmsys::RegularExpression> propertyValueRegex, |
1122 | | bool followDependencies, std::string messagePrefix, |
1123 | | cmExecutionStatus& status) |
1124 | 0 | { |
1125 | 0 | cmListFileBacktrace const bt = status.GetMakefile().GetBacktrace(); |
1126 | 0 | status.GetMakefile().AddGeneratorAction( |
1127 | 0 | [targetNames, namedProperties, all, propertyNameRegexStr, |
1128 | 0 | propertyValueRegexStr, propertyNameRegex, propertyValueRegex, |
1129 | 0 | followDependencies, messagePrefix, bt]( |
1130 | 0 | cmLocalGenerator& lg, cmListFileBacktrace const& /*lambdaBt*/) mutable { |
1131 | 0 | std::vector<std::string> const configs = |
1132 | 0 | lg.GetMakefile()->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); |
1133 | 0 | std::string const config = |
1134 | 0 | configs.empty() ? std::string() : configs.front(); |
1135 | 0 | bool const hasRegex = propertyNameRegexStr || propertyValueRegexStr; |
1136 | 0 | cmake* cmakeInst = lg.GetMakefile()->GetCMakeInstance(); |
1137 | |
|
1138 | 0 | std::string out = messagePrefix; |
1139 | 0 | bool anyEmitted = false; |
1140 | 0 | for (std::string const& name : targetNames) { |
1141 | 0 | cmGeneratorTarget* root = lg.FindGeneratorTargetToUse(name); |
1142 | 0 | if (!root) { |
1143 | 0 | out += cmStrCat("\n No such TARGET \"", name, "\" !\n\n"); |
1144 | 0 | anyEmitted = true; |
1145 | 0 | continue; |
1146 | 0 | } |
1147 | 0 | std::vector<cmTarget*> targets; |
1148 | 0 | targets.push_back(root->Target); |
1149 | 0 | HeaderSuffix const suffix = |
1150 | 0 | followDependencies ? HeaderSuffix::Reachable : HeaderSuffix::None; |
1151 | 0 | if (followDependencies) { |
1152 | 0 | for (cmGeneratorTarget const* dep : |
1153 | 0 | CollectDependentTargets(root, config)) { |
1154 | 0 | targets.push_back(dep->Target); |
1155 | 0 | } |
1156 | 0 | } |
1157 | 0 | if (all) { |
1158 | 0 | bool const emitted = EmitAllPropertiesBlock( |
1159 | 0 | out, name, targets, suffix, propertyNameRegex, propertyValueRegex, |
1160 | 0 | propertyNameRegexStr, propertyValueRegexStr); |
1161 | 0 | if (emitted) { |
1162 | 0 | anyEmitted = true; |
1163 | 0 | } else if (hasRegex) { |
1164 | 0 | cmakeInst->IssueMessage( |
1165 | 0 | MessageType::WARNING, |
1166 | 0 | EmptyMatchWarningMessage(name, "TARGET", propertyNameRegexStr, |
1167 | 0 | propertyValueRegexStr), |
1168 | 0 | bt); |
1169 | 0 | } |
1170 | 0 | } else { |
1171 | 0 | NamedBlockResult const result = EmitNamedPropertiesBlock( |
1172 | 0 | out, name, targets, namedProperties, suffix, *lg.GetMakefile()); |
1173 | 0 | if (result == NamedBlockResult::Failed) { |
1174 | 0 | cmakeInst->IssueMessage( |
1175 | 0 | MessageType::FATAL_ERROR, |
1176 | 0 | cmStrCat("failed to retrieve properties for TARGET \"", name, |
1177 | 0 | "\""), |
1178 | 0 | bt); |
1179 | 0 | return; |
1180 | 0 | } |
1181 | 0 | if (result == NamedBlockResult::Emitted) { |
1182 | 0 | anyEmitted = true; |
1183 | 0 | } |
1184 | 0 | } |
1185 | 0 | } |
1186 | 0 | if (anyEmitted) { |
1187 | 0 | lg.GetMakefile()->DisplayStatus(out, -1); |
1188 | 0 | } |
1189 | 0 | }, |
1190 | 0 | cmMakefile::GeneratorActionWhen::AfterGeneratorTargets); |
1191 | 0 | return true; |
1192 | 0 | } |
1193 | | |
1194 | | bool cmCMakeLanguageCommandPRINT_PROPERTIES( |
1195 | | std::vector<std::string> const& args, cmExecutionStatus& status) |
1196 | 0 | { |
1197 | 0 | struct PrintPropertiesArg : public ArgumentParser::ParseResult |
1198 | 0 | { |
1199 | 0 | bool All = false; |
1200 | 0 | ArgumentParser::NonEmpty<std::vector<std::string>> Named; |
1201 | 0 | cm::optional<std::string> PropertyNameRegex; |
1202 | 0 | cm::optional<std::string> PropertyValueRegex; |
1203 | | // Internal: set by the deprecated cmake_print_properties() module wrapper |
1204 | | // to select the historical leading-blank-line output instead of the |
1205 | | // banner. Not part of the public interface. |
1206 | 0 | bool CmakePrintProperties = false; |
1207 | 0 | }; |
1208 | |
|
1209 | 0 | auto ArgsParser = |
1210 | 0 | cmArgumentParser<PrintPropertiesArg>() |
1211 | 0 | .Bind("ALL"_s, &PrintPropertiesArg::All) |
1212 | 0 | .Bind("NAMED"_s, &PrintPropertiesArg::Named) |
1213 | 0 | .Bind("PROPERTY_NAME_REGEX"_s, &PrintPropertiesArg::PropertyNameRegex) |
1214 | 0 | .Bind("PROPERTY_VALUE_REGEX"_s, &PrintPropertiesArg::PropertyValueRegex) |
1215 | 0 | .Bind("__CMAKE_PRINT_PROPERTIES"_s, |
1216 | 0 | &PrintPropertiesArg::CmakePrintProperties); |
1217 | |
|
1218 | 0 | std::vector<std::string> unparsed; |
1219 | 0 | auto parsedArgs = ArgsParser.Parse(args, &unparsed); |
1220 | |
|
1221 | 0 | if (unparsed.empty()) { |
1222 | 0 | return FatalError(status, |
1223 | 0 | cmStrCat("mode keyword missing in ", |
1224 | 0 | "cmake_language(PRINT_PROPERTIES) call, ", |
1225 | 0 | "there must be exactly one of TARGETS SOURCES " |
1226 | 0 | "TESTS DIRECTORIES CACHE_ENTRIES")); |
1227 | 0 | } |
1228 | | |
1229 | 0 | if (parsedArgs.MaybeReportError(status.GetMakefile())) { |
1230 | 0 | cmSystemTools::SetFatalErrorOccurred(); |
1231 | 0 | return true; |
1232 | 0 | } |
1233 | | |
1234 | | // Second parse args to get the mode |
1235 | 0 | struct PrintPropertiesModesArgs : public ArgumentParser::ParseResult |
1236 | 0 | { |
1237 | 0 | ArgumentParser::MaybeEmpty<std::vector<std::string>> Targets; |
1238 | 0 | ArgumentParser::MaybeEmpty<std::vector<std::string>> Sources; |
1239 | 0 | ArgumentParser::MaybeEmpty<std::vector<std::string>> Tests; |
1240 | 0 | ArgumentParser::MaybeEmpty<std::vector<std::string>> Directories; |
1241 | 0 | ArgumentParser::MaybeEmpty<std::vector<std::string>> CacheEntries; |
1242 | 0 | bool Deferred = false; |
1243 | 0 | bool FollowDependencies = false; |
1244 | 0 | }; |
1245 | 0 | auto const ArgsParserMode = |
1246 | 0 | cmArgumentParser<PrintPropertiesModesArgs>() |
1247 | 0 | .Bind("TARGETS"_s, &PrintPropertiesModesArgs::Targets) |
1248 | 0 | .Bind("SOURCES"_s, &PrintPropertiesModesArgs::Sources) |
1249 | 0 | .Bind("TESTS"_s, &PrintPropertiesModesArgs::Tests) |
1250 | 0 | .Bind("DIRECTORIES"_s, &PrintPropertiesModesArgs::Directories) |
1251 | 0 | .Bind("CACHE_ENTRIES"_s, &PrintPropertiesModesArgs::CacheEntries) |
1252 | 0 | .Bind("DEFERRED"_s, &PrintPropertiesModesArgs::Deferred) |
1253 | 0 | .Bind("FOLLOW_DEPENDENCIES"_s, |
1254 | 0 | &PrintPropertiesModesArgs::FollowDependencies); |
1255 | |
|
1256 | 0 | std::vector<std::string> modeArgs = unparsed; |
1257 | 0 | unparsed.clear(); |
1258 | 0 | auto parsedArgsMode = ArgsParserMode.Parse(modeArgs, &unparsed); |
1259 | |
|
1260 | 0 | if (!unparsed.empty()) { |
1261 | 0 | return FatalError( |
1262 | 0 | status, cmStrCat("Unknown keywords: \"", cmJoin(unparsed, " "), "\"")); |
1263 | 0 | } |
1264 | | |
1265 | 0 | if (parsedArgsMode.MaybeReportError(status.GetMakefile())) { |
1266 | 0 | cmSystemTools::SetFatalErrorOccurred(); |
1267 | 0 | return true; |
1268 | 0 | } |
1269 | 0 | std::vector<std::string> modes; |
1270 | 0 | std::vector<std::string> items; |
1271 | 0 | EntityKind kind = EntityKind::Target; |
1272 | 0 | if (!parsedArgsMode.Targets.empty()) { |
1273 | 0 | modes.push_back("TARGETS"); |
1274 | 0 | items = parsedArgsMode.Targets; |
1275 | 0 | kind = EntityKind::Target; |
1276 | 0 | } |
1277 | 0 | if (!parsedArgsMode.Sources.empty()) { |
1278 | 0 | modes.push_back("SOURCES"); |
1279 | 0 | items = parsedArgsMode.Sources; |
1280 | 0 | kind = EntityKind::Source; |
1281 | 0 | } |
1282 | 0 | if (!parsedArgsMode.Tests.empty()) { |
1283 | 0 | modes.push_back("TESTS"); |
1284 | 0 | items = parsedArgsMode.Tests; |
1285 | 0 | kind = EntityKind::Test; |
1286 | 0 | } |
1287 | 0 | if (!parsedArgsMode.Directories.empty()) { |
1288 | 0 | modes.push_back("DIRECTORIES"); |
1289 | 0 | items = parsedArgsMode.Directories; |
1290 | 0 | kind = EntityKind::Directory; |
1291 | 0 | } |
1292 | 0 | if (!parsedArgsMode.CacheEntries.empty()) { |
1293 | 0 | modes.push_back("CACHE_ENTRIES"); |
1294 | 0 | items = parsedArgsMode.CacheEntries; |
1295 | 0 | kind = EntityKind::Cache; |
1296 | 0 | } |
1297 | |
|
1298 | 0 | if (modes.empty()) { |
1299 | 0 | return FatalError(status, |
1300 | 0 | cmStrCat("mode keyword missing in ", |
1301 | 0 | "cmake_language(PRINT_PROPERTIES) call, ", |
1302 | 0 | "there must be exactly one of TARGETS SOURCES " |
1303 | 0 | "TESTS DIRECTORIES CACHE_ENTRIES")); |
1304 | 0 | } |
1305 | 0 | if (modes.size() > 1) { |
1306 | 0 | return FatalError(status, |
1307 | 0 | cmStrCat("multiple mode keywords used in ", |
1308 | 0 | "cmake_language(PRINT_PROPERTIES) call, ", |
1309 | 0 | "there must be exactly one of TARGETS SOURCES " |
1310 | 0 | "TESTS DIRECTORIES CACHE_ENTRIES.")); |
1311 | 0 | } |
1312 | 0 | std::string const mode = modes[0]; |
1313 | 0 | bool const isTargets = (mode == "TARGETS"); |
1314 | 0 | bool const hasNamed = !parsedArgs.Named.empty(); |
1315 | 0 | bool const hasRegex = |
1316 | 0 | parsedArgs.PropertyNameRegex || parsedArgs.PropertyValueRegex; |
1317 | |
|
1318 | 0 | if (!isTargets) { |
1319 | 0 | if (parsedArgs.All) { |
1320 | 0 | return FatalError(status, |
1321 | 0 | cmStrCat("ALL keyword in ", |
1322 | 0 | "cmake_language(PRINT_PROPERTIES) call ", |
1323 | 0 | "is only valid with the TARGETS scope.")); |
1324 | 0 | } |
1325 | 0 | if (hasRegex) { |
1326 | 0 | return FatalError( |
1327 | 0 | status, |
1328 | 0 | cmStrCat("PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX in ", |
1329 | 0 | "cmake_language(PRINT_PROPERTIES) call ", |
1330 | 0 | "are only valid with the TARGETS scope and ALL.")); |
1331 | 0 | } |
1332 | 0 | if (parsedArgsMode.Deferred) { |
1333 | 0 | return FatalError(status, |
1334 | 0 | cmStrCat("DEFERRED keyword in ", |
1335 | 0 | "cmake_language(PRINT_PROPERTIES) call ", |
1336 | 0 | "is only valid with the TARGETS scope.")); |
1337 | 0 | } |
1338 | 0 | if (parsedArgsMode.FollowDependencies) { |
1339 | 0 | return FatalError(status, |
1340 | 0 | cmStrCat("FOLLOW_DEPENDENCIES keyword in ", |
1341 | 0 | "cmake_language(PRINT_PROPERTIES) call ", |
1342 | 0 | "is only valid with the TARGETS scope.")); |
1343 | 0 | } |
1344 | 0 | if (!hasNamed) { |
1345 | 0 | return FatalError(status, |
1346 | 0 | cmStrCat("NAMED keyword missing in ", |
1347 | 0 | "cmake_language(PRINT_PROPERTIES) call ", |
1348 | 0 | "with ", mode, " scope.")); |
1349 | 0 | } |
1350 | 0 | } else { |
1351 | | // ALL and NAMED are mutually exclusive on TARGETS. |
1352 | 0 | if (parsedArgs.All && hasNamed) { |
1353 | 0 | return FatalError(status, |
1354 | 0 | cmStrCat("ALL and NAMED keywords in ", |
1355 | 0 | "cmake_language(PRINT_PROPERTIES) call ", |
1356 | 0 | "are mutually exclusive.")); |
1357 | 0 | } |
1358 | | // Regex filters require ALL - explicit or implicit. Combining regex |
1359 | | // with NAMED is an error. |
1360 | 0 | if (hasNamed && hasRegex) { |
1361 | 0 | return FatalError( |
1362 | 0 | status, |
1363 | 0 | cmStrCat("PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX in ", |
1364 | 0 | "cmake_language(PRINT_PROPERTIES) call ", |
1365 | 0 | "are only valid with ALL, not NAMED.")); |
1366 | 0 | } |
1367 | 0 | } |
1368 | | |
1369 | | // FOLLOW_DEPENDENCIES implies DEFERRED. |
1370 | 0 | if (parsedArgsMode.FollowDependencies) { |
1371 | 0 | parsedArgsMode.Deferred = true; |
1372 | 0 | } |
1373 | | |
1374 | | // Default to ALL when neither ALL nor NAMED is given (TARGETS only - the |
1375 | | // non-TARGETS path already rejected this combination above). |
1376 | 0 | bool const all = parsedArgs.All || !hasNamed; |
1377 | | |
1378 | | // __CMAKE_PRINT_PROPERTIES marks the deprecated cmake_print_properties() |
1379 | | // wrapper, which is always NAMED; reject it in ALL enumeration mode. |
1380 | 0 | if (all && parsedArgs.CmakePrintProperties) { |
1381 | 0 | return FatalError(status, |
1382 | 0 | cmStrCat("__CMAKE_PRINT_PROPERTIES in ", |
1383 | 0 | "cmake_language(PRINT_PROPERTIES) call ", |
1384 | 0 | "is only valid with NAMED.")); |
1385 | 0 | } |
1386 | | |
1387 | | // Compile regexes once up front so syntax errors are reported here rather |
1388 | | // than from inside helpers (in particular, from the deferred lambda at |
1389 | | // generate time). |
1390 | 0 | cm::optional<cmsys::RegularExpression> propertyNameRegex; |
1391 | 0 | cm::optional<cmsys::RegularExpression> propertyValueRegex; |
1392 | 0 | if (parsedArgs.PropertyNameRegex) { |
1393 | 0 | cmsys::RegularExpression re; |
1394 | 0 | if (!re.compile(*parsedArgs.PropertyNameRegex)) { |
1395 | 0 | return FatalError(status, |
1396 | 0 | cmStrCat("PROPERTY_NAME_REGEX regular expression \"", |
1397 | 0 | *parsedArgs.PropertyNameRegex, |
1398 | 0 | "\" cannot compile.")); |
1399 | 0 | } |
1400 | 0 | propertyNameRegex = std::move(re); |
1401 | 0 | } |
1402 | 0 | if (parsedArgs.PropertyValueRegex) { |
1403 | 0 | cmsys::RegularExpression re; |
1404 | 0 | if (!re.compile(*parsedArgs.PropertyValueRegex)) { |
1405 | 0 | return FatalError(status, |
1406 | 0 | cmStrCat("PROPERTY_VALUE_REGEX regular expression \"", |
1407 | 0 | *parsedArgs.PropertyValueRegex, |
1408 | 0 | "\" cannot compile.")); |
1409 | 0 | } |
1410 | 0 | propertyValueRegex = std::move(re); |
1411 | 0 | } |
1412 | | |
1413 | | // The message opens with a leading banner line. The internal |
1414 | | // __CMAKE_PRINT_PROPERTIES marker (not part of the public interface) selects |
1415 | | // the legacy format instead; cmake_print_properties() sets it to reproduce |
1416 | | // its historical leading blank line. |
1417 | 0 | std::string const messagePrefix = parsedArgs.CmakePrintProperties |
1418 | 0 | ? std::string("\n") |
1419 | 0 | : std::string("Printing properties...\n"); |
1420 | |
|
1421 | 0 | if (parsedArgsMode.Deferred) { |
1422 | 0 | return PrintTargetPropertiesDeferred( |
1423 | 0 | items, parsedArgs.Named, all, parsedArgs.PropertyNameRegex, |
1424 | 0 | parsedArgs.PropertyValueRegex, std::move(propertyNameRegex), |
1425 | 0 | std::move(propertyValueRegex), parsedArgsMode.FollowDependencies, |
1426 | 0 | messagePrefix, status); |
1427 | 0 | } |
1428 | | |
1429 | 0 | return PrintPropertiesConfigureTime( |
1430 | 0 | status, parsedArgs.Named, all, items, kind, parsedArgs.PropertyNameRegex, |
1431 | 0 | parsedArgs.PropertyValueRegex, propertyNameRegex, propertyValueRegex, |
1432 | 0 | messagePrefix); |
1433 | 0 | } |
1434 | | } |
1435 | | bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args, |
1436 | | cmExecutionStatus& status) |
1437 | 0 | { |
1438 | 0 | std::vector<std::string> expArgs; |
1439 | 0 | size_t rawArg = 0; |
1440 | 0 | size_t expArg = 0; |
1441 | | |
1442 | | // Helper to consume and expand one raw argument at a time. |
1443 | 0 | auto moreArgs = [&]() -> bool { |
1444 | 0 | while (expArg >= expArgs.size()) { |
1445 | 0 | if (rawArg >= args.size()) { |
1446 | 0 | return false; |
1447 | 0 | } |
1448 | 0 | std::vector<cmListFileArgument> tmpArg; |
1449 | 0 | tmpArg.emplace_back(args[rawArg++]); |
1450 | 0 | status.GetMakefile().ExpandArguments(tmpArg, expArgs); |
1451 | 0 | } |
1452 | 0 | return true; |
1453 | 0 | }; |
1454 | 0 | auto finishArgs = [&]() { |
1455 | 0 | std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end()); |
1456 | 0 | status.GetMakefile().ExpandArguments(tmpArgs, expArgs); |
1457 | 0 | rawArg = args.size(); |
1458 | 0 | }; |
1459 | |
|
1460 | 0 | if (!moreArgs()) { |
1461 | 0 | return FatalError(status, "called with incorrect number of arguments"); |
1462 | 0 | } |
1463 | 0 | if (expArgs[expArg] == "EXIT"_s) { |
1464 | 0 | ++expArg; // consume "EXIT". |
1465 | |
|
1466 | 0 | if (!moreArgs()) { |
1467 | 0 | return FatalError(status, "EXIT requires one argument"); |
1468 | 0 | } |
1469 | | |
1470 | 0 | if (!status.GetMakefile().GetCMakeInstance()->RoleSupportsExitCode()) { |
1471 | 0 | return FatalError(status, "EXIT can be used only in SCRIPT mode"); |
1472 | 0 | } |
1473 | | |
1474 | 0 | long retCode = 0; |
1475 | |
|
1476 | 0 | if (!cmStrToLong(expArgs[expArg], &retCode)) { |
1477 | 0 | return FatalError(status, |
1478 | 0 | cmStrCat("EXIT requires one integral argument, got \"", |
1479 | 0 | expArgs[expArg], '\"')); |
1480 | 0 | } |
1481 | | |
1482 | 0 | status.SetExitCode(static_cast<int>(retCode)); |
1483 | 0 | return true; |
1484 | 0 | } |
1485 | | |
1486 | 0 | if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) { |
1487 | 0 | finishArgs(); |
1488 | 0 | return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status); |
1489 | 0 | } |
1490 | | |
1491 | 0 | cm::optional<Defer> maybeDefer; |
1492 | 0 | if (expArgs[expArg] == "DEFER"_s) { |
1493 | 0 | ++expArg; // Consume "DEFER". |
1494 | |
|
1495 | 0 | if (!moreArgs()) { |
1496 | 0 | return FatalError(status, "DEFER requires at least one argument"); |
1497 | 0 | } |
1498 | | |
1499 | 0 | Defer defer; |
1500 | | |
1501 | | // Process optional arguments. |
1502 | 0 | while (moreArgs()) { |
1503 | 0 | if (expArgs[expArg] == "CALL"_s) { |
1504 | 0 | break; |
1505 | 0 | } |
1506 | 0 | if (expArgs[expArg] == "CANCEL_CALL"_s || |
1507 | 0 | expArgs[expArg] == "GET_CALL_IDS"_s || |
1508 | 0 | expArgs[expArg] == "GET_CALL"_s) { |
1509 | 0 | if (!defer.Id.empty() || !defer.IdVar.empty()) { |
1510 | 0 | return FatalError(status, |
1511 | 0 | cmStrCat("DEFER "_s, expArgs[expArg], |
1512 | 0 | " does not accept ID or ID_VAR."_s)); |
1513 | 0 | } |
1514 | 0 | finishArgs(); |
1515 | 0 | return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status); |
1516 | 0 | } |
1517 | 0 | if (expArgs[expArg] == "DIRECTORY"_s) { |
1518 | 0 | ++expArg; // Consume "DIRECTORY". |
1519 | 0 | if (defer.Directory) { |
1520 | 0 | return FatalError(status, |
1521 | 0 | "DEFER given multiple DIRECTORY arguments"); |
1522 | 0 | } |
1523 | 0 | if (!moreArgs()) { |
1524 | 0 | return FatalError(status, "DEFER DIRECTORY missing value"); |
1525 | 0 | } |
1526 | 0 | std::string dir = expArgs[expArg++]; |
1527 | 0 | if (dir.empty()) { |
1528 | 0 | return FatalError(status, "DEFER DIRECTORY may not be empty"); |
1529 | 0 | } |
1530 | 0 | dir = cmSystemTools::CollapseFullPath( |
1531 | 0 | dir, status.GetMakefile().GetCurrentSourceDirectory()); |
1532 | 0 | defer.Directory = |
1533 | 0 | status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir); |
1534 | 0 | if (!defer.Directory) { |
1535 | 0 | return FatalError(status, |
1536 | 0 | cmStrCat("DEFER DIRECTORY:\n "_s, dir, |
1537 | 0 | "\nis not known. " |
1538 | 0 | "It may not have been processed yet."_s)); |
1539 | 0 | } |
1540 | 0 | } else if (expArgs[expArg] == "ID"_s) { |
1541 | 0 | ++expArg; // Consume "ID". |
1542 | 0 | if (!defer.Id.empty()) { |
1543 | 0 | return FatalError(status, "DEFER given multiple ID arguments"); |
1544 | 0 | } |
1545 | 0 | if (!moreArgs()) { |
1546 | 0 | return FatalError(status, "DEFER ID missing value"); |
1547 | 0 | } |
1548 | 0 | defer.Id = expArgs[expArg++]; |
1549 | 0 | if (defer.Id.empty()) { |
1550 | 0 | return FatalError(status, "DEFER ID may not be empty"); |
1551 | 0 | } |
1552 | 0 | if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') { |
1553 | 0 | return FatalError(status, "DEFER ID may not start in A-Z."); |
1554 | 0 | } |
1555 | 0 | } else if (expArgs[expArg] == "ID_VAR"_s) { |
1556 | 0 | ++expArg; // Consume "ID_VAR". |
1557 | 0 | if (!defer.IdVar.empty()) { |
1558 | 0 | return FatalError(status, "DEFER given multiple ID_VAR arguments"); |
1559 | 0 | } |
1560 | 0 | if (!moreArgs()) { |
1561 | 0 | return FatalError(status, "DEFER ID_VAR missing variable name"); |
1562 | 0 | } |
1563 | 0 | defer.IdVar = expArgs[expArg++]; |
1564 | 0 | if (defer.IdVar.empty()) { |
1565 | 0 | return FatalError(status, "DEFER ID_VAR may not be empty"); |
1566 | 0 | } |
1567 | 0 | } else { |
1568 | 0 | return FatalError( |
1569 | 0 | status, cmStrCat("DEFER unknown option:\n "_s, expArgs[expArg])); |
1570 | 0 | } |
1571 | 0 | } |
1572 | | |
1573 | 0 | if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) { |
1574 | 0 | return FatalError(status, "DEFER must be followed by a CALL argument"); |
1575 | 0 | } |
1576 | | |
1577 | 0 | maybeDefer = std::move(defer); |
1578 | 0 | } |
1579 | | |
1580 | 0 | if (expArgs[expArg] == "CALL") { |
1581 | 0 | ++expArg; // Consume "CALL". |
1582 | | |
1583 | | // CALL requires a command name. |
1584 | 0 | if (!moreArgs()) { |
1585 | 0 | return FatalError(status, "CALL missing command name"); |
1586 | 0 | } |
1587 | 0 | std::string const& callCommand = expArgs[expArg++]; |
1588 | | |
1589 | | // CALL accepts no further expanded arguments. |
1590 | 0 | if (expArg != expArgs.size()) { |
1591 | 0 | return FatalError(status, "CALL command's arguments must be literal"); |
1592 | 0 | } |
1593 | | |
1594 | | // Run the CALL. |
1595 | 0 | return cmCMakeLanguageCommandCALL(args, callCommand, rawArg, |
1596 | 0 | std::move(maybeDefer), status); |
1597 | 0 | } |
1598 | | |
1599 | 0 | if (expArgs[expArg] == "EVAL") { |
1600 | 0 | return cmCMakeLanguageCommandEVAL(args, status); |
1601 | 0 | } |
1602 | | |
1603 | 0 | if (expArgs[expArg] == "GET_MESSAGE_LOG_LEVEL") { |
1604 | 0 | return cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(args, status); |
1605 | 0 | } |
1606 | | |
1607 | 0 | if (expArgs[expArg] == "GET_EXPERIMENTAL_FEATURE_ENABLED") { |
1608 | 0 | return cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(args, |
1609 | 0 | status); |
1610 | 0 | } |
1611 | | |
1612 | 0 | if (expArgs[expArg] == "PRINT_TARGETS") { |
1613 | 0 | return cmCMakeLanguageCommandPRINT_TARGETS(args, status); |
1614 | 0 | } |
1615 | | |
1616 | 0 | if (expArgs[expArg] == "PRINT_VARIABLES") { |
1617 | 0 | return cmCMakeLanguageCommandPRINT_VARIABLES(args, status); |
1618 | 0 | } |
1619 | | |
1620 | 0 | if (expArgs[expArg] == "TRACE") { |
1621 | 0 | ++expArg; // Consume "TRACE". |
1622 | |
|
1623 | 0 | if (!moreArgs()) { |
1624 | 0 | return FatalError(status, "TRACE missing a boolean value"); |
1625 | 0 | } |
1626 | | |
1627 | 0 | bool const value = cmValue::IsOn(expArgs[expArg++]); |
1628 | 0 | bool expand = false; |
1629 | |
|
1630 | 0 | if (value && moreArgs()) { |
1631 | 0 | expand = (expArgs[expArg] == "EXPAND"); |
1632 | 0 | if (!expand) { |
1633 | 0 | return FatalError( |
1634 | 0 | status, |
1635 | 0 | cmStrCat("TRACE ON given an invalid argument ", expArgs[expArg])); |
1636 | 0 | } |
1637 | 0 | ++expArg; |
1638 | 0 | } |
1639 | | |
1640 | 0 | if (moreArgs()) { |
1641 | 0 | return FatalError( |
1642 | 0 | status, |
1643 | 0 | cmStrCat("TRACE O", value ? "N" : "FF", " given too many arguments")); |
1644 | 0 | } |
1645 | | |
1646 | 0 | cmMakefile& makefile = status.GetMakefile(); |
1647 | 0 | if (value) { |
1648 | 0 | makefile.GetCMakeInstance()->PushTraceCmd(expand); |
1649 | 0 | return true; |
1650 | 0 | } |
1651 | 0 | return makefile.GetCMakeInstance()->PopTraceCmd() || |
1652 | 0 | FatalError(status, "TRACE OFF request without a corresponding TRACE ON"); |
1653 | 0 | } |
1654 | | |
1655 | 0 | if (expArgs[expArg] == "PRINT_PROPERTIES") { |
1656 | 0 | ++expArg; |
1657 | 0 | finishArgs(); |
1658 | 0 | std::vector<std::string> const printPropertyArgs(expArgs.begin() + expArg, |
1659 | 0 | expArgs.end()); |
1660 | 0 | return cmCMakeLanguageCommandPRINT_PROPERTIES(printPropertyArgs, status); |
1661 | 0 | } |
1662 | | |
1663 | 0 | return FatalError(status, "called with unknown meta-operation"); |
1664 | 0 | } |