1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmCMakePathCommand.h"
13 #include <cm/optional>
14 #include <cm/string_view>
15 #include <cmext/string_view>
17 #include "cmArgumentParser.h"
18 #include "cmArgumentParserTypes.h"
19 #include "cmCMakePath.h"
20 #include "cmExecutionStatus.h"
21 #include "cmMakefile.h"
23 #include "cmStringAlgorithms.h"
24 #include "cmSubcommandTable.h"
25 #include "cmSystemTools.h"
29 // Helper classes for argument parsing
30 template <typename Result>
31 class CMakePathArgumentParser : public cmArgumentParser<Result>
34 CMakePathArgumentParser()
35 : cmArgumentParser<Result>()
40 CMakePathArgumentParser& Bind(cm::static_string_view name, T Result::*member)
42 this->cmArgumentParser<Result>::Bind(name, member);
46 template <int Advance = 2>
47 Result Parse(std::vector<std::string> const& args) const
51 return this->cmArgumentParser<Result>::Parse(
52 cmMakeRange(args).advance(Advance), &this->Inputs);
55 const std::vector<std::string>& GetInputs() const { return this->Inputs; }
58 mutable std::vector<std::string> Inputs;
61 // OUTPUT_VARIABLE is expected
62 template <typename Result>
63 class ArgumentParserWithOutputVariable : public CMakePathArgumentParser<Result>
66 ArgumentParserWithOutputVariable()
67 : CMakePathArgumentParser<Result>()
69 this->Bind("OUTPUT_VARIABLE"_s, &Result::Output);
73 ArgumentParserWithOutputVariable& Bind(cm::static_string_view name,
76 this->cmArgumentParser<Result>::Bind(name, member);
80 template <int Advance = 2>
81 Result Parse(std::vector<std::string> const& args) const
83 return this->CMakePathArgumentParser<Result>::template Parse<Advance>(
88 struct OutputVariable : public ArgumentParser::ParseResult
90 cm::optional<ArgumentParser::NonEmpty<std::string>> Output;
92 // Usable when OUTPUT_VARIABLE is the only option
93 class OutputVariableParser
94 : public ArgumentParserWithOutputVariable<OutputVariable>
98 struct NormalizeOption
100 bool Normalize = false;
102 // Usable when NORMALIZE is the only option
103 class NormalizeParser : public CMakePathArgumentParser<NormalizeOption>
106 NormalizeParser() { this->Bind("NORMALIZE"_s, &NormalizeOption::Normalize); }
109 // retrieve value of input path from specified variable
110 bool getInputPath(const std::string& arg, cmExecutionStatus& status,
113 cmValue def = status.GetMakefile().GetDefinition(arg);
115 status.SetError("undefined variable for input path.");
123 bool HandleGetCommand(std::vector<std::string> const& args,
124 cmExecutionStatus& status)
126 static std::map<cm::string_view,
127 std::function<cmCMakePath(const cmCMakePath&, bool)>> const
128 actions{ { "ROOT_NAME"_s,
129 [](const cmCMakePath& path, bool) -> cmCMakePath {
130 return path.GetRootName();
132 { "ROOT_DIRECTORY"_s,
133 [](const cmCMakePath& path, bool) -> cmCMakePath {
134 return path.GetRootDirectory();
137 [](const cmCMakePath& path, bool) -> cmCMakePath {
138 return path.GetRootPath();
141 [](const cmCMakePath& path, bool) -> cmCMakePath {
142 return path.GetFileName();
145 [](const cmCMakePath& path, bool last_only) -> cmCMakePath {
147 return path.GetExtension();
149 return path.GetWideExtension();
152 [](const cmCMakePath& path, bool last_only) -> cmCMakePath {
154 return path.GetStem();
156 return path.GetNarrowStem();
159 [](const cmCMakePath& path, bool) -> cmCMakePath {
160 return path.GetRelativePath();
163 [](const cmCMakePath& path, bool) -> cmCMakePath {
164 return path.GetParentPath();
167 if (args.size() < 4) {
168 status.SetError("GET must be called with at least three arguments.");
172 const auto& action = args[2];
174 if (actions.find(action) == actions.end()) {
176 cmStrCat("GET called with an unknown action: ", action, "."));
182 bool LastOnly = false;
185 CMakePathArgumentParser<Arguments> parser;
186 if ((action == "EXTENSION"_s || action == "STEM"_s)) {
187 parser.Bind("LAST_ONLY"_s, &Arguments::LastOnly);
190 Arguments const arguments = parser.Parse<3>(args);
192 if (parser.GetInputs().size() != 1) {
193 status.SetError("GET called with unexpected arguments.");
196 if (parser.GetInputs().front().empty()) {
197 status.SetError("Invalid name for output variable.");
202 if (!getInputPath(args[1], status, path)) {
206 auto result = actions.at(action)(path, arguments.LastOnly);
208 status.GetMakefile().AddDefinition(parser.GetInputs().front(),
214 bool HandleSetCommand(std::vector<std::string> const& args,
215 cmExecutionStatus& status)
217 if (args.size() < 3 || args.size() > 4) {
218 status.SetError("SET must be called with two or three arguments.");
222 if (args[1].empty()) {
223 status.SetError("Invalid name for path variable.");
227 static NormalizeParser const parser;
229 const auto arguments = parser.Parse(args);
231 if (parser.GetInputs().size() != 1) {
232 status.SetError("SET called with unexpected arguments.");
237 cmCMakePath(parser.GetInputs().front(), cmCMakePath::native_format);
239 if (arguments.Normalize) {
240 path = path.Normal();
243 status.GetMakefile().AddDefinition(args[1], path.GenericString());
248 bool HandleAppendCommand(std::vector<std::string> const& args,
249 cmExecutionStatus& status)
251 if (args[1].empty()) {
252 status.SetError("Invalid name for path variable.");
256 static OutputVariableParser const parser{};
258 const auto arguments = parser.Parse(args);
260 if (arguments.MaybeReportError(status.GetMakefile())) {
264 cmCMakePath path(status.GetMakefile().GetSafeDefinition(args[1]));
265 for (const auto& input : parser.GetInputs()) {
269 status.GetMakefile().AddDefinition(
270 arguments.Output ? *arguments.Output : args[1], path.String());
275 bool HandleAppendStringCommand(std::vector<std::string> const& args,
276 cmExecutionStatus& status)
278 static OutputVariableParser const parser{};
280 const auto arguments = parser.Parse(args);
282 if (arguments.MaybeReportError(status.GetMakefile())) {
286 std::string inputPath;
287 if (!getInputPath(args[1], status, inputPath)) {
291 cmCMakePath path(inputPath);
292 for (const auto& input : parser.GetInputs()) {
296 status.GetMakefile().AddDefinition(
297 arguments.Output ? *arguments.Output : args[1], path.String());
302 bool HandleRemoveFilenameCommand(std::vector<std::string> const& args,
303 cmExecutionStatus& status)
305 static OutputVariableParser const parser{};
307 const auto arguments = parser.Parse(args);
309 if (arguments.MaybeReportError(status.GetMakefile())) {
313 if (!parser.GetInputs().empty()) {
314 status.SetError("REMOVE_FILENAME called with unexpected arguments.");
318 std::string inputPath;
319 if (!getInputPath(args[1], status, inputPath)) {
323 cmCMakePath path(inputPath);
324 path.RemoveFileName();
326 status.GetMakefile().AddDefinition(
327 arguments.Output ? *arguments.Output : args[1], path.String());
332 bool HandleReplaceFilenameCommand(std::vector<std::string> const& args,
333 cmExecutionStatus& status)
335 static OutputVariableParser const parser{};
337 const auto arguments = parser.Parse(args);
339 if (arguments.MaybeReportError(status.GetMakefile())) {
343 if (parser.GetInputs().size() > 1) {
344 status.SetError("REPLACE_FILENAME called with unexpected arguments.");
348 std::string inputPath;
349 if (!getInputPath(args[1], status, inputPath)) {
353 cmCMakePath path(inputPath);
354 path.ReplaceFileName(
355 parser.GetInputs().empty() ? "" : parser.GetInputs().front());
357 status.GetMakefile().AddDefinition(
358 arguments.Output ? *arguments.Output : args[1], path.String());
363 bool HandleRemoveExtensionCommand(std::vector<std::string> const& args,
364 cmExecutionStatus& status)
366 struct Arguments : public ArgumentParser::ParseResult
368 cm::optional<ArgumentParser::NonEmpty<std::string>> Output;
369 bool LastOnly = false;
372 static auto const parser =
373 ArgumentParserWithOutputVariable<Arguments>{}.Bind("LAST_ONLY"_s,
374 &Arguments::LastOnly);
376 Arguments const arguments = parser.Parse(args);
378 if (arguments.MaybeReportError(status.GetMakefile())) {
382 if (!parser.GetInputs().empty()) {
383 status.SetError("REMOVE_EXTENSION called with unexpected arguments.");
387 std::string inputPath;
388 if (!getInputPath(args[1], status, inputPath)) {
392 cmCMakePath path(inputPath);
394 if (arguments.LastOnly) {
395 path.RemoveExtension();
397 path.RemoveWideExtension();
400 status.GetMakefile().AddDefinition(
401 arguments.Output ? *arguments.Output : args[1], path.String());
406 bool HandleReplaceExtensionCommand(std::vector<std::string> const& args,
407 cmExecutionStatus& status)
409 struct Arguments : public ArgumentParser::ParseResult
411 cm::optional<ArgumentParser::NonEmpty<std::string>> Output;
412 bool LastOnly = false;
415 static auto const parser =
416 ArgumentParserWithOutputVariable<Arguments>{}.Bind("LAST_ONLY"_s,
417 &Arguments::LastOnly);
419 Arguments const arguments = parser.Parse(args);
421 if (arguments.MaybeReportError(status.GetMakefile())) {
425 if (parser.GetInputs().size() > 1) {
426 status.SetError("REPLACE_EXTENSION called with unexpected arguments.");
430 std::string inputPath;
431 if (!getInputPath(args[1], status, inputPath)) {
435 cmCMakePath path(inputPath);
436 cmCMakePath extension(
437 parser.GetInputs().empty() ? "" : parser.GetInputs().front());
439 if (arguments.LastOnly) {
440 path.ReplaceExtension(extension);
442 path.ReplaceWideExtension(extension);
445 status.GetMakefile().AddDefinition(
446 arguments.Output ? *arguments.Output : args[1], path.String());
451 bool HandleNormalPathCommand(std::vector<std::string> const& args,
452 cmExecutionStatus& status)
454 static OutputVariableParser const parser{};
456 const auto arguments = parser.Parse(args);
458 if (arguments.MaybeReportError(status.GetMakefile())) {
462 if (!parser.GetInputs().empty()) {
463 status.SetError("NORMAL_PATH called with unexpected arguments.");
467 std::string inputPath;
468 if (!getInputPath(args[1], status, inputPath)) {
472 auto path = cmCMakePath(inputPath).Normal();
474 status.GetMakefile().AddDefinition(
475 arguments.Output ? *arguments.Output : args[1], path.String());
480 bool HandleTransformPathCommand(
481 std::vector<std::string> const& args, cmExecutionStatus& status,
482 const std::function<cmCMakePath(const cmCMakePath&,
483 const std::string& base)>& transform,
484 bool normalizeOption = false)
486 struct Arguments : public ArgumentParser::ParseResult
488 cm::optional<ArgumentParser::NonEmpty<std::string>> Output;
489 cm::optional<std::string> BaseDirectory;
490 bool Normalize = false;
493 auto parser = ArgumentParserWithOutputVariable<Arguments>{}.Bind(
494 "BASE_DIRECTORY"_s, &Arguments::BaseDirectory);
495 if (normalizeOption) {
496 parser.Bind("NORMALIZE"_s, &Arguments::Normalize);
499 Arguments arguments = parser.Parse(args);
501 if (arguments.MaybeReportError(status.GetMakefile())) {
505 if (!parser.GetInputs().empty()) {
506 status.SetError(cmStrCat(args[0], " called with unexpected arguments."));
510 std::string baseDirectory;
511 if (arguments.BaseDirectory) {
512 baseDirectory = *arguments.BaseDirectory;
514 baseDirectory = status.GetMakefile().GetCurrentSourceDirectory();
517 std::string inputPath;
518 if (!getInputPath(args[1], status, inputPath)) {
522 auto path = transform(cmCMakePath(inputPath), baseDirectory);
523 if (arguments.Normalize) {
524 path = path.Normal();
527 status.GetMakefile().AddDefinition(
528 arguments.Output ? *arguments.Output : args[1], path.String());
533 bool HandleRelativePathCommand(std::vector<std::string> const& args,
534 cmExecutionStatus& status)
536 return HandleTransformPathCommand(
538 [](const cmCMakePath& path, const std::string& base) -> cmCMakePath {
539 return path.Relative(base);
543 bool HandleAbsolutePathCommand(std::vector<std::string> const& args,
544 cmExecutionStatus& status)
546 return HandleTransformPathCommand(
548 [](const cmCMakePath& path, const std::string& base) -> cmCMakePath {
549 return path.Absolute(base);
554 bool HandleNativePathCommand(std::vector<std::string> const& args,
555 cmExecutionStatus& status)
557 if (args.size() < 3 || args.size() > 4) {
558 status.SetError("NATIVE_PATH must be called with two or three arguments.");
562 static NormalizeParser const parser;
564 const auto arguments = parser.Parse(args);
566 if (parser.GetInputs().size() != 1) {
567 status.SetError("NATIVE_PATH called with unexpected arguments.");
570 if (parser.GetInputs().front().empty()) {
571 status.SetError("Invalid name for output variable.");
575 std::string inputPath;
576 if (!getInputPath(args[1], status, inputPath)) {
580 cmCMakePath path(inputPath);
581 if (arguments.Normalize) {
582 path = path.Normal();
585 status.GetMakefile().AddDefinition(parser.GetInputs().front(),
586 path.NativeString());
591 bool HandleConvertCommand(std::vector<std::string> const& args,
592 cmExecutionStatus& status)
594 #if defined(_WIN32) && !defined(__CYGWIN__)
595 const auto pathSep = ";"_s;
597 const auto pathSep = ":"_s;
599 const auto cmakePath = "TO_CMAKE_PATH_LIST"_s;
600 const auto nativePath = "TO_NATIVE_PATH_LIST"_s;
602 if (args.size() < 4 || args.size() > 5) {
603 status.SetError("CONVERT must be called with three or four arguments.");
607 const auto& action = args[2];
609 if (action != cmakePath && action != nativePath) {
611 cmStrCat("CONVERT called with an unknown action: ", action, "."));
615 if (args[3].empty()) {
616 status.SetError("Invalid name for output variable.");
620 static NormalizeParser const parser;
622 const auto arguments = parser.Parse<4>(args);
624 if (!parser.GetInputs().empty()) {
625 status.SetError("CONVERT called with unexpected arguments.");
629 std::vector<std::string> paths;
631 if (action == cmakePath) {
632 paths = cmSystemTools::SplitString(args[1], pathSep.front());
634 cmExpandList(args[1], paths);
637 for (auto& path : paths) {
638 auto p = cmCMakePath(path,
639 action == cmakePath ? cmCMakePath::native_format
640 : cmCMakePath::generic_format);
641 if (arguments.Normalize) {
644 if (action == cmakePath) {
645 path = p.GenericString();
647 path = p.NativeString();
651 auto value = cmJoin(paths, action == cmakePath ? ";"_s : pathSep);
652 status.GetMakefile().AddDefinition(args[3], value);
657 bool HandleCompareCommand(std::vector<std::string> const& args,
658 cmExecutionStatus& status)
660 if (args.size() != 5) {
661 status.SetError("COMPARE must be called with four arguments.");
665 static std::map<cm::string_view,
666 std::function<bool(const cmCMakePath&,
667 const cmCMakePath&)>> const operators{
669 [](const cmCMakePath& path1, const cmCMakePath& path2) -> bool {
670 return path1 == path2;
673 [](const cmCMakePath& path1, const cmCMakePath& path2) -> bool {
674 return path1 != path2;
678 const auto op = operators.find(args[2]);
679 if (op == operators.end()) {
680 status.SetError(cmStrCat(
681 "COMPARE called with an unknown comparison operator: ", args[2], "."));
685 if (args[4].empty()) {
686 status.SetError("Invalid name for output variable.");
690 cmCMakePath path1(args[1]);
691 cmCMakePath path2(args[3]);
692 auto result = op->second(path1, path2);
694 status.GetMakefile().AddDefinitionBool(args[4], result);
699 bool HandleHasItemCommand(
700 std::vector<std::string> const& args, cmExecutionStatus& status,
701 const std::function<bool(const cmCMakePath&)>& has_item)
703 if (args.size() != 3) {
705 cmStrCat(args.front(), " must be called with two arguments."));
709 std::string inputPath;
710 if (!getInputPath(args[1], status, inputPath)) {
714 if (args[2].empty()) {
715 status.SetError("Invalid name for output variable.");
719 cmCMakePath path(inputPath);
720 auto result = has_item(path);
722 status.GetMakefile().AddDefinitionBool(args[2], result);
727 bool HandleHasRootNameCommand(std::vector<std::string> const& args,
728 cmExecutionStatus& status)
730 return HandleHasItemCommand(
732 [](const cmCMakePath& path) -> bool { return path.HasRootName(); });
735 bool HandleHasRootDirectoryCommand(std::vector<std::string> const& args,
736 cmExecutionStatus& status)
738 return HandleHasItemCommand(
740 [](const cmCMakePath& path) -> bool { return path.HasRootDirectory(); });
743 bool HandleHasRootPathCommand(std::vector<std::string> const& args,
744 cmExecutionStatus& status)
746 return HandleHasItemCommand(
748 [](const cmCMakePath& path) -> bool { return path.HasRootPath(); });
751 bool HandleHasFilenameCommand(std::vector<std::string> const& args,
752 cmExecutionStatus& status)
754 return HandleHasItemCommand(
756 [](const cmCMakePath& path) -> bool { return path.HasFileName(); });
759 bool HandleHasExtensionCommand(std::vector<std::string> const& args,
760 cmExecutionStatus& status)
762 return HandleHasItemCommand(
764 [](const cmCMakePath& path) -> bool { return path.HasExtension(); });
767 bool HandleHasStemCommand(std::vector<std::string> const& args,
768 cmExecutionStatus& status)
770 return HandleHasItemCommand(
772 [](const cmCMakePath& path) -> bool { return path.HasStem(); });
775 bool HandleHasRelativePartCommand(std::vector<std::string> const& args,
776 cmExecutionStatus& status)
778 return HandleHasItemCommand(
780 [](const cmCMakePath& path) -> bool { return path.HasRelativePath(); });
783 bool HandleHasParentPathCommand(std::vector<std::string> const& args,
784 cmExecutionStatus& status)
786 return HandleHasItemCommand(
788 [](const cmCMakePath& path) -> bool { return path.HasParentPath(); });
791 bool HandleIsAbsoluteCommand(std::vector<std::string> const& args,
792 cmExecutionStatus& status)
794 if (args.size() != 3) {
795 status.SetError("IS_ABSOLUTE must be called with two arguments.");
799 std::string inputPath;
800 if (!getInputPath(args[1], status, inputPath)) {
804 if (args[2].empty()) {
805 status.SetError("Invalid name for output variable.");
809 bool isAbsolute = cmCMakePath(inputPath).IsAbsolute();
811 status.GetMakefile().AddDefinitionBool(args[2], isAbsolute);
816 bool HandleIsRelativeCommand(std::vector<std::string> const& args,
817 cmExecutionStatus& status)
819 if (args.size() != 3) {
820 status.SetError("IS_RELATIVE must be called with two arguments.");
824 std::string inputPath;
825 if (!getInputPath(args[1], status, inputPath)) {
829 if (args[2].empty()) {
830 status.SetError("Invalid name for output variable.");
834 bool isRelative = cmCMakePath(inputPath).IsRelative();
836 status.GetMakefile().AddDefinitionBool(args[2], isRelative);
841 bool HandleIsPrefixCommand(std::vector<std::string> const& args,
842 cmExecutionStatus& status)
844 if (args.size() < 4 || args.size() > 5) {
845 status.SetError("IS_PREFIX must be called with three or four arguments.");
849 static NormalizeParser const parser;
851 const auto arguments = parser.Parse(args);
853 if (parser.GetInputs().size() != 2) {
854 status.SetError("IS_PREFIX called with unexpected arguments.");
858 std::string inputPath;
859 if (!getInputPath(args[1], status, inputPath)) {
863 const auto& input = parser.GetInputs().front();
864 const auto& output = parser.GetInputs().back();
866 if (output.empty()) {
867 status.SetError("Invalid name for output variable.");
872 if (arguments.Normalize) {
874 cmCMakePath(inputPath).Normal().IsPrefix(cmCMakePath(input).Normal());
876 isPrefix = cmCMakePath(inputPath).IsPrefix(input);
879 status.GetMakefile().AddDefinitionBool(output, isPrefix);
884 bool HandleHashCommand(std::vector<std::string> const& args,
885 cmExecutionStatus& status)
887 if (args.size() != 3) {
888 status.SetError("HASH must be called with two arguments.");
892 std::string inputPath;
893 if (!getInputPath(args[1], status, inputPath)) {
897 const auto& output = args[2];
899 if (output.empty()) {
900 status.SetError("Invalid name for output variable.");
904 auto hash = hash_value(cmCMakePath(inputPath).Normal());
906 std::ostringstream out;
907 out << std::setbase(16) << hash;
909 status.GetMakefile().AddDefinition(output, out.str());
913 } // anonymous namespace
915 bool cmCMakePathCommand(std::vector<std::string> const& args,
916 cmExecutionStatus& status)
918 if (args.size() < 2) {
919 status.SetError("must be called with at least two arguments.");
923 static cmSubcommandTable const subcommand{
924 { "GET"_s, HandleGetCommand },
925 { "SET"_s, HandleSetCommand },
926 { "APPEND"_s, HandleAppendCommand },
927 { "APPEND_STRING"_s, HandleAppendStringCommand },
928 { "REMOVE_FILENAME"_s, HandleRemoveFilenameCommand },
929 { "REPLACE_FILENAME"_s, HandleReplaceFilenameCommand },
930 { "REMOVE_EXTENSION"_s, HandleRemoveExtensionCommand },
931 { "REPLACE_EXTENSION"_s, HandleReplaceExtensionCommand },
932 { "NORMAL_PATH"_s, HandleNormalPathCommand },
933 { "RELATIVE_PATH"_s, HandleRelativePathCommand },
934 { "ABSOLUTE_PATH"_s, HandleAbsolutePathCommand },
935 { "NATIVE_PATH"_s, HandleNativePathCommand },
936 { "CONVERT"_s, HandleConvertCommand },
937 { "COMPARE"_s, HandleCompareCommand },
938 { "HAS_ROOT_NAME"_s, HandleHasRootNameCommand },
939 { "HAS_ROOT_DIRECTORY"_s, HandleHasRootDirectoryCommand },
940 { "HAS_ROOT_PATH"_s, HandleHasRootPathCommand },
941 { "HAS_FILENAME"_s, HandleHasFilenameCommand },
942 { "HAS_EXTENSION"_s, HandleHasExtensionCommand },
943 { "HAS_STEM"_s, HandleHasStemCommand },
944 { "HAS_RELATIVE_PART"_s, HandleHasRelativePartCommand },
945 { "HAS_PARENT_PATH"_s, HandleHasParentPathCommand },
946 { "IS_ABSOLUTE"_s, HandleIsAbsoluteCommand },
947 { "IS_RELATIVE"_s, HandleIsRelativeCommand },
948 { "IS_PREFIX"_s, HandleIsPrefixCommand },
949 { "HASH"_s, HandleHashCommand }
952 return subcommand(args[0], args, status);