From 7230f0ef9b84b9916c4a130f8788ace4393859ba Mon Sep 17 00:00:00 2001 From: Alec Leamas Date: Mon, 15 Mar 2021 10:54:09 +0100 Subject: [PATCH] isCompatible: Refactor and assume compile time setup (#2185). Fix the issues mentioned in #2185. This includes removing the runtime target detection. Instead, a new cmake option OCPN_TARGET_TUPLE is added which hardwires the target platform. OCPN_TARGET_TUPLE must be defined on all platforms where the platform detection in configure time fails. This includes all cross compiled platforms. The syntax for OCPN_TARGET_TUPLE is "target;target-version;arch" for example "flatpak-x86_64;18.08;x86_64". These corresponds to the elements , and in the metadata file. Using values not accepted in the XML metadata is not recommended. --- cmake/GetArch.cmake | 7 +- cmake/TargetSetup.cmake | 6 +- flatpak/org.opencpn.OpenCPN.yaml | 1 + include/OCPNPlatform.h | 2 +- include/PluginHandler.h | 4 +- src/OCPNPlatform.cpp | 2 +- src/PluginHandler.cpp | 277 ++++++++++++++----------------- 7 files changed, 136 insertions(+), 163 deletions(-) diff --git a/cmake/GetArch.cmake b/cmake/GetArch.cmake index 9eadae5820..61445e1651 100644 --- a/cmake/GetArch.cmake +++ b/cmake/GetArch.cmake @@ -9,8 +9,8 @@ # Based on code from nohal function(GetArch) - if (NOT "${OCPN_TARGET_TUPLE}" STREQUAL "") - list(GET ${OCPN_TARGET_TUPLE} 2 ARCH) + if (NOT OCPN_TARGET_TUPLE STREQUAL "") + list(GET OCPN_TARGET_TUPLE 2 ARCH) elseif (WIN32) # Should really be i386 since we are on win32. However, it's x86_64 for now, # see #2027 @@ -45,6 +45,9 @@ function(GetArch) elseif (EXISTS /etc/gentoo-release) set (LIB_INSTALL_DIR "lib${LIB_SUFFIX}") endif () + if (OCPN_FLATPAK AND ARCH STREQUAL "arm64") + set(ARCH "aarch64") + endif () endif () set (LIB_INSTALL_DIR ${LIB_INSTALL_DIR} PARENT_SCOPE) set (ARCH ${ARCH} PARENT_SCOPE) diff --git a/cmake/TargetSetup.cmake b/cmake/TargetSetup.cmake index d364f76f54..93ceb63220 100644 --- a/cmake/TargetSetup.cmake +++ b/cmake/TargetSetup.cmake @@ -1,9 +1,9 @@ # # Export variables used in plugin setup: PKG_TARGET, PKG_TARGET_VERSION. -if (NOT "${OCPN_TARGET_TUPLE}" STREQUAL "") - list(GET ${_target} 0 PKG_TARGET) - list(GET ${_target} 1 PKG_TARGET_VERSION) +if (NOT OCPN_TARGET_TUPLE STREQUAL "") + list(GET OCPN_TARGET_TUPLE 0 PKG_TARGET) + list(GET OCPN_TARGET_TUPLE 1 PKG_TARGET_VERSION) elseif (OCPN_FLATPAK) set(PKG_TARGET "flatpak") set(manifest_path "${PROJECT_SOURCE_DIR}/flatpak/org.opencpn.OpenCPN.yaml") diff --git a/flatpak/org.opencpn.OpenCPN.yaml b/flatpak/org.opencpn.OpenCPN.yaml index 97a9fbb91d..18e33d2a38 100644 --- a/flatpak/org.opencpn.OpenCPN.yaml +++ b/flatpak/org.opencpn.OpenCPN.yaml @@ -83,6 +83,7 @@ modules: - -DOCPN_CI_BUILD=ON - -DOCPN_FLATPAK=ON - -DBUILD_SHARED_LIBS=OFF + - -DOCPN_TARGET_TUPLE=flatpak-x86_64;18.08;x86_64 - -DCMAKE_FIND_ROOT_PATH=/app - -DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER - -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH diff --git a/include/OCPNPlatform.h b/include/OCPNPlatform.h index 06275365f7..6298bec700 100644 --- a/include/OCPNPlatform.h +++ b/include/OCPNPlatform.h @@ -58,7 +58,7 @@ class OCPN_OSDetail std::string osd_name; std::string osd_version; - std::vector osd_name_like; + std::vector osd_names_like; std::string osd_arch; std::string osd_ID; std::string osd_build_name; diff --git a/include/PluginHandler.h b/include/PluginHandler.h index 23ff09121f..0137ed9ec0 100644 --- a/include/PluginHandler.h +++ b/include/PluginHandler.h @@ -77,8 +77,8 @@ class CompatOs { public: static CompatOs* getInstance(); - std::string name() { return _name; } - std::string version() { return _version; } + std::string name() const { return _name; } + std::string version() const { return _version; } private: CompatOs(); diff --git a/src/OCPNPlatform.cpp b/src/OCPNPlatform.cpp index b74d48bc22..581be6eae6 100644 --- a/src/OCPNPlatform.cpp +++ b/src/OCPNPlatform.cpp @@ -396,7 +396,7 @@ bool OCPNPlatform::DetectOSDetail( OCPN_OSDetail *detail) } if(val.Length()){ - detail->osd_name_like = ocpn::split(val.mb_str(), " "); + detail->osd_names_like = ocpn::split(val.mb_str(), " "); } } diff --git a/src/PluginHandler.cpp b/src/PluginHandler.cpp index 8e35047c7a..19f2184c20 100644 --- a/src/PluginHandler.cpp +++ b/src/PluginHandler.cpp @@ -162,6 +162,102 @@ static std::string dirListPath(std::string name) } +/** Plugin ABI encapsulation. */ +class Plugin { + public: + Plugin(const PluginMetadata& metadata) { + wxLogDebug("Plugin: setting up name: %s", metadata.name); + m_abi = metadata.target; + m_abi_version = metadata.target_version; + m_major_version = ocpn::split(m_abi_version.c_str(), ".")[0]; + wxLogDebug("Plugin: init: abi: %s, abi_version: %s, major ver: %s", + m_abi, m_abi_version, m_major_version); + + } + const std::string& abi() const { return m_abi; } + const std::string& abi_version() const { return m_abi_version; } + const std::string& major_version() const { return m_major_version; } + const std::string& name() const { return m_name; } + + private: + std::string m_abi; + std::string m_abi_version; + std::string m_major_version; + std::string m_name; +}; + + +/** Host ABI encapsulation and plugin compatibility checks. */ +class Host { + public: + Host(CompatOs* compatOs) { + m_abi = compatOs->name(); + m_abi_version = compatOs->version(); + m_major_version = ocpn::split(m_abi_version.c_str(), ".")[0]; + wxLogDebug("Host: init: abi: %s, abi_version: %s, major ver: %s", + m_abi, m_abi_version, m_major_version); + } + + bool is_version_compatible(const Plugin& plugin) const { + if (ocpn::startswith(plugin.abi(), "ubuntu")) { + return plugin.abi_version() == m_abi_version; + } + return plugin.major_version() == m_major_version; + } + + // Test if plugin abi is a Debian version compatible with hosts's + // ubuntu version. + bool is_debian_plugin_compatible(const Plugin& plugin) const { + static const std::vector debian_versions = { + "9;ubuntu-x86_64;16.04", + "11;ubuntu-gtk3-x86_64;20.04" + "sid;ubuntu-gtk3-x86_64;20.04" + }; + if (ocpn::startswith(m_abi, "debian-x86_64")) { + wxLogDebug("Checking for debian and ubuntu"); + const std::string host_version = + m_major_version + ";" + plugin.abi() + ";" + + plugin.abi_version(); + for (auto& v: debian_versions) { + if (host_version == v) { + return true; + } + } + } + return false; + } + + // Plugin and host abi differs. Check if plugin is compatible anyway + // by comparing the plugin abi with the list of abis similar to the + // host abi. Typically a host on a Ubuntu derivative which might be + // compatible with a plugin built for the derivative's Ubuntu base. + bool is_similar_plugin_compatible(const Plugin& plugin) const { + OCPN_OSDetail* os_detail = g_Platform->GetOSDetail(); + for (auto& name_like: os_detail->osd_names_like) { + const std::string osd_abi = + name_like + "-" + os_detail->osd_arch; + if (osd_abi == plugin.abi()) { + if (is_version_compatible(plugin)) { + return true; + } + } + } + return false; + } + + const std::string& abi() const { return m_abi; } + + const std::string& abi_version() const { return m_abi_version; } + + const std::string& major_version() const { return m_major_version; } + + private: + std::string m_abi; + std::string m_abi_version; + std::string m_major_version; +}; + + CompatOs* CompatOs::getInstance() { static std::string last_global_os(""); @@ -172,30 +268,19 @@ CompatOs* CompatOs::getInstance() last_global_os = g_compatOS; } return instance; -} +}; CompatOs::CompatOs(): _name(PKG_TARGET), _version(PKG_TARGET_VERSION) { // Get the specified system definition, - // From the OCPN_OSDetail structure probed at startup. - // or the environment override, + // from the environment override, // or the config file override - // or the baked in (build system) values. Not too useful in cross-build environments... - - OCPN_OSDetail *os_detail = g_Platform->GetOSDetail(); + // or the baked in (build system) values. std::string compatOS(_name); std::string compatOsVersion(_version); - // Handle the most common cross-compile, safely -#ifdef ocpnARM - if(os_detail->osd_ID.size()) - compatOS = os_detail->osd_ID; - if(os_detail->osd_version.size()) - compatOsVersion = os_detail->osd_version; -#endif - if (getenv("OPENCPN_COMPAT_TARGET") != 0) { _name = getenv("OPENCPN_COMPAT_TARGET"); if (_name.find(':') != std::string::npos) { @@ -222,154 +307,38 @@ std::string PluginHandler::fileListPath(std::string name) return pluginsConfigDir() + SEP + name + ".files"; } + bool PluginHandler::isCompatible(const PluginMetadata& metadata, const char* os, const char* os_version) - { - wxLogDebug("Plugin compatibility check"); - wxLogDebug("name: %s, target: %s, target_arch: %s", - metadata.name, metadata.target, metadata.target_arch); - OCPN_OSDetail *os_detail = g_Platform->GetOSDetail(); - - // Get the specified system definition, - // From the OCPN_OSDetail structure probed at startup. - // or the environment override, - // or the config file override - // or the baked in (build system) values. Not too useful in cross-build environments... - - std::string compatOS(os); - std::string compatOsVersion(os_version); - - // Handle the most common cross-compile, safely -#ifdef ocpnARM - if(os_detail->osd_ID.size()) - compatOS = os_detail->osd_ID; - if(os_detail->osd_version.size()) - compatOsVersion = os_detail->osd_version; -#endif - - if (getenv("OPENCPN_COMPAT_TARGET") != 0) { - // Undocumented test hook. - compatOS = getenv("OPENCPN_COMPAT_TARGET"); - if (compatOS.find(':') != std::string::npos) { - auto tokens = ocpn::split(compatOS.c_str(), ":"); - compatOS = tokens[0]; - compatOsVersion = tokens[1]; - } - } - else if (g_compatOS != "") { - // CompatOS and CompatOsVersion in opencpn.conf/.ini file. - compatOS = g_compatOS; - if (g_compatOsVersion != ""){ - compatOsVersion = g_compatOsVersion; - } - } - compatOS = ocpn::tolower(compatOS); - compatOsVersion = ocpn::tolower(compatOsVersion); - - // Compare to the required values in the metadata - std::string plugin_os = ocpn::tolower(metadata.target); - - // msvc is simple... - if (compatOS == "msvc") { - return (plugin_os == "msvc"); - } - - // And so is MacOS... - if (compatOS == "darwin") { - return (plugin_os == "darwin"); + auto compatOS = CompatOs::getInstance(); + Host host(compatOS); + Plugin plugin(metadata); + + if (plugin.abi() == "msvc" + || plugin.abi() == "darwin" + || plugin.abi() == "android-armeabi-v7a" + || plugin.abi() == "android-arm64-v8a" + ) { + bool ok = plugin.abi() == host.abi(); + wxLogDebug("Returning %s for %s", (ok ? "ok" : "fail"), host.abi()); + return ok; } - - const std::string compatOS_ARCH = - compatOS + "-" + ocpn::tolower(os_detail->osd_arch); - DEBUG_LOG << "Plugin compatibility check1: " << metadata.name - << " OS: " << compatOS_ARCH << " Plugin: " << plugin_os; - bool rv = false; - std::string plugin_os_version = ocpn::tolower(metadata.target_version); - - auto meta_vers = ocpn::split(plugin_os_version.c_str(), ".")[0]; - DEBUG_LOG << "compatOS_ARCH: " << compatOS_ARCH - << " compatOS_Build_ARCH: " << os_detail->osd_build_arch - << " build target: " << PKG_TARGET << "plugin_os: " << plugin_os; - if (compatOS_ARCH == plugin_os - || os_detail->osd_build_arch == metadata.target_arch - ) { - // OS matches so far, so must compare versions - - if (ocpn::startswith(plugin_os, "ubuntu")){ - DEBUG_LOG - << "plugin_os_version: " << plugin_os_version - << " CompatOsVersion: " << compatOsVersion - << " osd_build_version: " << os_detail->osd_build_version; - if (plugin_os_version == compatOsVersion - || plugin_os_version == os_detail->osd_build_version - ) { - // Full version comparison required - rv = true; - } - } - else{ - auto target_vers = ocpn::split(compatOsVersion.c_str(), ".")[0]; - DEBUG_LOG << "meta_vers: " << meta_vers - << " target_vers; " << target_vers - << " osd_build_version: " << os_detail->osd_build_version - << " version: " << metadata.version; - if (meta_vers == target_vers - || os_detail->osd_build_version == metadata.version - ) { - rv = true;; - } - } + if (host.abi() == plugin.abi() && host.is_version_compatible(plugin)) { + rv = true; + wxLogDebug("Found matching abi version %s", plugin.abi_version()); } - else { - // running OS may be "like" some known OS - for(unsigned int i=0 ; i < os_detail->osd_name_like.size(); i++) { - std::string osd_like_arch = - os_detail->osd_name_like[i] + "-" + os_detail->osd_arch; - if( osd_like_arch == plugin_os){ - if (ocpn::startswith(plugin_os, "ubuntu")){ - if( plugin_os_version == os_detail->osd_version ) { - // Full version comparison required - rv = true; - } - } - else{ - auto target_vers = - ocpn::split(os_detail->osd_version.c_str(), ".")[0]; - if( meta_vers == target_vers ) - rv = true; - } - } - } + else if (host.is_similar_plugin_compatible(plugin)) { + rv = true; + wxLogDebug("Found similar abi"); } - - // Special case tests for vanilla debian, which can use some variants - // x of Ubuntu plugins - if (!rv){ - wxLogDebug("Checking for debian and ubuntu"); - if (ocpn::startswith(compatOS_ARCH, "debian-x86_64")){ - auto target_vers = ocpn::split(compatOsVersion.c_str(), ".")[0]; - if (target_vers == std::string("9") ){ // Stretch - if (plugin_os == std::string("ubuntu-x86_64") - && plugin_os_version == std::string("16.04") - ) { - rv = true; - } - } - else if (target_vers == "11" || target_vers == "sid") { - if (plugin_os == std::string("ubuntu-gtk3-x86_64") - && plugin_os_version == std::string("20.04") - ) { - rv = true; - } - } - } + else if (host.is_debian_plugin_compatible(plugin)) { + rv = true; + wxLogDebug("Found Ubuntu version matching Debian host"); } - DEBUG_LOG << "Plugin compatibility checkFinal: " - << (rv ? "ACCEPTED" : "REJECTED") << ":" << metadata.name - << " PluginOS: " << plugin_os - << " PluginVersion: " << plugin_os_version; + DEBUG_LOG << "Plugin compatibility check Final: " + << (rv ? "ACCEPTED: " : "REJECTED: ") << metadata.name; return rv; }