You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cmake/Source/cmGeneratorTarget_Compatibl...

742 lines
23 KiB

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
/* clang-format off */
#include "cmGeneratorTarget.h"
/* clang-format on */
#include <algorithm>
#include <cassert>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include <cm/memory>
#include <cmext/algorithm>
#include "cmComputeLinkInformation.h"
#include "cmGeneratorExpression.h"
#include "cmList.h"
#include "cmLocalGenerator.h"
#include "cmMessageType.h"
#include "cmRange.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmValue.h"
namespace {
using UseTo = cmGeneratorTarget::UseTo;
}
const cmGeneratorTarget::CompatibleInterfacesBase&
cmGeneratorTarget::GetCompatibleInterfaces(std::string const& config) const
{
cmGeneratorTarget::CompatibleInterfaces& compat =
this->CompatibleInterfacesMap[config];
if (!compat.Done) {
compat.Done = true;
compat.PropsBool.insert("POSITION_INDEPENDENT_CODE");
compat.PropsString.insert("AUTOUIC_OPTIONS");
std::vector<cmGeneratorTarget const*> const& deps =
this->GetLinkImplementationClosure(config, UseTo::Compile);
for (cmGeneratorTarget const* li : deps) {
#define CM_READ_COMPATIBLE_INTERFACE(X, x) \
if (cmValue prop = li->GetProperty("COMPATIBLE_INTERFACE_" #X)) { \
cmList props(*prop); \
compat.Props##x.insert(props.begin(), props.end()); \
}
CM_READ_COMPATIBLE_INTERFACE(BOOL, Bool)
CM_READ_COMPATIBLE_INTERFACE(STRING, String)
CM_READ_COMPATIBLE_INTERFACE(NUMBER_MIN, NumberMin)
CM_READ_COMPATIBLE_INTERFACE(NUMBER_MAX, NumberMax)
#undef CM_READ_COMPATIBLE_INTERFACE
}
}
return compat;
}
bool cmGeneratorTarget::IsLinkInterfaceDependentBoolProperty(
const std::string& p, const std::string& config) const
{
if (this->GetType() == cmStateEnums::OBJECT_LIBRARY ||
this->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
return false;
}
return this->GetCompatibleInterfaces(config).PropsBool.count(p) > 0;
}
bool cmGeneratorTarget::IsLinkInterfaceDependentStringProperty(
const std::string& p, const std::string& config) const
{
if (this->GetType() == cmStateEnums::OBJECT_LIBRARY ||
this->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
return false;
}
return this->GetCompatibleInterfaces(config).PropsString.count(p) > 0;
}
bool cmGeneratorTarget::IsLinkInterfaceDependentNumberMinProperty(
const std::string& p, const std::string& config) const
{
if (this->GetType() == cmStateEnums::OBJECT_LIBRARY ||
this->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
return false;
}
return this->GetCompatibleInterfaces(config).PropsNumberMin.count(p) > 0;
}
bool cmGeneratorTarget::IsLinkInterfaceDependentNumberMaxProperty(
const std::string& p, const std::string& config) const
{
if (this->GetType() == cmStateEnums::OBJECT_LIBRARY ||
this->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
return false;
}
return this->GetCompatibleInterfaces(config).PropsNumberMax.count(p) > 0;
}
enum CompatibleType
{
BoolType,
StringType,
NumberMinType,
NumberMaxType
};
template <typename PropertyType>
PropertyType getLinkInterfaceDependentProperty(cmGeneratorTarget const* tgt,
const std::string& prop,
const std::string& config,
CompatibleType, PropertyType*);
template <>
bool getLinkInterfaceDependentProperty(cmGeneratorTarget const* tgt,
const std::string& prop,
const std::string& config,
CompatibleType /*unused*/,
bool* /*unused*/)
{
return tgt->GetLinkInterfaceDependentBoolProperty(prop, config);
}
template <>
const char* getLinkInterfaceDependentProperty(cmGeneratorTarget const* tgt,
const std::string& prop,
const std::string& config,
CompatibleType t,
const char** /*unused*/)
{
switch (t) {
case BoolType:
assert(false &&
"String compatibility check function called for boolean");
return nullptr;
case StringType:
return tgt->GetLinkInterfaceDependentStringProperty(prop, config);
case NumberMinType:
return tgt->GetLinkInterfaceDependentNumberMinProperty(prop, config);
case NumberMaxType:
return tgt->GetLinkInterfaceDependentNumberMaxProperty(prop, config);
}
assert(false && "Unreachable!");
return nullptr;
}
template <typename PropertyType>
void checkPropertyConsistency(cmGeneratorTarget const* depender,
cmGeneratorTarget const* dependee,
const std::string& propName,
std::set<std::string>& emitted,
const std::string& config, CompatibleType t,
PropertyType* /*unused*/)
{
cmValue prop = dependee->GetProperty(propName);
if (!prop) {
return;
}
cmList props{ *prop };
std::string pdir =
cmStrCat(cmSystemTools::GetCMakeRoot(), "/Help/prop_tgt/");
for (std::string const& p : props) {
std::string pname = cmSystemTools::HelpFileName(p);
std::string pfile = pdir + pname + ".rst";
if (cmSystemTools::FileExists(pfile, true)) {
std::ostringstream e;
e << "Target \"" << dependee->GetName() << "\" has property \"" << p
<< "\" listed in its " << propName
<< " property. "
"This is not allowed. Only user-defined properties may appear "
"listed in the "
<< propName << " property.";
depender->GetLocalGenerator()->IssueMessage(MessageType::FATAL_ERROR,
e.str());
return;
}
if (emitted.insert(p).second) {
getLinkInterfaceDependentProperty<PropertyType>(depender, p, config, t,
nullptr);
if (cmSystemTools::GetErrorOccurredFlag()) {
return;
}
}
}
}
namespace {
std::string intersect(const std::set<std::string>& s1,
const std::set<std::string>& s2)
{
std::set<std::string> intersect;
std::set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(),
std::inserter(intersect, intersect.begin()));
if (!intersect.empty()) {
return *intersect.begin();
}
return "";
}
std::string intersect(const std::set<std::string>& s1,
const std::set<std::string>& s2,
const std::set<std::string>& s3)
{
std::string result;
result = intersect(s1, s2);
if (!result.empty()) {
return result;
}
result = intersect(s1, s3);
if (!result.empty()) {
return result;
}
return intersect(s2, s3);
}
std::string intersect(const std::set<std::string>& s1,
const std::set<std::string>& s2,
const std::set<std::string>& s3,
const std::set<std::string>& s4)
{
std::string result;
result = intersect(s1, s2);
if (!result.empty()) {
return result;
}
result = intersect(s1, s3);
if (!result.empty()) {
return result;
}
result = intersect(s1, s4);
if (!result.empty()) {
return result;
}
return intersect(s2, s3, s4);
}
}
void cmGeneratorTarget::CheckPropertyCompatibility(
cmComputeLinkInformation& info, const std::string& config) const
{
const cmComputeLinkInformation::ItemVector& deps = info.GetItems();
std::set<std::string> emittedBools;
static const std::string strBool = "COMPATIBLE_INTERFACE_BOOL";
std::set<std::string> emittedStrings;
static const std::string strString = "COMPATIBLE_INTERFACE_STRING";
std::set<std::string> emittedMinNumbers;
static const std::string strNumMin = "COMPATIBLE_INTERFACE_NUMBER_MIN";
std::set<std::string> emittedMaxNumbers;
static const std::string strNumMax = "COMPATIBLE_INTERFACE_NUMBER_MAX";
for (auto const& dep : deps) {
if (!dep.Target || dep.Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
continue;
}
checkPropertyConsistency<bool>(this, dep.Target, strBool, emittedBools,
config, BoolType, nullptr);
if (cmSystemTools::GetErrorOccurredFlag()) {
return;
}
checkPropertyConsistency<const char*>(this, dep.Target, strString,
emittedStrings, config, StringType,
nullptr);
if (cmSystemTools::GetErrorOccurredFlag()) {
return;
}
checkPropertyConsistency<const char*>(this, dep.Target, strNumMin,
emittedMinNumbers, config,
NumberMinType, nullptr);
if (cmSystemTools::GetErrorOccurredFlag()) {
return;
}
checkPropertyConsistency<const char*>(this, dep.Target, strNumMax,
emittedMaxNumbers, config,
NumberMaxType, nullptr);
if (cmSystemTools::GetErrorOccurredFlag()) {
return;
}
}
std::string prop = intersect(emittedBools, emittedStrings, emittedMinNumbers,
emittedMaxNumbers);
if (!prop.empty()) {
// Use a sorted std::vector to keep the error message sorted.
std::vector<std::string> props;
auto i = emittedBools.find(prop);
if (i != emittedBools.end()) {
props.push_back(strBool);
}
i = emittedStrings.find(prop);
if (i != emittedStrings.end()) {
props.push_back(strString);
}
i = emittedMinNumbers.find(prop);
if (i != emittedMinNumbers.end()) {
props.push_back(strNumMin);
}
i = emittedMaxNumbers.find(prop);
if (i != emittedMaxNumbers.end()) {
props.push_back(strNumMax);
}
std::sort(props.begin(), props.end());
std::string propsString = cmStrCat(
cmJoin(cmMakeRange(props).retreat(1), ", "), " and the ", props.back());
std::ostringstream e;
e << "Property \"" << prop << "\" appears in both the " << propsString
<< " property in the dependencies of target \"" << this->GetName()
<< "\". This is not allowed. A property may only require "
"compatibility "
"in a boolean interpretation, a numeric minimum, a numeric maximum "
"or a "
"string interpretation, but not a mixture.";
this->LocalGenerator->IssueMessage(MessageType::FATAL_ERROR, e.str());
}
}
template <typename PropertyType>
std::string valueAsString(PropertyType);
template <>
std::string valueAsString<bool>(bool value)
{
return value ? "TRUE" : "FALSE";
}
template <>
std::string valueAsString<const char*>(const char* value)
{
return value ? value : "(unset)";
}
template <>
std::string valueAsString<std::string>(std::string value)
{
return value;
}
template <>
std::string valueAsString<cmValue>(cmValue value)
{
return value ? *value : std::string("(unset)");
}
template <>
std::string valueAsString<std::nullptr_t>(std::nullptr_t /*unused*/)
{
return "(unset)";
}
static std::string compatibilityType(CompatibleType t)
{
switch (t) {
case BoolType:
return "Boolean compatibility";
case StringType:
return "String compatibility";
case NumberMaxType:
return "Numeric maximum compatibility";
case NumberMinType:
return "Numeric minimum compatibility";
}
assert(false && "Unreachable!");
return "";
}
static std::string compatibilityAgree(CompatibleType t, bool dominant)
{
switch (t) {
case BoolType:
case StringType:
return dominant ? "(Disagree)\n" : "(Agree)\n";
case NumberMaxType:
case NumberMinType:
return dominant ? "(Dominant)\n" : "(Ignored)\n";
}
assert(false && "Unreachable!");
return "";
}
template <typename PropertyType>
PropertyType getTypedProperty(
cmGeneratorTarget const* tgt, const std::string& prop,
cmGeneratorExpressionInterpreter* genexInterpreter = nullptr);
template <>
bool getTypedProperty<bool>(cmGeneratorTarget const* tgt,
const std::string& prop,
cmGeneratorExpressionInterpreter* genexInterpreter)
{
if (genexInterpreter == nullptr) {
return tgt->GetPropertyAsBool(prop);
}
cmValue value = tgt->GetProperty(prop);
return cmIsOn(genexInterpreter->Evaluate(value ? *value : "", prop));
}
template <>
const char* getTypedProperty<const char*>(
cmGeneratorTarget const* tgt, const std::string& prop,
cmGeneratorExpressionInterpreter* genexInterpreter)
{
cmValue value = tgt->GetProperty(prop);
if (genexInterpreter == nullptr) {
return value.GetCStr();
}
return genexInterpreter->Evaluate(value ? *value : "", prop).c_str();
}
template <>
std::string getTypedProperty<std::string>(
cmGeneratorTarget const* tgt, const std::string& prop,
cmGeneratorExpressionInterpreter* genexInterpreter)
{
cmValue value = tgt->GetProperty(prop);
if (genexInterpreter == nullptr) {
return valueAsString(value);
}
return genexInterpreter->Evaluate(value ? *value : "", prop);
}
template <typename PropertyType>
PropertyType impliedValue(PropertyType);
template <>
bool impliedValue<bool>(bool /*unused*/)
{
return false;
}
template <>
const char* impliedValue<const char*>(const char* /*unused*/)
{
return "";
}
template <>
std::string impliedValue<std::string>(std::string /*unused*/) // NOLINT(*)
{
return std::string();
}
template <typename PropertyType>
std::pair<bool, PropertyType> consistentProperty(PropertyType lhs,
PropertyType rhs,
CompatibleType t);
template <>
std::pair<bool, bool> consistentProperty(bool lhs, bool rhs,
CompatibleType /*unused*/)
{
return { lhs == rhs, lhs };
}
static std::pair<bool, const char*> consistentStringProperty(const char* lhs,
const char* rhs)
{
const bool b = strcmp(lhs, rhs) == 0;
return { b, b ? lhs : nullptr };
}
static std::pair<bool, std::string> consistentStringProperty(
const std::string& lhs, const std::string& rhs)
{
const bool b = lhs == rhs;
return { b, b ? lhs : valueAsString(nullptr) };
}
static std::pair<bool, const char*> consistentNumberProperty(const char* lhs,
const char* rhs,
CompatibleType t)
{
char* pEnd;
long lnum = strtol(lhs, &pEnd, 0);
if (pEnd == lhs || *pEnd != '\0' || errno == ERANGE) {
return { false, nullptr };
}
long rnum = strtol(rhs, &pEnd, 0);
if (pEnd == rhs || *pEnd != '\0' || errno == ERANGE) {
return { false, nullptr };
}
if (t == NumberMaxType) {
return { true, std::max(lnum, rnum) == lnum ? lhs : rhs };
}
return { true, std::min(lnum, rnum) == lnum ? lhs : rhs };
}
template <>
std::pair<bool, const char*> consistentProperty(const char* lhs,
const char* rhs,
CompatibleType t)
{
if (!lhs && !rhs) {
return { true, lhs };
}
if (!lhs) {
return { true, rhs };
}
if (!rhs) {
return { true, lhs };
}
switch (t) {
case BoolType: {
bool same = cmIsOn(lhs) == cmIsOn(rhs);
return { same, same ? lhs : nullptr };
}
case StringType:
return consistentStringProperty(lhs, rhs);
case NumberMinType:
case NumberMaxType:
return consistentNumberProperty(lhs, rhs, t);
}
assert(false && "Unreachable!");
return { false, nullptr };
}
static std::pair<bool, std::string> consistentProperty(const std::string& lhs,
const std::string& rhs,
CompatibleType t)
{
const std::string null_ptr = valueAsString(nullptr);
if (lhs == null_ptr && rhs == null_ptr) {
return { true, lhs };
}
if (lhs == null_ptr) {
return { true, rhs };
}
if (rhs == null_ptr) {
return { true, lhs };
}
switch (t) {
case BoolType: {
bool same = cmIsOn(lhs) == cmIsOn(rhs);
return { same, same ? lhs : null_ptr };
}
case StringType:
return consistentStringProperty(lhs, rhs);
case NumberMinType:
case NumberMaxType: {
auto value = consistentNumberProperty(lhs.c_str(), rhs.c_str(), t);
return { value.first,
value.first ? std::string(value.second) : null_ptr };
}
}
assert(false && "Unreachable!");
return { false, null_ptr };
}
template <typename PropertyType>
PropertyType checkInterfacePropertyCompatibility(cmGeneratorTarget const* tgt,
const std::string& p,
const std::string& config,
const char* defaultValue,
CompatibleType t,
PropertyType* /*unused*/)
{
PropertyType propContent = getTypedProperty<PropertyType>(tgt, p);
std::vector<std::string> headPropKeys = tgt->GetPropertyKeys();
const bool explicitlySet = cm::contains(headPropKeys, p);
const bool impliedByUse = tgt->IsNullImpliedByLinkLibraries(p);
assert((impliedByUse ^ explicitlySet) || (!impliedByUse && !explicitlySet));
std::vector<cmGeneratorTarget const*> const& deps =
tgt->GetLinkImplementationClosure(config, UseTo::Compile);
if (deps.empty()) {
return propContent;
}
bool propInitialized = explicitlySet;
std::string report = cmStrCat(" * Target \"", tgt->GetName());
if (explicitlySet) {
report += "\" has property content \"";
report += valueAsString<PropertyType>(propContent);
report += "\"\n";
} else if (impliedByUse) {
report += "\" property is implied by use.\n";
} else {
report += "\" property not set.\n";
}
std::string interfaceProperty = "INTERFACE_" + p;
std::unique_ptr<cmGeneratorExpressionInterpreter> genexInterpreter;
if (p == "POSITION_INDEPENDENT_CODE") {
// Corresponds to EvaluatingPICExpression.
genexInterpreter = cm::make_unique<cmGeneratorExpressionInterpreter>(
tgt->GetLocalGenerator(), config, tgt);
}
for (cmGeneratorTarget const* theTarget : deps) {
// An error should be reported if one dependency
// has INTERFACE_POSITION_INDEPENDENT_CODE ON and the other
// has INTERFACE_POSITION_INDEPENDENT_CODE OFF, or if the
// target itself has a POSITION_INDEPENDENT_CODE which disagrees
// with a dependency.
std::vector<std::string> propKeys = theTarget->GetPropertyKeys();
const bool ifaceIsSet = cm::contains(propKeys, interfaceProperty);
PropertyType ifacePropContent = getTypedProperty<PropertyType>(
theTarget, interfaceProperty, genexInterpreter.get());
std::string reportEntry;
if (ifaceIsSet) {
reportEntry += " * Target \"";
reportEntry += theTarget->GetName();
reportEntry += "\" property value \"";
reportEntry += valueAsString<PropertyType>(ifacePropContent);
reportEntry += "\" ";
}
if (explicitlySet) {
if (ifaceIsSet) {
std::pair<bool, PropertyType> consistent =
consistentProperty(propContent, ifacePropContent, t);
report += reportEntry;
report += compatibilityAgree(t, propContent != consistent.second);
if (!consistent.first) {
std::ostringstream e;
e << "Property " << p << " on target \"" << tgt->GetName()
<< "\" does\nnot match the "
"INTERFACE_"
<< p
<< " property requirement\nof "
"dependency \""
<< theTarget->GetName() << "\".\n";
cmSystemTools::Error(e.str());
break;
}
propContent = consistent.second;
continue;
}
// Explicitly set on target and not set in iface. Can't disagree.
continue;
}
if (impliedByUse) {
propContent = impliedValue<PropertyType>(propContent);
if (ifaceIsSet) {
std::pair<bool, PropertyType> consistent =
consistentProperty(propContent, ifacePropContent, t);
report += reportEntry;
report += compatibilityAgree(t, propContent != consistent.second);
if (!consistent.first) {
std::ostringstream e;
e << "Property " << p << " on target \"" << tgt->GetName()
<< "\" is\nimplied to be " << defaultValue
<< " because it was used to determine the link libraries\n"
"already. The INTERFACE_"
<< p << " property on\ndependency \"" << theTarget->GetName()
<< "\" is in conflict.\n";
cmSystemTools::Error(e.str());
break;
}
propContent = consistent.second;
continue;
}
// Implicitly set on target and not set in iface. Can't disagree.
continue;
}
if (ifaceIsSet) {
if (propInitialized) {
std::pair<bool, PropertyType> consistent =
consistentProperty(propContent, ifacePropContent, t);
report += reportEntry;
report += compatibilityAgree(t, propContent != consistent.second);
if (!consistent.first) {
std::ostringstream e;
e << "The INTERFACE_" << p << " property of \""
<< theTarget->GetName() << "\" does\nnot agree with the value of "
<< p << " already determined\nfor \"" << tgt->GetName() << "\".\n";
cmSystemTools::Error(e.str());
break;
}
propContent = consistent.second;
continue;
}
report += reportEntry + "(Interface set)\n";
propContent = ifacePropContent;
propInitialized = true;
} else {
// Not set. Nothing to agree on.
continue;
}
}
tgt->ReportPropertyOrigin(p, valueAsString<PropertyType>(propContent),
report, compatibilityType(t));
return propContent;
}
bool cmGeneratorTarget::GetLinkInterfaceDependentBoolProperty(
const std::string& p, const std::string& config) const
{
return checkInterfacePropertyCompatibility<bool>(this, p, config, "FALSE",
BoolType, nullptr);
}
std::string cmGeneratorTarget::GetLinkInterfaceDependentStringAsBoolProperty(
const std::string& p, const std::string& config) const
{
return checkInterfacePropertyCompatibility<std::string>(
this, p, config, "FALSE", BoolType, nullptr);
}
const char* cmGeneratorTarget::GetLinkInterfaceDependentStringProperty(
const std::string& p, const std::string& config) const
{
return checkInterfacePropertyCompatibility<const char*>(
this, p, config, "empty", StringType, nullptr);
}
const char* cmGeneratorTarget::GetLinkInterfaceDependentNumberMinProperty(
const std::string& p, const std::string& config) const
{
return checkInterfacePropertyCompatibility<const char*>(
this, p, config, "empty", NumberMinType, nullptr);
}
const char* cmGeneratorTarget::GetLinkInterfaceDependentNumberMaxProperty(
const std::string& p, const std::string& config) const
{
return checkInterfacePropertyCompatibility<const char*>(
this, p, config, "empty", NumberMaxType, nullptr);
}