You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildstream.apache.org by no...@apache.org on 2020/12/29 12:52:57 UTC

[buildstream] branch richardmaw/artifact-subcommands created (now 986b1e6)

This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a change to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git.


      at 986b1e6  NEWS: Describe new subcommands

This branch includes the following new commits:

     new 3d326ca  caches: Allow flagging in context whether to require read-only access
     new adc8074  cli.py: Separate target and artifact ref completion
     new fe015b3  cli: Make artifact log guess a default element
     new c3fca9f  cli: Split classify_artifacts helper up
     new 43242b5  cli: Factor virtual directory loading out of artifact_log
     new 987d5fc  cli: Add artifact checkout subcommand
     new f169bcc  cli: Add artifact delete subcommand
     new 2ca28df  cli: Add artifact list subcommand
     new 94f64af  cli: Add artifact list-contents subcommand
     new 145722b  cli: Add artifact diff subcommand
     new af82039  tests: Use bst artifact checkout instead of bst checkout
     new b40d101  contrib/bst-docker-import: use bst artifact checkout
     new 986b1e6  NEWS: Describe new subcommands

The 13 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[buildstream] 13/13: NEWS: Describe new subcommands

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 986b1e630c781acbfe91715083df109455569e08
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Wed Dec 12 18:55:48 2018 +0000

    NEWS: Describe new subcommands
---
 NEWS | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/NEWS b/NEWS
index 7ee63f9..763d31d 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,23 @@
 buildstream 1.3.1
 =================
 
+  o Added `bst artifact {list,list-contents,delete,diff}` subcommands.
+
+    `bst artifact list` shows the artifacts in the cache.
+    `bst artifact list-contents` shows the files in artifacts in the cache.
+    `bst artifact delete` removes artifacts from the cache.
+    `bst artifact diff` summarises the difference between artifacts.
+
+  o Added `bst artifact checkout` subcommand.
+
+    This implements the features of `bst checkout`,
+    though it takes `--directory` and `--tar` options
+    instead of using a location argument,
+    since it may be passed multiple element targets or artifact refs.
+
+    `--deps`, `--tar` and `--integrate` are not supported
+    when not passed a single element target.
+
   o Added `bst artifact log` subcommand for viewing build logs.
 
   o BREAKING CHANGE: The bst source-bundle command has been removed. The


[buildstream] 05/13: cli: Factor virtual directory loading out of artifact_log

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 43242b56fffc2789da269caac782adedf5b1cb5e
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Wed Dec 12 18:27:57 2018 +0000

    cli: Factor virtual directory loading out of artifact_log
---
 buildstream/_frontend/cli.py | 55 +++++++++++++++++++++++++-------------------
 1 file changed, 31 insertions(+), 24 deletions(-)

diff --git a/buildstream/_frontend/cli.py b/buildstream/_frontend/cli.py
index 4b3fcba..ee4a912 100644
--- a/buildstream/_frontend/cli.py
+++ b/buildstream/_frontend/cli.py
@@ -7,6 +7,7 @@ from tempfile import TemporaryDirectory
 import click
 from .. import _yaml
 from .._exceptions import BstError, LoadError, AppError
+from ..storage._casbaseddirectory import CasBasedDirectory
 from .._versions import BST_FORMAT_VERSION
 from .complete import main_bashcomplete, complete_path, CompleteUnhandled
 
@@ -1004,6 +1005,35 @@ def _classify_artifacts(names, cas, project_directory):
     return targets, refs
 
 
+def _load_vdirs(app, elements, artifacts):
+    from .._exceptions import CASError
+    from .._message import MessageType
+    from .._pipeline import PipelineSelection
+    cache = app.context.artifactcache
+    vdirs = []
+
+    for ref in artifacts:
+        try:
+            cache_id = cache.cas.resolve_ref(ref, update_mtime=True)
+            vdir = CasBasedDirectory(cache.cas, cache_id)
+            vdirs.append(vdir)
+        except CASError as e:
+            app._message(MessageType.WARN, "Artifact {} is not cached".format(ref), detail=str(e))
+            continue
+
+    if elements:
+        elements = app.stream.load_selection(elements, selection=PipelineSelection.NONE)
+        for element in elements:
+            if not element._cached():
+                app._message(MessageType.WARN, "Element {} is not cached".format(element))
+                continue
+            ref = cache.get_artifact_fullname(element, element._get_cache_key())
+            cache_id = cache.cas.resolve_ref(ref, update_mtime=True)
+            vdir = CasBasedDirectory(cache.cas, cache_id)
+            vdirs.append(vdir)
+
+    return vdirs
+
 
 @cli.group(short_help="Manipulate cached artifacts")
 def artifact():
@@ -1018,10 +1048,6 @@ def artifact():
 @click.pass_obj
 def artifact_log(app, artifacts):
     """Show logs of all artifacts"""
-    from .._exceptions import CASError
-    from .._message import MessageType
-    from .._pipeline import PipelineSelection
-    from ..storage._casbaseddirectory import CasBasedDirectory
 
     with ExitStack() as stack:
         stack.enter_context(app.initialized())
@@ -1035,27 +1061,8 @@ def artifact_log(app, artifacts):
             if element is not None:
                 elements = [element]
 
-        vdirs = []
+        vdirs = _load_vdirs(app, elements, artifacts)
         extractdirs = []
-        if artifacts:
-            for ref in artifacts:
-                try:
-                    cache_id = cache.cas.resolve_ref(ref, update_mtime=True)
-                    vdir = CasBasedDirectory(cache.cas, cache_id)
-                    vdirs.append(vdir)
-                except CASError as e:
-                    app._message(MessageType.WARN, "Artifact {} is not cached".format(ref), detail=str(e))
-                    continue
-        if elements:
-            elements = app.stream.load_selection(elements, selection=PipelineSelection.NONE)
-            for element in elements:
-                if not element._cached():
-                    app._message(MessageType.WARN, "Element {} is not cached".format(element))
-                    continue
-                ref = cache.get_artifact_fullname(element, element._get_cache_key())
-                cache_id = cache.cas.resolve_ref(ref, update_mtime=True)
-                vdir = CasBasedDirectory(cache.cas, cache_id)
-                vdirs.append(vdir)
 
         for vdir in vdirs:
             # NOTE: If reading the logs feels unresponsive, here would be a good place to provide progress information.


[buildstream] 03/13: cli: Make artifact log guess a default element

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit fe015b38f08ce582e2a38705b2536d0873574ee6
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Wed Dec 12 18:20:31 2018 +0000

    cli: Make artifact log guess a default element
---
 buildstream/_frontend/cli.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/buildstream/_frontend/cli.py b/buildstream/_frontend/cli.py
index 222be1c..49da15c 100644
--- a/buildstream/_frontend/cli.py
+++ b/buildstream/_frontend/cli.py
@@ -1018,6 +1018,11 @@ def artifact_log(app, artifacts):
         elements, artifacts = _classify_artifacts(artifacts, cache.cas,
                                                   app.project.directory)
 
+        if not elements and not artifacts:
+            element = app.context.guess_element()
+            if element is not None:
+                elements = [element]
+
         vdirs = []
         extractdirs = []
         if artifacts:


[buildstream] 06/13: cli: Add artifact checkout subcommand

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 987d5fc2a831c525f46e53ea0fd5639eadfd1f53
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Wed Dec 12 18:32:39 2018 +0000

    cli: Add artifact checkout subcommand
    
    This implements a superset of the features of `bst checkout`,
    but uses a --directory option rather than a location argument,
    and --tar takes a location instead of being a flag.
    
    If multiple element targets or any artifact refs are passed
    then it does not support creating a tarball, running integration commands
    or including dependencies of the targets.
---
 buildstream/_frontend/cli.py | 81 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/buildstream/_frontend/cli.py b/buildstream/_frontend/cli.py
index ee4a912..8ac2078 100644
--- a/buildstream/_frontend/cli.py
+++ b/buildstream/_frontend/cli.py
@@ -1079,6 +1079,87 @@ def artifact_log(app, artifacts):
                     click.echo_via_pager(data)
 
 
+#####################################################################
+#                     Artifact Checkout Command                     #
+#####################################################################
+@artifact.command(name='checkout', short_help="Checkout contents of an artifact")
+@click.option('--force', '-f', default=False, is_flag=True,
+              help="Allow files to be overwritten")
+@click.option('--deps', '-d', default=None,
+              type=click.Choice(['run', 'build', 'none']),
+              help='The dependencies to checkout (default: run)')
+@click.option('--integrate/--no-integrate', default=None, is_flag=True,
+              help="Whether to run integration commands")
+@click.option('--hardlinks', default=False, is_flag=True,
+              help="Checkout hardlinks instead of copying if possible")
+@click.option('--tar', default=None, metavar='LOCATION',
+              type=click.Path(),
+              help="Create a tarball from the artifact contents instead "
+                   "of a file tree. If LOCATION is '-', the tarball "
+                   "will be dumped to the standard output.")
+@click.option('--directory', default=None,
+              type=click.Path(file_okay=False),
+              help="The directory to write the tarball to")
+@click.argument('artifacts', type=click.Path(), nargs=-1)
+@click.pass_obj
+def artifact_checkout(app, force, deps, integrate, hardlinks, tar, directory, artifacts):
+    """Checkout contents of an artifact"""
+    from ..element import Scope
+
+    if hardlinks and tar is not None:
+        click.echo("ERROR: options --hardlinks and --tar conflict", err=True)
+        sys.exit(-1)
+
+    if tar is None and directory is None:
+        click.echo("ERROR: One of --directory or --tar must be provided", err=True)
+        sys.exit(-1)
+
+    if tar is not None and directory is not None:
+        click.echo("ERROR: options --directory and --tar conflict", err=True)
+        sys.exit(-1)
+
+    if tar is not None:
+        location = tar
+        tar = True
+    else:
+        location = os.getcwd() if directory is None else directory
+        tar = False
+
+    with app.initialized():
+        cache = app.context.artifactcache
+
+        elements, artifacts = _classify_artifacts(artifacts, cache.cas,
+                                                  app.project.directory)
+
+        if not elements and not artifacts:
+            element = app.context.guess_element()
+            if element is not None:
+                elements = [element]
+
+        if (artifacts or len(elements) > 1) and (integrate is not None or deps is not None or tar):
+            raise AppError("--integrate, --deps and --tar may not be used with artifact refs or multiple elements")
+
+        if elements and not artifacts and len(elements) == 1:
+            if deps == "build":
+                scope = Scope.BUILD
+            elif deps == "none":
+                scope = Scope.NONE
+            else:
+                scope = Scope.RUN
+            app.stream.checkout(elements[0],
+                                location=location,
+                                force=force,
+                                scope=scope,
+                                integrate=True if integrate is None else integrate,
+                                hardlinks=hardlinks,
+                                tar=tar)
+            return
+
+        for vdir in _load_vdirs(app, elements, artifacts):
+            vdir = vdir.descend(["files"])
+            vdir.export_files(directory, can_link=hardlinks, can_destroy=force)
+
+
 ##################################################################
 #                      DEPRECATED Commands                       #
 ##################################################################


[buildstream] 02/13: cli.py: Separate target and artifact ref completion

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit adc8074638130436a193c7e76ec47e74b277bd78
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Wed Dec 12 18:11:36 2018 +0000

    cli.py: Separate target and artifact ref completion
    
    It doesn't make sense for operations such as listing the contents of the cache
    to list elements in the working tree,
    so generically completing artifacts as both artifact refs and element targets
    is unhelpful.
---
 buildstream/_frontend/cli.py | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/buildstream/_frontend/cli.py b/buildstream/_frontend/cli.py
index 29fc4cf..222be1c 100644
--- a/buildstream/_frontend/cli.py
+++ b/buildstream/_frontend/cli.py
@@ -113,7 +113,7 @@ def complete_target(args, incomplete):
 
 def complete_artifact(args, incomplete):
     from .._context import Context
-    ctx = Context()
+    ctx = Context(read_only=True)
 
     config = None
     for i, arg in enumerate(args):
@@ -121,11 +121,7 @@ def complete_artifact(args, incomplete):
             config = args[i + 1]
     ctx.load(config)
 
-    # element targets are valid artifact names
-    complete_list = complete_target(args, incomplete)
-    complete_list.extend(ref for ref in ctx.artifactcache.cas.list_refs() if ref.startswith(incomplete))
-
-    return complete_list
+    return [ref for ref in ctx.artifactcache.cas.list_refs() if ref.startswith(incomplete)]
 
 
 def override_completions(cmd, cmd_param, args, incomplete):
@@ -150,7 +146,9 @@ def override_completions(cmd, cmd_param, args, incomplete):
                 cmd_param.opts == ['--track-except']):
             return complete_target(args, incomplete)
         if cmd_param.name == 'artifacts':
-            return complete_artifact(args, incomplete)
+            complete_list = complete_target(args, incomplete)
+            complete_list.extend(complete_artifact(args, incomplete))
+            return complete_list
 
     raise CompleteUnhandled()
 


[buildstream] 12/13: contrib/bst-docker-import: use bst artifact checkout

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit b40d1011a04a7ee2f88112e3d2f908d8bedff08f
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Tue Dec 11 16:59:04 2018 +0000

    contrib/bst-docker-import: use bst artifact checkout
---
 contrib/bst-docker-import | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/contrib/bst-docker-import b/contrib/bst-docker-import
index dfb16d7..c15eaf1 100755
--- a/contrib/bst-docker-import
+++ b/contrib/bst-docker-import
@@ -90,7 +90,7 @@ element="$1"
 checkout_tar="bst-checkout-$(basename "$element")-$RANDOM.tar"
 
 echo "INFO: Checking out $element ..." >&2
-$bst_cmd checkout --tar "$element" "$checkout_tar" || die "Failed to checkout $element"
+$bst_cmd artifact checkout "$element" --tar "$checkout_tar" || die "Failed to checkout $element"
 echo "INFO: Successfully checked out $element" >&2
 
 echo "INFO: Importing Docker image ..." >&2


[buildstream] 11/13: tests: Use bst artifact checkout instead of bst checkout

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit af82039df76bc9c735c4dac9a9d15cba01b1a127
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Wed Dec 5 18:44:59 2018 +0000

    tests: Use bst artifact checkout instead of bst checkout
---
 tests/artifactcache/expiry.py           |  5 +++--
 tests/completions/completions.py        | 12 ++++++-----
 tests/examples/autotools.py             |  2 +-
 tests/examples/developing.py            |  2 +-
 tests/examples/first-project.py         |  2 +-
 tests/examples/flatpak-autotools.py     |  2 +-
 tests/frontend/buildcheckout.py         | 38 +++++++++++++++++----------------
 tests/frontend/compose_splits.py        |  2 +-
 tests/frontend/mirror.py                |  4 ++--
 tests/frontend/workspace.py             |  6 +++---
 tests/integration/autotools.py          |  4 ++--
 tests/integration/cachedfail.py         |  2 +-
 tests/integration/cmake.py              |  4 ++--
 tests/integration/compose-symlinks.py   |  3 ++-
 tests/integration/compose.py            |  2 +-
 tests/integration/import.py             |  2 +-
 tests/integration/make.py               |  2 +-
 tests/integration/manual.py             |  6 +++---
 tests/integration/pip_element.py        |  2 +-
 tests/integration/pip_source.py         |  2 +-
 tests/integration/script.py             | 24 ++++++++++-----------
 tests/integration/shell.py              |  2 +-
 tests/integration/source-determinism.py |  4 ++--
 tests/integration/stack.py              |  2 +-
 tests/integration/symlinks.py           |  6 +++---
 tests/loader/junctions.py               | 16 +++++++-------
 tests/plugins/filter.py                 | 15 +++++++------
 tests/sources/bzr.py                    |  2 +-
 tests/sources/deb.py                    |  6 +++---
 tests/sources/git.py                    | 14 ++++++------
 tests/sources/local.py                  |  6 +++---
 tests/sources/patch.py                  |  8 +++----
 tests/sources/previous_source_access.py |  2 +-
 tests/sources/remote.py                 |  8 +++----
 tests/sources/tar.py                    | 12 +++++------
 tests/sources/zip.py                    |  8 +++----
 36 files changed, 123 insertions(+), 116 deletions(-)

diff --git a/tests/artifactcache/expiry.py b/tests/artifactcache/expiry.py
index 05cbe32..a8b05c3 100644
--- a/tests/artifactcache/expiry.py
+++ b/tests/artifactcache/expiry.py
@@ -132,7 +132,7 @@ def test_expiry_order(cli, datafiles, tmpdir):
     wait_for_cache_granularity()
 
     # Now extract dep.bst
-    res = cli.run(project=project, args=['checkout', 'dep.bst', checkout])
+    res = cli.run(project=project, args=['artifact', 'checkout', 'dep.bst', '--directory', checkout])
     res.assert_success()
 
     # Finally, build something that will cause the cache to overflow
@@ -379,7 +379,8 @@ def test_extract_expiry(cli, datafiles, tmpdir):
     assert cli.get_element_state(project, 'target.bst') == 'cached'
 
     # Force creating extract
-    res = cli.run(project=project, args=['checkout', 'target.bst', os.path.join(str(tmpdir), 'checkout')])
+    res = cli.run(project=project, args=['artifact', 'checkout', 'target.bst',
+                                         '--directory', os.path.join(str(tmpdir), 'checkout')])
     res.assert_success()
 
     extractdir = os.path.join(project, 'cache', 'artifacts', 'extract', 'test', 'target')
diff --git a/tests/completions/completions.py b/tests/completions/completions.py
index 372ed78..c37d59c 100644
--- a/tests/completions/completions.py
+++ b/tests/completions/completions.py
@@ -79,7 +79,7 @@ MIXED_ELEMENTS = PROJECT_ELEMENTS + INVALID_ELEMENTS
 
 
 def assert_completion(cli, cmd, word_idx, expected, cwd=None):
-    result = cli.run(cwd=cwd, env={
+    result = cli.run(project='.', cwd=cwd, env={
         '_BST_COMPLETION': 'complete',
         'COMP_WORDS': cmd,
         'COMP_CWORD': str(word_idx)
@@ -218,8 +218,9 @@ def test_option_directory(datafiles, cli, cmd, word_idx, expected, subdir):
      ['compose-all.bst ', 'compose-include-bin.bst ', 'compose-exclude-dev.bst '], 'files'),
 
     # Also try multi arguments together
-    ('project', 'bst --directory ../ checkout t ', 4, ['target.bst '], 'files'),
-    ('project', 'bst --directory ../ checkout target.bst ', 5, ['bin-files/', 'dev-files/'], 'files'),
+    ('project', 'bst --directory ../ artifact checkout t ', 5, ['target.bst '], 'files'),
+    ('project', 'bst --directory ../ artifact checkout --directory ', 6,
+     ['bin-files/', 'dev-files/'], 'files'),
 
     # When running in the project directory
     ('no-element-path', 'bst show ', 2,
@@ -242,8 +243,9 @@ def test_option_directory(datafiles, cli, cmd, word_idx, expected, subdir):
      ['compose-all.bst ', 'compose-include-bin.bst ', 'compose-exclude-dev.bst '], 'files'),
 
     # Also try multi arguments together
-    ('no-element-path', 'bst --directory ../ checkout t ', 4, ['target.bst '], 'files'),
-    ('no-element-path', 'bst --directory ../ checkout target.bst ', 5, ['bin-files/', 'dev-files/'], 'files'),
+    ('no-element-path', 'bst --directory ../ artifact checkout t ', 5, ['target.bst '], 'files'),
+    ('no-element-path', 'bst --directory ../ artifact checkout --directory ', 6,
+     ['bin-files/', 'dev-files/'], 'files'),
 
     # When element-path have sub-folders
     ('sub-folders', 'bst show base', 2, ['base/wanted.bst '], None),
diff --git a/tests/examples/autotools.py b/tests/examples/autotools.py
index af440cc..30f5076 100644
--- a/tests/examples/autotools.py
+++ b/tests/examples/autotools.py
@@ -25,7 +25,7 @@ def test_autotools_build(cli, tmpdir, datafiles):
     result = cli.run(project=project, args=['build', 'hello.bst'])
     result.assert_success()
 
-    result = cli.run(project=project, args=['checkout', 'hello.bst', checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'hello.bst', '--directory', checkout])
     result.assert_success()
 
     assert_contains(checkout, ['/usr', '/usr/lib', '/usr/bin',
diff --git a/tests/examples/developing.py b/tests/examples/developing.py
index 3b09962..166fcf3 100644
--- a/tests/examples/developing.py
+++ b/tests/examples/developing.py
@@ -26,7 +26,7 @@ def test_autotools_build(cli, tmpdir, datafiles):
     result = cli.run(project=project, args=['build', 'hello.bst'])
     result.assert_success()
 
-    result = cli.run(project=project, args=['checkout', 'hello.bst', checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'hello.bst', '--directory', checkout])
     result.assert_success()
 
     assert_contains(checkout, ['/usr', '/usr/lib', '/usr/bin',
diff --git a/tests/examples/first-project.py b/tests/examples/first-project.py
index dac1814..821d2c1 100644
--- a/tests/examples/first-project.py
+++ b/tests/examples/first-project.py
@@ -23,7 +23,7 @@ def test_first_project_build_checkout(cli, tmpdir, datafiles):
     result = cli.run(project=project, args=['build', 'hello.bst'])
     assert result.exit_code == 0
 
-    result = cli.run(project=project, args=['checkout', 'hello.bst', checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'hello.bst', '--directory', checkout])
     assert result.exit_code == 0
 
     assert_contains(checkout, ['/hello.world'])
diff --git a/tests/examples/flatpak-autotools.py b/tests/examples/flatpak-autotools.py
index d63771e..4153a95 100644
--- a/tests/examples/flatpak-autotools.py
+++ b/tests/examples/flatpak-autotools.py
@@ -44,7 +44,7 @@ def test_autotools_build(cli, tmpdir, datafiles):
     result = cli.run(project=project, args=['build', 'hello.bst'])
     assert result.exit_code == 0
 
-    result = cli.run(project=project, args=['checkout', 'hello.bst', checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'hello.bst', '--directory', checkout])
     assert result.exit_code == 0
 
     assert_contains(checkout, ['/usr', '/usr/lib', '/usr/bin',
diff --git a/tests/frontend/buildcheckout.py b/tests/frontend/buildcheckout.py
index dc7ce68..8393c26 100644
--- a/tests/frontend/buildcheckout.py
+++ b/tests/frontend/buildcheckout.py
@@ -43,10 +43,10 @@ def test_build_checkout(datafiles, cli, strict, hardlinks):
     assert not os.listdir(builddir)
 
     # Prepare checkout args
-    checkout_args = strict_args(['checkout'], strict)
+    checkout_args = strict_args(['artifact', 'checkout'], strict)
     if hardlinks == "hardlinks":
         checkout_args += ['--hardlinks']
-    checkout_args += ['target.bst', checkout]
+    checkout_args += ['target.bst', '--directory', checkout]
 
     # Now check it out
     result = cli.run(project=project, args=checkout_args)
@@ -116,7 +116,8 @@ def test_build_checkout_deps(datafiles, cli, deps):
     assert not os.listdir(builddir)
 
     # Now check it out
-    result = cli.run(project=project, args=['checkout', element_name, '--deps', deps, checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', element_name,
+                                            '--deps', deps, '--directory', checkout])
     result.assert_success()
 
     # Verify output of this element
@@ -147,7 +148,7 @@ def test_build_checkout_unbuilt(datafiles, cli):
     checkout = os.path.join(cli.directory, 'checkout')
 
     # Check that checking out an unbuilt element fails nicely
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkout])
     result.assert_main_error(ErrorDomain.STREAM, "uncached-checkout-attempt")
 
 
@@ -163,7 +164,7 @@ def test_build_checkout_tarball(datafiles, cli):
     assert os.path.isdir(builddir)
     assert not os.listdir(builddir)
 
-    checkout_args = ['checkout', '--tar', 'target.bst', checkout]
+    checkout_args = ['artifact', 'checkout', '--tar', checkout, 'target.bst']
 
     result = cli.run(project=project, args=checkout_args)
     result.assert_success()
@@ -185,7 +186,7 @@ def test_build_checkout_tarball_stdout(datafiles, cli):
     assert os.path.isdir(builddir)
     assert not os.listdir(builddir)
 
-    checkout_args = ['checkout', '--tar', 'target.bst', '-']
+    checkout_args = ['artifact', 'checkout', '--tar', '-', 'target.bst']
 
     result = cli.run(project=project, args=checkout_args, binary_capture=True)
     result.assert_success()
@@ -211,13 +212,13 @@ def test_build_checkout_tarball_is_deterministic(datafiles, cli):
     assert os.path.isdir(builddir)
     assert not os.listdir(builddir)
 
-    checkout_args = ['checkout', '--force', '--tar', 'target.bst']
+    checkout_args = ['artifact', 'checkout', '--force', 'target.bst']
 
-    checkout_args1 = checkout_args + [tarball1]
+    checkout_args1 = checkout_args + ['--tar', tarball1]
     result = cli.run(project=project, args=checkout_args1)
     result.assert_success()
 
-    checkout_args2 = checkout_args + [tarball2]
+    checkout_args2 = checkout_args + ['--tar', tarball2]
     result = cli.run(project=project, args=checkout_args2)
     result.assert_success()
 
@@ -254,10 +255,10 @@ def test_build_checkout_nonempty(datafiles, cli, hardlinks):
         f.write("Hello")
 
     # Prepare checkout args
-    checkout_args = ['checkout']
+    checkout_args = ['artifact', 'checkout']
     if hardlinks == "hardlinks":
         checkout_args += ['--hardlinks']
-    checkout_args += ['target.bst', checkout]
+    checkout_args += ['target.bst', '--directory', checkout]
 
     # Now check it out
     result = cli.run(project=project, args=checkout_args)
@@ -286,10 +287,10 @@ def test_build_checkout_force(datafiles, cli, hardlinks):
         f.write("Hello")
 
     # Prepare checkout args
-    checkout_args = ['checkout', '--force']
+    checkout_args = ['artifact', 'checkout', '--force']
     if hardlinks == "hardlinks":
         checkout_args += ['--hardlinks']
-    checkout_args += ['target.bst', checkout]
+    checkout_args += ['target.bst', '--directory', checkout]
 
     # Now check it out
     result = cli.run(project=project, args=checkout_args)
@@ -323,7 +324,7 @@ def test_build_checkout_force_tarball(datafiles, cli):
     with open(tarball, "w") as f:
         f.write("Hello")
 
-    checkout_args = ['checkout', '--force', '--tar', 'target.bst', tarball]
+    checkout_args = ['artifact', 'checkout', '--force', '--tar', tarball, 'target.bst']
 
     result = cli.run(project=project, args=checkout_args)
     result.assert_success()
@@ -371,7 +372,7 @@ def test_fetch_build_checkout(cli, tmpdir, datafiles, strict, kind):
 
     # Now check it out
     result = cli.run(project=project, args=strict_args([
-        'checkout', element_name, checkout
+        'artifact', 'checkout', element_name, '--directory', checkout
     ], strict))
     result.assert_success()
 
@@ -515,7 +516,7 @@ def test_build_checkout_junction(cli, tmpdir, datafiles):
 
     # Now check it out
     result = cli.run(project=project, args=[
-        'checkout', 'junction-dep.bst', checkout
+        'artifact', 'checkout', 'junction-dep.bst', '--directory', checkout
     ])
     result.assert_success()
 
@@ -578,7 +579,7 @@ def test_build_checkout_workspaced_junction(cli, tmpdir, datafiles):
 
     # Now check it out
     result = cli.run(project=project, args=[
-        'checkout', 'junction-dep.bst', checkout
+        'artifact', 'checkout', 'junction-dep.bst', '--directory', checkout
     ])
     result.assert_success()
 
@@ -602,7 +603,8 @@ def test_build_checkout_cross_junction(datafiles, cli, tmpdir):
     result = cli.run(project=project, args=['build', 'junction.bst:import-etc.bst'])
     result.assert_success()
 
-    result = cli.run(project=project, args=['checkout', 'junction.bst:import-etc.bst', checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'junction.bst:import-etc.bst',
+                                            '--directory', checkout])
     result.assert_success()
 
     filename = os.path.join(checkout, 'etc', 'animal.conf')
diff --git a/tests/frontend/compose_splits.py b/tests/frontend/compose_splits.py
index b5a2e76..97558b6 100644
--- a/tests/frontend/compose_splits.py
+++ b/tests/frontend/compose_splits.py
@@ -24,7 +24,7 @@ def test_compose_splits(datafiles, cli, target):
 
     # Now check it out
     result = cli.run(project=project, args=[
-        'checkout', target, checkout
+        'artifact', 'checkout', target, '--directory', checkout
     ])
     result.assert_success()
 
diff --git a/tests/frontend/mirror.py b/tests/frontend/mirror.py
index ccfe2ca..3cadd15 100644
--- a/tests/frontend/mirror.py
+++ b/tests/frontend/mirror.py
@@ -856,7 +856,7 @@ def test_mirror_fallback_git_only_submodules(cli, tmpdir, datafiles):
     result.assert_success()
 
     checkout = os.path.join(str(tmpdir), 'checkout')
-    result = cli.run(project=project_dir, args=['checkout', element_name, checkout])
+    result = cli.run(project=project_dir, args=['artifact', 'checkout', element_name, '--directory', checkout])
     result.assert_success()
 
     assert os.path.exists(os.path.join(checkout, 'bin', 'bin', 'hello'))
@@ -952,7 +952,7 @@ def test_mirror_fallback_git_with_submodules(cli, tmpdir, datafiles):
     result.assert_success()
 
     checkout = os.path.join(str(tmpdir), 'checkout')
-    result = cli.run(project=project_dir, args=['checkout', element_name, checkout])
+    result = cli.run(project=project_dir, args=['artifact', 'checkout', element_name, '--directory', checkout])
     result.assert_success()
 
     assert os.path.exists(os.path.join(checkout, 'bin', 'bin', 'hello'))
diff --git a/tests/frontend/workspace.py b/tests/frontend/workspace.py
index 00c0bd8..7c5f37d 100644
--- a/tests/frontend/workspace.py
+++ b/tests/frontend/workspace.py
@@ -655,7 +655,7 @@ def test_build(cli, tmpdir_factory, datafiles, kind, strict, from_workspace, gue
 
     # Checkout the result
     result = cli.run(project=project,
-                     args=args_dir + ['checkout'] + args_elm + [checkout])
+                     args=args_dir + ['artifact', 'checkout', '--directory', checkout] + args_elm)
     result.assert_success()
 
     # Check that the pony.conf from the modified workspace exists
@@ -757,7 +757,7 @@ def test_detect_modifications(cli, tmpdir, datafiles, modification, strict):
 
     # Checkout the result
     result = cli.run(project=project, args=[
-        'checkout', element_name, checkout
+        'artifact', 'checkout', element_name, '--directory', checkout
     ])
     result.assert_success()
 
@@ -1033,7 +1033,7 @@ def test_cache_key_workspace_in_dependencies(cli, tmpdir, datafiles, strict):
 
     # Checkout the result
     result = cli.run(project=project, args=[
-        'checkout', back_dep_element_name, checkout
+        'artifact', 'checkout', back_dep_element_name, '--directory', checkout
     ])
     result.assert_success()
 
diff --git a/tests/integration/autotools.py b/tests/integration/autotools.py
index 1dc7eea..c7070ad 100644
--- a/tests/integration/autotools.py
+++ b/tests/integration/autotools.py
@@ -28,7 +28,7 @@ def test_autotools_build(cli, tmpdir, datafiles):
     result = cli.run(project=project, args=['build', element_name])
     assert result.exit_code == 0
 
-    result = cli.run(project=project, args=['checkout', element_name, checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert result.exit_code == 0
 
     assert_contains(checkout, ['/usr', '/usr/lib', '/usr/bin',
@@ -51,7 +51,7 @@ def test_autotools_confroot_build(cli, tmpdir, datafiles):
     result = cli.run(project=project, args=['build', element_name])
     assert result.exit_code == 0
 
-    result = cli.run(project=project, args=['checkout', element_name, checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert result.exit_code == 0
 
     assert_contains(checkout, ['/usr', '/usr/lib', '/usr/bin',
diff --git a/tests/integration/cachedfail.py b/tests/integration/cachedfail.py
index d902bac..2c79e6a 100644
--- a/tests/integration/cachedfail.py
+++ b/tests/integration/cachedfail.py
@@ -54,7 +54,7 @@ def test_build_checkout_cached_fail(cli, tmpdir, datafiles):
 
     # Now check it out
     result = cli.run(project=project, args=[
-        'checkout', 'element.bst', checkout
+        'artifact', 'checkout', 'element.bst', '--directory', checkout
     ])
     result.assert_success()
 
diff --git a/tests/integration/cmake.py b/tests/integration/cmake.py
index 235dee3..e82ec65 100644
--- a/tests/integration/cmake.py
+++ b/tests/integration/cmake.py
@@ -25,7 +25,7 @@ def test_cmake_build(cli, tmpdir, datafiles):
     result = cli.run(project=project, args=['build', element_name])
     assert result.exit_code == 0
 
-    result = cli.run(project=project, args=['checkout', element_name, checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert result.exit_code == 0
 
     assert_contains(checkout, ['/usr', '/usr/bin', '/usr/bin/hello'])
@@ -41,7 +41,7 @@ def test_cmake_confroot_build(cli, tmpdir, datafiles):
     result = cli.run(project=project, args=['build', element_name])
     assert result.exit_code == 0
 
-    result = cli.run(project=project, args=['checkout', element_name, checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert result.exit_code == 0
 
     assert_contains(checkout, ['/usr', '/usr/bin', '/usr/bin/hello'])
diff --git a/tests/integration/compose-symlinks.py b/tests/integration/compose-symlinks.py
index bf279fa..2599d8b 100644
--- a/tests/integration/compose-symlinks.py
+++ b/tests/integration/compose-symlinks.py
@@ -36,7 +36,8 @@ def test_compose_symlinks(cli, tmpdir, datafiles):
     result = cli.run(project=project, args=['build', 'compose-symlinks/compose.bst'])
     result.assert_success()
 
-    result = cli.run(project=project, args=['checkout', 'compose-symlinks/compose.bst', checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'compose-symlinks/compose.bst',
+                                            '--directory', checkout])
     result.assert_success()
 
     assert set(walk_dir(checkout)) == set(['/sbin', '/usr', '/usr/sbin',
diff --git a/tests/integration/compose.py b/tests/integration/compose.py
index 36e1da7..6c18ea9 100644
--- a/tests/integration/compose.py
+++ b/tests/integration/compose.py
@@ -97,7 +97,7 @@ def test_compose_include(cli, tmpdir, datafiles, include_domains,
     result = cli.run(project=project, args=['build', element_name])
     assert result.exit_code == 0
 
-    result = cli.run(project=project, args=['checkout', element_name, checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert result.exit_code == 0
 
     assert set(walk_dir(checkout)) == set(expected)
diff --git a/tests/integration/import.py b/tests/integration/import.py
index 6925a0a..8426405 100644
--- a/tests/integration/import.py
+++ b/tests/integration/import.py
@@ -53,7 +53,7 @@ def test_import(cli, tmpdir, datafiles, source, target, path, expected):
     res = cli.run(project=project, args=['build', element_name])
     assert res.exit_code == 0
 
-    cli.run(project=project, args=['checkout', element_name, checkout])
+    cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert res.exit_code == 0
 
     assert set(walk_dir(checkout)) == set(expected)
diff --git a/tests/integration/make.py b/tests/integration/make.py
index 3b8d1e4..806b874 100644
--- a/tests/integration/make.py
+++ b/tests/integration/make.py
@@ -28,7 +28,7 @@ def test_make_build(cli, tmpdir, datafiles):
     result = cli.run(project=project, args=['build', element_name])
     assert result.exit_code == 0
 
-    result = cli.run(project=project, args=['checkout', element_name, checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert result.exit_code == 0
 
     assert_contains(checkout, ['/usr', '/usr/bin',
diff --git a/tests/integration/manual.py b/tests/integration/manual.py
index c6a905d..2a3dc3b 100644
--- a/tests/integration/manual.py
+++ b/tests/integration/manual.py
@@ -52,7 +52,7 @@ def test_manual_element(cli, tmpdir, datafiles):
     res = cli.run(project=project, args=['build', element_name])
     assert res.exit_code == 0
 
-    cli.run(project=project, args=['checkout', element_name, checkout])
+    cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert res.exit_code == 0
 
     with open(os.path.join(checkout, 'test')) as f:
@@ -86,7 +86,7 @@ def test_manual_element_environment(cli, tmpdir, datafiles):
     res = cli.run(project=project, args=['build', element_name])
     assert res.exit_code == 0
 
-    cli.run(project=project, args=['checkout', element_name, checkout])
+    cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert res.exit_code == 0
 
     with open(os.path.join(checkout, 'test')) as f:
@@ -119,7 +119,7 @@ def test_manual_element_noparallel(cli, tmpdir, datafiles):
     res = cli.run(project=project, args=['build', element_name])
     assert res.exit_code == 0
 
-    cli.run(project=project, args=['checkout', element_name, checkout])
+    cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert res.exit_code == 0
 
     with open(os.path.join(checkout, 'test')) as f:
diff --git a/tests/integration/pip_element.py b/tests/integration/pip_element.py
index 13ada09..523bc20 100644
--- a/tests/integration/pip_element.py
+++ b/tests/integration/pip_element.py
@@ -47,7 +47,7 @@ def test_pip_build(cli, tmpdir, datafiles):
     result = cli.run(project=project, args=['build', element_name])
     assert result.exit_code == 0
 
-    result = cli.run(project=project, args=['checkout', element_name, checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert result.exit_code == 0
 
     assert_contains(checkout, ['/usr', '/usr/lib', '/usr/bin',
diff --git a/tests/integration/pip_source.py b/tests/integration/pip_source.py
index d6cbb98..3948fc4 100644
--- a/tests/integration/pip_source.py
+++ b/tests/integration/pip_source.py
@@ -58,7 +58,7 @@ def test_pip_source_import(cli, tmpdir, datafiles, setup_pypi_repo):
     result = cli.run(project=project, args=['build', element_name])
     assert result.exit_code == 0
 
-    result = cli.run(project=project, args=['checkout', element_name, checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert result.exit_code == 0
 
     assert_contains(checkout, ['/.bst_pip_downloads',
diff --git a/tests/integration/script.py b/tests/integration/script.py
index 6203c32..ff1b8aa 100644
--- a/tests/integration/script.py
+++ b/tests/integration/script.py
@@ -49,7 +49,7 @@ def test_script(cli, tmpdir, datafiles):
     res = cli.run(project=project, args=['build', element_name])
     assert res.exit_code == 0
 
-    res = cli.run(project=project, args=['checkout', element_name, checkout])
+    res = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert res.exit_code == 0
 
     with open(os.path.join(checkout, 'test')) as f:
@@ -81,7 +81,7 @@ def test_script_root(cli, tmpdir, datafiles):
     res = cli.run(project=project, args=['build', element_name])
     assert res.exit_code == 0
 
-    res = cli.run(project=project, args=['checkout', element_name, checkout])
+    res = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert res.exit_code == 0
 
     with open(os.path.join(checkout, 'test')) as f:
@@ -135,7 +135,7 @@ def test_script_cwd(cli, tmpdir, datafiles):
     res = cli.run(project=project, args=['build', element_name])
     assert res.exit_code == 0
 
-    res = cli.run(project=project, args=['checkout', element_name, checkout])
+    res = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert res.exit_code == 0
 
     with open(os.path.join(checkout, 'test')) as f:
@@ -154,7 +154,7 @@ def test_script_layout(cli, tmpdir, datafiles):
     res = cli.run(project=project, args=['build', element_name])
     assert res.exit_code == 0
 
-    cli.run(project=project, args=['checkout', element_name, checkout])
+    cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert res.exit_code == 0
 
     with open(os.path.join(checkout, 'test')) as f:
@@ -175,8 +175,8 @@ def test_regression_cache_corruption(cli, tmpdir, datafiles):
     res = cli.run(project=project, args=['build', canary_element_name])
     assert res.exit_code == 0
 
-    res = cli.run(project=project, args=['checkout', canary_element_name,
-                                         checkout_original])
+    res = cli.run(project=project, args=['artifact', 'checkout', canary_element_name,
+                                         '--directory', checkout_original])
     assert res.exit_code == 0
 
     with open(os.path.join(checkout_original, 'canary')) as f:
@@ -185,8 +185,8 @@ def test_regression_cache_corruption(cli, tmpdir, datafiles):
     res = cli.run(project=project, args=['build', element_name])
     assert res.exit_code == 0
 
-    res = cli.run(project=project, args=['checkout', canary_element_name,
-                                         checkout_after])
+    res = cli.run(project=project, args=['artifact', 'checkout', canary_element_name,
+                                         '--directory', checkout_after])
     assert res.exit_code == 0
 
     with open(os.path.join(checkout_after, 'canary')) as f:
@@ -215,8 +215,8 @@ def test_regression_cache_corruption_2(cli, tmpdir, datafiles):
     res = cli.run(project=project, args=['build', canary_element_name])
     assert res.exit_code == 0
 
-    res = cli.run(project=project, args=['checkout', canary_element_name,
-                                         checkout_original])
+    res = cli.run(project=project, args=['artifact', 'checkout', canary_element_name,
+                                         '--directory', checkout_original])
     assert res.exit_code == 0
 
     with open(os.path.join(checkout_original, 'canary')) as f:
@@ -225,8 +225,8 @@ def test_regression_cache_corruption_2(cli, tmpdir, datafiles):
     res = cli.run(project=project, args=['build', element_name])
     assert res.exit_code == 0
 
-    res = cli.run(project=project, args=['checkout', canary_element_name,
-                                         checkout_after])
+    res = cli.run(project=project, args=['artifact', 'checkout', canary_element_name,
+                                         '--directory', checkout_after])
     assert res.exit_code == 0
 
     with open(os.path.join(checkout_after, 'canary')) as f:
diff --git a/tests/integration/shell.py b/tests/integration/shell.py
index 983cab6..f77e101 100644
--- a/tests/integration/shell.py
+++ b/tests/integration/shell.py
@@ -328,7 +328,7 @@ def test_sysroot(cli, tmpdir, datafiles):
     # Build and check out a sysroot
     res = cli.run(project=project, args=['build', base_element])
     res.assert_success()
-    res = cli.run(project=project, args=['checkout', base_element, checkout_dir])
+    res = cli.run(project=project, args=['artifact', 'checkout', base_element, '--directory', checkout_dir])
     res.assert_success()
 
     # Mutate the sysroot
diff --git a/tests/integration/source-determinism.py b/tests/integration/source-determinism.py
index fa12593..98fe88d 100644
--- a/tests/integration/source-determinism.py
+++ b/tests/integration/source-determinism.py
@@ -87,7 +87,7 @@ def test_deterministic_source_umask(cli, tmpdir, datafiles, kind, integration_ca
             result = cli.run(project=project, args=['build', element_name])
             result.assert_success()
 
-            result = cli.run(project=project, args=['checkout', element_name, checkoutdir])
+            result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkoutdir])
             result.assert_success()
 
             with open(os.path.join(checkoutdir, 'ls-l'), 'r') as f:
@@ -150,7 +150,7 @@ def test_deterministic_source_local(cli, tmpdir, datafiles, integration_cache):
             result = cli.run(project=project, args=['build', element_name])
             result.assert_success()
 
-            result = cli.run(project=project, args=['checkout', element_name, checkoutdir])
+            result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkoutdir])
             result.assert_success()
 
             with open(os.path.join(checkoutdir, 'ls-l'), 'r') as f:
diff --git a/tests/integration/stack.py b/tests/integration/stack.py
index e48e00c..93b3880 100644
--- a/tests/integration/stack.py
+++ b/tests/integration/stack.py
@@ -26,7 +26,7 @@ def test_stack(cli, tmpdir, datafiles):
     res = cli.run(project=project, args=['build', element_name])
     assert res.exit_code == 0
 
-    cli.run(project=project, args=['checkout', element_name, checkout])
+    cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert res.exit_code == 0
 
     with open(os.path.join(checkout, 'hi')) as f:
diff --git a/tests/integration/symlinks.py b/tests/integration/symlinks.py
index 7b8703c..51bf6b1 100644
--- a/tests/integration/symlinks.py
+++ b/tests/integration/symlinks.py
@@ -28,7 +28,7 @@ def test_absolute_symlinks_made_relative(cli, tmpdir, datafiles):
     result = cli.run(project=project, args=['build', element_name])
     assert result.exit_code == 0
 
-    result = cli.run(project=project, args=['checkout', element_name, checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert result.exit_code == 0
 
     symlink = os.path.join(checkout, 'opt', 'orgname')
@@ -52,7 +52,7 @@ def test_allow_overlaps_inside_symlink_with_dangling_target(cli, tmpdir, datafil
     result = cli.run(project=project, args=['build', element_name])
     assert result.exit_code == 0
 
-    result = cli.run(project=project, args=['checkout', element_name, checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert result.exit_code == 0
 
     # See the dangling-symlink*.bst elements for details on what we are testing.
@@ -73,6 +73,6 @@ def test_detect_symlink_overlaps_pointing_outside_sandbox(cli, tmpdir, datafiles
     # ...but when we compose them together, the overlaps create paths that
     # point outside the sandbox which BuildStream needs to detect before it
     # tries to actually write there.
-    result = cli.run(project=project, args=['checkout', element_name, checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
     assert result.exit_code == -1
     assert "Destination path resolves to a path outside of the staging area" in result.stderr
diff --git a/tests/loader/junctions.py b/tests/loader/junctions.py
index d97c9f7..90608d0 100644
--- a/tests/loader/junctions.py
+++ b/tests/loader/junctions.py
@@ -39,7 +39,7 @@ def test_simple_build(cli, tmpdir, datafiles):
     # Build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the checkout contains the expected files from both projects
@@ -70,7 +70,7 @@ def test_nested_simple(cli, tmpdir, datafiles):
     # Build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the checkout contains the expected files from all subprojects
@@ -94,7 +94,7 @@ def test_nested_double(cli, tmpdir, datafiles):
     # Build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the checkout contains the expected files from all subprojects
@@ -167,7 +167,7 @@ def test_options_default(cli, tmpdir, datafiles):
     # Build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     assert(os.path.exists(os.path.join(checkoutdir, 'pony.txt')))
@@ -184,7 +184,7 @@ def test_options(cli, tmpdir, datafiles):
     # Build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     assert(not os.path.exists(os.path.join(checkoutdir, 'pony.txt')))
@@ -201,7 +201,7 @@ def test_options_inherit(cli, tmpdir, datafiles):
     # Build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     assert(not os.path.exists(os.path.join(checkoutdir, 'pony.txt')))
@@ -262,7 +262,7 @@ def test_git_build(cli, tmpdir, datafiles):
     # Build (with implicit fetch of subproject), checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the checkout contains the expected files from both projects
@@ -303,7 +303,7 @@ def test_build_git_cross_junction_names(cli, tmpdir, datafiles):
     # Build (with implicit fetch of subproject), checkout
     result = cli.run(project=project, args=['build', 'base.bst:target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'base.bst:target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'base.bst:target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the checkout contains the expected files from both projects
diff --git a/tests/plugins/filter.py b/tests/plugins/filter.py
index 31b23c1..6c0a3ee 100644
--- a/tests/plugins/filter.py
+++ b/tests/plugins/filter.py
@@ -18,7 +18,7 @@ def test_filter_include(datafiles, cli, tmpdir):
     result.assert_success()
 
     checkout = os.path.join(tmpdir.dirname, tmpdir.basename, 'checkout')
-    result = cli.run(project=project, args=['checkout', 'output-include.bst', checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'output-include.bst', '--directory', checkout])
     result.assert_success()
     assert os.path.exists(os.path.join(checkout, "foo"))
     assert not os.path.exists(os.path.join(checkout, "bar"))
@@ -31,7 +31,8 @@ def test_filter_include_dynamic(datafiles, cli, tmpdir):
     result.assert_success()
 
     checkout = os.path.join(tmpdir.dirname, tmpdir.basename, 'checkout')
-    result = cli.run(project=project, args=['checkout', 'output-dynamic-include.bst', checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'output-dynamic-include.bst',
+                                            '--directory', checkout])
     result.assert_success()
     assert os.path.exists(os.path.join(checkout, "foo"))
     assert not os.path.exists(os.path.join(checkout, "bar"))
@@ -44,7 +45,7 @@ def test_filter_exclude(datafiles, cli, tmpdir):
     result.assert_success()
 
     checkout = os.path.join(tmpdir.dirname, tmpdir.basename, 'checkout')
-    result = cli.run(project=project, args=['checkout', 'output-exclude.bst', checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'output-exclude.bst', '--directory', checkout])
     result.assert_success()
     assert not os.path.exists(os.path.join(checkout, "foo"))
     assert os.path.exists(os.path.join(checkout, "bar"))
@@ -57,7 +58,7 @@ def test_filter_orphans(datafiles, cli, tmpdir):
     result.assert_success()
 
     checkout = os.path.join(tmpdir.dirname, tmpdir.basename, 'checkout')
-    result = cli.run(project=project, args=['checkout', 'output-orphans.bst', checkout])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'output-orphans.bst', '--directory', checkout])
     result.assert_success()
     assert os.path.exists(os.path.join(checkout, "baz"))
 
@@ -137,7 +138,7 @@ def test_filter_workspace_build(datafiles, cli, tmpdir):
     result = cli.run(project=project, args=['build', 'output-orphans.bst'])
     result.assert_success()
     checkout_dir = os.path.join(tempdir, "checkout")
-    result = cli.run(project=project, args=['checkout', 'output-orphans.bst', checkout_dir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'output-orphans.bst', '--directory', checkout_dir])
     result.assert_success()
     assert os.path.exists(os.path.join(checkout_dir, "quux"))
 
@@ -157,7 +158,7 @@ def test_filter_workspace_close(datafiles, cli, tmpdir):
     result = cli.run(project=project, args=['build', 'output-orphans.bst'])
     result.assert_success()
     checkout_dir = os.path.join(tempdir, "checkout")
-    result = cli.run(project=project, args=['checkout', 'output-orphans.bst', checkout_dir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'output-orphans.bst', '--directory', checkout_dir])
     result.assert_success()
     assert not os.path.exists(os.path.join(checkout_dir, "quux"))
 
@@ -177,7 +178,7 @@ def test_filter_workspace_reset(datafiles, cli, tmpdir):
     result = cli.run(project=project, args=['build', 'output-orphans.bst'])
     result.assert_success()
     checkout_dir = os.path.join(tempdir, "checkout")
-    result = cli.run(project=project, args=['checkout', 'output-orphans.bst', checkout_dir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'output-orphans.bst', '--directory', checkout_dir])
     result.assert_success()
     assert not os.path.exists(os.path.join(checkout_dir, "quux"))
 
diff --git a/tests/sources/bzr.py b/tests/sources/bzr.py
index 9895180..a56005f 100644
--- a/tests/sources/bzr.py
+++ b/tests/sources/bzr.py
@@ -36,7 +36,7 @@ def test_fetch_checkout(cli, tmpdir, datafiles):
     assert result.exit_code == 0
     result = cli.run(project=project, args=['build', 'target.bst'])
     assert result.exit_code == 0
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     assert result.exit_code == 0
 
     # Assert we checked out the file as it was commited
diff --git a/tests/sources/deb.py b/tests/sources/deb.py
index b925fc9..b40358e 100644
--- a/tests/sources/deb.py
+++ b/tests/sources/deb.py
@@ -114,7 +114,7 @@ def test_stage_default_basedir(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the content of the first directory is checked out (base-dir: '')
@@ -142,7 +142,7 @@ def test_stage_no_basedir(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the full content of the tarball is checked out (base-dir: '')
@@ -170,7 +170,7 @@ def test_stage_explicit_basedir(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the content of the first directory is checked out (base-dir: '')
diff --git a/tests/sources/git.py b/tests/sources/git.py
index f9aa62b..dfb2187 100644
--- a/tests/sources/git.py
+++ b/tests/sources/git.py
@@ -95,7 +95,7 @@ def test_submodule_fetch_checkout(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Assert we checked out both files at their expected location
@@ -134,7 +134,7 @@ def test_submodule_fetch_source_enable_explicit(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Assert we checked out both files at their expected location
@@ -173,7 +173,7 @@ def test_submodule_fetch_source_disable(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Assert we checked out both files at their expected location
@@ -212,7 +212,7 @@ def test_submodule_fetch_submodule_does_override(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Assert we checked out both files at their expected location
@@ -256,7 +256,7 @@ def test_submodule_fetch_submodule_individual_checkout(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Assert we checked out files at their expected location
@@ -301,7 +301,7 @@ def test_submodule_fetch_submodule_individual_checkout_explicit(cli, tmpdir, dat
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Assert we checked out files at their expected location
@@ -341,7 +341,7 @@ def test_submodule_fetch_project_override(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Assert we checked out both files at their expected location
diff --git a/tests/sources/local.py b/tests/sources/local.py
index de12473..4a0851d 100644
--- a/tests/sources/local.py
+++ b/tests/sources/local.py
@@ -77,7 +77,7 @@ def test_stage_file(cli, tmpdir, datafiles):
     # Build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the checkout contains the expected file
@@ -92,7 +92,7 @@ def test_stage_directory(cli, tmpdir, datafiles):
     # Build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the checkout contains the expected file and directory and other file
@@ -117,7 +117,7 @@ def test_stage_symlink(cli, tmpdir, datafiles):
     # Build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the checkout contains the expected file and directory and other file
diff --git a/tests/sources/patch.py b/tests/sources/patch.py
index 39d4336..51ae690 100644
--- a/tests/sources/patch.py
+++ b/tests/sources/patch.py
@@ -75,7 +75,7 @@ def test_stage_and_patch(cli, tmpdir, datafiles):
     # Build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Test the file.txt was patched and changed
@@ -113,7 +113,7 @@ def test_stage_separate_patch_dir(cli, tmpdir, datafiles):
     # Track, fetch, build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Test the file.txt was patched and changed
@@ -129,7 +129,7 @@ def test_stage_multiple_patches(cli, tmpdir, datafiles):
     # Track, fetch, build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Test the file.txt was patched and changed
@@ -145,7 +145,7 @@ def test_patch_strip_level(cli, tmpdir, datafiles):
     # Track, fetch, build, checkout
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Test the file.txt was patched and changed
diff --git a/tests/sources/previous_source_access.py b/tests/sources/previous_source_access.py
index f090ac8..26640d1 100644
--- a/tests/sources/previous_source_access.py
+++ b/tests/sources/previous_source_access.py
@@ -35,7 +35,7 @@ def test_custom_transform_source(cli, tmpdir, datafiles):
     ])
     destpath = os.path.join(cli.directory, 'checkout')
     result = cli.run(project=project, args=[
-        'checkout', 'target.bst', destpath
+        'artifact', 'checkout', 'target.bst', '--directory', destpath
     ])
     result.assert_success()
     # Assert that files from both sources exist, and that they have
diff --git a/tests/sources/remote.py b/tests/sources/remote.py
index 6062c9c..b8cf049 100644
--- a/tests/sources/remote.py
+++ b/tests/sources/remote.py
@@ -89,7 +89,7 @@ def test_simple_file_build(cli, tmpdir, datafiles):
     result.assert_success()
 
     result = cli.run(project=project, args=[
-        'checkout', 'target.bst', checkoutdir
+        'artifact', 'checkout', 'target.bst', '--directory', checkoutdir
     ])
     result.assert_success()
     # Note that the url of the file in target.bst is actually /dir/file
@@ -122,7 +122,7 @@ def test_simple_file_custom_name_build(cli, tmpdir, datafiles):
     result.assert_success()
 
     result = cli.run(project=project, args=[
-        'checkout', 'target.bst', checkoutdir
+        'artifact', 'checkout', 'target.bst', '--directory', checkoutdir
     ])
     result.assert_success()
     assert(not os.path.exists(os.path.join(checkoutdir, 'file')))
@@ -169,7 +169,7 @@ def test_executable(cli, tmpdir, datafiles):
     ])
 
     result = cli.run(project=project, args=[
-        'checkout', 'target-custom-executable.bst', checkoutdir
+        'artifact', 'checkout', 'target-custom-executable.bst', '--directory', checkoutdir
     ])
     mode = os.stat(os.path.join(checkoutdir, 'some-custom-file')).st_mode
     assert (mode & stat.S_IEXEC)
@@ -202,7 +202,7 @@ def test_use_netrc(cli, datafiles, server_type, tmpdir):
         result.assert_success()
         result = cli.run(project=project, args=['build', 'target.bst'])
         result.assert_success()
-        result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+        result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
         result.assert_success()
 
         checkout_file = os.path.join(checkoutdir, 'file')
diff --git a/tests/sources/tar.py b/tests/sources/tar.py
index 35eb837..406d670 100644
--- a/tests/sources/tar.py
+++ b/tests/sources/tar.py
@@ -139,7 +139,7 @@ def test_stage_default_basedir(cli, tmpdir, datafiles, srcdir):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the content of the first directory is checked out (base-dir: '*')
@@ -168,7 +168,7 @@ def test_stage_no_basedir(cli, tmpdir, datafiles, srcdir):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the full content of the tarball is checked out (base-dir: '')
@@ -197,7 +197,7 @@ def test_stage_explicit_basedir(cli, tmpdir, datafiles, srcdir):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the content of the first directory is checked out (base-dir: '*')
@@ -233,7 +233,7 @@ def test_stage_contains_links(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the content of the first directory is checked out (base-dir: '*')
@@ -262,7 +262,7 @@ def test_stage_default_basedir_lzip(cli, tmpdir, datafiles, srcdir):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target-lz.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target-lz.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target-lz.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the content of the first directory is checked out (base-dir: '*')
@@ -348,7 +348,7 @@ def test_use_netrc(cli, datafiles, server_type, tmpdir):
         result.assert_success()
         result = cli.run(project=project, args=['build', 'target.bst'])
         result.assert_success()
-        result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+        result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
         result.assert_success()
 
         original_dir = os.path.join(str(datafiles), 'content', 'a')
diff --git a/tests/sources/zip.py b/tests/sources/zip.py
index 36e0334..009862e 100644
--- a/tests/sources/zip.py
+++ b/tests/sources/zip.py
@@ -123,7 +123,7 @@ def test_stage_default_basedir(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the content of the first directory is checked out (base-dir: '*')
@@ -151,7 +151,7 @@ def test_stage_no_basedir(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the full content of the tarball is checked out (base-dir: '')
@@ -179,7 +179,7 @@ def test_stage_explicit_basedir(cli, tmpdir, datafiles):
     result.assert_success()
     result = cli.run(project=project, args=['build', 'target.bst'])
     result.assert_success()
-    result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+    result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
     result.assert_success()
 
     # Check that the content of the first directory is checked out (base-dir: '*')
@@ -221,7 +221,7 @@ def test_use_netrc(cli, datafiles, server_type, tmpdir):
         result.assert_success()
         result = cli.run(project=project, args=['build', 'target.bst'])
         result.assert_success()
-        result = cli.run(project=project, args=['checkout', 'target.bst', checkoutdir])
+        result = cli.run(project=project, args=['artifact', 'checkout', 'target.bst', '--directory', checkoutdir])
         result.assert_success()
 
         original_dir = os.path.join(str(datafiles), 'content', 'a')


[buildstream] 01/13: caches: Allow flagging in context whether to require read-only access

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 3d326ca831cf1b597177a6ff0626dc2de72dba57
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Wed Dec 5 18:42:15 2018 +0000

    caches: Allow flagging in context whether to require read-only access
---
 buildstream/_artifactcache/artifactcache.py | 10 +++++-
 buildstream/_artifactcache/cascache.py      | 55 +++++++++++++++++++++++++----
 buildstream/_context.py                     |  6 ++--
 3 files changed, 61 insertions(+), 10 deletions(-)

diff --git a/buildstream/_artifactcache/artifactcache.py b/buildstream/_artifactcache/artifactcache.py
index b4b8df3..f139972 100644
--- a/buildstream/_artifactcache/artifactcache.py
+++ b/buildstream/_artifactcache/artifactcache.py
@@ -75,7 +75,8 @@ class ArtifactCache():
         self._has_fetch_remotes = False
         self._has_push_remotes = False
 
-        os.makedirs(self.extractdir, exist_ok=True)
+        if not context.read_only:
+            os.makedirs(self.extractdir, exist_ok=True)
 
         self._calculate_cache_quota()
 
@@ -477,6 +478,7 @@ class ArtifactCache():
     #               Bytes, or None if defer_prune is True
     #
     def remove(self, ref):
+        assert not self.context.read_only
 
         # Remove extract if not used by other ref
         tree = self.cas.resolve_ref(ref)
@@ -517,6 +519,7 @@ class ArtifactCache():
     # Returns: path to extracted artifact
     #
     def extract(self, element, key, subdir=None):
+        assert not self.context.read_only
         ref = self.get_artifact_fullname(element, key)
 
         path = os.path.join(self.extractdir, element._get_project().name, element.normal_name)
@@ -533,6 +536,7 @@ class ArtifactCache():
     #     keys (list): The cache keys to use
     #
     def commit(self, element, content, keys):
+        assert not self.context.read_only
         refs = [self.get_artifact_fullname(element, key) for key in keys]
 
         self.cas.commit(refs, content)
@@ -649,6 +653,7 @@ class ArtifactCache():
     #   (bool): True if pull was successful, False if artifact was not available
     #
     def pull(self, element, key, *, progress=None, subdir=None, excluded_subdirs=None):
+        assert not self.context.read_only
         ref = self.get_artifact_fullname(element, key)
 
         project = element._get_project()
@@ -688,6 +693,7 @@ class ArtifactCache():
     #     digest (Digest): The digest of the tree
     #
     def pull_tree(self, project, digest):
+        assert not self.context.read_only
         for remote in self._remotes[project]:
             digest = self.cas.pull_tree(remote, digest)
 
@@ -761,6 +767,7 @@ class ArtifactCache():
     #     newkey (str): A new cache key for the artifact
     #
     def link_key(self, element, oldkey, newkey):
+        assert not self.context.read_only
         oldref = self.get_artifact_fullname(element, oldkey)
         newref = self.get_artifact_fullname(element, newkey)
 
@@ -815,6 +822,7 @@ class ArtifactCache():
     #    size (int): The size of the artifact cache to record
     #
     def _write_cache_size(self, size):
+        assert not self.context.read_only
         assert isinstance(size, int)
         size_file_path = os.path.join(self.context.artifactdir, CACHE_SIZE_FILE)
         with utils.save_file_atomic(size_file_path, "w") as f:
diff --git a/buildstream/_artifactcache/cascache.py b/buildstream/_artifactcache/cascache.py
index 9ba748d..53b773f 100644
--- a/buildstream/_artifactcache/cascache.py
+++ b/buildstream/_artifactcache/cascache.py
@@ -105,21 +105,27 @@ class BlobNotFound(CASError):
 #
 class CASCache():
 
-    def __init__(self, path):
+    def __init__(self, path, *, read_only=False):
         self.casdir = os.path.join(path, 'cas')
         self.tmpdir = os.path.join(path, 'tmp')
-        os.makedirs(os.path.join(self.casdir, 'refs', 'heads'), exist_ok=True)
-        os.makedirs(os.path.join(self.casdir, 'objects'), exist_ok=True)
-        os.makedirs(self.tmpdir, exist_ok=True)
+        self._read_only = read_only
+
+        headdir = os.path.join(self.casdir, 'refs', 'heads')
+        objdir = os.path.join(self.casdir, 'objects')
+        if read_only:
+            self._initialized = (os.path.isdir(headdir) and os.path.isdir(objdir) and os.path.isdir(self.tmpdir))
+        else:
+            os.makedirs(os.path.join(self.casdir, 'refs', 'heads'), exist_ok=True)
+            os.makedirs(os.path.join(self.casdir, 'objects'), exist_ok=True)
+            os.makedirs(self.tmpdir, exist_ok=True)
+            self._initialized = True
 
     # preflight():
     #
     # Preflight check.
     #
     def preflight(self):
-        headdir = os.path.join(self.casdir, 'refs', 'heads')
-        objdir = os.path.join(self.casdir, 'objects')
-        if not (os.path.isdir(headdir) and os.path.isdir(objdir)):
+        if not self._initialized:
             raise CASError("CAS repository check failed for '{}'".format(self.casdir))
 
     # contains():
@@ -132,6 +138,9 @@ class CASCache():
     # Returns: True if the ref is in the cache, False otherwise
     #
     def contains(self, ref):
+        if not self._initialized:
+            return False
+
         refpath = self._refpath(ref)
 
         # This assumes that the repository doesn't have any dangling pointers
@@ -149,6 +158,9 @@ class CASCache():
     # Returns: True if the subdir exists & is populated in the cache, False otherwise
     #
     def contains_subdir_artifact(self, ref, subdir):
+        if not self._initialized:
+            return False
+
         tree = self.resolve_ref(ref)
 
         # This assumes that the subdir digest is present in the element tree
@@ -174,6 +186,8 @@ class CASCache():
     # Returns: path to extracted directory
     #
     def extract(self, ref, path, subdir=None):
+        assert self._initialized
+
         tree = self.resolve_ref(ref, update_mtime=True)
 
         originaldest = dest = os.path.join(path, tree.hash)
@@ -214,6 +228,8 @@ class CASCache():
     #     path (str): The directory to import
     #
     def commit(self, refs, path):
+        assert self._initialized and not self._read_only
+
         tree = self._commit_directory(path)
 
         for ref in refs:
@@ -230,6 +246,8 @@ class CASCache():
     #     subdir (str): A subdirectory to limit the comparison to
     #
     def diff(self, ref_a, ref_b, *, subdir=None):
+        assert self._initialized
+
         tree_a = self.resolve_ref(ref_a)
         tree_b = self.resolve_ref(ref_b)
 
@@ -283,6 +301,7 @@ class CASCache():
     #   (bool): True if pull was successful, False if ref was not available
     #
     def pull(self, ref, remote, *, progress=None, subdir=None, excluded_subdirs=None):
+        assert self._initialized and not self._read_only
         try:
             remote.init()
 
@@ -322,6 +341,7 @@ class CASCache():
     #     digest (Digest): The digest of the tree
     #
     def pull_tree(self, remote, digest):
+        assert self._initialized and not self._read_only
         try:
             remote.init()
 
@@ -344,6 +364,7 @@ class CASCache():
     #     newref (str): A new ref for the same directory
     #
     def link_ref(self, oldref, newref):
+        assert self._initialized and not self._read_only
         tree = self.resolve_ref(oldref)
 
         self.set_ref(newref, tree)
@@ -363,6 +384,7 @@ class CASCache():
     #   (CASError): if there was an error
     #
     def push(self, refs, remote):
+        assert self._initialized
         skipped_remote = True
         try:
             for ref in refs:
@@ -411,6 +433,7 @@ class CASCache():
     #     (CASError): if there was an error
     #
     def push_directory(self, remote, directory):
+        assert self._initialized
         remote.init()
 
         self._send_directory(remote, directory.ref)
@@ -427,6 +450,7 @@ class CASCache():
     #     (CASError): if there was an error
     #
     def push_message(self, remote, message):
+        assert self._initialized
 
         message_buffer = message.SerializeToString()
         message_digest = utils._message_digest(message_buffer)
@@ -488,6 +512,7 @@ class CASCache():
     # Either `path` or `buffer` must be passed, but not both.
     #
     def add_object(self, *, digest=None, path=None, buffer=None, link_directly=False):
+        assert self._initialized and not self._read_only
         # Exactly one of the two parameters has to be specified
         assert (path is None) != (buffer is None)
 
@@ -543,6 +568,7 @@ class CASCache():
     #     ref (str): The name of the ref
     #
     def set_ref(self, ref, tree):
+        assert self._initialized and not self._read_only
         refpath = self._refpath(ref)
         os.makedirs(os.path.dirname(refpath), exist_ok=True)
         with utils.save_file_atomic(refpath, 'wb', tempdir=self.tmpdir) as f:
@@ -560,6 +586,7 @@ class CASCache():
     #     (Digest): The digest stored in the ref
     #
     def resolve_ref(self, ref, *, update_mtime=False):
+        assert self._initialized
         refpath = self._refpath(ref)
 
         try:
@@ -582,6 +609,7 @@ class CASCache():
     #     ref (str): The ref to update
     #
     def update_mtime(self, ref):
+        assert self._initialized and not self._read_only
         try:
             os.utime(self._refpath(ref))
         except FileNotFoundError as e:
@@ -595,6 +623,8 @@ class CASCache():
     #    (int): The size of the cache.
     #
     def calculate_cache_size(self):
+        if not self._initialized:
+            return 0
         return utils._get_dir_size(self.casdir)
 
     # list_refs():
@@ -605,6 +635,8 @@ class CASCache():
     #     (list) - A list of refs in LRM order
     #
     def list_refs(self):
+        if not self._initialized:
+            return []
         # string of: /path/to/repo/refs/heads
         ref_heads = os.path.join(self.casdir, 'refs', 'heads')
 
@@ -630,6 +662,8 @@ class CASCache():
     #     (list) - A list of objects and timestamps in LRM order
     #
     def list_objects(self):
+        if not self._initialized:
+            return []
         objs = []
         mtimes = []
 
@@ -648,6 +682,7 @@ class CASCache():
         return sorted(zip(mtimes, objs))
 
     def clean_up_refs_until(self, time):
+        assert self._initialized and not self._read_only
         ref_heads = os.path.join(self.casdir, 'refs', 'heads')
 
         for root, _, files in os.walk(ref_heads):
@@ -672,6 +707,7 @@ class CASCache():
     #               Bytes, or None if defer_prune is True
     #
     def remove(self, ref, *, defer_prune=False):
+        assert self._initialized and not self._read_only
 
         # Remove cache ref
         refpath = self._refpath(ref)
@@ -691,6 +727,10 @@ class CASCache():
     # Prune unreachable objects from the repo.
     #
     def prune(self):
+        if not self._initialized:
+            return 0
+        assert self._initialized and not self._read_only
+
         ref_heads = os.path.join(self.casdir, 'refs', 'heads')
 
         pruned = 0
@@ -717,6 +757,7 @@ class CASCache():
         return pruned
 
     def update_tree_mtime(self, tree):
+        assert self._initialized and not self._read_only
         reachable = set()
         self._reachable_refs_dir(reachable, tree, update_mtime=True)
 
diff --git a/buildstream/_context.py b/buildstream/_context.py
index e0eea99..93164f7 100644
--- a/buildstream/_context.py
+++ b/buildstream/_context.py
@@ -52,7 +52,9 @@ from .plugin import _plugin_lookup
 #
 class Context():
 
-    def __init__(self, directory=None):
+    def __init__(self, directory=None, *, read_only=False):
+        # Whether to avoid operations that may involve making changes
+        self.read_only = read_only
 
         # Filename indicating which configuration file was used, or None for the defaults
         self.config_origin = None
@@ -640,7 +642,7 @@ class Context():
 
     def get_cascache(self):
         if self._cascache is None:
-            self._cascache = CASCache(self.artifactdir)
+            self._cascache = CASCache(self.artifactdir, read_only=self.read_only)
         return self._cascache
 
     # guess_element()


[buildstream] 09/13: cli: Add artifact list-contents subcommand

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 94f64af55eae44f16452228e6f34a2dff47a5c83
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Wed Dec 12 18:46:48 2018 +0000

    cli: Add artifact list-contents subcommand
---
 buildstream/_frontend/cli.py | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/buildstream/_frontend/cli.py b/buildstream/_frontend/cli.py
index 5f7a059..139d902 100644
--- a/buildstream/_frontend/cli.py
+++ b/buildstream/_frontend/cli.py
@@ -1212,6 +1212,37 @@ def artifact_list(app, null, artifact_prefix):
               end=sentinel)
 
 
+##########################################################################
+#                     Artifact List Contents Command                     #
+##########################################################################
+@artifact.command(name='list-contents', short_help="List contents of artifacts")
+@click.option('--null', '-z', default=False, is_flag=True,
+              help="Separate tokens with NUL bytes instead of newlines")
+@click.argument('artifacts', type=click.Path(), nargs=-1)
+@click.pass_obj
+def artifact_list_contents(app, null, artifacts):
+    """List the contents of artifacts"""
+
+    sentinel = '\0' if null else '\n'
+
+    with app.initialized():
+        cache = app.context.artifactcache
+
+        elements, artifacts = _classify_artifacts(artifacts, cache.cas,
+                                                  app.project.directory)
+
+        if not elements and not artifacts:
+            element = app.context.guess_element()
+            if element is not None:
+                elements = [element]
+
+        vdirs = _load_vdirs(app, elements, artifacts)
+
+        for vdir in vdirs:
+            vdir = vdir.descend(["files"])
+            print(sentinel.join(vdir.list_relative_paths()), end=sentinel)
+
+
 ##################################################################
 #                      DEPRECATED Commands                       #
 ##################################################################


[buildstream] 10/13: cli: Add artifact diff subcommand

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 145722b692a5757c649e197591986c78d26f6603
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Wed Dec 12 18:47:52 2018 +0000

    cli: Add artifact diff subcommand
---
 buildstream/_frontend/cli.py | 47 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 46 insertions(+), 1 deletion(-)

diff --git a/buildstream/_frontend/cli.py b/buildstream/_frontend/cli.py
index 139d902..8cfe638 100644
--- a/buildstream/_frontend/cli.py
+++ b/buildstream/_frontend/cli.py
@@ -146,7 +146,7 @@ def override_completions(cmd, cmd_param, args, incomplete):
                 cmd_param.opts == ['--track'] or
                 cmd_param.opts == ['--track-except']):
             return complete_target(args, incomplete)
-        if cmd_param.name == 'artifacts':
+        if cmd_param.name in ('artifacts', 'artifact_orig', 'artifact_new'):
             complete_list = complete_target(args, incomplete)
             complete_list.extend(complete_artifact(args, incomplete))
             return complete_list
@@ -1243,6 +1243,51 @@ def artifact_list_contents(app, null, artifacts):
             print(sentinel.join(vdir.list_relative_paths()), end=sentinel)
 
 
+#################################################################
+#                     Artifact Diff Command                     #
+#################################################################
+@artifact.command(name='diff', short_help="Compute the difference between two artifacts")
+@click.option('--null', '-z', default=False, is_flag=True,
+              help="Separate tokens with NUL bytes instead of newlines")
+@click.argument('artifact_orig', type=click.Path(), nargs=1)
+@click.argument('artifact_new', type=click.Path(), nargs=1)
+@click.pass_obj
+def artifact_diff(app, null, artifact_orig, artifact_new):
+    """List the differences between artifacts"""
+    from .._pipeline import PipelineSelection
+
+    sentinel = '\0' if null else '\n'
+
+    with app.initialized():
+        cache = app.context.artifactcache
+
+        def parse_artifact_ref(artifact_ref):
+            elements, artifacts = _classify_artifacts((artifact_ref,), cache.cas,
+                                                      app.project.directory)
+            if len(elements) + len(artifacts) > 1:
+                raise AppError("{} expanded to multiple artifacts".format(artifact_ref))
+
+            if artifacts:
+                return artifacts[0]
+
+            elements = app.stream.load_selection(elements, selection=PipelineSelection.NONE)
+            element = elements[0]
+            if not element._cached():
+                raise AppError("Element {} is not cached".format(artifact_ref))
+            return cache.get_artifact_fullname(element, element._get_cache_key())
+
+        orig = parse_artifact_ref(artifact_orig)
+        new = parse_artifact_ref(artifact_new)
+
+        modified, removed, added = cache.cas.diff(orig, new, subdir="files")
+        if modified:
+            print(sentinel.join('modified ' + path for path in modified), end=sentinel)
+        if removed:
+            print(sentinel.join('removed  ' + path for path in removed), end=sentinel)
+        if added:
+            print(sentinel.join('added    ' + path for path in added), end=sentinel)
+
+
 ##################################################################
 #                      DEPRECATED Commands                       #
 ##################################################################


[buildstream] 07/13: cli: Add artifact delete subcommand

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit f169bcce8f499e4049bed427900ad615e08ffba8
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Wed Dec 12 18:37:53 2018 +0000

    cli: Add artifact delete subcommand
---
 buildstream/_frontend/cli.py | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/buildstream/_frontend/cli.py b/buildstream/_frontend/cli.py
index 8ac2078..09d25cc 100644
--- a/buildstream/_frontend/cli.py
+++ b/buildstream/_frontend/cli.py
@@ -1160,6 +1160,34 @@ def artifact_checkout(app, force, deps, integrate, hardlinks, tar, directory, ar
             vdir.export_files(directory, can_link=hardlinks, can_destroy=force)
 
 
+###################################################################
+#                     Artifact Delete Command                     #
+###################################################################
+@artifact.command(name='delete', short_help="Delete matching artifacts")
+@click.argument('artifacts', type=click.Path(), nargs=-1)
+@click.pass_obj
+def artifact_delete(app, artifacts):
+    '''Delete matching artifacts from the cache'''
+    from .._pipeline import PipelineSelection
+    with app.initialized():
+        cache = app.context.artifactcache
+
+        elements, artifacts = _classify_artifacts(artifacts, cache.cas,
+                                                  app.project.directory)
+
+        if not elements and not artifacts:
+            element = app.context.guess_element()
+            if element is not None:
+                elements = [element]
+
+        if elements:
+            elements = app.stream.load_selection(elements, selection=PipelineSelection.NONE)
+            for element in elements:
+                cache.remove(cache.get_artifact_fullname(element, element._get_cache_key()))
+        for i, ref in enumerate(artifacts, start=1):
+            cache.cas.remove(ref, defer_prune=(i != len(artifacts)))
+
+
 ##################################################################
 #                      DEPRECATED Commands                       #
 ##################################################################


[buildstream] 08/13: cli: Add artifact list subcommand

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 2ca28df61a4d5b6cefa3aa15c7e8c0b2971f0448
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Wed Dec 12 18:44:05 2018 +0000

    cli: Add artifact list subcommand
---
 buildstream/_frontend/cli.py | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/buildstream/_frontend/cli.py b/buildstream/_frontend/cli.py
index 09d25cc..5f7a059 100644
--- a/buildstream/_frontend/cli.py
+++ b/buildstream/_frontend/cli.py
@@ -150,6 +150,8 @@ def override_completions(cmd, cmd_param, args, incomplete):
             complete_list = complete_target(args, incomplete)
             complete_list.extend(complete_artifact(args, incomplete))
             return complete_list
+        if cmd_param.name == 'artifact_prefix':
+            return complete_artifact(args, incomplete)
 
     raise CompleteUnhandled()
 
@@ -1188,6 +1190,28 @@ def artifact_delete(app, artifacts):
             cache.cas.remove(ref, defer_prune=(i != len(artifacts)))
 
 
+#################################################################
+#                     Artifact List Command                     #
+#################################################################
+@artifact.command(name='list', short_help="List artifacts that match the prefix")
+@click.option('--null', '-z', default=False, is_flag=True,
+              help="Separate tokens with NUL bytes instead of newlines")
+@click.argument('artifact_prefix', type=click.Path(), nargs=-1)
+@click.pass_obj
+def artifact_list(app, null, artifact_prefix):
+    """List artifact refs that match the prefix"""
+
+    sentinel = '\0' if null else '\n'
+
+    with app.initialized():
+        cas = app.context.artifactcache.cas
+
+        prefixes = _classify_artifact_refs(artifact_prefix, cas)
+        print(sentinel.join(ref for ref in cas.list_refs()
+                            if any(ref.startswith(pfx) for pfx in prefixes)),
+              end=sentinel)
+
+
 ##################################################################
 #                      DEPRECATED Commands                       #
 ##################################################################


[buildstream] 04/13: cli: Split classify_artifacts helper up

Posted by no...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch richardmaw/artifact-subcommands
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit c3fca9f2000a3fa3bb4f5b918c8d67212d2018fa
Author: Richard Maw <ri...@codethink.co.uk>
AuthorDate: Wed Dec 12 18:23:19 2018 +0000

    cli: Split classify_artifacts helper up
    
    Classifying artifact refs is useful for glob expansion
    even in cases where an element target name would be inappropriate.
---
 buildstream/_frontend/cli.py | 52 +++++++++++++++++++++++++++-----------------
 1 file changed, 32 insertions(+), 20 deletions(-)

diff --git a/buildstream/_frontend/cli.py b/buildstream/_frontend/cli.py
index 49da15c..4b3fcba 100644
--- a/buildstream/_frontend/cli.py
+++ b/buildstream/_frontend/cli.py
@@ -961,36 +961,48 @@ def workspace_list(app):
 #############################################################
 #                     Artifact Commands                     #
 #############################################################
-def _classify_artifacts(names, cas, project_directory):
-    element_targets = []
-    artifact_refs = []
-    element_globs = []
-    artifact_globs = []
-
+def _classify_element_targets(names, project_directory):
+    globs = []
+    targets = []
+    unmatched = []
     for name in names:
         if name.endswith('.bst'):
             if any(c in "*?[" for c in name):
-                element_globs.append(name)
+                globs.append(name)
             else:
-                element_targets.append(name)
+                targets.append(name)
         else:
-            if any(c in "*?[" for c in name):
-                artifact_globs.append(name)
-            else:
-                artifact_refs.append(name)
+            unmatched.append(name)
 
-    if element_globs:
+    if globs:
         for dirpath, _, filenames in os.walk(project_directory):
             for filename in filenames:
-                element_path = os.path.join(dirpath, filename).lstrip(project_directory).lstrip('/')
-                if any(fnmatch(element_path, glob) for glob in element_globs):
-                    element_targets.append(element_path)
+                element_path = os.path.relpath(os.path.join(dirpath, filename), start=project_directory)
+                if any(fnmatch(element_path, glob) for glob in globs):
+                    targets.append(element_path)
+    return targets, unmatched
+
+
+def _classify_artifact_refs(names, cas):
+    globs = []
+    refs = []
+    for name in names:
+        if any(c in "*?[" for c in name):
+            globs.append(name)
+        else:
+            refs.append(name)
+    if globs:
+        refs.extend(ref for ref in cas.list_refs()
+                    if any(fnmatch(ref, glob) for glob in globs))
+    return refs
+
+
+def _classify_artifacts(names, cas, project_directory):
+    targets, unmatched = _classify_element_targets(names, project_directory)
+    refs = _classify_artifact_refs(unmatched, cas)
 
-    if artifact_globs:
-        artifact_refs.extend(ref for ref in cas.list_refs()
-                             if any(fnmatch(ref, glob) for glob in artifact_globs))
+    return targets, refs
 
-    return element_targets, artifact_refs
 
 
 @cli.group(short_help="Manipulate cached artifacts")