diff options
author | Brad King <brad.king@kitware.com> | 2024-05-17 15:58:46 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2024-05-20 23:26:05 (GMT) |
commit | b4924c562a9197b84b1f511043a5b2775cb865b3 (patch) | |
tree | eb692dd119b444377324e8f80427e78ff12bc57a | |
parent | cdd7112a5d3ad4b5bd30aae33e82f779a6b77cfe (diff) | |
download | CMake-b4924c562a9197b84b1f511043a5b2775cb865b3.zip CMake-b4924c562a9197b84b1f511043a5b2775cb865b3.tar.gz CMake-b4924c562a9197b84b1f511043a5b2775cb865b3.tar.bz2 |
cmGeneratorTarget: Factor compatible interface properties into own source
-rw-r--r-- | Source/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Source/cmGeneratorTarget.cxx | 708 | ||||
-rw-r--r-- | Source/cmGeneratorTarget_CompatibleInterface.cxx | 737 | ||||
-rwxr-xr-x | bootstrap | 1 |
4 files changed, 739 insertions, 708 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 6e41fcb..5962719 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -285,6 +285,7 @@ add_library( cmGeneratorExpression.h cmGeneratorTarget.cxx cmGeneratorTarget.h + cmGeneratorTarget_CompatibleInterface.cxx cmGeneratorTarget_IncludeDirectories.cxx cmGeneratorTarget_LinkDirectories.cxx cmGeneratorTarget_Options.cxx diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index ad821b6..6238ed2 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -6,12 +6,9 @@ #include <array> #include <cassert> #include <cctype> -#include <cerrno> #include <cstddef> #include <cstdio> -#include <cstdlib> #include <cstring> -#include <iterator> #include <sstream> #include <type_traits> #include <unordered_set> @@ -3920,676 +3917,6 @@ void cmGeneratorTarget::ConstructSourceFileFlags() const } } -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); - 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); - - 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::SetDeviceLink(bool deviceLink) { bool previous = this->DeviceLink; @@ -4597,41 +3924,6 @@ bool cmGeneratorTarget::SetDeviceLink(bool deviceLink) return previous; } -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); -} - cmComputeLinkInformation* cmGeneratorTarget::GetLinkInformation( const std::string& config) const { diff --git a/Source/cmGeneratorTarget_CompatibleInterface.cxx b/Source/cmGeneratorTarget_CompatibleInterface.cxx new file mode 100644 index 0000000..b6eb373 --- /dev/null +++ b/Source/cmGeneratorTarget_CompatibleInterface.cxx @@ -0,0 +1,737 @@ +/* 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" + +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); + 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); + + 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); +} @@ -381,6 +381,7 @@ CMAKE_CXX_SOURCES="\ cmGeneratorExpressionNode \ cmGeneratorExpressionParser \ cmGeneratorTarget \ + cmGeneratorTarget_CompatibleInterface \ cmGeneratorTarget_IncludeDirectories \ cmGeneratorTarget_LinkDirectories \ cmGeneratorTarget_Options \ |