From 0c85c16932da9b7dc0e0961e5c7e49cad92eac0e Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 8 Mar 2022 23:17:03 -0600 Subject: [PATCH] Core: Support disabling Addon by FreeCAD version If package.xml metadata file exists, it is scanned for FreeCAD version compatibility before the Addon is loaded. If the Addon specifies that it is explicitly not compatible with the current version of FreeCAD, the Addon is not loaded. --- src/App/FreeCADInit.py | 18 ++++++++++++++++-- src/App/Metadata.cpp | 19 ++++++++++++++++++- src/App/Metadata.h | 6 ++++++ src/App/MetadataPy.xml | 10 ++++++++++ src/App/MetadataPyImp.cpp | 19 +++++++++++++++++-- src/Gui/FreeCADGuiInit.py | 15 ++++++++++++++- 6 files changed, 81 insertions(+), 6 deletions(-) diff --git a/src/App/FreeCADInit.py b/src/App/FreeCADInit.py index 685165c97f..9ab51ebbd0 100644 --- a/src/App/FreeCADInit.py +++ b/src/App/FreeCADInit.py @@ -200,10 +200,16 @@ def InitApplications(): MetadataFile = os.path.join(Dir, "package.xml") if os.path.exists(MetadataFile): meta = FreeCAD.Metadata(MetadataFile) + if not meta.supportsCurrentFreeCAD(): + Msg(f'NOTICE: {meta.Name} does not support this version of FreeCAD, so is being skipped\n') + continue content = meta.Content if "workbench" in content: workbenches = content["workbench"] for workbench in workbenches: + if not workbench.supportsCurrentFreeCAD(): + Msg(f'NOTICE: {meta.Name} content item {workbench.Name} does not support this version of FreeCAD, so is being skipped\n') + continue subdirectory = workbench.Name if not workbench.Subdirectory else workbench.Subdirectory subdirectory = subdirectory.replace("/",os.path.sep) subdirectory = os.path.join(Dir, subdirectory) @@ -226,12 +232,20 @@ def InitApplications(): if freecad_module_ispkg: Log('Init: Initializing ' + freecad_module_name + '\n') try: - - stopFile = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", freecad_module_name, "ADDON_DISABLED") + # Check for a stopfile + stopFile = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", freecad_module_name[8:], "ADDON_DISABLED") if os.path.exists(stopFile): Msg(f'NOTICE: Addon "{freecad_module_name}" disabled by presence of ADDON_DISABLED stopfile\n') continue + # Make sure that package.xml (if present) does not exclude this version of FreeCAD + MetadataFile = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", freecad_module_name[8:], "package.xml") + if os.path.exists(MetadataFile): + meta = FreeCAD.Metadata(MetadataFile) + if not meta.supportsCurrentFreeCAD(): + Msg(f'NOTICE: Addon "{freecad_module_name}" does not support this version of FreeCAD, so is being skipped\n') + continue + freecad_module = importlib.import_module(freecad_module_name) extension_modules += [freecad_module_name] if any (module_name == 'init' for _, module_name, ispkg in pkgutil.iter_modules(freecad_module.__path__)): diff --git a/src/App/Metadata.cpp b/src/App/Metadata.cpp index dc0e4973ec..7be5c24a53 100644 --- a/src/App/Metadata.cpp +++ b/src/App/Metadata.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) 2021 Chris Hennes * + * Copyright (c) 2022 FreeCAD Project Association * * * * This file is part of the FreeCAD CAx development system. * * * @@ -456,6 +456,23 @@ bool Metadata::satisfies(const Meta::Dependency& dep) return true; } +bool App::Metadata::supportsCurrentFreeCAD() const +{ + static auto fcVersion = Meta::Version(); + if (fcVersion == Meta::Version()) { + std::map& config = App::Application::Config(); + std::stringstream ss; + ss << config["BuildVersionMajor"] << "." << config["BuildVersionMinor"] << "." << config["BuildRevision"].empty() ? "0" : config["BuildRevision"]; + fcVersion = Meta::Version(ss.str()); + } + + if (_freecadmin != Meta::Version() && _freecadmin > fcVersion) + return false; + else if (_freecadmax != Meta::Version() && _freecadmax < fcVersion) + return false; + return true; +} + void Metadata::appendToElement(DOMElement* root) const { appendSimpleXMLNode(root, "name", _name); diff --git a/src/App/Metadata.h b/src/App/Metadata.h index 67b1f76a25..968d91b51d 100644 --- a/src/App/Metadata.h +++ b/src/App/Metadata.h @@ -268,6 +268,12 @@ namespace App { */ bool satisfies(const Meta::Dependency&); + /** + * Determine whether the current metadata specifies support for the currently-running version of FreeCAD. + * Does not interrogate content items, which must be querried individually. + */ + bool supportsCurrentFreeCAD() const; + private: std::string _name; diff --git a/src/App/MetadataPy.xml b/src/App/MetadataPy.xml index 7736ee5d3c..c5202c56e0 100644 --- a/src/App/MetadataPy.xml +++ b/src/App/MetadataPy.xml @@ -174,6 +174,16 @@ limited to 0.20 as the lowest known version since the metadata standard was adde + + + supportsCurrentFreeCAD() +Returns false if this metadata object directly indicates that it does not support the current +version of FreeCAD, or true if it makes no indication, or specifically indicates that it does +support the current version. Does not recurse into Content items. + + + + getGenericMetadata(name) diff --git a/src/App/MetadataPyImp.cpp b/src/App/MetadataPyImp.cpp index e88e8d7f3c..5c0785f985 100644 --- a/src/App/MetadataPyImp.cpp +++ b/src/App/MetadataPyImp.cpp @@ -318,8 +318,11 @@ void MetadataPy::setFreeCADMax(Py::Object args) getMetadataPtr()->setFreeCADMax(App::Meta::Version(version)); } -PyObject* MetadataPy::getFirstSupportedFreeCADVersion(PyObject*) +PyObject* MetadataPy::getFirstSupportedFreeCADVersion(PyObject* p) { + if (!PyArg_ParseTuple(p, "")) + return nullptr; + // Short-circuit: if the toplevel sets a version, then the lower-levels are overridden if (getMetadataPtr()->freecadmin() != App::Meta::Version()) return Py::new_reference_to(Py::String(getMetadataPtr()->freecadmin().str())); @@ -341,8 +344,11 @@ PyObject* MetadataPy::getFirstSupportedFreeCADVersion(PyObject*) } } -PyObject* MetadataPy::getLastSupportedFreeCADVersion(PyObject*) +PyObject* MetadataPy::getLastSupportedFreeCADVersion(PyObject* p) { + if (!PyArg_ParseTuple(p, "")) + return nullptr; + // Short-circuit: if the toplevel sets a version, then the lower-levels are overridden if (getMetadataPtr()->freecadmax() != App::Meta::Version()) return Py::new_reference_to(Py::String(getMetadataPtr()->freecadmax().str())); @@ -364,6 +370,15 @@ PyObject* MetadataPy::getLastSupportedFreeCADVersion(PyObject*) } } +PyObject* MetadataPy::supportsCurrentFreeCAD(PyObject* p) +{ + if (!PyArg_ParseTuple(p, "")) + return nullptr; + + bool result = getMetadataPtr()->supportsCurrentFreeCAD(); + return Py::new_reference_to(Py::Boolean(result)); +} + PyObject* MetadataPy::getCustomAttributes(const char* /*attr*/) const { return 0; diff --git a/src/Gui/FreeCADGuiInit.py b/src/Gui/FreeCADGuiInit.py index b42db6e412..4e03b4fce5 100644 --- a/src/Gui/FreeCADGuiInit.py +++ b/src/Gui/FreeCADGuiInit.py @@ -150,11 +150,15 @@ def InitApplications(): MetadataFile = os.path.join(Dir, "package.xml") if os.path.exists(MetadataFile): meta = FreeCAD.Metadata(MetadataFile) + if not meta.supportsCurrentFreeCAD(): + continue content = meta.Content if "workbench" in content: FreeCAD.Gui.addIconPath(Dir) workbenches = content["workbench"] for workbench_metadata in workbenches: + if not workbench_metadata.supportsCurrentFreeCAD(): + continue subdirectory = workbench_metadata.Name if not workbench_metadata.Subdirectory else workbench_metadata.Subdirectory subdirectory = subdirectory.replace("/",os.path.sep) subdirectory = os.path.join(Dir, subdirectory) @@ -183,9 +187,18 @@ def InitApplications(): import freecad freecad.gui = FreeCADGui for _, freecad_module_name, freecad_module_ispkg in pkgutil.iter_modules(freecad.__path__, "freecad."): - stopFile = os.path.join(Dir, "ADDON_DISABLED") + # Check for a stopfile + stopFile = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", freecad_module_name[8:], "ADDON_DISABLED") if os.path.exists(stopFile): continue + + # Make sure that package.xml (if present) does not exclude this version of FreeCAD + MetadataFile = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", freecad_module_name[8:], "package.xml") + if os.path.exists(MetadataFile): + meta = FreeCAD.Metadata(MetadataFile) + if not meta.supportsCurrentFreeCAD(): + continue + if freecad_module_ispkg: Log('Init: Initializing ' + freecad_module_name + '\n') try: