summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Maw <richard.maw@codethink.co.uk>2014-01-17 16:38:19 (GMT)
committerRichard Maw <richard.maw@codethink.co.uk>2014-01-17 16:38:19 (GMT)
commit18f24fbb0c35905af06f5af0915813fd1f0c22b3 (patch)
tree08c467976ca1292d1f7f11af450ab57396351943
parent11c4c8f8019457b27d9b4c1c7e7a928d6f87c321 (diff)
parent9901b78e48fddeda2ed7f6dbf954abcde8fa9a0f (diff)
downloadmorph-18f24fbb0c35905af06f5af0915813fd1f0c22b3.tar.gz
Merge artifact splitting work
Rationale ========= This patch series implements the concept of stratum splitting. For a long time we've had code to split a chunk into multiple artifacts, however there's not been a way to split strata up, or systems select a subset of the produced stratum artifacts to be included in the system. This patch series implements the ability to split strata and have systems include them in a way which still has the same behaviour if no rules are specified, but with default rules that split chunk artifacts up into various components, strata into runtime and development versions and has systems include everything by default, but can be told to include less. The default rules have chunk foo split up into -bins, -libs, -devel, -doc, -locale and -misc. These rules can be overridden in the chunk morphology by adding the new 'products' field, which lists match rules like the following: products: - artifact: libudev include: - (usr/)?lib(32|64)?/lubg?udev\..* - artifact: udev include: - (usr/)?s?bin/udev* - (usr/)?lib(32|64|exec)?/systemd/systemd-udevd Strata are by default split into -runtime and -devel. -devel by default contains chunks ending with -devel and -doc, -runtime contains everything else. Extra match rules can be added to a stratum similarly to chunks, but instead of matching file names, they match artifact names. products: - artifact: core-python include: - "cpython-.*" # lazy shortcut to put all of cpython in this stratum - "python-.*" # lazy shortcut to include all python chunks in Additionally, in chunk specs, chunk artifacts may be assigned to stratum artifacts, this takes precedence over products match rules in the stratum and the default match rules. Assigning the chunk to `null` will discard the chunk. chunks: ... - name: systemd ... artifacts: libudev: foundation-runtime udev: foundation-runtime systemd-doc: null By default a system includes every produced artifact of every stratum listed. Instead a subset can be specified in the stratum spec as follows: name: tiny-system strata: - name: build-essential ... artifacts: - build-essential-runtime
-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 67fb944..3377379 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 8edbbde..d4b15cb 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 17f038a..ae0cfcf 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 b685902..6f62b4d 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 0000000..246691d
--- /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 6fb7dc5..23e3b81 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 a9a94a4..60361ec 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 4b3b210..6485f51 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 bab89aa..2dca738 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 d7e2e3b..2312abc 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 2f033a7..4e73e90 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 d774335..18d2061 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 0e0d920..fd72aa9 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 aaa1d1c..ba90313 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 e7c1d9f..637544b 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 8b87467..c2fbc5e 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 5afafef..3462dd3 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 6e1e67d..30504e0 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 e7f45f5..d11bf26 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 99b0a99..75a2e4d 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 942301e..c9d50bb 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 511222e..9e4a5d9 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 0016b27..ff944af 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 5fc4fb0..236954f 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 281ab10..7473990 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 fe106ad..f67d54c 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 c896c84..4141dee 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 923fb21..0ac6622 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 329cdd7..b59d002 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 c3c0057..c996e76 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 bf9836d..6dda504 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 c217175..ba5cd32 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 8077cac..683441c 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 00b9ed2..ab5186d 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 ccf80a8..3410b11 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 b1823eb..f66d402 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 5cbe4c7..180e949 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 a046952..e1dccb4 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 4d4abdb..4d4c3a1 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 75b9d0d..56d8073 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 3d5201e..2e8270d 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 c9294c3..9a8b41d 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 22292c1..e69de29 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 e9b8ecd..ca9648c 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 306c16f..0014e54 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 eee106f..9c53ee6 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 ca06454..fe4ed4e 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 4048565..1e36ca8 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 ab1453d..2c70d30 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 c34ba59..1f5bc87 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 5b6b29a..cc45df6 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 35f933b..855ecc5 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 0fad95b..5b9b39d 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 eae0134..49c663e 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 0000000..e332419
--- /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"