| /* 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 <functional> |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <unordered_map> |
| #include <utility> |
| |
| #include <cm/optional> |
| #include <cm/string_view> |
| #include <cmext/algorithm> |
| #include <cmext/string_view> |
| |
| #include "cmArgumentParser.h" |
| #include "cmArgumentParserTypes.h" |
| #include "cmCMakePath.h" |
| #include "cmDiagnostics.h" |
| #include "cmExecutionStatus.h" |
| #include "cmMakefile.h" |
| #include "cmSourceFile.h" |
| #include "cmSourceFileLocation.h" |
| #include "cmSourceGroup.h" |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.h" |
| #include "cmTarget.h" |
| |
| namespace { |
| template <typename Args> |
| bool ProcessTree(Args const& args, cmExecutionStatus& status) |
| { |
| cmMakefile& mf = status.GetMakefile(); |
| |
| auto const& currentSourceDir = mf.GetCurrentSourceDirectory(); |
| std::vector<std::string> files; |
| if (args.Files) { |
| files.reserve(args.Files->size()); |
| |
| for (auto const& file : *args.Files) { |
| if (file.empty()) { |
| continue; |
| } |
| std::string fullPath = |
| cmSystemTools::CollapseFullPath(file, currentSourceDir); |
| files.emplace_back(std::move(fullPath)); |
| } |
| } else { |
| std::vector<std::unique_ptr<cmSourceFile>> const& sources = |
| mf.GetSourceFiles(); |
| for (auto const& src : sources) { |
| if (!src->GetIsGenerated()) { |
| files.push_back(cmSystemTools::CollapseFullPath( |
| src->GetLocation().GetFullPath(), currentSourceDir)); |
| } |
| } |
| } |
| |
| // final checks |
| cmCMakePath const root{ cmSystemTools::CollapseFullPath(*args.Tree) }; |
| cmCMakePath const prefix{ |
| cmCMakePath{ args.Prefix ? *args.Prefix : "" }.Normal() |
| }; |
| |
| auto it = files.begin(); |
| while (it != files.end()) { |
| if (it->empty() || cmSystemTools::FileIsDirectory(*it) || |
| (it->back() == '/' || it->back() == '\\')) { |
| // Ignore any empty files or directories |
| it = files.erase(it); |
| continue; |
| } |
| |
| cmCMakePath file{ *it }; |
| if (!root.IsPrefix(file)) { |
| status.SetError(cmStrCat('"', root.GenericString(), |
| "\" is not a prefix of file \"", |
| file.GenericString(), '"')); |
| return false; |
| } |
| |
| // source groups generation |
| cmCMakePath sourceGroup = prefix / file.Relative(root); |
| if (sourceGroup.HasParentPath()) { |
| sourceGroup = sourceGroup.GetParentPath(); |
| } |
| std::vector<std::string> tokenizedSG = |
| cmTokenize(sourceGroup.GenericString(), '/', cmTokenizerMode::New); |
| |
| auto* sg = mf.GetOrCreateSourceGroup(tokenizedSG); |
| if (!sg) { |
| status.SetError(cmStrCat("could not create source group for file \"", |
| file.GenericString(), '"')); |
| return false; |
| } |
| sg->AddGroupFile(file.GenericString()); |
| |
| ++it; |
| } |
| return true; |
| } |
| } // namespace |
| |
| bool cmSourceGroupCommand(std::vector<std::string> const& args, |
| cmExecutionStatus& status) |
| { |
| if (args.empty()) { |
| status.SetError("called with incorrect number of arguments"); |
| return false; |
| } |
| |
| static cm::string_view const Keywords[]{ "TREE"_s, "PREFIX"_s, "FILES"_s, |
| "REGULAR_EXPRESSION"_s }; |
| cmMakefile& mf = status.GetMakefile(); |
| |
| if (args.size() == 2 && !cm::contains(Keywords, args[0]) && |
| !cm::contains(Keywords, args[1])) { |
| // The pre-1.8 version of the command is being invoked. |
| cmSourceGroup* sg = mf.GetOrCreateSourceGroup(args[0]); |
| if (!sg) { |
| status.SetError("Could not create or find source group."); |
| return false; |
| } |
| sg->SetGroupRegex(args[1]); |
| return true; |
| } |
| |
| struct Arguments : public ArgumentParser::ParseResult |
| { |
| cm::optional<std::string> GroupName; |
| cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Files; |
| cm::optional<std::string> Regex; |
| cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> |
| FileSets; |
| cm::optional<ArgumentParser::NonEmpty<std::string>> Tree; |
| cm::optional<ArgumentParser::MaybeEmpty<std::string>> Prefix; |
| |
| std::map<std::string, std::set<std::string>> FileSetsPerTarget; |
| }; |
| |
| auto unsupportedKeyword = |
| [&mf](Arguments&, cm::string_view key, |
| cm::string_view /*value */) -> ArgumentParser::Continue { |
| mf.IssueDiagnostic( |
| cmDiagnostics::CMD_AUTHOR, |
| cmStrCat("keyword \"", key, "\" will be ignored in this context.")); |
| return ArgumentParser::Continue::Yes; |
| }; |
| // to distinguish REGULAR_EXPRESSION without values from with an empty string |
| auto handleRegex = [](Arguments& result, |
| cm::string_view value) -> ArgumentParser::Continue { |
| if (value.data()) { |
| result.Regex = std::string{ value }; |
| } |
| return ArgumentParser::Continue::No; |
| }; |
| auto handleTarget = [](Arguments& result, |
| cm::string_view value) -> ArgumentParser::Continue { |
| if (!result.FileSets) { |
| result.AddKeywordError( |
| "TARGET", cmStrCat("FILE_SETS is required for TARGET ", value, '.')); |
| return ArgumentParser::Continue::No; |
| } |
| if (!result.FileSets->empty()) { |
| result.FileSetsPerTarget[std::string{ value }].insert( |
| result.FileSets->begin(), result.FileSets->end()); |
| } |
| result.FileSets.reset(); |
| |
| return ArgumentParser::Continue::Yes; |
| }; |
| |
| std::vector<std::string> unexpectedArgs; |
| auto parser = |
| cmArgumentParser<Arguments>{}.Bind("FILES"_s, &Arguments::Files); |
| |
| if (cm::contains(args, "TREE")) { |
| // this is the TREE syntax |
| parser.Bind("TREE"_s, &Arguments::Tree) |
| .Bind("PREFIX"_s, &Arguments::Prefix) |
| .Bind("REGULAR_EXPRESSION"_s, unsupportedKeyword) |
| .Bind("FILE_SETS"_s, unsupportedKeyword) |
| .Bind("TARGET"_s, unsupportedKeyword); |
| } else { |
| // assume that first argument is the group name |
| parser.Bind(0, &Arguments::GroupName) |
| .Bind("REGULAR_EXPRESSION"_s, handleRegex, 0) |
| .Bind("FILE_SETS"_s, &Arguments::FileSets) |
| .Bind("TARGET"_s, handleTarget) |
| .Bind("TREE"_s, unsupportedKeyword) |
| .Bind("PREFIX"_s, unsupportedKeyword); |
| } |
| |
| auto parsedArgs = parser.Parse(args, &unexpectedArgs); |
| |
| // do various checks for arguments consistency |
| if (!parsedArgs.Check("", &unexpectedArgs, status)) { |
| cmSystemTools::SetFatalErrorOccurred(); |
| return false; |
| } |
| if (!parsedArgs.Tree && !parsedArgs.GroupName) { |
| status.SetError("missing source group name."); |
| cmSystemTools::SetFatalErrorOccurred(); |
| return false; |
| } |
| if (!parsedArgs.Tree && parsedArgs.FileSets) { |
| status.SetError(cmStrCat("TARGET is required for FILE_SETS ", |
| cmJoin(*parsedArgs.FileSets, ", "), '.')); |
| return false; |
| } |
| |
| if (parsedArgs.Tree) { |
| if (!ProcessTree(parsedArgs, status)) { |
| cmSystemTools::SetFatalErrorOccurred(); |
| return false; |
| } |
| } else { |
| if (!parsedArgs.Files && !parsedArgs.Regex && |
| parsedArgs.FileSetsPerTarget.empty()) { |
| // group is not created |
| return true; |
| } |
| |
| auto* sg = mf.GetOrCreateSourceGroup(*parsedArgs.GroupName); |
| if (!sg) { |
| status.SetError("could not create or find source group"); |
| cmSystemTools::SetFatalErrorOccurred(); |
| return false; |
| } |
| |
| if (parsedArgs.Regex) { |
| if (!sg->SetGroupRegex(*parsedArgs.Regex)) { |
| status.SetError(cmStrCat("regular expression \"", *parsedArgs.Regex, |
| "\" is invalid")); |
| return false; |
| } |
| } |
| if (parsedArgs.Files) { |
| auto const& currentSourceDir = mf.GetCurrentSourceDirectory(); |
| for (auto const& file : *parsedArgs.Files) { |
| sg->AddGroupFile( |
| cmSystemTools::CollapseFullPath(file, currentSourceDir)); |
| } |
| } |
| for (auto& item : parsedArgs.FileSetsPerTarget) { |
| // check validity of arguments |
| auto it = mf.GetTargets().find(item.first); |
| if (it == mf.GetTargets().end()) { |
| mf.IssueDiagnostic( |
| cmDiagnostics::CMD_AUTHOR, |
| cmStrCat( |
| "TARGET \"", item.first, |
| "\" is not defined in this directory. It will be ignored.")); |
| continue; |
| } |
| |
| cmTarget const& target = it->second; |
| for (auto it2 = item.second.begin(); it2 != item.second.end();) { |
| if (!target.GetFileSet(*it2)) { |
| mf.IssueDiagnostic(cmDiagnostics::CMD_AUTHOR, |
| cmStrCat("FILE_SET \"", *it2, |
| "\" is not known for TARGET \"", |
| item.first, "\". It will ignored.")); |
| it2 = item.second.erase(it2); |
| } else { |
| ++it2; |
| } |
| } |
| if (!item.second.empty()) { |
| sg->AddGroupFileSets(item.first, item.second); |
| } |
| } |
| } |
| |
| return true; |
| } |