From d63bd7e9def528d3ed59a2798452ac1da58ebea4 Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Fri, 24 Apr 2020 18:12:54 +0900 Subject: Plugin loading refactor, removing all versioning Plugin format versioning was decided to be removed for local plugins and any plugins for which we do not load an explicitly provided plugin. For pip, this will be handled with a standard distutils/setuptools approach, allowing users to specify pip style version boundaries in the plugin origins. This patch refactors plugin loading so that all related code goes into the private _pluginfactory module, a new small PluginOrigin type was added to better manipulate loaded origins. Test cases have been removed and will be readded in a following commit, adjusted to new expectations. --- doc/examples/flatpak-autotools/project.conf | 2 +- src/buildstream/_elementfactory.py | 65 ----- src/buildstream/_plugincontext.py | 306 --------------------- src/buildstream/_pluginfactory/__init__.py | 20 ++ src/buildstream/_pluginfactory/elementfactory.py | 58 ++++ src/buildstream/_pluginfactory/pluginfactory.py | 271 ++++++++++++++++++ src/buildstream/_pluginfactory/pluginorigin.py | 130 +++++++++ src/buildstream/_pluginfactory/sourcefactory.py | 59 ++++ src/buildstream/_project.py | 101 +------ src/buildstream/_sourcefactory.py | 66 ----- src/buildstream/plugin.py | 21 -- src/buildstream/testing/_sourcetests/utils.py | 2 +- tests/elements/filter/basic/project.conf | 2 +- tests/format/project.py | 4 +- .../format/project/duplicate-plugins/project.conf | 8 +- tests/format/project/local-plugin/project.conf | 2 +- tests/format/project/plugin-allowed/project.conf | 2 +- .../project/plugin-preflight-error/project.conf | 2 +- tests/frontend/configurable_warnings.py | 4 +- tests/frontend/configuredwarning/project.conf | 4 +- tests/frontend/consistencyerror/project.conf | 4 +- tests/frontend/mirror.py | 2 +- tests/frontend/push.py | 2 +- tests/internals/pluginfactory.py | 287 ------------------- .../pluginfactory/anotherelement/__init__.py | 0 .../internals/pluginfactory/anotherelement/foo.py | 9 - .../pluginfactory/anothersource/__init__.py | 0 tests/internals/pluginfactory/anothersource/foo.py | 9 - tests/internals/pluginfactory/badsetup/__init__.py | 0 tests/internals/pluginfactory/badsetup/foo.py | 6 - .../pluginfactory/badversionelement/__init__.py | 0 .../pluginfactory/badversionelement/foo.py | 11 - .../pluginfactory/badversionsource/__init__.py | 0 .../pluginfactory/badversionsource/foo.py | 11 - .../pluginfactory/customelement/__init__.py | 0 tests/internals/pluginfactory/customelement/foo.py | 9 - .../pluginfactory/customsource/__init__.py | 0 tests/internals/pluginfactory/customsource/foo.py | 9 - tests/internals/pluginfactory/nosetup/__init__.py | 0 tests/internals/pluginfactory/nosetup/foo.py | 8 - tests/internals/pluginfactory/notatype/__init__.py | 0 tests/internals/pluginfactory/notatype/foo.py | 6 - .../internals/pluginfactory/wrongtype/__init__.py | 0 tests/internals/pluginfactory/wrongtype/foo.py | 12 - tests/internals/pluginloading.py | 22 -- .../badversionelement/customelements/__init__.py | 0 .../badversionelement/customelements/foo.py | 19 -- .../badversionelement/elements/simple.bst | 4 - .../pluginloading/badversionelement/project.conf | 10 - .../badversionsource/customsources/__init__.py | 0 .../badversionsource/customsources/foo.py | 16 -- .../badversionsource/elements/simple.bst | 6 - .../pluginloading/badversionsource/project.conf | 10 - .../pluginloading/customelement/project.conf | 2 +- .../pluginloading/customsource/project.conf | 2 +- .../deprecationwarnings/project/project.conf | 2 +- tests/sourcecache/project/project.conf | 2 +- tests/sources/no-fetch-cached/project.conf | 2 +- tests/sources/previous_source_access/project.conf | 2 +- tests/sources/project_key_test/project.conf | 2 +- 60 files changed, 576 insertions(+), 1039 deletions(-) delete mode 100644 src/buildstream/_elementfactory.py delete mode 100644 src/buildstream/_plugincontext.py create mode 100644 src/buildstream/_pluginfactory/__init__.py create mode 100644 src/buildstream/_pluginfactory/elementfactory.py create mode 100644 src/buildstream/_pluginfactory/pluginfactory.py create mode 100644 src/buildstream/_pluginfactory/pluginorigin.py create mode 100644 src/buildstream/_pluginfactory/sourcefactory.py delete mode 100644 src/buildstream/_sourcefactory.py delete mode 100644 tests/internals/pluginfactory.py delete mode 100644 tests/internals/pluginfactory/anotherelement/__init__.py delete mode 100644 tests/internals/pluginfactory/anotherelement/foo.py delete mode 100644 tests/internals/pluginfactory/anothersource/__init__.py delete mode 100644 tests/internals/pluginfactory/anothersource/foo.py delete mode 100644 tests/internals/pluginfactory/badsetup/__init__.py delete mode 100644 tests/internals/pluginfactory/badsetup/foo.py delete mode 100644 tests/internals/pluginfactory/badversionelement/__init__.py delete mode 100644 tests/internals/pluginfactory/badversionelement/foo.py delete mode 100644 tests/internals/pluginfactory/badversionsource/__init__.py delete mode 100644 tests/internals/pluginfactory/badversionsource/foo.py delete mode 100644 tests/internals/pluginfactory/customelement/__init__.py delete mode 100644 tests/internals/pluginfactory/customelement/foo.py delete mode 100644 tests/internals/pluginfactory/customsource/__init__.py delete mode 100644 tests/internals/pluginfactory/customsource/foo.py delete mode 100644 tests/internals/pluginfactory/nosetup/__init__.py delete mode 100644 tests/internals/pluginfactory/nosetup/foo.py delete mode 100644 tests/internals/pluginfactory/notatype/__init__.py delete mode 100644 tests/internals/pluginfactory/notatype/foo.py delete mode 100644 tests/internals/pluginfactory/wrongtype/__init__.py delete mode 100644 tests/internals/pluginfactory/wrongtype/foo.py delete mode 100644 tests/internals/pluginloading/badversionelement/customelements/__init__.py delete mode 100644 tests/internals/pluginloading/badversionelement/customelements/foo.py delete mode 100644 tests/internals/pluginloading/badversionelement/elements/simple.bst delete mode 100644 tests/internals/pluginloading/badversionelement/project.conf delete mode 100644 tests/internals/pluginloading/badversionsource/customsources/__init__.py delete mode 100644 tests/internals/pluginloading/badversionsource/customsources/foo.py delete mode 100644 tests/internals/pluginloading/badversionsource/elements/simple.bst delete mode 100644 tests/internals/pluginloading/badversionsource/project.conf diff --git a/doc/examples/flatpak-autotools/project.conf b/doc/examples/flatpak-autotools/project.conf index 08dc6ee32..d7a0a396d 100644 --- a/doc/examples/flatpak-autotools/project.conf +++ b/doc/examples/flatpak-autotools/project.conf @@ -19,4 +19,4 @@ plugins: - origin: pip package-name: bst-plugins-experimental sources: - ostree: 0 + - ostree diff --git a/src/buildstream/_elementfactory.py b/src/buildstream/_elementfactory.py deleted file mode 100644 index 89e01a885..000000000 --- a/src/buildstream/_elementfactory.py +++ /dev/null @@ -1,65 +0,0 @@ -# -# Copyright (C) 2016 Codethink Limited -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library. If not, see . -# -# Authors: -# Tristan Van Berkom - -from . import _site -from ._plugincontext import PluginContext -from .element import Element - - -# A ElementFactory creates Element instances -# in the context of a given factory -# -# Args: -# plugin_base (PluginBase): The main PluginBase object to work with -# plugin_origins (list): Data used to search for external Element plugins -# -class ElementFactory(PluginContext): - def __init__(self, plugin_base, *, format_versions={}, plugin_origins=None): - - super().__init__( - plugin_base, - Element, - [_site.element_plugins], - "buildstream.plugins.elements", - plugin_origins=plugin_origins, - format_versions=format_versions, - ) - - # create(): - # - # Create an Element object, the pipeline uses this to create Element - # objects on demand for a given pipeline. - # - # Args: - # context (object): The Context object for processing - # project (object): The project object - # meta (object): The loaded MetaElement - # - # Returns: A newly created Element object of the appropriate kind - # - # Raises: - # PluginError (if the kind lookup failed) - # LoadError (if the element itself took issue with the config) - # - def create(self, context, project, meta): - element_type, default_config = self.lookup(meta.kind) - element = element_type(context, project, meta, default_config) - version = self._format_versions.get(meta.kind, 0) - self._assert_plugin_format(element, version) - return element diff --git a/src/buildstream/_plugincontext.py b/src/buildstream/_plugincontext.py deleted file mode 100644 index 3a195e239..000000000 --- a/src/buildstream/_plugincontext.py +++ /dev/null @@ -1,306 +0,0 @@ -# -# Copyright (C) 2016 Codethink Limited -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library. If not, see . -# -# Authors: -# Tristan Van Berkom - -import os -import inspect - -from ._exceptions import PluginError, LoadError -from .exceptions import LoadErrorReason -from . import utils - - -# A Context for loading plugin types -# -# Args: -# plugin_base (PluginBase): The main PluginBase object to work with -# base_type (type): A base object type for this context -# site_plugin_path (str): Path to where buildstream keeps plugins -# entrypoint_group (str): Name of the entry point group that provides plugins -# plugin_origins (list): Data used to search for plugins -# format_versions (dict): A dict of meta.kind to the integer minimum -# version number for each plugin to be loaded -# -# Since multiple pipelines can be processed recursively -# within the same interpretor, it's important that we have -# one context associated to the processing of a given pipeline, -# this way sources and element types which are particular to -# a given BuildStream project are isolated to their respective -# Pipelines. -# -class PluginContext: - def __init__( - self, plugin_base, base_type, site_plugin_path, entrypoint_group, *, plugin_origins=None, format_versions={} - ): - - # For pickling across processes, make sure this context has a unique - # identifier, which we prepend to the identifier of each PluginSource. - # This keeps plugins loaded during the first and second pass distinct - # from eachother. - self._identifier = str(id(self)) - - # The plugin kinds which were loaded - self.loaded_dependencies = [] - - # - # Private members - # - self._base_type = base_type # The base class plugins derive from - self._types = {} # Plugin type lookup table by kind - self._plugin_origins = plugin_origins or [] - - # The PluginSource object - self._plugin_base = plugin_base - self._site_plugin_path = site_plugin_path - self._entrypoint_group = entrypoint_group - self._alternate_sources = {} - self._format_versions = format_versions - - self._init_site_source() - - def _init_site_source(self): - self._site_source = self._plugin_base.make_plugin_source( - searchpath=self._site_plugin_path, identifier=self._identifier + "site", - ) - - def __getstate__(self): - state = self.__dict__.copy() - - # PluginSource is not a picklable type, so we must reconstruct this one - # as best we can when unpickling. - # - # Since the values of `_types` depend on the PluginSource, we must also - # get rid of those. It is only a cache - we will automatically recreate - # them on demand. - # - # Similarly we must clear out the `_alternate_sources` cache. - # - # Note that this method of referring to members is error-prone in that - # a later 'search and replace' renaming might miss these. Guard against - # this by making sure we are not creating new members, only clearing - # existing ones. - # - del state["_site_source"] - assert "_types" in state - state["_types"] = {} - assert "_alternate_sources" in state - state["_alternate_sources"] = {} - - return state - - def __setstate__(self, state): - self.__dict__.update(state) - - # Note that in order to enable plugins to be unpickled along with this - # PluginSource, we would also have to set and restore the 'identifier' - # of the PluginSource. We would also have to recreate `_types` as it - # was before unpickling them. We are not using this method in - # BuildStream, so the identifier is not restored here. - self._init_site_source() - - # lookup(): - # - # Fetches a type loaded from a plugin in this plugin context - # - # Args: - # kind (str): The kind of Plugin to create - # - # Returns: the type associated with the given kind - # - # Raises: PluginError - # - def lookup(self, kind): - return self._ensure_plugin(kind) - - # all_loaded_plugins(): - # - # Returns: an iterable over all the loaded plugins. - # - def all_loaded_plugins(self): - return self._types.values() - - def _get_local_plugin_source(self, path): - if ("local", path) not in self._alternate_sources: - # key by a tuple to avoid collision - source = self._plugin_base.make_plugin_source(searchpath=[path], identifier=self._identifier + path,) - # Ensure that sources never get garbage collected, - # as they'll take the plugins with them. - self._alternate_sources[("local", path)] = source - else: - source = self._alternate_sources[("local", path)] - return source - - def _get_pip_plugin_source(self, package_name, kind): - defaults = None - if ("pip", package_name) not in self._alternate_sources: - import pkg_resources - - # key by a tuple to avoid collision - try: - package = pkg_resources.get_entry_info(package_name, self._entrypoint_group, kind) - except pkg_resources.DistributionNotFound as e: - raise PluginError("Failed to load {} plugin '{}': {}".format(self._base_type.__name__, kind, e)) from e - - if package is None: - raise PluginError("Pip package {} does not contain a plugin named '{}'".format(package_name, kind)) - - location = package.dist.get_resource_filename( - pkg_resources._manager, package.module_name.replace(".", os.sep) + ".py" - ) - - # Also load the defaults - required since setuptools - # may need to extract the file. - try: - defaults = package.dist.get_resource_filename( - pkg_resources._manager, package.module_name.replace(".", os.sep) + ".yaml" - ) - except KeyError: - # The plugin didn't have an accompanying YAML file - defaults = None - - source = self._plugin_base.make_plugin_source( - searchpath=[os.path.dirname(location)], identifier=self._identifier + os.path.dirname(location), - ) - self._alternate_sources[("pip", package_name)] = source - - else: - source = self._alternate_sources[("pip", package_name)] - - return source, defaults - - def _ensure_plugin(self, kind): - - if kind not in self._types: - # Check whether the plugin is specified in plugins - source = None - defaults = None - loaded_dependency = False - - for origin in self._plugin_origins: - if kind not in origin.get_str_list("plugins"): - continue - - if origin.get_str("origin") == "local": - local_path = origin.get_str("path") - source = self._get_local_plugin_source(local_path) - elif origin.get_str("origin") == "pip": - package_name = origin.get_str("package-name") - source, defaults = self._get_pip_plugin_source(package_name, kind) - else: - raise PluginError( - "Failed to load plugin '{}': " - "Unexpected plugin origin '{}'".format(kind, origin.get_str("origin")) - ) - loaded_dependency = True - break - - # Fall back to getting the source from site - if not source: - if kind not in self._site_source.list_plugins(): - raise PluginError("No {} type registered for kind '{}'".format(self._base_type.__name__, kind)) - - source = self._site_source - - self._types[kind] = self._load_plugin(source, kind, defaults) - if loaded_dependency: - self.loaded_dependencies.append(kind) - - return self._types[kind] - - def _load_plugin(self, source, kind, defaults): - - try: - plugin = source.load_plugin(kind) - - if not defaults: - plugin_file = inspect.getfile(plugin) - plugin_dir = os.path.dirname(plugin_file) - plugin_conf_name = "{}.yaml".format(kind) - defaults = os.path.join(plugin_dir, plugin_conf_name) - - except ImportError as e: - raise PluginError("Failed to load {} plugin '{}': {}".format(self._base_type.__name__, kind, e)) from e - - try: - plugin_type = plugin.setup() - except AttributeError as e: - raise PluginError( - "{} plugin '{}' did not provide a setup() function".format(self._base_type.__name__, kind) - ) from e - except TypeError as e: - raise PluginError( - "setup symbol in {} plugin '{}' is not a function".format(self._base_type.__name__, kind) - ) from e - - self._assert_plugin(kind, plugin_type) - self._assert_version(kind, plugin_type) - return (plugin_type, defaults) - - def _assert_plugin(self, kind, plugin_type): - if kind in self._types: - raise PluginError( - "Tried to register {} plugin for existing kind '{}' " - "(already registered {})".format(self._base_type.__name__, kind, self._types[kind].__name__) - ) - try: - if not issubclass(plugin_type, self._base_type): - raise PluginError( - "{} plugin '{}' returned type '{}', which is not a subclass of {}".format( - self._base_type.__name__, kind, plugin_type.__name__, self._base_type.__name__ - ) - ) - except TypeError as e: - raise PluginError( - "{} plugin '{}' returned something that is not a type (expected subclass of {})".format( - self._base_type.__name__, kind, self._base_type.__name__ - ) - ) from e - - def _assert_version(self, kind, plugin_type): - - # Now assert BuildStream version - bst_major, bst_minor = utils.get_bst_version() - - req_major = plugin_type.BST_REQUIRED_VERSION_MAJOR - req_minor = plugin_type.BST_REQUIRED_VERSION_MINOR - - if (bst_major, bst_minor) < (req_major, req_minor): - raise PluginError( - "BuildStream {}.{} is too old for {} plugin '{}' (requires {}.{})".format( - bst_major, - bst_minor, - self._base_type.__name__, - kind, - plugin_type.BST_REQUIRED_VERSION_MAJOR, - plugin_type.BST_REQUIRED_VERSION_MINOR, - ) - ) - - # _assert_plugin_format() - # - # Helper to raise a PluginError if the loaded plugin is of a lesser version then - # the required version for this plugin - # - def _assert_plugin_format(self, plugin, version): - if plugin.BST_FORMAT_VERSION < version: - raise LoadError( - "{}: Format version {} is too old for requested version {}".format( - plugin, plugin.BST_FORMAT_VERSION, version - ), - LoadErrorReason.UNSUPPORTED_PLUGIN, - ) diff --git a/src/buildstream/_pluginfactory/__init__.py b/src/buildstream/_pluginfactory/__init__.py new file mode 100644 index 000000000..fe69b6e77 --- /dev/null +++ b/src/buildstream/_pluginfactory/__init__.py @@ -0,0 +1,20 @@ +# +# Copyright (C) 2020 Codethink Limited +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . +# + +from .pluginorigin import PluginOrigin, PluginOriginType +from .sourcefactory import SourceFactory +from .elementfactory import ElementFactory diff --git a/src/buildstream/_pluginfactory/elementfactory.py b/src/buildstream/_pluginfactory/elementfactory.py new file mode 100644 index 000000000..8879a4173 --- /dev/null +++ b/src/buildstream/_pluginfactory/elementfactory.py @@ -0,0 +1,58 @@ +# +# Copyright (C) 2016 Codethink Limited +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . +# +# Authors: +# Tristan Van Berkom + +from .. import _site +from ..element import Element + +from .pluginfactory import PluginFactory + + +# A ElementFactory creates Element instances +# in the context of a given factory +# +# Args: +# plugin_base (PluginBase): The main PluginBase object to work with +# +class ElementFactory(PluginFactory): + def __init__(self, plugin_base): + + super().__init__( + plugin_base, Element, [_site.element_plugins], "buildstream.plugins.elements", + ) + + # create(): + # + # Create an Element object, the pipeline uses this to create Element + # objects on demand for a given pipeline. + # + # Args: + # context (object): The Context object for processing + # project (object): The project object + # meta (object): The loaded MetaElement + # + # Returns: A newly created Element object of the appropriate kind + # + # Raises: + # PluginError (if the kind lookup failed) + # LoadError (if the element itself took issue with the config) + # + def create(self, context, project, meta): + element_type, default_config = self.lookup(meta.kind) + element = element_type(context, project, meta, default_config) + return element diff --git a/src/buildstream/_pluginfactory/pluginfactory.py b/src/buildstream/_pluginfactory/pluginfactory.py new file mode 100644 index 000000000..c42b0a3d2 --- /dev/null +++ b/src/buildstream/_pluginfactory/pluginfactory.py @@ -0,0 +1,271 @@ +# +# Copyright (C) 2016 Codethink Limited +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . +# +# Authors: +# Tristan Van Berkom + +import os +import inspect + +from .._exceptions import PluginError + +from .pluginorigin import PluginOrigin, PluginOriginType + + +# A Context for loading plugin types +# +# Args: +# plugin_base (PluginBase): The main PluginBase object to work with +# base_type (type): A base object type for this context +# site_plugin_path (str): Path to where buildstream keeps plugins +# entrypoint_group (str): Name of the entry point group that provides plugins +# +# Since multiple pipelines can be processed recursively +# within the same interpretor, it's important that we have +# one context associated to the processing of a given pipeline, +# this way sources and element types which are particular to +# a given BuildStream project are isolated to their respective +# Pipelines. +# +class PluginFactory: + def __init__(self, plugin_base, base_type, site_plugin_path, entrypoint_group): + + # The plugin kinds which were loaded + self.loaded_dependencies = [] + + # + # Private members + # + + # For pickling across processes, make sure this context has a unique + # identifier, which we prepend to the identifier of each PluginSource. + # This keeps plugins loaded during the first and second pass distinct + # from eachother. + self._identifier = str(id(self)) + + self._base_type = base_type # The base class plugins derive from + self._types = {} # Plugin type lookup table by kind + self._origins = {} # PluginOrigin lookup table by kind + + # The PluginSource object + self._plugin_base = plugin_base + self._site_plugin_path = site_plugin_path + self._entrypoint_group = entrypoint_group + self._alternate_sources = {} + + self._init_site_source() + + def _init_site_source(self): + self._site_source = self._plugin_base.make_plugin_source( + searchpath=self._site_plugin_path, identifier=self._identifier + "site", + ) + + def __getstate__(self): + state = self.__dict__.copy() + + # PluginSource is not a picklable type, so we must reconstruct this one + # as best we can when unpickling. + # + # Since the values of `_types` depend on the PluginSource, we must also + # get rid of those. It is only a cache - we will automatically recreate + # them on demand. + # + # Similarly we must clear out the `_alternate_sources` cache. + # + # Note that this method of referring to members is error-prone in that + # a later 'search and replace' renaming might miss these. Guard against + # this by making sure we are not creating new members, only clearing + # existing ones. + # + del state["_site_source"] + assert "_types" in state + state["_types"] = {} + assert "_alternate_sources" in state + state["_alternate_sources"] = {} + + return state + + def __setstate__(self, state): + self.__dict__.update(state) + + # Note that in order to enable plugins to be unpickled along with this + # PluginSource, we would also have to set and restore the 'identifier' + # of the PluginSource. We would also have to recreate `_types` as it + # was before unpickling them. We are not using this method in + # BuildStream, so the identifier is not restored here. + self._init_site_source() + + # lookup(): + # + # Fetches a type loaded from a plugin in this plugin context + # + # Args: + # kind (str): The kind of Plugin to create + # + # Returns: the type associated with the given kind + # + # Raises: PluginError + # + def lookup(self, kind): + return self._ensure_plugin(kind) + + # register_plugin_origin(): + # + # Registers the PluginOrigin to use for the given plugin kind + # + # Args: + # kind (str): The kind identifier of the Plugin + # origin (PluginOrigin): The PluginOrigin providing the plugin + # + def register_plugin_origin(self, kind: str, origin: PluginOrigin): + if kind in self._origins: + raise PluginError( + "More than one {} plugin registered as kind '{}'".format(self._base_type.__name__, kind), + reason="duplicate-plugin", + ) + + self._origins[kind] = origin + + # all_loaded_plugins(): + # + # Returns: an iterable over all the loaded plugins. + # + def all_loaded_plugins(self): + return self._types.values() + + def _get_local_plugin_source(self, path): + if ("local", path) not in self._alternate_sources: + # key by a tuple to avoid collision + source = self._plugin_base.make_plugin_source(searchpath=[path], identifier=self._identifier + path,) + # Ensure that sources never get garbage collected, + # as they'll take the plugins with them. + self._alternate_sources[("local", path)] = source + else: + source = self._alternate_sources[("local", path)] + return source + + def _get_pip_plugin_source(self, package_name, kind): + defaults = None + if ("pip", package_name) not in self._alternate_sources: + import pkg_resources + + # key by a tuple to avoid collision + try: + package = pkg_resources.get_entry_info(package_name, self._entrypoint_group, kind) + except pkg_resources.DistributionNotFound as e: + raise PluginError("Failed to load {} plugin '{}': {}".format(self._base_type.__name__, kind, e)) from e + + if package is None: + raise PluginError("Pip package {} does not contain a plugin named '{}'".format(package_name, kind)) + + location = package.dist.get_resource_filename( + pkg_resources._manager, package.module_name.replace(".", os.sep) + ".py" + ) + + # Also load the defaults - required since setuptools + # may need to extract the file. + try: + defaults = package.dist.get_resource_filename( + pkg_resources._manager, package.module_name.replace(".", os.sep) + ".yaml" + ) + except KeyError: + # The plugin didn't have an accompanying YAML file + defaults = None + + source = self._plugin_base.make_plugin_source( + searchpath=[os.path.dirname(location)], identifier=self._identifier + os.path.dirname(location), + ) + self._alternate_sources[("pip", package_name)] = source + + else: + source = self._alternate_sources[("pip", package_name)] + + return source, defaults + + def _ensure_plugin(self, kind): + + if kind not in self._types: + source = None + defaults = None + + origin = self._origins.get(kind, None) + if origin: + # Try getting the plugin source from a registered origin + if origin.origin_type == PluginOriginType.LOCAL: + source = self._get_local_plugin_source(origin.path) + elif origin.origin_type == PluginOriginType.PIP: + source, defaults = self._get_pip_plugin_source(origin.package_name, kind) + else: + assert False, "Encountered invalid plugin origin type" + else: + # Try getting it from the core plugins + if kind not in self._site_source.list_plugins(): + raise PluginError("No {} type registered for kind '{}'".format(self._base_type.__name__, kind)) + + source = self._site_source + + self._types[kind] = self._load_plugin(source, kind, defaults) + self.loaded_dependencies.append(kind) + + return self._types[kind] + + def _load_plugin(self, source, kind, defaults): + + try: + plugin = source.load_plugin(kind) + + if not defaults: + plugin_file = inspect.getfile(plugin) + plugin_dir = os.path.dirname(plugin_file) + plugin_conf_name = "{}.yaml".format(kind) + defaults = os.path.join(plugin_dir, plugin_conf_name) + + except ImportError as e: + raise PluginError("Failed to load {} plugin '{}': {}".format(self._base_type.__name__, kind, e)) from e + + try: + plugin_type = plugin.setup() + except AttributeError as e: + raise PluginError( + "{} plugin '{}' did not provide a setup() function".format(self._base_type.__name__, kind) + ) from e + except TypeError as e: + raise PluginError( + "setup symbol in {} plugin '{}' is not a function".format(self._base_type.__name__, kind) + ) from e + + self._assert_plugin(kind, plugin_type) + return (plugin_type, defaults) + + def _assert_plugin(self, kind, plugin_type): + if kind in self._types: + raise PluginError( + "Tried to register {} plugin for existing kind '{}' " + "(already registered {})".format(self._base_type.__name__, kind, self._types[kind].__name__) + ) + try: + if not issubclass(plugin_type, self._base_type): + raise PluginError( + "{} plugin '{}' returned type '{}', which is not a subclass of {}".format( + self._base_type.__name__, kind, plugin_type.__name__, self._base_type.__name__ + ) + ) + except TypeError as e: + raise PluginError( + "{} plugin '{}' returned something that is not a type (expected subclass of {})".format( + self._base_type.__name__, kind, self._base_type.__name__ + ) + ) from e diff --git a/src/buildstream/_pluginfactory/pluginorigin.py b/src/buildstream/_pluginfactory/pluginorigin.py new file mode 100644 index 000000000..50852711b --- /dev/null +++ b/src/buildstream/_pluginfactory/pluginorigin.py @@ -0,0 +1,130 @@ +# +# Copyright (C) 2020 Codethink Limited +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . +# + +import os + +from ..types import FastEnum + + +# PluginOriginType: +# +# An enumeration depicting the type of plugin origin +# +class PluginOriginType(FastEnum): + LOCAL = "local" + PIP = "pip" + + +# PluginOrigin +# +# Base class holding common properties of all origins. +# +class PluginOrigin: + + # Common fields valid for all plugin origins + _COMMON_CONFIG_KEYS = ["origin", "sources", "elements"] + + def __init__(self, origin_type): + + # Public + self.origin_type = origin_type + self.elements = [] + self.sources = [] + + # Private + self._project = None + self._kinds = {} + + # new_from_node() + # + # Load a PluginOrigin from the YAML in project.conf + # + # Args: + # project (Project): The project from whence this origin is loaded + # origin_node (MappingNode): The node defining this origin + # + # Returns: + # (PluginOrigin): The newly created PluginOrigin + # + @classmethod + def new_from_node(cls, project, origin_node): + + origin_type = origin_node.get_enum("origin", PluginOriginType) + + if origin_type == PluginOriginType.LOCAL: + origin = PluginOriginLocal() + elif origin_type == PluginOriginType.PIP: + origin = PluginOriginPip() + + origin._project = project + origin._load(origin_node) + + origin.elements = origin_node.get_str_list("elements", []) + origin.sources = origin_node.get_str_list("sources", []) + + return origin + + # _load() + # + # Abstract method for loading data from the origin node, this + # method should not load the source and element lists. + # + # Args: + # origin_node (MappingNode): The node defining this origin + # + def _load(self, origin_node): + pass + + +# PluginOriginLocal +# +# PluginOrigin for local plugins +# +class PluginOriginLocal(PluginOrigin): + def __init__(self): + super().__init__(PluginOriginType.LOCAL) + + # An absolute path to where the plugin can be found + # + self.path = None + + def _load(self, origin_node): + + origin_node.validate_keys(["path", *PluginOrigin._COMMON_CONFIG_KEYS]) + + path_node = origin_node.get_scalar("path") + path = self._project.get_path_from_node(path_node, check_is_dir=True) + + self.path = os.path.join(self._project.directory, path) + + +# PluginOriginPip +# +# PluginOrigin for pip plugins +# +class PluginOriginPip(PluginOrigin): + def __init__(self): + super().__init__(PluginOriginType.PIP) + + # The pip package name to extract plugins from + # + self.package_name = None + + def _load(self, origin_node): + + origin_node.validate_keys(["package-name", *PluginOrigin._COMMON_CONFIG_KEYS]) + self.package_name = origin_node.get_str("package-name") diff --git a/src/buildstream/_pluginfactory/sourcefactory.py b/src/buildstream/_pluginfactory/sourcefactory.py new file mode 100644 index 000000000..9f6a09784 --- /dev/null +++ b/src/buildstream/_pluginfactory/sourcefactory.py @@ -0,0 +1,59 @@ +# +# Copyright (C) 2016 Codethink Limited +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . +# +# Authors: +# Tristan Van Berkom + +from .. import _site +from ..source import Source + +from .pluginfactory import PluginFactory + + +# A SourceFactory creates Source instances +# in the context of a given factory +# +# Args: +# plugin_base (PluginBase): The main PluginBase object to work with +# +class SourceFactory(PluginFactory): + def __init__(self, plugin_base): + + super().__init__( + plugin_base, Source, [_site.source_plugins], "buildstream.plugins.sources", + ) + + # create(): + # + # Create a Source object, the pipeline uses this to create Source + # objects on demand for a given pipeline. + # + # Args: + # context (object): The Context object for processing + # project (object): The project object + # meta (object): The loaded MetaSource + # + # Returns: + # A newly created Source object of the appropriate kind + # + # Raises: + # PluginError (if the kind lookup failed) + # LoadError (if the source itself took issue with the config) + # + def create(self, context, project, meta): + source_type, _ = self.lookup(meta.kind) + source = source_type(context, project, meta) + return source diff --git a/src/buildstream/_project.py b/src/buildstream/_project.py index 3527d211f..40524d7ad 100644 --- a/src/buildstream/_project.py +++ b/src/buildstream/_project.py @@ -36,13 +36,11 @@ from ._artifactcache import ArtifactCache from ._sourcecache import SourceCache from .node import ScalarNode, SequenceNode, _assert_symbol_name from .sandbox import SandboxRemote -from ._elementfactory import ElementFactory -from ._sourcefactory import SourceFactory +from ._pluginfactory import ElementFactory, SourceFactory, PluginOrigin from .types import CoreWarnings from ._projectrefs import ProjectRefs, ProjectRefStorage from ._loader import Loader from .element import Element -from .types import FastEnum from ._message import Message, MessageType from ._includes import Includes from ._workspaces import WORKSPACE_PROJECT_FILE @@ -52,13 +50,6 @@ from ._workspaces import WORKSPACE_PROJECT_FILE _PROJECT_CONF_FILE = "project.conf" -# List of all places plugins can come from -class PluginOrigins(FastEnum): - CORE = "core" - LOCAL = "local" - PIP = "pip" - - # HostMount() # # A simple object describing the behavior of @@ -951,87 +942,19 @@ class Project: return project_directory, workspace_element def _load_plugin_factories(self, config, output): - plugin_source_origins = [] # Origins of custom sources - plugin_element_origins = [] # Origins of custom elements - - # Plugin origins and versions - origins = config.get_sequence("plugins", default=[]) - source_format_versions = {} - element_format_versions = {} - for origin in origins: - allowed_origin_fields = [ - "origin", - "sources", - "elements", - "package-name", - "path", - ] - origin.validate_keys(allowed_origin_fields) - - # Store source versions for checking later - source_versions = origin.get_mapping("sources", default={}) - for key in source_versions.keys(): - if key in source_format_versions: - raise LoadError("Duplicate listing of source '{}'".format(key), LoadErrorReason.INVALID_YAML) - source_format_versions[key] = source_versions.get_int(key) - - # Store element versions for checking later - element_versions = origin.get_mapping("elements", default={}) - for key in element_versions.keys(): - if key in element_format_versions: - raise LoadError("Duplicate listing of element '{}'".format(key), LoadErrorReason.INVALID_YAML) - element_format_versions[key] = element_versions.get_int(key) - - # Store the origins if they're not 'core'. - # core elements are loaded by default, so storing is unnecessary. - origin_value = origin.get_enum("origin", PluginOrigins) - - if origin_value != PluginOrigins.CORE: - self._store_origin(origin, "sources", plugin_source_origins) - self._store_origin(origin, "elements", plugin_element_origins) - + # Create the factories pluginbase = PluginBase(package="buildstream.plugins") - output.element_factory = ElementFactory( - pluginbase, plugin_origins=plugin_element_origins, format_versions=element_format_versions - ) - output.source_factory = SourceFactory( - pluginbase, plugin_origins=plugin_source_origins, format_versions=source_format_versions - ) + output.element_factory = ElementFactory(pluginbase) + output.source_factory = SourceFactory(pluginbase) - # _store_origin() - # - # Helper function to store plugin origins - # - # Args: - # origin (node) - a node indicating the origin of a group of - # plugins. - # plugin_group (str) - The name of the type of plugin that is being - # loaded - # destination (list) - A list of nodes to store the origins in - # - # Raises: - # LoadError if 'origin' is an unexpected value - def _store_origin(self, origin, plugin_group, destination): - expected_groups = ["sources", "elements"] - if plugin_group not in expected_groups: - raise LoadError( - "Unexpected plugin group: {}, expecting {}".format(plugin_group, expected_groups), - LoadErrorReason.INVALID_DATA, - ) - if plugin_group in origin.keys(): - origin_node = origin.clone() - plugins = origin.get_mapping(plugin_group, default={}) - origin_node["plugins"] = plugins.keys() - - for group in expected_groups: - if group in origin_node: - del origin_node[group] - - if origin_node.get_enum("origin", PluginOrigins) == PluginOrigins.LOCAL: - path = self.get_path_from_node(origin.get_scalar("path"), check_is_dir=True) - # paths are passed in relative to the project, but must be absolute - origin_node["path"] = os.path.join(self.directory, path) - destination.append(origin_node) + # Load the plugin origins and register them to their factories + origins = config.get_sequence("plugins", default=[]) + for origin_node in origins: + origin = PluginOrigin.new_from_node(self, origin_node) + for kind in origin.elements: + output.element_factory.register_plugin_origin(kind, origin) + for kind in origin.sources: + output.source_factory.register_plugin_origin(kind, origin) # _warning_is_fatal(): # diff --git a/src/buildstream/_sourcefactory.py b/src/buildstream/_sourcefactory.py deleted file mode 100644 index 254c482d6..000000000 --- a/src/buildstream/_sourcefactory.py +++ /dev/null @@ -1,66 +0,0 @@ -# -# Copyright (C) 2016 Codethink Limited -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library. If not, see . -# -# Authors: -# Tristan Van Berkom - -from . import _site -from ._plugincontext import PluginContext -from .source import Source - - -# A SourceFactory creates Source instances -# in the context of a given factory -# -# Args: -# plugin_base (PluginBase): The main PluginBase object to work with -# plugin_origins (list): Data used to search for external Source plugins -# -class SourceFactory(PluginContext): - def __init__(self, plugin_base, *, format_versions={}, plugin_origins=None): - - super().__init__( - plugin_base, - Source, - [_site.source_plugins], - "buildstream.plugins.sources", - format_versions=format_versions, - plugin_origins=plugin_origins, - ) - - # create(): - # - # Create a Source object, the pipeline uses this to create Source - # objects on demand for a given pipeline. - # - # Args: - # context (object): The Context object for processing - # project (object): The project object - # meta (object): The loaded MetaSource - # - # Returns: - # A newly created Source object of the appropriate kind - # - # Raises: - # PluginError (if the kind lookup failed) - # LoadError (if the source itself took issue with the config) - # - def create(self, context, project, meta): - source_type, _ = self.lookup(meta.kind) - source = source_type(context, project, meta) - version = self._format_versions.get(meta.kind, 0) - self._assert_plugin_format(source, version) - return source diff --git a/src/buildstream/plugin.py b/src/buildstream/plugin.py index 2182f5a08..4f2d74304 100644 --- a/src/buildstream/plugin.py +++ b/src/buildstream/plugin.py @@ -148,27 +148,6 @@ class Plugin: which are included in the buildstream namespace. """ - BST_REQUIRED_VERSION_MAJOR = 0 - """Minimum required major version""" - - BST_REQUIRED_VERSION_MINOR = 0 - """Minimum required minor version""" - - BST_FORMAT_VERSION = 0 - """The plugin's YAML format version - - This should be set to ``1`` the first time any new configuration - is understood by your :func:`Plugin.configure() ` - implementation and subsequently bumped every time your - configuration is enhanced. - - .. note:: - - Plugins are expected to maintain backward compatibility - in the format and configurations they expose. The versioning - is intended to track availability of new features only. - """ - BST_PLUGIN_DEPRECATED = False """True if this element plugin has been deprecated. diff --git a/src/buildstream/testing/_sourcetests/utils.py b/src/buildstream/testing/_sourcetests/utils.py index 116506807..4cfb696bd 100644 --- a/src/buildstream/testing/_sourcetests/utils.py +++ b/src/buildstream/testing/_sourcetests/utils.py @@ -71,7 +71,7 @@ def add_plugins_conf(project, plugin_kind): if plugin_package is not None: project_conf["plugins"] = [ - {"origin": "pip", "package-name": plugin_package, "sources": {plugin_kind: 0,},}, + {"origin": "pip", "package-name": plugin_package, "sources": [plugin_kind],}, ] _yaml.roundtrip_dump(project_conf, project_conf_file) diff --git a/tests/elements/filter/basic/project.conf b/tests/elements/filter/basic/project.conf index e0b487d01..023943f79 100644 --- a/tests/elements/filter/basic/project.conf +++ b/tests/elements/filter/basic/project.conf @@ -5,4 +5,4 @@ plugins: - origin: local path: element_plugins elements: - dynamic: 0 + - dynamic diff --git a/tests/format/project.py b/tests/format/project.py index c2e2f733f..c4b2a480a 100644 --- a/tests/format/project.py +++ b/tests/format/project.py @@ -177,7 +177,7 @@ def test_plugin_no_load_ref(cli, datafiles, ref_storage): "name": "test", "min-version": "2.0", "ref-storage": ref_storage, - "plugins": [{"origin": "local", "path": "plugins", "sources": {"noloadref": 0}}], + "plugins": [{"origin": "local", "path": "plugins", "sources": ["noloadref"]}], } _yaml.roundtrip_dump(config, os.path.join(project, "project.conf")) @@ -202,7 +202,7 @@ def test_plugin_preflight_error(cli, datafiles): def test_duplicate_plugins(cli, datafiles): project = os.path.join(datafiles.dirname, datafiles.basename, "duplicate-plugins") result = cli.run(project=project, silent=True, args=["show", "element.bst"]) - result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_YAML) + result.assert_main_error(ErrorDomain.PLUGIN, "duplicate-plugin") # Assert that we get a different cache key for target.bst, depending diff --git a/tests/format/project/duplicate-plugins/project.conf b/tests/format/project/duplicate-plugins/project.conf index a413e6137..495ba0678 100644 --- a/tests/format/project/duplicate-plugins/project.conf +++ b/tests/format/project/duplicate-plugins/project.conf @@ -5,12 +5,12 @@ plugins: - origin: local path: bar elements: - foo: 0 + - foo sources: - frob: 0 + - frob - origin: local path: baz elements: - foo: 0 + - foo sources: - frob: 0 + - frob diff --git a/tests/format/project/local-plugin/project.conf b/tests/format/project/local-plugin/project.conf index ccb6ef433..7d8cc1aea 100644 --- a/tests/format/project/local-plugin/project.conf +++ b/tests/format/project/local-plugin/project.conf @@ -4,4 +4,4 @@ plugins: - origin: local path: plugins sources: - mysource: 0 + - mysource diff --git a/tests/format/project/plugin-allowed/project.conf b/tests/format/project/plugin-allowed/project.conf index 3896b7bc2..97107edf6 100644 --- a/tests/format/project/plugin-allowed/project.conf +++ b/tests/format/project/plugin-allowed/project.conf @@ -5,4 +5,4 @@ plugins: - origin: local path: plugins elements: - foo: 0 + - foo diff --git a/tests/format/project/plugin-preflight-error/project.conf b/tests/format/project/plugin-preflight-error/project.conf index 07d60daf2..ed1ddb99f 100644 --- a/tests/format/project/plugin-preflight-error/project.conf +++ b/tests/format/project/plugin-preflight-error/project.conf @@ -9,4 +9,4 @@ plugins: - origin: local path: errorplugin sources: - preflighterror: 0 + - preflighterror diff --git a/tests/frontend/configurable_warnings.py b/tests/frontend/configurable_warnings.py index e61db6c37..53409a512 100644 --- a/tests/frontend/configurable_warnings.py +++ b/tests/frontend/configurable_warnings.py @@ -18,9 +18,7 @@ def get_project(fatal_warnings): "name": "test", "min-version": "2.0", "element-path": "elements", - "plugins": [ - {"origin": "local", "path": "plugins", "elements": {"warninga": 0, "warningb": 0, "corewarn": 0,}} - ], + "plugins": [{"origin": "local", "path": "plugins", "elements": ["warninga", "warningb", "corewarn"]}], "fatal-warnings": fatal_warnings, } diff --git a/tests/frontend/configuredwarning/project.conf b/tests/frontend/configuredwarning/project.conf index 195ee176a..9d75ad18b 100644 --- a/tests/frontend/configuredwarning/project.conf +++ b/tests/frontend/configuredwarning/project.conf @@ -5,5 +5,5 @@ plugins: - origin: local path: element_plugins elements: - warninga: 0 - warningb: 0 + - warninga + - warningb diff --git a/tests/frontend/consistencyerror/project.conf b/tests/frontend/consistencyerror/project.conf index d28ba12df..f5c1b39c5 100644 --- a/tests/frontend/consistencyerror/project.conf +++ b/tests/frontend/consistencyerror/project.conf @@ -9,5 +9,5 @@ plugins: - origin: local path: plugins sources: - consistencyerror: 0 - consistencybug: 0 + - consistencyerror + - consistencybug diff --git a/tests/frontend/mirror.py b/tests/frontend/mirror.py index 848047ee8..bffc754e7 100644 --- a/tests/frontend/mirror.py +++ b/tests/frontend/mirror.py @@ -49,7 +49,7 @@ def generate_project(): {"name": "arrakis", "aliases": {"foo": ["OFO/"], "bar": ["RBA/"],},}, {"name": "oz", "aliases": {"foo": ["ooF/"], "bar": ["raB/"],}}, ], - "plugins": [{"origin": "local", "path": "sources", "sources": {"fetch_source": 0}}], + "plugins": [{"origin": "local", "path": "sources", "sources": ["fetch_source"]}], } return project diff --git a/tests/frontend/push.py b/tests/frontend/push.py index 362084372..970885784 100644 --- a/tests/frontend/push.py +++ b/tests/frontend/push.py @@ -641,7 +641,7 @@ def test_push_after_rebuild(cli, tmpdir, datafiles): config={ "element-path": "elements", "min-version": "2.0", - "plugins": [{"origin": "local", "path": "plugins", "elements": {"randomelement": 0}}], + "plugins": [{"origin": "local", "path": "plugins", "elements": ["randomelement"]}], }, ) diff --git a/tests/internals/pluginfactory.py b/tests/internals/pluginfactory.py deleted file mode 100644 index 13b9d3aae..000000000 --- a/tests/internals/pluginfactory.py +++ /dev/null @@ -1,287 +0,0 @@ -# Pylint doesn't play well with fixtures and dependency injection from pytest -# pylint: disable=redefined-outer-name - -import os -import pytest - -from pluginbase import PluginBase -from buildstream import Node -from buildstream._elementfactory import ElementFactory -from buildstream._sourcefactory import SourceFactory -from buildstream._exceptions import PluginError - -DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "pluginfactory",) - - -# Simple fixture to create a PluginBase object that -# we use for loading plugins. -@pytest.fixture() -def plugin_fixture(): - return {"base": PluginBase(package="buildstream.plugins")} - - -############################################################## -# Basics: test the fixture, test we can create the factories # -############################################################## -def test_fixture(plugin_fixture): - assert isinstance(plugin_fixture["base"], PluginBase) - - -def test_source_factory(plugin_fixture): - factory = SourceFactory(plugin_fixture["base"]) - assert isinstance(factory, SourceFactory) - - -def test_element_factory(plugin_fixture): - factory = ElementFactory(plugin_fixture["base"]) - assert isinstance(factory, ElementFactory) - - -############################################################## -# Check that we can load custom sources & elements # -############################################################## -@pytest.mark.datafiles(os.path.join(DATA_DIR, "customsource")) -def test_custom_source(plugin_fixture, datafiles): - plugins = [ - Node.from_dict( - {"origin": "local", "path": os.path.join(datafiles.dirname, datafiles.basename), "plugins": ["foo"]} - ) - ] - factory = SourceFactory(plugin_fixture["base"], plugin_origins=plugins) - assert isinstance(factory, SourceFactory) - - foo_type, _ = factory.lookup("foo") - assert foo_type.__name__ == "FooSource" - - -@pytest.mark.datafiles(os.path.join(DATA_DIR, "customelement")) -def test_custom_element(plugin_fixture, datafiles): - plugins = [ - Node.from_dict( - {"origin": "local", "path": os.path.join(datafiles.dirname, datafiles.basename), "plugins": ["foo"]} - ) - ] - factory = ElementFactory(plugin_fixture["base"], plugin_origins=plugins) - assert isinstance(factory, ElementFactory) - - foo_type, _ = factory.lookup("foo") - assert foo_type.__name__ == "FooElement" - - -############################################################## -# Check plugin loading failure modes # -############################################################## -def test_missing_source(plugin_fixture): - factory = SourceFactory(plugin_fixture["base"]) - assert isinstance(factory, SourceFactory) - - # Test fails if PluginError is not raised - with pytest.raises(PluginError): - factory.lookup("foo") - - -def test_missing_element(plugin_fixture): - factory = ElementFactory(plugin_fixture["base"]) - assert isinstance(factory, ElementFactory) - - # Test fails if PluginError is not raised - with pytest.raises(PluginError): - factory.lookup("foo") - - -# Load a factory with a plugin that returns a value instead of Source subclass -@pytest.mark.datafiles(os.path.join(DATA_DIR, "notatype")) -def test_source_notatype(plugin_fixture, datafiles): - plugins = [ - Node.from_dict( - {"origin": "local", "path": os.path.join(datafiles.dirname, datafiles.basename), "plugins": ["foo"]} - ) - ] - factory = SourceFactory(plugin_fixture["base"], plugin_origins=plugins) - with pytest.raises(PluginError): - factory.lookup("foo") - - -# Load a factory with a plugin that returns a value instead of Element subclass -@pytest.mark.datafiles(os.path.join(DATA_DIR, "notatype")) -def test_element_notatype(plugin_fixture, datafiles): - plugins = [ - Node.from_dict( - {"origin": "local", "path": os.path.join(datafiles.dirname, datafiles.basename), "plugins": ["foo"]} - ) - ] - factory = ElementFactory(plugin_fixture["base"], plugin_origins=plugins) - with pytest.raises(PluginError): - factory.lookup("foo") - - -# Load a factory with a plugin that returns a type -# which is not a Source subclass -@pytest.mark.datafiles(os.path.join(DATA_DIR, "wrongtype")) -def test_source_wrongtype(plugin_fixture, datafiles): - plugins = [ - Node.from_dict( - {"origin": "local", "path": os.path.join(datafiles.dirname, datafiles.basename), "plugins": ["foo"]} - ) - ] - factory = SourceFactory(plugin_fixture["base"], plugin_origins=plugins) - with pytest.raises(PluginError): - factory.lookup("foo") - - -# Load a factory with a plugin that returns a type -# which is not a Element subclass -@pytest.mark.datafiles(os.path.join(DATA_DIR, "wrongtype")) -def test_element_wrongtype(plugin_fixture, datafiles): - plugins = [ - Node.from_dict( - {"origin": "local", "path": os.path.join(datafiles.dirname, datafiles.basename), "plugins": ["foo"]} - ) - ] - factory = ElementFactory(plugin_fixture["base"], plugin_origins=plugins) - with pytest.raises(PluginError): - factory.lookup("foo") - - -# Load a factory with a plugin which fails to provide a setup() function -@pytest.mark.datafiles(os.path.join(DATA_DIR, "nosetup")) -def test_source_missing_setup(plugin_fixture, datafiles): - plugins = [ - Node.from_dict( - {"origin": "local", "path": os.path.join(datafiles.dirname, datafiles.basename), "plugins": ["foo"]} - ) - ] - factory = SourceFactory(plugin_fixture["base"], plugin_origins=plugins) - with pytest.raises(PluginError): - factory.lookup("foo") - - -# Load a factory with a plugin which fails to provide a setup() function -@pytest.mark.datafiles(os.path.join(DATA_DIR, "nosetup")) -def test_element_missing_setup(plugin_fixture, datafiles): - plugins = [ - Node.from_dict( - {"origin": "local", "path": os.path.join(datafiles.dirname, datafiles.basename), "plugins": ["foo"]} - ) - ] - factory = ElementFactory(plugin_fixture["base"], plugin_origins=plugins) - with pytest.raises(PluginError): - factory.lookup("foo") - - -# Load a factory with a plugin which provides a setup symbol -# that is not a function -@pytest.mark.datafiles(os.path.join(DATA_DIR, "badsetup")) -def test_source_bad_setup(plugin_fixture, datafiles): - plugins = [ - Node.from_dict( - {"origin": "local", "path": os.path.join(datafiles.dirname, datafiles.basename), "plugins": ["foo"]} - ) - ] - factory = SourceFactory(plugin_fixture["base"], plugin_origins=plugins) - with pytest.raises(PluginError): - factory.lookup("foo") - - -# Load a factory with a plugin which provides a setup symbol -# that is not a function -@pytest.mark.datafiles(os.path.join(DATA_DIR, "badsetup")) -def test_element_bad_setup(plugin_fixture, datafiles): - plugins = [ - Node.from_dict( - {"origin": "local", "path": os.path.join(datafiles.dirname, datafiles.basename), "plugins": ["foo"]} - ) - ] - factory = ElementFactory(plugin_fixture["base"], plugin_origins=plugins) - with pytest.raises(PluginError): - factory.lookup("foo") - - -# Load a factory with a plugin which requires an absurdly -# high version of buildstream -@pytest.mark.datafiles(os.path.join(DATA_DIR, "badversionsource")) -def test_source_badversion(plugin_fixture, datafiles): - plugins = [ - Node.from_dict( - {"origin": "local", "path": os.path.join(datafiles.dirname, datafiles.basename), "plugins": ["foo"]} - ) - ] - factory = SourceFactory(plugin_fixture["base"], plugin_origins=plugins) - with pytest.raises(PluginError): - factory.lookup("foo") - - -# Load a factory with a plugin which requires an absurdly -# high version of buildstream -@pytest.mark.datafiles(os.path.join(DATA_DIR, "badversionelement")) -def test_element_badversion(plugin_fixture, datafiles): - plugins = [ - Node.from_dict( - {"origin": "local", "path": os.path.join(datafiles.dirname, datafiles.basename), "plugins": ["foo"]} - ) - ] - factory = ElementFactory(plugin_fixture["base"], plugin_origins=plugins) - with pytest.raises(PluginError): - factory.lookup("foo") - - -############################################################## -# Check we can load different contexts of plugin # -############################################################## - -# Load two factories, both of which define a different 'foo' plugin -@pytest.mark.datafiles(DATA_DIR) -def test_source_multicontext(plugin_fixture, datafiles): - plugins1 = Node.from_dict( - { - "origin": "local", - "path": os.path.join(datafiles.dirname, datafiles.basename, "customsource"), - "plugins": ["foo"], - } - ) - plugins2 = Node.from_dict( - { - "origin": "local", - "path": os.path.join(datafiles.dirname, datafiles.basename, "anothersource"), - "plugins": ["foo"], - } - ) - - factory1 = SourceFactory(plugin_fixture["base"], plugin_origins=[plugins1]) - factory2 = SourceFactory(plugin_fixture["base"], plugin_origins=[plugins2]) - assert isinstance(factory1, SourceFactory) - assert isinstance(factory2, SourceFactory) - - foo_type1, _ = factory1.lookup("foo") - foo_type2, _ = factory2.lookup("foo") - assert foo_type1.__name__ == "FooSource" - assert foo_type2.__name__ == "AnotherFooSource" - - -# Load two factories, both of which define a different 'foo' plugin -@pytest.mark.datafiles(DATA_DIR) -def test_element_multicontext(plugin_fixture, datafiles): - plugins1 = Node.from_dict( - { - "origin": "local", - "path": os.path.join(datafiles.dirname, datafiles.basename, "customelement"), - "plugins": ["foo"], - } - ) - plugins2 = Node.from_dict( - { - "origin": "local", - "path": os.path.join(datafiles.dirname, datafiles.basename, "anotherelement"), - "plugins": ["foo"], - } - ) - - factory1 = ElementFactory(plugin_fixture["base"], plugin_origins=[plugins1]) - factory2 = ElementFactory(plugin_fixture["base"], plugin_origins=[plugins2]) - assert isinstance(factory1, ElementFactory) - assert isinstance(factory2, ElementFactory) - - foo_type1, _ = factory1.lookup("foo") - foo_type2, _ = factory2.lookup("foo") - assert foo_type1.__name__ == "FooElement" - assert foo_type2.__name__ == "AnotherFooElement" diff --git a/tests/internals/pluginfactory/anotherelement/__init__.py b/tests/internals/pluginfactory/anotherelement/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/internals/pluginfactory/anotherelement/foo.py b/tests/internals/pluginfactory/anotherelement/foo.py deleted file mode 100644 index 2e067a94f..000000000 --- a/tests/internals/pluginfactory/anotherelement/foo.py +++ /dev/null @@ -1,9 +0,0 @@ -from buildstream import Element - - -class AnotherFooElement(Element): - pass - - -def setup(): - return AnotherFooElement diff --git a/tests/internals/pluginfactory/anothersource/__init__.py b/tests/internals/pluginfactory/anothersource/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/internals/pluginfactory/anothersource/foo.py b/tests/internals/pluginfactory/anothersource/foo.py deleted file mode 100644 index 4675b965f..000000000 --- a/tests/internals/pluginfactory/anothersource/foo.py +++ /dev/null @@ -1,9 +0,0 @@ -from buildstream import Source - - -class AnotherFooSource(Source): - pass - - -def setup(): - return AnotherFooSource diff --git a/tests/internals/pluginfactory/badsetup/__init__.py b/tests/internals/pluginfactory/badsetup/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/internals/pluginfactory/badsetup/foo.py b/tests/internals/pluginfactory/badsetup/foo.py deleted file mode 100644 index 145f2577b..000000000 --- a/tests/internals/pluginfactory/badsetup/foo.py +++ /dev/null @@ -1,6 +0,0 @@ -# A plugin is supposed to define a setup function -# which returns the type that the plugin provides -# -# This plugin provides a setup() symbol that is -# not even a function -setup = 5 diff --git a/tests/internals/pluginfactory/badversionelement/__init__.py b/tests/internals/pluginfactory/badversionelement/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/internals/pluginfactory/badversionelement/foo.py b/tests/internals/pluginfactory/badversionelement/foo.py deleted file mode 100644 index 2a8b12abe..000000000 --- a/tests/internals/pluginfactory/badversionelement/foo.py +++ /dev/null @@ -1,11 +0,0 @@ -from buildstream import Element - - -class FooElement(Element): - - # We have a little while until we have to manually modify this - BST_REQUIRED_VERSION_MAJOR = 5000 - - -def setup(): - return FooElement diff --git a/tests/internals/pluginfactory/badversionsource/__init__.py b/tests/internals/pluginfactory/badversionsource/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/internals/pluginfactory/badversionsource/foo.py b/tests/internals/pluginfactory/badversionsource/foo.py deleted file mode 100644 index 23333a9d8..000000000 --- a/tests/internals/pluginfactory/badversionsource/foo.py +++ /dev/null @@ -1,11 +0,0 @@ -from buildstream import Source - - -class FooSource(Source): - - # We have a little while until we have to manually modify this - BST_REQUIRED_VERSION_MAJOR = 5000 - - -def setup(): - return FooSource diff --git a/tests/internals/pluginfactory/customelement/__init__.py b/tests/internals/pluginfactory/customelement/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/internals/pluginfactory/customelement/foo.py b/tests/internals/pluginfactory/customelement/foo.py deleted file mode 100644 index 260de8b27..000000000 --- a/tests/internals/pluginfactory/customelement/foo.py +++ /dev/null @@ -1,9 +0,0 @@ -from buildstream import Element - - -class FooElement(Element): - pass - - -def setup(): - return FooElement diff --git a/tests/internals/pluginfactory/customsource/__init__.py b/tests/internals/pluginfactory/customsource/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/internals/pluginfactory/customsource/foo.py b/tests/internals/pluginfactory/customsource/foo.py deleted file mode 100644 index de78a00ce..000000000 --- a/tests/internals/pluginfactory/customsource/foo.py +++ /dev/null @@ -1,9 +0,0 @@ -from buildstream import Source - - -class FooSource(Source): - pass - - -def setup(): - return FooSource diff --git a/tests/internals/pluginfactory/nosetup/__init__.py b/tests/internals/pluginfactory/nosetup/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/internals/pluginfactory/nosetup/foo.py b/tests/internals/pluginfactory/nosetup/foo.py deleted file mode 100644 index 0b5a4fa7e..000000000 --- a/tests/internals/pluginfactory/nosetup/foo.py +++ /dev/null @@ -1,8 +0,0 @@ -# A plugin is supposed to define a setup function -# which returns the type that the plugin provides -# -# This plugin fails to do so - - -def useless(): - print("Hello World") diff --git a/tests/internals/pluginfactory/notatype/__init__.py b/tests/internals/pluginfactory/notatype/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/internals/pluginfactory/notatype/foo.py b/tests/internals/pluginfactory/notatype/foo.py deleted file mode 100644 index 311a4fb32..000000000 --- a/tests/internals/pluginfactory/notatype/foo.py +++ /dev/null @@ -1,6 +0,0 @@ -# Plugins are supposed to return a subclass type -# of Source or Element, depending on plugin type. - - -def setup(): - return 5 diff --git a/tests/internals/pluginfactory/wrongtype/__init__.py b/tests/internals/pluginfactory/wrongtype/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/internals/pluginfactory/wrongtype/foo.py b/tests/internals/pluginfactory/wrongtype/foo.py deleted file mode 100644 index 37d9f6bfe..000000000 --- a/tests/internals/pluginfactory/wrongtype/foo.py +++ /dev/null @@ -1,12 +0,0 @@ -# Plugins are supposed to return a subclass type -# of Source or Element, depending on plugin type. -# -# This one fails the requirement - - -class Foo: - pass - - -def setup(): - return Foo diff --git a/tests/internals/pluginloading.py b/tests/internals/pluginloading.py index 0685b09da..1f4446541 100644 --- a/tests/internals/pluginloading.py +++ b/tests/internals/pluginloading.py @@ -3,8 +3,6 @@ import os import pytest from buildstream._project import Project -from buildstream._exceptions import LoadError -from buildstream.exceptions import LoadErrorReason from buildstream._pipeline import Pipeline from tests.testutils import dummy_context @@ -38,23 +36,3 @@ def test_customelement(datafiles, tmpdir): basedir = str(datafiles) with create_pipeline(tmpdir, basedir, "simple.bst") as targets: assert targets[0].get_kind() == "foo" - - -@pytest.mark.datafiles(os.path.join(DATA_DIR, "badversionsource")) -def test_badversionsource(datafiles, tmpdir): - basedir = str(datafiles) - - with pytest.raises(LoadError) as exc, create_pipeline(tmpdir, basedir, "simple.bst"): - pass - - assert exc.value.reason == LoadErrorReason.UNSUPPORTED_PLUGIN - - -@pytest.mark.datafiles(os.path.join(DATA_DIR, "badversionelement")) -def test_badversionelement(datafiles, tmpdir): - basedir = str(datafiles) - - with pytest.raises(LoadError) as exc, create_pipeline(tmpdir, basedir, "simple.bst"): - pass - - assert exc.value.reason == LoadErrorReason.UNSUPPORTED_PLUGIN diff --git a/tests/internals/pluginloading/badversionelement/customelements/__init__.py b/tests/internals/pluginloading/badversionelement/customelements/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/internals/pluginloading/badversionelement/customelements/foo.py b/tests/internals/pluginloading/badversionelement/customelements/foo.py deleted file mode 100644 index 75536e87f..000000000 --- a/tests/internals/pluginloading/badversionelement/customelements/foo.py +++ /dev/null @@ -1,19 +0,0 @@ -from buildstream import Element - - -class FooElement(Element): - - BST_FORMAT_VERSION = 5 - - def preflight(self): - pass - - def configure(self, node): - pass - - def get_unique_key(self): - return {} - - -def setup(): - return FooElement diff --git a/tests/internals/pluginloading/badversionelement/elements/simple.bst b/tests/internals/pluginloading/badversionelement/elements/simple.bst deleted file mode 100644 index f949dc5b5..000000000 --- a/tests/internals/pluginloading/badversionelement/elements/simple.bst +++ /dev/null @@ -1,4 +0,0 @@ -kind: foo -description: Custom foo element -config: - some: thing diff --git a/tests/internals/pluginloading/badversionelement/project.conf b/tests/internals/pluginloading/badversionelement/project.conf deleted file mode 100644 index bff73e251..000000000 --- a/tests/internals/pluginloading/badversionelement/project.conf +++ /dev/null @@ -1,10 +0,0 @@ -name: pony -min-version: 2.0 -element-path: elements - -plugins: -- origin: local - path: customelements - elements: - # We provided bar at version 5, should be a conflict. - foo: 10 diff --git a/tests/internals/pluginloading/badversionsource/customsources/__init__.py b/tests/internals/pluginloading/badversionsource/customsources/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/internals/pluginloading/badversionsource/customsources/foo.py b/tests/internals/pluginloading/badversionsource/customsources/foo.py deleted file mode 100644 index 628f99b29..000000000 --- a/tests/internals/pluginloading/badversionsource/customsources/foo.py +++ /dev/null @@ -1,16 +0,0 @@ -from buildstream import Source - - -class BarSource(Source): - - BST_FORMAT_VERSION = 5 - - def preflight(self): - pass - - def configure(self, node): - pass - - -def setup(): - return BarSource diff --git a/tests/internals/pluginloading/badversionsource/elements/simple.bst b/tests/internals/pluginloading/badversionsource/elements/simple.bst deleted file mode 100644 index 7e0cc43b7..000000000 --- a/tests/internals/pluginloading/badversionsource/elements/simple.bst +++ /dev/null @@ -1,6 +0,0 @@ -kind: autotools -description: Custom foo source -sources: -- kind: foo - ref: 1.2.3 - uri: http://ponyland.com diff --git a/tests/internals/pluginloading/badversionsource/project.conf b/tests/internals/pluginloading/badversionsource/project.conf deleted file mode 100644 index cd5b2dc82..000000000 --- a/tests/internals/pluginloading/badversionsource/project.conf +++ /dev/null @@ -1,10 +0,0 @@ -name: pony -min-version: 2.0 -element-path: elements - -plugins: -- origin: local - path: customsources - sources: - # We provided bar at version 5, should be a conflict. - foo: 10 diff --git a/tests/internals/pluginloading/customelement/project.conf b/tests/internals/pluginloading/customelement/project.conf index 6a33cc504..2619bdf82 100644 --- a/tests/internals/pluginloading/customelement/project.conf +++ b/tests/internals/pluginloading/customelement/project.conf @@ -5,4 +5,4 @@ plugins: - origin: local path: pluginelements elements: - foo: 0 + - foo diff --git a/tests/internals/pluginloading/customsource/project.conf b/tests/internals/pluginloading/customsource/project.conf index 87d9b5d09..5cb6da537 100644 --- a/tests/internals/pluginloading/customsource/project.conf +++ b/tests/internals/pluginloading/customsource/project.conf @@ -5,4 +5,4 @@ plugins: - origin: local path: pluginsources sources: - foo: 0 + - foo diff --git a/tests/plugins/deprecationwarnings/project/project.conf b/tests/plugins/deprecationwarnings/project/project.conf index 8a2536cab..9e03afe0a 100644 --- a/tests/plugins/deprecationwarnings/project/project.conf +++ b/tests/plugins/deprecationwarnings/project/project.conf @@ -12,4 +12,4 @@ plugins: - origin: local path: plugins/elements elements: - deprecated_plugin: 0 + - deprecated_plugin diff --git a/tests/sourcecache/project/project.conf b/tests/sourcecache/project/project.conf index dde417f71..e3635e8a7 100644 --- a/tests/sourcecache/project/project.conf +++ b/tests/sourcecache/project/project.conf @@ -9,4 +9,4 @@ plugins: - origin: local path: plugins/elements elements: - always_fail: 0 \ No newline at end of file + - always_fail diff --git a/tests/sources/no-fetch-cached/project.conf b/tests/sources/no-fetch-cached/project.conf index eeb00d432..ef5b4a3b9 100644 --- a/tests/sources/no-fetch-cached/project.conf +++ b/tests/sources/no-fetch-cached/project.conf @@ -6,4 +6,4 @@ plugins: - origin: local path: plugins/sources sources: - always_cached: 0 + - always_cached diff --git a/tests/sources/previous_source_access/project.conf b/tests/sources/previous_source_access/project.conf index 2d3bb1fb2..b08df35f0 100644 --- a/tests/sources/previous_source_access/project.conf +++ b/tests/sources/previous_source_access/project.conf @@ -11,4 +11,4 @@ plugins: - origin: local path: plugins/sources sources: - foo_transform: 0 + - foo_transform diff --git a/tests/sources/project_key_test/project.conf b/tests/sources/project_key_test/project.conf index fb9ba1f88..6b82d4055 100644 --- a/tests/sources/project_key_test/project.conf +++ b/tests/sources/project_key_test/project.conf @@ -8,4 +8,4 @@ plugins: - origin: local path: plugins/sources sources: - key-test: 0 \ No newline at end of file + - key-test -- cgit v1.2.1 From 3b1af6036c14d4a9b017776358b4ac4c5622ec9e Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Mon, 27 Apr 2020 15:35:24 +0900 Subject: tests/plugins/deprecationwarnings.py: Moved from it's subdirectory Before adding more tests to this directory, just making the directory match more the structure of other test directories. --- tests/plugins/deprecationwarnings.py | 38 ++++++++++++++++++++++ tests/plugins/deprecationwarnings/__init__.py | 0 .../deprecationwarnings/deprecationwarnings.py | 38 ---------------------- .../deprecationwarnings/elements/deprecated.bst | 1 + .../plugins/elements/deprecated_plugin.py | 11 +++++++ .../plugins/elements/deprecated_plugin.yaml | 22 +++++++++++++ tests/plugins/deprecationwarnings/project.conf | 15 +++++++++ .../project/elements/deprecated.bst | 1 - .../project/plugins/elements/deprecated_plugin.py | 11 ------- .../plugins/elements/deprecated_plugin.yaml | 22 ------------- .../deprecationwarnings/project/project.conf | 15 --------- 11 files changed, 87 insertions(+), 87 deletions(-) create mode 100644 tests/plugins/deprecationwarnings.py delete mode 100644 tests/plugins/deprecationwarnings/__init__.py delete mode 100644 tests/plugins/deprecationwarnings/deprecationwarnings.py create mode 100644 tests/plugins/deprecationwarnings/elements/deprecated.bst create mode 100644 tests/plugins/deprecationwarnings/plugins/elements/deprecated_plugin.py create mode 100644 tests/plugins/deprecationwarnings/plugins/elements/deprecated_plugin.yaml create mode 100644 tests/plugins/deprecationwarnings/project.conf delete mode 100644 tests/plugins/deprecationwarnings/project/elements/deprecated.bst delete mode 100644 tests/plugins/deprecationwarnings/project/plugins/elements/deprecated_plugin.py delete mode 100644 tests/plugins/deprecationwarnings/project/plugins/elements/deprecated_plugin.yaml delete mode 100644 tests/plugins/deprecationwarnings/project/project.conf diff --git a/tests/plugins/deprecationwarnings.py b/tests/plugins/deprecationwarnings.py new file mode 100644 index 000000000..a4da3ea72 --- /dev/null +++ b/tests/plugins/deprecationwarnings.py @@ -0,0 +1,38 @@ +# Pylint doesn't play well with fixtures and dependency injection from pytest +# pylint: disable=redefined-outer-name + +import os + +import pytest + +from buildstream.testing import cli # pylint: disable=unused-import + + +DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "deprecationwarnings") + +_DEPRECATION_MESSAGE = "Here is some detail." +_DEPRECATION_WARNING = "Using deprecated plugin deprecated_plugin: {}".format(_DEPRECATION_MESSAGE) + + +@pytest.mark.datafiles(DATA_DIR) +def test_deprecation_warning_present(cli, datafiles): + project = str(datafiles) + result = cli.run(project=project, args=["show", "deprecated.bst"]) + result.assert_success() + assert _DEPRECATION_WARNING in result.stderr + + +@pytest.mark.datafiles(DATA_DIR) +def test_suppress_deprecation_warning(cli, datafiles): + project = str(datafiles) + cli.run(project=project, args=["show", "manual.bst"]) + + element_overrides = "elements:\n" " deprecated_plugin:\n" " suppress-deprecation-warnings : True\n" + + project_conf = os.path.join(project, "project.conf") + with open(project_conf, "a") as f: + f.write(element_overrides) + + result = cli.run(project=project, args=["show", "deprecated.bst"]) + result.assert_success() + assert _DEPRECATION_WARNING not in result.stderr diff --git a/tests/plugins/deprecationwarnings/__init__.py b/tests/plugins/deprecationwarnings/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/plugins/deprecationwarnings/deprecationwarnings.py b/tests/plugins/deprecationwarnings/deprecationwarnings.py deleted file mode 100644 index 628faea68..000000000 --- a/tests/plugins/deprecationwarnings/deprecationwarnings.py +++ /dev/null @@ -1,38 +0,0 @@ -# Pylint doesn't play well with fixtures and dependency injection from pytest -# pylint: disable=redefined-outer-name - -import os - -import pytest - -from buildstream.testing import cli # pylint: disable=unused-import - - -DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "project") - -_DEPRECATION_MESSAGE = "Here is some detail." -_DEPRECATION_WARNING = "Using deprecated plugin deprecated_plugin: {}".format(_DEPRECATION_MESSAGE) - - -@pytest.mark.datafiles(DATA_DIR) -def test_deprecation_warning_present(cli, datafiles): - project = str(datafiles) - result = cli.run(project=project, args=["show", "deprecated.bst"]) - result.assert_success() - assert _DEPRECATION_WARNING in result.stderr - - -@pytest.mark.datafiles(DATA_DIR) -def test_suppress_deprecation_warning(cli, datafiles): - project = str(datafiles) - cli.run(project=project, args=["show", "manual.bst"]) - - element_overrides = "elements:\n" " deprecated_plugin:\n" " suppress-deprecation-warnings : True\n" - - project_conf = os.path.join(project, "project.conf") - with open(project_conf, "a") as f: - f.write(element_overrides) - - result = cli.run(project=project, args=["show", "deprecated.bst"]) - result.assert_success() - assert _DEPRECATION_WARNING not in result.stderr diff --git a/tests/plugins/deprecationwarnings/elements/deprecated.bst b/tests/plugins/deprecationwarnings/elements/deprecated.bst new file mode 100644 index 000000000..e80bd91cd --- /dev/null +++ b/tests/plugins/deprecationwarnings/elements/deprecated.bst @@ -0,0 +1 @@ +kind: deprecated_plugin \ No newline at end of file diff --git a/tests/plugins/deprecationwarnings/plugins/elements/deprecated_plugin.py b/tests/plugins/deprecationwarnings/plugins/elements/deprecated_plugin.py new file mode 100644 index 000000000..1badf59af --- /dev/null +++ b/tests/plugins/deprecationwarnings/plugins/elements/deprecated_plugin.py @@ -0,0 +1,11 @@ +from buildstream import BuildElement + + +class DeprecatedPlugin(BuildElement): + BST_PLUGIN_DEPRECATED = True + BST_PLUGIN_DEPRECATION_MESSAGE = "Here is some detail." + + +# Plugin entry point +def setup(): + return DeprecatedPlugin diff --git a/tests/plugins/deprecationwarnings/plugins/elements/deprecated_plugin.yaml b/tests/plugins/deprecationwarnings/plugins/elements/deprecated_plugin.yaml new file mode 100644 index 000000000..1c07cd8b2 --- /dev/null +++ b/tests/plugins/deprecationwarnings/plugins/elements/deprecated_plugin.yaml @@ -0,0 +1,22 @@ +# Deprecated-plugin build element does not provide any default +# build commands +config: + + # Commands for configuring the software + # + configure-commands: [] + + # Commands for building the software + # + build-commands: [] + + # Commands for installing the software into a + # destination folder + # + install-commands: [] + + # Commands for stripping installed binaries + # + strip-commands: + - | + %{strip-binaries} \ No newline at end of file diff --git a/tests/plugins/deprecationwarnings/project.conf b/tests/plugins/deprecationwarnings/project.conf new file mode 100644 index 000000000..9e03afe0a --- /dev/null +++ b/tests/plugins/deprecationwarnings/project.conf @@ -0,0 +1,15 @@ +# Unique project name +name: deprecation-warnings + +# Required BuildStream version +min-version: 2.0 + +# Subdirectory where elements are stored +element-path: elements + +plugins: + +- origin: local + path: plugins/elements + elements: + - deprecated_plugin diff --git a/tests/plugins/deprecationwarnings/project/elements/deprecated.bst b/tests/plugins/deprecationwarnings/project/elements/deprecated.bst deleted file mode 100644 index e80bd91cd..000000000 --- a/tests/plugins/deprecationwarnings/project/elements/deprecated.bst +++ /dev/null @@ -1 +0,0 @@ -kind: deprecated_plugin \ No newline at end of file diff --git a/tests/plugins/deprecationwarnings/project/plugins/elements/deprecated_plugin.py b/tests/plugins/deprecationwarnings/project/plugins/elements/deprecated_plugin.py deleted file mode 100644 index 1badf59af..000000000 --- a/tests/plugins/deprecationwarnings/project/plugins/elements/deprecated_plugin.py +++ /dev/null @@ -1,11 +0,0 @@ -from buildstream import BuildElement - - -class DeprecatedPlugin(BuildElement): - BST_PLUGIN_DEPRECATED = True - BST_PLUGIN_DEPRECATION_MESSAGE = "Here is some detail." - - -# Plugin entry point -def setup(): - return DeprecatedPlugin diff --git a/tests/plugins/deprecationwarnings/project/plugins/elements/deprecated_plugin.yaml b/tests/plugins/deprecationwarnings/project/plugins/elements/deprecated_plugin.yaml deleted file mode 100644 index 1c07cd8b2..000000000 --- a/tests/plugins/deprecationwarnings/project/plugins/elements/deprecated_plugin.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Deprecated-plugin build element does not provide any default -# build commands -config: - - # Commands for configuring the software - # - configure-commands: [] - - # Commands for building the software - # - build-commands: [] - - # Commands for installing the software into a - # destination folder - # - install-commands: [] - - # Commands for stripping installed binaries - # - strip-commands: - - | - %{strip-binaries} \ No newline at end of file diff --git a/tests/plugins/deprecationwarnings/project/project.conf b/tests/plugins/deprecationwarnings/project/project.conf deleted file mode 100644 index 9e03afe0a..000000000 --- a/tests/plugins/deprecationwarnings/project/project.conf +++ /dev/null @@ -1,15 +0,0 @@ -# Unique project name -name: deprecation-warnings - -# Required BuildStream version -min-version: 2.0 - -# Subdirectory where elements are stored -element-path: elements - -plugins: - -- origin: local - path: plugins/elements - elements: - - deprecated_plugin -- cgit v1.2.1 From b3556a3284708b904f20586d4636c31a91106809 Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Mon, 27 Apr 2020 17:12:08 +0900 Subject: tests/plugins/loading.py: New test replaces removed internal test This test tests some of the basic failure modes of plugin loading, which used to be written as internal tests but is not implemented as a proper end-to-end test. This commit also adds some machine readable reason codes to pluginfactory.py so that we can assert the errors more specifically. --- src/buildstream/_pluginfactory/pluginfactory.py | 12 ++- tests/plugins/loading.py | 110 +++++++++++++++++++++ tests/plugins/loading/plugins/nosetup/nosetup.py | 8 ++ .../plugins/setupnotfunction/setupnotfunction.py | 6 ++ .../setupreturnsbadtype/setupreturnsbadtype.py | 15 +++ .../setupreturnsnottype/setupreturnsnottype.py | 8 ++ tests/plugins/loading/project.conf | 8 ++ 7 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 tests/plugins/loading.py create mode 100644 tests/plugins/loading/plugins/nosetup/nosetup.py create mode 100644 tests/plugins/loading/plugins/setupnotfunction/setupnotfunction.py create mode 100644 tests/plugins/loading/plugins/setupreturnsbadtype/setupreturnsbadtype.py create mode 100644 tests/plugins/loading/plugins/setupreturnsnottype/setupreturnsnottype.py create mode 100644 tests/plugins/loading/project.conf diff --git a/src/buildstream/_pluginfactory/pluginfactory.py b/src/buildstream/_pluginfactory/pluginfactory.py index c42b0a3d2..7e5ae2db0 100644 --- a/src/buildstream/_pluginfactory/pluginfactory.py +++ b/src/buildstream/_pluginfactory/pluginfactory.py @@ -240,11 +240,13 @@ class PluginFactory: plugin_type = plugin.setup() except AttributeError as e: raise PluginError( - "{} plugin '{}' did not provide a setup() function".format(self._base_type.__name__, kind) + "{} plugin '{}' did not provide a setup() function".format(self._base_type.__name__, kind), + reason="missing-setup-function", ) from e except TypeError as e: raise PluginError( - "setup symbol in {} plugin '{}' is not a function".format(self._base_type.__name__, kind) + "setup symbol in {} plugin '{}' is not a function".format(self._base_type.__name__, kind), + reason="setup-is-not-function", ) from e self._assert_plugin(kind, plugin_type) @@ -261,11 +263,13 @@ class PluginFactory: raise PluginError( "{} plugin '{}' returned type '{}', which is not a subclass of {}".format( self._base_type.__name__, kind, plugin_type.__name__, self._base_type.__name__ - ) + ), + reason="setup-returns-bad-type", ) except TypeError as e: raise PluginError( "{} plugin '{}' returned something that is not a type (expected subclass of {})".format( self._base_type.__name__, kind, self._base_type.__name__ - ) + ), + reason="setup-returns-not-type", ) from e diff --git a/tests/plugins/loading.py b/tests/plugins/loading.py new file mode 100644 index 000000000..1c8a321d5 --- /dev/null +++ b/tests/plugins/loading.py @@ -0,0 +1,110 @@ +# Pylint doesn't play well with fixtures and dependency injection from pytest +# pylint: disable=redefined-outer-name + +# +# This test case tests the failure modes of loading a plugin +# after it has already been discovered via it's origin. +# + +import os +import pytest + +from buildstream.exceptions import ErrorDomain +from buildstream.testing import cli # pylint: disable=unused-import +from buildstream import _yaml + + +DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "loading") + + +def update_project(project_path, updated_configuration): + project_conf_path = os.path.join(project_path, "project.conf") + project_conf = _yaml.roundtrip_load(project_conf_path) + + project_conf.update(updated_configuration) + + _yaml.roundtrip_dump(project_conf, project_conf_path) + + +# Sets up the element.bst file so that it requires a source +# or element plugin. +# +def setup_element(project_path, plugin_type, plugin_name): + element_dir = os.path.join(project_path, "elements") + element_path = os.path.join(element_dir, "element.bst") + os.makedirs(element_dir, exist_ok=True) + + if plugin_type == "elements": + element = {"kind": plugin_name} + else: + element = {"kind": "manual", "sources": [{"kind": plugin_name}]} + + _yaml.roundtrip_dump(element, element_path) + + +#################################################### +# Tests # +#################################################### +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("plugin_type", [("elements"), ("sources")]) +def test_nosetup(cli, datafiles, plugin_type): + project = str(datafiles) + + update_project(project, {"plugins": [{"origin": "local", "path": "plugins/nosetup", plugin_type: ["nosetup"]}]}) + setup_element(project, plugin_type, "nosetup") + + result = cli.run(project=project, args=["show", "element.bst"]) + result.assert_main_error(ErrorDomain.PLUGIN, "missing-setup-function") + + +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("plugin_type", [("elements"), ("sources")]) +def test_setup_not_function(cli, datafiles, plugin_type): + project = str(datafiles) + + update_project( + project, + {"plugins": [{"origin": "local", "path": "plugins/setupnotfunction", plugin_type: ["setupnotfunction"]}]}, + ) + setup_element(project, plugin_type, "setupnotfunction") + + result = cli.run(project=project, args=["show", "element.bst"]) + result.assert_main_error(ErrorDomain.PLUGIN, "setup-is-not-function") + + +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("plugin_type", [("elements"), ("sources")]) +def test_setup_returns_not_type(cli, datafiles, plugin_type): + project = str(datafiles) + + update_project( + project, + { + "plugins": [ + {"origin": "local", "path": "plugins/setupreturnsnottype", plugin_type: ["setupreturnsnottype"]} + ] + }, + ) + setup_element(project, plugin_type, "setupreturnsnottype") + + result = cli.run(project=project, args=["show", "element.bst"]) + result.assert_main_error(ErrorDomain.PLUGIN, "setup-returns-not-type") + + +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("plugin_type", [("elements"), ("sources")]) +def test_setup_returns_bad_type(cli, datafiles, plugin_type): + project = str(datafiles) + + update_project( + project, + { + "plugins": [ + {"origin": "local", "path": "plugins/setupreturnsbadtype", plugin_type: ["setupreturnsbadtype"]} + ] + }, + ) + setup_element(project, plugin_type, "setupreturnsbadtype") + + result = cli.run(project=project, args=["show", "element.bst"]) + result.assert_main_error(ErrorDomain.PLUGIN, "setup-returns-bad-type") diff --git a/tests/plugins/loading/plugins/nosetup/nosetup.py b/tests/plugins/loading/plugins/nosetup/nosetup.py new file mode 100644 index 000000000..0b5a4fa7e --- /dev/null +++ b/tests/plugins/loading/plugins/nosetup/nosetup.py @@ -0,0 +1,8 @@ +# A plugin is supposed to define a setup function +# which returns the type that the plugin provides +# +# This plugin fails to do so + + +def useless(): + print("Hello World") diff --git a/tests/plugins/loading/plugins/setupnotfunction/setupnotfunction.py b/tests/plugins/loading/plugins/setupnotfunction/setupnotfunction.py new file mode 100644 index 000000000..1272f4ee0 --- /dev/null +++ b/tests/plugins/loading/plugins/setupnotfunction/setupnotfunction.py @@ -0,0 +1,6 @@ +# A plugin's setup symbol is supposed to be a function +# which returns the plugin type. +# +# This plugin's setup symbol is not such a function. + +setup = 9 diff --git a/tests/plugins/loading/plugins/setupreturnsbadtype/setupreturnsbadtype.py b/tests/plugins/loading/plugins/setupreturnsbadtype/setupreturnsbadtype.py new file mode 100644 index 000000000..25795c06b --- /dev/null +++ b/tests/plugins/loading/plugins/setupreturnsbadtype/setupreturnsbadtype.py @@ -0,0 +1,15 @@ +# A plugin's setup symbol is supposed to be a function +# which returns the plugin type, which should be a subclass +# of Source or Element depending on the plugin type. +# +# This plugin's setup function returns a different kind +# of type. + + +class Pony: + def __init__(self): + self.pony = 12 + + +def setup(): + return Pony diff --git a/tests/plugins/loading/plugins/setupreturnsnottype/setupreturnsnottype.py b/tests/plugins/loading/plugins/setupreturnsnottype/setupreturnsnottype.py new file mode 100644 index 000000000..d47367ea9 --- /dev/null +++ b/tests/plugins/loading/plugins/setupreturnsnottype/setupreturnsnottype.py @@ -0,0 +1,8 @@ +# A plugin's setup symbol is supposed to be a function +# which returns the plugin type. +# +# This plugin's setup function returns a number instead + + +def setup(): + return 5 diff --git a/tests/plugins/loading/project.conf b/tests/plugins/loading/project.conf new file mode 100644 index 000000000..738051af9 --- /dev/null +++ b/tests/plugins/loading/project.conf @@ -0,0 +1,8 @@ +# This project.conf gets rewritten for each plugin loading test +name: test + +# Required BuildStream version +min-version: 2.0 + +# Subdirectory where elements are stored +element-path: elements -- cgit v1.2.1 From a311a14f4b0a649b5f872ab020885b0a1d6e09d1 Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Tue, 28 Apr 2020 19:23:31 +0900 Subject: plugin.py/pluginfactory.py: Implementing BST_MIN_VERSION The BST_MIN_VERSION guards assert that the BuildStream core which loaded the plugin is compatible with the plugin itself. This commit adds BST_MIN_VERSION to the base plugin.py with documentation informing Plugin authors how to set the minimum version, and also adds the assertions at plugin loading time in pluginfactory.py. This commit also: * Adds the BST_MIN_VERSION specification to all current core plugins * Adds the BST_MIN_VERSION specification to plugins used in test cases --- src/buildstream/_exceptions.py | 4 +- src/buildstream/_pluginfactory/pluginfactory.py | 45 ++++++++++++++++++++++ src/buildstream/plugin.py | 26 +++++++++++++ src/buildstream/plugins/elements/autotools.py | 4 ++ src/buildstream/plugins/elements/compose.py | 2 + src/buildstream/plugins/elements/filter.py | 2 + src/buildstream/plugins/elements/import.py | 2 + src/buildstream/plugins/elements/junction.py | 2 + src/buildstream/plugins/elements/manual.py | 4 ++ src/buildstream/plugins/elements/pip.py | 4 ++ src/buildstream/plugins/elements/script.py | 4 ++ src/buildstream/plugins/elements/stack.py | 3 ++ src/buildstream/plugins/sources/bzr.py | 2 + src/buildstream/plugins/sources/git.py | 3 +- src/buildstream/plugins/sources/local.py | 2 + src/buildstream/plugins/sources/patch.py | 2 + src/buildstream/plugins/sources/pip.py | 2 + src/buildstream/plugins/sources/remote.py | 2 + src/buildstream/plugins/sources/tar.py | 2 + src/buildstream/plugins/sources/workspace.py | 3 ++ src/buildstream/plugins/sources/zip.py | 2 + .../filter/basic/element_plugins/dynamic.py | 3 ++ tests/format/project/plugin-allowed/plugins/foo.py | 3 ++ .../forbidden-plugins/forbidden-plugin.py | 3 ++ .../plugin-no-load-ref/plugins/noloadref.py | 3 ++ .../errorplugin/preflighterror.py | 3 ++ .../frontend/configuredwarning/plugins/corewarn.py | 3 ++ .../frontend/configuredwarning/plugins/warninga.py | 3 ++ .../frontend/configuredwarning/plugins/warningb.py | 3 ++ .../consistencyerror/plugins/consistencybug.py | 3 ++ .../consistencyerror/plugins/consistencyerror.py | 3 ++ tests/frontend/project/plugins/randomelement.py | 3 ++ tests/frontend/project/sources/fetch_source.py | 3 ++ .../customelement/pluginelements/foo.py | 3 ++ .../customsource/pluginsources/foo.py | 3 ++ .../plugins/elements/deprecated_plugin.py | 1 + .../project/plugins/elements/always_fail.py | 3 ++ .../plugins/sources/always_cached.py | 2 + .../plugins/sources/foo_transform.py | 1 + .../project_key_test/plugins/sources/key-test.py | 2 + 40 files changed, 170 insertions(+), 3 deletions(-) diff --git a/src/buildstream/_exceptions.py b/src/buildstream/_exceptions.py index f92ab822b..30fc26e71 100644 --- a/src/buildstream/_exceptions.py +++ b/src/buildstream/_exceptions.py @@ -124,8 +124,8 @@ class BstError(Exception): # or by the base :class:`.Plugin` element itself. # class PluginError(BstError): - def __init__(self, message, reason=None, temporary=False): - super().__init__(message, domain=ErrorDomain.PLUGIN, reason=reason, temporary=temporary) + def __init__(self, message, *, reason=None, detail=None, temporary=False): + super().__init__(message, domain=ErrorDomain.PLUGIN, detail=detail, reason=reason, temporary=temporary) # LoadError diff --git a/src/buildstream/_pluginfactory/pluginfactory.py b/src/buildstream/_pluginfactory/pluginfactory.py index 7e5ae2db0..27321c62f 100644 --- a/src/buildstream/_pluginfactory/pluginfactory.py +++ b/src/buildstream/_pluginfactory/pluginfactory.py @@ -20,6 +20,8 @@ import os import inspect +from .. import utils +from ..utils import UtilError from .._exceptions import PluginError from .pluginorigin import PluginOrigin, PluginOriginType @@ -250,6 +252,8 @@ class PluginFactory: ) from e self._assert_plugin(kind, plugin_type) + self._assert_min_version(kind, plugin_type) + return (plugin_type, defaults) def _assert_plugin(self, kind, plugin_type): @@ -273,3 +277,44 @@ class PluginFactory: ), reason="setup-returns-not-type", ) from e + + def _assert_min_version(self, kind, plugin_type): + + if plugin_type.BST_MIN_VERSION is None: + raise PluginError( + "{} plugin '{}' did not specify BST_MIN_VERSION".format(self._base_type.__name__, kind), + reason="missing-min-version", + detail="Are you trying to use a BuildStream 1 plugin with a BuildStream 2 project ?", + ) + + try: + min_version_major, min_version_minor = utils._parse_version(plugin_type.BST_MIN_VERSION) + except UtilError as e: + raise PluginError( + "{} plugin '{}' specified malformed BST_MIN_VERSION: {}".format( + self._base_type.__name__, kind, plugin_type.BST_MIN_VERSION + ), + reason="malformed-min-version", + detail="BST_MIN_VERSION must be specified as 'MAJOR.MINOR' with " + + "numeric major and minor minimum required version numbers", + ) from e + + bst_major, bst_minor = utils._get_bst_api_version() + + if min_version_major != bst_major: + raise PluginError( + "{} plugin '{}' requires BuildStream {}, but is being loaded with BuildStream {}".format( + self._base_type.__name__, kind, min_version_major, bst_major + ), + reason="incompatible-major-version", + detail="You will need to find the correct version of this plugin for your project.", + ) + + if min_version_minor > bst_minor: + raise PluginError( + "{} plugin '{}' requires BuildStream {}, but is being loaded with BuildStream {}.{}".format( + self._base_type.__name__, kind, plugin_type.BST_MIN_VERSION, bst_major, bst_minor + ), + reason="incompatible-minor-version", + detail="Please upgrade to BuildStream {}".format(plugin_type.BST_MIN_VERSION), + ) diff --git a/src/buildstream/plugin.py b/src/buildstream/plugin.py index 4f2d74304..935db6523 100644 --- a/src/buildstream/plugin.py +++ b/src/buildstream/plugin.py @@ -148,6 +148,32 @@ class Plugin: which are included in the buildstream namespace. """ + BST_MIN_VERSION = None + """The minimum required version of BuildStream required by this plugin. + + The version must be expressed as the string *"."*, where the + *major* version number is the API version and the *minor* version number is + the revision of the same BuildStream API where new symbols might have been + added to the API. + + **Example:** + + The following statement means that this plugin works with *BuildStream 2.X*, + only if *X >= 2*: + + .. code:: python + + class Foo(Source): + + # Our plugin requires 2.2 + BST_MIN_VERSION = "2.2" + + .. note:: + + This version works exactly the same was as the :ref:`min-version ` + which must be specified in the project.conf file. + """ + BST_PLUGIN_DEPRECATED = False """True if this element plugin has been deprecated. diff --git a/src/buildstream/plugins/elements/autotools.py b/src/buildstream/plugins/elements/autotools.py index c4396c5cc..5f5a1b465 100644 --- a/src/buildstream/plugins/elements/autotools.py +++ b/src/buildstream/plugins/elements/autotools.py @@ -60,6 +60,10 @@ from buildstream import BuildElement, SandboxFlags # Element implementation for the 'autotools' kind. class AutotoolsElement(BuildElement): + # pylint: disable=attribute-defined-outside-init + + BST_MIN_VERSION = "2.0" + # Enable command batching across prepare() and assemble() def configure_sandbox(self, sandbox): super().configure_sandbox(sandbox) diff --git a/src/buildstream/plugins/elements/compose.py b/src/buildstream/plugins/elements/compose.py index 6e4aed193..9c136df9d 100644 --- a/src/buildstream/plugins/elements/compose.py +++ b/src/buildstream/plugins/elements/compose.py @@ -41,6 +41,8 @@ from buildstream import Element, Scope class ComposeElement(Element): # pylint: disable=attribute-defined-outside-init + BST_MIN_VERSION = "2.0" + # The compose element's output is its dependencies, so # we must rebuild if the dependencies change even when # not in strict build plans. diff --git a/src/buildstream/plugins/elements/filter.py b/src/buildstream/plugins/elements/filter.py index 37b205fe2..62c04e027 100644 --- a/src/buildstream/plugins/elements/filter.py +++ b/src/buildstream/plugins/elements/filter.py @@ -140,6 +140,8 @@ from buildstream import Element, ElementError, Scope class FilterElement(Element): # pylint: disable=attribute-defined-outside-init + BST_MIN_VERSION = "2.0" + BST_ARTIFACT_VERSION = 1 # The filter element's output is its dependencies, so diff --git a/src/buildstream/plugins/elements/import.py b/src/buildstream/plugins/elements/import.py index 2bbd11354..d9961aa6f 100644 --- a/src/buildstream/plugins/elements/import.py +++ b/src/buildstream/plugins/elements/import.py @@ -38,6 +38,8 @@ from buildstream import Element, ElementError class ImportElement(Element): # pylint: disable=attribute-defined-outside-init + BST_MIN_VERSION = "2.0" + # Import elements do not run any commands BST_RUN_COMMANDS = False diff --git a/src/buildstream/plugins/elements/junction.py b/src/buildstream/plugins/elements/junction.py index 46c4ddf68..917482d63 100644 --- a/src/buildstream/plugins/elements/junction.py +++ b/src/buildstream/plugins/elements/junction.py @@ -165,6 +165,8 @@ from buildstream._pipeline import PipelineError class JunctionElement(Element): # pylint: disable=attribute-defined-outside-init + BST_MIN_VERSION = "2.0" + # Junctions are not allowed any dependencies BST_FORBID_BDEPENDS = True BST_FORBID_RDEPENDS = True diff --git a/src/buildstream/plugins/elements/manual.py b/src/buildstream/plugins/elements/manual.py index c95b8e5a2..125c5142b 100644 --- a/src/buildstream/plugins/elements/manual.py +++ b/src/buildstream/plugins/elements/manual.py @@ -36,6 +36,10 @@ from buildstream import BuildElement, SandboxFlags # Element implementation for the 'manual' kind. class ManualElement(BuildElement): + # pylint: disable=attribute-defined-outside-init + + BST_MIN_VERSION = "2.0" + # Enable command batching across prepare() and assemble() def configure_sandbox(self, sandbox): super().configure_sandbox(sandbox) diff --git a/src/buildstream/plugins/elements/pip.py b/src/buildstream/plugins/elements/pip.py index 06e187b69..5f805f460 100644 --- a/src/buildstream/plugins/elements/pip.py +++ b/src/buildstream/plugins/elements/pip.py @@ -36,6 +36,10 @@ from buildstream import BuildElement, SandboxFlags # Element implementation for the 'pip' kind. class PipElement(BuildElement): + # pylint: disable=attribute-defined-outside-init + + BST_MIN_VERSION = "2.0" + # Enable command batching across prepare() and assemble() def configure_sandbox(self, sandbox): super().configure_sandbox(sandbox) diff --git a/src/buildstream/plugins/elements/script.py b/src/buildstream/plugins/elements/script.py index e355e5f05..9d780ebe2 100644 --- a/src/buildstream/plugins/elements/script.py +++ b/src/buildstream/plugins/elements/script.py @@ -40,6 +40,10 @@ import buildstream # Element implementation for the 'script' kind. class ScriptElement(buildstream.ScriptElement): + # pylint: disable=attribute-defined-outside-init + + BST_MIN_VERSION = "2.0" + def configure(self, node): for n in node.get_sequence("layout", []): dst = self.node_subst_vars(n.get_scalar("destination")) diff --git a/src/buildstream/plugins/elements/stack.py b/src/buildstream/plugins/elements/stack.py index df11a257f..b15f67073 100644 --- a/src/buildstream/plugins/elements/stack.py +++ b/src/buildstream/plugins/elements/stack.py @@ -29,6 +29,9 @@ from buildstream import Element # Element implementation for the 'stack' kind. class StackElement(Element): + # pylint: disable=attribute-defined-outside-init + + BST_MIN_VERSION = "2.0" # This plugin does not produce any artifacts when built BST_ELEMENT_HAS_ARTIFACT = False diff --git a/src/buildstream/plugins/sources/bzr.py b/src/buildstream/plugins/sources/bzr.py index 8a02eff95..93b554472 100644 --- a/src/buildstream/plugins/sources/bzr.py +++ b/src/buildstream/plugins/sources/bzr.py @@ -66,6 +66,8 @@ from buildstream import utils class BzrSource(Source): # pylint: disable=attribute-defined-outside-init + BST_MIN_VERSION = "2.0" + def configure(self, node): node.validate_keys(["url", "track", "ref", *Source.COMMON_CONFIG_KEYS]) diff --git a/src/buildstream/plugins/sources/git.py b/src/buildstream/plugins/sources/git.py index 473166132..6427d4b04 100644 --- a/src/buildstream/plugins/sources/git.py +++ b/src/buildstream/plugins/sources/git.py @@ -156,7 +156,8 @@ from buildstream import _GitSourceBase class GitSource(_GitSourceBase): - pass + + BST_MIN_VERSION = "2.0" # Plugin entry point diff --git a/src/buildstream/plugins/sources/local.py b/src/buildstream/plugins/sources/local.py index 57bcf14df..c39e09417 100644 --- a/src/buildstream/plugins/sources/local.py +++ b/src/buildstream/plugins/sources/local.py @@ -44,6 +44,8 @@ from buildstream import Source, SourceError class LocalSource(Source): # pylint: disable=attribute-defined-outside-init + BST_MIN_VERSION = "2.0" + BST_STAGE_VIRTUAL_DIRECTORY = True BST_KEY_REQUIRES_STAGE = True diff --git a/src/buildstream/plugins/sources/patch.py b/src/buildstream/plugins/sources/patch.py index f33dfedec..18672df8a 100644 --- a/src/buildstream/plugins/sources/patch.py +++ b/src/buildstream/plugins/sources/patch.py @@ -52,6 +52,8 @@ from buildstream import utils class PatchSource(Source): # pylint: disable=attribute-defined-outside-init + BST_MIN_VERSION = "2.0" + BST_REQUIRES_PREVIOUS_SOURCES_STAGE = True def configure(self, node): diff --git a/src/buildstream/plugins/sources/pip.py b/src/buildstream/plugins/sources/pip.py index 69c08e81f..c0885ce5a 100644 --- a/src/buildstream/plugins/sources/pip.py +++ b/src/buildstream/plugins/sources/pip.py @@ -98,6 +98,8 @@ _SDIST_RE = re.compile(r"^([\w.-]+?)-((?:[\d.]+){2,})\.(?:tar|tar.bz2|tar.gz|tar class PipSource(Source): # pylint: disable=attribute-defined-outside-init + BST_MIN_VERSION = "2.0" + # We need access to previous sources at track time to use requirements.txt # but not at fetch time as self.ref should contain sufficient information # for this plugin diff --git a/src/buildstream/plugins/sources/remote.py b/src/buildstream/plugins/sources/remote.py index 29333c0d9..57d8743a7 100644 --- a/src/buildstream/plugins/sources/remote.py +++ b/src/buildstream/plugins/sources/remote.py @@ -55,6 +55,8 @@ from ._downloadablefilesource import DownloadableFileSource class RemoteSource(DownloadableFileSource): # pylint: disable=attribute-defined-outside-init + BST_MIN_VERSION = "2.0" + def configure(self, node): super().configure(node) diff --git a/src/buildstream/plugins/sources/tar.py b/src/buildstream/plugins/sources/tar.py index fc4f5736a..793fd11c9 100644 --- a/src/buildstream/plugins/sources/tar.py +++ b/src/buildstream/plugins/sources/tar.py @@ -92,6 +92,8 @@ class ReadableTarInfo(tarfile.TarInfo): class TarSource(DownloadableFileSource): # pylint: disable=attribute-defined-outside-init + BST_MIN_VERSION = "2.0" + def configure(self, node): super().configure(node) diff --git a/src/buildstream/plugins/sources/workspace.py b/src/buildstream/plugins/sources/workspace.py index 13e2bb37d..796f2b3d9 100644 --- a/src/buildstream/plugins/sources/workspace.py +++ b/src/buildstream/plugins/sources/workspace.py @@ -45,6 +45,9 @@ from buildstream.node import MappingNode class WorkspaceSource(Source): # pylint: disable=attribute-defined-outside-init + + BST_MIN_VERSION = "2.0" + BST_STAGE_VIRTUAL_DIRECTORY = True BST_KEY_REQUIRES_STAGE = True diff --git a/src/buildstream/plugins/sources/zip.py b/src/buildstream/plugins/sources/zip.py index 47823dfde..c112cf10e 100644 --- a/src/buildstream/plugins/sources/zip.py +++ b/src/buildstream/plugins/sources/zip.py @@ -69,6 +69,8 @@ from ._downloadablefilesource import DownloadableFileSource class ZipSource(DownloadableFileSource): # pylint: disable=attribute-defined-outside-init + BST_MIN_VERSION = "2.0" + def configure(self, node): super().configure(node) diff --git a/tests/elements/filter/basic/element_plugins/dynamic.py b/tests/elements/filter/basic/element_plugins/dynamic.py index bf079111f..db92a6647 100644 --- a/tests/elements/filter/basic/element_plugins/dynamic.py +++ b/tests/elements/filter/basic/element_plugins/dynamic.py @@ -3,6 +3,9 @@ from buildstream import Element, Scope # Copies files from the dependent element but inserts split-rules using dynamic data class DynamicElement(Element): + + BST_MIN_VERSION = "2.0" + def configure(self, node): node.validate_keys(["split-rules"]) self.split_rules = {key: value.as_str_list() for key, value in node.get_mapping("split-rules").items()} diff --git a/tests/format/project/plugin-allowed/plugins/foo.py b/tests/format/project/plugin-allowed/plugins/foo.py index bf884233c..76d9bfd3c 100644 --- a/tests/format/project/plugin-allowed/plugins/foo.py +++ b/tests/format/project/plugin-allowed/plugins/foo.py @@ -2,6 +2,9 @@ from buildstream import Element class FooElement(Element): + + BST_MIN_VERSION = "2.0" + def configure(self, config): pass diff --git a/tests/format/project/plugin-forbidden/forbidden-plugins/forbidden-plugin.py b/tests/format/project/plugin-forbidden/forbidden-plugins/forbidden-plugin.py index bf884233c..76d9bfd3c 100644 --- a/tests/format/project/plugin-forbidden/forbidden-plugins/forbidden-plugin.py +++ b/tests/format/project/plugin-forbidden/forbidden-plugins/forbidden-plugin.py @@ -2,6 +2,9 @@ from buildstream import Element class FooElement(Element): + + BST_MIN_VERSION = "2.0" + def configure(self, config): pass diff --git a/tests/format/project/plugin-no-load-ref/plugins/noloadref.py b/tests/format/project/plugin-no-load-ref/plugins/noloadref.py index e2fe0ac46..2b8fd0b51 100644 --- a/tests/format/project/plugin-no-load-ref/plugins/noloadref.py +++ b/tests/format/project/plugin-no-load-ref/plugins/noloadref.py @@ -6,6 +6,9 @@ from buildstream import Source # Use this to test that the core behaves as expected with such plugins. # class NoLoadRefSource(Source): + + BST_MIN_VERSION = "2.0" + def configure(self, node): pass diff --git a/tests/format/project/plugin-preflight-error/errorplugin/preflighterror.py b/tests/format/project/plugin-preflight-error/errorplugin/preflighterror.py index db2895f8b..a03cb64ee 100644 --- a/tests/format/project/plugin-preflight-error/errorplugin/preflighterror.py +++ b/tests/format/project/plugin-preflight-error/errorplugin/preflighterror.py @@ -2,6 +2,9 @@ from buildstream import Source, SourceError class PreflightErrorSource(Source): + + BST_MIN_VERSION = "2.0" + def configure(self, node): pass diff --git a/tests/frontend/configuredwarning/plugins/corewarn.py b/tests/frontend/configuredwarning/plugins/corewarn.py index bef0a4904..bcd40753c 100644 --- a/tests/frontend/configuredwarning/plugins/corewarn.py +++ b/tests/frontend/configuredwarning/plugins/corewarn.py @@ -3,6 +3,9 @@ from buildstream.plugin import CoreWarnings class CoreWarn(Element): + + BST_MIN_VERSION = "2.0" + def configure(self, node): pass diff --git a/tests/frontend/configuredwarning/plugins/warninga.py b/tests/frontend/configuredwarning/plugins/warninga.py index 9fd8dc61b..4ad0f3d20 100644 --- a/tests/frontend/configuredwarning/plugins/warninga.py +++ b/tests/frontend/configuredwarning/plugins/warninga.py @@ -4,6 +4,9 @@ WARNING_A = "warning-a" class WarningA(Element): + + BST_MIN_VERSION = "2.0" + def configure(self, node): pass diff --git a/tests/frontend/configuredwarning/plugins/warningb.py b/tests/frontend/configuredwarning/plugins/warningb.py index 64d25ef39..c7a995cf8 100644 --- a/tests/frontend/configuredwarning/plugins/warningb.py +++ b/tests/frontend/configuredwarning/plugins/warningb.py @@ -4,6 +4,9 @@ WARNING_B = "warning-b" class WarningB(Element): + + BST_MIN_VERSION = "2.0" + def configure(self, node): pass diff --git a/tests/frontend/consistencyerror/plugins/consistencybug.py b/tests/frontend/consistencyerror/plugins/consistencybug.py index abcbbc997..1952894ca 100644 --- a/tests/frontend/consistencyerror/plugins/consistencybug.py +++ b/tests/frontend/consistencyerror/plugins/consistencybug.py @@ -2,6 +2,9 @@ from buildstream import Source class ConsistencyBugSource(Source): + + BST_MIN_VERSION = "2.0" + def configure(self, node): pass diff --git a/tests/frontend/consistencyerror/plugins/consistencyerror.py b/tests/frontend/consistencyerror/plugins/consistencyerror.py index 2e30d4842..34af45782 100644 --- a/tests/frontend/consistencyerror/plugins/consistencyerror.py +++ b/tests/frontend/consistencyerror/plugins/consistencyerror.py @@ -2,6 +2,9 @@ from buildstream import Source, SourceError class ConsistencyErrorSource(Source): + + BST_MIN_VERSION = "2.0" + def configure(self, node): pass diff --git a/tests/frontend/project/plugins/randomelement.py b/tests/frontend/project/plugins/randomelement.py index e9be98fc7..12afaaa6d 100644 --- a/tests/frontend/project/plugins/randomelement.py +++ b/tests/frontend/project/plugins/randomelement.py @@ -4,6 +4,9 @@ from buildstream import Element class RandomElement(Element): + + BST_MIN_VERSION = "2.0" + def configure(self, node): pass diff --git a/tests/frontend/project/sources/fetch_source.py b/tests/frontend/project/sources/fetch_source.py index c62d1d29a..6078e5e5f 100644 --- a/tests/frontend/project/sources/fetch_source.py +++ b/tests/frontend/project/sources/fetch_source.py @@ -32,6 +32,9 @@ class FetchFetcher(SourceFetcher): class FetchSource(Source): + + BST_MIN_VERSION = "2.0" + # Read config to know which URLs to fetch def configure(self, node): self.original_urls = node.get_str_list("urls") diff --git a/tests/internals/pluginloading/customelement/pluginelements/foo.py b/tests/internals/pluginloading/customelement/pluginelements/foo.py index c6a85a5b1..bdb6c8982 100644 --- a/tests/internals/pluginloading/customelement/pluginelements/foo.py +++ b/tests/internals/pluginloading/customelement/pluginelements/foo.py @@ -2,6 +2,9 @@ from buildstream import Element class FooElement(Element): + + BST_MIN_VERSION = "2.0" + def preflight(self): pass diff --git a/tests/internals/pluginloading/customsource/pluginsources/foo.py b/tests/internals/pluginloading/customsource/pluginsources/foo.py index fce5239b1..c5229f3e2 100644 --- a/tests/internals/pluginloading/customsource/pluginsources/foo.py +++ b/tests/internals/pluginloading/customsource/pluginsources/foo.py @@ -2,6 +2,9 @@ from buildstream import Source class FooSource(Source): + + BST_MIN_VERSION = "2.0" + def preflight(self): pass diff --git a/tests/plugins/deprecationwarnings/plugins/elements/deprecated_plugin.py b/tests/plugins/deprecationwarnings/plugins/elements/deprecated_plugin.py index 1badf59af..244009764 100644 --- a/tests/plugins/deprecationwarnings/plugins/elements/deprecated_plugin.py +++ b/tests/plugins/deprecationwarnings/plugins/elements/deprecated_plugin.py @@ -2,6 +2,7 @@ from buildstream import BuildElement class DeprecatedPlugin(BuildElement): + BST_MIN_VERSION = "2.0" BST_PLUGIN_DEPRECATED = True BST_PLUGIN_DEPRECATION_MESSAGE = "Here is some detail." diff --git a/tests/sourcecache/project/plugins/elements/always_fail.py b/tests/sourcecache/project/plugins/elements/always_fail.py index 43dba5626..bc3ed57b6 100644 --- a/tests/sourcecache/project/plugins/elements/always_fail.py +++ b/tests/sourcecache/project/plugins/elements/always_fail.py @@ -23,6 +23,9 @@ from buildstream.buildelement import BuildElement class AlwaysFail(BuildElement): + + BST_MIN_VERSION = "2.0" + def assemble(self, sandbox): raise ElementError("Always fails") diff --git a/tests/sources/no-fetch-cached/plugins/sources/always_cached.py b/tests/sources/no-fetch-cached/plugins/sources/always_cached.py index aef13279b..b5dfd772c 100644 --- a/tests/sources/no-fetch-cached/plugins/sources/always_cached.py +++ b/tests/sources/no-fetch-cached/plugins/sources/always_cached.py @@ -11,6 +11,8 @@ from buildstream import Source class AlwaysCachedSource(Source): + BST_MIN_VERSION = "2.0" + def configure(self, node): pass diff --git a/tests/sources/previous_source_access/plugins/sources/foo_transform.py b/tests/sources/previous_source_access/plugins/sources/foo_transform.py index dbc44c8aa..15fef43f8 100644 --- a/tests/sources/previous_source_access/plugins/sources/foo_transform.py +++ b/tests/sources/previous_source_access/plugins/sources/foo_transform.py @@ -14,6 +14,7 @@ from buildstream import Source, SourceError, utils class FooTransformSource(Source): + BST_MIN_VERSION = "2.0" # We need access to previous both at track time and fetch time BST_REQUIRES_PREVIOUS_SOURCES_TRACK = True diff --git a/tests/sources/project_key_test/plugins/sources/key-test.py b/tests/sources/project_key_test/plugins/sources/key-test.py index 88a211738..046034804 100644 --- a/tests/sources/project_key_test/plugins/sources/key-test.py +++ b/tests/sources/project_key_test/plugins/sources/key-test.py @@ -7,6 +7,8 @@ class KeyTest(Source): """ This plugin should fail if get_unique_key is called before track """ + BST_MIN_VERSION = "2.0" + def preflight(self): pass -- cgit v1.2.1 From fdaf73f62b8c340f1a13dd20c6c65702ec09716f Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Tue, 28 Apr 2020 19:15:59 +0900 Subject: tests/plugins/loading.py: Added tests for plugin BST_MIN_VERSION guards --- setup.cfg | 2 +- tests/plugins/loading.py | 92 ++++++++++++++++++++++ .../incompatiblemajor/incompatiblemajor.py | 10 +++ .../incompatibleminor/incompatibleminor.py | 10 +++ .../malformedminversion/malformedminversion.py | 11 +++ .../plugins/elements/nominversion/nominversion.py | 10 +++ .../sources/incompatiblemajor/incompatiblemajor.py | 10 +++ .../sources/incompatibleminor/incompatibleminor.py | 10 +++ .../malformedminversion/malformedminversion.py | 11 +++ .../plugins/sources/nominversion/nominversion.py | 10 +++ 10 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 tests/plugins/loading/plugins/elements/incompatiblemajor/incompatiblemajor.py create mode 100644 tests/plugins/loading/plugins/elements/incompatibleminor/incompatibleminor.py create mode 100644 tests/plugins/loading/plugins/elements/malformedminversion/malformedminversion.py create mode 100644 tests/plugins/loading/plugins/elements/nominversion/nominversion.py create mode 100644 tests/plugins/loading/plugins/sources/incompatiblemajor/incompatiblemajor.py create mode 100644 tests/plugins/loading/plugins/sources/incompatibleminor/incompatibleminor.py create mode 100644 tests/plugins/loading/plugins/sources/malformedminversion/malformedminversion.py create mode 100644 tests/plugins/loading/plugins/sources/nominversion/nominversion.py diff --git a/setup.cfg b/setup.cfg index 7d3599e14..0853264f6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,7 +9,7 @@ parentdir_prefix = BuildStream- [tool:pytest] addopts = --verbose --basetemp ./tmp --durations=20 --timeout=1800 -norecursedirs = src tests/integration/project integration-cache tmp __pycache__ .eggs +norecursedirs = src tests/integration/project tests/plugins/loading integration-cache tmp __pycache__ .eggs python_files = tests/*/*.py env = D:BST_TEST_SUITE=True diff --git a/tests/plugins/loading.py b/tests/plugins/loading.py index 1c8a321d5..a368e9952 100644 --- a/tests/plugins/loading.py +++ b/tests/plugins/loading.py @@ -108,3 +108,95 @@ def test_setup_returns_bad_type(cli, datafiles, plugin_type): result = cli.run(project=project, args=["show", "element.bst"]) result.assert_main_error(ErrorDomain.PLUGIN, "setup-returns-bad-type") + + +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("plugin_type", [("elements"), ("sources")]) +def test_missing_min_version(cli, datafiles, plugin_type): + project = str(datafiles) + + update_project( + project, + { + "plugins": [ + { + "origin": "local", + "path": os.path.join("plugins", plugin_type, "nominversion"), + plugin_type: ["nominversion"], + } + ] + }, + ) + setup_element(project, plugin_type, "nominversion") + + result = cli.run(project=project, args=["show", "element.bst"]) + result.assert_main_error(ErrorDomain.PLUGIN, "missing-min-version") + + +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("plugin_type", [("elements"), ("sources")]) +def test_malformed_min_version(cli, datafiles, plugin_type): + project = str(datafiles) + + update_project( + project, + { + "plugins": [ + { + "origin": "local", + "path": os.path.join("plugins", plugin_type, "malformedminversion"), + plugin_type: ["malformedminversion"], + } + ] + }, + ) + setup_element(project, plugin_type, "malformedminversion") + + result = cli.run(project=project, args=["show", "element.bst"]) + result.assert_main_error(ErrorDomain.PLUGIN, "malformed-min-version") + + +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("plugin_type", [("elements"), ("sources")]) +def test_incompatible_major_version(cli, datafiles, plugin_type): + project = str(datafiles) + + update_project( + project, + { + "plugins": [ + { + "origin": "local", + "path": os.path.join("plugins", plugin_type, "incompatiblemajor"), + plugin_type: ["incompatiblemajor"], + } + ] + }, + ) + setup_element(project, plugin_type, "incompatiblemajor") + + result = cli.run(project=project, args=["show", "element.bst"]) + result.assert_main_error(ErrorDomain.PLUGIN, "incompatible-major-version") + + +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("plugin_type", [("elements"), ("sources")]) +def test_incompatible_minor_version(cli, datafiles, plugin_type): + project = str(datafiles) + + update_project( + project, + { + "plugins": [ + { + "origin": "local", + "path": os.path.join("plugins", plugin_type, "incompatibleminor"), + plugin_type: ["incompatibleminor"], + } + ] + }, + ) + setup_element(project, plugin_type, "incompatibleminor") + + result = cli.run(project=project, args=["show", "element.bst"]) + result.assert_main_error(ErrorDomain.PLUGIN, "incompatible-minor-version") diff --git a/tests/plugins/loading/plugins/elements/incompatiblemajor/incompatiblemajor.py b/tests/plugins/loading/plugins/elements/incompatiblemajor/incompatiblemajor.py new file mode 100644 index 000000000..a0ea59309 --- /dev/null +++ b/tests/plugins/loading/plugins/elements/incompatiblemajor/incompatiblemajor.py @@ -0,0 +1,10 @@ +from buildstream import Element + + +class IncompatibleMajor(Element): + + BST_MIN_VERSION = "1.0" + + +def setup(): + return IncompatibleMajor diff --git a/tests/plugins/loading/plugins/elements/incompatibleminor/incompatibleminor.py b/tests/plugins/loading/plugins/elements/incompatibleminor/incompatibleminor.py new file mode 100644 index 000000000..d9967caaa --- /dev/null +++ b/tests/plugins/loading/plugins/elements/incompatibleminor/incompatibleminor.py @@ -0,0 +1,10 @@ +from buildstream import Element + + +class IncompatibleMinor(Element): + + BST_MIN_VERSION = "2.1000" + + +def setup(): + return IncompatibleMinor diff --git a/tests/plugins/loading/plugins/elements/malformedminversion/malformedminversion.py b/tests/plugins/loading/plugins/elements/malformedminversion/malformedminversion.py new file mode 100644 index 000000000..5f96c1897 --- /dev/null +++ b/tests/plugins/loading/plugins/elements/malformedminversion/malformedminversion.py @@ -0,0 +1,11 @@ +# Plugins are required to specify the BST_MIN_VERSION +from buildstream import Element + + +class MalformedMinVersion(Element): + + BST_MIN_VERSION = "1.pony" + + +def setup(): + return MalformedMinVersion diff --git a/tests/plugins/loading/plugins/elements/nominversion/nominversion.py b/tests/plugins/loading/plugins/elements/nominversion/nominversion.py new file mode 100644 index 000000000..e3f5b0cb6 --- /dev/null +++ b/tests/plugins/loading/plugins/elements/nominversion/nominversion.py @@ -0,0 +1,10 @@ +# Plugins are required to specify the BST_MIN_VERSION +from buildstream import Element + + +class NoMinVersion(Element): + pass + + +def setup(): + return NoMinVersion diff --git a/tests/plugins/loading/plugins/sources/incompatiblemajor/incompatiblemajor.py b/tests/plugins/loading/plugins/sources/incompatiblemajor/incompatiblemajor.py new file mode 100644 index 000000000..f2d088b77 --- /dev/null +++ b/tests/plugins/loading/plugins/sources/incompatiblemajor/incompatiblemajor.py @@ -0,0 +1,10 @@ +from buildstream import Source + + +class IncompatibleMajor(Source): + + BST_MIN_VERSION = "1.0" + + +def setup(): + return IncompatibleMajor diff --git a/tests/plugins/loading/plugins/sources/incompatibleminor/incompatibleminor.py b/tests/plugins/loading/plugins/sources/incompatibleminor/incompatibleminor.py new file mode 100644 index 000000000..2282534ae --- /dev/null +++ b/tests/plugins/loading/plugins/sources/incompatibleminor/incompatibleminor.py @@ -0,0 +1,10 @@ +from buildstream import Source + + +class IncompatibleMinor(Source): + + BST_MIN_VERSION = "2.1000" + + +def setup(): + return IncompatibleMinor diff --git a/tests/plugins/loading/plugins/sources/malformedminversion/malformedminversion.py b/tests/plugins/loading/plugins/sources/malformedminversion/malformedminversion.py new file mode 100644 index 000000000..90474597c --- /dev/null +++ b/tests/plugins/loading/plugins/sources/malformedminversion/malformedminversion.py @@ -0,0 +1,11 @@ +# Plugins are required to specify the BST_MIN_VERSION +from buildstream import Source + + +class MalformedMinVersion(Source): + + BST_MIN_VERSION = "1.pony" + + +def setup(): + return MalformedMinVersion diff --git a/tests/plugins/loading/plugins/sources/nominversion/nominversion.py b/tests/plugins/loading/plugins/sources/nominversion/nominversion.py new file mode 100644 index 000000000..12cdfa296 --- /dev/null +++ b/tests/plugins/loading/plugins/sources/nominversion/nominversion.py @@ -0,0 +1,10 @@ +# Plugins are required to specify the BST_MIN_VERSION +from buildstream import Source + + +class NoMinVersion(Source): + pass + + +def setup(): + return NoMinVersion -- cgit v1.2.1 From 1339b0e56c5a685d5da0f617a0b6f205c03d1251 Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Tue, 28 Apr 2020 20:09:08 +0900 Subject: tox.ini: Update reference to updated plugins with BST_MIN_VERSOIN set --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index fd9017db2..94e4048ce 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ isolated_build = true # Configuration variables to share across environments [config] -BST_PLUGINS_EXPERIMENTAL_VERSION = 1.93.1.1 +BST_PLUGINS_EXPERIMENTAL_VERSION = 1.93.2 # # Defaults for all environments -- cgit v1.2.1