mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
cmake_path(IS_PREFIX) and $<PATH:IS_PREFIX> treated an empty path as a prefix of every path, including another empty path, following std::filesystem::path. A prefix that is empty because a variable was set to an empty value, or because a generator expression argument expanded to nothing, therefore satisfied a check meant to reject it. Return false for an empty prefix, in cmCMakePath::IsPrefix so that every caller shares one implementation and both the plain and NORMALIZE forms are covered. Normalizing an empty path leaves it empty, so no separate handling of NORMALIZE is needed. Unlike the component comparison, which follows std::filesystem::path deliberately, IsPrefix has no counterpart in the standard: it borrows path iteration but the predicate itself is defined by CMake, so an empty prefix is a gap to fill rather than a standard answer to override. Add policy CMP0223 and restore the old result behind it at the two released surfaces. The other callers of IsPrefix, source_group() and the Makefile generator's source classification, take the new behavior ungated: source_group() rejects an empty TREE argument before reaching it, and the generator passes the source and binary directories. The if(PATH_IS_PREFIX) operator, new in this same release, follows the policy too rather than simply taking the new behavior, so that it agrees with cmake_path(IS_PREFIX) in every policy state and the parity assertions in its test hold unconditionally. Fixes: #28077
976 lines
26 KiB
C++
976 lines
26 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file LICENSE.rst or https://cmake.org/licensing for details. */
|
|
#include "cmCMakePathCommand.h"
|
|
|
|
#include <functional>
|
|
#include <iomanip>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <cm/optional>
|
|
#include <cm/string_view>
|
|
#include <cmext/string_view>
|
|
|
|
#include "cmArgumentParser.h"
|
|
#include "cmArgumentParserTypes.h"
|
|
#include "cmCMakePath.h"
|
|
#include "cmExecutionStatus.h"
|
|
#include "cmList.h"
|
|
#include "cmMakefile.h"
|
|
#include "cmPolicies.h"
|
|
#include "cmRange.h"
|
|
#include "cmStringAlgorithms.h"
|
|
#include "cmSubcommandTable.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmValue.h"
|
|
|
|
namespace {
|
|
// Helper classes for argument parsing
|
|
template <typename Result>
|
|
class CMakePathArgumentParser : public cmArgumentParser<Result>
|
|
{
|
|
public:
|
|
CMakePathArgumentParser()
|
|
: cmArgumentParser<Result>()
|
|
{
|
|
}
|
|
|
|
template <typename T>
|
|
CMakePathArgumentParser& Bind(cm::static_string_view name, T Result::*member)
|
|
{
|
|
this->cmArgumentParser<Result>::Bind(name, member);
|
|
return *this;
|
|
}
|
|
|
|
template <int Advance = 2>
|
|
Result Parse(std::vector<std::string> const& args) const
|
|
{
|
|
this->Inputs.clear();
|
|
|
|
return this->cmArgumentParser<Result>::Parse(
|
|
cmMakeRange(args).advance(Advance), &this->Inputs);
|
|
}
|
|
|
|
std::vector<std::string> const& GetInputs() const { return this->Inputs; }
|
|
|
|
protected:
|
|
mutable std::vector<std::string> Inputs;
|
|
};
|
|
|
|
// OUTPUT_VARIABLE is expected
|
|
template <typename Result>
|
|
class ArgumentParserWithOutputVariable : public CMakePathArgumentParser<Result>
|
|
{
|
|
public:
|
|
ArgumentParserWithOutputVariable()
|
|
: CMakePathArgumentParser<Result>()
|
|
{
|
|
this->Bind("OUTPUT_VARIABLE"_s, &Result::Output);
|
|
}
|
|
|
|
template <typename T>
|
|
ArgumentParserWithOutputVariable& Bind(cm::static_string_view name,
|
|
T Result::*member)
|
|
{
|
|
this->cmArgumentParser<Result>::Bind(name, member);
|
|
return *this;
|
|
}
|
|
|
|
template <int Advance = 2>
|
|
Result Parse(std::vector<std::string> const& args) const
|
|
{
|
|
return this->CMakePathArgumentParser<Result>::template Parse<Advance>(
|
|
args);
|
|
}
|
|
};
|
|
|
|
struct OutputVariable : public ArgumentParser::ParseResult
|
|
{
|
|
cm::optional<ArgumentParser::NonEmpty<std::string>> Output;
|
|
};
|
|
// Usable when OUTPUT_VARIABLE is the only option
|
|
class OutputVariableParser
|
|
: public ArgumentParserWithOutputVariable<OutputVariable>
|
|
{
|
|
};
|
|
|
|
struct NormalizeOption
|
|
{
|
|
bool Normalize = false;
|
|
};
|
|
// Usable when NORMALIZE is the only option
|
|
class NormalizeParser : public CMakePathArgumentParser<NormalizeOption>
|
|
{
|
|
public:
|
|
NormalizeParser() { this->Bind("NORMALIZE"_s, &NormalizeOption::Normalize); }
|
|
};
|
|
|
|
// retrieve value of input path from specified variable
|
|
bool getInputPath(std::string const& arg, cmExecutionStatus& status,
|
|
std::string& path)
|
|
{
|
|
cmValue def = status.GetMakefile().GetDefinition(arg);
|
|
if (!def) {
|
|
status.SetError("undefined variable for input path.");
|
|
return false;
|
|
}
|
|
|
|
path = *def;
|
|
return true;
|
|
}
|
|
|
|
bool HandleGetCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
static std::map<cm::string_view,
|
|
std::function<cmCMakePath(cmCMakePath const&, bool)>> const
|
|
actions{ { "ROOT_NAME"_s,
|
|
[](cmCMakePath const& path, bool) -> cmCMakePath {
|
|
return path.GetRootName();
|
|
} },
|
|
{ "ROOT_DIRECTORY"_s,
|
|
[](cmCMakePath const& path, bool) -> cmCMakePath {
|
|
return path.GetRootDirectory();
|
|
} },
|
|
{ "ROOT_PATH"_s,
|
|
[](cmCMakePath const& path, bool) -> cmCMakePath {
|
|
return path.GetRootPath();
|
|
} },
|
|
{ "FILENAME"_s,
|
|
[](cmCMakePath const& path, bool) -> cmCMakePath {
|
|
return path.GetFileName();
|
|
} },
|
|
{ "EXTENSION"_s,
|
|
[](cmCMakePath const& path, bool last_only) -> cmCMakePath {
|
|
if (last_only) {
|
|
return path.GetExtension();
|
|
}
|
|
return path.GetWideExtension();
|
|
} },
|
|
{ "STEM"_s,
|
|
[](cmCMakePath const& path, bool last_only) -> cmCMakePath {
|
|
if (last_only) {
|
|
return path.GetStem();
|
|
}
|
|
return path.GetNarrowStem();
|
|
} },
|
|
{ "RELATIVE_PART"_s,
|
|
[](cmCMakePath const& path, bool) -> cmCMakePath {
|
|
return path.GetRelativePath();
|
|
} },
|
|
{ "PARENT_PATH"_s,
|
|
[](cmCMakePath const& path, bool) -> cmCMakePath {
|
|
return path.GetParentPath();
|
|
} } };
|
|
|
|
if (args.size() < 4) {
|
|
status.SetError("GET must be called with at least three arguments.");
|
|
return false;
|
|
}
|
|
|
|
auto const& 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<Arguments> 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<std::string> 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;
|
|
|
|
auto const 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<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
if (args[1].empty()) {
|
|
status.SetError("Invalid name for path variable.");
|
|
return false;
|
|
}
|
|
|
|
static OutputVariableParser const parser{};
|
|
|
|
auto const arguments = parser.Parse(args);
|
|
|
|
if (arguments.MaybeReportError(status.GetMakefile())) {
|
|
return true;
|
|
}
|
|
|
|
cmCMakePath path(status.GetMakefile().GetSafeDefinition(args[1]));
|
|
for (auto const& input : parser.GetInputs()) {
|
|
path /= input;
|
|
}
|
|
|
|
status.GetMakefile().AddDefinition(
|
|
arguments.Output ? *arguments.Output : args[1], path.String());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HandleAppendStringCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
static OutputVariableParser const parser{};
|
|
|
|
auto const arguments = parser.Parse(args);
|
|
|
|
if (arguments.MaybeReportError(status.GetMakefile())) {
|
|
return true;
|
|
}
|
|
|
|
std::string inputPath;
|
|
if (!getInputPath(args[1], status, inputPath)) {
|
|
return false;
|
|
}
|
|
|
|
cmCMakePath path(inputPath);
|
|
for (auto const& input : parser.GetInputs()) {
|
|
path += input;
|
|
}
|
|
|
|
status.GetMakefile().AddDefinition(
|
|
arguments.Output ? *arguments.Output : args[1], path.String());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HandleRemoveFilenameCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
static OutputVariableParser const parser{};
|
|
|
|
auto const arguments = parser.Parse(args);
|
|
|
|
if (arguments.MaybeReportError(status.GetMakefile())) {
|
|
return true;
|
|
}
|
|
|
|
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 ? *arguments.Output : args[1], path.String());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HandleReplaceFilenameCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
static OutputVariableParser const parser{};
|
|
|
|
auto const arguments = parser.Parse(args);
|
|
|
|
if (arguments.MaybeReportError(status.GetMakefile())) {
|
|
return true;
|
|
}
|
|
|
|
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 ? *arguments.Output : args[1], path.String());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HandleRemoveExtensionCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
struct Arguments : public ArgumentParser::ParseResult
|
|
{
|
|
cm::optional<ArgumentParser::NonEmpty<std::string>> Output;
|
|
bool LastOnly = false;
|
|
};
|
|
|
|
static auto const parser =
|
|
ArgumentParserWithOutputVariable<Arguments>{}.Bind("LAST_ONLY"_s,
|
|
&Arguments::LastOnly);
|
|
|
|
Arguments const arguments = parser.Parse(args);
|
|
|
|
if (arguments.MaybeReportError(status.GetMakefile())) {
|
|
return true;
|
|
}
|
|
|
|
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 ? *arguments.Output : args[1], path.String());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HandleReplaceExtensionCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
struct Arguments : public ArgumentParser::ParseResult
|
|
{
|
|
cm::optional<ArgumentParser::NonEmpty<std::string>> Output;
|
|
bool LastOnly = false;
|
|
};
|
|
|
|
static auto const parser =
|
|
ArgumentParserWithOutputVariable<Arguments>{}.Bind("LAST_ONLY"_s,
|
|
&Arguments::LastOnly);
|
|
|
|
Arguments const arguments = parser.Parse(args);
|
|
|
|
if (arguments.MaybeReportError(status.GetMakefile())) {
|
|
return true;
|
|
}
|
|
|
|
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 ? *arguments.Output : args[1], path.String());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HandleNormalPathCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
static OutputVariableParser const parser{};
|
|
|
|
auto const arguments = parser.Parse(args);
|
|
|
|
if (arguments.MaybeReportError(status.GetMakefile())) {
|
|
return true;
|
|
}
|
|
|
|
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 ? *arguments.Output : args[1], path.String());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HandleTransformPathCommand(
|
|
std::vector<std::string> const& args, cmExecutionStatus& status,
|
|
std::function<cmCMakePath(cmCMakePath const&,
|
|
std::string const& base)> const& transform,
|
|
bool normalizeOption = false)
|
|
{
|
|
struct Arguments : public ArgumentParser::ParseResult
|
|
{
|
|
cm::optional<ArgumentParser::NonEmpty<std::string>> Output;
|
|
cm::optional<std::string> BaseDirectory;
|
|
bool Normalize = false;
|
|
};
|
|
|
|
auto parser = ArgumentParserWithOutputVariable<Arguments>{}.Bind(
|
|
"BASE_DIRECTORY"_s, &Arguments::BaseDirectory);
|
|
if (normalizeOption) {
|
|
parser.Bind("NORMALIZE"_s, &Arguments::Normalize);
|
|
}
|
|
|
|
Arguments arguments = parser.Parse(args);
|
|
|
|
if (arguments.MaybeReportError(status.GetMakefile())) {
|
|
return true;
|
|
}
|
|
|
|
if (!parser.GetInputs().empty()) {
|
|
status.SetError(cmStrCat(args[0], " called with unexpected arguments."));
|
|
return false;
|
|
}
|
|
|
|
std::string baseDirectory;
|
|
if (arguments.BaseDirectory) {
|
|
baseDirectory = *arguments.BaseDirectory;
|
|
} else {
|
|
baseDirectory = status.GetMakefile().GetCurrentSourceDirectory();
|
|
}
|
|
|
|
std::string inputPath;
|
|
if (!getInputPath(args[1], status, inputPath)) {
|
|
return false;
|
|
}
|
|
|
|
auto path = transform(cmCMakePath(inputPath), baseDirectory);
|
|
if (arguments.Normalize) {
|
|
path = path.Normal();
|
|
}
|
|
|
|
status.GetMakefile().AddDefinition(
|
|
arguments.Output ? *arguments.Output : args[1], path.String());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HandleRelativePathCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
return HandleTransformPathCommand(
|
|
args, status,
|
|
[](cmCMakePath const& path, std::string const& base) -> cmCMakePath {
|
|
return path.Relative(base);
|
|
});
|
|
}
|
|
|
|
bool HandleAbsolutePathCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
return HandleTransformPathCommand(
|
|
args, status,
|
|
[](cmCMakePath const& path, std::string const& base) -> cmCMakePath {
|
|
return path.Absolute(base);
|
|
},
|
|
true);
|
|
}
|
|
|
|
bool HandleNativePathCommand(std::vector<std::string> 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;
|
|
|
|
auto const 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<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
#if defined(_WIN32) && !defined(__CYGWIN__)
|
|
auto const pathSep = ";"_s;
|
|
#else
|
|
auto const pathSep = ":"_s;
|
|
#endif
|
|
auto const cmakePath = "TO_CMAKE_PATH_LIST"_s;
|
|
auto const 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;
|
|
}
|
|
|
|
auto const& 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;
|
|
|
|
auto const arguments = parser.Parse<4>(args);
|
|
|
|
if (!parser.GetInputs().empty()) {
|
|
status.SetError("CONVERT called with unexpected arguments.");
|
|
return false;
|
|
}
|
|
|
|
cmList paths;
|
|
|
|
if (action == cmakePath) {
|
|
paths = cmSystemTools::SplitString(args[1], pathSep.front());
|
|
} else {
|
|
paths.assign(args[1]);
|
|
}
|
|
|
|
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 = action == cmakePath ? paths.to_string() : paths.join(pathSep);
|
|
status.GetMakefile().AddDefinition(args[3], value);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HandleCompareCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
if (args.size() != 5) {
|
|
status.SetError("COMPARE must be called with four arguments.");
|
|
return false;
|
|
}
|
|
|
|
static std::map<cm::string_view,
|
|
std::function<bool(cmCMakePath const&,
|
|
cmCMakePath const&)>> const operators{
|
|
{ "EQUAL"_s,
|
|
[](cmCMakePath const& path1, cmCMakePath const& path2) -> bool {
|
|
return path1 == path2;
|
|
} },
|
|
{ "NOT_EQUAL"_s,
|
|
[](cmCMakePath const& path1, cmCMakePath const& path2) -> bool {
|
|
return path1 != path2;
|
|
} }
|
|
};
|
|
|
|
auto const 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<std::string> const& args, cmExecutionStatus& status,
|
|
std::function<bool(cmCMakePath const&)> const& 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<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
return HandleHasItemCommand(
|
|
args, status,
|
|
[](cmCMakePath const& path) -> bool { return path.HasRootName(); });
|
|
}
|
|
|
|
bool HandleHasRootDirectoryCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
return HandleHasItemCommand(
|
|
args, status,
|
|
[](cmCMakePath const& path) -> bool { return path.HasRootDirectory(); });
|
|
}
|
|
|
|
bool HandleHasRootPathCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
return HandleHasItemCommand(
|
|
args, status,
|
|
[](cmCMakePath const& path) -> bool { return path.HasRootPath(); });
|
|
}
|
|
|
|
bool HandleHasFilenameCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
return HandleHasItemCommand(
|
|
args, status,
|
|
[](cmCMakePath const& path) -> bool { return path.HasFileName(); });
|
|
}
|
|
|
|
bool HandleHasExtensionCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
return HandleHasItemCommand(
|
|
args, status,
|
|
[](cmCMakePath const& path) -> bool { return path.HasExtension(); });
|
|
}
|
|
|
|
bool HandleHasStemCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
return HandleHasItemCommand(
|
|
args, status,
|
|
[](cmCMakePath const& path) -> bool { return path.HasStem(); });
|
|
}
|
|
|
|
bool HandleHasRelativePartCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
return HandleHasItemCommand(
|
|
args, status,
|
|
[](cmCMakePath const& path) -> bool { return path.HasRelativePath(); });
|
|
}
|
|
|
|
bool HandleHasParentPathCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
return HandleHasItemCommand(
|
|
args, status,
|
|
[](cmCMakePath const& path) -> bool { return path.HasParentPath(); });
|
|
}
|
|
|
|
bool HandleIsAbsoluteCommand(std::vector<std::string> 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<std::string> 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;
|
|
}
|
|
|
|
// CMP0223: an empty path used to be a prefix of every path.
|
|
bool IsPrefixCMP0223(cmCMakePath const& prefix, cmMakefile& mf)
|
|
{
|
|
if (!prefix.IsEmpty()) {
|
|
return false;
|
|
}
|
|
switch (mf.GetPolicyStatus(cmPolicies::CMP0223)) {
|
|
case cmPolicies::WARN:
|
|
mf.IssuePolicyWarning(cmPolicies::CMP0223);
|
|
CM_FALLTHROUGH;
|
|
case cmPolicies::OLD:
|
|
return true;
|
|
case cmPolicies::NEW:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool HandleIsPrefixCommand(std::vector<std::string> 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;
|
|
|
|
auto const 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;
|
|
}
|
|
|
|
auto const& input = parser.GetInputs().front();
|
|
auto const& output = parser.GetInputs().back();
|
|
|
|
if (output.empty()) {
|
|
status.SetError("Invalid name for output variable.");
|
|
return false;
|
|
}
|
|
|
|
cmCMakePath prefix{ inputPath };
|
|
cmCMakePath value{ input };
|
|
if (arguments.Normalize) {
|
|
prefix = prefix.Normal();
|
|
value = value.Normal();
|
|
}
|
|
|
|
bool const isPrefix =
|
|
prefix.IsPrefix(value) || IsPrefixCMP0223(prefix, status.GetMakefile());
|
|
|
|
status.GetMakefile().AddDefinitionBool(output, isPrefix);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HandleHashCommand(std::vector<std::string> 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;
|
|
}
|
|
|
|
auto const& 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<std::string> 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_PART"_s, HandleHasRelativePartCommand },
|
|
{ "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);
|
|
}
|