/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCMakePathCommand.h" #include #include #include #include #include #include #include #include #include #include #include "cmArgumentParser.h" #include "cmCMakePath.h" #include "cmExecutionStatus.h" #include "cmMakefile.h" #include "cmRange.h" #include "cmStringAlgorithms.h" #include "cmSubcommandTable.h" #include "cmSystemTools.h" namespace { // Helper classes for argument parsing template class CMakePathArgumentParser : public cmArgumentParser { public: CMakePathArgumentParser() : cmArgumentParser() { } template CMakePathArgumentParser& Bind(cm::static_string_view name, T Result::*member) { this->cmArgumentParser::Bind(name, member); return *this; } template Result Parse(std::vector const& args, std::vector* keywordsMissingValue = nullptr, std::vector* parsedKeywords = nullptr) const { this->Inputs.clear(); return this->cmArgumentParser::Parse( cmMakeRange(args).advance(Advance), &this->Inputs, keywordsMissingValue, parsedKeywords); } const std::vector& GetInputs() const { return this->Inputs; } protected: mutable std::vector Inputs; }; // OUTPUT_VARIABLE is expected template class ArgumentParserWithOutputVariable : public CMakePathArgumentParser { public: ArgumentParserWithOutputVariable() : CMakePathArgumentParser() { this->Bind("OUTPUT_VARIABLE"_s, &Result::Output); } template ArgumentParserWithOutputVariable& Bind(cm::static_string_view name, T Result::*member) { this->cmArgumentParser::Bind(name, member); return *this; } template Result Parse(std::vector const& args) const { this->KeywordsMissingValue.clear(); this->ParsedKeywords.clear(); return this->CMakePathArgumentParser::template Parse( args, &this->KeywordsMissingValue, &this->ParsedKeywords); } const std::vector& GetKeywordsMissingValue() const { return this->KeywordsMissingValue; } const std::vector& GetParsedKeywords() const { return this->ParsedKeywords; } bool checkOutputVariable(const Result& arguments, cmExecutionStatus& status) const { if (std::find(this->GetKeywordsMissingValue().begin(), this->GetKeywordsMissingValue().end(), "OUTPUT_VARIABLE"_s) != this->GetKeywordsMissingValue().end()) { status.SetError("OUTPUT_VARIABLE requires an argument."); return false; } if (std::find(this->GetParsedKeywords().begin(), this->GetParsedKeywords().end(), "OUTPUT_VARIABLE"_s) != this->GetParsedKeywords().end() && arguments.Output.empty()) { status.SetError("Invalid name for output variable."); return false; } return true; } private: mutable std::vector KeywordsMissingValue; mutable std::vector ParsedKeywords; }; struct OutputVariable { std::string Output; }; // Usable when OUTPUT_VARIABLE is the only option class OutputVariableParser : public ArgumentParserWithOutputVariable { }; struct NormalizeOption { bool Normalize = false; }; // Usable when NORMALIZE is the only option class NormalizeParser : public CMakePathArgumentParser { public: NormalizeParser() { this->Bind("NORMALIZE"_s, &NormalizeOption::Normalize); } }; // retrieve value of input path from specified variable bool getInputPath(const std::string& arg, cmExecutionStatus& status, std::string& path) { auto def = status.GetMakefile().GetDefinition(arg); if (def == nullptr) { status.SetError("undefined variable for input path."); return false; } path = *def; return true; } bool HandleGetCommand(std::vector const& args, cmExecutionStatus& status) { static std::map> const actions{ { "ROOT_NAME"_s, [](const cmCMakePath& path, bool) -> cmCMakePath { return path.GetRootName(); } }, { "ROOT_DIRECTORY"_s, [](const cmCMakePath& path, bool) -> cmCMakePath { return path.GetRootDirectory(); } }, { "ROOT_PATH"_s, [](const cmCMakePath& path, bool) -> cmCMakePath { return path.GetRootPath(); } }, { "FILENAME"_s, [](const cmCMakePath& path, bool) -> cmCMakePath { return path.GetFileName(); } }, { "EXTENSION"_s, [](const cmCMakePath& path, bool last_only) -> cmCMakePath { if (last_only) { return path.GetExtension(); } return path.GetWideExtension(); } }, { "STEM"_s, [](const cmCMakePath& path, bool last_only) -> cmCMakePath { if (last_only) { return path.GetStem(); } return path.GetNarrowStem(); } }, { "RELATIVE_PATH"_s, [](const cmCMakePath& path, bool) -> cmCMakePath { return path.GetRelativePath(); } }, { "PARENT_PATH"_s, [](const cmCMakePath& path, bool) -> cmCMakePath { return path.GetParentPath(); } } }; if (args.size() < 4) { status.SetError("GET must be called with at least three arguments."); return false; } const auto& action = args[2]; if (actions.find(action) == actions.end()) { status.SetError( cmStrCat("GET called with an unknown action: ", action, ".")); return false; } struct Arguments { bool LastOnly = false; }; CMakePathArgumentParser parser; if ((action == "EXTENSION"_s || action == "STEM"_s)) { parser.Bind("LAST_ONLY"_s, &Arguments::LastOnly); } Arguments const arguments = parser.Parse<3>(args); if (parser.GetInputs().size() != 1) { status.SetError("GET called with unexpected arguments."); return false; } if (parser.GetInputs().front().empty()) { status.SetError("Invalid name for output variable."); return false; } std::string path; if (!getInputPath(args[1], status, path)) { return false; } auto result = actions.at(action)(path, arguments.LastOnly); status.GetMakefile().AddDefinition(parser.GetInputs().front(), result.String()); return true; } bool HandleSetCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() < 3 || args.size() > 4) { status.SetError("SET must be called with two or three arguments."); return false; } if (args[1].empty()) { status.SetError("Invalid name for path variable."); return false; } static NormalizeParser const parser; const auto arguments = parser.Parse(args); if (parser.GetInputs().size() != 1) { status.SetError("SET called with unexpected arguments."); return false; } auto path = cmCMakePath(parser.GetInputs().front(), cmCMakePath::native_format); if (arguments.Normalize) { path = path.Normal(); } status.GetMakefile().AddDefinition(args[1], path.GenericString()); return true; } bool HandleAppendCommand(std::vector const& args, cmExecutionStatus& status) { if (args[1].empty()) { status.SetError("Invalid name for path variable."); return false; } static OutputVariableParser const parser{}; const auto arguments = parser.Parse(args); if (!parser.checkOutputVariable(arguments, status)) { return false; } cmCMakePath path(status.GetMakefile().GetSafeDefinition(args[1])); for (const auto& input : parser.GetInputs()) { path /= input; } status.GetMakefile().AddDefinition( arguments.Output.empty() ? args[1] : arguments.Output, path.String()); return true; } bool HandleAppendStringCommand(std::vector const& args, cmExecutionStatus& status) { static OutputVariableParser const parser{}; const auto arguments = parser.Parse(args); if (!parser.checkOutputVariable(arguments, status)) { return false; } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } cmCMakePath path(inputPath); for (const auto& input : parser.GetInputs()) { path += input; } status.GetMakefile().AddDefinition( arguments.Output.empty() ? args[1] : arguments.Output, path.String()); return true; } bool HandleRemoveFilenameCommand(std::vector const& args, cmExecutionStatus& status) { static OutputVariableParser const parser{}; const auto arguments = parser.Parse(args); if (!parser.checkOutputVariable(arguments, status)) { return false; } if (!parser.GetInputs().empty()) { status.SetError("REMOVE_FILENAME called with unexpected arguments."); return false; } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } cmCMakePath path(inputPath); path.RemoveFileName(); status.GetMakefile().AddDefinition( arguments.Output.empty() ? args[1] : arguments.Output, path.String()); return true; } bool HandleReplaceFilenameCommand(std::vector const& args, cmExecutionStatus& status) { static OutputVariableParser const parser{}; const auto arguments = parser.Parse(args); if (!parser.checkOutputVariable(arguments, status)) { return false; } if (parser.GetInputs().size() > 1) { status.SetError("REPLACE_FILENAME called with unexpected arguments."); return false; } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } cmCMakePath path(inputPath); path.ReplaceFileName( parser.GetInputs().empty() ? "" : parser.GetInputs().front()); status.GetMakefile().AddDefinition( arguments.Output.empty() ? args[1] : arguments.Output, path.String()); return true; } bool HandleRemoveExtensionCommand(std::vector const& args, cmExecutionStatus& status) { struct Arguments { std::string Output; bool LastOnly = false; }; static auto const parser = ArgumentParserWithOutputVariable{}.Bind("LAST_ONLY"_s, &Arguments::LastOnly); Arguments const arguments = parser.Parse(args); if (!parser.checkOutputVariable(arguments, status)) { return false; } if (!parser.GetInputs().empty()) { status.SetError("REMOVE_EXTENSION called with unexpected arguments."); return false; } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } cmCMakePath path(inputPath); if (arguments.LastOnly) { path.RemoveExtension(); } else { path.RemoveWideExtension(); } status.GetMakefile().AddDefinition( arguments.Output.empty() ? args[1] : arguments.Output, path.String()); return true; } bool HandleReplaceExtensionCommand(std::vector const& args, cmExecutionStatus& status) { struct Arguments { std::string Output; bool LastOnly = false; }; static auto const parser = ArgumentParserWithOutputVariable{}.Bind("LAST_ONLY"_s, &Arguments::LastOnly); Arguments const arguments = parser.Parse(args); if (!parser.checkOutputVariable(arguments, status)) { return false; } if (parser.GetInputs().size() > 1) { status.SetError("REPLACE_EXTENSION called with unexpected arguments."); return false; } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } cmCMakePath path(inputPath); cmCMakePath extension( parser.GetInputs().empty() ? "" : parser.GetInputs().front()); if (arguments.LastOnly) { path.ReplaceExtension(extension); } else { path.ReplaceWideExtension(extension); } status.GetMakefile().AddDefinition( arguments.Output.empty() ? args[1] : arguments.Output, path.String()); return true; } bool HandleNormalPathCommand(std::vector const& args, cmExecutionStatus& status) { static OutputVariableParser const parser{}; const auto arguments = parser.Parse(args); if (!parser.checkOutputVariable(arguments, status)) { return false; } if (!parser.GetInputs().empty()) { status.SetError("NORMAL_PATH called with unexpected arguments."); return false; } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } auto path = cmCMakePath(inputPath).Normal(); status.GetMakefile().AddDefinition( arguments.Output.empty() ? args[1] : arguments.Output, path.String()); return true; } bool HandleTransformPathCommand( std::vector const& args, cmExecutionStatus& status, const std::function& transform, bool normalizeOption = false) { struct Arguments { std::string Output; std::string BaseDirectory; bool Normalize = false; }; auto parser = ArgumentParserWithOutputVariable{}.Bind( "BASE_DIRECTORY"_s, &Arguments::BaseDirectory); if (normalizeOption) { parser.Bind("NORMALIZE"_s, &Arguments::Normalize); } Arguments arguments = parser.Parse(args); if (!parser.checkOutputVariable(arguments, status)) { return false; } if (!parser.GetInputs().empty()) { status.SetError(cmStrCat(args[0], " called with unexpected arguments.")); return false; } if (std::find(parser.GetKeywordsMissingValue().begin(), parser.GetKeywordsMissingValue().end(), "BASE_DIRECTORY"_s) != parser.GetKeywordsMissingValue().end()) { status.SetError("BASE_DIRECTORY requires an argument."); return false; } if (std::find(parser.GetParsedKeywords().begin(), parser.GetParsedKeywords().end(), "BASE_DIRECTORY"_s) == parser.GetParsedKeywords().end()) { arguments.BaseDirectory = status.GetMakefile().GetCurrentSourceDirectory(); } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } auto path = transform(cmCMakePath(inputPath), arguments.BaseDirectory); if (arguments.Normalize) { path = path.Normal(); } status.GetMakefile().AddDefinition( arguments.Output.empty() ? args[1] : arguments.Output, path.String()); return true; } bool HandleRelativePathCommand(std::vector const& args, cmExecutionStatus& status) { return HandleTransformPathCommand( args, status, [](const cmCMakePath& path, const std::string& base) -> cmCMakePath { return path.Relative(base); }); } bool HandleAbsolutePathCommand(std::vector const& args, cmExecutionStatus& status) { return HandleTransformPathCommand( args, status, [](const cmCMakePath& path, const std::string& base) -> cmCMakePath { return path.Absolute(base); }, true); } bool HandleNativePathCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() < 3 || args.size() > 4) { status.SetError("NATIVE_PATH must be called with two or three arguments."); return false; } static NormalizeParser const parser; const auto arguments = parser.Parse(args); if (parser.GetInputs().size() != 1) { status.SetError("NATIVE_PATH called with unexpected arguments."); return false; } if (parser.GetInputs().front().empty()) { status.SetError("Invalid name for output variable."); return false; } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } cmCMakePath path(inputPath); if (arguments.Normalize) { path = path.Normal(); } status.GetMakefile().AddDefinition(parser.GetInputs().front(), path.NativeString()); return true; } bool HandleConvertCommand(std::vector const& args, cmExecutionStatus& status) { #if defined(_WIN32) && !defined(__CYGWIN__) const auto pathSep = ";"_s; #else const auto pathSep = ":"_s; #endif const auto cmakePath = "TO_CMAKE_PATH_LIST"_s; const auto nativePath = "TO_NATIVE_PATH_LIST"_s; if (args.size() < 4 || args.size() > 5) { status.SetError("CONVERT must be called with three or four arguments."); return false; } const auto& action = args[2]; if (action != cmakePath && action != nativePath) { status.SetError( cmStrCat("CONVERT called with an unknown action: ", action, ".")); return false; } if (args[3].empty()) { status.SetError("Invalid name for output variable."); return false; } static NormalizeParser const parser; const auto arguments = parser.Parse<4>(args); if (!parser.GetInputs().empty()) { status.SetError("CONVERT called with unexpected arguments."); return false; } std::vector paths; if (action == cmakePath) { paths = cmSystemTools::SplitString(args[1], pathSep.front()); } else { cmExpandList(args[1], paths); } for (auto& path : paths) { auto p = cmCMakePath(path, action == cmakePath ? cmCMakePath::native_format : cmCMakePath::generic_format); if (arguments.Normalize) { p = p.Normal(); } if (action == cmakePath) { path = p.GenericString(); } else { path = p.NativeString(); } } auto value = cmJoin(paths, action == cmakePath ? ";"_s : pathSep); status.GetMakefile().AddDefinition(args[3], value); return true; } bool HandleCompareCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() != 5) { status.SetError("COMPARE must be called with four arguments."); return false; } static std::map> const operators{ { "EQUAL"_s, [](const cmCMakePath& path1, const cmCMakePath& path2) -> bool { return path1 == path2; } }, { "NOT_EQUAL"_s, [](const cmCMakePath& path1, const cmCMakePath& path2) -> bool { return path1 != path2; } } }; const auto op = operators.find(args[2]); if (op == operators.end()) { status.SetError(cmStrCat( "COMPARE called with an unknown comparison operator: ", args[2], ".")); return false; } if (args[4].empty()) { status.SetError("Invalid name for output variable."); return false; } cmCMakePath path1(args[1]); cmCMakePath path2(args[3]); auto result = op->second(path1, path2); status.GetMakefile().AddDefinitionBool(args[4], result); return true; } bool HandleHasItemCommand( std::vector const& args, cmExecutionStatus& status, const std::function& has_item) { if (args.size() != 3) { status.SetError( cmStrCat(args.front(), " must be called with two arguments.")); return false; } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } if (args[2].empty()) { status.SetError("Invalid name for output variable."); return false; } cmCMakePath path(inputPath); auto result = has_item(path); status.GetMakefile().AddDefinitionBool(args[2], result); return true; } bool HandleHasRootNameCommand(std::vector const& args, cmExecutionStatus& status) { return HandleHasItemCommand( args, status, [](const cmCMakePath& path) -> bool { return path.HasRootName(); }); } bool HandleHasRootDirectoryCommand(std::vector const& args, cmExecutionStatus& status) { return HandleHasItemCommand( args, status, [](const cmCMakePath& path) -> bool { return path.HasRootDirectory(); }); } bool HandleHasRootPathCommand(std::vector const& args, cmExecutionStatus& status) { return HandleHasItemCommand( args, status, [](const cmCMakePath& path) -> bool { return path.HasRootPath(); }); } bool HandleHasFilenameCommand(std::vector const& args, cmExecutionStatus& status) { return HandleHasItemCommand( args, status, [](const cmCMakePath& path) -> bool { return path.HasFileName(); }); } bool HandleHasExtensionCommand(std::vector const& args, cmExecutionStatus& status) { return HandleHasItemCommand( args, status, [](const cmCMakePath& path) -> bool { return path.HasExtension(); }); } bool HandleHasStemCommand(std::vector const& args, cmExecutionStatus& status) { return HandleHasItemCommand( args, status, [](const cmCMakePath& path) -> bool { return path.HasStem(); }); } bool HandleHasRelativePathCommand(std::vector const& args, cmExecutionStatus& status) { return HandleHasItemCommand( args, status, [](const cmCMakePath& path) -> bool { return path.HasRelativePath(); }); } bool HandleHasParentPathCommand(std::vector const& args, cmExecutionStatus& status) { return HandleHasItemCommand( args, status, [](const cmCMakePath& path) -> bool { return path.HasParentPath(); }); } bool HandleIsAbsoluteCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() != 3) { status.SetError("IS_ABSOLUTE must be called with two arguments."); return false; } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } if (args[2].empty()) { status.SetError("Invalid name for output variable."); return false; } bool isAbsolute = cmCMakePath(inputPath).IsAbsolute(); status.GetMakefile().AddDefinitionBool(args[2], isAbsolute); return true; } bool HandleIsRelativeCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() != 3) { status.SetError("IS_RELATIVE must be called with two arguments."); return false; } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } if (args[2].empty()) { status.SetError("Invalid name for output variable."); return false; } bool isRelative = cmCMakePath(inputPath).IsRelative(); status.GetMakefile().AddDefinitionBool(args[2], isRelative); return true; } bool HandleIsPrefixCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() < 4 || args.size() > 5) { status.SetError("IS_PREFIX must be called with three or four arguments."); return false; } static NormalizeParser const parser; const auto arguments = parser.Parse(args); if (parser.GetInputs().size() != 2) { status.SetError("IS_PREFIX called with unexpected arguments."); return false; } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } const auto& input = parser.GetInputs().front(); const auto& output = parser.GetInputs().back(); if (output.empty()) { status.SetError("Invalid name for output variable."); return false; } bool isPrefix; if (arguments.Normalize) { isPrefix = cmCMakePath(inputPath).Normal().IsPrefix(cmCMakePath(input).Normal()); } else { isPrefix = cmCMakePath(inputPath).IsPrefix(input); } status.GetMakefile().AddDefinitionBool(output, isPrefix); return true; } bool HandleHashCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() != 3) { status.SetError("HASH must be called with two arguments."); return false; } std::string inputPath; if (!getInputPath(args[1], status, inputPath)) { return false; } const auto& output = args[2]; if (output.empty()) { status.SetError("Invalid name for output variable."); return false; } auto hash = hash_value(cmCMakePath(inputPath).Normal()); std::ostringstream out; out << std::setbase(16) << hash; status.GetMakefile().AddDefinition(output, out.str()); return true; } } // anonymous namespace bool cmCMakePathCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() < 2) { status.SetError("must be called with at least two arguments."); return false; } static cmSubcommandTable const subcommand{ { "GET"_s, HandleGetCommand }, { "SET"_s, HandleSetCommand }, { "APPEND"_s, HandleAppendCommand }, { "APPEND_STRING"_s, HandleAppendStringCommand }, { "REMOVE_FILENAME"_s, HandleRemoveFilenameCommand }, { "REPLACE_FILENAME"_s, HandleReplaceFilenameCommand }, { "REMOVE_EXTENSION"_s, HandleRemoveExtensionCommand }, { "REPLACE_EXTENSION"_s, HandleReplaceExtensionCommand }, { "NORMAL_PATH"_s, HandleNormalPathCommand }, { "RELATIVE_PATH"_s, HandleRelativePathCommand }, { "ABSOLUTE_PATH"_s, HandleAbsolutePathCommand }, { "NATIVE_PATH"_s, HandleNativePathCommand }, { "CONVERT"_s, HandleConvertCommand }, { "COMPARE"_s, HandleCompareCommand }, { "HAS_ROOT_NAME"_s, HandleHasRootNameCommand }, { "HAS_ROOT_DIRECTORY"_s, HandleHasRootDirectoryCommand }, { "HAS_ROOT_PATH"_s, HandleHasRootPathCommand }, { "HAS_FILENAME"_s, HandleHasFilenameCommand }, { "HAS_EXTENSION"_s, HandleHasExtensionCommand }, { "HAS_STEM"_s, HandleHasStemCommand }, { "HAS_RELATIVE_PATH"_s, HandleHasRelativePathCommand }, { "HAS_PARENT_PATH"_s, HandleHasParentPathCommand }, { "IS_ABSOLUTE"_s, HandleIsAbsoluteCommand }, { "IS_RELATIVE"_s, HandleIsRelativeCommand }, { "IS_PREFIX"_s, HandleIsPrefixCommand }, { "HASH"_s, HandleHashCommand } }; return subcommand(args[0], args, status); }