/* 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 #include #include #include #include #include #include #include #include #include #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 bool ProcessTree(Args const& args, cmExecutionStatus& status) { cmMakefile& mf = status.GetMakefile(); auto const& currentSourceDir = mf.GetCurrentSourceDirectory(); std::vector 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> 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 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 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 GroupName; cm::optional>> Files; cm::optional Regex; cm::optional>> FileSets; cm::optional> Tree; cm::optional> Prefix; std::map> 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 unexpectedArgs; auto parser = cmArgumentParser{}.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; }