summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--morphlib/__init__.py3
-rw-r--r--morphlib/artifact_tests.py4
-rw-r--r--morphlib/artifactresolver.py204
-rw-r--r--morphlib/artifactresolver_tests.py589
-rw-r--r--morphlib/artifactsplitrule.py303
-rw-r--r--morphlib/bins.py70
-rw-r--r--morphlib/bins_tests.py39
-rw-r--r--morphlib/buildcommand.py32
-rw-r--r--morphlib/builder2.py84
-rw-r--r--morphlib/cachekeycomputer.py4
-rw-r--r--morphlib/cachekeycomputer_tests.py16
-rw-r--r--morphlib/localartifactcache_tests.py4
-rw-r--r--morphlib/morph2.py13
-rw-r--r--morphlib/morph2_tests.py6
-rw-r--r--morphlib/morphloader.py120
-rw-r--r--morphlib/morphloader_tests.py98
-rw-r--r--morphlib/morphologyfactory.py9
-rw-r--r--morphlib/morphologyfactory_tests.py16
-rw-r--r--morphlib/remoteartifactcache_tests.py4
-rw-r--r--morphlib/source.py11
-rwxr-xr-xtests.as-root/branch-from-image-works.script4
-rwxr-xr-xtests.as-root/metadata-includes-repo-alias.script10
-rwxr-xr-xtests.as-root/run-in-artifact-with-different-artifacts.script6
-rw-r--r--tests.as-root/run-in-artifact-with-different-artifacts.stderr2
-rw-r--r--tests.as-root/run-in-artifact-with-different-artifacts.stdout32
-rw-r--r--tests.as-root/system-overlap.stdout2
-rw-r--r--tests.as-root/tarball-image-is-sensible.stdout30
-rwxr-xr-xtests.build/bootstrap-mode.script16
-rw-r--r--tests.build/bootstrap-mode.stdout11
-rwxr-xr-xtests.build/build-stratum-with-submodules.script4
-rw-r--r--tests.build/build-stratum-with-submodules.stdout4
-rwxr-xr-xtests.build/build-system-autotools.script8
-rw-r--r--tests.build/build-system-autotools.stdout5
-rwxr-xr-xtests.build/build-system-cmake.script8
-rw-r--r--tests.build/build-system-cmake.stdout6
-rwxr-xr-xtests.build/build-system-cpan.script8
-rw-r--r--tests.build/build-system-cpan.stdout1
-rwxr-xr-xtests.build/build-system-python-distutils.script8
-rw-r--r--tests.build/build-system-python-distutils.stdout4
-rwxr-xr-xtests.build/build-system.script4
-rw-r--r--tests.build/build-system.stdout4
-rwxr-xr-xtests.build/morphless-chunks.script8
-rw-r--r--tests.build/morphless-chunks.stdout5
-rwxr-xr-xtests.build/prefix.script6
-rwxr-xr-xtests.build/rebuild-cached-stratum.script16
-rw-r--r--tests.build/rebuild-cached-stratum.stdout26
-rwxr-xr-xtests.build/stratum-overlap-writes-overlap.script6
-rw-r--r--tests.build/stratum-overlap-writes-overlap.stdout4
-rw-r--r--tests/show-dependencies.stdout1776
-rw-r--r--without-test-modules1
-rw-r--r--yarns/building.yarn2
-rw-r--r--yarns/deployment.yarn9
-rw-r--r--yarns/implementations.yarn192
-rw-r--r--yarns/regression.yarn2
-rw-r--r--yarns/splitting.yarn129
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"