From c19000038bd7b56c08013b7063a2d891c845358a Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Mon, 20 Aug 2018 17:04:52 -0700 Subject: schemas: rework schemas so that they can be included from other files - Each schema now has a top-level `properties` and `schema` attribute. - The `properties` is a fragment that can be included in other jsonschemas, via Python, not via '$ref' - Th `schema` is a complete `jsonschema` with `title` and `$schema` properties. --- lib/spack/spack/config.py | 23 +-- lib/spack/spack/environment.py | 1 - lib/spack/spack/schema/__init__.py | 10 +- lib/spack/spack/schema/compilers.py | 201 ++++++++++++------------ lib/spack/spack/schema/config.py | 99 ++++++------ lib/spack/spack/schema/env.py | 15 +- lib/spack/spack/schema/mirrors.py | 26 ++-- lib/spack/spack/schema/modules.py | 296 +++++++++++++++++++----------------- lib/spack/spack/schema/packages.py | 141 ++++++++--------- lib/spack/spack/schema/repos.py | 20 ++- lib/spack/spack/test/config.py | 64 +++++++- 11 files changed, 496 insertions(+), 400 deletions(-) (limited to 'lib') diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py index 179972299e..0d2d5046a9 100644 --- a/lib/spack/spack/config.py +++ b/lib/spack/spack/config.py @@ -51,7 +51,12 @@ from llnl.util.filesystem import mkdirp import spack.paths import spack.architecture -import spack.schema +import spack.schema.compilers +import spack.schema.mirrors +import spack.schema.repos +import spack.schema.packages +import spack.schema.modules +import spack.schema.config from spack.error import SpackError # Hacked yaml for configuration files preserves line numbers. @@ -175,7 +180,7 @@ class ConfigScope(object): try: mkdirp(self.path) with open(filename, 'w') as f: - _validate_section(data, section_schemas[section]) + _validate(data, section_schemas[section]) syaml.dump(data, stream=f, default_flow_style=False) except (yaml.YAMLError, IOError) as e: raise ConfigFileError( @@ -216,7 +221,7 @@ class InternalConfigScope(ConfigScope): if data: for section in data: dsec = data[section] - _validate_section({section: dsec}, section_schemas[section]) + _validate({section: dsec}, section_schemas[section]) self.sections[section] = _mark_internal( syaml.syaml_dict({section: dsec}), name) @@ -234,7 +239,7 @@ class InternalConfigScope(ConfigScope): """This only validates, as the data is already in memory.""" data = self.get_section(section) if data is not None: - _validate_section(data, section_schemas[section]) + _validate(data, section_schemas[section]) self.sections[section] = _mark_internal(data, self.name) def __repr__(self): @@ -585,7 +590,7 @@ def _validate_section_name(section): % (section, " ".join(section_schemas.keys()))) -def _validate_section(data, schema): +def _validate(data, schema): """Validate data read in from a Spack YAML file. This leverages the line information (start_mark, end_mark) stored @@ -593,13 +598,13 @@ def _validate_section(data, schema): """ import jsonschema - if not hasattr(_validate_section, 'validator'): + if not hasattr(_validate, 'validator'): default_setting_validator = _extend_with_default( jsonschema.Draft4Validator) - _validate_section.validator = default_setting_validator + _validate.validator = default_setting_validator try: - _validate_section.validator(schema).validate(data) + _validate.validator(schema).validate(data) except jsonschema.ValidationError as e: raise ConfigFormatError(e, data) @@ -623,7 +628,7 @@ def _read_config_file(filename, schema): data = _mark_overrides(syaml.load(f)) if data: - _validate_section(data, schema) + _validate(data, schema) return data except MarkedYAMLError as e: diff --git a/lib/spack/spack/environment.py b/lib/spack/spack/environment.py index cc34f8a7ba..5b745c6c9b 100644 --- a/lib/spack/spack/environment.py +++ b/lib/spack/spack/environment.py @@ -85,7 +85,6 @@ def deactivate(): return - def root(name): """Get the root directory for an environment by name.""" return fs.join_path(env_path, name) diff --git a/lib/spack/spack/schema/__init__.py b/lib/spack/spack/schema/__init__.py index f2533f2cc2..b7a90827f2 100644 --- a/lib/spack/spack/schema/__init__.py +++ b/lib/spack/spack/schema/__init__.py @@ -3,12 +3,4 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -"""This module contains jsonschema files for all of Spack's YAML formats. -""" -from llnl.util.lang import list_modules - -# Automatically bring in all sub-modules -__all__ = [] -for mod in list_modules(__path__[0]): - __import__('%s.%s' % (__name__, mod)) - __all__.append(mod) +"""This module contains jsonschema files for all of Spack's YAML formats.""" diff --git a/lib/spack/spack/schema/compilers.py b/lib/spack/spack/schema/compilers.py index ab09767221..139462d2d4 100644 --- a/lib/spack/spack/schema/compilers.py +++ b/lib/spack/spack/schema/compilers.py @@ -10,114 +10,119 @@ """ -schema = { - '$schema': 'http://json-schema.org/schema#', - 'title': 'Spack compiler configuration file schema', - 'type': 'object', - 'additionalProperties': False, - 'properties': { - 'compilers': { - 'type': 'array', - 'items': { - 'type': 'object', - 'additionalProperties': False, - 'properties': { - 'compiler': { - 'type': 'object', - 'additionalProperties': False, - 'required': [ - 'paths', 'spec', 'modules', 'operating_system'], - 'properties': { - 'paths': { - 'type': 'object', - 'required': ['cc', 'cxx', 'f77', 'fc'], - 'additionalProperties': False, - 'properties': { - 'cc': {'anyOf': [{'type': 'string'}, +#: Properties for inclusion in other schemas +properties = { + 'compilers': { + 'type': 'array', + 'items': [{ + 'type': 'object', + 'additionalProperties': False, + 'properties': { + 'compiler': { + 'type': 'object', + 'additionalProperties': False, + 'required': [ + 'paths', 'spec', 'modules', 'operating_system'], + 'properties': { + 'paths': { + 'type': 'object', + 'required': ['cc', 'cxx', 'f77', 'fc'], + 'additionalProperties': False, + 'properties': { + 'cc': {'anyOf': [{'type': 'string'}, + {'type': 'null'}]}, + 'cxx': {'anyOf': [{'type': 'string'}, + {'type': 'null'}]}, + 'f77': {'anyOf': [{'type': 'string'}, + {'type': 'null'}]}, + 'fc': {'anyOf': [{'type': 'string'}, + {'type': 'null'}]}}}, + 'flags': { + 'type': 'object', + 'additionalProperties': False, + 'properties': { + 'cflags': {'anyOf': [{'type': 'string'}, + {'type': 'null'}]}, + 'cxxflags': {'anyOf': [{'type': 'string'}, + {'type': 'null'}]}, + 'fflags': {'anyOf': [{'type': 'string'}, + {'type': 'null'}]}, + 'cppflags': {'anyOf': [{'type': 'string'}, + {'type': 'null'}]}, + 'ldflags': {'anyOf': [{'type': 'string'}, {'type': 'null'}]}, - 'cxx': {'anyOf': [{'type': 'string'}, - {'type': 'null'}]}, - 'f77': {'anyOf': [{'type': 'string'}, - {'type': 'null'}]}, - 'fc': {'anyOf': [{'type': 'string'}, - {'type': 'null'}]}}}, - 'flags': { - 'type': 'object', - 'additionalProperties': False, - 'properties': { - 'cflags': {'anyOf': [{'type': 'string'}, - {'type': 'null'}]}, - 'cxxflags': {'anyOf': [{'type': 'string'}, - {'type': 'null'}]}, - 'fflags': {'anyOf': [{'type': 'string'}, - {'type': 'null'}]}, - 'cppflags': {'anyOf': [{'type': 'string'}, - {'type': 'null'}]}, - 'ldflags': {'anyOf': [{'type': 'string'}, - {'type': 'null'}]}, - 'ldlibs': {'anyOf': [{'type': 'string'}, - {'type': 'null'}]}}}, - 'spec': {'type': 'string'}, - 'operating_system': {'type': 'string'}, - 'target': {'type': 'string'}, - 'alias': {'anyOf': [{'type': 'string'}, - {'type': 'null'}]}, - 'modules': {'anyOf': [{'type': 'string'}, - {'type': 'null'}, - {'type': 'array'}]}, - 'environment': { - 'type': 'object', - 'default': {}, - 'additionalProperties': False, - 'properties': { - 'set': { - 'type': 'object', - 'patternProperties': { - # Variable name - r'\w[\w-]*': { - 'anyOf': [{'type': 'string'}, - {'type': 'number'}] - } - } - }, - 'unset': { - 'type': 'object', - 'patternProperties': { - # Variable name - r'\w[\w-]*': {'type': 'null'} + 'ldlibs': {'anyOf': [{'type': 'string'}, + {'type': 'null'}]}}}, + 'spec': {'type': 'string'}, + 'operating_system': {'type': 'string'}, + 'target': {'type': 'string'}, + 'alias': {'anyOf': [{'type': 'string'}, + {'type': 'null'}]}, + 'modules': {'anyOf': [{'type': 'string'}, + {'type': 'null'}, + {'type': 'array'}]}, + 'environment': { + 'type': 'object', + 'default': {}, + 'additionalProperties': False, + 'properties': { + 'set': { + 'type': 'object', + 'patternProperties': { + # Variable name + r'\w[\w-]*': { + 'anyOf': [{'type': 'string'}, + {'type': 'number'}] } - }, - 'prepend-path': { - 'type': 'object', - 'patternProperties': { - # Variable name - r'\w[\w-]*': { - 'anyOf': [{'type': 'string'}, - {'type': 'number'}] - } + } + }, + 'unset': { + 'type': 'object', + 'patternProperties': { + # Variable name + r'\w[\w-]*': {'type': 'null'} + } + }, + 'prepend-path': { + 'type': 'object', + 'patternProperties': { + # Variable name + r'\w[\w-]*': { + 'anyOf': [{'type': 'string'}, + {'type': 'number'}] } - }, - 'append-path': { - 'type': 'object', - 'patternProperties': { - # Variable name - r'\w[\w-]*': { - 'anyOf': [{'type': 'string'}, - {'type': 'number'}] - } + } + }, + 'append-path': { + 'type': 'object', + 'patternProperties': { + # Variable name + r'\w[\w-]*': { + 'anyOf': [{'type': 'string'}, + {'type': 'number'}] } } } - }, - 'extra_rpaths': { - 'type': 'array', - 'default': [], - 'items': {'type': 'string'} } + }, + 'extra_rpaths': { + 'type': 'array', + 'default': [], + 'items': {'type': 'string'} } } } } - } + }] } } + + +#: Full schema with metadata +schema = { + '$schema': 'http://json-schema.org/schema#', + 'title': 'Spack compiler configuration file schema', + 'type': 'object', + 'additionalProperties': False, + 'properties': properties, +} diff --git a/lib/spack/spack/schema/config.py b/lib/spack/spack/schema/config.py index 583fe79fbf..0558a7a9ae 100644 --- a/lib/spack/spack/schema/config.py +++ b/lib/spack/spack/schema/config.py @@ -10,56 +10,61 @@ """ +#: Properties for inclusion in other schemas +properties = { + 'config': { + 'type': 'object', + 'default': {}, + 'properties': { + 'install_tree': {'type': 'string'}, + 'install_hash_length': {'type': 'integer', 'minimum': 1}, + 'install_path_scheme': {'type': 'string'}, + 'build_stage': { + 'oneOf': [ + {'type': 'string'}, + {'type': 'array', + 'items': {'type': 'string'}}], + }, + 'template_dirs': { + 'type': 'array', + 'items': {'type': 'string'} + }, + 'module_roots': { + 'type': 'object', + 'additionalProperties': False, + 'properties': { + 'tcl': {'type': 'string'}, + 'lmod': {'type': 'string'}, + 'dotkit': {'type': 'string'}, + }, + }, + 'source_cache': {'type': 'string'}, + 'misc_cache': {'type': 'string'}, + 'verify_ssl': {'type': 'boolean'}, + 'debug': {'type': 'boolean'}, + 'checksum': {'type': 'boolean'}, + 'locks': {'type': 'boolean'}, + 'dirty': {'type': 'boolean'}, + 'build_language': {'type': 'string'}, + 'build_jobs': {'type': 'integer', 'minimum': 1}, + 'ccache': {'type': 'boolean'}, + 'db_lock_timeout': {'type': 'integer', 'minimum': 1}, + 'package_lock_timeout': { + 'anyOf': [ + {'type': 'integer', 'minimum': 1}, + {'type': 'null'} + ], + }, + }, + }, +} + + +#: Full schema with metadata schema = { '$schema': 'http://json-schema.org/schema#', 'title': 'Spack core configuration file schema', 'type': 'object', 'additionalProperties': False, - 'patternProperties': { - 'config': { - 'type': 'object', - 'default': {}, - 'properties': { - 'install_tree': {'type': 'string'}, - 'install_hash_length': {'type': 'integer', 'minimum': 1}, - 'install_path_scheme': {'type': 'string'}, - 'build_stage': { - 'oneOf': [ - {'type': 'string'}, - {'type': 'array', - 'items': {'type': 'string'}}], - }, - 'template_dirs': { - 'type': 'array', - 'items': {'type': 'string'} - }, - 'module_roots': { - 'type': 'object', - 'additionalProperties': False, - 'properties': { - 'tcl': {'type': 'string'}, - 'lmod': {'type': 'string'}, - 'dotkit': {'type': 'string'}, - }, - }, - 'source_cache': {'type': 'string'}, - 'misc_cache': {'type': 'string'}, - 'verify_ssl': {'type': 'boolean'}, - 'debug': {'type': 'boolean'}, - 'checksum': {'type': 'boolean'}, - 'locks': {'type': 'boolean'}, - 'dirty': {'type': 'boolean'}, - 'build_language': {'type': 'string'}, - 'build_jobs': {'type': 'integer', 'minimum': 1}, - 'ccache': {'type': 'boolean'}, - 'db_lock_timeout': {'type': 'integer', 'minimum': 1}, - 'package_lock_timeout': { - 'anyOf': [ - {'type': 'integer', 'minimum': 1}, - {'type': 'null'} - ], - }, - } - }, - }, + 'properties': properties, } diff --git a/lib/spack/spack/schema/env.py b/lib/spack/spack/schema/env.py index dab4d6a5bd..676d65ec62 100644 --- a/lib/spack/spack/schema/env.py +++ b/lib/spack/spack/schema/env.py @@ -12,19 +12,22 @@ schema = { '$schema': 'http://json-schema.org/schema#', - 'title': 'Spack Environments user configuration file schema', + 'title': 'Spack environment file schema', 'type': 'object', 'additionalProperties': False, - 'properties': { - 'env': { + 'patternProperties': { + '^env|spack$': { 'type': 'object', 'default': {}, 'properties': { - 'configs': { + 'include': { 'type': 'array', - 'default': [], - 'items': {'type': 'string'} + 'items': { + 'type': 'string' + }, }, + + 'specs': { 'type': 'object', 'default': {}, diff --git a/lib/spack/spack/schema/mirrors.py b/lib/spack/spack/schema/mirrors.py index 8832552660..345f43db66 100644 --- a/lib/spack/spack/schema/mirrors.py +++ b/lib/spack/spack/schema/mirrors.py @@ -10,20 +10,24 @@ """ +#: Properties for inclusion in other schemas +properties = { + 'mirrors': { + 'type': 'object', + 'default': {}, + 'additionalProperties': False, + 'patternProperties': { + r'\w[\w-]*': {'type': 'string'}, + }, + }, +} + + +#: Full schema with metadata schema = { '$schema': 'http://json-schema.org/schema#', 'title': 'Spack mirror configuration file schema', 'type': 'object', 'additionalProperties': False, - 'patternProperties': { - r'mirrors': { - 'type': 'object', - 'default': {}, - 'additionalProperties': False, - 'patternProperties': { - r'\w[\w-]*': { - 'type': 'string'}, - }, - }, - }, + 'properties': properties, } diff --git a/lib/spack/spack/schema/modules.py b/lib/spack/spack/schema/modules.py index d1dc9c7605..0a54856105 100644 --- a/lib/spack/spack/schema/modules.py +++ b/lib/spack/spack/schema/modules.py @@ -10,158 +10,170 @@ """ -schema = { - '$schema': 'http://json-schema.org/schema#', - 'title': 'Spack module file configuration file schema', - 'type': 'object', - 'additionalProperties': False, - 'definitions': { - 'array_of_strings': { - 'type': 'array', - 'default': [], - 'items': { +#: Definitions for parts of module schema +definitions = { + 'array_of_strings': { + 'type': 'array', + 'default': [], + 'items': { + 'type': 'string' + } + }, + 'dictionary_of_strings': { + 'type': 'object', + 'patternProperties': { + r'\w[\w-]*': { # key 'type': 'string' } - }, - 'dictionary_of_strings': { - 'type': 'object', - 'patternProperties': { - r'\w[\w-]*': { # key - 'type': 'string' - } - } - }, - 'dependency_selection': { - 'type': 'string', - 'enum': ['none', 'direct', 'all'] - }, - 'module_file_configuration': { - 'type': 'object', - 'default': {}, - 'additionalProperties': False, - 'properties': { - 'filter': { - 'type': 'object', - 'default': {}, - 'additionalProperties': False, - 'properties': { - 'environment_blacklist': { - 'type': 'array', - 'default': [], - 'items': { - 'type': 'string' - } + } + }, + 'dependency_selection': { + 'type': 'string', + 'enum': ['none', 'direct', 'all'] + }, + 'module_file_configuration': { + 'type': 'object', + 'default': {}, + 'additionalProperties': False, + 'properties': { + 'filter': { + 'type': 'object', + 'default': {}, + 'additionalProperties': False, + 'properties': { + 'environment_blacklist': { + 'type': 'array', + 'default': [], + 'items': { + 'type': 'string' } } + } + }, + 'template': { + 'type': 'string' + }, + 'autoload': { + '$ref': '#/definitions/dependency_selection'}, + 'prerequisites': { + '$ref': '#/definitions/dependency_selection'}, + 'conflict': { + '$ref': '#/definitions/array_of_strings'}, + 'load': { + '$ref': '#/definitions/array_of_strings'}, + 'suffixes': { + '$ref': '#/definitions/dictionary_of_strings'}, + 'environment': { + 'type': 'object', + 'default': {}, + 'additionalProperties': False, + 'properties': { + 'set': { + '$ref': '#/definitions/dictionary_of_strings'}, + 'unset': { + '$ref': '#/definitions/array_of_strings'}, + 'prepend_path': { + '$ref': '#/definitions/dictionary_of_strings'}, + 'append_path': { + '$ref': '#/definitions/dictionary_of_strings'} + } + } + } + }, + 'module_type_configuration': { + 'type': 'object', + 'default': {}, + 'anyOf': [ + {'properties': { + 'verbose': { + 'type': 'boolean', + 'default': False }, - 'template': { - 'type': 'string' + 'hash_length': { + 'type': 'integer', + 'minimum': 0, + 'default': 7 }, - 'autoload': { - '$ref': '#/definitions/dependency_selection'}, - 'prerequisites': { - '$ref': '#/definitions/dependency_selection'}, - 'conflict': { + 'whitelist': { '$ref': '#/definitions/array_of_strings'}, - 'load': { + 'blacklist': { '$ref': '#/definitions/array_of_strings'}, - 'suffixes': { - '$ref': '#/definitions/dictionary_of_strings'}, - 'environment': { - 'type': 'object', - 'default': {}, - 'additionalProperties': False, - 'properties': { - 'set': { - '$ref': '#/definitions/dictionary_of_strings'}, - 'unset': { - '$ref': '#/definitions/array_of_strings'}, - 'prepend_path': { - '$ref': '#/definitions/dictionary_of_strings'}, - 'append_path': { - '$ref': '#/definitions/dictionary_of_strings'} - } + 'blacklist_implicits': { + 'type': 'boolean', + 'default': False + }, + 'naming_scheme': { + 'type': 'string' # Can we be more specific here? } - } - }, - 'module_type_configuration': { - 'type': 'object', - 'default': {}, - 'anyOf': [ - {'properties': { - 'verbose': { - 'type': 'boolean', - 'default': False - }, - 'hash_length': { - 'type': 'integer', - 'minimum': 0, - 'default': 7 - }, - 'whitelist': { - '$ref': '#/definitions/array_of_strings'}, - 'blacklist': { - '$ref': '#/definitions/array_of_strings'}, - 'blacklist_implicits': { - 'type': 'boolean', - 'default': False - }, - 'naming_scheme': { - 'type': 'string' # Can we be more specific here? - } - }}, - {'patternProperties': { + }}, + {'patternProperties': { + r'\w[\w-]*': { + '$ref': '#/definitions/module_file_configuration' + } + }} + ] + } +} + + +# Properties for inclusion into other schemas (requires definitions) +properties = { + 'modules': { + 'type': 'object', + 'default': {}, + 'additionalProperties': False, + 'properties': { + 'prefix_inspections': { + 'type': 'object', + 'patternProperties': { + # prefix-relative path to be inspected for existence r'\w[\w-]*': { - '$ref': '#/definitions/module_file_configuration' - } - }} - ] - } - }, - 'patternProperties': { - r'modules': { - 'type': 'object', - 'default': {}, - 'additionalProperties': False, - 'properties': { - 'prefix_inspections': { - 'type': 'object', - 'patternProperties': { - # prefix-relative path to be inspected for existence - r'\w[\w-]*': { - '$ref': '#/definitions/array_of_strings'}}}, - 'enable': { - 'type': 'array', - 'default': [], - 'items': { - 'type': 'string', - 'enum': ['tcl', 'dotkit', 'lmod']}}, - 'lmod': { - 'allOf': [ - # Base configuration - {'$ref': '#/definitions/module_type_configuration'}, - { - 'core_compilers': { - '$ref': '#/definitions/array_of_strings' - }, - 'hierarchical_scheme': { - '$ref': '#/definitions/array_of_strings' - } - } # Specific lmod extensions - ]}, - 'tcl': { - 'allOf': [ - # Base configuration - {'$ref': '#/definitions/module_type_configuration'}, - {} # Specific tcl extensions - ]}, - 'dotkit': { - 'allOf': [ - # Base configuration - {'$ref': '#/definitions/module_type_configuration'}, - {} # Specific dotkit extensions - ]}, - } + '$ref': '#/definitions/array_of_strings'}}}, + 'enable': { + 'type': 'array', + 'default': [], + 'items': { + 'type': 'string', + 'enum': ['tcl', 'dotkit', 'lmod']}}, + 'lmod': { + 'allOf': [ + # Base configuration + {'$ref': '#/definitions/module_type_configuration'}, + { + 'core_compilers': { + '$ref': '#/definitions/array_of_strings' + }, + 'hierarchical_scheme': { + '$ref': '#/definitions/array_of_strings' + } + } # Specific lmod extensions + ] + }, + 'tcl': { + 'allOf': [ + # Base configuration + {'$ref': '#/definitions/module_type_configuration'}, + {} # Specific tcl extensions + ] + }, + 'dotkit': { + 'allOf': [ + # Base configuration + {'$ref': '#/definitions/module_type_configuration'}, + {} # Specific dotkit extensions + ] + }, }, }, } + + +#: Full schema with metadata +schema = { + '$schema': 'http://json-schema.org/schema#', + 'title': 'Spack module file configuration file schema', + 'definitions': definitions, + 'type': 'object', + 'additionalProperties': False, + 'properties': properties, +} diff --git a/lib/spack/spack/schema/packages.py b/lib/spack/spack/schema/packages.py index a8782fc23b..c762a75ba4 100644 --- a/lib/spack/spack/schema/packages.py +++ b/lib/spack/spack/schema/packages.py @@ -10,79 +10,84 @@ """ -schema = { - '$schema': 'http://json-schema.org/schema#', - 'title': 'Spack package configuration file schema', - 'type': 'object', - 'additionalProperties': False, - 'patternProperties': { - r'packages': { - 'type': 'object', - 'default': {}, - 'additionalProperties': False, - 'patternProperties': { - r'\w[\w-]*': { # package name - 'type': 'object', - 'default': {}, - 'additionalProperties': False, - 'properties': { - 'version': { - 'type': 'array', - 'default': [], - # version strings - 'items': {'anyOf': [{'type': 'string'}, - {'type': 'number'}]}}, - 'compiler': { - 'type': 'array', - 'default': [], - 'items': {'type': 'string'}}, # compiler specs - 'buildable': { - 'type': 'boolean', - 'default': True, - }, - 'permissions': { - 'type': 'object', - 'additionalProperties': False, - 'properties': { - 'read': { - 'type': 'string', - 'enum': ['user', 'group', 'world'], - }, - 'write': { - 'type': 'string', - 'enum': ['user', 'group', 'world'], - }, - 'group': { - 'type': 'string', - }, +#: Properties for inclusion in other schemas +properties = { + 'packages': { + 'type': 'object', + 'default': {}, + 'additionalProperties': False, + 'patternProperties': { + r'\w[\w-]*': { # package name + 'type': 'object', + 'default': {}, + 'additionalProperties': False, + 'properties': { + 'version': { + 'type': 'array', + 'default': [], + # version strings + 'items': {'anyOf': [{'type': 'string'}, + {'type': 'number'}]}}, + 'compiler': { + 'type': 'array', + 'default': [], + 'items': {'type': 'string'}}, # compiler specs + 'buildable': { + 'type': 'boolean', + 'default': True, + }, + 'permissions': { + 'type': 'object', + 'additionalProperties': False, + 'properties': { + 'read': { + 'type': 'string', + 'enum': ['user', 'group', 'world'], + }, + 'write': { + 'type': 'string', + 'enum': ['user', 'group', 'world'], + }, + 'group': { + 'type': 'string', }, - }, - 'modules': { - 'type': 'object', - 'default': {}, - }, - 'providers': { - 'type': 'object', - 'default': {}, - 'additionalProperties': False, - 'patternProperties': { - r'\w[\w-]*': { - 'type': 'array', - 'default': [], - 'items': {'type': 'string'}, }, }, }, - 'paths': { - 'type': 'object', - 'default': {}, - }, - 'variants': { - 'oneOf': [ - {'type': 'string'}, - {'type': 'array', - 'items': {'type': 'string'}}], }, }, + 'modules': { + 'type': 'object', + 'default': {}, + }, + 'providers': { + 'type': 'object', + 'default': {}, + 'additionalProperties': False, + 'patternProperties': { + r'\w[\w-]*': { + 'type': 'array', + 'default': [], + 'items': {'type': 'string'}, }, }, }, + 'paths': { + 'type': 'object', + 'default': {}, + }, + 'variants': { + 'oneOf': [ + {'type': 'string'}, + {'type': 'array', + 'items': {'type': 'string'}}], + }, }, }, }, }, } + + +#: Full schema with metadata +schema = { + '$schema': 'http://json-schema.org/schema#', + 'title': 'Spack package configuration file schema', + 'type': 'object', + 'additionalProperties': False, + 'properties': properties, +} diff --git a/lib/spack/spack/schema/repos.py b/lib/spack/spack/schema/repos.py index fc5e5a4276..1ee0457d99 100644 --- a/lib/spack/spack/schema/repos.py +++ b/lib/spack/spack/schema/repos.py @@ -10,17 +10,21 @@ """ +#: Properties for inclusion in other schemas +properties = { + 'repos': { + 'type': 'array', + 'default': [], + 'items': {'type': 'string'}, + }, +} + + +#: Full schema with metadata schema = { '$schema': 'http://json-schema.org/schema#', 'title': 'Spack repository configuration file schema', 'type': 'object', 'additionalProperties': False, - 'patternProperties': { - r'repos': { - 'type': 'array', - 'default': [], - 'items': { - 'type': 'string'}, - }, - }, + 'properties': properties, } diff --git a/lib/spack/spack/test/config.py b/lib/spack/spack/test/config.py index ea725473cb..3f58a9c799 100644 --- a/lib/spack/spack/test/config.py +++ b/lib/spack/spack/test/config.py @@ -7,6 +7,7 @@ import os import collections import getpass import tempfile +from six import StringIO from llnl.util.filesystem import touch, mkdirp @@ -15,6 +16,12 @@ import ruamel.yaml as yaml import spack.paths import spack.config +import spack.schema.compilers +import spack.schema.config +import spack.schema.packages +import spack.schema.mirrors +import spack.schema.repos +import spack.util.spack_yaml as syaml from spack.util.path import canonicalize_path @@ -631,7 +638,7 @@ config: spack.config._add_command_line_scopes(mutable_config, [str(tmpdir)]) -def test_immuntable_scope(tmpdir): +def test_immutable_scope(tmpdir): config_yaml = str(tmpdir.join('config.yaml')) with open(config_yaml, 'w') as f: f.write("""\ @@ -645,3 +652,58 @@ config: with pytest.raises(spack.config.ConfigError): scope.write_section('config') + + +def check_schema(name, file_contents): + """Check a Spack YAML schema against some data""" + f = StringIO(file_contents) + data = syaml.load(f) + spack.config._validate(data, name) + + +def test_bad_config_yaml(tmpdir): + with pytest.raises(spack.config.ConfigFormatError): + check_schema(spack.schema.config.schema, """\ +config: + verify_ssl: False + module_roots: + fmod: /some/fake/location +""") + + +def test_bad_mirrors_yaml(tmpdir): + with pytest.raises(spack.config.ConfigFormatError): + check_schema(spack.schema.mirrors.schema, """\ +mirrors: + local: True +""") + + +def test_bad_repos_yaml(tmpdir): + with pytest.raises(spack.config.ConfigFormatError): + check_schema(spack.schema.repos.schema, """\ +repos: + True +""") + + +def test_bad_compilers_yaml(tmpdir): + with pytest.raises(spack.config.ConfigFormatError): + check_schema(spack.schema.compilers.schema, """\ +compilers: + key_instead_of_list: 'value' +""") + + with pytest.raises(spack.config.ConfigFormatError): + check_schema(spack.schema.compilers.schema, """\ +compilers: + - shmompiler: + environment: /bad/value +""") + + with pytest.raises(spack.config.ConfigFormatError): + check_schema(spack.schema.compilers.schema, """\ +compilers: + - compiler: + fenfironfent: /bad/value +""") -- cgit v1.2.3-60-g2f50