summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbst-marge-bot <marge-bot@buildstream.build>2020-05-12 11:01:21 +0000
committerbst-marge-bot <marge-bot@buildstream.build>2020-05-12 11:01:21 +0000
commitf21e747dc9c12259fe157bb2b739f24469417556 (patch)
treee7b683eca74fed8453c5a4001ac5e05c5fb747ea
parent6ef3094f8abaa568b3f5efe8ae2c2aa4d0054ec2 (diff)
parent496a42db9d3b8ee176718d07e9f1859a54644594 (diff)
downloadbuildstream-f21e747dc9c12259fe157bb2b739f24469417556.tar.gz
Merge branch 'tristan/bst-1/bst-2-detection' into 'bst-1'
Detect non BuildStream 1 projects and plugins See merge request BuildStream/buildstream!1923
-rw-r--r--buildstream/_exceptions.py4
-rw-r--r--buildstream/_plugincontext.py26
-rw-r--r--buildstream/_project.py31
-rw-r--r--buildstream/utils.py27
-rw-r--r--setup.cfg2
-rw-r--r--tests/format/project.py8
-rw-r--r--tests/format/project/not-bst-1/project.conf4
-rw-r--r--tests/plugins/bst2.py60
-rw-r--r--tests/plugins/bst2/elements/bst2.py19
-rw-r--r--tests/plugins/bst2/elements/malformed.py19
-rw-r--r--tests/plugins/bst2/sources/bst2.py32
-rw-r--r--tests/plugins/bst2/sources/malformed.py32
12 files changed, 261 insertions, 3 deletions
diff --git a/buildstream/_exceptions.py b/buildstream/_exceptions.py
index d4ab1ea52..8f3f17b9d 100644
--- a/buildstream/_exceptions.py
+++ b/buildstream/_exceptions.py
@@ -137,8 +137,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=False)
+ def __init__(self, message, *, detail=None, reason=None, temporary=False):
+ super().__init__(message, domain=ErrorDomain.PLUGIN, detail=detail, reason=reason, temporary=False)
# LoadErrorReason
diff --git a/buildstream/_plugincontext.py b/buildstream/_plugincontext.py
index 5a7097485..10e32e58a 100644
--- a/buildstream/_plugincontext.py
+++ b/buildstream/_plugincontext.py
@@ -22,6 +22,7 @@ import inspect
from ._exceptions import PluginError, LoadError, LoadErrorReason
from . import utils
+from .utils import UtilError
# A Context for loading plugin types
@@ -223,6 +224,31 @@ class PluginContext():
plugin_type.BST_REQUIRED_VERSION_MAJOR,
plugin_type.BST_REQUIRED_VERSION_MINOR))
+ # If a BST_MIN_VERSION was specified, then we need to raise an error
+ # that we are loading a plugin which targets the wrong BuildStream version.
+ #
+ try:
+ min_version = plugin_type.BST_MIN_VERSION
+ except AttributeError:
+ return
+
+ # Handle malformed version string specified by plugin
+ #
+ try:
+ major, minor = utils._parse_version(min_version)
+ except UtilError as e:
+ raise PluginError(
+ "Loaded plugin '{}' is not a BuildStream 1 plugin".format(kind),
+ detail="Error parsing BST_MIN_VERSION: {}".format(e),
+ reason="plugin-version-mismatch"
+ ) from e
+
+ raise PluginError(
+ "Loaded plugin '{}' is a BuildStream {} plugin".format(kind, major),
+ detail="You need to use BuildStream 1 plugins with BuildStream 1 projects",
+ reason="plugin-version-mismatch"
+ )
+
# _assert_plugin_format()
#
# Helper to raise a PluginError if the loaded plugin is of a lesser version then
diff --git a/buildstream/_project.py b/buildstream/_project.py
index 5530aa23d..0f327c66d 100644
--- a/buildstream/_project.py
+++ b/buildstream/_project.py
@@ -401,6 +401,37 @@ class Project():
"Project requested format version {}, but BuildStream {}.{} only supports up until format version {}"
.format(format_version, major, minor, BST_FORMAT_VERSION))
+ # Since BuildStream 2, project.conf is required to specify min-version.
+ #
+ # Detect this and raise an error, indicating which major version of BuildStream
+ # should be used for this project.
+ #
+ min_version = _yaml.node_get(pre_config_node, str, 'min-version', default_value=None)
+ if min_version:
+
+ # Handle case of malformed min-version
+ #
+ try:
+ major, minor = utils._parse_version(min_version)
+ except UtilError as e:
+ raise LoadError(
+ LoadErrorReason.UNSUPPORTED_PROJECT,
+ "This is not a BuildStream 1 project: {}".format(e)
+ ) from e
+
+ # Raise a helpful error indicating what the user should do to
+ # use this project.
+ #
+ raise LoadError(
+ LoadErrorReason.UNSUPPORTED_PROJECT,
+ "Tried to load a BuildStream {} project with BuildStream 1".format(major),
+
+ # TODO: Include a link to the appropriate documentation for parallel
+ # installing different BuildStream versions.
+ #
+ detail="Please install at least BuildStream {}.{} to use this project".format(major, minor)
+ )
+
# FIXME:
#
# Performing this check manually in the absense
diff --git a/buildstream/utils.py b/buildstream/utils.py
index d02777897..f141cb15d 100644
--- a/buildstream/utils.py
+++ b/buildstream/utils.py
@@ -1207,3 +1207,30 @@ def _deduplicate(iterable, key=None):
if k not in seen:
seen_add(k)
yield element
+
+
+# _parse_version():
+#
+# Args:
+# version (str): The file name from which to determine compression
+#
+# Returns:
+# A 2-tuple of form (major_version: int, minor_version: int)
+#
+# Raises:
+# UtilError: In the case of a malformed version string
+#
+def _parse_version(version):
+
+ try:
+ versions = version.split(".")
+ except AttributeError as e:
+ raise UtilError("Malformed version string: {}".format(version),)
+
+ try:
+ major = int(versions[0])
+ minor = int(versions[1])
+ except (IndexError, ValueError):
+ raise UtilError("Malformed version string: {}".format(version),)
+
+ return major, minor
diff --git a/setup.cfg b/setup.cfg
index 7c2c139e3..7dfa2579f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -12,7 +12,7 @@ test=pytest
[tool:pytest]
addopts = --verbose --basetemp ./tmp --pep8 --pylint --pylint-rcfile=.pylintrc --durations=20
-norecursedirs = tests/integration/project integration-cache tmp __pycache__ .eggs
+norecursedirs = tests/integration/project tests/plugins/bst2 integration-cache tmp __pycache__ .eggs
python_files = tests/*/*.py
pep8maxlinelength = 119
pep8ignore =
diff --git a/tests/format/project.py b/tests/format/project.py
index 83fd38489..e36932eb5 100644
--- a/tests/format/project.py
+++ b/tests/format/project.py
@@ -117,6 +117,14 @@ def test_project_unsupported(cli, datafiles):
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.UNSUPPORTED_PROJECT)
+@pytest.mark.datafiles(os.path.join(DATA_DIR))
+def test_project_unsupported_not_bst1(cli, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename, "not-bst-1")
+
+ result = cli.run(project=project, args=['workspace', 'list'])
+ result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.UNSUPPORTED_PROJECT)
+
+
@pytest.mark.datafiles(os.path.join(DATA_DIR, 'element-path'))
def test_missing_element_path_directory(cli, datafiles):
project = os.path.join(datafiles.dirname, datafiles.basename)
diff --git a/tests/format/project/not-bst-1/project.conf b/tests/format/project/not-bst-1/project.conf
new file mode 100644
index 000000000..02ad4f504
--- /dev/null
+++ b/tests/format/project/not-bst-1/project.conf
@@ -0,0 +1,4 @@
+# A BuildStream 2 project
+name: foo
+
+min-version: 2.0
diff --git a/tests/plugins/bst2.py b/tests/plugins/bst2.py
new file mode 100644
index 000000000..ca7529dfc
--- /dev/null
+++ b/tests/plugins/bst2.py
@@ -0,0 +1,60 @@
+# 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 tests.testutils import cli # pylint: disable=unused-import
+from buildstream import _yaml
+
+
+DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "bst2")
+
+
+# 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_path = os.path.join(project_path, "element.bst")
+
+ if plugin_type == "elements":
+ element = {"kind": plugin_name}
+ else:
+ element = {"kind": "manual", "sources": [{"kind": plugin_name}]}
+
+ _yaml.dump(element, element_path)
+
+
+####################################################
+# Tests #
+####################################################
+@pytest.mark.datafiles(DATA_DIR)
+@pytest.mark.parametrize("plugin_type", ["elements", "sources"])
+@pytest.mark.parametrize("plugin", ["bst2", "malformed"])
+def test_plugin_bst2(cli, datafiles, plugin_type, plugin):
+ project = str(datafiles)
+ project_conf_path = os.path.join(project, "project.conf")
+ project_conf = {
+ "name": "test",
+ "plugins": [
+ {
+ "origin": "local",
+ "path": plugin_type,
+ plugin_type: {
+ plugin: 0
+ }
+ }
+ ]
+ }
+ _yaml.dump(project_conf, project_conf_path)
+
+ setup_element(project, plugin_type, plugin)
+
+ result = cli.run(project=project, args=["show", "element.bst"])
+ result.assert_main_error(ErrorDomain.PLUGIN, "plugin-version-mismatch")
diff --git a/tests/plugins/bst2/elements/bst2.py b/tests/plugins/bst2/elements/bst2.py
new file mode 100644
index 000000000..34a7e4398
--- /dev/null
+++ b/tests/plugins/bst2/elements/bst2.py
@@ -0,0 +1,19 @@
+from buildstream import Element
+
+
+class Found(Element):
+ BST_MIN_VERSION = "2.0"
+
+ def configure(self, node):
+ pass
+
+ def preflight(self):
+ pass
+
+ def get_unique_key(self):
+ return {}
+
+
+# Plugin entry point
+def setup():
+ return Found
diff --git a/tests/plugins/bst2/elements/malformed.py b/tests/plugins/bst2/elements/malformed.py
new file mode 100644
index 000000000..d3fe97a6a
--- /dev/null
+++ b/tests/plugins/bst2/elements/malformed.py
@@ -0,0 +1,19 @@
+from buildstream import Element
+
+
+class Found(Element):
+ BST_MIN_VERSION = 5
+
+ def configure(self, node):
+ pass
+
+ def preflight(self):
+ pass
+
+ def get_unique_key(self):
+ return {}
+
+
+# Plugin entry point
+def setup():
+ return Found
diff --git a/tests/plugins/bst2/sources/bst2.py b/tests/plugins/bst2/sources/bst2.py
new file mode 100644
index 000000000..4ab40f005
--- /dev/null
+++ b/tests/plugins/bst2/sources/bst2.py
@@ -0,0 +1,32 @@
+from buildstream import Source
+
+
+class Found(Source):
+ BST_MIN_VERSION = "2.0"
+
+ def configure(self, node):
+ pass
+
+ def preflight(self):
+ pass
+
+ def get_unique_key(self):
+ return {}
+
+ def load_ref(self, node):
+ pass
+
+ def get_ref(self):
+ return {}
+
+ def set_ref(self, ref, node):
+ pass
+
+ def is_cached(self):
+ return False
+
+
+# Plugin entry point
+def setup():
+
+ return Found
diff --git a/tests/plugins/bst2/sources/malformed.py b/tests/plugins/bst2/sources/malformed.py
new file mode 100644
index 000000000..786e3d619
--- /dev/null
+++ b/tests/plugins/bst2/sources/malformed.py
@@ -0,0 +1,32 @@
+from buildstream import Source
+
+
+class Found(Source):
+ BST_MIN_VERSION = "a pony"
+
+ def configure(self, node):
+ pass
+
+ def preflight(self):
+ pass
+
+ def get_unique_key(self):
+ return {}
+
+ def load_ref(self, node):
+ pass
+
+ def get_ref(self):
+ return {}
+
+ def set_ref(self, ref, node):
+ pass
+
+ def is_cached(self):
+ return False
+
+
+# Plugin entry point
+def setup():
+
+ return Found