| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmSourceGroupCommand.h" | |
| #include <cstddef> | |
| #include <map> | |
| #include <memory> | |
| #include <set> | |
| #include <utility> | |
| #include <cmext/algorithm> | |
| #include "cmExecutionStatus.h" | |
| #include "cmMakefile.h" | |
| #include "cmSourceFile.h" | |
| #include "cmSourceFileLocation.h" | |
| #include "cmSourceGroup.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| namespace { | |
| using ParsedArguments = std::map<std::string, std::vector<std::string>>; | |
| using ExpectedOptions = std::vector<std::string>; | |
| std::string const kTreeOptionName = "TREE"; | |
| std::string const kPrefixOptionName = "PREFIX"; | |
| std::string const kFilesOptionName = "FILES"; | |
| std::string const kRegexOptionName = "REGULAR_EXPRESSION"; | |
| std::string const kSourceGroupOptionName = "<sg_name>"; | |
| std::set<std::string> getSourceGroupFilesPaths( | |
| std::string const& root, std::vector<std::string> const& files) | |
| { | |
| std::set<std::string> ret; | |
| std::string::size_type const rootLength = root.length(); | |
| for (std::string const& file : files) { | |
| ret.insert(file.substr(rootLength + 1)); // +1 to also omnit last '/' | |
| } | |
| return ret; | |
| } | |
| bool rootIsPrefix(std::string const& root, | |
| std::vector<std::string> const& files, std::string& error) | |
| { | |
| for (std::string const& file : files) { | |
| if (!cmHasPrefix(file, root)) { | |
| error = cmStrCat("ROOT: ", root, " is not a prefix of file: ", file); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| std::vector<std::string> prepareFilesPathsForTree( | |
| std::vector<std::string> const& filesPaths, | |
| std::string const& currentSourceDir) | |
| { | |
| std::vector<std::string> prepared; | |
| prepared.reserve(filesPaths.size()); | |
| for (auto const& filePath : filesPaths) { | |
| std::string fullPath = | |
| cmSystemTools::CollapseFullPath(filePath, currentSourceDir); | |
| // If provided file path is actually not a directory, silently ignore it. | |
| if (cmSystemTools::FileIsDirectory(fullPath)) { | |
| continue; | |
| } | |
| // Handle directory that doesn't exist yet. | |
| if (!fullPath.empty() && | |
| (fullPath.back() == '/' || fullPath.back() == '\\')) { | |
| continue; | |
| } | |
| prepared.emplace_back(std::move(fullPath)); | |
| } | |
| return prepared; | |
| } | |
| bool addFilesToItsSourceGroups(std::string const& root, | |
| std::set<std::string> const& sgFilesPaths, | |
| std::string const& prefix, cmMakefile& makefile, | |
| std::string& errorMsg) | |
| { | |
| cmSourceGroup* sg; | |
| for (std::string const& sgFilesPath : sgFilesPaths) { | |
| std::vector<std::string> tokenizedPath = cmTokenize( | |
| prefix.empty() ? sgFilesPath : cmStrCat(prefix, '/', sgFilesPath), | |
| R"(\/)", cmTokenizerMode::New); | |
| if (tokenizedPath.empty()) { | |
| continue; | |
| } | |
| tokenizedPath.pop_back(); | |
| if (tokenizedPath.empty()) { | |
| tokenizedPath.emplace_back(); | |
| } | |
| sg = makefile.GetOrCreateSourceGroup(tokenizedPath); | |
| if (!sg) { | |
| errorMsg = "Could not create source group for file: " + sgFilesPath; | |
| return false; | |
| } | |
| std::string const fullPath = | |
| cmSystemTools::CollapseFullPath(sgFilesPath, root); | |
| sg->AddGroupFile(fullPath); | |
| } | |
| return true; | |
| } | |
| ExpectedOptions getExpectedOptions() | |
| { | |
| ExpectedOptions options; | |
| options.push_back(kTreeOptionName); | |
| options.push_back(kPrefixOptionName); | |
| options.push_back(kFilesOptionName); | |
| options.push_back(kRegexOptionName); | |
| return options; | |
| } | |
| bool isExpectedOption(std::string const& argument, | |
| ExpectedOptions const& expectedOptions) | |
| { | |
| return cm::contains(expectedOptions, argument); | |
| } | |
| void parseArguments(std::vector<std::string> const& args, | |
| ParsedArguments& parsedArguments) | |
| { | |
| ExpectedOptions const expectedOptions = getExpectedOptions(); | |
| size_t i = 0; | |
| // at this point we know that args vector is not empty | |
| // if first argument is not one of expected options it's source group name | |
| if (!isExpectedOption(args[0], expectedOptions)) { | |
| // get source group name and go to next argument | |
| parsedArguments[kSourceGroupOptionName].push_back(args[0]); | |
| ++i; | |
| } | |
| for (; i < args.size();) { | |
| // get current option and increment index to go to next argument | |
| std::string const& currentOption = args[i++]; | |
| // create current option entry in parsed arguments | |
| std::vector<std::string>& currentOptionArguments = | |
| parsedArguments[currentOption]; | |
| // collect option arguments while we won't find another expected option | |
| while (i < args.size() && !isExpectedOption(args[i], expectedOptions)) { | |
| currentOptionArguments.push_back(args[i++]); | |
| } | |
| } | |
| } | |
| } // namespace | |
| static bool checkArgumentsPreconditions(ParsedArguments const& parsedArguments, | |
| std::string& errorMsg); | |
| static bool processTree(cmMakefile& mf, ParsedArguments& parsedArguments, | |
| std::string& errorMsg); | |
| static bool checkSingleParameterArgumentPreconditions( | |
| std::string const& argument, ParsedArguments const& parsedArguments, | |
| std::string& errorMsg); | |
| bool cmSourceGroupCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.empty()) { | |
| status.SetError("called with incorrect number of arguments"); | |
| return false; | |
| } | |
| cmMakefile& mf = status.GetMakefile(); | |
| // If only two arguments are given, the pre-1.8 version of the | |
| // command is being invoked. | |
| bool isShortTreeSyntax = | |
| ((args.size() == 2) && (args[0] == kTreeOptionName) && | |
| cmSystemTools::FileIsDirectory(args[1])); | |
| if (args.size() == 2 && args[1] != kFilesOptionName && !isShortTreeSyntax) { | |
| cmSourceGroup* sg = mf.GetOrCreateSourceGroup(args[0]); | |
| if (!sg) { | |
| status.SetError("Could not create or find source group"); | |
| return false; | |
| } | |
| sg->SetGroupRegex(args[1].c_str()); | |
| return true; | |
| } | |
| ParsedArguments parsedArguments; | |
| std::string errorMsg; | |
| parseArguments(args, parsedArguments); | |
| if (!checkArgumentsPreconditions(parsedArguments, errorMsg)) { | |
| return false; | |
| } | |
| if (parsedArguments.find(kTreeOptionName) != parsedArguments.end()) { | |
| if (!processTree(mf, parsedArguments, errorMsg)) { | |
| status.SetError(errorMsg); | |
| return false; | |
| } | |
| } else { | |
| if (parsedArguments.find(kSourceGroupOptionName) == | |
| parsedArguments.end()) { | |
| status.SetError("Missing source group name."); | |
| return false; | |
| } | |
| cmSourceGroup* sg = mf.GetOrCreateSourceGroup(args[0]); | |
| if (!sg) { | |
| status.SetError("Could not create or find source group"); | |
| return false; | |
| } | |
| // handle regex | |
| if (parsedArguments.find(kRegexOptionName) != parsedArguments.end()) { | |
| std::string const& sgRegex = parsedArguments[kRegexOptionName].front(); | |
| sg->SetGroupRegex(sgRegex.c_str()); | |
| } | |
| // handle files | |
| std::vector<std::string> const& filesArguments = | |
| parsedArguments[kFilesOptionName]; | |
| for (auto const& filesArg : filesArguments) { | |
| std::string src = filesArg; | |
| src = | |
| cmSystemTools::CollapseFullPath(src, mf.GetCurrentSourceDirectory()); | |
| sg->AddGroupFile(src); | |
| } | |
| } | |
| return true; | |
| } | |
| static bool checkArgumentsPreconditions(ParsedArguments const& parsedArguments, | |
| std::string& errorMsg) | |
| { | |
| return checkSingleParameterArgumentPreconditions( | |
| kPrefixOptionName, parsedArguments, errorMsg) && | |
| checkSingleParameterArgumentPreconditions(kTreeOptionName, parsedArguments, | |
| errorMsg) && | |
| checkSingleParameterArgumentPreconditions(kRegexOptionName, | |
| parsedArguments, errorMsg); | |
| } | |
| static bool processTree(cmMakefile& mf, ParsedArguments& parsedArguments, | |
| std::string& errorMsg) | |
| { | |
| std::string const root = | |
| cmSystemTools::CollapseFullPath(parsedArguments[kTreeOptionName].front()); | |
| std::string prefix = parsedArguments[kPrefixOptionName].empty() | |
| ? "" | |
| : parsedArguments[kPrefixOptionName].front(); | |
| std::vector<std::string> files; | |
| auto filesArgIt = parsedArguments.find(kFilesOptionName); | |
| if (filesArgIt != parsedArguments.end()) { | |
| files = filesArgIt->second; | |
| } else { | |
| std::vector<std::unique_ptr<cmSourceFile>> const& srcFiles = | |
| mf.GetSourceFiles(); | |
| for (auto const& srcFile : srcFiles) { | |
| if (!srcFile->GetIsGenerated()) { | |
| files.push_back(srcFile->GetLocation().GetFullPath()); | |
| } | |
| } | |
| } | |
| std::vector<std::string> const filesVector = | |
| prepareFilesPathsForTree(files, mf.GetCurrentSourceDirectory()); | |
| if (!rootIsPrefix(root, filesVector, errorMsg)) { | |
| return false; | |
| } | |
| std::set<std::string> sourceGroupPaths = | |
| getSourceGroupFilesPaths(root, filesVector); | |
| return addFilesToItsSourceGroups(root, sourceGroupPaths, prefix, mf, | |
| errorMsg); | |
| } | |
| static bool checkSingleParameterArgumentPreconditions( | |
| std::string const& argument, ParsedArguments const& parsedArguments, | |
| std::string& errorMsg) | |
| { | |
| auto foundArgument = parsedArguments.find(argument); | |
| if (foundArgument != parsedArguments.end()) { | |
| std::vector<std::string> const& optionArguments = foundArgument->second; | |
| if (optionArguments.empty()) { | |
| errorMsg = argument + " argument given without an argument."; | |
| return false; | |
| } | |
| if (optionArguments.size() > 1) { | |
| errorMsg = "too many arguments passed to " + argument + "."; | |
| return false; | |
| } | |
| } | |
| return true; | |
| } |