diff options
32 files changed, 518 insertions, 142 deletions
@@ -134,6 +134,7 @@ then echo 'Checking source code for silliness' if ! (git ls-files --cached | grep -v '\.gz$' | + grep -v '\.json-schema$' | grep -Ev 'tests[^/]*/.*\.std(out|err)' | grep -vF 'tests.build/build-system-autotools.script' | xargs -r scripts/check-silliness); then diff --git a/morphlib/__init__.py b/morphlib/__init__.py index 81540a31..36e5c3a8 100644 --- a/morphlib/__init__.py +++ b/morphlib/__init__.py @@ -60,6 +60,7 @@ import builder import cachedrepo import cachekeycomputer import cmdline_parse_utils +import defaults import definitions_repo import definitions_version import extensions diff --git a/morphlib/artifactresolver_tests.py b/morphlib/artifactresolver_tests.py index 20617c65..4bf42a93 100644 --- a/morphlib/artifactresolver_tests.py +++ b/morphlib/artifactresolver_tests.py @@ -20,6 +20,12 @@ import yaml import morphlib +default_split_rules = { + 'chunk': morphlib.artifactsplitrule.DEFAULT_CHUNK_RULES, + 'stratum': morphlib.artifactsplitrule.DEFAULT_STRATUM_RULES, +} + + def get_chunk_morphology(name, artifact_names=[]): assert(isinstance(artifact_names, list)) @@ -88,9 +94,9 @@ class ArtifactResolverTests(unittest.TestCase): pool = morphlib.sourcepool.SourcePool() morph = get_chunk_morphology('chunk') - sources = morphlib.source.make_sources('repo', 'ref', - 'chunk.morph', 'sha1', - 'tree', morph) + sources = morphlib.source.make_sources( + 'repo', 'ref', 'chunk.morph', 'sha1', 'tree', morph, + default_split_rules=default_split_rules) for source in sources: pool.add(source) @@ -109,9 +115,9 @@ class ArtifactResolverTests(unittest.TestCase): pool = morphlib.sourcepool.SourcePool() morph = get_chunk_morphology('chunk', ['chunk-foobar']) - sources = morphlib.source.make_sources('repo', 'ref', - 'chunk.morph', 'sha1', - 'tree', morph) + sources = morphlib.source.make_sources( + 'repo', 'ref', 'chunk.morph', 'sha1', 'tree', morph, + default_split_rules=default_split_rules) for source in sources: pool.add(source) @@ -129,9 +135,9 @@ class ArtifactResolverTests(unittest.TestCase): pool = morphlib.sourcepool.SourcePool() morph = get_chunk_morphology('chunk', ['chunk-baz', 'chunk-qux']) - sources = morphlib.source.make_sources('repo', 'ref', - 'chunk.morph', 'sha1', - 'tree', morph) + sources = morphlib.source.make_sources( + 'repo', 'ref', 'chunk.morph', 'sha1', 'tree', morph, + default_split_rules=default_split_rules) for source in sources: pool.add(source) @@ -151,18 +157,18 @@ class ArtifactResolverTests(unittest.TestCase): pool = morphlib.sourcepool.SourcePool() morph = get_chunk_morphology('chunk') - sources = morphlib.source.make_sources('repo', 'ref', - 'chunk.morph', 'sha1', - 'tree', morph) + sources = morphlib.source.make_sources( + 'repo', 'ref', 'chunk.morph', 'sha1', 'tree', morph, + default_split_rules=default_split_rules) for chunk in sources: pool.add(chunk) morph = get_stratum_morphology( 'stratum', chunks=[('chunk', 'chunk', 'repo', 'ref')]) - stratum_sources = set(morphlib.source.make_sources('repo', 'ref', - 'stratum.morph', - 'sha1', 'tree', - morph)) + stratum_sources = set( + morphlib.source.make_sources( + 'repo', 'ref', 'stratum.morph', 'sha1', 'tree', morph, + default_split_rules=default_split_rules)) for stratum in stratum_sources: pool.add(stratum) @@ -197,9 +203,9 @@ class ArtifactResolverTests(unittest.TestCase): pool = morphlib.sourcepool.SourcePool() morph = get_chunk_morphology('chunk', ['chunk-foo', 'chunk-bar']) - sources = morphlib.source.make_sources('repo', 'ref', - 'chunk.morph', 'sha1', - 'tree', morph) + sources = morphlib.source.make_sources( + 'repo', 'ref', 'chunk.morph', 'sha1', 'tree', morph, + default_split_rules=default_split_rules) for chunk in sources: pool.add(chunk) @@ -208,10 +214,10 @@ class ArtifactResolverTests(unittest.TestCase): chunks=[ ('chunk', 'chunk', 'repo', 'ref'), ]) - stratum_sources = set(morphlib.source.make_sources('repo', 'ref', - 'stratum.morph', - 'sha1', 'tree', - morph)) + stratum_sources = set( + morphlib.source.make_sources( + 'repo', 'ref', 'stratum.morph', 'sha1', 'tree', morph, + default_split_rules=default_split_rules)) for stratum in stratum_sources: pool.add(stratum) @@ -246,7 +252,8 @@ class ArtifactResolverTests(unittest.TestCase): chunk = get_chunk_morphology('chunk1') chunk1, = morphlib.source.make_sources( - 'repo', 'original/ref', 'chunk1.morph', 'sha1', 'tree', chunk) + 'repo', 'original/ref', 'chunk1.morph', 'sha1', 'tree', chunk, + default_split_rules=default_split_rules) pool.add(chunk1) morph = get_stratum_morphology( @@ -254,15 +261,16 @@ class ArtifactResolverTests(unittest.TestCase): chunks=[(loader.save_to_string(chunk), 'chunk1.morph', 'repo', 'original/ref')], build_depends=['stratum2']) - sources = morphlib.source.make_sources('repo', 'original/ref', - 'stratum1.morph', 'sha1', - 'tree', morph) + sources = morphlib.source.make_sources( + 'repo', 'original/ref', 'stratum1.morph', 'sha1', 'tree', morph, + default_split_rules=default_split_rules) for stratum1 in sources: pool.add(stratum1) chunk = get_chunk_morphology('chunk2') chunk2, = morphlib.source.make_sources( - 'repo', 'original/ref', 'chunk2.morph', 'sha1', 'tree', chunk) + 'repo', 'original/ref', 'chunk2.morph', 'sha1', 'tree', chunk, + default_split_rules=default_split_rules) pool.add(chunk2) morph = get_stratum_morphology( @@ -270,9 +278,9 @@ class ArtifactResolverTests(unittest.TestCase): chunks=[(loader.save_to_string(chunk), 'chunk2.morph', 'repo', 'original/ref')], build_depends=['stratum1']) - sources = morphlib.source.make_sources('repo', 'original/ref', - 'stratum2.morph', 'sha1', - 'tree', morph) + sources = morphlib.source.make_sources( + 'repo', 'original/ref', 'stratum2.morph', 'sha1', 'tree', morph, + default_split_rules=default_split_rules) for stratum2 in sources: pool.add(stratum2) @@ -301,23 +309,23 @@ class ArtifactResolverTests(unittest.TestCase): build-system: manual build-depends: [] ''') - sources = morphlib.source.make_sources('repo', 'original/ref', - 'stratum.morph', 'sha1', - 'tree', morph) + sources = morphlib.source.make_sources( + 'repo', 'original/ref', 'stratum.morph', 'sha1', 'tree', morph, + default_split_rules=default_split_rules) for stratum in sources: pool.add(stratum) morph = get_chunk_morphology('chunk1') - sources = morphlib.source.make_sources('repo', 'original/ref', - 'chunk1.morph', 'sha1', - 'tree', morph) + sources = morphlib.source.make_sources( + 'repo', 'original/ref', 'chunk1.morph', 'sha1', 'tree', morph, + default_split_rules=default_split_rules) for chunk1 in sources: pool.add(chunk1) morph = get_chunk_morphology('chunk2') - sources = morphlib.source.make_sources('repo', 'original/ref', - 'chunk2.morph', 'sha1', - 'tree', morph) + sources = morphlib.source.make_sources( + 'repo', 'original/ref', 'chunk2.morph', 'sha1', 'tree', morph, + default_split_rules=default_split_rules) for chunk2 in sources: pool.add(chunk2) diff --git a/morphlib/artifactsplitrule.py b/morphlib/artifactsplitrule.py index b5ebdf83..ba5abe02 100644 --- a/morphlib/artifactsplitrule.py +++ b/morphlib/artifactsplitrule.py @@ -189,10 +189,6 @@ class SplitRules(collections.Iterable): for artifact, rule in self._rules) -# TODO: Work out a good way to feed new defaults in. This is good for -# the usual Linux userspace, but we may find issues and need a -# migration path to a more useful set, or develop a system with -# a different layout, like Android. DEFAULT_CHUNK_RULES = [ ('-bins', [ r"(usr/)?s?bin/.*" ]), ('-libs', [ @@ -229,6 +225,14 @@ DEFAULT_STRATUM_RULES = [ ] +# A 'no-op' set of split rules. An empty list would cause everything to be +# ignored, which is unlikely to ever be what a user wants, and breaks some +# internal bits of Morph. +EMPTY_RULES = [ + ('', [r'.*']) +] + + def unify_chunk_matches(morphology, default_rules=DEFAULT_CHUNK_RULES): '''Create split rules including defaults and per-chunk rules. @@ -237,6 +241,8 @@ def unify_chunk_matches(morphology, default_rules=DEFAULT_CHUNK_RULES): by building the chunk to the chunk artifact they should be put in. ''' + if default_rules is None or len(default_rules) == 0: + default_rules = EMPTY_RULES split_rules = SplitRules() @@ -265,6 +271,8 @@ def unify_stratum_matches(morphology, default_rules=DEFAULT_STRATUM_RULES): strata to the stratum artifact they should be put in. ''' + if default_rules is None or len(default_rules) == 0: + default_rules = EMPTY_RULES assignment_split_rules = SplitRules() for spec in morphology['chunks']: @@ -296,7 +304,7 @@ def unify_stratum_matches(morphology, default_rules=DEFAULT_STRATUM_RULES): match_split_rules)) -def unify_system_matches(morphology): +def unify_system_matches(morphology, default_rules=[]): '''Create split rules including defaults and per-chunk rules. With rules specified in the morphology's 'products' field and the diff --git a/morphlib/buildsystem.py b/morphlib/buildsystem.py index d7fbaf73..29d35627 100644 --- a/morphlib/buildsystem.py +++ b/morphlib/buildsystem.py @@ -44,11 +44,10 @@ _STRIP_COMMAND = r'''find "$DESTDIR" -type f \ class BuildSystem(object): - '''Predefined commands for common build systems. + '''Predefined command sequences for a given build system. - Some build systems are well known: autotools, for example. We provide - pre-defined build commands for these so that they don't need to be copied - and pasted many times in the build instructions. + For example, you can have an 'autotools' build system, which runs + 'configure', 'make' and 'make install'. ''' @@ -69,6 +68,14 @@ class BuildSystem(object): self.strip_commands = [] self.post_strip_commands = [] + def from_dict(self, name, commands): + self.name = name + + self.configure_commands = commands.get('configure-commands', []) + self.build_commands = commands.get('build-commands', []) + self.install_commands = commands.get('install-commands', []) + self.strip_commands = commands.get('strip-commands', []) + def __getitem__(self, key): key = '_'.join(key.split('-')) return getattr(self, key) diff --git a/morphlib/buildsystem_tests.py b/morphlib/buildsystem_tests.py index b49d30ae..6b6bf30f 100644 --- a/morphlib/buildsystem_tests.py +++ b/morphlib/buildsystem_tests.py @@ -43,6 +43,15 @@ class BuildSystemTests(unittest.TestCase): morph = self.bs.get_morphology('foobar') self.assertTrue(morph.__class__.__name__ == 'Morphology') + def test_construct_from_dict(self): + '''Test parsing a dict of information from a DEFAULTS file.''' + + commands_dict = { + 'configure-commands': 'foo' + } + self.bs.from_dict('test', commands_dict) + self.assertEqual(self.bs.configure_commands, 'foo') + class LookupBuildSystemTests(unittest.TestCase): diff --git a/morphlib/cachekeycomputer_tests.py b/morphlib/cachekeycomputer_tests.py index fbf680f0..6e736796 100644 --- a/morphlib/cachekeycomputer_tests.py +++ b/morphlib/cachekeycomputer_tests.py @@ -28,6 +28,12 @@ class DummyBuildEnvironment: self.env = env +default_split_rules = { + 'chunk': morphlib.artifactsplitrule.DEFAULT_CHUNK_RULES, + 'stratum': morphlib.artifactsplitrule.DEFAULT_STRATUM_RULES, +} + + class CacheKeyComputerTests(unittest.TestCase): def setUp(self): @@ -86,9 +92,9 @@ class CacheKeyComputerTests(unittest.TestCase): ''', }.iteritems(): morph = loader.load_from_string(text) - sources = morphlib.source.make_sources('repo', 'original/ref', - name, 'sha1', - 'tree', morph) + sources = morphlib.source.make_sources( + 'repo', 'original/ref', name, 'sha1', 'tree', morph, + default_split_rules=default_split_rules) for source in sources: self.source_pool.add(source) self.build_env = DummyBuildEnvironment({ diff --git a/morphlib/defaults.py b/morphlib/defaults.py new file mode 100644 index 00000000..9e695a90 --- /dev/null +++ b/morphlib/defaults.py @@ -0,0 +1,108 @@ +# Copyright (C) 2015 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +# +# =*= License: GPL-2 =*= + + +import cliapp +import jsonschema +import yaml + +import os + +import morphlib + + +class Defaults(object): + '''Represents predefined default values specific to Baserock definitions. + + The DEFAULTS file was added in definitions format version 7, which lets + users set these defaults. The text of DEFAULTS file can be passed in as + 'text', and will be validated and parsed if definitions_version >= 7. + + Prior to version 7, the defaults were hardcoded in Morph. These defaults + will be returned if definitions_version < 7. + + ''' + def __init__(self, definitions_version, text=None): + self._build_systems = {} + self._split_rules = {} + + schema_path = os.path.join(morphlib.util.schemas_directory(), + 'defaults.json-schema') + with open(schema_path) as f: + self.schema = yaml.load(f) + + if definitions_version >= 7: + if text: + self._build_systems, self._split_rules = self._parse(text) + else: + self._build_systems, self._split_rules = self._builtins() + + def _parse(self, text): + build_systems = {} + split_rules = {} + + # This reports errors against <string> rather than the actual filename, + # which is sad. + data = yaml.safe_load(text) + + if data is None: + # It's OK to be empty, I guess. + return build_systems, split_rules + + try: + # It would be nice if this could give line numbers when it spotted + # errors. Seems tricky. + jsonschema.validate(data, self.schema) + except jsonschema.ValidationError as e: + raise cliapp.AppException('Invalid DEFAULTS file: %s' % e.message) + + build_system_data = data.get('build-systems', {}) + for name, commands in build_system_data.items(): + build_system = morphlib.buildsystem.BuildSystem() + build_system.from_dict(name, commands) + build_systems[name] = build_system + + # It would make sense to create artifactsplitrule.SplitRule instances + # here, instead of an unlabelled data structure. That would need some + # changes to source.make_sources() and the 'artifactsplitrule' module. + split_rules_data = data.get('split-rules', {}) + for kind, rules in split_rules_data.items(): + split_rules[kind] = [] + for rule in rules: + rule_unlabelled = (rule['artifact'], rule['include']) + split_rules[kind].append(rule_unlabelled) + + return build_systems, split_rules + + def _builtins(self): + build_systems = {} + split_rules = {} + + for build_system in morphlib.buildsystem.build_systems: + build_systems[build_system.name] = build_system + + split_rules['chunk'] = \ + morphlib.artifactsplitrule.DEFAULT_CHUNK_RULES + split_rules['stratum'] = \ + morphlib.artifactsplitrule.DEFAULT_STRATUM_RULES + + return build_systems, split_rules + + def build_systems(self): + return self._build_systems + + def split_rules(self): + return self._split_rules diff --git a/morphlib/definitions_repo.py b/morphlib/definitions_repo.py index 301e5b86..6d14fcbb 100644 --- a/morphlib/definitions_repo.py +++ b/morphlib/definitions_repo.py @@ -23,6 +23,7 @@ import logging import os import urlparse import uuid +import warnings import morphlib import gitdir @@ -236,9 +237,25 @@ class DefinitionsRepo(gitdir.GitDirectory): mf = morphlib.morphologyfinder.MorphologyFinder(self) version_text = mf.read_file('VERSION') - morphlib.definitions_version.check_version_file(version_text) + version = morphlib.definitions_version.check_version_file(version_text) - loader = morphlib.morphloader.MorphologyLoader() + defaults_text = mf.read_file('DEFAULTS', allow_missing=True) + + if version < 7: + if defaults_text is not None: + warnings.warn( + "Ignoring DEFAULTS file, because these definitions are " + "version %i" % version) + defaults_text = None + else: + if defaults_text is None: + warnings.warn("No DEFAULTS file found.") + + defaults = morphlib.defaults.Defaults(version, + text=defaults_text) + + loader = morphlib.morphloader.MorphologyLoader( + predefined_build_systems=defaults.build_systems()) return loader diff --git a/morphlib/definitions_version.py b/morphlib/definitions_version.py index bc72d2ad..2fb7785a 100644 --- a/morphlib/definitions_version.py +++ b/morphlib/definitions_version.py @@ -24,7 +24,7 @@ import yaml import morphlib -SUPPORTED_VERSIONS = [6] +SUPPORTED_VERSIONS = [6, 7] class DefinitionsVersionError(cliapp.AppException): diff --git a/morphlib/morphloader.py b/morphlib/morphloader.py index f85c5d4d..ba48b778 100644 --- a/morphlib/morphloader.py +++ b/morphlib/morphloader.py @@ -109,6 +109,13 @@ class UnknownArchitectureError(MorphologyValidationError): % (arch, morph_filename)) +class UnknownBuildSystemError(MorphologyValidationError): + + def __init__(self, build_system, morph_filename): + self.msg = ('Undefined build system %s in morphology %s' + % (build_system, morph_filename)) + + class NoStratumBuildDependenciesError(MorphologyValidationError): def __init__(self, stratum_name, morph_filename): @@ -404,8 +411,12 @@ class MorphologyLoader(object): } def __init__(self, - lookup_build_system=morphlib.buildsystem.lookup_build_system): - self._lookup_build_system = lookup_build_system + predefined_build_systems={}): + self._predefined_build_systems = predefined_build_systems.copy() + + if 'manual' not in self._predefined_build_systems: + self._predefined_build_systems['manual'] = \ + morphlib.buildsystem.ManualBuildSystem() def parse_morphology_text(self, text, morph_filename): '''Parse a textual morphology. @@ -811,10 +822,12 @@ class MorphologyLoader(object): if morph['max-jobs'] is not None: morph['max-jobs'] = int(morph['max-jobs']) - def _unset_chunk_defaults(self, morph): # pragma: no cover + def _unset_chunk_defaults(self, morph): # pragma: no cover + # This is only used by the deprecated branch-and-merge plugin, and + # probably doesn't work correctly for definitions V7 and newer. default_bs = self._static_defaults['chunk']['build-system'] - bs = self._lookup_build_system( - morph.get('build-system', default_bs)) + bs_name = morph.get('build-system', default_bs) + bs = self.lookup_build_system(bs_name) for key in self._static_defaults['chunk']: if key not in morph: continue if 'commands' not in key: continue @@ -823,11 +836,19 @@ class MorphologyLoader(object): if morph[key] == default_value: del morph[key] + def lookup_build_system(self, name): + return self._predefined_build_systems[name] + def set_commands(self, morph): - default = self._static_defaults['chunk']['build-system'] - bs = self._lookup_build_system( - morph.get('build-system', default)) if morph['kind'] == 'chunk': + default = self._static_defaults['chunk']['build-system'] + bs_name = morph.get('build-system', default) + + try: + bs = self.lookup_build_system(bs_name) + except KeyError: + raise UnknownBuildSystemError(bs_name, morph['name']) + for key in self._static_defaults['chunk']: if 'commands' not in key: continue if key not in morph: diff --git a/morphlib/morphloader_tests.py b/morphlib/morphloader_tests.py index 6117573e..392b0cbc 100644 --- a/morphlib/morphloader_tests.py +++ b/morphlib/morphloader_tests.py @@ -60,12 +60,12 @@ class MorphologyLoaderTests(unittest.TestCase): string = '''\ name: foo kind: chunk -build-system: dummy +build-system: manual ''' morph = self.loader.parse_morphology_text(string, 'test') self.assertEqual(morph['kind'], 'chunk') self.assertEqual(morph['name'], 'foo') - self.assertEqual(morph['build-system'], 'dummy') + self.assertEqual(morph['build-system'], 'manual') def test_fails_to_parse_utter_garbage(self): self.assertRaises( @@ -494,43 +494,43 @@ chunks: string = '''\ name: foo kind: chunk -build-system: dummy +build-system: manual ''' morph = self.loader.load_from_string(string) self.assertEqual(morph['kind'], 'chunk') self.assertEqual(morph['name'], 'foo') - self.assertEqual(morph['build-system'], 'dummy') + self.assertEqual(morph['build-system'], 'manual') def test_loads_json_from_string(self): string = '''\ { "name": "foo", "kind": "chunk", - "build-system": "dummy" + "build-system": "manual" } ''' morph = self.loader.load_from_string(string) self.assertEqual(morph['kind'], 'chunk') self.assertEqual(morph['name'], 'foo') - self.assertEqual(morph['build-system'], 'dummy') + self.assertEqual(morph['build-system'], 'manual') def test_loads_from_file(self): with open(self.filename, 'w') as f: f.write('''\ name: foo kind: chunk -build-system: dummy +build-system: manual ''') morph = self.loader.load_from_file(self.filename) self.assertEqual(morph['kind'], 'chunk') self.assertEqual(morph['name'], 'foo') - self.assertEqual(morph['build-system'], 'dummy') + self.assertEqual(morph['build-system'], 'manual') def test_saves_to_string(self): morph = morphlib.morphology.Morphology({ 'name': 'foo', 'kind': 'chunk', - 'build-system': 'dummy', + 'build-system': 'manual', }) text = self.loader.save_to_string(morph) @@ -539,14 +539,14 @@ build-system: dummy self.assertEqual(text, '''\ name: foo kind: chunk -build-system: dummy +build-system: manual ''') def test_saves_to_file(self): morph = morphlib.morphology.Morphology({ 'name': 'foo', 'kind': 'chunk', - 'build-system': 'dummy', + 'build-system': 'manual', }) self.loader.save_to_file(self.filename, morph) @@ -558,7 +558,7 @@ build-system: dummy self.assertEqual(text, '''\ name: foo kind: chunk -build-system: dummy +build-system: manual ''') def test_validate_does_not_set_defaults(self): @@ -966,11 +966,12 @@ build-system: dummy ) s = self.loader.save_to_string(m) - def test_smoketest_strip_commands(self): - dummy_buildsystem = morphlib.buildsystem.DummyBuildSystem() - loader = morphlib.morphloader.MorphologyLoader( - lookup_build_system=lambda x: dummy_buildsystem) - m = morphlib.morphology.Morphology( - {'name': 'test', 'kind': 'chunk', 'build-system': 'dummy'}) - loader.set_commands(m) - self.assertEqual(m['strip-commands'], dummy_buildsystem.strip_commands) + + def test_unknown_build_system(self): + m = morphlib.morphology.Morphology({ + 'kind': 'chunk', + 'name': 'foo', + 'build-system': 'monkey scientist', + }) + with self.assertRaises(morphlib.morphloader.UnknownBuildSystemError): + s = self.loader.set_commands(m) diff --git a/morphlib/morphologyfinder.py b/morphlib/morphologyfinder.py index 9184c160..a334350e 100644 --- a/morphlib/morphologyfinder.py +++ b/morphlib/morphologyfinder.py @@ -15,9 +15,7 @@ # =*= License: GPL-2 =*= -import cliapp - -import morphlib +import errno class MorphologyFinder(object): @@ -33,9 +31,15 @@ class MorphologyFinder(object): self.gitdir = gitdir self.ref = ref - def read_file(self, filename): + def read_file(self, filename, allow_missing=False): '''Return the text of a file inside the Git repo.''' - return self.gitdir.read_file(filename, self.ref) + try: + return self.gitdir.read_file(filename, self.ref) + except IOError as e: + if allow_missing and e.errno == errno.ENOENT: + return None + else: + raise def list_morphologies(self): '''Return the filenames of all morphologies in the (repo, ref). diff --git a/morphlib/morphologyfinder_tests.py b/morphlib/morphologyfinder_tests.py index 59772e23..3312b627 100644 --- a/morphlib/morphologyfinder_tests.py +++ b/morphlib/morphologyfinder_tests.py @@ -103,6 +103,10 @@ class MorphologyFinderTests(unittest.TestCase): mf = morphlib.morphologyfinder.MorphologyFinder(gd) self.assertEqual(mf.read_file('foo.morph'), "altered morphology text") + with self.assertRaises(IOError): + mf.read_file('missing') + self.assertEqual(mf.read_file('missing', allow_missing=True), + None) def test_read_morph_raises_no_worktree_no_ref(self): gd = morphlib.gitdir.GitDirectory(self.mirror) diff --git a/morphlib/schemas/defaults.json-schema b/morphlib/schemas/defaults.json-schema new file mode 100644 index 00000000..2f713425 --- /dev/null +++ b/morphlib/schemas/defaults.json-schema @@ -0,0 +1,66 @@ +$schema: http://json-schema.org/draft-04/schema# +id: http://git.baserock.org/cgi-bin/cgit.cgi/baserock/baserock/definitions.git/tree/schemas/defaults.json-schema + +description: | + This is a JSON-Schema description of the DEFAULTS file specified in the + Baserock definitions format. DEFAULTS is a YAML file that contains global + defaults for a set of Baserock definitions. + + This JSON-Schema file is valid for VERSION 7 of the Baserock definitions + YAML serialisation format. + + The Baserock definitions YAML serialisation format is the recommended way of + representing Baserock definitions on disk. The actual data model is described + separately. See <https://wiki.baserock.org/definitions> for more information. + + This schema is represented as YAML, so that it can be edited more easily. + You may need to convert to JSON if using a JSON-Schema tool that expects + its input to be an actual string containing data serialised as JSON. + +definitions: + command-sequence: + type: array + items: {type: string} + + build-system: + type: object + additionalProperties: false + properties: + build-commands: {$ref: '#/definitions/command-sequence'} + configure-commands: {$ref: '#/definitions/command-sequence'} + install-commands: {$ref: '#/definitions/command-sequence'} + strip-commands: {$ref: '#/definitions/command-sequence'} + + split-rules: + type: array + items: + type: object + + required: [artifact, include] + additionalProperties: false + + properties: + artifact: {type: string} + include: + type: array + items: + type: string + format: regex + +type: object +additionalProperties: false + +properties: + # Predefined build systems. + build-systems: + type: object + patternProperties: + ^.*$: {$ref: '#/definitions/build-system'} + + # Predefined artifact splitting rules. + split-rules: + type: object + additionalProperties: false + properties: + chunk: {$ref: '#/definitions/split-rules'} + stratum: {$ref: '#/definitions/split-rules'} diff --git a/morphlib/source.py b/morphlib/source.py index 2c96bcbd..135c14cc 100644 --- a/morphlib/source.py +++ b/morphlib/source.py @@ -77,12 +77,14 @@ class Source(object): return artifact in self.dependencies -def make_sources(reponame, ref, filename, absref, tree, morphology): +def make_sources(reponame, ref, filename, absref, tree, morphology, + default_split_rules={}): kind = morphology['kind'] if kind in ('system', 'chunk'): unifier = getattr(morphlib.artifactsplitrule, 'unify_%s_matches' % kind) - split_rules = unifier(morphology) + split_rules = unifier(morphology, + default_rules=default_split_rules.get(kind, {})) # chunk and system sources are named after the morphology source_name = morphology['name'] source = morphlib.source.Source(source_name, reponame, ref, @@ -93,7 +95,8 @@ def make_sources(reponame, ref, filename, absref, tree, morphology): yield source elif kind == 'stratum': # pragma: no cover unifier = morphlib.artifactsplitrule.unify_stratum_matches - split_rules = unifier(morphology) + split_rules = unifier(morphology, + default_rules=default_split_rules.get(kind, {})) for name in split_rules.artifacts: source = morphlib.source.Source( name, # stratum source name is artifact name diff --git a/morphlib/sourceresolver.py b/morphlib/sourceresolver.py index 0b32598f..1056af16 100644 --- a/morphlib/sourceresolver.py +++ b/morphlib/sourceresolver.py @@ -19,7 +19,7 @@ import cPickle import logging import os import pylru -import yaml +import warnings import cliapp @@ -259,6 +259,36 @@ class SourceResolver(object): return morphlib.definitions_version.check_version_file(version_text) + def _get_defaults(self, definitions_checkout_dir, + definitions_version=7): # pragma: no cover + '''Return the default build system commands, and default split rules. + + This function returns a tuple with two dicts. + + The defaults are read from a file named DEFAULTS in the definitions + directory, if the definitions follow format version 7 or later. If the + definitions follow version 6 or earlier, hardcoded defaults are used. + + ''' + # Read default build systems and split rules from DEFAULTS file. + defaults_text = self._get_file_contents_from_definitions( + definitions_checkout_dir, 'DEFAULTS') + + if definitions_version < 7: + if defaults_text is not None: + warnings.warn( + "Ignoring DEFAULTS file, because these definitions are " + "version %i" % definitions_version) + defaults_text = None + else: + if defaults_text is None: + warnings.warn("No DEFAULTS file found.") + + defaults = morphlib.defaults.Defaults(definitions_version, + text=defaults_text) + + return defaults.build_systems(), defaults.split_rules() + def _get_morphology(self, resolved_morphologies, definitions_checkout_dir, morph_loader, filename): '''Read the morphology at the specified location. @@ -287,7 +317,8 @@ class SourceResolver(object): definitions_tree, morph_loader, system_filenames, - visit): + visit, + predefined_split_rules): definitions_queue = collections.deque(system_filenames) chunk_queue = set() @@ -304,7 +335,8 @@ class SourceResolver(object): raise MorphologyNotFoundError(filename) visit(definitions_repo, definitions_ref, filename, - definitions_absref, definitions_tree, morphology) + definitions_absref, definitions_tree, morphology, + predefined_split_rules) if morphology['kind'] == 'cluster': raise cliapp.AppException( @@ -349,8 +381,8 @@ class SourceResolver(object): def process_chunk(self, resolved_morphologies, resolved_trees, definitions_checkout_dir, morph_loader, chunk_repo, - chunk_ref, filename, chunk_buildsystem, - visit): + chunk_ref, filename, chunk_buildsystem, visit, + predefined_split_rules): absref, tree = self._resolve_ref(resolved_trees, chunk_repo, chunk_ref) if chunk_buildsystem is None: @@ -370,7 +402,8 @@ class SourceResolver(object): morphology = self._create_morphology_for_build_system( morph_loader, buildsystem, filename) - visit(chunk_repo, chunk_ref, filename, absref, tree, morphology) + visit(chunk_repo, chunk_ref, filename, absref, tree, morphology, + predefined_split_rules) def traverse_morphs(self, definitions_repo, definitions_ref, system_filenames, @@ -400,7 +433,13 @@ class SourceResolver(object): definitions_version = self._check_version_file( definitions_checkout_dir) - morph_loader = morphlib.morphloader.MorphologyLoader() + + predefined_build_systems, predefined_split_rules = \ + self._get_defaults( + definitions_checkout_dir, definitions_version) + + morph_loader = morphlib.morphloader.MorphologyLoader( + predefined_build_systems=predefined_build_systems) # First, process the system and its stratum morphologies. These # will all live in the same Git repository, and will point to @@ -408,14 +447,15 @@ class SourceResolver(object): chunk_queue = self._process_definitions_with_children( resolved_morphologies, definitions_checkout_dir, definitions_repo, definitions_ref, definitions_absref, - definitions_tree, morph_loader, - system_filenames, visit) + definitions_tree, morph_loader, system_filenames, visit, + predefined_split_rules) # Now process all the chunks involved in the build. for repo, ref, filename, buildsystem in chunk_queue: self.process_chunk(resolved_morphologies, resolved_trees, definitions_checkout_dir, morph_loader, - repo, ref, filename, buildsystem, visit) + repo, ref, filename, buildsystem, visit, + predefined_split_rules) def create_source_pool(lrc, rrc, repo, ref, filenames, cachedir, @@ -437,10 +477,11 @@ def create_source_pool(lrc, rrc, repo, ref, filenames, cachedir, ''' pool = morphlib.sourcepool.SourcePool() - def add_to_pool(reponame, ref, filename, absref, tree, morphology): - sources = morphlib.source.make_sources(reponame, ref, - filename, absref, - tree, morphology) + def add_to_pool(reponame, ref, filename, absref, tree, morphology, + predefined_split_rules): + sources = morphlib.source.make_sources( + reponame, ref, filename, absref, tree, morphology, + predefined_split_rules) for source in sources: pool.add(source) diff --git a/morphlib/util.py b/morphlib/util.py index 284fe305..e34799df 100644 --- a/morphlib/util.py +++ b/morphlib/util.py @@ -734,6 +734,7 @@ def temp_dir(*args, **kwargs): #pragma: no cover if cleanup_on_success: shutil.rmtree(td, ignore_errors=True) + def copyfileobj(fsrc, fdst, length=16*1024, callback=lambda x: None): #pragma: no cover ''' This is similar to shutil.copyfileobj @@ -775,3 +776,9 @@ class ProgressBar(object): progress, self._expected_size, self._unit) sys.stderr.write(s) sys.stderr.flush() + + +def schemas_directory(): # pragma: no cover + '''Returns a path to the schemas/ subdirectory of the 'morphlib' module.''' + code_dir = os.path.dirname(morphlib.__file__) + return os.path.join(code_dir, 'schemas') diff --git a/tests.branching/branch-creates-new-system-branch-not-from-master.stdout b/tests.branching/branch-creates-new-system-branch-not-from-master.stdout index 78b3ca33..c5682475 100644 --- a/tests.branching/branch-creates-new-system-branch-not-from-master.stdout +++ b/tests.branching/branch-creates-new-system-branch-not-from-master.stdout @@ -7,6 +7,7 @@ d ./newbranch/test d ./newbranch/test/morphs d ./newbranch/test/morphs/.git f ./newbranch/.morph-system-branch/config +f ./newbranch/test/morphs/DEFAULTS f ./newbranch/test/morphs/VERSION f ./newbranch/test/morphs/hello-stratum.morph f ./newbranch/test/morphs/hello-system.morph diff --git a/tests.branching/branch-works-anywhere.stdout b/tests.branching/branch-works-anywhere.stdout index 4e6c3e27..e3c7bfb2 100644 --- a/tests.branching/branch-works-anywhere.stdout +++ b/tests.branching/branch-works-anywhere.stdout @@ -7,6 +7,7 @@ d ./branch1/test d ./branch1/test/morphs d ./branch1/test/morphs/.git f ./branch1/.morph-system-branch/config +f ./branch1/test/morphs/DEFAULTS f ./branch1/test/morphs/VERSION f ./branch1/test/morphs/hello-stratum.morph f ./branch1/test/morphs/hello-system.morph @@ -24,10 +25,12 @@ d ./branch2/test d ./branch2/test/morphs d ./branch2/test/morphs/.git f ./branch1/.morph-system-branch/config +f ./branch1/test/morphs/DEFAULTS f ./branch1/test/morphs/VERSION f ./branch1/test/morphs/hello-stratum.morph f ./branch1/test/morphs/hello-system.morph f ./branch2/.morph-system-branch/config +f ./branch2/test/morphs/DEFAULTS f ./branch2/test/morphs/VERSION f ./branch2/test/morphs/hello-stratum.morph f ./branch2/test/morphs/hello-system.morph @@ -50,14 +53,17 @@ d ./branch3/test d ./branch3/test/morphs d ./branch3/test/morphs/.git f ./branch1/.morph-system-branch/config +f ./branch1/test/morphs/DEFAULTS f ./branch1/test/morphs/VERSION f ./branch1/test/morphs/hello-stratum.morph f ./branch1/test/morphs/hello-system.morph f ./branch2/.morph-system-branch/config +f ./branch2/test/morphs/DEFAULTS f ./branch2/test/morphs/VERSION f ./branch2/test/morphs/hello-stratum.morph f ./branch2/test/morphs/hello-system.morph f ./branch3/.morph-system-branch/config +f ./branch3/test/morphs/DEFAULTS f ./branch3/test/morphs/VERSION f ./branch3/test/morphs/hello-stratum.morph f ./branch3/test/morphs/hello-system.morph @@ -85,18 +91,22 @@ d ./branch4/test d ./branch4/test/morphs d ./branch4/test/morphs/.git f ./branch1/.morph-system-branch/config +f ./branch1/test/morphs/DEFAULTS f ./branch1/test/morphs/VERSION f ./branch1/test/morphs/hello-stratum.morph f ./branch1/test/morphs/hello-system.morph f ./branch2/.morph-system-branch/config +f ./branch2/test/morphs/DEFAULTS f ./branch2/test/morphs/VERSION f ./branch2/test/morphs/hello-stratum.morph f ./branch2/test/morphs/hello-system.morph f ./branch3/.morph-system-branch/config +f ./branch3/test/morphs/DEFAULTS f ./branch3/test/morphs/VERSION f ./branch3/test/morphs/hello-stratum.morph f ./branch3/test/morphs/hello-system.morph f ./branch4/.morph-system-branch/config +f ./branch4/test/morphs/DEFAULTS f ./branch4/test/morphs/VERSION f ./branch4/test/morphs/hello-stratum.morph f ./branch4/test/morphs/hello-system.morph diff --git a/tests.branching/checkout-works-anywhere.stdout b/tests.branching/checkout-works-anywhere.stdout index d7f6903a..737a605e 100644 --- a/tests.branching/checkout-works-anywhere.stdout +++ b/tests.branching/checkout-works-anywhere.stdout @@ -7,6 +7,7 @@ d ./master/test d ./master/test/morphs d ./master/test/morphs/.git f ./master/.morph-system-branch/config +f ./master/test/morphs/DEFAULTS f ./master/test/morphs/VERSION f ./master/test/morphs/hello-stratum.morph f ./master/test/morphs/hello-system.morph @@ -24,10 +25,12 @@ d ./newbranch/test d ./newbranch/test/morphs d ./newbranch/test/morphs/.git f ./master/.morph-system-branch/config +f ./master/test/morphs/DEFAULTS f ./master/test/morphs/VERSION f ./master/test/morphs/hello-stratum.morph f ./master/test/morphs/hello-system.morph f ./newbranch/.morph-system-branch/config +f ./newbranch/test/morphs/DEFAULTS f ./newbranch/test/morphs/VERSION f ./newbranch/test/morphs/hello-stratum.morph f ./newbranch/test/morphs/hello-system.morph diff --git a/tests.branching/setup b/tests.branching/setup index 816d5830..c10b4fee 100755 --- a/tests.branching/setup +++ b/tests.branching/setup @@ -47,7 +47,10 @@ mkdir "$DATADIR/morphs" ## Create a link to this repo that has a .git suffix ln -s "$DATADIR/morphs" "$DATADIR/morphs.git" -echo 'version: 6' > "$DATADIR/morphs/VERSION" +echo 'version: 7' > "$DATADIR/morphs/VERSION" + +# We don't build anything, so empty DEFAULTS file is fine. +echo '' > "$DATADIR/morphs/DEFAULTS" cat <<EOF > "$DATADIR/morphs/hello-system.morph" name: hello-system diff --git a/tests.build/ambiguous-refs.script b/tests.build/ambiguous-refs.script index 181af3cc..84fca86e 100755 --- a/tests.build/ambiguous-refs.script +++ b/tests.build/ambiguous-refs.script @@ -19,6 +19,9 @@ ## instead of 'git rev-parse --verify': show-ref returns a list of partial ## matches sorted alphabetically, so any code using it may resolve refs +## The 'autotools' build system is defined in the DEFAULTS file created +## by the 'setup' script. + set -eu # Create a ref that will show up in 'git show-ref' before the real master ref diff --git a/tests.build/build-stratum-with-submodules.script b/tests.build/build-stratum-with-submodules.script index 2dd2b924..b3073af1 100755 --- a/tests.build/build-stratum-with-submodules.script +++ b/tests.build/build-stratum-with-submodules.script @@ -24,6 +24,7 @@ set -eu parent="$DATADIR/parent-repo" mkdir "$parent" + echo "No real content here" > "$parent/dummy" "$SRCDIR/scripts/run-git-in" "$parent" init --quiet @@ -49,6 +50,7 @@ name: hello-stratum kind: stratum chunks: - name: parent + morph: parent.morph repo: test:parent-repo ref: master morph: parent.morph diff --git a/tests.build/prefix.script b/tests.build/prefix.script index 291b25cf..0ff077b8 100755 --- a/tests.build/prefix.script +++ b/tests.build/prefix.script @@ -49,6 +49,7 @@ name: hello-stratum kind: stratum chunks: - name: xyzzy + morph: xyzzy.morph repo: test:chunk-repo ref: master morph: xyzzy.morph @@ -56,6 +57,7 @@ chunks: build-mode: test prefix: /plover - name: plugh + morph: plugh.morph repo: test:chunk-repo ref: master morph: plugh.morph @@ -70,6 +72,6 @@ git commit -q -m "Update build definitions" test:morphs-repo master hello-system cd "$DATADIR/cache/artifacts" -first_chunk=$(ls -1 *.chunk.xyzzy-* | head -n1 | cut -c -64) -second_chunk=$(ls -1 *.chunk.plugh-* | head -n1 | cut -c -64) +first_chunk=$(ls -1 *.chunk.xyzzy* | head -n1 | cut -c -64) +second_chunk=$(ls -1 *.chunk.plugh* | head -n1 | cut -c -64) cat $first_chunk.build-log $second_chunk.build-log diff --git a/tests.build/rebuild-cached-stratum.script b/tests.build/rebuild-cached-stratum.script index e2e0face..bc1b83fd 100755 --- a/tests.build/rebuild-cached-stratum.script +++ b/tests.build/rebuild-cached-stratum.script @@ -41,7 +41,7 @@ cache="$DATADIR/cache/artifacts" "$SRCDIR/scripts/test-morph" build-morphology \ test:morphs-repo rebuild-cached-stratum hello-system echo "first build:" -(cd "$cache" && ls *.chunk.* *hello-stratum-* | sed 's/^[^.]*\./ /' | +(cd "$cache" && ls *.chunk.* *hello-stratum* | sed 's/^[^.]*\./ /' | LC_ALL=C sort -u) # Change the chunk. @@ -53,6 +53,6 @@ echo "first build:" "$SRCDIR/scripts/test-morph" build-morphology \ test:morphs-repo rebuild-cached-stratum hello-system echo "second build:" -(cd "$cache" && ls *.chunk.* *hello-stratum-* | sed 's/^[^.]*\./ /' | +(cd "$cache" && ls *.chunk.* *hello-stratum* | sed 's/^[^.]*\./ /' | LC_ALL=C sort -u) diff --git a/tests.build/rebuild-cached-stratum.stdout b/tests.build/rebuild-cached-stratum.stdout index 9c53ee60..4672e7f2 100644 --- a/tests.build/rebuild-cached-stratum.stdout +++ b/tests.build/rebuild-cached-stratum.stdout @@ -1,22 +1,8 @@ first build: - chunk.hello-bins - chunk.hello-devel - chunk.hello-doc - chunk.hello-libs - chunk.hello-locale - chunk.hello-misc - stratum.hello-stratum-devel - stratum.hello-stratum-devel.meta - stratum.hello-stratum-runtime - stratum.hello-stratum-runtime.meta + chunk.hello-chunk + stratum.hello-stratum + stratum.hello-stratum.meta second build: - chunk.hello-bins - chunk.hello-devel - chunk.hello-doc - chunk.hello-libs - chunk.hello-locale - chunk.hello-misc - stratum.hello-stratum-devel - stratum.hello-stratum-devel.meta - stratum.hello-stratum-runtime - stratum.hello-stratum-runtime.meta + chunk.hello-chunk + stratum.hello-stratum + stratum.hello-stratum.meta diff --git a/tests.build/setup b/tests.build/setup index 7936584b..ca60d426 100755 --- a/tests.build/setup +++ b/tests.build/setup @@ -55,8 +55,7 @@ int main(void) EOF git add hello.c -git commit --quiet -m "add a hello world program and morph" - +git commit --quiet -m "add a hello world program" git checkout --quiet master @@ -68,11 +67,28 @@ mkdir "$morphsrepo" cd "$morphsrepo" git init --quiet -echo 'version: 6' > VERSION +echo 'version: 7' > VERSION git add VERSION +cat <<'EOF' > DEFAULTS +# This is a deliberately minimal DEFAULTS file. + +# There are no splitting rules defined, because it's important that Morph +# still works correctly when the user didn't define any. + +build-systems: + autotools: + configure-commands: + - ./configure + build-commands: + - make + install-commands: + - make DESTDIR="$DESTDIR" install +EOF +git add DEFAULTS + cat <<EOF > hello.morph -name: hello +name: hello-chunk kind: chunk build-commands: - gcc -o hello hello.c @@ -88,6 +104,7 @@ name: hello-stratum kind: stratum chunks: - name: hello + morph: hello.morph repo: test:chunk-repo ref: farrokh morph: hello.morph diff --git a/tests.build/setup-build-essential b/tests.build/setup-build-essential index 8868db68..28847d76 100755 --- a/tests.build/setup-build-essential +++ b/tests.build/setup-build-essential @@ -17,6 +17,7 @@ # Set up a stratum which resembles Baserock's 'build-essential' slightly. Used # for testing 'morph cross-bootstrap' and the 'bootstrap' build mode. +# Add a mock compiler chunk. mkdir -p "$DATADIR/cc-repo" cd "$DATADIR/cc-repo" @@ -32,9 +33,6 @@ git commit -q -m "Create compiler chunk" cd "$DATADIR/morphs-repo" -# Require 'cc' in hello-chunk. We should have the second version available -# but *not* the first one. - cat <<EOF > "stage1-cc.morph" name: stage1-cc kind: chunk @@ -53,6 +51,11 @@ install-commands: - install -m 755 morph-test-cc "\$DESTDIR\$PREFIX/bin/morph-test-cc" EOF +git add cc.morph stage1-cc.morph +git commit -q -m "Add build instructions for mock compiler." + +# Require 'cc' in hello-chunk. We should have the second version available +# but *not* the first one. cat <<EOF > "hello.morph" name: hello kind: chunk @@ -76,6 +79,7 @@ name: build-essential kind: stratum chunks: - name: stage1-cc + morph: stage1-cc.morph repo: test:cc-repo ref: master morph: stage1-cc.morph @@ -83,6 +87,7 @@ chunks: build-mode: bootstrap prefix: /tools - name: cc + morph: cc.morph repo: test:cc-repo ref: master morph: cc.morph @@ -98,6 +103,7 @@ build-depends: - morph: build-essential chunks: - name: hello + morph: hello.morph repo: test:chunk-repo ref: farrokh morph: hello.morph diff --git a/without-test-modules b/without-test-modules index f42f0f94..77803473 100644 --- a/without-test-modules +++ b/without-test-modules @@ -62,3 +62,4 @@ distbuild/worker_build_scheduler.py morphlib/buildbranch.py morphlib/definitions_repo.py morphlib/sourceresolver.py +morphlib/defaults.py diff --git a/yarns/implementations.yarn b/yarns/implementations.yarn index ff1971f8..9f23107b 100644 --- a/yarns/implementations.yarn +++ b/yarns/implementations.yarn @@ -250,7 +250,37 @@ another to hold a chunk. mkdir "$DATADIR/gits/morphs" cd "$DATADIR/gits/morphs" git init . - echo 'version: 6' > VERSION + echo 'version: 7' > VERSION + + install -m644 -D /dev/stdin << EOF "DEFAULTS" + # This is a simplified version of the DEFAULTS file supplied with the + # Baserock reference system definitions. + build-systems: + autotools: + configure-commands: + - ./configure + build-commands: + - make + install-commands: + - make install + split-rules: + chunk: + - artifact: -devel + include: + - (usr/)?include/.* + - (usr/)?lib/.*\.a + - (usr/)?share/man/.* + - artifact: -runtime + include: + - .* + stratum: + - artifact: -devel + include: + - .*-devel + - artifact: -runtime + include: + - .*-runtime + EOF arch=$(run_morph print-architecture) install -m644 -D /dev/stdin << EOF "systems/test-system.morph" @@ -333,7 +363,7 @@ another to hold a chunk. install-commands: - copy files system-integration: - test-chunk-bins: + test-chunk-runtime: 00-passwd: - | create file /etc/passwd diff --git a/yarns/regression.yarn b/yarns/regression.yarn index c424f437..aa98eec5 100644 --- a/yarns/regression.yarn +++ b/yarns/regression.yarn @@ -59,17 +59,17 @@ source it depended on. GIVEN a workspace AND a git server AND system systems/test-system.morph uses core-runtime from core - AND stratum strata/core.morph has match rules: [{artifact: core-runtime, include: [.*-(bins|libs|locale)]}, {artifact: core-devel, include: [.*-(devel|doc|misc)]}] + AND stratum strata/core.morph has match rules: [{artifact: core-runtime, include: [.*-devel]}, {artifact: core-devel, include: [.*-runtime]}] WHEN the user checks out the system branch called master GIVEN a cluster called test-cluster.morph in system branch master AND a system in cluster test-cluster.morph in branch master called test-system AND system test-system in cluster test-cluster.morph in branch master builds systems/test-system.morph AND system test-system in cluster test-cluster.morph in branch master has deployment type: tar WHEN the user builds the system systems/test-system.morph in branch master - GIVEN stratum strata/core.morph in system branch master has match rules: [{artifact: core-runtime, include: [.*-(bins|libs|misc)]}, {artifact: core-devel, include: [.*-(devel|doc|locale)]}] + GIVEN stratum strata/core.morph in system branch master has match rules: [{artifact: core-runtime, include: [.*-runtime]}, {artifact: core-devel, include: [.*-devel]}] WHEN the user builds the system systems/test-system.morph in branch master AND the user deploys the cluster test-cluster.morph in branch master with options test-system.location="$DATADIR/test.tar" - THEN tarball test.tar contains baserock/test-chunk-misc.meta + THEN tarball test.tar contains baserock/test-chunk-runtime.meta FINALLY the git server is shut down |