mirror of
https://github.com/Kitware/CMake.git
synced 2026-08-09 17:18:18 +00:00
278 lines
8.7 KiB
C++
278 lines
8.7 KiB
C++
/* 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;
|
|
}
|