From 2a618686f17b6a652bfda1348014fe31ff8e2f88 Mon Sep 17 00:00:00 2001 From: Jan Kiszka Date: Mon, 17 Jul 2017 22:38:05 +0200 Subject: [PATCH] Switch to separate config file version Use a separately incremented integer to track the configuration file format version. We start with 2 due to the change that 23c3a951f6e4 introduced. 1 is declared to be equivalent to the original '0.10'. The separate versioning has the advantage of being able to increment it already during the development cycle, and using it with config files that test/exploit the new format. Using an integer has the advantages of a) differentiating it clearly from the now independent kas version and b) simplifying the version parsing. We can now also remove the string type restriction. Signed-off-by: Jan Kiszka --- docs/format-changelog.rst | 25 ++++++++++++++++++ docs/index.rst | 1 + docs/userguide.rst | 20 ++++++++------- kas/__init__.py | 2 +- kas/__version__.py | 3 ++- kas/includehandler.py | 49 +++++++++++++----------------------- tests/test_includehandler.py | 43 +++++++++++-------------------- 7 files changed, 72 insertions(+), 71 deletions(-) create mode 100644 docs/format-changelog.rst diff --git a/docs/format-changelog.rst b/docs/format-changelog.rst new file mode 100644 index 0000000..7f85700 --- /dev/null +++ b/docs/format-changelog.rst @@ -0,0 +1,25 @@ +Configuration Format Changes +============================ + +Version 1 (Alias '0.10') +------------------------ + +Added +~~~~~ + +- Include mechanism +- Version check + + +Version 2 +--------- + +Changed +~~~~~~~ + +- Configuration file versions are now integers + +Fixed +~~~~~ + +- Including files from repos that are not defined in the current file diff --git a/docs/index.rst b/docs/index.rst index 84586fc..c7e1615 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,6 +9,7 @@ Contents: intro userguide devguide + format-changelog Indices and tables ================== diff --git a/docs/userguide.rst b/docs/userguide.rst index 4b7d488..cdde691 100644 --- a/docs/userguide.rst +++ b/docs/userguide.rst @@ -135,11 +135,11 @@ arguable easier to read, this documentation focuses on the YAML format. # Every file needs to contain a header, that provides kas with information # about the context of this file. header: - # The `version` entry in the header describes for which kas version this - # file was created. It is used by kas to figure out if it is compatible - # with this file. Every version x.y.z should be compatible with - # the configuration file version x.y. (x, y and z are numbers) - version: "x.y" + # The `version` entry in the header describes for which configuration + # format version this file was created for. It is used by kas to figure + # out if it is compatible with this file. The version is an integer that + # is increased on every format change. + version: x # The machine as it is written into the `local.conf` of bitbake. machine: qemu # The distro name as it is written into the `local.conf` of bitbake. @@ -193,7 +193,7 @@ repository/layer like this: .. code-block:: yaml header: - version: "x.y" + version: x includes: - base.yml - bsp.yml @@ -209,7 +209,7 @@ Its also possible to include configuration files from other repos like this: .. code-block:: yaml header: - version: "x.y" + version: x includes: - repo: poky file: kas-poky.yml @@ -256,8 +256,10 @@ Static configuration reference The header of every kas configuration file. It contains information about context of the file. - * ``version``: string [required] - Lets kas check if it is compatible with this file. + * ``version``: integer [required] + Lets kas check if it is compatible with this file. See the + :doc:`configuration format changelog ` for the + format history and latest available version. * ``includes``: list [optional] A list of configuration files this current file is based on. They are diff --git a/kas/__init__.py b/kas/__init__.py index 645b095..27714fd 100644 --- a/kas/__init__.py +++ b/kas/__init__.py @@ -24,7 +24,7 @@ """ from .__version__ import __version__ -from .__version__ import __compatible_version__ +from .__version__ import __file_version__, __compatible_file_version__ __license__ = 'MIT' __copyright__ = 'Copyright (c) Siemens AG, 2017' diff --git a/kas/__version__.py b/kas/__version__.py index dcdb26b..c710bc1 100644 --- a/kas/__version__.py +++ b/kas/__version__.py @@ -26,4 +26,5 @@ __license__ = 'MIT' __copyright__ = 'Copyright (c) Siemens AG, 2017' __version__ = '0.10.0' -__compatible_version__ = '0.10' +__file_version__ = 2 +__compatible_file_version__ = 1 diff --git a/kas/includehandler.py b/kas/includehandler.py index a44587b..2f9a62f 100644 --- a/kas/includehandler.py +++ b/kas/includehandler.py @@ -29,14 +29,8 @@ import os import collections import functools import logging -try: - # pylint: disable=no-name-in-module - from distutils.version import StrictVersion -except ImportError as exc: - logging.error("Could not import StrictVersion") - raise exc -from . import __version__, __compatible_version__ +from . import __file_version__, __compatible_file_version__ __license__ = 'MIT' __copyright__ = 'Copyright (c) Siemens AG, 2017' @@ -78,38 +72,29 @@ def load_config(filename): raise LoadConfigException('Header missing or empty', filename) try: - file_version_string = header.get('version', None) + version = header.get('version', None) except AttributeError: raise LoadConfigException('Header is not a dictionary', filename) - if not file_version_string: + if not version: raise LoadConfigException('Version missing or empty', filename) try: - if not isinstance(file_version_string, str): - raise LoadConfigException('Version has to be a string', - filename) - - file_version = StrictVersion() - file_version.parse(file_version_string) - kas_version = StrictVersion() - kas_version.parse(__version__) - lower_version = StrictVersion() - lower_version.parse(__compatible_version__) - - # Remove patch version, because we provide limited forwards - # compatibility: - if file_version.version[2] > 0: - file_version.prerelease = None - file_version.version = tuple(list(file_version.version[:2]) + [0]) - - if file_version < lower_version or kas_version < file_version: - raise LoadConfigException('This version of kas is compatible with ' - 'version {} to {}, file has version {}' - .format(lower_version, kas_version, - file_version), filename) + version_value = int(version) except ValueError: - raise LoadConfigException('Not expected version format', filename) + # Be compatible: version string '0.10' is equivalent to file version 1 + if isinstance(version, str) and version == '0.10': + version_value = 1 + else: + raise LoadConfigException('Unexpected version format', filename) + + if version_value < __compatible_file_version__ or \ + version_value > __file_version__: + raise LoadConfigException('This version of kas is compatible with ' + 'version {} to {}, file has version {}' + .format(__compatible_file_version__, + __file_version__, version_value), + filename) return config diff --git a/tests/test_includehandler.py b/tests/test_includehandler.py index ce80602..ea19d24 100644 --- a/tests/test_includehandler.py +++ b/tests/test_includehandler.py @@ -34,8 +34,8 @@ from kas import includehandler @pytest.fixture(autouse=True) def fixed_version(monkeypatch): - monkeypatch.setattr(includehandler, '__version__', '0.5.0') - monkeypatch.setattr(includehandler, '__compatible_version__', '0.5') + monkeypatch.setattr(includehandler, '__file_version__', 5) + monkeypatch.setattr(includehandler, '__compatible_file_version__', 4) class MockFileIO(io.StringIO): @@ -103,50 +103,37 @@ class TestLoadConfig(object): self.util_exception_content(testvector) - def test_err_version_invalid_type(self): - exception = includehandler.LoadConfigException - testvector = [ - ('header: {version: 1}', exception), - ('header: {version: a}', exception), - ('header: {version: 0.4}', exception), - ('header: {version: 0.5}', exception), - ('header: {version: 0.6}', exception), - ] - - self.util_exception_content(testvector) - def test_err_version_invalid_format(self): exception = includehandler.LoadConfigException testvector = [ - ('header: {version: "0.4"}', exception), - ('header: {version: "0.4.5"}', exception), - ('header: {version: "0.5a"}', exception), - ('header: {version: "0.5.a"}', exception), - ('header: {version: "0.5.4.3"}', exception), - ('header: {version: "0.50"}', exception), - ('header: {version: "0.6a"}', exception), - ('header: {version: "0.6"}', exception), + ('header: {version: "0.5"}', exception), + ('header: {version: "x"}', exception), + ('header: {version: 3}', exception), + ('header: {version: 6}', exception), ] self.util_exception_content(testvector) def test_header_valid(self): testvector = [ - 'header: {version: "0.5"}', - 'header: {version: "0.5.0"}', - 'header: {version: "0.5.1"}', - 'header: {version: "0.5.10"}', - 'header: {version: "0.5"}', + 'header: {version: "4"}', + 'header: {version: 4}', + 'header: {version: 5}', ] for string in testvector: with patch_open(includehandler, string=string): includehandler.load_config('x.yml') + def test_compat_version(self, monkeypatch): + monkeypatch.setattr(includehandler, '__compatible_file_version__', 1) + with patch_open(includehandler, string='header: {version: "0.10"}'): + includehandler.load_config('x.yml') + class TestGlobalIncludes(object): header = ''' header: - version: "0.5" + version: 5 {}''' def util_include_content(self, testvector):