From 56f996b81d3843b9d2a131dd778912f9ebd57f48 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 6 Dec 2013 11:09:58 +0000 Subject: yarns: Remove redundant repo in IMPLEMENTS THEN morph build the system... doesn't need the repository to be specified, just the branch; morph is able to work it out for itself. --- yarns/building.yarn | 2 +- yarns/implementations.yarn | 4 ++-- yarns/regression.yarn | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/yarns/building.yarn b/yarns/building.yarn index 5b6b29a0..cc45df65 100644 --- a/yarns/building.yarn +++ b/yarns/building.yarn @@ -6,4 +6,4 @@ Morph Building Tests AND a git server WHEN the user checks out the system branch called master AND the user creates an uncommitted system morphology called base-system for our architecture in system branch master - THEN morph build the system base-system of the branch master of the repo test:morphs + THEN morph build the system base-system of the branch master diff --git a/yarns/implementations.yarn b/yarns/implementations.yarn index 0fad95be..dc8f06af 100644 --- a/yarns/implementations.yarn +++ b/yarns/implementations.yarn @@ -616,6 +616,6 @@ 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" diff --git a/yarns/regression.yarn b/yarns/regression.yarn index eae01343..49c663ec 100644 --- a/yarns/regression.yarn +++ b/yarns/regression.yarn @@ -10,7 +10,7 @@ Testing if we can build after checking out from a tag. GIVEN a workspace AND a git server WHEN the user checks out the system tag called test-tag - THEN morph build the system test-system of the tag test-tag of the repo test:morphs + THEN morph build the system test-system of the tag test-tag Running `morph branch` when the branch directory exists doesn't -- cgit v1.2.1 From af7ee68094089a15c028f338af43b0fb9e3637c8 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 6 Dec 2013 14:34:07 +0000 Subject: yarns: Make test chunk create representative files It's useful for the splitting tests that will be implemented later, for there to be files that are representative of the files that would be on a common root filesystem, so the match rules can be tested by the right chunk artifacts containing the right files. --- yarns/implementations.yarn | 136 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 2 deletions(-) diff --git a/yarns/implementations.yarn b/yarns/implementations.yarn index dc8f06af..60e1dda1 100644 --- a/yarns/implementations.yarn +++ b/yarns/implementations.yarn @@ -96,10 +96,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 . -- cgit v1.2.1 From 848118b07b38a534bd0b53a8d972001a5fb22777 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Thu, 19 Dec 2013 15:23:51 +0000 Subject: yarns: Add a deploy test --- yarns/deployment.yarn | 9 +++++++++ yarns/implementations.yarn | 43 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/yarns/deployment.yarn b/yarns/deployment.yarn index 35f933b7..855ecc52 100644 --- a/yarns/deployment.yarn +++ b/yarns/deployment.yarn @@ -8,3 +8,12 @@ Morph Deployment Tests AND the user attempts to deploy the system test-system in branch master THEN morph failed AND the deploy error message includes the string "morph deploy is only supported for cluster morphologies" + + SCENARIO deploying a cluster morphology + GIVEN a workspace + AND a git server + WHEN the user checks out the system branch called master + GIVEN a cluster called test-cluster for deploying only the test-system system as type tar in system branch master + WHEN the user builds the system test-system in branch master + AND the user attempts to deploy the cluster test-cluster in branch master with options system.location=test.tar + THEN morph succeeded diff --git a/yarns/implementations.yarn b/yarns/implementations.yarn index 60e1dda1..083035fe 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. @@ -656,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 =================================================== -- cgit v1.2.1 From 623a908220316a8064d3187597b255f5056b1478 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 13 Dec 2013 16:50:32 +0000 Subject: unit tests: Fix invalid morphologies Later validation work causes the morphologies to be validated, when they weren't previously. This would cause the test suite to not pass, since the morphologies defined in the tests are malformed. One common problem was tests that, instead of a name field, had the name of the morpholgy in a field called "chunk". There were a few cases of new fields being needed, since the tests were written before they became mandatory. The most interesting failure was a Source being created, which instead of being passed a morphology object, was passed a string. --- morphlib/artifact_tests.py | 4 ++-- morphlib/cachekeycomputer_tests.py | 16 +++++++++------- morphlib/localartifactcache_tests.py | 4 ++-- morphlib/remoteartifactcache_tests.py | 4 ++-- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/morphlib/artifact_tests.py b/morphlib/artifact_tests.py index 8edbbde2..d4b15cba 100644 --- a/morphlib/artifact_tests.py +++ b/morphlib/artifact_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,7 +26,7 @@ class ArtifactTests(unittest.TestCase): morph = morphlib.morph2.Morphology( ''' { - "chunk": "chunk", + "name": "chunk", "kind": "chunk", "chunks": { "chunk-runtime": [ diff --git a/morphlib/cachekeycomputer_tests.py b/morphlib/cachekeycomputer_tests.py index 2f033a7a..4e73e905 100644 --- a/morphlib/cachekeycomputer_tests.py +++ b/morphlib/cachekeycomputer_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -53,7 +53,8 @@ class CacheKeyComputerTests(unittest.TestCase): { "name": "chunk", "repo": "repo", - "ref": "original/ref" + "ref": "original/ref", + "build-depends": [] } ] }''', @@ -64,12 +65,14 @@ class CacheKeyComputerTests(unittest.TestCase): { "name": "chunk2", "repo": "repo", - "ref": "original/ref" + "ref": "original/ref", + "build-depends": [] }, { "name": "chunk3", "repo": "repo", - "ref": "original/ref" + "ref": "original/ref", + "build-depends": [] } ] }''', @@ -118,7 +121,6 @@ class CacheKeyComputerTests(unittest.TestCase): for artifact in self.artifacts: if artifact.name == name: return artifact - raise def test_compute_key_hashes_all_types(self): runcount = {'thing': 0, 'dict': 0, 'list': 0, 'tuple': 0} @@ -184,8 +186,8 @@ class CacheKeyComputerTests(unittest.TestCase): self.assertEqual(old_sha, new_sha) def test_same_morphology_added_to_source_pool_only_appears_once(self): - src = morphlib.source.Source('repo', 'original/ref', 'sha', 'tree', - '{"name": "chunk", "kind": "chunk"}', + m = morphlib.morph2.Morphology('{"name": "chunk", "kind": "chunk"}') + src = morphlib.source.Source('repo', 'original/ref', 'sha', 'tree', m, 'chunk.morph') sp = morphlib.sourcepool.SourcePool() sp.add(src) diff --git a/morphlib/localartifactcache_tests.py b/morphlib/localartifactcache_tests.py index d7743359..18d20612 100644 --- a/morphlib/localartifactcache_tests.py +++ b/morphlib/localartifactcache_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012,2013 Codethink Limited +# Copyright (C) 2012,2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -30,7 +30,7 @@ class LocalArtifactCacheTests(unittest.TestCase): morph = morphlib.morph2.Morphology( ''' { - "chunk": "chunk", + "name": "chunk", "kind": "chunk", "artifacts": { "chunk-runtime": [ diff --git a/morphlib/remoteartifactcache_tests.py b/morphlib/remoteartifactcache_tests.py index e7f45f58..d11bf264 100644 --- a/morphlib/remoteartifactcache_tests.py +++ b/morphlib/remoteartifactcache_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -27,7 +27,7 @@ class RemoteArtifactCacheTests(unittest.TestCase): morph = morphlib.morph2.Morphology( ''' { - "chunk": "chunk", + "name": "chunk", "kind": "chunk", "artifacts": { "chunk-runtime": [ -- cgit v1.2.1 From b1ae48ff49dcbd34fb9a9a4eac62868b9195f9ef Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Wed, 18 Dec 2013 18:33:58 +0000 Subject: BuildCommand: Validate multiple root morphologies When you attempt to build a stratum or chunk, the ArtifactResolver can return multiple root artifacts, since the root source produces multiple artifacts. Rather than having the BuildCommand complain that there's multiple root artifacts, it now validates all the produced artifacts too, since that will validate the kinds of artifacts produced, and give a more useful error message, that you're trying to build a stratum or chunk directly. If all the produced artifacts validate, then an exception is raised to signal that it got multiple artifacts, when it only expected one. --- morphlib/buildcommand.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/morphlib/buildcommand.py b/morphlib/buildcommand.py index 4b3b2108..6485f510 100644 --- a/morphlib/buildcommand.py +++ b/morphlib/buildcommand.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,6 +22,14 @@ import tempfile import morphlib +class MultipleRootArtifactsError(morphlib.Error): + + def __init__(self, artifacts): + self.msg = ('System build has multiple root artifacts: %r' + % [a.name for a in artifacts]) + self.artifacts = artifacts + + class BuildCommand(object): '''High level logic for building. @@ -142,7 +150,15 @@ class BuildCommand(object): artifacts = ar.resolve_artifacts(srcpool) self.app.status(msg='Computing build order', chatty=True) - root_artifact = self._find_root_artifact(artifacts) + root_artifacts = self._find_root_artifacts(artifacts) + if len(root_artifacts) > 1: + # Validate root artifacts, since validation covers errors + # such as trying to build a chunk or stratum directly, + # and this is one cause for having multiple root artifacts + for root_artifact in root_artifacts: + self._validate_root_artifact(root_artifact) + raise MultipleRootArtifactsError(root_artifacts) + root_artifact = root_artifacts[0] # Validate the root artifact here, since it's a costly function # to finalise it, so any pre finalisation validation is better @@ -231,8 +247,8 @@ class BuildCommand(object): other.morphology['kind'], wanted)) - def _find_root_artifact(self, artifacts): - '''Find the root artifact among a set of artifacts in a DAG. + def _find_root_artifacts(self, artifacts): + '''Find all the root artifacts among a set of artifacts in a DAG. It would be nice if the ArtifactResolver would return its results in a more useful order to save us from needing to do this -- the root object @@ -240,13 +256,7 @@ class BuildCommand(object): ''' - maybe = set(artifacts) - for a in artifacts: - for dep in a.dependencies: - if dep in maybe: - maybe.remove(dep) - assert len(maybe) == 1 - return maybe.pop() + return [a for a in artifacts if not a.dependents] def build_in_order(self, root_artifact): '''Build everything specified in a build order.''' -- cgit v1.2.1 From 8c92d5783274dd3f154c1609f3e6ce18571b46a9 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 13 Dec 2013 17:01:44 +0000 Subject: morph2.Morphology: add trivial .get method This it convenient, as it allows the new validation code to validate the old morphology class during the transition period. --- morphlib/morph2.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/morphlib/morph2.py b/morphlib/morph2.py index 0e0d9201..942322ee 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 @@ -87,6 +87,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: -- cgit v1.2.1 From ef5e11a25c56e5f0f6f755f0f7c7be5584c4b8e5 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 7 Jan 2014 17:10:06 +0000 Subject: Source: Create all Artifact objects in advance This simplifies logic for the ArtifactResolver, since it doesn't need to have its own cache of (source, artifact_name) -> artifact and the artifacts a source produces can be iterated directly with the artifacts attribute, rather than having to iterate over the names and look up the artifact object. --- morphlib/source.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/morphlib/source.py b/morphlib/source.py index 99b0a993..8673af79 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,7 @@ 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. ''' @@ -42,6 +43,8 @@ class Source(object): self.tree = tree self.morphology = morphology self.filename = filename + self.artifacts = {name: morphlib.artifact.Artifact(self, name) + for name in morphology.builds_artifacts} def __str__(self): # pragma: no cover return '%s|%s|%s' % (self.repo_name, -- cgit v1.2.1 From 74d4cfc34478881685365a0c674342ebe24f4c0a Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Wed, 15 Jan 2014 16:56:30 +0000 Subject: ArtifactResolver: Use Artifacts from Sources --- morphlib/artifactresolver.py | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/morphlib/artifactresolver.py b/morphlib/artifactresolver.py index 17f038a2..e64d458b 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 @@ -71,13 +71,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,7 +91,7 @@ class ArtifactResolver(object): source = queue.popleft() if source.morphology['kind'] == 'system': - systems = [self._get_artifact(source, a) + systems = [source.artifacts[a] for a in source.morphology.builds_artifacts] if any(a not in self._added_artifacts for a in systems): @@ -109,8 +107,8 @@ class ArtifactResolver(object): 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]) + artifact = source.artifacts[ + source.morphology.builds_artifacts[0]] if not artifact in self._added_artifacts: artifacts.append(artifact) @@ -126,7 +124,7 @@ class ArtifactResolver(object): elif source.morphology['kind'] == 'chunk': names = source.morphology.builds_artifacts for name in names: - artifact = self._get_artifact(source, name) + artifact = source.artifacts[name] if not artifact in self._added_artifacts: artifacts.append(artifact) self._added_artifacts.add(artifact) @@ -141,15 +139,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 = [] @@ -160,7 +149,7 @@ class ArtifactResolver(object): '%s.morph' % info['morph']) stratum_name = stratum_source.morphology.builds_artifacts[0] - stratum = self._get_artifact(stratum_source, stratum_name) + stratum = stratum_source.artifacts[stratum_name] for system in systems: system.add_dependency(stratum) @@ -182,8 +171,8 @@ class ArtifactResolver(object): stratum_info['ref'] or stratum.source.original_ref, '%s.morph' % stratum_info['morph']) - other_stratum = self._get_artifact( - other_source, other_source.morphology.builds_artifacts[0]) + other_stratum = other_source.artifacts[ + other_source.morphology.builds_artifacts[0]] strata.append(other_stratum) @@ -210,7 +199,7 @@ class ArtifactResolver(object): if not info['name'] in possible_names: raise UndefinedChunkArtifactError(stratum.source, info['name']) - chunk_artifact = self._get_artifact(chunk_source, info['name']) + chunk_artifact = chunk_source.artifacts[info['name']] chunk_artifacts.append(chunk_artifact) artifacts.append(chunk_artifact) -- cgit v1.2.1 From bea188ba004eb1b8d3057a5cfa2c1a167ef72d14 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Mon, 9 Dec 2013 15:41:29 +0000 Subject: Replace chunk 'chunks' field with 'products' I think that it's confusing for both strata and chunk morphologies to have a 'chunks' field, with the former listing sources and the latter listing rules for splitting this source into artifacts. The design for splitting strata has roughly the same idea, but operating on chunk artifact names, rather than file names, so a name that can be used for both was chosen. Splits and artifacts weren't satisfactory names, so they're now called 'products'. It was decided to break backwards compatibility of chunk morphologies being able to specify 'chunks', since the format has changed, so extra code would be required to translate the format, and the only users of the 'chunks' field was the test suite, since there was no way to select from the system, which chunk artifacts were included. --- morphlib/builder2.py | 4 ++-- morphlib/morph2.py | 3 +-- morphlib/morph2_tests.py | 6 +++--- morphlib/morphloader.py | 4 ++-- morphlib/morphloader_tests.py | 4 ++-- morphlib/morphologyfactory.py | 7 ++++--- morphlib/morphologyfactory_tests.py | 16 +++++++++++----- 7 files changed, 25 insertions(+), 19 deletions(-) diff --git a/morphlib/builder2.py b/morphlib/builder2.py index bab89aa2..34ebaa81 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 @@ -423,7 +423,7 @@ class ChunkBuilder(BuilderBase): built_artifacts = [] filenames = [] with self.build_watch('create-chunks'): - specs = self.artifact.source.morphology['chunks'] + specs = self.artifact.source.morphology['products'] if len(specs) == 0: specs = { self.artifact.source.morphology['name']: ['.'], diff --git a/morphlib/morph2.py b/morphlib/morph2.py index 942322ee..fd72aa94 100644 --- a/morphlib/morph2.py +++ b/morphlib/morph2.py @@ -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') ], diff --git a/morphlib/morph2_tests.py b/morphlib/morph2_tests.py index aaa1d1cc..ba90313f 100644 --- a/morphlib/morph2_tests.py +++ b/morphlib/morph2_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -50,7 +50,7 @@ class MorphologyTests(unittest.TestCase): self.assertEqual(m['install-commands'], None) self.assertEqual(m['post-install-commands'], None) self.assertEqual(m['max-jobs'], None) - self.assertEqual(m['chunks'], []) + self.assertEqual(m['products'], []) if morphlib.got_yaml: def test_parses_simple_yaml_chunk(self): @@ -76,7 +76,7 @@ class MorphologyTests(unittest.TestCase): self.assertEqual(m['install-commands'], None) self.assertEqual(m['post-install-commands'], None) self.assertEqual(m['max-jobs'], None) - self.assertEqual(m['chunks'], []) + self.assertEqual(m['products'], []) def test_sets_stratum_chunks_repo_and_morph_from_name(self): m = Morphology(''' diff --git a/morphlib/morphloader.py b/morphlib/morphloader.py index e7c1d9ff..e1ec15bd 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 @@ -185,7 +185,7 @@ class MorphologyLoader(object): 'install-commands': [], 'post-install-commands': [], 'devices': [], - 'chunks': [], + 'products': [], 'max-jobs': None, 'build-system': 'manual', }, diff --git a/morphlib/morphloader_tests.py b/morphlib/morphloader_tests.py index 8b87467a..907f3762 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 @@ -438,7 +438,7 @@ name: foo 'pre-install-commands': [], 'post-install-commands': [], - 'chunks': [], + 'products': [], 'devices': [], 'max-jobs': None, }) diff --git a/morphlib/morphologyfactory.py b/morphlib/morphologyfactory.py index 5afafefb..e6fabd51 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,8 +167,9 @@ 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']] diff --git a/morphlib/morphologyfactory_tests.py b/morphlib/morphologyfactory_tests.py index 6e1e67d3..30504e00 100644 --- a/morphlib/morphologyfactory_tests.py +++ b/morphlib/morphologyfactory_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -50,10 +50,16 @@ class FakeLocalRepo(object): "name": "chunk-split", "kind": "chunk", "build-system": "bar", - "chunks": { - "chunk-split-runtime": [], - "chunk-split-devel": [] - } + "products": [ + { + "artifact": "chunk-split-runtime", + "include": [] + }, + { + "artifact": "chunk-split-devel", + "include": [] + } + ] }''', 'stratum.morph': '''{ "name": "stratum", -- cgit v1.2.1 From 7fb78e54f4c7206ff391fd4edb0ece200109f1e7 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 7 Jan 2014 16:47:54 +0000 Subject: MorphologyLoader: Validate new fields --- morphlib/morphloader.py | 116 +++++++++++++++++++++++++++++++++++++++--- morphlib/morphloader_tests.py | 94 ++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+), 6 deletions(-) diff --git a/morphlib/morphloader.py b/morphlib/morphloader.py index e1ec15bd..637544be 100644 --- a/morphlib/morphloader.py +++ b/morphlib/morphloader.py @@ -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.''' @@ -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 907f3762..c2fbc5e8 100644 --- a/morphlib/morphloader_tests.py +++ b/morphlib/morphloader_tests.py @@ -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', @@ -491,6 +584,7 @@ name: foo 'build-depends': [], }, ], + 'products': [], }) def test_unsets_defaults_for_strata(self): -- cgit v1.2.1 From 17628d7851fbaf284d03373e4fcfed28cb36bc59 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 7 Jan 2014 16:50:09 +0000 Subject: MorphologyFactory: validate new fields using loader code --- morphlib/morphologyfactory.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/morphlib/morphologyfactory.py b/morphlib/morphologyfactory.py index e6fabd51..3462dd36 100644 --- a/morphlib/morphologyfactory.py +++ b/morphlib/morphologyfactory.py @@ -174,3 +174,5 @@ class MorphologyFactory(object): morphology.builds_artifacts = [morphology['name']] morphology.needs_artifact_metadata_cached = False + + morphlib.morphloader.MorphologyLoader._validate_chunk(morphology) -- cgit v1.2.1 From b5e881996901e9b5646afc58b7c7dadc82491059 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Wed, 8 Jan 2014 17:39:44 +0000 Subject: Add split rules to sources This introduces a new artifactsplitrule module, which tries to provide a nice abstraction over matching a sequence of things to a bunch of outputs, to be used by both chunks splitting, for separating files out into chunk artifacts, the stratum splitting, where chunks are aggregated into stratum artifacts, and systems selecting the right strata to go into the artifact. --- morphlib/__init__.py | 3 +- morphlib/artifactsplitrule.py | 289 ++++++++++++++++++++++++++++++++++++++++++ morphlib/source.py | 8 +- without-test-modules | 1 + 4 files changed, 299 insertions(+), 2 deletions(-) create mode 100644 morphlib/artifactsplitrule.py diff --git a/morphlib/__init__.py b/morphlib/__init__.py index 67fb944d..33773791 100644 --- a/morphlib/__init__.py +++ b/morphlib/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -48,6 +48,7 @@ class Error(cliapp.AppException): import artifact import artifactcachereference import artifactresolver +import artifactsplitrule import branchmanager import bins import buildbranch diff --git a/morphlib/artifactsplitrule.py b/morphlib/artifactsplitrule.py new file mode 100644 index 00000000..f30b05a3 --- /dev/null +++ b/morphlib/artifactsplitrule.py @@ -0,0 +1,289 @@ +# 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/source.py b/morphlib/source.py index 8673af79..75a2e4de 100644 --- a/morphlib/source.py +++ b/morphlib/source.py @@ -31,6 +31,7 @@ class Source(object): * ``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,8 +44,13 @@ class Source(object): self.tree = tree 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 morphology.builds_artifacts} + for name in self.split_rules.artifacts} def __str__(self): # pragma: no cover return '%s|%s|%s' % (self.repo_name, diff --git a/without-test-modules b/without-test-modules index c34ba59c..1f5bc872 100644 --- a/without-test-modules +++ b/without-test-modules @@ -1,5 +1,6 @@ morphlib/__init__.py morphlib/artifactcachereference.py +morphlib/artifactsplitrule.py morphlib/builddependencygraph.py morphlib/tester.py morphlib/git.py -- cgit v1.2.1 From 90678ea8f8a0cbb95c049ef1fad9f40c3a2122a7 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Wed, 8 Jan 2014 14:29:14 +0000 Subject: CacheKeyComputer: Include split rules in computation Chunk artifacts need the [(artifact_name, [regular_expression])] so that if the default split rules change, or the blending rule changes, then an extra version field doesn't need to be added to the cache key computer. This is for future plans to allow the split rules to be configurable and allow us to more easily change them. System and Stratum artifact computations don't need this, since those splitting rules are already expressed in the dependencies information. --- morphlib/cachekeycomputer.py | 4 +++- tests.as-root/run-in-artifact-with-different-artifacts.stderr | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/morphlib/cachekeycomputer.py b/morphlib/cachekeycomputer.py index d7e2e3b1..2312abc3 100644 --- a/morphlib/cachekeycomputer.py +++ b/morphlib/cachekeycomputer.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -90,6 +90,8 @@ class CacheKeyComputer(object): keys['build-mode'] = artifact.source.build_mode keys['prefix'] = artifact.source.prefix keys['tree'] = artifact.source.tree + keys['split-rules'] = [(a, [rgx.pattern for rgx in r._regexes]) + for (a, r) in artifact.source.split_rules] elif kind in ('system', 'stratum'): morphology = artifact.source.morphology le_dict = dict((k, morphology[k]) for k in morphology.keys()) diff --git a/tests.as-root/run-in-artifact-with-different-artifacts.stderr b/tests.as-root/run-in-artifact-with-different-artifacts.stderr index 5fc4fb0f..6e9a5f8f 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/ef470cc0efd6c64992198f9d224e4b68dc452528919e2b7cf3f10efa1b88541a.stratum.linux-stratum cannot be extracted or mounted -- cgit v1.2.1 From 137645ee210637ce7fee2f1a5a6ad17f4e6674c0 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 13 Dec 2013 17:02:48 +0000 Subject: ArtifactResolver: Generate dependencies from split rules One important change is that the builds_artifacts field of Morphologies is not used any more, since the split rules provide this information. Another important change is that the ArtifactResolver now only returns aritfacts that are required to build the root artifact, rather than every artifact in the build. Previously there was no distinction. This is required because when artifact splitting is in effect, some artifacts may be produced, but not depended on by anything. This confuses the BuildCommand, which expects to be able to find a single root artifact. NOTE: This change breaks artifact construction until "Split chunk morphologies according to new rules" and "Split Stratum artifacts according to new rules", since systems and strata depend on artifacts that weren't created. --- morphlib/artifactresolver.py | 191 ++++++------ morphlib/artifactresolver_tests.py | 592 ++++++++++--------------------------- 2 files changed, 256 insertions(+), 527 deletions(-) diff --git a/morphlib/artifactresolver.py b/morphlib/artifactresolver.py index e64d458b..ae0cfcf5 100644 --- a/morphlib/artifactresolver.py +++ b/morphlib/artifactresolver.py @@ -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): @@ -91,12 +78,13 @@ class ArtifactResolver(object): source = queue.popleft() if source.morphology['kind'] == 'system': - systems = [source.artifacts[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) @@ -106,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 = source.artifacts[ - 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 = source.artifacts[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 @@ -147,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 = stratum_source.artifacts[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 = other_source.artifacts[ - 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) - queue.append(other_source) + stratum.add_dependency(other_stratum) + + 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 = chunk_source.artifacts[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'] @@ -215,23 +204,45 @@ class ArtifactResolver(object): build_depends = info.get('build-depends', None) - if build_depends is None: - for earlier_artifact in processed_artifacts: - if earlier_artifact.depends_on(chunk_artifact): - raise MutualDependencyError( - chunk_artifact, earlier_artifact) - chunk_artifact.add_dependency(earlier_artifact) - elif isinstance(build_depends, list): + for ca_name in chunk_source.split_rules.artifacts: + chunk_artifact = chunk_source.artifacts[ca_name] + + # Add our stratum's build depends as dependencies of this chunk + for other_stratum in stratum_build_depends: + chunk_artifact.add_dependency(other_stratum) + + # Add dependencies between chunks mentioned in this stratum + if isinstance(build_depends, list): for name in build_depends: - other_artifact = name_to_processed_artifact.get(name, None) - if other_artifact: - chunk_artifact.add_dependency(other_artifact) - else: + if name not in name_to_processed_artifacts: raise DependencyOrderError( - stratum, info['name'], name) + source, info['name'], name) + other_artifacts = name_to_processed_artifacts[name] + for other_artifact in other_artifacts: + for ca_name in chunk_source.split_rules.artifacts: + chunk_artifact = chunk_source.artifacts[ca_name] + chunk_artifact.add_dependency(other_artifact) else: - raise DependencyFormatError(stratum, info['name']) - processed_artifacts.append(chunk_artifact) - name_to_processed_artifact[info['name']] = chunk_artifact + raise DependencyFormatError(source, info['name']) + + # Add build dependencies between our stratum's artifacts + # and the chunk artifacts produced by this stratum. + matches, overlaps, unmatched = source.split_rules.partition( + ((chunk_name, ca_name) for ca_name + in chunk_source.split_rules.artifacts)) + for stratum in strata: + for (chunk_name, ca_name) in matches[stratum.name]: + chunk_artifact = chunk_source.artifacts[ca_name] + stratum.add_dependency(chunk_artifact) + # Only return chunks required to build strata we need + if chunk_artifact not in artifacts: + artifacts.append(chunk_artifact) + + + # Add these chunks to the processed artifacts, so other + # chunks may refer to them. + name_to_processed_artifacts[info['name']] = \ + [chunk_source.artifacts[n] for n + in chunk_source.split_rules.artifacts] return artifacts diff --git a/morphlib/artifactresolver_tests.py b/morphlib/artifactresolver_tests.py index b685902e..3a9b7f55 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)) - def test_resolve_single_chunk_with_two_artifact(self): + 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_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(artifacts[0].source, source) - self.assertEqual(artifacts[0].name, 'chunk-devel') - self.assertEqual(artifacts[0].dependencies, []) - self.assertEqual(artifacts[0].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.assertEqual(len(artifacts), + sum(len(s.split_rules.artifacts) for s in 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)) + 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, []) - 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() - - 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]]) + 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)) - 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,64 @@ 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(artifacts[0].source, stratum1) - self.assertEqual(artifacts[0].name, 'stratum1') - self.assertEqual(artifacts[0].dependencies, []) - self.assertEqual(artifacts[0].dependents, [artifacts[1], artifacts[2]]) - - 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, []) - - 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]]) + 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)) + + for c1_a in chunk1_artifacts: + self.assertEqual(c1_a.dependencies, []) + assert_depended_on_by_some(c1_a, stratum1_artifacts) + + 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) + + 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 +386,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 +436,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 +509,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 -- cgit v1.2.1 From c5c2583dcf6f3dffad06b1e635ed3e9c5fd6b6b2 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Wed, 18 Dec 2013 17:21:41 +0000 Subject: Split chunk morphologies according to new rules Filenames are now matched before chunks are constructed, so bins.create_chunk now takes a list of relative file names. bins.chunk_contents is gone, since this is now handled by passing source.split_rules.partition the file names. We now don't consider it to be a problem for directories to remain in the DESTDIR after artifacts have been removed, since we need to handle file matches implying their parent directories, and explicit matches against directories. NOTE: The bins_tests were broken in this patch, and are fixed in the next. This was done to try and aid readability of the patch series. Full functionality is still broken until stratum splitting is fixed. --- morphlib/bins.py | 70 ++++++---------------------------------------------- morphlib/builder2.py | 64 ++++++++++++++++++++++++++++++----------------- 2 files changed, 49 insertions(+), 85 deletions(-) diff --git a/morphlib/bins.py b/morphlib/bins.py index 6fb7dc5a..23e3b812 100644 --- a/morphlib/bins.py +++ b/morphlib/bins.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -50,60 +50,7 @@ def safe_makefile(self, tarinfo, targetpath): tarfile.TarFile.makefile = safe_makefile -def _chunk_filenames(rootdir, regexps, dump_memory_profile=None): - - '''Return the filenames for a chunk from the contents of a directory. - - Only files and directories that match at least one of the regular - expressions are accepted. The regular expressions are implicitly - anchored to the beginning of the string, but not the end. The - filenames are relative to rootdir. - - ''' - - dump_memory_profile = dump_memory_profile or (lambda msg: None) - - def matches(filename): - return any(x.match(filename) for x in compiled) - - def names_to_root(filename): - yield filename - while filename != rootdir: - filename = os.path.dirname(filename) - yield filename - - compiled = [re.compile(x) for x in regexps] - include = set() - for dirname, subdirs, basenames in os.walk(rootdir): - subdirpaths = [os.path.join(dirname, x) for x in subdirs] - subdirsymlinks = [x for x in subdirpaths if os.path.islink(x)] - filenames = [os.path.join(dirname, x) for x in basenames] - for filename in [dirname] + subdirsymlinks + filenames: - if matches(os.path.relpath(filename, rootdir)): - for name in names_to_root(filename): - if name not in include: - include.add(name) - else: - logging.debug('regexp MISMATCH: %s' % filename) - dump_memory_profile('after walking') - - return sorted(include) # get dirs before contents - - -def chunk_contents(rootdir, regexps): - ''' Return the list of files in a chunk, with the rootdir - stripped off. - - ''' - - filenames = _chunk_filenames(rootdir, regexps) - # The first entry is the rootdir directory, which we don't need - filenames.pop(0) - contents = [str[len(rootdir):] for str in filenames] - return contents - - -def create_chunk(rootdir, f, regexps, dump_memory_profile=None): +def create_chunk(rootdir, f, include, dump_memory_profile=None): '''Create a chunk from the contents of a directory. ``f`` is an open file handle, to which the tar file is written. @@ -118,14 +65,15 @@ def create_chunk(rootdir, f, regexps, dump_memory_profile=None): # does not complain about an implausibly old timestamp. normalized_timestamp = 683074800 - include = _chunk_filenames(rootdir, regexps, dump_memory_profile) dump_memory_profile('at beginning of create_chunk') + path_pairs = [(relname, os.path.join(rootdir, relname)) + for relname in include] tar = tarfile.open(fileobj=f, mode='w') - for filename in include: + for relname, filename in path_pairs: # Normalize mtime for everything. tarinfo = tar.gettarinfo(filename, - arcname=os.path.relpath(filename, rootdir)) + arcname=relname) tarinfo.ctime = normalized_timestamp tarinfo.mtime = normalized_timestamp if tarinfo.isreg(): @@ -135,11 +83,9 @@ def create_chunk(rootdir, f, regexps, dump_memory_profile=None): tar.addfile(tarinfo) tar.close() - include.remove(rootdir) - for filename in reversed(include): + for relname, filename in reversed(path_pairs): if os.path.isdir(filename) and not os.path.islink(filename): - if not os.listdir(filename): - os.rmdir(filename) + continue else: os.remove(filename) dump_memory_profile('after removing in create_chunks') diff --git a/morphlib/builder2.py b/morphlib/builder2.py index 34ebaa81..594786e6 100644 --- a/morphlib/builder2.py +++ b/morphlib/builder2.py @@ -422,33 +422,51 @@ class ChunkBuilder(BuilderBase): def assemble_chunk_artifacts(self, destdir): # pragma: no cover built_artifacts = [] filenames = [] - with self.build_watch('create-chunks'): - specs = self.artifact.source.morphology['products'] - 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] + 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.local_artifact_cache.put(artifact) as f: - contents = morphlib.bins.chunk_contents(destdir, patterns) - self.write_metadata(destdir, artifact_name, contents) + with self.build_watch('create-chunks'): + 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='assembling chunk %s' % artifact_name, - chatty=True) - self.app.status(msg='assembling into %s' % f.name, - chatty=True) 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)) -- cgit v1.2.1 From 4dcdb271865ed769b5ef2648f6779286fde2e2ed Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Wed, 18 Dec 2013 18:40:22 +0000 Subject: Unit tests: Fix up for changes to chunk construct api --- morphlib/bins_tests.py | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/morphlib/bins_tests.py b/morphlib/bins_tests.py index a9a94a44..60361ece 100644 --- a/morphlib/bins_tests.py +++ b/morphlib/bins_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -107,42 +107,33 @@ class ChunkTests(BinsTest): self.instdir_orig_files = self.recursive_lstat(self.instdir) - def create_chunk(self, regexps): + def create_chunk(self, includes): self.populate_instdir() - morphlib.bins.create_chunk(self.instdir, self.chunk_f, regexps) + morphlib.bins.create_chunk(self.instdir, self.chunk_f, includes) self.chunk_f.flush() - def chunk_contents(self, regexps): - self.populate_instdir() - return morphlib.bins.chunk_contents(self.instdir, regexps) - def unpack_chunk(self): os.mkdir(self.unpacked) morphlib.bins.unpack_binary(self.chunk_file, self.unpacked) - def test_empties_everything(self): - self.create_chunk(['.']) + def test_empties_files(self): + self.create_chunk(['bin/foo', 'lib/libfoo.so']) self.assertEqual([x for x, y in self.recursive_lstat(self.instdir)], - ['.']) + ['.', 'bin', 'lib']) def test_creates_and_unpacks_chunk_exactly(self): - self.create_chunk(['.']) + self.create_chunk(['bin', 'bin/foo', 'lib', 'lib/libfoo.so']) self.unpack_chunk() self.assertEqual(self.instdir_orig_files, self.recursive_lstat(self.unpacked)) def test_uses_only_matching_names(self): - self.create_chunk(['bin']) + self.create_chunk(['bin/foo']) self.unpack_chunk() self.assertEqual([x for x, y in self.recursive_lstat(self.unpacked)], ['.', 'bin', 'bin/foo']) self.assertEqual([x for x, y in self.recursive_lstat(self.instdir)], - ['.', 'lib', 'lib/libfoo.so']) - - def test_list_chunk_contents(self): - contents = self.chunk_contents(['.']) - self.assertEqual(contents, - ['/bin', '/bin/foo', '/lib', '/lib/libfoo.so']) + ['.', 'bin', 'lib', 'lib/libfoo.so']) def test_does_not_compress_artifact(self): self.create_chunk(['bin']) @@ -176,13 +167,13 @@ class ExtractTests(unittest.TestCase): with open(os.path.join(basedir, 'babar'), 'w') as f: pass os.symlink('babar', os.path.join(basedir, 'bar')) - return ['.'] + return ['babar'] linktar = self.create_chunk(make_linkfile) def make_file(basedir): with open(os.path.join(basedir, 'bar'), 'w') as f: pass - return ['.'] + return ['bar'] filetar = self.create_chunk(make_file) os.mkdir(self.unpacked) @@ -194,12 +185,12 @@ class ExtractTests(unittest.TestCase): def test_extracted_dirs_keep_links(self): def make_usrlink(basedir): os.symlink('.', os.path.join(basedir, 'usr')) - return ['.'] + return ['usr'] linktar = self.create_chunk(make_usrlink) def make_usrdir(basedir): os.mkdir(os.path.join(basedir, 'usr')) - return ['.'] + return ['usr'] dirtar = self.create_chunk(make_usrdir) morphlib.bins.unpack_binary_from_file(linktar, self.unpacked) @@ -210,14 +201,14 @@ class ExtractTests(unittest.TestCase): def test_extracted_files_follow_links(self): def make_usrlink(basedir): os.symlink('.', os.path.join(basedir, 'usr')) - return ['.'] + return ['usr'] linktar = self.create_chunk(make_usrlink) def make_usrdir(basedir): os.mkdir(os.path.join(basedir, 'usr')) with open(os.path.join(basedir, 'usr', 'foo'), 'w') as f: pass - return ['.'] + return ['usr', 'usr/foo'] dirtar = self.create_chunk(make_usrdir) morphlib.bins.unpack_binary_from_file(linktar, self.unpacked) -- cgit v1.2.1 From 57692c25491c4da23dac2691e035dba8f10e14d0 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 7 Jan 2014 17:51:08 +0000 Subject: Use given artifact when constructing Strata For some reason we used to create a new Artifact object with the same name as the Stratum morphology for our cache key. This is non-sensical, since we already have an Artifact object and it breaks splitting strata. NOTE: cmdtest tests do not pass, since they list files and artifacts produced, which has changed since the new default splitting rules were added. The next patch fixes this, but was kept as a separate commit for readability. --- morphlib/builder2.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/morphlib/builder2.py b/morphlib/builder2.py index 594786e6..43ae48c4 100644 --- a/morphlib/builder2.py +++ b/morphlib/builder2.py @@ -476,8 +476,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 @@ -513,16 +513,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 -- cgit v1.2.1 From 188fafc42db06161a1e1a39591b0d20cc083c5dc Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Wed, 18 Dec 2013 18:40:43 +0000 Subject: cmdtests: Take into account new constructed artifacts --- tests.as-root/branch-from-image-works.script | 4 +- tests.as-root/metadata-includes-repo-alias.script | 10 +- ...run-in-artifact-with-different-artifacts.script | 6 +- ...run-in-artifact-with-different-artifacts.stderr | 2 +- ...run-in-artifact-with-different-artifacts.stdout | 32 +- tests.as-root/system-overlap.stdout | 2 +- tests.as-root/tarball-image-is-sensible.stdout | 30 +- tests.build/bootstrap-mode.script | 16 +- tests.build/bootstrap-mode.stdout | 11 +- tests.build/build-stratum-with-submodules.script | 4 +- tests.build/build-stratum-with-submodules.stdout | 4 - tests.build/build-system-autotools.script | 8 +- tests.build/build-system-autotools.stdout | 5 - tests.build/build-system-cmake.script | 8 +- tests.build/build-system-cmake.stdout | 6 - tests.build/build-system-cpan.script | 8 +- tests.build/build-system-cpan.stdout | 1 - tests.build/build-system-python-distutils.script | 8 +- tests.build/build-system-python-distutils.stdout | 4 - tests.build/build-system.script | 4 +- tests.build/build-system.stdout | 4 - tests.build/morphless-chunks.script | 8 +- tests.build/morphless-chunks.stdout | 5 - tests.build/prefix.script | 6 +- tests.build/rebuild-cached-stratum.script | 16 +- tests.build/rebuild-cached-stratum.stdout | 26 +- tests.build/stratum-overlap-writes-overlap.script | 6 +- tests.build/stratum-overlap-writes-overlap.stdout | 4 +- tests/show-dependencies.stdout | 1776 ++++++++++++++++++-- 29 files changed, 1809 insertions(+), 215 deletions(-) diff --git a/tests.as-root/branch-from-image-works.script b/tests.as-root/branch-from-image-works.script index 942301e8..c9d50bbb 100755 --- a/tests.as-root/branch-from-image-works.script +++ b/tests.as-root/branch-from-image-works.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2013 Codethink Limited +# Copyright (C) 2013-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ tar -xf "$tar" -C "$extracted" get_sha1(){ sed -nre '/sha1/s/^.*([0-9a-f]{40}).*$/\1/p' "$1" } -hello_chunk_commit=$(get_sha1 "$extracted/baserock/hello.meta") +hello_chunk_commit=$(get_sha1 "$extracted/baserock/hello-bins.meta") # Make a commit so that petrifying from HEAD is detectable chunkrepo="$DATADIR/chunk-repo" diff --git a/tests.as-root/metadata-includes-repo-alias.script b/tests.as-root/metadata-includes-repo-alias.script index 511222e2..9e4a5d98 100755 --- a/tests.as-root/metadata-includes-repo-alias.script +++ b/tests.as-root/metadata-includes-repo-alias.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2013 Codethink Limited +# Copyright (C) 2013-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -39,11 +39,11 @@ cd "$extracted/baserock" # Check for test:morphs in System and Stratum #grep -q -F -e test:morphs hello-tarball.meta # tarball bug -grep -q -F -e test:morphs hello-stratum.meta -grep -q -F -e test:morphs linux-stratum.meta +grep -q -F -e test:morphs hello-stratum-runtime.meta +grep -q -F -e test:morphs linux-stratum-runtime.meta # Check for test:kernel-repo in linux -grep -q -F -e test:kernel-repo linux.meta +grep -q -F -e test:kernel-repo linux-misc.meta # Check for test:chunk-repo in hello -grep -q -F -e test:chunk-repo hello.meta +grep -q -F -e test:chunk-repo hello-bins.meta diff --git a/tests.as-root/run-in-artifact-with-different-artifacts.script b/tests.as-root/run-in-artifact-with-different-artifacts.script index 0016b278..ff944af4 100755 --- a/tests.as-root/run-in-artifact-with-different-artifacts.script +++ b/tests.as-root/run-in-artifact-with-different-artifacts.script @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -27,9 +27,9 @@ set -eu test:morphs master linux-system system=$(find "$DATADIR/cache/artifacts" -maxdepth 1 -name '*.system.*-rootfs') -chunk=$(find "$DATADIR/cache/artifacts" -maxdepth 1 -name '*.chunk.linux') +chunk=$(find "$DATADIR/cache/artifacts" -maxdepth 1 -name '*.chunk.linux-misc') stratum=$(find "$DATADIR/cache/artifacts" -maxdepth 1 \ - -name '*.stratum.linux-stratum') + -name '*.stratum.linux-stratum-runtime') # Run 'run-in-artifact' with the system artifact. echo "System:" diff --git a/tests.as-root/run-in-artifact-with-different-artifacts.stderr b/tests.as-root/run-in-artifact-with-different-artifacts.stderr index 6e9a5f8f..236954f8 100644 --- a/tests.as-root/run-in-artifact-with-different-artifacts.stderr +++ b/tests.as-root/run-in-artifact-with-different-artifacts.stderr @@ -1 +1 @@ -ERROR: Artifact TMP/cache/artifacts/ef470cc0efd6c64992198f9d224e4b68dc452528919e2b7cf3f10efa1b88541a.stratum.linux-stratum cannot be extracted or mounted +ERROR: Artifact TMP/cache/artifacts/4a8b7a698ae79f417b1ff1541b883bd9f99fdf287a0d1a4176e7353dbe51a5fa.stratum.linux-stratum-runtime cannot be extracted or mounted diff --git a/tests.as-root/run-in-artifact-with-different-artifacts.stdout b/tests.as-root/run-in-artifact-with-different-artifacts.stdout index 281ab109..7473990b 100644 --- a/tests.as-root/run-in-artifact-with-different-artifacts.stdout +++ b/tests.as-root/run-in-artifact-with-different-artifacts.stdout @@ -1,14 +1,32 @@ System: -hello-stratum.meta -hello.meta -linux-stratum.meta +hello-bins.meta +hello-devel.meta +hello-doc.meta +hello-libs.meta +hello-locale.meta +hello-misc.meta +hello-stratum-devel.meta +hello-stratum-runtime.meta +linux-bins.meta +linux-devel.meta +linux-doc.meta +linux-libs.meta +linux-locale.meta +linux-misc.meta +linux-stratum-devel.meta +linux-stratum-runtime.meta linux-system-rootfs.meta -linux.meta -tools-stratum.meta -tools.meta +tools-bins.meta +tools-devel.meta +tools-doc.meta +tools-libs.meta +tools-locale.meta +tools-misc.meta +tools-stratum-devel.meta +tools-stratum-runtime.meta Chunk: -linux.meta +linux-misc.meta Stratum: Failed diff --git a/tests.as-root/system-overlap.stdout b/tests.as-root/system-overlap.stdout index fe106ad9..f67d54c8 100644 --- a/tests.as-root/system-overlap.stdout +++ b/tests.as-root/system-overlap.stdout @@ -1,3 +1,3 @@ -foo-barqux-stratum foo-baz-stratum +foo-barqux-stratum-runtime foo-baz-stratum-runtime bin/foo bin/bar diff --git a/tests.as-root/tarball-image-is-sensible.stdout b/tests.as-root/tarball-image-is-sensible.stdout index c896c847..4141dee8 100644 --- a/tests.as-root/tarball-image-is-sensible.stdout +++ b/tests.as-root/tarball-image-is-sensible.stdout @@ -1,11 +1,29 @@ ./baserock/ -./baserock/hello-stratum.meta +./baserock/hello-bins.meta +./baserock/hello-devel.meta +./baserock/hello-doc.meta +./baserock/hello-libs.meta +./baserock/hello-locale.meta +./baserock/hello-misc.meta +./baserock/hello-stratum-devel.meta +./baserock/hello-stratum-runtime.meta ./baserock/hello-tarball-rootfs.meta -./baserock/hello.meta -./baserock/link-stratum.meta -./baserock/links.meta -./baserock/linux-stratum.meta -./baserock/linux.meta +./baserock/link-stratum-devel.meta +./baserock/link-stratum-runtime.meta +./baserock/links-bins.meta +./baserock/links-devel.meta +./baserock/links-doc.meta +./baserock/links-libs.meta +./baserock/links-locale.meta +./baserock/links-misc.meta +./baserock/linux-bins.meta +./baserock/linux-devel.meta +./baserock/linux-doc.meta +./baserock/linux-libs.meta +./baserock/linux-locale.meta +./baserock/linux-misc.meta +./baserock/linux-stratum-devel.meta +./baserock/linux-stratum-runtime.meta ./bin/ ./bin/hello* ./bin/true diff --git a/tests.build/bootstrap-mode.script b/tests.build/bootstrap-mode.script index 923fb21f..0ac66220 100755 --- a/tests.build/bootstrap-mode.script +++ b/tests.build/bootstrap-mode.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -29,11 +29,15 @@ set -eu test:morphs-repo master hello-system cd "$DATADIR/cache/artifacts" -echo "build-essential stratum:" -stratum=$(ls *.stratum.build-essential) -cat $stratum | sed 's/[a-f0-9]\{64\}/xxxx/g' -echo +echo "build-essential strata:" +for stratum in $(find . -regex '.*\.stratum\.build-essential-[^.]*$' | sort) +do + echo "$stratum" + sed 's/[a-f0-9]\{64\}/xxxx/g' "$stratum" + echo +done echo echo "hello-system:" system=$(ls *hello-system-rootfs) -tar tf "$system" | LC_ALL=C sort | sed '/^\.\/./s:^\./::' +tar tf "$system" | LC_ALL=C sort -u | sed '/^\.\/./s:^\./::' \ + | grep -v '^baserock' diff --git a/tests.build/bootstrap-mode.stdout b/tests.build/bootstrap-mode.stdout index 329cdd78..b59d0029 100644 --- a/tests.build/bootstrap-mode.stdout +++ b/tests.build/bootstrap-mode.stdout @@ -1,12 +1,11 @@ -build-essential stratum: -["xxxx.chunk.cc"] +build-essential strata: +./5bbfd4cb94017e7b72e20ee4f91a76bed8085aa96176cf87ecce54ec71d5ddae.stratum.build-essential-devel +["xxxx.chunk.cc-devel", "xxxx.chunk.cc-doc"] +./c8fe8efd4f8c6edcd309bb2cf0a308c93dfd905bbff64be98c5b07f350951fde.stratum.build-essential-runtime +["xxxx.chunk.cc-bins", "xxxx.chunk.cc-libs", "xxxx.chunk.cc-locale", "xxxx.chunk.cc-misc"] hello-system: ./ -baserock/ -baserock/hello-stratum.meta -baserock/hello-system-rootfs.meta -baserock/hello.meta etc/ etc/fstab etc/os-release diff --git a/tests.build/build-stratum-with-submodules.script b/tests.build/build-stratum-with-submodules.script index c3c00578..c996e769 100755 --- a/tests.build/build-stratum-with-submodules.script +++ b/tests.build/build-stratum-with-submodules.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -71,5 +71,5 @@ EOF test:morphs-repo master hello-system system=$(ls "$DATADIR/cache/artifacts/"*hello-system-rootfs) -tar tf $system | LC_ALL=C sort | sed '/^\.\/./s:^\./::' +tar tf $system | LC_ALL=C sort | sed '/^\.\/./s:^\./::' | grep -v '^baserock/' diff --git a/tests.build/build-stratum-with-submodules.stdout b/tests.build/build-stratum-with-submodules.stdout index bf9836d7..6dda5049 100644 --- a/tests.build/build-stratum-with-submodules.stdout +++ b/tests.build/build-stratum-with-submodules.stdout @@ -1,8 +1,4 @@ ./ -baserock/ -baserock/hello-stratum.meta -baserock/hello-system-rootfs.meta -baserock/parent.meta etc/ etc/fstab etc/os-release diff --git a/tests.build/build-system-autotools.script b/tests.build/build-system-autotools.script index c2171750..ba5cd32f 100755 --- a/tests.build/build-system-autotools.script +++ b/tests.build/build-system-autotools.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -52,7 +52,5 @@ git commit --quiet -m "Convert hello to an autotools project" for chunk in "$DATADIR/cache/artifacts/"*.chunk.* do - echo "$chunk:" | sed 's/[^.]*//' - tar -tf "$chunk" | LC_ALL=C sort | sed '/^\.\/./s:^\./::' - echo -done + tar -tf "$chunk" +done | LC_ALL=C sort -u | sed '/^\.\/./s:^\./::' | grep -Ee '^(bin|etc)' diff --git a/tests.build/build-system-autotools.stdout b/tests.build/build-system-autotools.stdout index 8077cac2..683441c9 100644 --- a/tests.build/build-system-autotools.stdout +++ b/tests.build/build-system-autotools.stdout @@ -1,8 +1,3 @@ -.chunk.hello: -./ -baserock/ -baserock/hello.meta bin/ bin/hello etc/ - diff --git a/tests.build/build-system-cmake.script b/tests.build/build-system-cmake.script index 00b9ed23..ab5186d7 100755 --- a/tests.build/build-system-cmake.script +++ b/tests.build/build-system-cmake.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -53,7 +53,5 @@ git commit --quiet -m "Convert hello to a cmake project" for chunk in "$DATADIR/cache/artifacts/"*.chunk.* do - echo "$chunk:" | sed 's/[^.]*//' - tar -tf "$chunk" | LC_ALL=C sort | sed '/^\.\/./s:^\./::' - echo -done + tar -tf "$chunk" +done | LC_ALL=C sort -u | sed '/^\.\/./s:^\./::' | grep -Ee '^(usr/)?(bin|etc)' diff --git a/tests.build/build-system-cmake.stdout b/tests.build/build-system-cmake.stdout index ccf80a86..3410b113 100644 --- a/tests.build/build-system-cmake.stdout +++ b/tests.build/build-system-cmake.stdout @@ -1,8 +1,2 @@ -.chunk.hello: -./ -baserock/ -baserock/hello.meta -usr/ usr/bin/ usr/bin/hello - diff --git a/tests.build/build-system-cpan.script b/tests.build/build-system-cpan.script index b1823eb5..f66d4027 100755 --- a/tests.build/build-system-cpan.script +++ b/tests.build/build-system-cpan.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -81,7 +81,5 @@ git commit -q -m "Set custom install prefix for hello" for chunk in "$DATADIR/cache/artifacts/"*.chunk.* do - echo "$chunk:" | sed 's/[^.]*//' - tar -tf "$chunk" | LC_ALL=C sort | - sed -e '/^\.\/./s:^\./::' | grep -F "bin/hello" -done + tar -tf "$chunk" +done | LC_ALL=C sort | sed '/^\.\/./s:^\./::' | grep -F 'bin/hello' diff --git a/tests.build/build-system-cpan.stdout b/tests.build/build-system-cpan.stdout index 5cbe4c73..180e949b 100644 --- a/tests.build/build-system-cpan.stdout +++ b/tests.build/build-system-cpan.stdout @@ -1,2 +1 @@ -.chunk.hello: bin/hello diff --git a/tests.build/build-system-python-distutils.script b/tests.build/build-system-python-distutils.script index a0469528..e1dccb4b 100755 --- a/tests.build/build-system-python-distutils.script +++ b/tests.build/build-system-python-distutils.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -79,10 +79,8 @@ git commit -q -m "Set custom install prefix for hello" for chunk in "$DATADIR/cache/artifacts/"*.chunk.* do - echo "$chunk:" | sed 's/[^.]*//' - tar -tf "$chunk" | LC_ALL=C sort | sed '/^\.\/./s:^\./::' - echo -done | + tar -tf "$chunk" +done | LC_ALL=C sort -u | sed '/^\.\/./s:^\./::' | grep -Ee '^(bin|lib)' | sed -e 's:^local/::' \ -e 's:lib/python2.[6-9]/:lib/python2.x/:' \ -e 's:/hello-0\.0\.0[^/]*\.egg-info$:/hello.egg-info/:' \ diff --git a/tests.build/build-system-python-distutils.stdout b/tests.build/build-system-python-distutils.stdout index 4d4abdbb..4d4c3a1e 100644 --- a/tests.build/build-system-python-distutils.stdout +++ b/tests.build/build-system-python-distutils.stdout @@ -1,7 +1,3 @@ -.chunk.hello: -./ -baserock/ -baserock/hello.meta bin/ bin/hello lib/ diff --git a/tests.build/build-system.script b/tests.build/build-system.script index 75b9d0d0..56d80735 100755 --- a/tests.build/build-system.script +++ b/tests.build/build-system.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -24,4 +24,4 @@ set -eu test:morphs-repo master hello-system system=$(ls "$DATADIR/cache/artifacts/"*hello-system-rootfs) -tar tf $system | LC_ALL=C sort | sed '/^\.\/./s:^\./::' +tar tf $system | LC_ALL=C sort | sed '/^\.\/./s:^\./::' | grep -v '^baserock/' diff --git a/tests.build/build-system.stdout b/tests.build/build-system.stdout index 3d5201ee..2e8270dc 100644 --- a/tests.build/build-system.stdout +++ b/tests.build/build-system.stdout @@ -1,8 +1,4 @@ ./ -baserock/ -baserock/hello-stratum.meta -baserock/hello-system-rootfs.meta -baserock/hello.meta bin/ bin/hello etc/ diff --git a/tests.build/morphless-chunks.script b/tests.build/morphless-chunks.script index c9294c3e..9a8b41dd 100755 --- a/tests.build/morphless-chunks.script +++ b/tests.build/morphless-chunks.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2012-2013 Codethink Limited +# Copyright (C) 2012-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -44,7 +44,5 @@ git commit -q -m "Convert hello into an autodetectable chunk" for chunk in "$DATADIR/cache/artifacts/"*.chunk.* do - echo "$chunk:" | sed 's/[^.]*//' - tar -tf "$chunk" | LC_ALL=C sort | sed '/^\.\/./s:^\./::' - echo -done + tar -tf "$chunk" +done | cat >/dev/null # No files get installed apart from metadata diff --git a/tests.build/morphless-chunks.stdout b/tests.build/morphless-chunks.stdout index 22292c14..e69de29b 100644 --- a/tests.build/morphless-chunks.stdout +++ b/tests.build/morphless-chunks.stdout @@ -1,5 +0,0 @@ -.chunk.hello: -./ -baserock/ -baserock/hello.meta - diff --git a/tests.build/prefix.script b/tests.build/prefix.script index e9b8ecd2..ca9648c9 100755 --- a/tests.build/prefix.script +++ b/tests.build/prefix.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2013 Codethink Limited +# Copyright (C) 2013-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -82,6 +82,6 @@ git commit -q -m "Update stratum" test:morphs-repo master hello-system cd "$DATADIR/cache/artifacts" -first_chunk=$(ls -1 *.chunk.xyzzy | cut -c -64) -second_chunk=$(ls -1 *.chunk.plugh | cut -c -64) +first_chunk=$(ls -1 *.chunk.xyzzy-* | head -n1 | cut -c -64) +second_chunk=$(ls -1 *.chunk.plugh-* | head -n1 | cut -c -64) cat $first_chunk.build-log $second_chunk.build-log diff --git a/tests.build/rebuild-cached-stratum.script b/tests.build/rebuild-cached-stratum.script index 306c16f2..0014e545 100755 --- a/tests.build/rebuild-cached-stratum.script +++ b/tests.build/rebuild-cached-stratum.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -30,7 +30,7 @@ cache="$DATADIR/cache/artifacts" git checkout --quiet farrokh && git checkout --quiet -b rebuild-cached-stratum) -# Make a branch in the morphs repo and modify the stratum to refer to +# Make a branch in the morphs repo and modify the stratum to refer to # the new chunk branch. (cd "$DATADIR/morphs-repo" && git checkout --quiet -b rebuild-cached-stratum && @@ -42,18 +42,18 @@ cache="$DATADIR/cache/artifacts" "$SRCDIR/scripts/test-morph" build-morphology \ test:morphs-repo rebuild-cached-stratum hello-system echo "first build:" -(cd "$cache" && ls *.chunk.* *hello-stratum | sed 's/^[^.]*\./ /' | - LC_ALL=C sort) +(cd "$cache" && ls *.chunk.* *hello-stratum-* | sed 's/^[^.]*\./ /' | + LC_ALL=C sort -u) # Change the chunk. -(cd "$DATADIR/chunk-repo" && - echo >> hello.c && +(cd "$DATADIR/chunk-repo" && + echo >> hello.c && git commit --quiet -am change) # Rebuild. "$SRCDIR/scripts/test-morph" build-morphology \ test:morphs-repo rebuild-cached-stratum hello-system echo "second build:" -(cd "$cache" && ls *.chunk.* *hello-stratum | sed 's/^[^.]*\./ /' | - LC_ALL=C sort) +(cd "$cache" && ls *.chunk.* *hello-stratum-* | sed 's/^[^.]*\./ /' | + LC_ALL=C sort -u) diff --git a/tests.build/rebuild-cached-stratum.stdout b/tests.build/rebuild-cached-stratum.stdout index eee106f5..9c53ee60 100644 --- a/tests.build/rebuild-cached-stratum.stdout +++ b/tests.build/rebuild-cached-stratum.stdout @@ -1,8 +1,22 @@ first build: - chunk.hello - stratum.hello-stratum + chunk.hello-bins + chunk.hello-devel + chunk.hello-doc + chunk.hello-libs + chunk.hello-locale + chunk.hello-misc + stratum.hello-stratum-devel + stratum.hello-stratum-devel.meta + stratum.hello-stratum-runtime + stratum.hello-stratum-runtime.meta second build: - chunk.hello - chunk.hello - stratum.hello-stratum - stratum.hello-stratum + chunk.hello-bins + chunk.hello-devel + chunk.hello-doc + chunk.hello-libs + chunk.hello-locale + chunk.hello-misc + stratum.hello-stratum-devel + stratum.hello-stratum-devel.meta + stratum.hello-stratum-runtime + stratum.hello-stratum-runtime.meta diff --git a/tests.build/stratum-overlap-writes-overlap.script b/tests.build/stratum-overlap-writes-overlap.script index ca06454b..fe4ed4ee 100755 --- a/tests.build/stratum-overlap-writes-overlap.script +++ b/tests.build/stratum-overlap-writes-overlap.script @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011-2013 Codethink Limited +# Copyright (C) 2011-2014 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -27,9 +27,9 @@ cache="$DATADIR/cache/artifacts" "$SRCDIR/scripts/test-morph" build-morphology \ test:morphs-repo overlap hello-system > /dev/null "$SRCDIR/scripts/list-overlaps" groups \ - "$cache"/*.stratum.hello-stratum.overlaps | + "$cache"/*.stratum.hello-stratum-*.overlaps | while IFS='\n' read overlaps; do echo $overlaps "$SRCDIR/scripts/list-overlaps" list-files \ - "$cache"/*.stratum.hello-stratum.overlaps $overlaps + "$cache"/*.stratum.hello-stratum-*.overlaps $overlaps done diff --git a/tests.build/stratum-overlap-writes-overlap.stdout b/tests.build/stratum-overlap-writes-overlap.stdout index 40485659..1e36ca83 100644 --- a/tests.build/stratum-overlap-writes-overlap.stdout +++ b/tests.build/stratum-overlap-writes-overlap.stdout @@ -1,4 +1,4 @@ -overlap-foo-baz overlap-foobar overlap-fooqux +overlap-foo-baz-bins overlap-foobar-bins overlap-fooqux-bins bin/foo -overlap-foo-baz overlap-foobar +overlap-foo-baz-bins overlap-foobar-bins bin/bar diff --git a/tests/show-dependencies.stdout b/tests/show-dependencies.stdout index ab1453db..2c70d30a 100644 --- a/tests/show-dependencies.stdout +++ b/tests/show-dependencies.stdout @@ -1,100 +1,1680 @@ dependency graph for test-repo|master|xfce-system: test-repo|master|xfce-system|xfce-system-rootfs - -> test-repo|master|xfce-core|xfce-core - test-repo|master|xfce-core|xfce-core - -> test-repo|master|exo|exo - -> test-repo|master|garcon|garcon - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|libxfce4util|libxfce4util - -> test-repo|master|thunar|thunar - -> test-repo|master|tumbler|tumbler - -> test-repo|master|xfce4-appfinder|xfce4-appfinder - -> test-repo|master|xfce4-panel|xfce4-panel - -> test-repo|master|xfce4-session|xfce4-session - -> test-repo|master|xfce4-settings|xfce4-settings - -> test-repo|master|xfconf|xfconf - -> test-repo|master|xfdesktop|xfdesktop - -> test-repo|master|xfwm4|xfwm4 - test-repo|master|gtk-xfce-engine|gtk-xfce-engine - -> test-repo|master|garcon|garcon - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|xfconf|xfconf - test-repo|master|xfce4-appfinder|xfce4-appfinder - -> test-repo|master|garcon|garcon - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|xfconf|xfconf - test-repo|master|xfdesktop|xfdesktop - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|xfconf|xfconf - test-repo|master|xfwm4|xfwm4 - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|xfconf|xfconf - test-repo|master|xfce4-session|xfce4-session - -> test-repo|master|exo|exo - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|xfconf|xfconf - test-repo|master|xfce4-settings|xfce4-settings - -> test-repo|master|exo|exo - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|xfconf|xfconf - test-repo|master|xfce4-panel|xfce4-panel - -> test-repo|master|exo|exo - -> test-repo|master|garcon|garcon - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - test-repo|master|tumbler|tumbler - -> test-repo|master|gtk-stack|gtk-stack - test-repo|master|thunar|thunar - -> test-repo|master|exo|exo - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4ui|libxfce4ui - test-repo|master|garcon|garcon - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4util|libxfce4util - test-repo|master|exo|exo - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4util|libxfce4util - test-repo|master|libxfce4ui|libxfce4ui - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|xfconf|xfconf - test-repo|master|xfconf|xfconf - -> test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|libxfce4util|libxfce4util - test-repo|master|libxfce4util|libxfce4util - -> test-repo|master|gtk-stack|gtk-stack - test-repo|master|gtk-stack|gtk-stack - -> test-repo|master|cairo|cairo - -> test-repo|master|dbus-glib|dbus-glib - -> test-repo|master|dbus|dbus - -> test-repo|master|fontconfig|fontconfig - -> test-repo|master|freetype|freetype - -> test-repo|master|gdk-pixbuf|gdk-pixbuf - -> test-repo|master|glib|glib - -> test-repo|master|gtk|gtk - -> test-repo|master|pango|pango - test-repo|master|dbus-glib|dbus-glib - -> test-repo|master|dbus|dbus - -> test-repo|master|glib|glib - test-repo|master|dbus|dbus - test-repo|master|gtk|gtk - -> test-repo|master|cairo|cairo - -> test-repo|master|gdk-pixbuf|gdk-pixbuf - -> test-repo|master|glib|glib - -> test-repo|master|pango|pango - test-repo|master|gdk-pixbuf|gdk-pixbuf - -> test-repo|master|glib|glib - test-repo|master|glib|glib - test-repo|master|pango|pango - -> test-repo|master|fontconfig|fontconfig - -> test-repo|master|freetype|freetype - test-repo|master|cairo|cairo - test-repo|master|fontconfig|fontconfig - test-repo|master|freetype|freetype + -> test-repo|master|xfce-core|xfce-core-devel + -> test-repo|master|xfce-core|xfce-core-runtime + test-repo|master|xfce-core|xfce-core-runtime + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine-bins + -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine-libs + -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine-locale + -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine-misc + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + -> test-repo|master|thunar|thunar-bins + -> test-repo|master|thunar|thunar-libs + -> test-repo|master|thunar|thunar-locale + -> test-repo|master|thunar|thunar-misc + -> test-repo|master|tumbler|tumbler-bins + -> test-repo|master|tumbler|tumbler-libs + -> test-repo|master|tumbler|tumbler-locale + -> test-repo|master|tumbler|tumbler-misc + -> test-repo|master|xfce4-appfinder|xfce4-appfinder-bins + -> test-repo|master|xfce4-appfinder|xfce4-appfinder-libs + -> test-repo|master|xfce4-appfinder|xfce4-appfinder-locale + -> test-repo|master|xfce4-appfinder|xfce4-appfinder-misc + -> test-repo|master|xfce4-panel|xfce4-panel-bins + -> test-repo|master|xfce4-panel|xfce4-panel-libs + -> test-repo|master|xfce4-panel|xfce4-panel-locale + -> test-repo|master|xfce4-panel|xfce4-panel-misc + -> test-repo|master|xfce4-session|xfce4-session-bins + -> test-repo|master|xfce4-session|xfce4-session-libs + -> test-repo|master|xfce4-session|xfce4-session-locale + -> test-repo|master|xfce4-session|xfce4-session-misc + -> test-repo|master|xfce4-settings|xfce4-settings-bins + -> test-repo|master|xfce4-settings|xfce4-settings-libs + -> test-repo|master|xfce4-settings|xfce4-settings-locale + -> test-repo|master|xfce4-settings|xfce4-settings-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + -> test-repo|master|xfdesktop|xfdesktop-bins + -> test-repo|master|xfdesktop|xfdesktop-libs + -> test-repo|master|xfdesktop|xfdesktop-locale + -> test-repo|master|xfdesktop|xfdesktop-misc + -> test-repo|master|xfwm4|xfwm4-bins + -> test-repo|master|xfwm4|xfwm4-libs + -> test-repo|master|xfwm4|xfwm4-locale + -> test-repo|master|xfwm4|xfwm4-misc + test-repo|master|gtk-xfce-engine|gtk-xfce-engine-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|gtk-xfce-engine|gtk-xfce-engine-locale + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|gtk-xfce-engine|gtk-xfce-engine-libs + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|gtk-xfce-engine|gtk-xfce-engine-bins + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-appfinder|xfce4-appfinder-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-appfinder|xfce4-appfinder-locale + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-appfinder|xfce4-appfinder-libs + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-appfinder|xfce4-appfinder-bins + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfdesktop|xfdesktop-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfdesktop|xfdesktop-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfdesktop|xfdesktop-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfdesktop|xfdesktop-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfwm4|xfwm4-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfwm4|xfwm4-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfwm4|xfwm4-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfwm4|xfwm4-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-session|xfce4-session-misc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-session|xfce4-session-locale + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-session|xfce4-session-libs + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-session|xfce4-session-bins + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-settings|xfce4-settings-misc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-settings|xfce4-settings-locale + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-settings|xfce4-settings-libs + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-settings|xfce4-settings-bins + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-panel|xfce4-panel-misc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|xfce4-panel|xfce4-panel-locale + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|xfce4-panel|xfce4-panel-libs + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|xfce4-panel|xfce4-panel-bins + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|tumbler|tumbler-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|tumbler|tumbler-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|tumbler|tumbler-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|tumbler|tumbler-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|thunar|thunar-misc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|thunar|thunar-locale + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|thunar|thunar-libs + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|thunar|thunar-bins + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|xfce-core|xfce-core-devel + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine-devel + -> test-repo|master|gtk-xfce-engine|gtk-xfce-engine-doc + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|thunar|thunar-devel + -> test-repo|master|thunar|thunar-doc + -> test-repo|master|tumbler|tumbler-devel + -> test-repo|master|tumbler|tumbler-doc + -> test-repo|master|xfce4-appfinder|xfce4-appfinder-devel + -> test-repo|master|xfce4-appfinder|xfce4-appfinder-doc + -> test-repo|master|xfce4-panel|xfce4-panel-devel + -> test-repo|master|xfce4-panel|xfce4-panel-doc + -> test-repo|master|xfce4-session|xfce4-session-devel + -> test-repo|master|xfce4-session|xfce4-session-doc + -> test-repo|master|xfce4-settings|xfce4-settings-devel + -> test-repo|master|xfce4-settings|xfce4-settings-doc + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfdesktop|xfdesktop-devel + -> test-repo|master|xfdesktop|xfdesktop-doc + -> test-repo|master|xfwm4|xfwm4-devel + -> test-repo|master|xfwm4|xfwm4-doc + test-repo|master|gtk-xfce-engine|gtk-xfce-engine-doc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|gtk-xfce-engine|gtk-xfce-engine-devel + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-appfinder|xfce4-appfinder-doc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-appfinder|xfce4-appfinder-devel + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfdesktop|xfdesktop-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfdesktop|xfdesktop-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfwm4|xfwm4-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfwm4|xfwm4-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-session|xfce4-session-doc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-session|xfce4-session-devel + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-settings|xfce4-settings-doc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-settings|xfce4-settings-devel + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfce4-panel|xfce4-panel-doc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|xfce4-panel|xfce4-panel-devel + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|garcon|garcon-bins + -> test-repo|master|garcon|garcon-devel + -> test-repo|master|garcon|garcon-doc + -> test-repo|master|garcon|garcon-libs + -> test-repo|master|garcon|garcon-locale + -> test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|garcon|garcon-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|garcon|garcon-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|garcon|garcon-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|garcon|garcon-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|tumbler|tumbler-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|tumbler|tumbler-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|thunar|thunar-doc + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|thunar|thunar-devel + -> test-repo|master|exo|exo-bins + -> test-repo|master|exo|exo-devel + -> test-repo|master|exo|exo-doc + -> test-repo|master|exo|exo-libs + -> test-repo|master|exo|exo-locale + -> test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|libxfce4ui|libxfce4ui-misc + test-repo|master|exo|exo-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|exo|exo-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|exo|exo-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|exo|exo-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|libxfce4ui|libxfce4ui-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|libxfce4ui|libxfce4ui-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|libxfce4ui|libxfce4ui-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|libxfce4ui|libxfce4ui-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|garcon|garcon-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|garcon|garcon-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|exo|exo-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|exo|exo-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|libxfce4ui|libxfce4ui-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|libxfce4ui|libxfce4ui-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|xfconf|xfconf-bins + -> test-repo|master|xfconf|xfconf-devel + -> test-repo|master|xfconf|xfconf-doc + -> test-repo|master|xfconf|xfconf-libs + -> test-repo|master|xfconf|xfconf-locale + -> test-repo|master|xfconf|xfconf-misc + test-repo|master|xfconf|xfconf-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|xfconf|xfconf-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|xfconf|xfconf-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|xfconf|xfconf-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|xfconf|xfconf-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|xfconf|xfconf-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|libxfce4util|libxfce4util-misc + test-repo|master|libxfce4util|libxfce4util-misc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|libxfce4util|libxfce4util-locale + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|libxfce4util|libxfce4util-libs + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|libxfce4util|libxfce4util-bins + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|libxfce4util|libxfce4util-doc + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|libxfce4util|libxfce4util-devel + -> test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|gtk-stack|gtk-stack-runtime + test-repo|master|gtk-stack|gtk-stack-runtime + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|dbus-glib|dbus-glib-bins + -> test-repo|master|dbus-glib|dbus-glib-libs + -> test-repo|master|dbus-glib|dbus-glib-locale + -> test-repo|master|dbus-glib|dbus-glib-misc + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|gtk|gtk-bins + -> test-repo|master|gtk|gtk-libs + -> test-repo|master|gtk|gtk-locale + -> test-repo|master|gtk|gtk-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|dbus-glib|dbus-glib-misc + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|dbus-glib|dbus-glib-locale + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|dbus-glib|dbus-glib-libs + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|dbus-glib|dbus-glib-bins + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|gtk|gtk-misc + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|gtk|gtk-locale + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|gtk|gtk-libs + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|gtk|gtk-bins + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|gtk-stack|gtk-stack-devel + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|dbus-glib|dbus-glib-devel + -> test-repo|master|dbus-glib|dbus-glib-doc + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|gtk|gtk-devel + -> test-repo|master|gtk|gtk-doc + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + test-repo|master|dbus-glib|dbus-glib-doc + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|dbus-glib|dbus-glib-devel + -> test-repo|master|dbus|dbus-bins + -> test-repo|master|dbus|dbus-devel + -> test-repo|master|dbus|dbus-doc + -> test-repo|master|dbus|dbus-libs + -> test-repo|master|dbus|dbus-locale + -> test-repo|master|dbus|dbus-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|dbus|dbus-misc + test-repo|master|dbus|dbus-locale + test-repo|master|dbus|dbus-libs + test-repo|master|dbus|dbus-bins + test-repo|master|dbus|dbus-doc + test-repo|master|dbus|dbus-devel + test-repo|master|gtk|gtk-doc + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|gtk|gtk-devel + -> test-repo|master|cairo|cairo-bins + -> test-repo|master|cairo|cairo-devel + -> test-repo|master|cairo|cairo-doc + -> test-repo|master|cairo|cairo-libs + -> test-repo|master|cairo|cairo-locale + -> test-repo|master|cairo|cairo-misc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + -> test-repo|master|pango|pango-bins + -> test-repo|master|pango|pango-devel + -> test-repo|master|pango|pango-doc + -> test-repo|master|pango|pango-libs + -> test-repo|master|pango|pango-locale + -> test-repo|master|pango|pango-misc + test-repo|master|pango|pango-misc + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + test-repo|master|pango|pango-locale + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + test-repo|master|pango|pango-libs + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + test-repo|master|pango|pango-bins + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + test-repo|master|gdk-pixbuf|gdk-pixbuf-misc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|gdk-pixbuf|gdk-pixbuf-locale + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|gdk-pixbuf|gdk-pixbuf-libs + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|gdk-pixbuf|gdk-pixbuf-bins + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|cairo|cairo-misc + test-repo|master|cairo|cairo-locale + test-repo|master|cairo|cairo-libs + test-repo|master|cairo|cairo-bins + test-repo|master|gdk-pixbuf|gdk-pixbuf-doc + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|gdk-pixbuf|gdk-pixbuf-devel + -> test-repo|master|glib|glib-bins + -> test-repo|master|glib|glib-devel + -> test-repo|master|glib|glib-doc + -> test-repo|master|glib|glib-libs + -> test-repo|master|glib|glib-locale + -> test-repo|master|glib|glib-misc + test-repo|master|glib|glib-misc + test-repo|master|glib|glib-locale + test-repo|master|glib|glib-libs + test-repo|master|glib|glib-bins + test-repo|master|glib|glib-doc + test-repo|master|glib|glib-devel + test-repo|master|pango|pango-doc + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + test-repo|master|pango|pango-devel + -> test-repo|master|fontconfig|fontconfig-bins + -> test-repo|master|fontconfig|fontconfig-devel + -> test-repo|master|fontconfig|fontconfig-doc + -> test-repo|master|fontconfig|fontconfig-libs + -> test-repo|master|fontconfig|fontconfig-locale + -> test-repo|master|fontconfig|fontconfig-misc + -> test-repo|master|freetype|freetype-bins + -> test-repo|master|freetype|freetype-devel + -> test-repo|master|freetype|freetype-doc + -> test-repo|master|freetype|freetype-libs + -> test-repo|master|freetype|freetype-locale + -> test-repo|master|freetype|freetype-misc + test-repo|master|fontconfig|fontconfig-misc + test-repo|master|fontconfig|fontconfig-locale + test-repo|master|fontconfig|fontconfig-libs + test-repo|master|fontconfig|fontconfig-bins + test-repo|master|freetype|freetype-misc + test-repo|master|freetype|freetype-locale + test-repo|master|freetype|freetype-libs + test-repo|master|freetype|freetype-bins + test-repo|master|cairo|cairo-doc + test-repo|master|cairo|cairo-devel + test-repo|master|fontconfig|fontconfig-doc + test-repo|master|fontconfig|fontconfig-devel + test-repo|master|freetype|freetype-doc + test-repo|master|freetype|freetype-devel -- cgit v1.2.1 From 9901b78e48fddeda2ed7f6dbf954abcde8fa9a0f Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Fri, 6 Dec 2013 15:45:55 +0000 Subject: yarns: Add tests for building systems with splitting This includes tests for systems with the default splits and a system that selects only one of the produced stratum artifacts to go into the system artifact, since this is roughly the expected use-case for the tiny system morphologies. --- yarns/implementations.yarn | 9 ++++ yarns/splitting.yarn | 129 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 yarns/splitting.yarn diff --git a/yarns/implementations.yarn b/yarns/implementations.yarn index 083035fe..5b9b39df 100644 --- a/yarns/implementations.yarn +++ b/yarns/implementations.yarn @@ -788,3 +788,12 @@ Implementations for building systems 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/splitting.yarn b/yarns/splitting.yarn new file mode 100644 index 00000000..e3324190 --- /dev/null +++ b/yarns/splitting.yarn @@ -0,0 +1,129 @@ +Artifact splitting tests +======================== + + SCENARIO building a system with morphologies that have splitting rules + GIVEN a workspace + AND a git server + + AND chunk test-chunk includes the default splitting rules + AND stratum test-stratum includes the default splitting rules + AND system test-system includes the default splitting rules + + WHEN the user checks out the system branch called master + THEN morph build the system test-system of the branch master + + + + SCENARIO building a system only using runtime strata + GIVEN a workspace + AND a git server + AND system test-system only uses test-stratum-runtime from test-stratum + WHEN the user checks out the system branch called master + GIVEN a cluster called test-cluster for deploying only the test-system system as type tar in system branch master + WHEN the user builds the system test-system in branch master + AND the user attempts to deploy the cluster test-cluster in branch master with options system.location="$DATADIR/test.tar" + THEN tarball test.tar contains bin/test + AND tarball test.tar contains lib/libtest.so + AND tarball test.tar doesn't contain lib/libtest.a + AND tarball test.tar doesn't contain man/man3/test.3.gz + +Implementations +--------------- + + IMPLEMENTS GIVEN chunk (\S+) includes the default splitting rules + # Append default products rules + cat <>"$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 <"$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" -- cgit v1.2.1