diff options
55 files changed, 3058 insertions, 930 deletions
diff --git a/morphlib/__init__.py b/morphlib/__init__.py index 67fb944d..33773791 100644 --- a/morphlib/__init__.py +++ b/morphlib/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -48,6 +48,7 @@ class Error(cliapp.AppException): import artifact import artifactcachereference import artifactresolver +import artifactsplitrule import branchmanager import bins import buildbranch diff --git a/morphlib/artifact_tests.py b/morphlib/artifact_tests.py index 8edbbde2..d4b15cba 100644 --- a/morphlib/artifact_tests.py +++ b/morphlib/artifact_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -26,7 +26,7 @@ class ArtifactTests(unittest.TestCase): morph = morphlib.morph2.Morphology( ''' { - "chunk": "chunk", + "name": "chunk", "kind": "chunk", "chunks": { "chunk-runtime": [ diff --git a/morphlib/artifactresolver.py b/morphlib/artifactresolver.py index 17f038a2..ae0cfcf5 100644 --- a/morphlib/artifactresolver.py +++ b/morphlib/artifactresolver.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -16,6 +16,7 @@ import cliapp import collections +import logging import morphlib @@ -29,35 +30,21 @@ class MutualDependencyError(cliapp.AppException): class DependencyOrderError(cliapp.AppException): - def __init__(self, stratum, chunk, dependency_name): + def __init__(self, stratum_source, chunk, dependency_name): cliapp.AppException.__init__( self, 'In stratum %s, chunk %s references its dependency %s ' 'before it is defined' % - (stratum.source, chunk, dependency_name)) + (stratum_source, chunk, dependency_name)) class DependencyFormatError(cliapp.AppException): - def __init__(self, stratum, chunk): + def __init__(self, stratum_source, chunk): cliapp.AppException.__init__( self, 'In stratum %s, chunk %s uses an invalid ' - 'build-depends format' % (stratum.source, chunk)) + 'build-depends format' % (stratum_source, chunk)) -class UndefinedChunkArtifactError(cliapp.AppException): - - '''Exception raised when non-existent artifacts are referenced. - - Usually, this will only occur when a stratum refers to a chunk - artifact that is not defined in a chunk. - - ''' - - def __init__(self, parent, reference): - cliapp.AppException.__init__( - self, 'Undefined chunk artifact "%s" referenced in ' - 'stratum %s' % (reference, parent)) - class ArtifactResolver(object): @@ -71,13 +58,11 @@ class ArtifactResolver(object): ''' def __init__(self): - self._cached_artifacts = None self._added_artifacts = None self._source_pool = None def resolve_artifacts(self, source_pool): self._source_pool = source_pool - self._cached_artifacts = {} self._added_artifacts = set() artifacts = self._resolve_artifacts_recursively() @@ -93,12 +78,13 @@ class ArtifactResolver(object): source = queue.popleft() if source.morphology['kind'] == 'system': - systems = [self._get_artifact(source, a) - for a in source.morphology.builds_artifacts] + systems = [source.artifacts[name] + for name in source.split_rules.artifacts] - if any(a not in self._added_artifacts for a in systems): - artifacts.extend(systems) - self._added_artifacts.update(systems) + for system in (s for s in systems + if s not in self._added_artifacts): + artifacts.append(system) + self._added_artifacts.add(system) resolved_artifacts = self._resolve_system_dependencies( systems, source, queue) @@ -108,28 +94,36 @@ class ArtifactResolver(object): artifacts.append(artifact) self._added_artifacts.add(artifact) elif source.morphology['kind'] == 'stratum': - assert len(source.morphology.builds_artifacts) == 1 - artifact = self._get_artifact( - source, source.morphology.builds_artifacts[0]) - - if not artifact in self._added_artifacts: - artifacts.append(artifact) - self._added_artifacts.add(artifact) + strata = [source.artifacts[name] + for name in source.split_rules.artifacts] + + # If we were not given systems, return the strata here, + # rather than have the systems return them. + if not any(s.morphology['kind'] == 'system' + for s in self._source_pool): + for stratum in (s for s in strata + if s not in self._added_artifacts): + artifacts.append(stratum) + self._added_artifacts.add(stratum) resolved_artifacts = self._resolve_stratum_dependencies( - artifact, queue) + strata, source, queue) for artifact in resolved_artifacts: if not artifact in self._added_artifacts: artifacts.append(artifact) self._added_artifacts.add(artifact) elif source.morphology['kind'] == 'chunk': - names = source.morphology.builds_artifacts - for name in names: - artifact = self._get_artifact(source, name) - if not artifact in self._added_artifacts: - artifacts.append(artifact) - self._added_artifacts.add(artifact) + chunks = [source.artifacts[name] + for name in source.split_rules.artifacts] + # If we were only given chunks, return them here, rather than + # have the strata return them. + if not any(s.morphology['kind'] == 'stratum' + for s in self._source_pool): + for chunk in (c for c in chunks + if c not in self._added_artifacts): + artifacts.append(chunk) + self._added_artifacts.add(chunk) return artifacts @@ -141,15 +135,6 @@ class ArtifactResolver(object): if x.morphology['kind'] != 'chunk'] return collections.deque(sources) - def _get_artifact(self, source, name): - info = (source, name) - if info in self._cached_artifacts: - return self._cached_artifacts[info] - else: - artifact = morphlib.artifact.Artifact(info[0], info[1]) - self._cached_artifacts[info] = artifact - return artifact - def _resolve_system_dependencies(self, systems, source, queue): artifacts = [] @@ -158,67 +143,60 @@ class ArtifactResolver(object): info['repo'] or source.repo_name, info['ref'] or source.original_ref, '%s.morph' % info['morph']) + stratum_name = stratum_source.morphology['name'] - stratum_name = stratum_source.morphology.builds_artifacts[0] - stratum = self._get_artifact(stratum_source, stratum_name) - + matches, overlaps, unmatched = source.split_rules.partition( + ((stratum_name, sta_name) for sta_name + in stratum_source.split_rules.artifacts)) for system in systems: - system.add_dependency(stratum) - queue.append(stratum_source) + for (stratum_name, sta_name) in matches[system.name]: + stratum = stratum_source.artifacts[sta_name] + system.add_dependency(stratum) + artifacts.append(stratum) - artifacts.append(stratum) + queue.append(stratum_source) return artifacts - def _resolve_stratum_dependencies(self, stratum, queue): + def _resolve_stratum_dependencies(self, strata, source, queue): artifacts = [] - strata = [] + stratum_build_depends = [] - if stratum.source.morphology['build-depends']: - for stratum_info in stratum.source.morphology['build-depends']: - other_source = self._source_pool.lookup( - stratum_info['repo'] or stratum.source.repo_name, - stratum_info['ref'] or stratum.source.original_ref, - '%s.morph' % stratum_info['morph']) + for stratum_info in source.morphology.get('build-depends') or []: + other_source = self._source_pool.lookup( + stratum_info['repo'] or source.repo_name, + stratum_info['ref'] or source.original_ref, + '%s.morph' % stratum_info['morph']) - other_stratum = self._get_artifact( - other_source, other_source.morphology.builds_artifacts[0]) + # Make every stratum artifact this stratum source produces + # depend on every stratum artifact the other stratum source + # produces. + for sta_name in other_source.split_rules.artifacts: + other_stratum = other_source.artifacts[sta_name] - strata.append(other_stratum) + stratum_build_depends.append(other_stratum) artifacts.append(other_stratum) - if other_stratum.depends_on(stratum): - raise MutualDependencyError(stratum, other_stratum) + for stratum in strata: + if other_stratum.depends_on(stratum): + raise MutualDependencyError(stratum, other_stratum) + + stratum.add_dependency(other_stratum) - stratum.add_dependency(other_stratum) - queue.append(other_source) + queue.append(other_source) # 'name' here is the chunk artifact name - chunk_artifacts = [] - processed_artifacts = [] - name_to_processed_artifact = {} + name_to_processed_artifacts = {} - for info in stratum.source.morphology['chunks']: + for info in source.morphology['chunks']: chunk_source = self._source_pool.lookup( info['repo'], info['ref'], '%s.morph' % info['morph']) - possible_names = chunk_source.morphology.builds_artifacts - if not info['name'] in possible_names: - raise UndefinedChunkArtifactError(stratum.source, info['name']) - - chunk_artifact = self._get_artifact(chunk_source, info['name']) - chunk_artifacts.append(chunk_artifact) - - artifacts.append(chunk_artifact) - - stratum.add_dependency(chunk_artifact) - - for other_stratum in strata: - chunk_artifact.add_dependency(other_stratum) + chunk_name = chunk_source.morphology['name'] # Resolve now to avoid a search for the parent morphology later chunk_source.build_mode = info['build-mode'] @@ -226,23 +204,45 @@ class ArtifactResolver(object): build_depends = info.get('build-depends', None) - if build_depends is None: - for earlier_artifact in processed_artifacts: - if earlier_artifact.depends_on(chunk_artifact): - raise MutualDependencyError( - chunk_artifact, earlier_artifact) - chunk_artifact.add_dependency(earlier_artifact) - elif isinstance(build_depends, list): + for ca_name in chunk_source.split_rules.artifacts: + chunk_artifact = chunk_source.artifacts[ca_name] + + # Add our stratum's build depends as dependencies of this chunk + for other_stratum in stratum_build_depends: + chunk_artifact.add_dependency(other_stratum) + + # Add dependencies between chunks mentioned in this stratum + if isinstance(build_depends, list): for name in build_depends: - other_artifact = name_to_processed_artifact.get(name, None) - if other_artifact: - chunk_artifact.add_dependency(other_artifact) - else: + if name not in name_to_processed_artifacts: raise DependencyOrderError( - stratum, info['name'], name) + source, info['name'], name) + other_artifacts = name_to_processed_artifacts[name] + for other_artifact in other_artifacts: + for ca_name in chunk_source.split_rules.artifacts: + chunk_artifact = chunk_source.artifacts[ca_name] + chunk_artifact.add_dependency(other_artifact) else: - raise DependencyFormatError(stratum, info['name']) - processed_artifacts.append(chunk_artifact) - name_to_processed_artifact[info['name']] = chunk_artifact + raise DependencyFormatError(source, info['name']) + + # Add build dependencies between our stratum's artifacts + # and the chunk artifacts produced by this stratum. + matches, overlaps, unmatched = source.split_rules.partition( + ((chunk_name, ca_name) for ca_name + in chunk_source.split_rules.artifacts)) + for stratum in strata: + for (chunk_name, ca_name) in matches[stratum.name]: + chunk_artifact = chunk_source.artifacts[ca_name] + stratum.add_dependency(chunk_artifact) + # Only return chunks required to build strata we need + if chunk_artifact not in artifacts: + artifacts.append(chunk_artifact) + + + # Add these chunks to the processed artifacts, so other + # chunks may refer to them. + name_to_processed_artifacts[info['name']] = \ + [chunk_source.artifacts[n] for n + in chunk_source.split_rules.artifacts] return artifacts diff --git a/morphlib/artifactresolver_tests.py b/morphlib/artifactresolver_tests.py index b685902e..6f62b4d1 100644 --- a/morphlib/artifactresolver_tests.py +++ b/morphlib/artifactresolver_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -14,6 +14,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import itertools import json import unittest @@ -27,16 +28,15 @@ class FakeChunkMorphology(morphlib.morph2.Morphology): if artifact_names: # fake a list of artifacts - artifacts = {} + artifacts = [] for artifact_name in artifact_names: - artifacts[artifact_name] = [artifact_name] - text = (''' - { - "name": "%s", + artifacts.append({'artifact': artifact_name, + 'include': artifact_name}) + text = json.dumps({ + "name": name, "kind": "chunk", - "chunks": %s - } - ''' % (name, json.dumps(artifacts))) + "products": artifacts + }) self.builds_artifacts = artifact_names else: text = (''' @@ -61,7 +61,8 @@ class FakeStratumMorphology(morphlib.morph2.Morphology): 'name': source_name, 'morph': morph, 'repo': repo, - 'ref': ref + 'ref': ref, + 'build-depends': [], }) build_depends_list = [] for morph, repo, ref in build_depends: @@ -114,33 +115,37 @@ class ArtifactResolverTests(unittest.TestCase): artifacts = self.resolver.resolve_artifacts(pool) - self.assertEqual(len(artifacts), 1) + self.assertEqual(len(artifacts), + sum(len(s.split_rules.artifacts) for s in pool)) - self.assertEqual(artifacts[0].source, source) - self.assertEqual(artifacts[0].name, 'chunk') - self.assertEqual(artifacts[0].dependencies, []) - self.assertEqual(artifacts[0].dependents, []) + for artifact in artifacts: + self.assertEqual(artifact.source, source) + self.assertTrue(artifact.name.startswith('chunk')) + self.assertEqual(artifact.dependencies, []) + self.assertEqual(artifact.dependents, []) - def test_resolve_single_chunk_with_one_artifact(self): + def test_resolve_single_chunk_with_one_new_artifact(self): pool = morphlib.sourcepool.SourcePool() - morph = FakeChunkMorphology('chunk', ['chunk-runtime']) + morph = FakeChunkMorphology('chunk', ['chunk-foobar']) source = morphlib.source.Source( 'repo', 'ref', 'sha1', 'tree', morph, 'chunk.morph') pool.add(source) artifacts = self.resolver.resolve_artifacts(pool) - self.assertEqual(len(artifacts), 1) - self.assertEqual(artifacts[0].source, source) - self.assertEqual(artifacts[0].name, 'chunk-runtime') - self.assertEqual(artifacts[0].dependencies, []) - self.assertEqual(artifacts[0].dependents, []) + self.assertEqual(len(artifacts), + sum(len(s.split_rules.artifacts) for s in pool)) + + foobartifact, = (a for a in artifacts if a.name == 'chunk-foobar') + self.assertEqual(foobartifact.source, source) + self.assertEqual(foobartifact.dependencies, []) + self.assertEqual(foobartifact.dependents, []) - def test_resolve_single_chunk_with_two_artifact(self): + def test_resolve_single_chunk_with_two_new_artifacts(self): pool = morphlib.sourcepool.SourcePool() - morph = FakeChunkMorphology('chunk', ['chunk-runtime', 'chunk-devel']) + morph = FakeChunkMorphology('chunk', ['chunk-baz', 'chunk-qux']) source = morphlib.source.Source( 'repo', 'ref', 'sha1', 'tree', morph, 'chunk.morph') pool.add(source) @@ -148,88 +153,16 @@ class ArtifactResolverTests(unittest.TestCase): artifacts = self.resolver.resolve_artifacts(pool) artifacts.sort(key=lambda a: a.name) - self.assertEqual(len(artifacts), 2) + self.assertEqual(len(artifacts), + sum(len(s.split_rules.artifacts) for s in pool)) - self.assertEqual(artifacts[0].source, source) - self.assertEqual(artifacts[0].name, 'chunk-devel') - self.assertEqual(artifacts[0].dependencies, []) - self.assertEqual(artifacts[0].dependents, []) + for name in ('chunk-baz', 'chunk-qux'): + artifact, = (a for a in artifacts if a.name == name) + self.assertEqual(artifact.source, source) + self.assertEqual(artifact.dependencies, []) + self.assertEqual(artifact.dependents, []) - self.assertEqual(artifacts[1].source, source) - self.assertEqual(artifacts[1].name, 'chunk-runtime') - self.assertEqual(artifacts[1].dependencies, []) - self.assertEqual(artifacts[1].dependents, []) - - def test_resolve_a_single_empty_stratum(self): - pool = morphlib.sourcepool.SourcePool() - - morph = morphlib.morph2.Morphology( - ''' - { - "name": "foo", - "kind": "stratum" - } - ''') - morph.builds_artifacts = ['foo'] - stratum = morphlib.source.Source( - 'repo', 'original/ref', 'sha1', 'tree', morph, 'foo.morph') - pool.add(stratum) - - artifacts = self.resolver.resolve_artifacts(pool) - - self.assertEqual(artifacts[0].source, stratum) - self.assertEqual(artifacts[0].name, 'foo') - self.assertEqual(artifacts[0].dependencies, []) - self.assertEqual(artifacts[0].dependents, []) - - def test_resolve_a_single_empty_system(self): - pool = morphlib.sourcepool.SourcePool() - - morph = morphlib.morph2.Morphology( - ''' - { - "name": "foo", - "kind": "system" - } - ''') - morph.builds_artifacts = ['foo-rootfs'] - system = morphlib.source.Source( - 'repo', 'original/ref', 'sha1', 'tree', morph, 'foo.morph') - pool.add(system) - - artifacts = self.resolver.resolve_artifacts(pool) - - self.assertEqual(artifacts[0].source, system) - self.assertEqual(artifacts[0].name, 'foo-rootfs') - self.assertEqual(artifacts[0].dependencies, []) - self.assertEqual(artifacts[0].dependents, []) - - def test_resolve_a_single_empty_arm_system(self): - pool = morphlib.sourcepool.SourcePool() - - morph = morphlib.morph2.Morphology( - ''' - { - "name": "foo", - "kind": "system", - "arch": "armv7" - } - ''') - morph.builds_artifacts = ['foo-rootfs', 'foo-kernel'] - system = morphlib.source.Source( - 'repo', 'original/ref', 'sha1', 'tree', morph, 'foo.morph') - pool.add(system) - - artifacts = self.resolver.resolve_artifacts(pool) - - self.assertTrue(any((a.source == system and a.name == 'foo-rootfs' and - a.dependencies == [] and a.dependents == []) - for a in artifacts)) - self.assertTrue(any((a.source == system and a.name == 'foo-kernel' and - a.dependencies == [] and a.dependents == []) - for a in artifacts)) - - def test_resolve_stratum_and_chunk_with_no_subartifacts(self): + def test_resolve_stratum_and_chunk(self): pool = morphlib.sourcepool.SourcePool() morph = FakeChunkMorphology('chunk') @@ -245,103 +178,36 @@ class ArtifactResolverTests(unittest.TestCase): artifacts = self.resolver.resolve_artifacts(pool) - self.assertEqual(len(artifacts), 2) + self.assertEqual(len(artifacts), + sum(len(s.split_rules.artifacts) for s in pool)) - self.assertEqual(artifacts[0].source, stratum) - self.assertEqual(artifacts[0].name, 'stratum') - self.assertEqual(artifacts[0].dependencies, [artifacts[1]]) - self.assertEqual(artifacts[0].dependents, []) + stratum_artifacts = set(a for a in artifacts if a.source == stratum) + chunk_artifacts = set(a for a in artifacts if a.source == chunk) - self.assertEqual(artifacts[1].source, chunk) - self.assertEqual(artifacts[1].name, 'chunk') - self.assertEqual(artifacts[1].dependencies, []) - self.assertEqual(artifacts[1].dependents, [artifacts[0]]) + for stratum_artifact in stratum_artifacts: + self.assertTrue(stratum_artifact.name.startswith('stratum')) + self.assertEqual(stratum_artifact.dependents, []) + self.assertTrue(any(dep in chunk_artifacts + for dep in stratum_artifact.dependencies)) - def test_resolve_stratum_and_chunk_with_two_subartifacts(self): - pool = morphlib.sourcepool.SourcePool() + for chunk_artifact in chunk_artifacts: + self.assertTrue(chunk_artifact.name.startswith('chunk')) + self.assertEqual(chunk_artifact.dependencies, []) + self.assertTrue(any(dep in stratum_artifacts + for dep in chunk_artifact.dependents)) - morph = FakeChunkMorphology('chunk', ['chunk-devel', 'chunk-runtime']) - chunk = morphlib.source.Source( - 'repo', 'ref', 'sha1', 'tree', morph, 'chunk.morph') - pool.add(chunk) - - morph = FakeStratumMorphology( - 'stratum', - chunks=[ - ('chunk-devel', 'chunk', 'repo', 'ref'), - ('chunk-runtime', 'chunk', 'repo', 'ref') - ]) - stratum = morphlib.source.Source( - 'repo', 'ref', 'sha1', 'tree', morph, 'stratum.morph') - pool.add(stratum) - - artifacts = self.resolver.resolve_artifacts(pool) - - self.assertEqual(len(artifacts), 3) - - self.assertEqual(artifacts[0].source, stratum) - self.assertEqual(artifacts[0].name, 'stratum') - self.assertEqual(artifacts[0].dependencies, - [artifacts[1], artifacts[2]]) - self.assertEqual(artifacts[0].dependents, []) - - self.assertEqual(artifacts[1].source, chunk) - self.assertEqual(artifacts[1].name, 'chunk-devel') - self.assertEqual(artifacts[1].dependencies, []) - self.assertEqual(artifacts[1].dependents, [artifacts[0], artifacts[2]]) - - self.assertEqual(artifacts[2].source, chunk) - self.assertEqual(artifacts[2].name, 'chunk-runtime') - self.assertEqual(artifacts[2].dependencies, [artifacts[1]]) - self.assertEqual(artifacts[2].dependents, [artifacts[0]]) - - def test_resolve_stratum_and_chunk_with_one_used_subartifacts(self): + def test_resolve_stratum_and_chunk_with_two_new_artifacts(self): pool = morphlib.sourcepool.SourcePool() - morph = FakeChunkMorphology('chunk', ['chunk-devel', 'chunk-runtime']) + morph = FakeChunkMorphology('chunk', ['chunk-foo', 'chunk-bar']) chunk = morphlib.source.Source( 'repo', 'ref', 'sha1', 'tree', morph, 'chunk.morph') pool.add(chunk) morph = FakeStratumMorphology( 'stratum', - chunks=[('chunk-runtime', 'chunk', 'repo', 'ref')]) - stratum = morphlib.source.Source( - 'repo', 'ref', 'sha1', 'tree', morph, 'stratum.morph') - pool.add(stratum) - - artifacts = self.resolver.resolve_artifacts(pool) - - self.assertEqual(len(artifacts), 2) - - self.assertEqual(artifacts[0].source, stratum) - self.assertEqual(artifacts[0].name, 'stratum') - self.assertEqual(artifacts[0].dependencies, [artifacts[1]]) - self.assertEqual(artifacts[0].dependents, []) - - self.assertEqual(artifacts[1].source, chunk) - self.assertEqual(artifacts[1].name, 'chunk-runtime') - self.assertEqual(artifacts[1].dependencies, []) - self.assertEqual(artifacts[1].dependents, [artifacts[0]]) - - def test_resolving_two_different_chunk_artifacts_in_a_stratum(self): - pool = morphlib.sourcepool.SourcePool() - - morph = FakeChunkMorphology('foo') - foo_chunk = morphlib.source.Source( - 'repo', 'ref', 'sha1', 'tree', morph, 'foo.morph') - pool.add(foo_chunk) - - morph = FakeChunkMorphology('bar') - bar_chunk = morphlib.source.Source( - 'repo', 'ref', 'sha1', 'tree', morph, 'bar.morph') - pool.add(bar_chunk) - - morph = FakeStratumMorphology( - 'stratum', chunks=[ - ('foo', 'foo', 'repo', 'ref'), - ('bar', 'bar', 'repo', 'ref') + ('chunk', 'chunk', 'repo', 'ref'), ]) stratum = morphlib.source.Source( 'repo', 'ref', 'sha1', 'tree', morph, 'stratum.morph') @@ -349,114 +215,35 @@ class ArtifactResolverTests(unittest.TestCase): artifacts = self.resolver.resolve_artifacts(pool) - self.assertEqual(len(artifacts), 3) - - self.assertEqual(artifacts[0].source, stratum) - self.assertEqual(artifacts[0].name, 'stratum') - self.assertEqual(artifacts[0].dependencies, - [artifacts[1], artifacts[2]]) - self.assertEqual(artifacts[0].dependents, []) - - self.assertEqual(artifacts[1].source, foo_chunk) - self.assertEqual(artifacts[1].name, 'foo') - self.assertEqual(artifacts[1].dependencies, []) - self.assertEqual(artifacts[1].dependents, [artifacts[0], artifacts[2]]) - - self.assertEqual(artifacts[2].source, bar_chunk) - self.assertEqual(artifacts[2].name, 'bar') - self.assertEqual(artifacts[2].dependencies, [artifacts[1]]) - self.assertEqual(artifacts[2].dependents, [artifacts[0]]) - - def test_resolving_artifacts_for_a_chain_of_two_strata(self): - pool = morphlib.sourcepool.SourcePool() - - morph = FakeStratumMorphology('stratum1') - stratum1 = morphlib.source.Source( - 'repo', 'ref', 'sha1', 'tree', morph, 'stratum1.morph') - pool.add(stratum1) - - morph = FakeStratumMorphology( - 'stratum2', - chunks=[], - build_depends=[('stratum1', 'repo', 'ref')]) - stratum2 = morphlib.source.Source( - 'repo', 'ref', 'sha1', 'tree', morph, 'stratum2.morph') - pool.add(stratum2) - - artifacts = self.resolver.resolve_artifacts(pool) + self.assertEqual(len(artifacts), + sum(len(s.split_rules.artifacts) for s in pool)) - self.assertEqual(len(artifacts), 2) + stratum_artifacts = set(a for a in artifacts if a.source == stratum) + chunk_artifacts = set(a for a in artifacts if a.source == chunk) - self.assertEqual(artifacts[0].source, stratum1) - self.assertEqual(artifacts[0].name, 'stratum1') - self.assertEqual(artifacts[0].dependencies, []) - self.assertEqual(artifacts[0].dependents, [artifacts[1]]) + for stratum_artifact in stratum_artifacts: + self.assertTrue(stratum_artifact.name.startswith('stratum')) + self.assertEqual(stratum_artifact.dependents, []) + self.assertTrue(any(dep in chunk_artifacts + for dep in stratum_artifact.dependencies)) - self.assertEqual(artifacts[1].source, stratum2) - self.assertEqual(artifacts[1].name, 'stratum2') - self.assertEqual(artifacts[1].dependencies, [artifacts[0]]) - self.assertEqual(artifacts[1].dependents, []) + for chunk_artifact in chunk_artifacts: + self.assertTrue(chunk_artifact.name.startswith('chunk')) + self.assertEqual(chunk_artifact.dependencies, []) + self.assertTrue(any(dep in stratum_artifacts + for dep in chunk_artifact.dependents)) - def test_resolving_with_a_stratum_and_chunk_dependency_mix(self): + def test_resolving_artifacts_for_a_system_with_two_dependent_strata(self): pool = morphlib.sourcepool.SourcePool() - morph = FakeStratumMorphology('stratum1') - stratum1 = morphlib.source.Source( - 'repo', 'original/ref', 'sha1', 'tree', morph, 'stratum1.morph') - pool.add(stratum1) - - morph = FakeStratumMorphology( - 'stratum2', - chunks=[ - ('chunk1', 'chunk1', 'repo', 'original/ref'), - ('chunk2', 'chunk2', 'repo', 'original/ref') - ], - build_depends=[('stratum1', 'repo', 'original/ref')]) - stratum2 = morphlib.source.Source( - 'repo', 'original/ref', 'sha1', 'tree', morph, 'stratum2.morph') - pool.add(stratum2) - morph = FakeChunkMorphology('chunk1') chunk1 = morphlib.source.Source( 'repo', 'original/ref', 'sha1', 'tree', morph, 'chunk1.morph') pool.add(chunk1) - morph = FakeChunkMorphology('chunk2') - chunk2 = morphlib.source.Source( - 'repo', 'original/ref', 'sha1', 'tree', morph, 'chunk2.morph') - pool.add(chunk2) - - artifacts = self.resolver.resolve_artifacts(pool) - - self.assertEqual(len(artifacts), 4) - - self.assertEqual(artifacts[0].source, stratum1) - self.assertEqual(artifacts[0].name, 'stratum1') - self.assertEqual(artifacts[0].dependencies, []) - self.assertEqual(artifacts[0].dependents, - [artifacts[1], artifacts[2], artifacts[3]]) - - self.assertEqual(artifacts[1].source, stratum2) - self.assertEqual(artifacts[1].name, 'stratum2') - self.assertEqual(artifacts[1].dependencies, - [artifacts[0], artifacts[2], artifacts[3]]) - self.assertEqual(artifacts[1].dependents, []) - - self.assertEqual(artifacts[2].source, chunk1) - self.assertEqual(artifacts[2].name, 'chunk1') - self.assertEqual(artifacts[2].dependencies, [artifacts[0]]) - self.assertEqual(artifacts[2].dependents, [artifacts[1], artifacts[3]]) - - self.assertEqual(artifacts[3].source, chunk2) - self.assertEqual(artifacts[3].name, 'chunk2') - self.assertEqual(artifacts[3].dependencies, - [artifacts[0], artifacts[2]]) - self.assertEqual(artifacts[3].dependents, [artifacts[1]]) - - def test_resolving_artifacts_for_a_system_with_two_strata(self): - pool = morphlib.sourcepool.SourcePool() - - morph = FakeStratumMorphology('stratum1') + morph = FakeStratumMorphology( + 'stratum1', + chunks=[('chunk1', 'chunk1', 'repo', 'original/ref')]) stratum1 = morphlib.source.Source( 'repo', 'ref', 'sha1', 'tree', morph, 'stratum1.morph') pool.add(stratum1) @@ -485,31 +272,67 @@ class ArtifactResolverTests(unittest.TestCase): 'repo', 'ref', 'sha1', 'tree', morph, 'system.morph') pool.add(system) + morph = FakeChunkMorphology('chunk2') + chunk2 = morphlib.source.Source( + 'repo', 'original/ref', 'sha1', 'tree', morph, 'chunk2.morph') + pool.add(chunk2) + morph = FakeStratumMorphology( - 'stratum2', chunks=[], build_depends=[('stratum1', 'repo', 'ref')]) + 'stratum2', + chunks=[('chunk2', 'chunk2', 'repo', 'original/ref')], + build_depends=[('stratum1', 'repo', 'ref')]) stratum2 = morphlib.source.Source( 'repo', 'ref', 'sha1', 'tree', morph, 'stratum2.morph') pool.add(stratum2) artifacts = self.resolver.resolve_artifacts(pool) - self.assertEqual(len(artifacts), 3) + self.assertEqual(len(artifacts), + sum(len(s.split_rules.artifacts) for s in pool)) + + system_artifacts = set(a for a in artifacts if a.source == system) + stratum1_artifacts = set(a for a in artifacts if a.source == stratum1) + chunk1_artifacts = set(a for a in artifacts if a.source == chunk1) + stratum2_artifacts = set(a for a in artifacts if a.source == stratum2) + chunk2_artifacts = set(a for a in artifacts if a.source == chunk2) + + def assert_depended_on_by_some(artifact, parents): + self.assertNotEqual(len(artifact.dependents), 0) + self.assertTrue(any(a in artifact.dependents for a in parents)) + + def assert_depended_on_by_all(artifact, parents): + self.assertNotEqual(len(artifact.dependents), 0) + self.assertTrue(all(a in artifact.dependents for a in parents)) + + def assert_depends_on_some(artifact, children): + self.assertNotEqual(len(artifact.dependencies), 0) + self.assertTrue(any(a in children for a in artifact.dependencies)) + + def assert_depends_on_all(artifact, children): + self.assertNotEqual(len(artifact.dependencies), 0) + self.assertTrue(all(a in children for a in artifact.dependencies)) - self.assertEqual(artifacts[0].source, stratum1) - self.assertEqual(artifacts[0].name, 'stratum1') - self.assertEqual(artifacts[0].dependencies, []) - self.assertEqual(artifacts[0].dependents, [artifacts[1], artifacts[2]]) + for c1_a in chunk1_artifacts: + self.assertEqual(c1_a.dependencies, []) + assert_depended_on_by_some(c1_a, stratum1_artifacts) - self.assertEqual(artifacts[1].source, system) - self.assertEqual(artifacts[1].name, 'system-rootfs') - self.assertEqual(artifacts[1].dependencies, - [artifacts[0], artifacts[2]]) - self.assertEqual(artifacts[1].dependents, []) + for st1_a in stratum1_artifacts: + assert_depends_on_some(st1_a, chunk1_artifacts) + assert_depended_on_by_all(st1_a, chunk2_artifacts) + assert_depended_on_by_some(st1_a, system_artifacts) - self.assertEqual(artifacts[2].source, stratum2) - self.assertEqual(artifacts[2].name, 'stratum2') - self.assertEqual(artifacts[2].dependencies, [artifacts[0]]) - self.assertEqual(artifacts[2].dependents, [artifacts[1]]) + for c2_a in chunk2_artifacts: + assert_depends_on_all(c2_a, stratum1_artifacts) + assert_depended_on_by_some(c2_a, stratum2_artifacts) + + for st2_a in stratum2_artifacts: + assert_depends_on_some(st2_a, chunk2_artifacts) + assert_depended_on_by_some(st2_a, system_artifacts) + + for sy_a in system_artifacts: + self.assertEqual(sy_a.dependents, []) + assert_depends_on_some(sy_a, stratum1_artifacts) + assert_depends_on_some(sy_a, stratum2_artifacts) def test_resolving_stratum_with_explicit_chunk_dependencies(self): pool = morphlib.sourcepool.SourcePool() @@ -566,49 +389,33 @@ class ArtifactResolverTests(unittest.TestCase): artifacts = self.resolver.resolve_artifacts(pool) - self.assertEqual(len(artifacts), 4) - - self.assertEqual(artifacts[0].source, stratum) - self.assertEqual(artifacts[0].name, 'stratum') - self.assertEqual(artifacts[0].dependencies, - [artifacts[1], artifacts[2], artifacts[3]]) - self.assertEqual(artifacts[0].dependents, []) - - self.assertEqual(artifacts[1].source, chunk1) - self.assertEqual(artifacts[1].name, 'chunk1') - self.assertEqual(artifacts[1].dependencies, []) - self.assertEqual(artifacts[1].dependents, - [artifacts[0], artifacts[3]]) - - self.assertEqual(artifacts[2].source, chunk2) - self.assertEqual(artifacts[2].name, 'chunk2') - self.assertEqual(artifacts[2].dependencies, []) - self.assertEqual(artifacts[2].dependents, [artifacts[0], artifacts[3]]) - - self.assertEqual(artifacts[3].source, chunk3) - self.assertEqual(artifacts[3].name, 'chunk3') - self.assertEqual(artifacts[3].dependencies, - [artifacts[1], artifacts[2]]) - self.assertEqual(artifacts[3].dependents, [artifacts[0]]) - - def test_detection_of_invalid_chunk_artifact_references(self): - pool = morphlib.sourcepool.SourcePool() - - morph = FakeChunkMorphology('chunk') - chunk = morphlib.source.Source( - 'repo', 'ref', 'sha1', 'tree', morph, 'chunk.morph') - pool.add(chunk) - - morph = FakeStratumMorphology( - 'stratum', - chunks=[('chunk-runtime', 'chunk', 'repo', 'ref')]) - stratum = morphlib.source.Source( - 'repo', 'ref', 'sha1', 'tree', morph, 'stratum.morph') - pool.add(stratum) - - self.assertRaises( - morphlib.artifactresolver.UndefinedChunkArtifactError, - self.resolver.resolve_artifacts, pool) + self.assertEqual(len(artifacts), + sum(len(s.split_rules.artifacts) for s in pool)) + + stratum_artifacts = set(a for a in artifacts if a.source == stratum) + chunk_artifacts = [set(a for a in artifacts if a.source == source) + for source in (chunk1, chunk2, chunk3)] + all_chunks = set(itertools.chain.from_iterable(chunk_artifacts)) + + for st_a in stratum_artifacts: + self.assertEqual(st_a.dependents, []) + # This stratum depends on some chunk artifacts + self.assertTrue(any(a in st_a.dependencies for a in all_chunks)) + + for ca in chunk_artifacts[2]: + # There's a stratum dependent on this artifact + self.assertTrue(any(a in stratum_artifacts for a in ca.dependents)) + # chunk3's artifacts depend on chunk1 and chunk2's artifacts + self.assertEqual(set(ca.dependencies), + chunk_artifacts[0] | chunk_artifacts[1]) + + for ca in itertools.chain.from_iterable(chunk_artifacts[0:1]): + self.assertEqual(ca.dependencies, []) + # There's a stratum dependent on this artifact + self.assertTrue(any(a in stratum_artifacts for a in ca.dependents)) + # All chunk3's artifacts depend on this artifact + self.assertTrue(all(c3a in ca.dependents + for c3a in chunk_artifacts[2])) def test_detection_of_mutual_dependency_between_two_strata(self): pool = morphlib.sourcepool.SourcePool() @@ -632,97 +439,6 @@ class ArtifactResolverTests(unittest.TestCase): self.assertRaises(morphlib.artifactresolver.MutualDependencyError, self.resolver.resolve_artifacts, pool) - def test_detection_of_mutual_dependency_between_consecutive_chunks(self): - pool = morphlib.sourcepool.SourcePool() - - morph = FakeStratumMorphology( - 'stratum1', - chunks=[ - ('chunk1', 'chunk1', 'repo', 'original/ref'), - ('chunk2', 'chunk2', 'repo', 'original/ref') - ]) - stratum1 = morphlib.source.Source( - 'repo', 'original/ref', 'sha1', 'tree', morph, 'stratum1.morph') - pool.add(stratum1) - - morph = FakeStratumMorphology( - 'stratum2', - chunks=[ - ('chunk2', 'chunk2', 'repo', 'original/ref'), - ('chunk1', 'chunk1', 'repo', 'original/ref') - ], - build_depends=[('stratum1', 'repo', 'original/ref')]) - stratum2 = morphlib.source.Source( - 'repo', 'original/ref', 'sha1', 'tree', morph, 'stratum2.morph') - pool.add(stratum2) - - morph = FakeChunkMorphology('chunk1') - chunk1 = morphlib.source.Source( - 'repo', 'original/ref', 'sha1', 'tree', morph, 'chunk1.morph') - pool.add(chunk1) - - morph = FakeChunkMorphology('chunk2') - chunk2 = morphlib.source.Source( - 'repo', 'original/ref', 'sha1', 'tree', morph, 'chunk2.morph') - pool.add(chunk2) - - self.assertRaises(morphlib.artifactresolver.MutualDependencyError, - self.resolver.resolve_artifacts, pool) - - if 0: - # This situation is currently not possible - def test_graceful_handling_of_self_dependencies_of_chunks(self): - pool = morphlib.sourcepool.SourcePool() - - morph = morphlib.morph2.Morphology( - ''' - { - "name": "stratum", - "kind": "stratum", - "chunks": [ - { - "alias": "same-chunk-runtime", - "name": "chunk-runtime", - "morph": "chunk", - "repo": "repo", - "ref": "original/ref" - }, - { - "name": "chunk-runtime", - "morph": "chunk", - "repo": "repo", - "ref": "original/ref", - "build-depends": [ - "same-chunk-runtime" - ] - } - ] - } - ''') - morph.builds_artifacts = ['stratum'] - stratum = morphlib.source.Source( - 'repo', 'original/ref', 'sha1', 'tree', morph, 'stratum.morph') - pool.add(stratum) - - morph = FakeChunkMorphology('chunk') - chunk = morphlib.source.Source( - 'repo', 'original/ref', 'sha1', 'tree', morph, 'chunk.morph') - pool.add(chunk) - - artifacts = self.resolver.resolve_artifacts(pool) - - self.assertEqual(len(artifacts), 2) - - self.assertEqual(artifacts[0].source, stratum) - self.assertEqual(artifacts[0].name, 'stratum') - self.assertEqual(artifacts[0].dependencies, [artifacts[1]]) - self.assertEqual(artifacts[0].dependents, []) - - self.assertEqual(artifacts[1].source, chunk) - self.assertEqual(artifacts[1].name, 'chunk') - self.assertEqual(artifacts[1].dependencies, []) - self.assertEqual(artifacts[1].dependents, [artifacts[0]]) - def test_detection_of_chunk_dependencies_in_invalid_order(self): pool = morphlib.sourcepool.SourcePool() @@ -796,3 +512,8 @@ class ArtifactResolverTests(unittest.TestCase): self.assertRaises(morphlib.artifactresolver.DependencyFormatError, self.resolver.resolve_artifacts, pool) + + +# TODO: Expand test suite to include better dependency checking, many +# tests were removed due to the fundamental change in how artifacts +# and dependencies are constructed diff --git a/morphlib/artifactsplitrule.py b/morphlib/artifactsplitrule.py new file mode 100644 index 00000000..246691d8 --- /dev/null +++ b/morphlib/artifactsplitrule.py @@ -0,0 +1,303 @@ +# Copyright (C) 2013-2014 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +import collections +import itertools +import re + +import morphlib + + +class Rule(object): + '''Rule base class. + + Rules are passed an object and are expected to determine whether + it matches. It's roughly the same machinery for matching files + as artifacts, it's just that Files are given just the path, while + Artifact matches are given the artifact name and the name of the + source it came from. + + ''' + + def match(self, *args): + return True + + +class FileMatch(Rule): + '''Match a file path against a list of regular expressions. + + If the path matches any of the regular expressions, then the file + is counted as a valid match. + + ''' + + def __init__(self, regexes): + # Possible optimisation: compile regexes as one pattern + self._regexes = [re.compile(r) for r in regexes] + + def match(self, path): + return any(r.match(path) for r in self._regexes) + + +class ArtifactMatch(Rule): + '''Match an artifact's name against a list of regular expressions. + ''' + + def __init__(self, regexes): + # Possible optimisation: compile regexes as one pattern + self._regexes = [re.compile(r) for r in regexes] + + def match(self, (source_name, artifact_name)): + return any(r.match(artifact_name) for r in self._regexes) + + +class ArtifactAssign(Rule): + '''Match only artifacts with the specified source and artifact names. + + This is a valid match if the source and artifact names exactly match. + This is used for explicit artifact assignment e.g. chunk artifact + foo-bins which comes from chunk source foo goes into stratum + bar-runtime. + + ''' + + def __init__(self, source_name, artifact_name): + self._key = (source_name, artifact_name) + + def match(self, (source_name, artifact_name)): + return (source_name, artifact_name) == self._key + + +class SourceAssign(Rule): + '''Match only artifacts which come from the specified source. + + This is a valid match only if the artifact comes from the specified + source. e.g. all artifacts produced by source bar-runtime go into + system baz + + ''' + + def __init__(self, source_name): + self._source = source_name + + def match(self, (source_name, artifact_name)): + return source_name == self._source + + +class SplitRules(collections.Iterable): + '''Rules engine for splitting a source's artifacts. + + Rules are added with the .add(artifact, rule) method, though another + SplitRules may be created by passing a SplitRules to the constructor. + + .match(path|(source, artifact)) and .partition(iterable) are used + to determine if an artifact matches the rules. Rules are processed + in order, so more specific matches first can be followed by more + generic catch-all matches. + + ''' + + def __init__(self, *args): + self._rules = list(*args) + + def __iter__(self): + return iter(self._rules) + + def add(self, artifact, rule): + self._rules.append((artifact, rule)) + + @property + def artifacts(self): + '''Get names of all artifacts in the rule set. + + Returns artifact names in the order they were added to the rules, + and not repeating the artifact. + + ''' + + seen = set() + result = [] + for artifact_name, rule in self._rules: + if artifact_name not in seen: + seen.add(artifact_name) + result.append(artifact_name) + return result + + def match(self, *args): + '''Return all artifact names the given argument matches. + + It's returned in match order as a list, so it's possible to + detect overlapping matches, even though most of the time, the + only used entry will be the first. + + ''' + + return [a for a, r in self._rules if r.match(*args)] + + def partition(self, iterable): + '''Match many files or artifacts. + + It's the common case to take a bunch of filenames and determine + which artifact each should go to, so rather than implement this + logic in multiple places, it's here as a convenience method. + + ''' + + matches = collections.defaultdict(list) + overlaps = collections.defaultdict(set) + unmatched = set() + + for arg in iterable: + matched = self.match(arg) + if len(matched) == 0: + unmatched.add(arg) + continue + if len(matched) != 1: + overlaps[arg].update(matched) + matches[matched[0]].append(arg) + + return matches, overlaps, unmatched + + +# 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', [ + r"(usr/)?lib(32|64)?/lib[^/]*\.so(\.\d+)*", + r"(usr/)libexec/.*"]), + ('-devel', [ + r"(usr/)?include/.*", + r"(usr/)?lib(32|64)?/lib.*\.a", + r"(usr/)?lib(32|64)?/lib.*\.la", + r"(usr/)?(lib(32|64)?|share)/pkgconfig/.*\.pc"]), + ('-doc', [ + r"(usr/)?share/doc/.*", + r"(usr/)?share/man/.*", + r"(usr/)?share/info/.*"]), + ('-locale', [ + r"(usr/)?share/locale/.*", + r"(usr/)?share/i18n/.*", + r"(usr/)?share/zoneinfo/.*"]), + ('-misc', [ r".*" ]), +] + + +DEFAULT_STRATUM_RULES = [ + ('-devel', [ + r'.*-devel', + r'.*-debug', + r'.*-doc']), + ('-runtime', [ + r'.*-bins', + r'.*-libs', + r'.*-locale', + r'.*-misc', + r'.*']), +] + + +def unify_chunk_matches(morphology): + '''Create split rules including defaults and per-chunk rules. + + With rules specified in the morphology's 'products' field and the + default rules for chunks, generate rules to match the files produced + by building the chunk to the chunk artifact they should be put in. + + ''' + + split_rules = SplitRules() + + for ca_name, patterns in ((d['artifact'], d['include']) + for d in morphology['products']): + split_rules.add(ca_name, FileMatch(patterns)) + + name = morphology['name'] + for suffix, patterns in DEFAULT_CHUNK_RULES: + ca_name = name + suffix + # Default rules are replaced by explicit ones + if ca_name in split_rules.artifacts: + break + split_rules.add(ca_name, FileMatch(patterns)) + + return split_rules + + +def unify_stratum_matches(morphology): + '''Create split rules including defaults and per-stratum rules. + + With rules specified in the chunk spec's 'artifacts' fields, the + stratum's 'products' field and the default rules for strata, generate + rules to match the artifacts produced by building the chunks in the + strata to the stratum artifact they should be put in. + + ''' + + assignment_split_rules = SplitRules() + for spec in morphology['chunks']: + source_name = spec['name'] + for ca_name, sta_name in sorted(spec.get('artifacts', {}).iteritems()): + assignment_split_rules.add(sta_name, + ArtifactAssign(source_name, ca_name)) + + # Construct match rules separately, so we can use the SplitRules object's + # own knowledge of which rules already exist to determine whether + # to include the default rule. + # Rather than use the existing SplitRules, use a new one, since + # match rules suppliment assignment rules, rather than replace. + match_split_rules = SplitRules() + for sta_name, patterns in ((d['artifact'], d['include']) + for d in morphology.get('products', {})): + match_split_rules.add(sta_name, ArtifactMatch(patterns)) + + for suffix, patterns in DEFAULT_STRATUM_RULES: + sta_name = morphology['name'] + suffix + if sta_name in match_split_rules.artifacts: + break + match_split_rules.add(sta_name, ArtifactMatch(patterns)) + + # Construct a new SplitRules with the assignments before matches + return SplitRules(itertools.chain(assignment_split_rules, + match_split_rules)) + + +def unify_system_matches(morphology): + '''Create split rules including defaults and per-chunk rules. + + With rules specified in the morphology's 'products' field and the + default rules for chunks, generate rules to match the files produced + by building the chunk to the chunk artifact they should be put in. + + ''' + + name = morphology['name'] + '-rootfs' + split_rules = SplitRules() + + for spec in morphology['strata']: + source_name = spec.get('name', spec['morph']) + if spec.get('artifacts', None) is None: + split_rules.add(name, SourceAssign(source_name)) + continue + for sta_name in spec['artifacts']: + split_rules.add(name, ArtifactAssign(source_name, sta_name)) + + return split_rules + + +def unify_cluster_matches(_): + return None diff --git a/morphlib/bins.py b/morphlib/bins.py index 6fb7dc5a..23e3b812 100644 --- a/morphlib/bins.py +++ b/morphlib/bins.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -50,60 +50,7 @@ def safe_makefile(self, tarinfo, targetpath): tarfile.TarFile.makefile = safe_makefile -def _chunk_filenames(rootdir, regexps, dump_memory_profile=None): - - '''Return the filenames for a chunk from the contents of a directory. - - Only files and directories that match at least one of the regular - expressions are accepted. The regular expressions are implicitly - anchored to the beginning of the string, but not the end. The - filenames are relative to rootdir. - - ''' - - dump_memory_profile = dump_memory_profile or (lambda msg: None) - - def matches(filename): - return any(x.match(filename) for x in compiled) - - def names_to_root(filename): - yield filename - while filename != rootdir: - filename = os.path.dirname(filename) - yield filename - - compiled = [re.compile(x) for x in regexps] - include = set() - for dirname, subdirs, basenames in os.walk(rootdir): - subdirpaths = [os.path.join(dirname, x) for x in subdirs] - subdirsymlinks = [x for x in subdirpaths if os.path.islink(x)] - filenames = [os.path.join(dirname, x) for x in basenames] - for filename in [dirname] + subdirsymlinks + filenames: - if matches(os.path.relpath(filename, rootdir)): - for name in names_to_root(filename): - if name not in include: - include.add(name) - else: - logging.debug('regexp MISMATCH: %s' % filename) - dump_memory_profile('after walking') - - return sorted(include) # get dirs before contents - - -def chunk_contents(rootdir, regexps): - ''' Return the list of files in a chunk, with the rootdir - stripped off. - - ''' - - filenames = _chunk_filenames(rootdir, regexps) - # The first entry is the rootdir directory, which we don't need - filenames.pop(0) - contents = [str[len(rootdir):] for str in filenames] - return contents - - -def create_chunk(rootdir, f, regexps, dump_memory_profile=None): +def create_chunk(rootdir, f, include, dump_memory_profile=None): '''Create a chunk from the contents of a directory. ``f`` is an open file handle, to which the tar file is written. @@ -118,14 +65,15 @@ def create_chunk(rootdir, f, regexps, dump_memory_profile=None): # does not complain about an implausibly old timestamp. normalized_timestamp = 683074800 - include = _chunk_filenames(rootdir, regexps, dump_memory_profile) dump_memory_profile('at beginning of create_chunk') + path_pairs = [(relname, os.path.join(rootdir, relname)) + for relname in include] tar = tarfile.open(fileobj=f, mode='w') - for filename in include: + for relname, filename in path_pairs: # Normalize mtime for everything. tarinfo = tar.gettarinfo(filename, - arcname=os.path.relpath(filename, rootdir)) + arcname=relname) tarinfo.ctime = normalized_timestamp tarinfo.mtime = normalized_timestamp if tarinfo.isreg(): @@ -135,11 +83,9 @@ def create_chunk(rootdir, f, regexps, dump_memory_profile=None): tar.addfile(tarinfo) tar.close() - include.remove(rootdir) - for filename in reversed(include): + for relname, filename in reversed(path_pairs): if os.path.isdir(filename) and not os.path.islink(filename): - if not os.listdir(filename): - os.rmdir(filename) + continue else: os.remove(filename) dump_memory_profile('after removing in create_chunks') diff --git a/morphlib/bins_tests.py b/morphlib/bins_tests.py index a9a94a44..60361ece 100644 --- a/morphlib/bins_tests.py +++ b/morphlib/bins_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -107,42 +107,33 @@ class ChunkTests(BinsTest): self.instdir_orig_files = self.recursive_lstat(self.instdir) - def create_chunk(self, regexps): + def create_chunk(self, includes): self.populate_instdir() - morphlib.bins.create_chunk(self.instdir, self.chunk_f, regexps) + morphlib.bins.create_chunk(self.instdir, self.chunk_f, includes) self.chunk_f.flush() - def chunk_contents(self, regexps): - self.populate_instdir() - return morphlib.bins.chunk_contents(self.instdir, regexps) - def unpack_chunk(self): os.mkdir(self.unpacked) morphlib.bins.unpack_binary(self.chunk_file, self.unpacked) - def test_empties_everything(self): - self.create_chunk(['.']) + def test_empties_files(self): + self.create_chunk(['bin/foo', 'lib/libfoo.so']) self.assertEqual([x for x, y in self.recursive_lstat(self.instdir)], - ['.']) + ['.', 'bin', 'lib']) def test_creates_and_unpacks_chunk_exactly(self): - self.create_chunk(['.']) + self.create_chunk(['bin', 'bin/foo', 'lib', 'lib/libfoo.so']) self.unpack_chunk() self.assertEqual(self.instdir_orig_files, self.recursive_lstat(self.unpacked)) def test_uses_only_matching_names(self): - self.create_chunk(['bin']) + self.create_chunk(['bin/foo']) self.unpack_chunk() self.assertEqual([x for x, y in self.recursive_lstat(self.unpacked)], ['.', 'bin', 'bin/foo']) self.assertEqual([x for x, y in self.recursive_lstat(self.instdir)], - ['.', 'lib', 'lib/libfoo.so']) - - def test_list_chunk_contents(self): - contents = self.chunk_contents(['.']) - self.assertEqual(contents, - ['/bin', '/bin/foo', '/lib', '/lib/libfoo.so']) + ['.', 'bin', 'lib', 'lib/libfoo.so']) def test_does_not_compress_artifact(self): self.create_chunk(['bin']) @@ -176,13 +167,13 @@ class ExtractTests(unittest.TestCase): with open(os.path.join(basedir, 'babar'), 'w') as f: pass os.symlink('babar', os.path.join(basedir, 'bar')) - return ['.'] + return ['babar'] linktar = self.create_chunk(make_linkfile) def make_file(basedir): with open(os.path.join(basedir, 'bar'), 'w') as f: pass - return ['.'] + return ['bar'] filetar = self.create_chunk(make_file) os.mkdir(self.unpacked) @@ -194,12 +185,12 @@ class ExtractTests(unittest.TestCase): def test_extracted_dirs_keep_links(self): def make_usrlink(basedir): os.symlink('.', os.path.join(basedir, 'usr')) - return ['.'] + return ['usr'] linktar = self.create_chunk(make_usrlink) def make_usrdir(basedir): os.mkdir(os.path.join(basedir, 'usr')) - return ['.'] + return ['usr'] dirtar = self.create_chunk(make_usrdir) morphlib.bins.unpack_binary_from_file(linktar, self.unpacked) @@ -210,14 +201,14 @@ class ExtractTests(unittest.TestCase): def test_extracted_files_follow_links(self): def make_usrlink(basedir): os.symlink('.', os.path.join(basedir, 'usr')) - return ['.'] + return ['usr'] linktar = self.create_chunk(make_usrlink) def make_usrdir(basedir): os.mkdir(os.path.join(basedir, 'usr')) with open(os.path.join(basedir, 'usr', 'foo'), 'w') as f: pass - return ['.'] + return ['usr', 'usr/foo'] dirtar = self.create_chunk(make_usrdir) morphlib.bins.unpack_binary_from_file(linktar, self.unpacked) diff --git a/morphlib/buildcommand.py b/morphlib/buildcommand.py index 4b3b2108..6485f510 100644 --- a/morphlib/buildcommand.py +++ b/morphlib/buildcommand.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -22,6 +22,14 @@ import tempfile import morphlib +class MultipleRootArtifactsError(morphlib.Error): + + def __init__(self, artifacts): + self.msg = ('System build has multiple root artifacts: %r' + % [a.name for a in artifacts]) + self.artifacts = artifacts + + class BuildCommand(object): '''High level logic for building. @@ -142,7 +150,15 @@ class BuildCommand(object): artifacts = ar.resolve_artifacts(srcpool) self.app.status(msg='Computing build order', chatty=True) - root_artifact = self._find_root_artifact(artifacts) + root_artifacts = self._find_root_artifacts(artifacts) + if len(root_artifacts) > 1: + # Validate root artifacts, since validation covers errors + # such as trying to build a chunk or stratum directly, + # and this is one cause for having multiple root artifacts + for root_artifact in root_artifacts: + self._validate_root_artifact(root_artifact) + raise MultipleRootArtifactsError(root_artifacts) + root_artifact = root_artifacts[0] # Validate the root artifact here, since it's a costly function # to finalise it, so any pre finalisation validation is better @@ -231,8 +247,8 @@ class BuildCommand(object): other.morphology['kind'], wanted)) - def _find_root_artifact(self, artifacts): - '''Find the root artifact among a set of artifacts in a DAG. + def _find_root_artifacts(self, artifacts): + '''Find all the root artifacts among a set of artifacts in a DAG. It would be nice if the ArtifactResolver would return its results in a more useful order to save us from needing to do this -- the root object @@ -240,13 +256,7 @@ class BuildCommand(object): ''' - maybe = set(artifacts) - for a in artifacts: - for dep in a.dependencies: - if dep in maybe: - maybe.remove(dep) - assert len(maybe) == 1 - return maybe.pop() + return [a for a in artifacts if not a.dependents] def build_in_order(self, root_artifact): '''Build everything specified in a build order.''' diff --git a/morphlib/builder2.py b/morphlib/builder2.py index bab89aa2..2dca738c 100644 --- a/morphlib/builder2.py +++ b/morphlib/builder2.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -422,33 +422,53 @@ class ChunkBuilder(BuilderBase): def assemble_chunk_artifacts(self, destdir): # pragma: no cover built_artifacts = [] filenames = [] + source = self.artifact.source + split_rules = source.split_rules + + def filepaths(destdir): + for dirname, subdirs, basenames in os.walk(destdir): + subdirsymlinks = [os.path.join(dirname, x) for x in subdirs + if os.path.islink(x)] + filenames = [os.path.join(dirname, x) for x in basenames] + for relpath in (os.path.relpath(x, destdir) for x in + [dirname] + subdirsymlinks + filenames): + yield relpath + + with self.build_watch('determine-splits'): + matches, overlaps, unmatched = \ + split_rules.partition(filepaths(destdir)) + with self.build_watch('create-chunks'): - specs = self.artifact.source.morphology['chunks'] - if len(specs) == 0: - specs = { - self.artifact.source.morphology['name']: ['.'], - } - names = specs.keys() - names.sort(key=lambda name: [ord(c) for c in name]) - for artifact_name in names: - artifact = self.new_artifact(artifact_name) - patterns = specs[artifact_name] - patterns += [r'baserock/%s\.' % artifact_name] - - with self.local_artifact_cache.put(artifact) as f: - contents = morphlib.bins.chunk_contents(destdir, patterns) - self.write_metadata(destdir, artifact_name, contents) - - self.app.status(msg='assembling chunk %s' % artifact_name, - chatty=True) - self.app.status(msg='assembling into %s' % f.name, - chatty=True) + for chunk_artifact_name, chunk_artifact \ + in source.artifacts.iteritems(): + file_paths = matches[chunk_artifact_name] + chunk_artifact = source.artifacts[chunk_artifact_name] + + def all_parents(path): + while path != '': + yield path + path = os.path.dirname(path) + + def parentify(filenames): + names = set() + for name in filenames: + names.update(all_parents(name)) + return sorted(names) + + parented_paths = \ + parentify(file_paths + + ['baserock/%s.meta' % chunk_artifact_name]) + + with self.local_artifact_cache.put(chunk_artifact) as f: + self.write_metadata(destdir, chunk_artifact_name, + parented_paths) + self.app.status(msg='Creating chunk artifact %(name)s', - name=artifact.name) - morphlib.bins.create_chunk(destdir, f, patterns) - built_artifacts.append(artifact) + name=chunk_artifact_name) + morphlib.bins.create_chunk(destdir, f, parented_paths) + built_artifacts.append(chunk_artifact) - files = os.listdir(destdir) + for dirname, subdirs, files in os.walk(destdir): if files: raise Exception('DESTDIR %s is not empty: %s' % (destdir, files)) @@ -458,8 +478,8 @@ class ChunkBuilder(BuilderBase): s = self.artifact.source extract_sources(self.app, self.repo_cache, s.repo, s.sha1, srcdir) -class StratumBuilder(BuilderBase): +class StratumBuilder(BuilderBase): '''Build stratum artifacts.''' def is_constituent(self, artifact): # pragma: no cover @@ -495,16 +515,14 @@ class StratumBuilder(BuilderBase): with self.build_watch('create-chunk-list'): lac = self.local_artifact_cache - artifact_name = self.artifact.source.morphology['name'] - artifact = self.new_artifact(artifact_name) - contents = [x.name for x in constituents] - meta = self.create_metadata(artifact_name, contents) - with lac.put_artifact_metadata(artifact, 'meta') as f: + meta = self.create_metadata(self.artifact.name, + [x.name for x in constituents]) + with lac.put_artifact_metadata(self.artifact, 'meta') as f: json.dump(meta, f, indent=4, sort_keys=True) - with self.local_artifact_cache.put(artifact) as f: + with self.local_artifact_cache.put(self.artifact) as f: json.dump([c.basename() for c in constituents], f) self.save_build_times() - return [artifact] + return [self.artifact] class SystemBuilder(BuilderBase): # pragma: no cover diff --git a/morphlib/cachekeycomputer.py b/morphlib/cachekeycomputer.py index d7e2e3b1..2312abc3 100644 --- a/morphlib/cachekeycomputer.py +++ b/morphlib/cachekeycomputer.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -90,6 +90,8 @@ class CacheKeyComputer(object): keys['build-mode'] = artifact.source.build_mode keys['prefix'] = artifact.source.prefix keys['tree'] = artifact.source.tree + keys['split-rules'] = [(a, [rgx.pattern for rgx in r._regexes]) + for (a, r) in artifact.source.split_rules] elif kind in ('system', 'stratum'): morphology = artifact.source.morphology le_dict = dict((k, morphology[k]) for k in morphology.keys()) diff --git a/morphlib/cachekeycomputer_tests.py b/morphlib/cachekeycomputer_tests.py index 2f033a7a..4e73e905 100644 --- a/morphlib/cachekeycomputer_tests.py +++ b/morphlib/cachekeycomputer_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -53,7 +53,8 @@ class CacheKeyComputerTests(unittest.TestCase): { "name": "chunk", "repo": "repo", - "ref": "original/ref" + "ref": "original/ref", + "build-depends": [] } ] }''', @@ -64,12 +65,14 @@ class CacheKeyComputerTests(unittest.TestCase): { "name": "chunk2", "repo": "repo", - "ref": "original/ref" + "ref": "original/ref", + "build-depends": [] }, { "name": "chunk3", "repo": "repo", - "ref": "original/ref" + "ref": "original/ref", + "build-depends": [] } ] }''', @@ -118,7 +121,6 @@ class CacheKeyComputerTests(unittest.TestCase): for artifact in self.artifacts: if artifact.name == name: return artifact - raise def test_compute_key_hashes_all_types(self): runcount = {'thing': 0, 'dict': 0, 'list': 0, 'tuple': 0} @@ -184,8 +186,8 @@ class CacheKeyComputerTests(unittest.TestCase): self.assertEqual(old_sha, new_sha) def test_same_morphology_added_to_source_pool_only_appears_once(self): - src = morphlib.source.Source('repo', 'original/ref', 'sha', 'tree', - '{"name": "chunk", "kind": "chunk"}', + m = morphlib.morph2.Morphology('{"name": "chunk", "kind": "chunk"}') + src = morphlib.source.Source('repo', 'original/ref', 'sha', 'tree', m, 'chunk.morph') sp = morphlib.sourcepool.SourcePool() sp.add(src) diff --git a/morphlib/localartifactcache_tests.py b/morphlib/localartifactcache_tests.py index d7743359..18d20612 100644 --- a/morphlib/localartifactcache_tests.py +++ b/morphlib/localartifactcache_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012,2013 Codethink Limited +# Copyright (C) 2012,2014 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 @@ -30,7 +30,7 @@ class LocalArtifactCacheTests(unittest.TestCase): morph = morphlib.morph2.Morphology( ''' { - "chunk": "chunk", + "name": "chunk", "kind": "chunk", "artifacts": { "chunk-runtime": [ diff --git a/morphlib/morph2.py b/morphlib/morph2.py index 0e0d9201..fd72aa94 100644 --- a/morphlib/morph2.py +++ b/morphlib/morph2.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -14,7 +14,6 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import copy import re import morphlib @@ -45,7 +44,7 @@ class Morphology(object): ('install-commands', None), ('post-install-commands', None), ('devices', None), - ('chunks', []), + ('products', []), ('max-jobs', None), ('build-system', 'manual') ], @@ -87,6 +86,14 @@ class Morphology(object): def __contains__(self, key): return key in self._dict + # Not covered by tests, since it's trivial, morph2 is going away + # and only exists so the new morphology validation code can use it. + def get(self, key, default=None): # pragma: no cover + try: + return self[key] + except KeyError: + return default + def get_commands(self, which): '''Return the commands to run from a morphology or the build system''' if self[which] is None: diff --git a/morphlib/morph2_tests.py b/morphlib/morph2_tests.py index aaa1d1cc..ba90313f 100644 --- a/morphlib/morph2_tests.py +++ b/morphlib/morph2_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -50,7 +50,7 @@ class MorphologyTests(unittest.TestCase): self.assertEqual(m['install-commands'], None) self.assertEqual(m['post-install-commands'], None) self.assertEqual(m['max-jobs'], None) - self.assertEqual(m['chunks'], []) + self.assertEqual(m['products'], []) if morphlib.got_yaml: def test_parses_simple_yaml_chunk(self): @@ -76,7 +76,7 @@ class MorphologyTests(unittest.TestCase): self.assertEqual(m['install-commands'], None) self.assertEqual(m['post-install-commands'], None) self.assertEqual(m['max-jobs'], None) - self.assertEqual(m['chunks'], []) + self.assertEqual(m['products'], []) def test_sets_stratum_chunks_repo_and_morph_from_name(self): m = Morphology(''' diff --git a/morphlib/morphloader.py b/morphlib/morphloader.py index e7c1d9ff..637544be 100644 --- a/morphlib/morphloader.py +++ b/morphlib/morphloader.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013 Codethink Limited +# Copyright (C) 2013-2014 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 @@ -44,16 +44,33 @@ class UnknownKindError(morphlib.Error): class MissingFieldError(morphlib.Error): - def __init__(self, field, morphology): + def __init__(self, field, morphology_name): + self.field = field + self.morphology_name = morphology_name self.msg = ( - 'Missing field %s from morphology %s' % (field, morphology)) + 'Missing field %s from morphology %s' % (field, morphology_name)) class InvalidFieldError(morphlib.Error): - def __init__(self, field, morphology): + def __init__(self, field, morphology_name): + self.field = field + self.morphology_name = morphology_name self.msg = ( - 'Field %s not allowed in morphology %s' % (field, morphology)) + 'Field %s not allowed in morphology %s' % (field, morphology_name)) + + +class InvalidTypeError(morphlib.Error): + + def __init__(self, field, expected, actual, morphology_name): + self.field = field + self.expected = expected + self.actual = actual + self.morphology_name = morphology_name + self.msg = ( + 'Field %s expected type %s, got %s in morphology %s' % + (field, expected, actual, morphology_name)) + class ObsoleteFieldsError(morphlib.Error): @@ -140,6 +157,16 @@ class EmptySystemError(morphlib.Error): self, 'System %(system_name)s has no strata.' % locals()) +class MultipleValidationErrors(morphlib.Error): + + def __init__(self, name, errors): + self.name = name + self.errors = errors + self.msg = 'Multiple errors when validating %(name)s:' + for error in errors: + self.msg += ('\t' + str(error)) + + class MorphologyLoader(object): '''Load morphologies from disk, or save them back to disk.''' @@ -185,7 +212,7 @@ class MorphologyLoader(object): 'install-commands': [], 'post-install-commands': [], 'devices': [], - 'chunks': [], + 'products': [], 'max-jobs': None, 'build-system': 'manual', }, @@ -193,6 +220,7 @@ class MorphologyLoader(object): 'chunks': [], 'description': '', 'build-depends': [], + 'products': [], }, 'system': { 'description': '', @@ -356,8 +384,84 @@ class MorphologyLoader(object): spec.get('alias', spec['name']), morph.filename) - def _validate_chunk(self, morph): - pass + @classmethod + def _validate_chunk(cls, morphology): + errors = [] + + if 'products' in morphology: + cls._validate_products(morphology['name'], + morphology['products'], errors) + + if len(errors) == 1: + raise errors[0] + elif errors: + raise MultipleValidationErrors(morphology['name'], errors) + + @classmethod + def _validate_products(cls, morphology_name, products, errors): + '''Validate the products field is of the correct type.''' + if (not isinstance(products, collections.Iterable) + or isinstance(products, collections.Mapping)): + raise InvalidTypeError('products', list, + type(products), morphology_name) + + for spec_index, spec in enumerate(products): + + if not isinstance(spec, collections.Mapping): + e = InvalidTypeError('products[%d]' % spec_index, + dict, type(spec), morphology_name) + errors.append(e) + continue + + cls._validate_products_spec_fields_exist(morphology_name, + spec_index, spec, errors) + + if 'include' in spec: + cls._validate_products_specs_include( + morphology_name, spec_index, spec['include'], errors) + + product_spec_required_fields = ('artifact', 'include') + @classmethod + def _validate_products_spec_fields_exist( + cls, morphology_name, spec_index, spec, errors): + + given_fields = sorted(spec.iterkeys()) + missing = (field for field in cls.product_spec_required_fields + if field not in given_fields) + for field in missing: + e = MissingFieldError('products[%d].%s' % (spec_index, field), + morphology_name) + errors.append(e) + unexpected = (field for field in given_fields + if field not in cls.product_spec_required_fields) + for field in unexpected: + e = InvalidFieldError('products[%d].%s' % (spec_index, field), + morphology_name) + errors.append(e) + + @classmethod + def _validate_products_specs_include(cls, morphology_name, spec_index, + include_patterns, errors): + '''Validate that products' include field is a list of strings.''' + # Allow include to be most iterables, but not a mapping + # or a string, since iter of a mapping is just the keys, + # and the iter of a string is a 1 character length string, + # which would also validate as an iterable of strings. + if (not isinstance(include_patterns, collections.Iterable) + or isinstance(include_patterns, collections.Mapping) + or isinstance(include_patterns, basestring)): + + e = InvalidTypeError('products[%d].include' % spec_index, list, + type(include_patterns), morphology_name) + errors.append(e) + else: + for pattern_index, pattern in enumerate(include_patterns): + pattern_path = ('products[%d].include[%d]' % + (spec_index, pattern_index)) + if not isinstance(pattern, basestring): + e = InvalidTypeError(pattern_path, str, + type(pattern), morphology_name) + errors.append(e) def _require_field(self, field, morphology): if field not in morphology: diff --git a/morphlib/morphloader_tests.py b/morphlib/morphloader_tests.py index 8b87467a..c2fbc5e8 100644 --- a/morphlib/morphloader_tests.py +++ b/morphlib/morphloader_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013 Codethink Limited +# Copyright (C) 2013-2014 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 @@ -78,6 +78,99 @@ build-system: dummy self.assertRaises( morphlib.morphloader.InvalidFieldError, self.loader.validate, m) + def test_validate_requires_products_list(self): + m = morphlib.morph3.Morphology( + kind='chunk', + name='foo', + products={ + 'foo-runtime': ['.'], + 'foo-devel': ['.'], + }) + with self.assertRaises(morphlib.morphloader.InvalidTypeError) as cm: + self.loader.validate(m) + e = cm.exception + self.assertEqual(e.field, 'products') + self.assertEqual(e.expected, list) + self.assertEqual(e.actual, dict) + self.assertEqual(e.morphology_name, 'foo') + + def test_validate_requires_products_list_of_mappings(self): + m = morphlib.morph3.Morphology( + kind='chunk', + name='foo', + products=[ + 'foo-runtime', + ]) + with self.assertRaises(morphlib.morphloader.InvalidTypeError) as cm: + self.loader.validate(m) + e = cm.exception + self.assertEqual(e.field, 'products[0]') + self.assertEqual(e.expected, dict) + self.assertEqual(e.actual, str) + self.assertEqual(e.morphology_name, 'foo') + + def test_validate_requires_products_list_required_fields(self): + m = morphlib.morph3.Morphology( + kind='chunk', + name='foo', + products=[ + { + 'factiart': 'foo-runtime', + 'cludein': [], + } + ]) + with self.assertRaises(morphlib.morphloader.MultipleValidationErrors) \ + as cm: + self.loader.validate(m) + exs = cm.exception.errors + self.assertEqual(type(exs[0]), morphlib.morphloader.MissingFieldError) + self.assertEqual(exs[0].field, 'products[0].artifact') + self.assertEqual(type(exs[1]), morphlib.morphloader.MissingFieldError) + self.assertEqual(exs[1].field, 'products[0].include') + self.assertEqual(type(exs[2]), morphlib.morphloader.InvalidFieldError) + self.assertEqual(exs[2].field, 'products[0].cludein') + self.assertEqual(type(exs[3]), morphlib.morphloader.InvalidFieldError) + self.assertEqual(exs[3].field, 'products[0].factiart') + + def test_validate_requires_products_list_include_is_list(self): + m = morphlib.morph3.Morphology( + kind='chunk', + name='foo', + products=[ + { + 'artifact': 'foo-runtime', + 'include': '.*', + } + ]) + with self.assertRaises(morphlib.morphloader.InvalidTypeError) as cm: + self.loader.validate(m) + ex = cm.exception + self.assertEqual(ex.field, 'products[0].include') + self.assertEqual(ex.expected, list) + self.assertEqual(ex.actual, str) + self.assertEqual(ex.morphology_name, 'foo') + + def test_validate_requires_products_list_include_is_list_of_strings(self): + m = morphlib.morph3.Morphology( + kind='chunk', + name='foo', + products=[ + { + 'artifact': 'foo-runtime', + 'include': [ + 123, + ] + } + ]) + with self.assertRaises(morphlib.morphloader.InvalidTypeError) as cm: + self.loader.validate(m) + ex = cm.exception + self.assertEqual(ex.field, 'products[0].include[0]') + self.assertEqual(ex.expected, str) + self.assertEqual(ex.actual, int) + self.assertEqual(ex.morphology_name, 'foo') + + def test_fails_to_validate_stratum_with_no_fields(self): m = morphlib.morph3.Morphology({ 'kind': 'stratum', @@ -438,7 +531,7 @@ name: foo 'pre-install-commands': [], 'post-install-commands': [], - 'chunks': [], + 'products': [], 'devices': [], 'max-jobs': None, }) @@ -491,6 +584,7 @@ name: foo 'build-depends': [], }, ], + 'products': [], }) def test_unsets_defaults_for_strata(self): diff --git a/morphlib/morphologyfactory.py b/morphlib/morphologyfactory.py index 5afafefb..3462dd36 100644 --- a/morphlib/morphologyfactory.py +++ b/morphlib/morphologyfactory.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -167,9 +167,12 @@ class MorphologyFactory(object): def _check_and_tweak_chunk(self, morphology, reponame, sha1, filename): '''Check and tweak a chunk morphology.''' - if 'chunks' in morphology and len(morphology['chunks']) > 1: - morphology.builds_artifacts = morphology['chunks'].keys() + if 'products' in morphology and len(morphology['products']) > 1: + morphology.builds_artifacts = [d['artifact'] + for d in morphology['products']] else: morphology.builds_artifacts = [morphology['name']] morphology.needs_artifact_metadata_cached = False + + morphlib.morphloader.MorphologyLoader._validate_chunk(morphology) diff --git a/morphlib/morphologyfactory_tests.py b/morphlib/morphologyfactory_tests.py index 6e1e67d3..30504e00 100644 --- a/morphlib/morphologyfactory_tests.py +++ b/morphlib/morphologyfactory_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -50,10 +50,16 @@ class FakeLocalRepo(object): "name": "chunk-split", "kind": "chunk", "build-system": "bar", - "chunks": { - "chunk-split-runtime": [], - "chunk-split-devel": [] - } + "products": [ + { + "artifact": "chunk-split-runtime", + "include": [] + }, + { + "artifact": "chunk-split-devel", + "include": [] + } + ] }''', 'stratum.morph': '''{ "name": "stratum", diff --git a/morphlib/remoteartifactcache_tests.py b/morphlib/remoteartifactcache_tests.py index e7f45f58..d11bf264 100644 --- a/morphlib/remoteartifactcache_tests.py +++ b/morphlib/remoteartifactcache_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -27,7 +27,7 @@ class RemoteArtifactCacheTests(unittest.TestCase): morph = morphlib.morph2.Morphology( ''' { - "chunk": "chunk", + "name": "chunk", "kind": "chunk", "artifacts": { "chunk-runtime": [ diff --git a/morphlib/source.py b/morphlib/source.py index 99b0a993..75a2e4de 100644 --- a/morphlib/source.py +++ b/morphlib/source.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -30,6 +30,8 @@ class Source(object): * ``tree`` -- the SHA1 of the tree corresponding to the commit * ``morphology`` -- the in-memory representation of the morphology we use * ``filename`` -- basename of the morphology filename + * ``artifacts`` -- the set of artifacts this source produces. + * ``split_rules`` -- rules for splitting the source's produced artifacts ''' @@ -43,6 +45,13 @@ class Source(object): self.morphology = morphology self.filename = filename + kind = morphology['kind'] + unifier = getattr(morphlib.artifactsplitrule, + 'unify_%s_matches' % kind) + self.split_rules = unifier(morphology) + self.artifacts = {name: morphlib.artifact.Artifact(self, name) + for name in self.split_rules.artifacts} + def __str__(self): # pragma: no cover return '%s|%s|%s' % (self.repo_name, self.original_ref, diff --git a/tests.as-root/branch-from-image-works.script b/tests.as-root/branch-from-image-works.script index 942301e8..c9d50bbb 100755 --- a/tests.as-root/branch-from-image-works.script +++ b/tests.as-root/branch-from-image-works.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2013 Codethink Limited +# Copyright (C) 2013-2014 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 @@ -34,7 +34,7 @@ tar -xf "$tar" -C "$extracted" get_sha1(){ sed -nre '/sha1/s/^.*([0-9a-f]{40}).*$/\1/p' "$1" } -hello_chunk_commit=$(get_sha1 "$extracted/baserock/hello.meta") +hello_chunk_commit=$(get_sha1 "$extracted/baserock/hello-bins.meta") # Make a commit so that petrifying from HEAD is detectable chunkrepo="$DATADIR/chunk-repo" diff --git a/tests.as-root/metadata-includes-repo-alias.script b/tests.as-root/metadata-includes-repo-alias.script index 511222e2..9e4a5d98 100755 --- a/tests.as-root/metadata-includes-repo-alias.script +++ b/tests.as-root/metadata-includes-repo-alias.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2013 Codethink Limited +# Copyright (C) 2013-2014 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 @@ -39,11 +39,11 @@ cd "$extracted/baserock" # Check for test:morphs in System and Stratum #grep -q -F -e test:morphs hello-tarball.meta # tarball bug -grep -q -F -e test:morphs hello-stratum.meta -grep -q -F -e test:morphs linux-stratum.meta +grep -q -F -e test:morphs hello-stratum-runtime.meta +grep -q -F -e test:morphs linux-stratum-runtime.meta # Check for test:kernel-repo in linux -grep -q -F -e test:kernel-repo linux.meta +grep -q -F -e test:kernel-repo linux-misc.meta # Check for test:chunk-repo in hello -grep -q -F -e test:chunk-repo hello.meta +grep -q -F -e test:chunk-repo hello-bins.meta diff --git a/tests.as-root/run-in-artifact-with-different-artifacts.script b/tests.as-root/run-in-artifact-with-different-artifacts.script index 0016b278..ff944af4 100755 --- a/tests.as-root/run-in-artifact-with-different-artifacts.script +++ b/tests.as-root/run-in-artifact-with-different-artifacts.script @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -27,9 +27,9 @@ set -eu test:morphs master linux-system system=$(find "$DATADIR/cache/artifacts" -maxdepth 1 -name '*.system.*-rootfs') -chunk=$(find "$DATADIR/cache/artifacts" -maxdepth 1 -name '*.chunk.linux') +chunk=$(find "$DATADIR/cache/artifacts" -maxdepth 1 -name '*.chunk.linux-misc') stratum=$(find "$DATADIR/cache/artifacts" -maxdepth 1 \ - -name '*.stratum.linux-stratum') + -name '*.stratum.linux-stratum-runtime') # Run 'run-in-artifact' with the system artifact. echo "System:" diff --git a/tests.as-root/run-in-artifact-with-different-artifacts.stderr b/tests.as-root/run-in-artifact-with-different-artifacts.stderr index 5fc4fb0f..236954f8 100644 --- a/tests.as-root/run-in-artifact-with-different-artifacts.stderr +++ b/tests.as-root/run-in-artifact-with-different-artifacts.stderr @@ -1 +1 @@ -ERROR: Artifact TMP/cache/artifacts/67596ea97123eeb66afce92675dbb63eb8fd840d01f38902d4bf6f573b609499.stratum.linux-stratum cannot be extracted or mounted +ERROR: Artifact TMP/cache/artifacts/4a8b7a698ae79f417b1ff1541b883bd9f99fdf287a0d1a4176e7353dbe51a5fa.stratum.linux-stratum-runtime cannot be extracted or mounted diff --git a/tests.as-root/run-in-artifact-with-different-artifacts.stdout b/tests.as-root/run-in-artifact-with-different-artifacts.stdout index 281ab109..7473990b 100644 --- a/tests.as-root/run-in-artifact-with-different-artifacts.stdout +++ b/tests.as-root/run-in-artifact-with-different-artifacts.stdout @@ -1,14 +1,32 @@ System: -hello-stratum.meta -hello.meta -linux-stratum.meta +hello-bins.meta +hello-devel.meta +hello-doc.meta +hello-libs.meta +hello-locale.meta +hello-misc.meta +hello-stratum-devel.meta +hello-stratum-runtime.meta +linux-bins.meta +linux-devel.meta +linux-doc.meta +linux-libs.meta +linux-locale.meta +linux-misc.meta +linux-stratum-devel.meta +linux-stratum-runtime.meta linux-system-rootfs.meta -linux.meta -tools-stratum.meta -tools.meta +tools-bins.meta +tools-devel.meta +tools-doc.meta +tools-libs.meta +tools-locale.meta +tools-misc.meta +tools-stratum-devel.meta +tools-stratum-runtime.meta Chunk: -linux.meta +linux-misc.meta Stratum: Failed diff --git a/tests.as-root/system-overlap.stdout b/tests.as-root/system-overlap.stdout index fe106ad9..f67d54c8 100644 --- a/tests.as-root/system-overlap.stdout +++ b/tests.as-root/system-overlap.stdout @@ -1,3 +1,3 @@ -foo-barqux-stratum foo-baz-stratum +foo-barqux-stratum-runtime foo-baz-stratum-runtime bin/foo bin/bar diff --git a/tests.as-root/tarball-image-is-sensible.stdout b/tests.as-root/tarball-image-is-sensible.stdout index c896c847..4141dee8 100644 --- a/tests.as-root/tarball-image-is-sensible.stdout +++ b/tests.as-root/tarball-image-is-sensible.stdout @@ -1,11 +1,29 @@ ./baserock/ -./baserock/hello-stratum.meta +./baserock/hello-bins.meta +./baserock/hello-devel.meta +./baserock/hello-doc.meta +./baserock/hello-libs.meta +./baserock/hello-locale.meta +./baserock/hello-misc.meta +./baserock/hello-stratum-devel.meta +./baserock/hello-stratum-runtime.meta ./baserock/hello-tarball-rootfs.meta -./baserock/hello.meta -./baserock/link-stratum.meta -./baserock/links.meta -./baserock/linux-stratum.meta -./baserock/linux.meta +./baserock/link-stratum-devel.meta +./baserock/link-stratum-runtime.meta +./baserock/links-bins.meta +./baserock/links-devel.meta +./baserock/links-doc.meta +./baserock/links-libs.meta +./baserock/links-locale.meta +./baserock/links-misc.meta +./baserock/linux-bins.meta +./baserock/linux-devel.meta +./baserock/linux-doc.meta +./baserock/linux-libs.meta +./baserock/linux-locale.meta +./baserock/linux-misc.meta +./baserock/linux-stratum-devel.meta +./baserock/linux-stratum-runtime.meta ./bin/ ./bin/hello* ./bin/true diff --git a/tests.build/bootstrap-mode.script b/tests.build/bootstrap-mode.script index 923fb21f..0ac66220 100755 --- a/tests.build/bootstrap-mode.script +++ b/tests.build/bootstrap-mode.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -29,11 +29,15 @@ set -eu test:morphs-repo master hello-system cd "$DATADIR/cache/artifacts" -echo "build-essential stratum:" -stratum=$(ls *.stratum.build-essential) -cat $stratum | sed 's/[a-f0-9]\{64\}/xxxx/g' -echo +echo "build-essential strata:" +for stratum in $(find . -regex '.*\.stratum\.build-essential-[^.]*$' | sort) +do + echo "$stratum" + sed 's/[a-f0-9]\{64\}/xxxx/g' "$stratum" + echo +done echo echo "hello-system:" system=$(ls *hello-system-rootfs) -tar tf "$system" | LC_ALL=C sort | sed '/^\.\/./s:^\./::' +tar tf "$system" | LC_ALL=C sort -u | sed '/^\.\/./s:^\./::' \ + | grep -v '^baserock' diff --git a/tests.build/bootstrap-mode.stdout b/tests.build/bootstrap-mode.stdout index 329cdd78..b59d0029 100644 --- a/tests.build/bootstrap-mode.stdout +++ b/tests.build/bootstrap-mode.stdout @@ -1,12 +1,11 @@ -build-essential stratum: -["xxxx.chunk.cc"] +build-essential strata: +./5bbfd4cb94017e7b72e20ee4f91a76bed8085aa96176cf87ecce54ec71d5ddae.stratum.build-essential-devel +["xxxx.chunk.cc-devel", "xxxx.chunk.cc-doc"] +./c8fe8efd4f8c6edcd309bb2cf0a308c93dfd905bbff64be98c5b07f350951fde.stratum.build-essential-runtime +["xxxx.chunk.cc-bins", "xxxx.chunk.cc-libs", "xxxx.chunk.cc-locale", "xxxx.chunk.cc-misc"] hello-system: ./ -baserock/ -baserock/hello-stratum.meta -baserock/hello-system-rootfs.meta -baserock/hello.meta etc/ etc/fstab etc/os-release diff --git a/tests.build/build-stratum-with-submodules.script b/tests.build/build-stratum-with-submodules.script index c3c00578..c996e769 100755 --- a/tests.build/build-stratum-with-submodules.script +++ b/tests.build/build-stratum-with-submodules.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -71,5 +71,5 @@ EOF test:morphs-repo master hello-system system=$(ls "$DATADIR/cache/artifacts/"*hello-system-rootfs) -tar tf $system | LC_ALL=C sort | sed '/^\.\/./s:^\./::' +tar tf $system | LC_ALL=C sort | sed '/^\.\/./s:^\./::' | grep -v '^baserock/' diff --git a/tests.build/build-stratum-with-submodules.stdout b/tests.build/build-stratum-with-submodules.stdout index bf9836d7..6dda5049 100644 --- a/tests.build/build-stratum-with-submodules.stdout +++ b/tests.build/build-stratum-with-submodules.stdout @@ -1,8 +1,4 @@ ./ -baserock/ -baserock/hello-stratum.meta -baserock/hello-system-rootfs.meta -baserock/parent.meta etc/ etc/fstab etc/os-release diff --git a/tests.build/build-system-autotools.script b/tests.build/build-system-autotools.script index c2171750..ba5cd32f 100755 --- a/tests.build/build-system-autotools.script +++ b/tests.build/build-system-autotools.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -52,7 +52,5 @@ git commit --quiet -m "Convert hello to an autotools project" for chunk in "$DATADIR/cache/artifacts/"*.chunk.* do - echo "$chunk:" | sed 's/[^.]*//' - tar -tf "$chunk" | LC_ALL=C sort | sed '/^\.\/./s:^\./::' - echo -done + tar -tf "$chunk" +done | LC_ALL=C sort -u | sed '/^\.\/./s:^\./::' | grep -Ee '^(bin|etc)' diff --git a/tests.build/build-system-autotools.stdout b/tests.build/build-system-autotools.stdout index 8077cac2..683441c9 100644 --- a/tests.build/build-system-autotools.stdout +++ b/tests.build/build-system-autotools.stdout @@ -1,8 +1,3 @@ -.chunk.hello: -./ -baserock/ -baserock/hello.meta bin/ bin/hello etc/ - diff --git a/tests.build/build-system-cmake.script b/tests.build/build-system-cmake.script index 00b9ed23..ab5186d7 100755 --- a/tests.build/build-system-cmake.script +++ b/tests.build/build-system-cmake.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -53,7 +53,5 @@ git commit --quiet -m "Convert hello to a cmake project" for chunk in "$DATADIR/cache/artifacts/"*.chunk.* do - echo "$chunk:" | sed 's/[^.]*//' - tar -tf "$chunk" | LC_ALL=C sort | sed '/^\.\/./s:^\./::' - echo -done + tar -tf "$chunk" +done | LC_ALL=C sort -u | sed '/^\.\/./s:^\./::' | grep -Ee '^(usr/)?(bin|etc)' diff --git a/tests.build/build-system-cmake.stdout b/tests.build/build-system-cmake.stdout index ccf80a86..3410b113 100644 --- a/tests.build/build-system-cmake.stdout +++ b/tests.build/build-system-cmake.stdout @@ -1,8 +1,2 @@ -.chunk.hello: -./ -baserock/ -baserock/hello.meta -usr/ usr/bin/ usr/bin/hello - diff --git a/tests.build/build-system-cpan.script b/tests.build/build-system-cpan.script index b1823eb5..f66d4027 100755 --- a/tests.build/build-system-cpan.script +++ b/tests.build/build-system-cpan.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -81,7 +81,5 @@ git commit -q -m "Set custom install prefix for hello" for chunk in "$DATADIR/cache/artifacts/"*.chunk.* do - echo "$chunk:" | sed 's/[^.]*//' - tar -tf "$chunk" | LC_ALL=C sort | - sed -e '/^\.\/./s:^\./::' | grep -F "bin/hello" -done + tar -tf "$chunk" +done | LC_ALL=C sort | sed '/^\.\/./s:^\./::' | grep -F 'bin/hello' diff --git a/tests.build/build-system-cpan.stdout b/tests.build/build-system-cpan.stdout index 5cbe4c73..180e949b 100644 --- a/tests.build/build-system-cpan.stdout +++ b/tests.build/build-system-cpan.stdout @@ -1,2 +1 @@ -.chunk.hello: bin/hello diff --git a/tests.build/build-system-python-distutils.script b/tests.build/build-system-python-distutils.script index a0469528..e1dccb4b 100755 --- a/tests.build/build-system-python-distutils.script +++ b/tests.build/build-system-python-distutils.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -79,10 +79,8 @@ git commit -q -m "Set custom install prefix for hello" for chunk in "$DATADIR/cache/artifacts/"*.chunk.* do - echo "$chunk:" | sed 's/[^.]*//' - tar -tf "$chunk" | LC_ALL=C sort | sed '/^\.\/./s:^\./::' - echo -done | + tar -tf "$chunk" +done | LC_ALL=C sort -u | sed '/^\.\/./s:^\./::' | grep -Ee '^(bin|lib)' | sed -e 's:^local/::' \ -e 's:lib/python2.[6-9]/:lib/python2.x/:' \ -e 's:/hello-0\.0\.0[^/]*\.egg-info$:/hello.egg-info/:' \ diff --git a/tests.build/build-system-python-distutils.stdout b/tests.build/build-system-python-distutils.stdout index 4d4abdbb..4d4c3a1e 100644 --- a/tests.build/build-system-python-distutils.stdout +++ b/tests.build/build-system-python-distutils.stdout @@ -1,7 +1,3 @@ -.chunk.hello: -./ -baserock/ -baserock/hello.meta bin/ bin/hello lib/ diff --git a/tests.build/build-system.script b/tests.build/build-system.script index 75b9d0d0..56d80735 100755 --- a/tests.build/build-system.script +++ b/tests.build/build-system.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -24,4 +24,4 @@ set -eu test:morphs-repo master hello-system system=$(ls "$DATADIR/cache/artifacts/"*hello-system-rootfs) -tar tf $system | LC_ALL=C sort | sed '/^\.\/./s:^\./::' +tar tf $system | LC_ALL=C sort | sed '/^\.\/./s:^\./::' | grep -v '^baserock/' diff --git a/tests.build/build-system.stdout b/tests.build/build-system.stdout index 3d5201ee..2e8270dc 100644 --- a/tests.build/build-system.stdout +++ b/tests.build/build-system.stdout @@ -1,8 +1,4 @@ ./ -baserock/ -baserock/hello-stratum.meta -baserock/hello-system-rootfs.meta -baserock/hello.meta bin/ bin/hello etc/ diff --git a/tests.build/morphless-chunks.script b/tests.build/morphless-chunks.script index c9294c3e..9a8b41dd 100755 --- a/tests.build/morphless-chunks.script +++ b/tests.build/morphless-chunks.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 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 @@ -44,7 +44,5 @@ git commit -q -m "Convert hello into an autodetectable chunk" for chunk in "$DATADIR/cache/artifacts/"*.chunk.* do - echo "$chunk:" | sed 's/[^.]*//' - tar -tf "$chunk" | LC_ALL=C sort | sed '/^\.\/./s:^\./::' - echo -done + tar -tf "$chunk" +done | cat >/dev/null # No files get installed apart from metadata diff --git a/tests.build/morphless-chunks.stdout b/tests.build/morphless-chunks.stdout index 22292c14..e69de29b 100644 --- a/tests.build/morphless-chunks.stdout +++ b/tests.build/morphless-chunks.stdout @@ -1,5 +0,0 @@ -.chunk.hello: -./ -baserock/ -baserock/hello.meta - diff --git a/tests.build/prefix.script b/tests.build/prefix.script index e9b8ecd2..ca9648c9 100755 --- a/tests.build/prefix.script +++ b/tests.build/prefix.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2013 Codethink Limited +# Copyright (C) 2013-2014 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 @@ -82,6 +82,6 @@ git commit -q -m "Update stratum" test:morphs-repo master hello-system cd "$DATADIR/cache/artifacts" -first_chunk=$(ls -1 *.chunk.xyzzy | cut -c -64) -second_chunk=$(ls -1 *.chunk.plugh | 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 306c16f2..0014e545 100755 --- a/tests.build/rebuild-cached-stratum.script +++ b/tests.build/rebuild-cached-stratum.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -30,7 +30,7 @@ cache="$DATADIR/cache/artifacts" git checkout --quiet farrokh && git checkout --quiet -b rebuild-cached-stratum) -# Make a branch in the morphs repo and modify the stratum to refer to +# Make a branch in the morphs repo and modify the stratum to refer to # the new chunk branch. (cd "$DATADIR/morphs-repo" && git checkout --quiet -b rebuild-cached-stratum && @@ -42,18 +42,18 @@ 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/^[^.]*\./ /' | - LC_ALL=C sort) +(cd "$cache" && ls *.chunk.* *hello-stratum-* | sed 's/^[^.]*\./ /' | + LC_ALL=C sort -u) # Change the chunk. -(cd "$DATADIR/chunk-repo" && - echo >> hello.c && +(cd "$DATADIR/chunk-repo" && + echo >> hello.c && git commit --quiet -am change) # Rebuild. "$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/^[^.]*\./ /' | - LC_ALL=C sort) +(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 eee106f5..9c53ee60 100644 --- a/tests.build/rebuild-cached-stratum.stdout +++ b/tests.build/rebuild-cached-stratum.stdout @@ -1,8 +1,22 @@ first build: - chunk.hello - stratum.hello-stratum + 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 second build: - chunk.hello - chunk.hello - stratum.hello-stratum - stratum.hello-stratum + 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 diff --git a/tests.build/stratum-overlap-writes-overlap.script b/tests.build/stratum-overlap-writes-overlap.script index ca06454b..fe4ed4ee 100755 --- a/tests.build/stratum-overlap-writes-overlap.script +++ b/tests.build/stratum-overlap-writes-overlap.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 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 @@ -27,9 +27,9 @@ cache="$DATADIR/cache/artifacts" "$SRCDIR/scripts/test-morph" build-morphology \ test:morphs-repo overlap hello-system > /dev/null "$SRCDIR/scripts/list-overlaps" groups \ - "$cache"/*.stratum.hello-stratum.overlaps | + "$cache"/*.stratum.hello-stratum-*.overlaps | while IFS='\n' read overlaps; do echo $overlaps "$SRCDIR/scripts/list-overlaps" list-files \ - "$cache"/*.stratum.hello-stratum.overlaps $overlaps + "$cache"/*.stratum.hello-stratum-*.overlaps $overlaps done diff --git a/tests.build/stratum-overlap-writes-overlap.stdout b/tests.build/stratum-overlap-writes-overlap.stdout index 40485659..1e36ca83 100644 --- a/tests.build/stratum-overlap-writes-overlap.stdout +++ b/tests.build/stratum-overlap-writes-overlap.stdout @@ -1,4 +1,4 @@ -overlap-foo-baz overlap-foobar overlap-fooqux +overlap-foo-baz-bins overlap-foobar-bins overlap-fooqux-bins bin/foo -overlap-foo-baz overlap-foobar +overlap-foo-baz-bins overlap-foobar-bins bin/bar diff --git a/tests/show-dependencies.stdout b/tests/show-dependencies.stdout index ab1453db..2c70d30a 100644 --- a/tests/show-dependencies.stdout +++ b/tests/show-dependencies.stdout @@ -1,100 +1,1680 @@ dependency graph for test-repo|master|xfce-system: test-repo|master|xfce-system|xfce-system-rootfs - -> test-repo|master|xfce-core|xfce-core - test-repo|master|xfce-core|xfce-core - -> test-repo|master|exo|exo - -> test-repo|master|garcon|garcon - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|libxfce4util|libxfce4util - -> test-repo|master|thunar|thunar - -> test-repo|master|tumbler|tumbler - -> test-repo|master|xfce4-appfinder|xfce4-appfinder - -> test-repo|master|xfce4-panel|xfce4-panel - -> test-repo|master|xfce4-session|xfce4-session - -> test-repo|master|xfce4-settings|xfce4-settings - -> test-repo|master|xfconf|xfconf - -> test-repo|master|xfdesktop|xfdesktop - -> test-repo|master|xfwm4|xfwm4 - test-repo|master|gtk-xfce-engine|gtk-xfce-engine - -> test-repo|master|garcon|garcon - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|xfconf|xfconf - test-repo|master|xfce4-appfinder|xfce4-appfinder - -> test-repo|master|garcon|garcon - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|xfconf|xfconf - test-repo|master|xfdesktop|xfdesktop - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|xfconf|xfconf - test-repo|master|xfwm4|xfwm4 - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|xfconf|xfconf - test-repo|master|xfce4-session|xfce4-session - -> test-repo|master|exo|exo - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|xfconf|xfconf - test-repo|master|xfce4-settings|xfce4-settings - -> test-repo|master|exo|exo - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|xfconf|xfconf - test-repo|master|xfce4-panel|xfce4-panel - -> test-repo|master|exo|exo - -> test-repo|master|garcon|garcon - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - test-repo|master|tumbler|tumbler - -> test-repo|master|gtk-stack|gtk-stack - test-repo|master|thunar|thunar - -> test-repo|master|exo|exo - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - test-repo|master|garcon|garcon - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4util|libxfce4util - test-repo|master|exo|exo - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4util|libxfce4util - test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|xfconf|xfconf - test-repo|master|xfconf|xfconf - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4util|libxfce4util - test-repo|master|libxfce4util|libxfce4util - -> test-repo|master|gtk-stack|gtk-stack - test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|cairo|cairo - -> test-repo|master|dbus-glib|dbus-glib - -> test-repo|master|dbus|dbus - -> test-repo|master|fontconfig|fontconfig - -> test-repo|master|freetype|freetype - -> test-repo|master|gdk-pixbuf|gdk-pixbuf - -> test-repo|master|glib|glib - -> test-repo|master|gtk|gtk - -> test-repo|master|pango|pango - test-repo|master|dbus-glib|dbus-glib - -> test-repo|master|dbus|dbus - -> test-repo|master|glib|glib - test-repo|master|dbus|dbus - test-repo|master|gtk|gtk - -> test-repo|master|cairo|cairo - -> test-repo|master|gdk-pixbuf|gdk-pixbuf - -> test-repo|master|glib|glib - -> test-repo|master|pango|pango - test-repo|master|gdk-pixbuf|gdk-pixbuf - -> test-repo|master|glib|glib - test-repo|master|glib|glib - test-repo|master|pango|pango - -> test-repo|master|fontconfig|fontconfig - -> test-repo|master|freetype|freetype - test-repo|master|cairo|cairo - test-repo|master|fontconfig|fontconfig - test-repo|master|freetype|freetype + -> test-repo|master|xfce-core|xfce-core-devel + -> test-repo|master|xfce-core|xfce-core-runtime + test-repo|master|xfce-core|xfce-core-runtime + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine-bins + -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine-libs + -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine-locale + -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine-misc + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + -> test-repo|master|thunar|thunar-bins + -> test-repo|master|thunar|thunar-libs + -> test-repo|master|thunar|thunar-locale + -> test-repo|master|thunar|thunar-misc + -> test-repo|master|tumbler|tumbler-bins + -> test-repo|master|tumbler|tumbler-libs + -> test-repo|master|tumbler|tumbler-locale + -> test-repo|master|tumbler|tumbler-misc + -> test-repo|master|xfce4-appfinder|xfce4-appfinder-bins + -> test-repo|master|xfce4-appfinder|xfce4-appfinder-libs + -> test-repo|master|xfce4-appfinder|xfce4-appfinder-locale + -> test-repo|master|xfce4-appfinder|xfce4-appfinder-misc + -> test-repo|master|xfce4-panel|xfce4-panel-bins + -> test-repo|master|xfce4-panel|xfce4-panel-libs + -> test-repo|master|xfce4-panel|xfce4-panel-locale + -> test-repo|master|xfce4-panel|xfce4-panel-misc + -> test-repo|master|xfce4-session|xfce4-session-bins + -> test-repo|master|xfce4-session|xfce4-session-libs + -> test-repo|master|xfce4-session|xfce4-session-locale + -> test-repo|master|xfce4-session|xfce4-session-misc + -> test-repo|master|xfce4-settings|xfce4-settings-bins + -> test-repo|master|xfce4-settings|xfce4-settings-libs + -> test-repo|master|xfce4-settings|xfce4-settings-locale + -> test-repo|master|xfce4-settings|xfce4-settings-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + -> test-repo|master|xfdesktop|xfdesktop-bins + -> test-repo|master|xfdesktop|xfdesktop-libs + -> test-repo|master|xfdesktop|xfdesktop-locale + -> test-repo|master|xfdesktop|xfdesktop-misc + -> test-repo|master|xfwm4|xfwm4-bins + -> test-repo|master|xfwm4|xfwm4-libs + -> test-repo|master|xfwm4|xfwm4-locale + -> test-repo|master|xfwm4|xfwm4-misc + test-repo|master|gtk-xfce-engine|gtk-xfce-engine-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|gtk-xfce-engine|gtk-xfce-engine-locale + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|gtk-xfce-engine|gtk-xfce-engine-libs + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|gtk-xfce-engine|gtk-xfce-engine-bins + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-appfinder|xfce4-appfinder-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-appfinder|xfce4-appfinder-locale + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-appfinder|xfce4-appfinder-libs + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-appfinder|xfce4-appfinder-bins + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfdesktop|xfdesktop-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfdesktop|xfdesktop-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfdesktop|xfdesktop-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfdesktop|xfdesktop-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfwm4|xfwm4-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfwm4|xfwm4-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfwm4|xfwm4-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfwm4|xfwm4-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-session|xfce4-session-misc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-session|xfce4-session-locale + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-session|xfce4-session-libs + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-session|xfce4-session-bins + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-settings|xfce4-settings-misc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-settings|xfce4-settings-locale + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-settings|xfce4-settings-libs + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-settings|xfce4-settings-bins + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-panel|xfce4-panel-misc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|xfce4-panel|xfce4-panel-locale + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|xfce4-panel|xfce4-panel-libs + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|xfce4-panel|xfce4-panel-bins + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|tumbler|tumbler-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|tumbler|tumbler-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|tumbler|tumbler-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|tumbler|tumbler-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|thunar|thunar-misc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|thunar|thunar-locale + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|thunar|thunar-libs + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|thunar|thunar-bins + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|xfce-core|xfce-core-devel + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine-devel + -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine-doc + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|thunar|thunar-devel + -> test-repo|master|thunar|thunar-doc + -> test-repo|master|tumbler|tumbler-devel + -> test-repo|master|tumbler|tumbler-doc + -> test-repo|master|xfce4-appfinder|xfce4-appfinder-devel + -> test-repo|master|xfce4-appfinder|xfce4-appfinder-doc + -> test-repo|master|xfce4-panel|xfce4-panel-devel + -> test-repo|master|xfce4-panel|xfce4-panel-doc + -> test-repo|master|xfce4-session|xfce4-session-devel + -> test-repo|master|xfce4-session|xfce4-session-doc + -> test-repo|master|xfce4-settings|xfce4-settings-devel + -> test-repo|master|xfce4-settings|xfce4-settings-doc + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfdesktop|xfdesktop-devel + -> test-repo|master|xfdesktop|xfdesktop-doc + -> test-repo|master|xfwm4|xfwm4-devel + -> test-repo|master|xfwm4|xfwm4-doc + test-repo|master|gtk-xfce-engine|gtk-xfce-engine-doc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|gtk-xfce-engine|gtk-xfce-engine-devel + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-appfinder|xfce4-appfinder-doc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-appfinder|xfce4-appfinder-devel + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfdesktop|xfdesktop-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfdesktop|xfdesktop-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfwm4|xfwm4-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfwm4|xfwm4-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-session|xfce4-session-doc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-session|xfce4-session-devel + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-settings|xfce4-settings-doc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-settings|xfce4-settings-devel + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-panel|xfce4-panel-doc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|xfce4-panel|xfce4-panel-devel + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|garcon|garcon-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|garcon|garcon-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|garcon|garcon-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|tumbler|tumbler-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|tumbler|tumbler-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|thunar|thunar-doc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|thunar|thunar-devel + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|exo|exo-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|exo|exo-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|exo|exo-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|garcon|garcon-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|garcon|garcon-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|exo|exo-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|exo|exo-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfconf|xfconf-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|xfconf|xfconf-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|xfconf|xfconf-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|xfconf|xfconf-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|xfconf|xfconf-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|xfconf|xfconf-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|libxfce4util|libxfce4util-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|dbus-glib|dbus-glib-bins + -> test-repo|master|dbus-glib|dbus-glib-libs + -> test-repo|master|dbus-glib|dbus-glib-locale + -> test-repo|master|dbus-glib|dbus-glib-misc + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|gtk|gtk-bins + -> test-repo|master|gtk|gtk-libs + -> test-repo|master|gtk|gtk-locale + -> test-repo|master|gtk|gtk-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|dbus-glib|dbus-glib-misc + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|dbus-glib|dbus-glib-locale + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|dbus-glib|dbus-glib-libs + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|dbus-glib|dbus-glib-bins + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|gtk|gtk-misc + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|gtk|gtk-locale + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|gtk|gtk-libs + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|gtk|gtk-bins + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|dbus-glib|dbus-glib-devel + -> test-repo|master|dbus-glib|dbus-glib-doc + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|gtk|gtk-devel + -> test-repo|master|gtk|gtk-doc + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + test-repo|master|dbus-glib|dbus-glib-doc + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|dbus-glib|dbus-glib-devel + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|dbus|dbus-misc + test-repo|master|dbus|dbus-locale + test-repo|master|dbus|dbus-libs + test-repo|master|dbus|dbus-bins + test-repo|master|dbus|dbus-doc + test-repo|master|dbus|dbus-devel + test-repo|master|gtk|gtk-doc + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|gtk|gtk-devel + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|pango|pango-misc + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + test-repo|master|pango|pango-locale + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + test-repo|master|pango|pango-libs + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + test-repo|master|pango|pango-bins + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|cairo|cairo-misc + test-repo|master|cairo|cairo-locale + test-repo|master|cairo|cairo-libs + test-repo|master|cairo|cairo-bins + test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|glib|glib-misc + test-repo|master|glib|glib-locale + test-repo|master|glib|glib-libs + test-repo|master|glib|glib-bins + test-repo|master|glib|glib-doc + test-repo|master|glib|glib-devel + test-repo|master|pango|pango-doc + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + test-repo|master|pango|pango-devel + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + test-repo|master|fontconfig|fontconfig-misc + test-repo|master|fontconfig|fontconfig-locale + test-repo|master|fontconfig|fontconfig-libs + test-repo|master|fontconfig|fontconfig-bins + test-repo|master|freetype|freetype-misc + test-repo|master|freetype|freetype-locale + test-repo|master|freetype|freetype-libs + test-repo|master|freetype|freetype-bins + test-repo|master|cairo|cairo-doc + test-repo|master|cairo|cairo-devel + test-repo|master|fontconfig|fontconfig-doc + test-repo|master|fontconfig|fontconfig-devel + test-repo|master|freetype|freetype-doc + test-repo|master|freetype|freetype-devel diff --git a/without-test-modules b/without-test-modules index c34ba59c..1f5bc872 100644 --- a/without-test-modules +++ b/without-test-modules @@ -1,5 +1,6 @@ morphlib/__init__.py morphlib/artifactcachereference.py +morphlib/artifactsplitrule.py morphlib/builddependencygraph.py morphlib/tester.py morphlib/git.py diff --git a/yarns/building.yarn b/yarns/building.yarn index 5b6b29a0..cc45df65 100644 --- a/yarns/building.yarn +++ b/yarns/building.yarn @@ -6,4 +6,4 @@ Morph Building Tests AND a git server WHEN the user checks out the system branch called master AND the user creates an uncommitted system morphology called base-system for our architecture in system branch master - THEN morph build the system base-system of the branch master of the repo test:morphs + THEN morph build the system base-system of the branch master diff --git a/yarns/deployment.yarn b/yarns/deployment.yarn index 35f933b7..855ecc52 100644 --- a/yarns/deployment.yarn +++ b/yarns/deployment.yarn @@ -8,3 +8,12 @@ Morph Deployment Tests AND the user attempts to deploy the system test-system in branch master THEN morph failed AND the deploy error message includes the string "morph deploy is only supported for cluster morphologies" + + SCENARIO deploying a cluster morphology + GIVEN a workspace + AND a git server + WHEN the user checks out the system branch called master + GIVEN a cluster called test-cluster for deploying only the test-system system as type tar in system branch master + WHEN the user builds the system test-system in branch master + AND the user attempts to deploy the cluster test-cluster in branch master with options system.location=test.tar + THEN morph succeeded diff --git a/yarns/implementations.yarn b/yarns/implementations.yarn index 0fad95be..5b9b39df 100644 --- a/yarns/implementations.yarn +++ b/yarns/implementations.yarn @@ -30,6 +30,14 @@ we can test it later in a THEN step. 0) die "Morph should have failed, but didn't. Unexpected success!" ;; esac + IMPLEMENTS THEN morph succeeded + case $(cat "$DATADIR/morph-exit") in + 0) echo "Morph succeeded!" + ;; + *) die "Morph should have succeeded, but didn't. Unexpected failure!" + ;; + esac + We need to check that a workspace creation worked. This requires the directory to exist, and its `.morph` subdirectory to exist, and nothing else. @@ -96,10 +104,142 @@ another to hold a chunk. mkdir "$DATADIR/gits/test-chunk" - cat << EOF > "$DATADIR/gits/test-chunk/test-chunk.morph" + # To verify that chunk splitting works, we have a chunk that installs + # dummy files in all the places that different kinds of files are + # usually installed. e.g. executables in `/bin` and `/usr/bin` + + cat << 'EOF' > "$DATADIR/gits/test-chunk/test-chunk.morph" name: test-chunk kind: chunk - build-system: dummy + build-system: manual + + # `install-commands` is a list of shell commands to run. Commands + # may be on multiple lines, and indeed anything programmatic will + # benefit from doing so. Arguably we could have just one command, + # but it's split into multiple so that morph can inform us which + # command failed without us having to include a lot of status + # information in the command and look at the error message. + + install-commands: + + # It's important that we can test whether executables get + # installed, so we install an empty script into `/usr/bin/test` and + # `/usr/sbin/test`. + + # `install -D` will create the leading components for us, and install + # defaults to creating the file with its executable bit set. + + # `install` needs a source file to install, but since we only care + # that the file exists, rather than its contents, we can use /dev/null + # as the source. + + - | + for bindir in bin sbin; do + install -D /dev/null "$DESTDIR/$PREFIX/$bindir/test" + done + + # We need shared libraries too, sometimes they're libraries to support + # the executables that a chunk provides, sometimes for other chunks. + + # Libraries can be found in a variety of places, hence why we install + # them into lib, lib32 and lib64. + + # Shared libraries' file names start with lib and end with `.so` + # for shared-object, with version numbers optionally suffixed. + + - | + for libdir in lib lib32 lib64; do + dirpath="$DESTDIR/$PREFIX/$libdir" + install -D /dev/null "$dirpath/libtest.so" + ln -s libtest.so "$dirpath/libtest.so.0" + ln -s libtest.so.0 "$dirpath/libtest.so.0.0" + ln -s libtest.so.0.0 "$dirpath/libtest.so.0.0.0" + done + + # Shared objects aren't the only kind of library, some executable + # binaries count as libraries, such as git's plumbing commands. + + # In some distributions they go into /lib, in others, and the default + # autotools configuration, they go into /libexec. + + - | + install -D /dev/null "$DESTDIR/$PREFIX/libexec/test-bin" + + # As well as run-time libraries, there's development files. For C + # this is headers, which describe the API of the libraries, which + # then use the shared objects, and other files which are needed + # to build the executables, but aren't needed to run them, such as + # static libraries. + + # Header files go into `include` and end with `.h`. They are not + # executable, so the install command changes the permissions with the + # `-m` option. + + - | + install -D -m 644 /dev/null "$DESTDIR/$PREFIX/include/test.h" + + # `pkg-config` is a standard way to locate libraries and get the + # compiler flags needed to build with the library. It's also used + # for other configuration for packages that don't install binaries, + # so as well as being found in `lib/pkgconfig`, it can be found in + # `share/pkgconfig`, so we install dummy files to both. + + - | + for pkgdir in lib lib32 lib64 share; do + install -D -m 644 /dev/null \ + "$DESTDIR/$PREFIX/$pkgdir/pkgconfig/test.pc" + done + + # Static libraries can be used to build static binaries, which don't + # require their dependencies to be installed. They are typically in + # the form of `.a` archive and `.la` libtool archives. + + - | + for libdir in lib lib32 lib64; do + for libname in libtest.a libtest.la; do + install -D -m 644 /dev/null "$DESTDIR/$PREFIX/$libdir/$libname" + done + done + + # Packages may also install documentation, this comes in a variety + # of formats, but info pages, man pages and html documentation are + # the most common. + + - | + for docfile in info/test.info.gz man/man3/test.3.gz doc/test/doc.html; do + install -D -m 644 /dev/null "$DESTDIR/$PREFIX/share/$docfile" + done + + # Locale covers translations, timezones, keyboard layouts etc. in + # all manner of strange file formats and locations. + + # Locale provides various translations for specific messages. + + - | + install -D -m 644 /dev/null \ + "$DESTDIR/$PREFIX/share/locale/en_GB/LC_MESSAGES/test.mo" + + # Internationalisation (i18n) includes character maps and other data + # such as currency. + + - | + for localefile in i18n/locales/en_GB charmaps/UTF-8.gz; do + install -D -m 644 /dev/null "$DESTDIR/$PREFIX/share/$localefile" + done + + # Timezones are another kind of localisation. + + - | + install -D -m 644 /dev/null "$DESTDIR/$PREFIX/share/zoneinfo/UTC" + + # We also need a catch rule for everything that doesn't fit into + # the above categories, so to test that, we create some files that + # don't belong in one. + + - | + for cfgfile in test.conf README; do + install -D -m 644 /dev/null "$DESTDIR/$PREFIX/etc/test.d/$cfgfile" + done EOF run_in "$DATADIR/gits/test-chunk" git init . @@ -524,11 +664,40 @@ Implementation sections for cross-bootstraping Implementation sections for deployment ====================================== - IMPLEMENTS WHEN the user (attempts to deploy|deploys) the (system|cluster) (\S+) in branch (\S+) +Defaults are set in the cluster morphology, so we can deploy without +setting any extra parameters, but we also need to be able to override +them, so they can be added to the end of the implements section. + + IMPLEMENTS WHEN the user (attempts to deploy|deploys) the (system|cluster) (\S+) in branch (\S+)( with options (.*))? cd "$DATADIR/workspace/$MATCH_4" - set build "$MATCH_3" + set -- deploy "$MATCH_3" + if [ "$MATCH_5" != '' ]; then + # eval used so word splitting in the text is preserved + eval set -- '"$@"' $MATCH_6 + fi if [ $MATCH_1 == "deploys" ]; then run_morph "$@" - else attempt_morph deploy "$MATCH_3"; fi + else attempt_morph "$@"; fi + +To successfully deploy systems, we need a cluster morphology. Since the +common case is to just have one system, we generate a stub morphology +with only the minimal information. + + IMPLEMENTS GIVEN a cluster called (\S+) for deploying only the (\S+) system as type (\S+) in system branch (\S+) + name="$MATCH_1" + system="$MATCH_2" + type="$MATCH_3" + branch="$MATCH_4" + cat << EOF > "$DATADIR/workspace/$branch/test:morphs/$name.morph" + name: $name + kind: cluster + systems: + - morph: $system + repo: test:morphs + ref: $branch + deploy: + system: + type: $type + EOF Implementations sections for reading error messages =================================================== @@ -616,6 +785,15 @@ variables in `$DATADIR/env`. We treat the value as a format string for Implementations for building systems ------------------------------------ - IMPLEMENTS THEN morph build the system (\S+) of the (branch|tag) (\S+) of the repo (\S+) - cd "$DATADIR/workspace/$MATCH_3/$MATCH_4" + IMPLEMENTS THEN morph build the system (\S+) of the (branch|tag) (\S+) + cd "$DATADIR/workspace/$MATCH_3" run_morph build "$MATCH_1" + +Implementations for tarball inspection +-------------------------------------- + + IMPLEMENTS THEN tarball (\S+) contains (.*) + tar -tf "$DATADIR/$MATCH_1" | grep -Fe "$MATCH_2" + + IMPLEMENTS THEN tarball (\S+) doesn't contain (.*) + ! tar -tf "$DATADIR/$MATCH_1" | grep -Fe "$MATCH_2" diff --git a/yarns/regression.yarn b/yarns/regression.yarn index eae01343..49c663ec 100644 --- a/yarns/regression.yarn +++ b/yarns/regression.yarn @@ -10,7 +10,7 @@ Testing if we can build after checking out from a tag. GIVEN a workspace AND a git server WHEN the user checks out the system tag called test-tag - THEN morph build the system test-system of the tag test-tag of the repo test:morphs + THEN morph build the system test-system of the tag test-tag Running `morph branch` when the branch directory exists doesn't diff --git a/yarns/splitting.yarn b/yarns/splitting.yarn new file mode 100644 index 00000000..e3324190 --- /dev/null +++ b/yarns/splitting.yarn @@ -0,0 +1,129 @@ +Artifact splitting tests +======================== + + SCENARIO building a system with morphologies that have splitting rules + GIVEN a workspace + AND a git server + + AND chunk test-chunk includes the default splitting rules + AND stratum test-stratum includes the default splitting rules + AND system test-system includes the default splitting rules + + WHEN the user checks out the system branch called master + THEN morph build the system test-system of the branch master + + + + SCENARIO building a system only using runtime strata + GIVEN a workspace + AND a git server + AND system test-system only uses test-stratum-runtime from test-stratum + WHEN the user checks out the system branch called master + GIVEN a cluster called test-cluster for deploying only the test-system system as type tar in system branch master + WHEN the user builds the system test-system in branch master + AND the user attempts to deploy the cluster test-cluster in branch master with options system.location="$DATADIR/test.tar" + THEN tarball test.tar contains bin/test + AND tarball test.tar contains lib/libtest.so + AND tarball test.tar doesn't contain lib/libtest.a + AND tarball test.tar doesn't contain man/man3/test.3.gz + +Implementations +--------------- + + IMPLEMENTS GIVEN chunk (\S+) includes the default splitting rules + # Append default products rules + cat <<EOF >>"$DATADIR/gits/$MATCH_1/$MATCH_1.morph" + products: + - artifact: $MATCH_1-bins + include: [ "(usr/)?s?bin/.*" ] + - artifact: $MATCH_1-libs + include: + - (usr/)?lib(32|64)?/lib[^/]*\.so(\.\d+)* + - (usr/)?libexec/.* + - artifact: $MATCH_1-devel + include: + - (usr/)?include/.* + - (usr/)?lib(32|64)?/lib.*\.a + - (usr/)?lib(32|64)?/lib.*\.la + - (usr/)?(lib(32|64)?|share)/pkgconfig/.*\.pc + - artifact: $MATCH_1-doc + include: + - (usr/)?share/doc/.* + - (usr/)?share/man/.* + - (usr/)?share/info/.* + - artifact: $MATCH_1-locale + include: + - (usr/)?share/locale/.* + - (usr/)?share/i18n/.* + - (usr/)?share/zoneinfo/.* + - artifact: $MATCH_1-misc + include: [ .* ] + EOF + run_in "$DATADIR/gits/$MATCH_1" git add "$MATCH_1.morph" + run_in "$DATADIR/gits/$MATCH_1" git commit -m 'Add default splitting rules' + + IMPLEMENTS GIVEN stratum (\S+) includes the default splitting rules + # Append default products rules + cat <<EOF >"$DATADIR/gits/morphs/$MATCH_1.morph" + name: $MATCH_1 + kind: stratum + products: + - artifact: $MATCH_1-devel + include: + - .*-devel + - .*-debug + - .*-doc + - artifact: $MATCH_1-runtime + include: + - .*-bins + - .*-libs + - .*-locale + - .*-misc + - .* + chunks: + - name: test-chunk + repo: test:test-chunk + ref: master + morph: test-chunk + build-mode: test + build-depends: [] + artifacts: + test-chunk-bins: $MATCH_1-runtime + test-chunk-libs: $MATCH_1-runtime + test-chunk-locale: $MATCH_1-runtime + test-chunk-misc: $MATCH_1-runtime + test-chunk-devel: $MATCH_1-devel + test-chunk-doc: $MATCH_1-devel + EOF + run_in "$DATADIR/gits/morphs" git add "$MATCH_1.morph" + run_in "$DATADIR/gits/morphs" git commit -m 'Add default splitting rules' + + IMPLEMENTS GIVEN system (\S+) includes the default splitting rules + cat << EOF > "$DATADIR/gits/morphs/$MATCH_1.morph" + name: $MATCH_1 + kind: system + arch: $(run_morph print-architecture) + strata: + - name: test-stratum + repo: test:morphs + ref: master + morph: test-stratum + artifacts: + - test-stratum-runtime + - test-stratum-devel + EOF + run_in "$DATADIR/gits/morphs" git add "$MATCH_1.morph" + run_in "$DATADIR/gits/morphs" git commit -m 'Add default splitting rules' + + IMPLEMENTS GIVEN system (\S+) only uses (\S+) from (\S+) + python -c 'import sys, yaml + with open(sys.argv[1], "r") as f: + d = yaml.load(f) + for spec in d["strata"]: + if spec["name"] == sys.argv[3]: + spec["artifacts"] = [sys.argv[2]] + with open(sys.argv[1], "w") as f: + yaml.dump(d, f) + ' "$DATADIR/gits/morphs/$MATCH_1.morph" "$MATCH_2" "$MATCH_3" + run_in "$DATADIR/gits/morphs" git add "$MATCH_1.morph" + run_in "$DATADIR/gits/morphs" git commit -m "Make $MATCH_1 only use $MATCH_2" |