diff options
| author | Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> | 2020-04-20 20:30:17 +0900 |
|---|---|---|
| committer | Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> | 2020-04-25 00:16:01 +0900 |
| commit | 6ef6be52c1cd66245fc7f13a3c472024e10c3c1a (patch) | |
| tree | 19e1fa50917931aac784bca94d765f132c75805d /src | |
| parent | bacc060cfcf32cdb696ce1a3ef195ea9bed0d8c1 (diff) | |
| download | buildstream-6ef6be52c1cd66245fc7f13a3c472024e10c3c1a.tar.gz | |
Replace format-version with min-version
* "min-version" is specified as a <major>.<minor> point version
and uses the installed BuildStream version instead of having
a separate versioning number for the format.
* The presence of "format-version" is now used to indicate
that we might be loading a BuildStream 1 project.
* For now, where parsing the version at startup is concerned, and
also where `bst init` is concerned, we artificially bump the
detected BuildStream version to 2.0 if we detect a version < 2.0,
these exceptions can be removed once 2.0 is tagged and released.
Summary of changes:
_project.py: Now parse "min-version" and detect "format-version" to
warn about loading a BuildStream 1 project
_versions.py: Remove obsolete BST_FORMAT_VERSION numbers from here
data/projectconfig.yaml: Remove old "format-version" from defaults
utils.py: Added new private _parse_version() helper function, and another
_get_bst_api_version() to get an adjusted API version.
frontend/app.py, frontend/cli.py: Updated `bst init` implementation
testing (buildstream.testing): Updated testing utilities to generate
and use projects with min-version instead of format-version.
tests and examples: Updated to use min-version across the board.
Diffstat (limited to 'src')
| -rw-r--r-- | src/buildstream/_frontend/app.py | 93 | ||||
| -rw-r--r-- | src/buildstream/_frontend/cli.py | 25 | ||||
| -rw-r--r-- | src/buildstream/_project.py | 91 | ||||
| -rw-r--r-- | src/buildstream/_versions.py | 15 | ||||
| -rw-r--r-- | src/buildstream/data/projectconfig.yaml | 3 | ||||
| -rw-r--r-- | src/buildstream/exceptions.py | 2 | ||||
| -rw-r--r-- | src/buildstream/testing/_sourcetests/mirror.py | 4 | ||||
| -rw-r--r-- | src/buildstream/testing/_sourcetests/project/files/sub-project/project.conf | 2 | ||||
| -rw-r--r-- | src/buildstream/testing/_sourcetests/project/project.conf | 1 | ||||
| -rw-r--r-- | src/buildstream/testing/_sourcetests/track_cross_junction.py | 2 | ||||
| -rw-r--r-- | src/buildstream/testing/_yaml.py | 2 | ||||
| -rw-r--r-- | src/buildstream/utils.py | 57 |
12 files changed, 191 insertions, 106 deletions
diff --git a/src/buildstream/_frontend/app.py b/src/buildstream/_frontend/app.py index baf75c05d..8d4dd34b1 100644 --- a/src/buildstream/_frontend/app.py +++ b/src/buildstream/_frontend/app.py @@ -36,9 +36,10 @@ from .._exceptions import BstError, StreamError, LoadError, AppError from ..exceptions import LoadErrorReason from .._message import Message, MessageType, unconditional_messages from .._stream import Stream -from .._versions import BST_FORMAT_VERSION from ..types import _SchedulerErrorAction from .. import node +from .. import utils +from ..utils import UtilError # Import frontend assets from .profile import Profile @@ -357,18 +358,13 @@ class App: # # Args: # project_name (str): The project name, must be a valid symbol name - # format_version (int): The project format version, default is the latest version + # min_version (str): The minimum required version of BuildStream (default is current version) # element_path (str): The subdirectory to store elements in, default is 'elements' # force (bool): Allow overwriting an existing project.conf # target_directory (str): The target directory the project should be initialized in # def init_project( - self, - project_name, - format_version=BST_FORMAT_VERSION, - element_path="elements", - force=False, - target_directory=None, + self, project_name, min_version=None, element_path="elements", force=False, target_directory=None, ): if target_directory: directory = os.path.abspath(target_directory) @@ -378,6 +374,10 @@ class App: project_path = os.path.join(directory, "project.conf") + if min_version is None: + bst_major, bst_minor = utils.get_bst_version() + min_version = "{}.{}".format(bst_major, bst_minor) + try: if self._set_project_dir: raise AppError( @@ -394,7 +394,7 @@ class App: # If project name was specified, user interaction is not desired, just # perform some validation and write the project.conf node._assert_symbol_name(project_name, "project name") - self._assert_format_version(format_version) + self._assert_min_version(min_version) self._assert_element_path(element_path) elif not self.interactive: @@ -404,8 +404,8 @@ class App: ) else: # Collect the parameters using an interactive session - project_name, format_version, element_path = self._init_project_interactive( - project_name, format_version, element_path + project_name, min_version, element_path = self._init_project_interactive( + project_name, min_version, element_path ) # Create the directory if it doesnt exist @@ -429,8 +429,8 @@ class App: f.write( "# Unique project name\n" + "name: {}\n\n".format(project_name) - + "# Required BuildStream format version\n" - + "format-version: {}\n\n".format(format_version) + + "# Required BuildStream version\n" + + "min-version: {}\n\n".format(min_version) + "# Subdirectory where elements are stored\n" + "element-path: {}\n".format(element_path) ) @@ -827,20 +827,19 @@ class App: # Some validation routines for project initialization # - def _assert_format_version(self, format_version): - message = "The version must be supported by this " + "version of buildstream (0 - {})\n".format( - BST_FORMAT_VERSION - ) + def _assert_min_version(self, min_version): + bst_major, bst_minor = utils._get_bst_api_version() + message = "The minimum version must be a known version of BuildStream {}".format(bst_major) - # Validate that it is an integer + # Validate the version format try: - number = int(format_version) - except ValueError as e: - raise AppError(message, reason="invalid-format-version") from e + min_version_major, min_version_minor = utils._parse_version(min_version) + except UtilError as e: + raise AppError(str(e), reason="invalid-min-version") from e - # Validate that the specified version is supported - if number < 0 or number > BST_FORMAT_VERSION: - raise AppError(message, reason="invalid-format-version") + # Validate that this version can be loaded by the installed version of BuildStream + if min_version_major != bst_major or min_version_minor > bst_minor: + raise AppError(message, reason="invalid-min-version") def _assert_element_path(self, element_path): message = "The element path cannot be an absolute path or contain any '..' components\n" @@ -864,15 +863,21 @@ class App: # # Args: # project_name (str): The project name, must be a valid symbol name - # format_version (int): The project format version, default is the latest version + # min_version (str): The minimum BuildStream version, default is the latest version # element_path (str): The subdirectory to store elements in, default is 'elements' # # Returns: # project_name (str): The user selected project name - # format_version (int): The user selected format version + # min_version (int): The user selected minimum BuildStream version # element_path (str): The user selected element path # - def _init_project_interactive(self, project_name, format_version=BST_FORMAT_VERSION, element_path="elements"): + def _init_project_interactive(self, project_name, min_version=None, element_path="elements"): + + bst_major, bst_minor = utils._get_bst_api_version() + + if min_version is None: + min_version = "{}.{}".format(bst_major, bst_minor) + def project_name_proc(user_input): try: node._assert_symbol_name(user_input, "project name") @@ -881,9 +886,9 @@ class App: raise UsageError(message) from e return user_input - def format_version_proc(user_input): + def min_version_proc(user_input): try: - self._assert_format_version(user_input) + self._assert_min_version(user_input) except AppError as e: raise UsageError(str(e)) from e return user_input @@ -927,17 +932,21 @@ class App: project_name = click.prompt(self._content_profile.fmt("Project name"), value_proc=project_name_proc, err=True) click.echo("", err=True) - # Collect format version - click.echo(self._content_profile.fmt("Select the minimum required format version for your project"), err=True) - click.echo(self._format_profile.fmt("-----------------------------------------------------------"), err=True) + # Collect minimum BuildStream version + click.echo( + self._content_profile.fmt("Select the minimum required BuildStream version for your project"), err=True + ) + click.echo( + self._format_profile.fmt("----------------------------------------------------------------"), err=True + ) click.echo("", err=True) click.echo( self._detail_profile.fmt( w.fill( - "The format version is used to provide users who build your project " + "The minimum version is used to provide users who build your project " "with a helpful error message in the case that they do not have a recent " - "enough version of BuildStream supporting all the features which your " - "project might use." + "enough version of BuildStream to support all the features which your " + "project uses." ) ), err=True, @@ -946,19 +955,17 @@ class App: click.echo( self._detail_profile.fmt( w.fill( - "The lowest version allowed is 0, the currently installed version of BuildStream " - "supports up to format version {}.".format(BST_FORMAT_VERSION) + "The lowest version allowed is {major}.0, the currently installed version of BuildStream is {major}.{minor}".format( + major=bst_major, minor=bst_minor + ) ) ), err=True, ) click.echo("", err=True) - format_version = click.prompt( - self._content_profile.fmt("Format version"), - value_proc=format_version_proc, - default=format_version, - err=True, + min_version = click.prompt( + self._content_profile.fmt("Minimum version"), value_proc=min_version_proc, default=min_version, err=True, ) click.echo("", err=True) @@ -991,7 +998,7 @@ class App: self._content_profile.fmt("Element path"), value_proc=element_path_proc, default=element_path, err=True ) - return (project_name, format_version, element_path) + return (project_name, min_version, element_path) # diff --git a/src/buildstream/_frontend/cli.py b/src/buildstream/_frontend/cli.py index 44bb99f92..522f15115 100644 --- a/src/buildstream/_frontend/cli.py +++ b/src/buildstream/_frontend/cli.py @@ -7,10 +7,9 @@ import shutil import click from .. import _yaml from .._exceptions import BstError, LoadError, AppError -from .._versions import BST_FORMAT_VERSION from .complete import main_bashcomplete, complete_path, CompleteUnhandled from ..types import _CacheBuildTrees, _SchedulerErrorAction, _PipelineSelection -from ..utils import _get_compression, UtilError +from ..utils import UtilError ################################################################## @@ -414,12 +413,20 @@ def help_command(ctx, command): ################################################################## # Init Command # ################################################################## +def default_min_version(): + from .. import utils + + bst_major, bst_minor = utils._get_bst_api_version() + + return "{}.{}".format(bst_major, bst_minor) + + @cli.command(short_help="Initialize a new BuildStream project") @click.option("--project-name", type=click.STRING, help="The project name to use") @click.option( - "--format-version", - type=click.INT, - default=BST_FORMAT_VERSION, + "--min-version", + type=click.STRING, + default=default_min_version(), show_default=True, help="The required format version", ) @@ -433,7 +440,7 @@ def help_command(ctx, command): @click.option("--force", "-f", is_flag=True, help="Allow overwriting an existing project.conf") @click.argument("target-directory", nargs=1, required=False, type=click.Path(file_okay=False, writable=True)) @click.pass_obj -def init(app, project_name, format_version, element_path, force, target_directory): +def init(app, project_name, min_version, element_path, force, target_directory): """Initialize a new BuildStream project Creates a new BuildStream project.conf in the project @@ -442,7 +449,7 @@ def init(app, project_name, format_version, element_path, force, target_director Unless `--project-name` is specified, this will be an interactive session. """ - app.init_project(project_name, format_version, element_path, force, target_directory) + app.init_project(project_name, min_version, element_path, force, target_directory) ################################################################## @@ -1226,6 +1233,8 @@ def artifact_checkout(app, force, deps, integrate, hardlinks, tar, compression, When this command is executed from a workspace directory, the default is to checkout the artifact of the workspace element. """ + from .. import utils + if hardlinks and tar: click.echo("ERROR: options --hardlinks and --tar conflict", err=True) sys.exit(-1) @@ -1249,7 +1258,7 @@ def artifact_checkout(app, force, deps, integrate, hardlinks, tar, compression, else: location = tar try: - inferred_compression = _get_compression(tar) + inferred_compression = utils._get_compression(tar) except UtilError as e: click.echo("ERROR: Invalid file extension given with '--tar': {}".format(e), err=True) sys.exit(-1) diff --git a/src/buildstream/_project.py b/src/buildstream/_project.py index 9e5d92b7c..3527d211f 100644 --- a/src/buildstream/_project.py +++ b/src/buildstream/_project.py @@ -26,6 +26,7 @@ from pluginbase import PluginBase from . import utils from . import _site from . import _yaml +from .utils import UtilError from ._artifactelement import ArtifactElement from ._profile import Topics, PROFILER from ._exceptions import LoadError @@ -39,8 +40,6 @@ from ._elementfactory import ElementFactory from ._sourcefactory import SourceFactory from .types import CoreWarnings from ._projectrefs import ProjectRefs, ProjectRefStorage -from ._versions import BST_FORMAT_VERSION -from ._versions import BST_FORMAT_VERSION_MIN from ._loader import Loader from .element import Element from .types import FastEnum @@ -336,7 +335,7 @@ class Project: def _validate_node(self, node): node.validate_keys( [ - "format-version", + "min-version", "element-path", "variables", "environment", @@ -579,6 +578,72 @@ class Project: return tuple(default_targets) + # _validate_version() + # + # Asserts that we have a BuildStream installation which is recent + # enough for the project required version + # + # Args: + # config_node (dict) - YaML node of the configuration file. + # + # Raises: LoadError if there was a problem with the project.conf + # + def _validate_version(self, config_node): + + bst_major, bst_minor = utils._get_bst_api_version() + + # Use a custom error message for the absence of the required "min-version" + # as this may be an indication that we are trying to load a BuildStream 1 project. + # + min_version_node = config_node.get_scalar("min-version", None) + if min_version_node.is_none(): + p = config_node.get_provenance() + raise LoadError( + "{}: Dictionary did not contain expected key 'min-version'".format(p), + LoadErrorReason.INVALID_DATA, + # + # TODO: Provide a link to documentation on how to install + # BuildStream 1 in a venv + # + detail="If you are trying to use a BuildStream 1 project, " + + "please install BuildStream 1 to use this project.", + ) + + # Parse the project declared minimum required BuildStream version + min_version = min_version_node.as_str() + try: + min_version_major, min_version_minor = utils._parse_version(min_version) + except UtilError as e: + p = min_version_node.get_provenance() + raise LoadError( + "{}: {}\n".format(p, e), + LoadErrorReason.INVALID_DATA, + detail="The min-version must be specified as MAJOR.MINOR with " + + "numeric major and minor minimum required version numbers", + ) from e + + # Future proofing, in case there is ever a BuildStream 3 + if min_version_major != bst_major: + p = min_version_node.get_provenance() + raise LoadError( + "{}: Version mismatch".format(p), + LoadErrorReason.UNSUPPORTED_PROJECT, + detail="Project requires BuildStream {}, ".format(min_version_major) + + "but BuildStream {} is installed.\n".format(bst_major) + + "Please use BuildStream {} with this project.".format(min_version_major), + ) + + # Check minimal minor point requirement is satisfied + if min_version_minor > bst_minor: + p = min_version_node.get_provenance() + raise LoadError( + "{}: Version mismatch".format(p), + LoadErrorReason.UNSUPPORTED_PROJECT, + detail="Project requires at least BuildStream {}.{}, ".format(min_version_major, min_version_minor) + + "but BuildStream {}.{} is installed.\n".format(bst_major, bst_minor) + + "Please upgrade BuildStream.", + ) + # _load(): # # Loads the project configuration file in the project @@ -606,24 +671,8 @@ class Project: pre_config_node = self._default_config_node.clone() self._project_conf._composite(pre_config_node) - # Assert project's format version early, before validating toplevel keys - format_version = pre_config_node.get_int("format-version") - if format_version < BST_FORMAT_VERSION_MIN: - major, minor = utils.get_bst_version() - raise LoadError( - "Project requested format version {}, but BuildStream {}.{} only supports format version {} or above." - "Use latest 1.x release".format(format_version, major, minor, BST_FORMAT_VERSION_MIN), - LoadErrorReason.UNSUPPORTED_PROJECT, - ) - - if BST_FORMAT_VERSION < format_version: - major, minor = utils.get_bst_version() - raise LoadError( - "Project requested format version {}, but BuildStream {}.{} only supports up until format version {}".format( - format_version, major, minor, BST_FORMAT_VERSION - ), - LoadErrorReason.UNSUPPORTED_PROJECT, - ) + # Assert project's minimum required version early, before validating toplevel keys + self._validate_version(pre_config_node) self._validate_node(pre_config_node) diff --git a/src/buildstream/_versions.py b/src/buildstream/_versions.py index ad749865d..f97560b4d 100644 --- a/src/buildstream/_versions.py +++ b/src/buildstream/_versions.py @@ -17,21 +17,6 @@ # Authors: # Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> - -# The base BuildStream format version -# -# This version is bumped whenever enhancements are made -# to the `project.conf` format or the core element format. -# -BST_FORMAT_VERSION = 25 - -# The mimimum BuildStream format version supported -# -# This version is the minimum format version supported by the -# current buildstream version -# -BST_FORMAT_VERSION_MIN = 18 - # The base BuildStream artifact version # # The artifact version changes whenever the cache key diff --git a/src/buildstream/data/projectconfig.yaml b/src/buildstream/data/projectconfig.yaml index a2dc4ad9b..a2753c312 100644 --- a/src/buildstream/data/projectconfig.yaml +++ b/src/buildstream/data/projectconfig.yaml @@ -4,9 +4,6 @@ # General configuration defaults # -# Require format version 18 -format-version: 18 - # Elements are found at the project root element-path: . diff --git a/src/buildstream/exceptions.py b/src/buildstream/exceptions.py index 123e18d72..cc2bd6381 100644 --- a/src/buildstream/exceptions.py +++ b/src/buildstream/exceptions.py @@ -86,7 +86,7 @@ class LoadErrorReason(Enum): """ UNSUPPORTED_PROJECT = 7 - """BuildStream does not support the required project format version""" + """The project requires an incompatible BuildStream version""" UNSUPPORTED_PLUGIN = 8 """Project requires a newer version of a plugin than the one which was diff --git a/src/buildstream/testing/_sourcetests/mirror.py b/src/buildstream/testing/_sourcetests/mirror.py index de05b894c..69042747c 100644 --- a/src/buildstream/testing/_sourcetests/mirror.py +++ b/src/buildstream/testing/_sourcetests/mirror.py @@ -156,7 +156,7 @@ def test_mirror_from_includes(cli, tmpdir, datafiles, kind): config_project_dir = str(tmpdir.join("config")) os.makedirs(config_project_dir, exist_ok=True) - config_project = {"name": "config"} + config_project = {"name": "config", "min-version": "2.0"} _yaml.roundtrip_dump(config_project, os.path.join(config_project_dir, "project.conf")) extra_mirrors = {"mirrors": [{"name": "middle-earth", "aliases": {alias: [mirror_map + "/"],}}]} _yaml.roundtrip_dump(extra_mirrors, os.path.join(config_project_dir, "mirrors.yml")) @@ -199,7 +199,7 @@ def test_mirror_junction_from_includes(cli, tmpdir, datafiles, kind): config_project_dir = str(tmpdir.join("config")) os.makedirs(config_project_dir, exist_ok=True) - config_project = {"name": "config"} + config_project = {"name": "config", "min-version": "2.0"} _yaml.roundtrip_dump(config_project, os.path.join(config_project_dir, "project.conf")) extra_mirrors = {"mirrors": [{"name": "middle-earth", "aliases": {alias: [mirror_map + "/"],}}]} _yaml.roundtrip_dump(extra_mirrors, os.path.join(config_project_dir, "mirrors.yml")) diff --git a/src/buildstream/testing/_sourcetests/project/files/sub-project/project.conf b/src/buildstream/testing/_sourcetests/project/files/sub-project/project.conf index bbb8414a3..74cfd2583 100644 --- a/src/buildstream/testing/_sourcetests/project/files/sub-project/project.conf +++ b/src/buildstream/testing/_sourcetests/project/files/sub-project/project.conf @@ -1,4 +1,4 @@ # Project config for frontend build test name: subtest - +min-version: 2.0 element-path: elements diff --git a/src/buildstream/testing/_sourcetests/project/project.conf b/src/buildstream/testing/_sourcetests/project/project.conf index 05b68bfeb..22fe9477f 100644 --- a/src/buildstream/testing/_sourcetests/project/project.conf +++ b/src/buildstream/testing/_sourcetests/project/project.conf @@ -1,5 +1,6 @@ # Project config for frontend build test name: test +min-version: 2.0 element-path: elements aliases: alpine: https://bst-integration-test-images.ams3.cdn.digitaloceanspaces.com/ diff --git a/src/buildstream/testing/_sourcetests/track_cross_junction.py b/src/buildstream/testing/_sourcetests/track_cross_junction.py index 2477b37ee..2c4141936 100644 --- a/src/buildstream/testing/_sourcetests/track_cross_junction.py +++ b/src/buildstream/testing/_sourcetests/track_cross_junction.py @@ -68,7 +68,7 @@ def generate_project(tmpdir, name, kind, config=None): subproject_path = os.path.join(str(tmpdir.join(project_name))) os.makedirs(os.path.join(subproject_path, "elements")) - project_conf = {"name": name, "element-path": "elements"} + project_conf = {"name": name, "min-version": "2.0", "element-path": "elements"} project_conf.update(config) _yaml.roundtrip_dump(project_conf, os.path.join(subproject_path, "project.conf")) add_plugins_conf(subproject_path, kind) diff --git a/src/buildstream/testing/_yaml.py b/src/buildstream/testing/_yaml.py index ccf65a1ae..0a16f3226 100644 --- a/src/buildstream/testing/_yaml.py +++ b/src/buildstream/testing/_yaml.py @@ -9,6 +9,8 @@ def generate_project(project_dir, config=None): project_file = os.path.join(project_dir, "project.conf") if "name" not in config: config["name"] = os.path.basename(project_dir) + if "min-version" not in config: + config["min-version"] = "2.0" roundtrip_dump(config, project_file) diff --git a/src/buildstream/utils.py b/src/buildstream/utils.py index 997c073b9..9f7690f7f 100644 --- a/src/buildstream/utils.py +++ b/src/buildstream/utils.py @@ -580,17 +580,9 @@ def get_bst_version() -> Tuple[int, int]: ) try: - return (int(versions[0]), int(versions[1])) - except IndexError: - raise UtilError( - "Cannot detect Major and Minor parts of the version\n" - "Version: {} not in XX.YY.whatever format".format(__version__) - ) - except ValueError: - raise UtilError( - "Cannot convert version to integer numbers\n" - "Version: {} not in Integer.Integer.whatever format".format(__version__) - ) + return _parse_version(__version__) + except UtilError as e: + raise UtilError("Failed to detect BuildStream version: {}\n".format(e)) from e def move_atomic(source: Union[Path, str], destination: Union[Path, str], *, ensure_parents: bool = True) -> None: @@ -1591,3 +1583,46 @@ def _is_single_threaded(): return True time.sleep(wait) return False + + +# _parse_version(): +# +# Args: +# version (str): The file name from which to determine compression +# +# Returns: +# A 2-tuple of form (major version, minor version) +# +# Raises: +# UtilError: In the case of a malformed version string +# +def _parse_version(version: str) -> Tuple[int, int]: + + versions = version.split(".") + try: + major = int(versions[0]) + minor = int(versions[1]) + except (IndexError, ValueError): + raise UtilError("Malformed version string: {}".format(version),) + + return (major, minor) + + +# _get_bst_api_version(): +# +# Fetch the current BuildStream API version, this +# ensures that we get "2.0" for example when we are +# in a development stage leading up to 2.0. +# +# Returns: +# A 2-tuple of form (major version, minor version) +# +def _get_bst_api_version() -> Tuple[int, int]: + + bst_major, bst_minor = get_bst_version() + + if bst_major < 2: + bst_major = 2 + bst_minor = 0 + + return (bst_major, bst_minor) |
