From 8e9a74d3881205a37eacc501d6a7496fa9febde8 Mon Sep 17 00:00:00 2001 From: James Ennis Date: Thu, 7 Mar 2019 11:09:27 +0000 Subject: _yaml.py: Move node_get_project_path to project.py _yaml.node_get_project_path() is currently only used by Project and by Plugin. This function has been moved to Project._get_path_from_node() and no longers requires a project directory as argument. --- buildstream/_project.py | 105 +++++++++++++++++++++++++++++++++++++++++++++--- buildstream/_yaml.py | 97 -------------------------------------------- buildstream/plugin.py | 7 ++-- 3 files changed, 103 insertions(+), 106 deletions(-) diff --git a/buildstream/_project.py b/buildstream/_project.py index 6df54f70f..6cbba497f 100644 --- a/buildstream/_project.py +++ b/buildstream/_project.py @@ -20,8 +20,10 @@ import gc import os +import sys from collections import OrderedDict from collections.abc import Mapping +from pathlib import Path from pluginbase import PluginBase from . import utils from . import _cachekey @@ -227,6 +229,100 @@ class Project(): return self._cache_key + # get_path_from_node() + # + # Fetches the project path from a dictionary node and validates it + # + # Paths are asserted to never lead to a directory outside of the project + # directory. In addition, paths can not point to symbolic links, fifos, + # sockets and block/character devices. + # + # The `check_is_file` and `check_is_dir` parameters can be used to + # perform additional validations on the path. Note that an exception + # will always be raised if both parameters are set to ``True``. + # + # Args: + # node (dict): A dictionary loaded from YAML + # key (str): The key whose value contains a path to validate + # check_is_file (bool): If ``True`` an error will also be raised + # if path does not point to a regular file. + # Defaults to ``False`` + # check_is_dir (bool): If ``True`` an error will be also raised + # if path does not point to a directory. + # Defaults to ``False`` + # Returns: + # (str): The project path + # + # Raises: + # (LoadError): In case that the project path is not valid or does not + # exist + # + def get_path_from_node(self, node, key, *, + check_is_file=False, check_is_dir=False): + path_str = _yaml.node_get(node, str, key) + path = Path(path_str) + project_dir_path = Path(self.directory) + + provenance = _yaml.node_get_provenance(node, key=key) + + if (project_dir_path / path).is_symlink(): + raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND, + "{}: Specified path '{}' must not point to " + "symbolic links " + .format(provenance, path_str)) + + if path.parts and path.parts[0] == '..': + raise LoadError(LoadErrorReason.PROJ_PATH_INVALID, + "{}: Specified path '{}' first component must " + "not be '..'" + .format(provenance, path_str)) + + try: + full_path = (project_dir_path / path) + if sys.version_info[0] == 3 and sys.version_info[1] < 6: + full_resolved_path = full_path.resolve() + else: + full_resolved_path = full_path.resolve(strict=True) # pylint: disable=unexpected-keyword-arg + except FileNotFoundError: + raise LoadError(LoadErrorReason.MISSING_FILE, + "{}: Specified path '{}' does not exist" + .format(provenance, path_str)) + + is_inside = project_dir_path.resolve() in full_resolved_path.parents or ( + full_resolved_path == project_dir_path) + + if not is_inside: + raise LoadError(LoadErrorReason.PROJ_PATH_INVALID, + "{}: Specified path '{}' must not lead outside of the " + "project directory" + .format(provenance, path_str)) + + if path.is_absolute(): + raise LoadError(LoadErrorReason.PROJ_PATH_INVALID, + "{}: Absolute path: '{}' invalid.\n" + "Please specify a path relative to the project's root." + .format(provenance, path)) + + if full_resolved_path.is_socket() or ( + full_resolved_path.is_fifo() or + full_resolved_path.is_block_device()): + raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND, + "{}: Specified path '{}' points to an unsupported " + "file kind" + .format(provenance, path_str)) + + if check_is_file and not full_resolved_path.is_file(): + raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND, + "{}: Specified path '{}' is not a regular file" + .format(provenance, path_str)) + + if check_is_dir and not full_resolved_path.is_dir(): + raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND, + "{}: Specified path '{}' is not a directory" + .format(provenance, path_str)) + + return path_str + def _validate_node(self, node): _yaml.node_validate(node, [ 'format-version', @@ -508,8 +604,8 @@ class Project(): self.element_path = os.path.join( self.directory, - _yaml.node_get_project_path(pre_config_node, 'element-path', self.directory, - check_is_dir=True) + self.get_path_from_node(pre_config_node, 'element-path', + check_is_dir=True) ) self.config.options = OptionPool(self.element_path) @@ -857,9 +953,8 @@ class Project(): if group in origin_dict: del origin_dict[group] if origin_dict['origin'] == 'local': - path = _yaml.node_get_project_path(origin, 'path', - self.directory, - check_is_dir=True) + path = self.get_path_from_node(origin, 'path', + check_is_dir=True) # paths are passed in relative to the project, but must be absolute origin_dict['path'] = os.path.join(self.directory, path) destination.append(origin_dict) diff --git a/buildstream/_yaml.py b/buildstream/_yaml.py index 74ed1bbf8..fc24a223b 100644 --- a/buildstream/_yaml.py +++ b/buildstream/_yaml.py @@ -22,7 +22,6 @@ import collections import string from copy import deepcopy from contextlib import ExitStack -from pathlib import Path from ruamel import yaml from ruamel.yaml.representer import SafeRepresenter, RoundTripRepresenter @@ -414,102 +413,6 @@ def node_get(node, expected_type, key, indices=None, *, default_value=_sentinel, return value -# node_get_project_path() -# -# Fetches a project path from a dictionary node and validates it -# -# Paths are asserted to never lead to a directory outside of the project -# directory. In addition, paths can not point to symbolic links, fifos, -# sockets and block/character devices. -# -# The `check_is_file` and `check_is_dir` parameters can be used to -# perform additional validations on the path. Note that an exception -# will always be raised if both parameters are set to ``True``. -# -# Args: -# node (dict): A dictionary loaded from YAML -# key (str): The key whose value contains a path to validate -# project_dir (str): The project directory -# check_is_file (bool): If ``True`` an error will also be raised -# if path does not point to a regular file. -# Defaults to ``False`` -# check_is_dir (bool): If ``True`` an error will be also raised -# if path does not point to a directory. -# Defaults to ``False`` -# Returns: -# (str): The project path -# -# Raises: -# (LoadError): In case that the project path is not valid or does not -# exist -# -def node_get_project_path(node, key, project_dir, *, - check_is_file=False, check_is_dir=False): - path_str = node_get(node, str, key) - path = Path(path_str) - project_dir_path = Path(project_dir) - - provenance = node_get_provenance(node, key=key) - - if (project_dir_path / path).is_symlink(): - raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND, - "{}: Specified path '{}' must not point to " - "symbolic links " - .format(provenance, path_str)) - - if path.parts and path.parts[0] == '..': - raise LoadError(LoadErrorReason.PROJ_PATH_INVALID, - "{}: Specified path '{}' first component must " - "not be '..'" - .format(provenance, path_str)) - - try: - full_path = (project_dir_path / path) - if sys.version_info[0] == 3 and sys.version_info[1] < 6: - full_resolved_path = full_path.resolve() - else: - full_resolved_path = full_path.resolve(strict=True) # pylint: disable=unexpected-keyword-arg - except FileNotFoundError: - raise LoadError(LoadErrorReason.MISSING_FILE, - "{}: Specified path '{}' does not exist" - .format(provenance, path_str)) - - is_inside = project_dir_path.resolve() in full_resolved_path.parents or ( - full_resolved_path == project_dir_path) - - if not is_inside: - raise LoadError(LoadErrorReason.PROJ_PATH_INVALID, - "{}: Specified path '{}' must not lead outside of the " - "project directory" - .format(provenance, path_str)) - - if path.is_absolute(): - raise LoadError(LoadErrorReason.PROJ_PATH_INVALID, - "{}: Absolute path: '{}' invalid.\n" - "Please specify a path relative to the project's root." - .format(provenance, path)) - - if full_resolved_path.is_socket() or ( - full_resolved_path.is_fifo() or - full_resolved_path.is_block_device()): - raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND, - "{}: Specified path '{}' points to an unsupported " - "file kind" - .format(provenance, path_str)) - - if check_is_file and not full_resolved_path.is_file(): - raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND, - "{}: Specified path '{}' is not a regular file" - .format(provenance, path_str)) - - if check_is_dir and not full_resolved_path.is_dir(): - raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND, - "{}: Specified path '{}' is not a directory" - .format(provenance, path_str)) - - return path_str - - # node_items() # # A convenience generator for iterating over loaded key/value diff --git a/buildstream/plugin.py b/buildstream/plugin.py index c21ae939e..f5daf31bf 100644 --- a/buildstream/plugin.py +++ b/buildstream/plugin.py @@ -420,10 +420,9 @@ class Plugin(): """ - return _yaml.node_get_project_path(node, key, - self.__project.directory, - check_is_file=check_is_file, - check_is_dir=check_is_dir) + return self.__project.get_path_from_node(node, key, + check_is_file=check_is_file, + check_is_dir=check_is_dir) def node_validate(self, node, valid_keys): """This should be used in :func:`~buildstream.plugin.Plugin.configure` -- cgit v1.2.1