summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJürg Billeter <j@bitron.ch>2019-03-13 17:04:53 +0000
committerJürg Billeter <j@bitron.ch>2019-03-13 17:04:53 +0000
commitac71ea61bc0cb551c744507d3d7f13ab85387d75 (patch)
tree5ce4a00361a6f262b885a977f93b1054b83fa6e6
parente4aace8d300a75aaebf9f7c2b87e53fab20478b3 (diff)
parent7604d239440f6cf4dc4401b28a291da0f7dcc721 (diff)
downloadbuildstream-ac71ea61bc0cb551c744507d3d7f13ab85387d75.tar.gz
Merge branch 'jennis/introduce_artifact_delete' into 'master'
-rw-r--r--NEWS4
-rw-r--r--buildstream/_artifactcache.py13
-rw-r--r--buildstream/_artifactelement.py4
-rw-r--r--buildstream/_cas/cascache.py16
-rw-r--r--buildstream/_frontend/cli.py14
-rw-r--r--buildstream/_stream.py42
-rw-r--r--doc/source/using_commands.rst7
-rw-r--r--man/bst-artifact-checkout.12
-rw-r--r--man/bst-artifact-delete.112
-rw-r--r--man/bst-artifact-log.12
-rw-r--r--man/bst-artifact-pull.12
-rw-r--r--man/bst-artifact-push.12
-rw-r--r--man/bst-artifact-server.12
-rw-r--r--man/bst-artifact.16
-rw-r--r--man/bst-build.12
-rw-r--r--man/bst-help.12
-rw-r--r--man/bst-init.14
-rw-r--r--man/bst-shell.12
-rw-r--r--man/bst-show.12
-rw-r--r--man/bst-source-checkout.12
-rw-r--r--man/bst-source-fetch.12
-rw-r--r--man/bst-source-track.12
-rw-r--r--man/bst-source.12
-rw-r--r--man/bst-workspace-close.12
-rw-r--r--man/bst-workspace-list.12
-rw-r--r--man/bst-workspace-open.12
-rw-r--r--man/bst-workspace-reset.12
-rw-r--r--man/bst-workspace.12
-rw-r--r--man/bst.15
-rw-r--r--tests/frontend/artifact.py142
-rw-r--r--tests/frontend/completions.py1
31 files changed, 275 insertions, 31 deletions
diff --git a/NEWS b/NEWS
index 0ff7702aa..85bf27c2c 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@
buildstream 1.3.1
=================
+ o Added `bst artifact delete` subcommand. This command removes artifacts from
+ the local cache. Mulitple element names and artifact refs can be specified
+ as arguments.
+
o BREAKING CHANGE: The top level commands `checkout`, `push` and `pull` have
been moved to the `bst artifact` subcommand group and are now obsolete.
For example, you must now use `bst artifact pull hello.bst`.
diff --git a/buildstream/_artifactcache.py b/buildstream/_artifactcache.py
index 330365025..7da2d959c 100644
--- a/buildstream/_artifactcache.py
+++ b/buildstream/_artifactcache.py
@@ -415,12 +415,21 @@ class ArtifactCache():
# Args:
# ref (artifact_name): The name of the artifact to remove (as
# generated by `Element.get_artifact_name`)
+ # defer_prune (bool): Optionally declare whether pruning should
+ # occur immediately after the ref is removed.
#
# Returns:
# (int): The amount of space recovered in the cache, in bytes
#
- def remove(self, ref):
- return self.cas.remove(ref)
+ def remove(self, ref, *, defer_prune=False):
+ return self.cas.remove(ref, defer_prune=defer_prune)
+
+ # prune():
+ #
+ # Prune the artifact cache of unreachable refs
+ #
+ def prune(self):
+ return self.cas.prune()
# get_artifact_directory():
#
diff --git a/buildstream/_artifactelement.py b/buildstream/_artifactelement.py
index a88e83aab..a7915eb28 100644
--- a/buildstream/_artifactelement.py
+++ b/buildstream/_artifactelement.py
@@ -59,6 +59,10 @@ class ArtifactElement(Element):
def _calculate_cache_key(self, dependencies=None):
return self._key
+ # Override Element._get_cache_key()
+ def _get_cache_key(self, strength=None):
+ return self._key
+
# verify_artifact_ref()
#
diff --git a/buildstream/_cas/cascache.py b/buildstream/_cas/cascache.py
index 802fc13fd..02030bb68 100644
--- a/buildstream/_cas/cascache.py
+++ b/buildstream/_cas/cascache.py
@@ -791,16 +791,20 @@ class CASCache():
def _reachable_refs_dir(self, reachable, tree, update_mtime=False):
if tree.hash in reachable:
return
+ try:
+ if update_mtime:
+ os.utime(self.objpath(tree))
- if update_mtime:
- os.utime(self.objpath(tree))
+ reachable.add(tree.hash)
- reachable.add(tree.hash)
+ directory = remote_execution_pb2.Directory()
- directory = remote_execution_pb2.Directory()
+ with open(self.objpath(tree), 'rb') as f:
+ directory.ParseFromString(f.read())
- with open(self.objpath(tree), 'rb') as f:
- directory.ParseFromString(f.read())
+ except FileNotFoundError:
+ # Just exit early if the file doesn't exist
+ return
for filenode in directory.files:
if update_mtime:
diff --git a/buildstream/_frontend/cli.py b/buildstream/_frontend/cli.py
index 02ca52e85..d8c46ce0c 100644
--- a/buildstream/_frontend/cli.py
+++ b/buildstream/_frontend/cli.py
@@ -1106,6 +1106,20 @@ def artifact_log(app, artifacts):
click.echo_via_pager(data)
+###################################################################
+# Artifact Delete Command #
+###################################################################
+@artifact.command(name='delete', short_help="Remove artifacts from the local cache")
+@click.option('--no-prune', 'no_prune', default=False, is_flag=True,
+ help="Do not prune the local cache of unreachable refs")
+@click.argument('artifacts', type=click.Path(), nargs=-1)
+@click.pass_obj
+def artifact_delete(app, artifacts, no_prune):
+ """Remove artifacts from the local cache"""
+ with app.initialized():
+ app.stream.artifact_delete(artifacts, no_prune)
+
+
##################################################################
# DEPRECATED Commands #
##################################################################
diff --git a/buildstream/_stream.py b/buildstream/_stream.py
index b0fce3817..5c880427c 100644
--- a/buildstream/_stream.py
+++ b/buildstream/_stream.py
@@ -30,11 +30,12 @@ from contextlib import contextmanager, suppress
from fnmatch import fnmatch
from ._artifactelement import verify_artifact_ref
-from ._exceptions import StreamError, ImplError, BstError, ArtifactElementError, set_last_task_error
+from ._exceptions import StreamError, ImplError, BstError, ArtifactElementError, CASCacheError, set_last_task_error
from ._message import Message, MessageType
from ._scheduler import Scheduler, SchedStatus, TrackQueue, FetchQueue, BuildQueue, PullQueue, PushQueue
from ._pipeline import Pipeline, PipelineSelection
from ._profile import Topics, profile_start, profile_end
+from .types import _KeyStrength
from . import utils, _yaml, _site
from . import Scope, Consistency
@@ -520,6 +521,45 @@ class Stream():
return logsdirs
+ # artifact_delete()
+ #
+ # Remove artifacts from the local cache
+ #
+ # Args:
+ # targets (str): Targets to remove
+ # no_prune (bool): Whether to prune the unreachable refs, default False
+ #
+ def artifact_delete(self, targets, no_prune):
+ # Return list of Element and/or ArtifactElement objects
+ target_objects = self.load_selection(targets, selection=PipelineSelection.NONE, load_refs=True)
+
+ # Some of the targets may refer to the same key, so first obtain a
+ # set of the refs to be removed.
+ remove_refs = set()
+ for obj in target_objects:
+ for key_strength in [_KeyStrength.STRONG, _KeyStrength.WEAK]:
+ key = obj._get_cache_key(strength=key_strength)
+ remove_refs.add(obj.get_artifact_name(key=key))
+
+ ref_removed = False
+ for ref in remove_refs:
+ try:
+ self._artifacts.remove(ref, defer_prune=True)
+ except CASCacheError as e:
+ self._message(MessageType.WARN, "{}".format(e))
+ continue
+
+ self._message(MessageType.INFO, "Removed: {}".format(ref))
+ ref_removed = True
+
+ # Prune the artifact cache
+ if ref_removed and not no_prune:
+ with self._context.timed_activity("Pruning artifact cache"):
+ self._artifacts.prune()
+
+ if not ref_removed:
+ self._message(MessageType.INFO, "No artifacts were removed")
+
# source_checkout()
#
# Checkout sources of the target element to the specified location
diff --git a/doc/source/using_commands.rst b/doc/source/using_commands.rst
index 312b907f8..53c36197e 100644
--- a/doc/source/using_commands.rst
+++ b/doc/source/using_commands.rst
@@ -101,6 +101,13 @@ Artifact subcommands
.. click:: buildstream._frontend.cli:artifact_push
:prog: bst artifact push
+----
+
+.. _invoking_artifact_delete:
+
+.. click:: buildstream._frontend.cli:artifact_delete
+ :prog: bst artifact delete
+
Source subcommands
------------------
diff --git a/man/bst-artifact-checkout.1 b/man/bst-artifact-checkout.1
index 2c11852c8..27b9954f4 100644
--- a/man/bst-artifact-checkout.1
+++ b/man/bst-artifact-checkout.1
@@ -1,4 +1,4 @@
-.TH "BST ARTIFACT CHECKOUT" "1" "12-Feb-2019" "" "bst artifact checkout Manual"
+.TH "BST ARTIFACT CHECKOUT" "1" "13-Mar-2019" "" "bst artifact checkout Manual"
.SH NAME
bst\-artifact\-checkout \- Checkout contents of an artifact
.SH SYNOPSIS
diff --git a/man/bst-artifact-delete.1 b/man/bst-artifact-delete.1
new file mode 100644
index 000000000..43ef9bb47
--- /dev/null
+++ b/man/bst-artifact-delete.1
@@ -0,0 +1,12 @@
+.TH "BST ARTIFACT DELETE" "1" "13-Mar-2019" "" "bst artifact delete Manual"
+.SH NAME
+bst\-artifact\-delete \- Remove artifacts from the local cache
+.SH SYNOPSIS
+.B bst artifact delete
+[OPTIONS] [ARTIFACTS]...
+.SH DESCRIPTION
+Show logs of all artifacts
+.SH OPTIONS
+.TP
+\fB\-\-no\-prune\fP
+Do not prune the local cache of unreachable refs
diff --git a/man/bst-artifact-log.1 b/man/bst-artifact-log.1
index 6045e2587..2be77021f 100644
--- a/man/bst-artifact-log.1
+++ b/man/bst-artifact-log.1
@@ -1,4 +1,4 @@
-.TH "BST ARTIFACT LOG" "1" "12-Feb-2019" "" "bst artifact log Manual"
+.TH "BST ARTIFACT LOG" "1" "13-Mar-2019" "" "bst artifact log Manual"
.SH NAME
bst\-artifact\-log \- Show logs of an artifact
.SH SYNOPSIS
diff --git a/man/bst-artifact-pull.1 b/man/bst-artifact-pull.1
index b01b2bb15..890090a44 100644
--- a/man/bst-artifact-pull.1
+++ b/man/bst-artifact-pull.1
@@ -1,4 +1,4 @@
-.TH "BST ARTIFACT PULL" "1" "12-Feb-2019" "" "bst artifact pull Manual"
+.TH "BST ARTIFACT PULL" "1" "13-Mar-2019" "" "bst artifact pull Manual"
.SH NAME
bst\-artifact\-pull \- Pull a built artifact
.SH SYNOPSIS
diff --git a/man/bst-artifact-push.1 b/man/bst-artifact-push.1
index bc87c8287..09b068449 100644
--- a/man/bst-artifact-push.1
+++ b/man/bst-artifact-push.1
@@ -1,4 +1,4 @@
-.TH "BST ARTIFACT PUSH" "1" "12-Feb-2019" "" "bst artifact push Manual"
+.TH "BST ARTIFACT PUSH" "1" "13-Mar-2019" "" "bst artifact push Manual"
.SH NAME
bst\-artifact\-push \- Push a built artifact
.SH SYNOPSIS
diff --git a/man/bst-artifact-server.1 b/man/bst-artifact-server.1
index f196b878d..d5d9b8cde 100644
--- a/man/bst-artifact-server.1
+++ b/man/bst-artifact-server.1
@@ -1,4 +1,4 @@
-.TH "BST-ARTIFACT-SERVER" "1" "12-Feb-2019" "" "bst-artifact-server Manual"
+.TH "BST-ARTIFACT-SERVER" "1" "13-Mar-2019" "" "bst-artifact-server Manual"
.SH NAME
bst-artifact-server \- CAS Artifact Server
.SH SYNOPSIS
diff --git a/man/bst-artifact.1 b/man/bst-artifact.1
index a4bbeb7b2..aa46e2ef0 100644
--- a/man/bst-artifact.1
+++ b/man/bst-artifact.1
@@ -1,4 +1,4 @@
-.TH "BST ARTIFACT" "1" "12-Feb-2019" "" "bst artifact Manual"
+.TH "BST ARTIFACT" "1" "13-Mar-2019" "" "bst artifact Manual"
.SH NAME
bst\-artifact \- Manipulate cached artifacts
.SH SYNOPSIS
@@ -23,3 +23,7 @@ Manipulate cached artifacts
\fBlog\fP
Show logs of an artifact
See \fBbst artifact-log(1)\fP for full documentation on the \fBlog\fP command.
+.PP
+\fBdelete\fP
+ Remove artifacts from the local cache
+ See \fBbst artifact-delete(1)\fP for full documentation on the \fBdelete\fP command.
diff --git a/man/bst-build.1 b/man/bst-build.1
index deee1301e..edb67a499 100644
--- a/man/bst-build.1
+++ b/man/bst-build.1
@@ -1,4 +1,4 @@
-.TH "BST BUILD" "1" "12-Feb-2019" "" "bst build Manual"
+.TH "BST BUILD" "1" "13-Mar-2019" "" "bst build Manual"
.SH NAME
bst\-build \- Build elements in a pipeline
.SH SYNOPSIS
diff --git a/man/bst-help.1 b/man/bst-help.1
index 35675db1d..feebf2463 100644
--- a/man/bst-help.1
+++ b/man/bst-help.1
@@ -1,4 +1,4 @@
-.TH "BST HELP" "1" "12-Feb-2019" "" "bst help Manual"
+.TH "BST HELP" "1" "13-Mar-2019" "" "bst help Manual"
.SH NAME
bst\-help \- Print usage information
.SH SYNOPSIS
diff --git a/man/bst-init.1 b/man/bst-init.1
index 9b7b119d9..6f41d97d9 100644
--- a/man/bst-init.1
+++ b/man/bst-init.1
@@ -1,4 +1,4 @@
-.TH "BST INIT" "1" "12-Feb-2019" "" "bst init Manual"
+.TH "BST INIT" "1" "13-Mar-2019" "" "bst init Manual"
.SH NAME
bst\-init \- Initialize a new BuildStream project
.SH SYNOPSIS
@@ -18,7 +18,7 @@ interactive session.
The project name to use
.TP
\fB\-\-format\-version\fP INTEGER
-The required format version (default: 21)
+The required format version (default: 23)
.TP
\fB\-\-element\-path\fP PATH
The subdirectory to store elements in (default: elements)
diff --git a/man/bst-shell.1 b/man/bst-shell.1
index d223d0d20..376cddb4b 100644
--- a/man/bst-shell.1
+++ b/man/bst-shell.1
@@ -1,4 +1,4 @@
-.TH "BST SHELL" "1" "12-Feb-2019" "" "bst shell Manual"
+.TH "BST SHELL" "1" "13-Mar-2019" "" "bst shell Manual"
.SH NAME
bst\-shell \- Shell into an element's sandbox environment
.SH SYNOPSIS
diff --git a/man/bst-show.1 b/man/bst-show.1
index 4fe0b71c2..54d02c3b6 100644
--- a/man/bst-show.1
+++ b/man/bst-show.1
@@ -1,4 +1,4 @@
-.TH "BST SHOW" "1" "12-Feb-2019" "" "bst show Manual"
+.TH "BST SHOW" "1" "13-Mar-2019" "" "bst show Manual"
.SH NAME
bst\-show \- Show elements in the pipeline
.SH SYNOPSIS
diff --git a/man/bst-source-checkout.1 b/man/bst-source-checkout.1
index f3101b679..aed31db7a 100644
--- a/man/bst-source-checkout.1
+++ b/man/bst-source-checkout.1
@@ -1,4 +1,4 @@
-.TH "BST SOURCE CHECKOUT" "1" "12-Feb-2019" "" "bst source checkout Manual"
+.TH "BST SOURCE CHECKOUT" "1" "13-Mar-2019" "" "bst source checkout Manual"
.SH NAME
bst\-source\-checkout \- Checkout sources for an element
.SH SYNOPSIS
diff --git a/man/bst-source-fetch.1 b/man/bst-source-fetch.1
index 0fb63a6d7..ddcd557e4 100644
--- a/man/bst-source-fetch.1
+++ b/man/bst-source-fetch.1
@@ -1,4 +1,4 @@
-.TH "BST SOURCE FETCH" "1" "12-Feb-2019" "" "bst source fetch Manual"
+.TH "BST SOURCE FETCH" "1" "13-Mar-2019" "" "bst source fetch Manual"
.SH NAME
bst\-source\-fetch \- Fetch sources in a pipeline
.SH SYNOPSIS
diff --git a/man/bst-source-track.1 b/man/bst-source-track.1
index 92f8cd310..9a24619a1 100644
--- a/man/bst-source-track.1
+++ b/man/bst-source-track.1
@@ -1,4 +1,4 @@
-.TH "BST SOURCE TRACK" "1" "12-Feb-2019" "" "bst source track Manual"
+.TH "BST SOURCE TRACK" "1" "13-Mar-2019" "" "bst source track Manual"
.SH NAME
bst\-source\-track \- Track new source references
.SH SYNOPSIS
diff --git a/man/bst-source.1 b/man/bst-source.1
index 897642541..4d396b3d0 100644
--- a/man/bst-source.1
+++ b/man/bst-source.1
@@ -1,4 +1,4 @@
-.TH "BST SOURCE" "1" "12-Feb-2019" "" "bst source Manual"
+.TH "BST SOURCE" "1" "13-Mar-2019" "" "bst source Manual"
.SH NAME
bst\-source \- Manipulate sources for an element
.SH SYNOPSIS
diff --git a/man/bst-workspace-close.1 b/man/bst-workspace-close.1
index 5c2659f63..0b43cdf22 100644
--- a/man/bst-workspace-close.1
+++ b/man/bst-workspace-close.1
@@ -1,4 +1,4 @@
-.TH "BST WORKSPACE CLOSE" "1" "12-Feb-2019" "" "bst workspace close Manual"
+.TH "BST WORKSPACE CLOSE" "1" "13-Mar-2019" "" "bst workspace close Manual"
.SH NAME
bst\-workspace\-close \- Close workspaces
.SH SYNOPSIS
diff --git a/man/bst-workspace-list.1 b/man/bst-workspace-list.1
index 6be37d631..f5252670d 100644
--- a/man/bst-workspace-list.1
+++ b/man/bst-workspace-list.1
@@ -1,4 +1,4 @@
-.TH "BST WORKSPACE LIST" "1" "12-Feb-2019" "" "bst workspace list Manual"
+.TH "BST WORKSPACE LIST" "1" "13-Mar-2019" "" "bst workspace list Manual"
.SH NAME
bst\-workspace\-list \- List open workspaces
.SH SYNOPSIS
diff --git a/man/bst-workspace-open.1 b/man/bst-workspace-open.1
index 4fd70fc15..4a1065d69 100644
--- a/man/bst-workspace-open.1
+++ b/man/bst-workspace-open.1
@@ -1,4 +1,4 @@
-.TH "BST WORKSPACE OPEN" "1" "12-Feb-2019" "" "bst workspace open Manual"
+.TH "BST WORKSPACE OPEN" "1" "13-Mar-2019" "" "bst workspace open Manual"
.SH NAME
bst\-workspace\-open \- Open a new workspace
.SH SYNOPSIS
diff --git a/man/bst-workspace-reset.1 b/man/bst-workspace-reset.1
index ad2fc707a..d37a7f1c2 100644
--- a/man/bst-workspace-reset.1
+++ b/man/bst-workspace-reset.1
@@ -1,4 +1,4 @@
-.TH "BST WORKSPACE RESET" "1" "12-Feb-2019" "" "bst workspace reset Manual"
+.TH "BST WORKSPACE RESET" "1" "13-Mar-2019" "" "bst workspace reset Manual"
.SH NAME
bst\-workspace\-reset \- Reset a workspace to its original state
.SH SYNOPSIS
diff --git a/man/bst-workspace.1 b/man/bst-workspace.1
index b90505e1a..337620584 100644
--- a/man/bst-workspace.1
+++ b/man/bst-workspace.1
@@ -1,4 +1,4 @@
-.TH "BST WORKSPACE" "1" "12-Feb-2019" "" "bst workspace Manual"
+.TH "BST WORKSPACE" "1" "13-Mar-2019" "" "bst workspace Manual"
.SH NAME
bst\-workspace \- Manipulate developer workspaces
.SH SYNOPSIS
diff --git a/man/bst.1 b/man/bst.1
index b1f6e9f93..ff5e79b6f 100644
--- a/man/bst.1
+++ b/man/bst.1
@@ -1,4 +1,4 @@
-.TH "BST" "1" "12-Feb-2019" "" "bst Manual"
+.TH "BST" "1" "13-Mar-2019" "" "bst Manual"
.SH NAME
bst \- Build and manipulate BuildStream projects...
.SH SYNOPSIS
@@ -67,6 +67,9 @@ The mirror to fetch from first, before attempting other mirrors
.TP
\fB\-\-pull\-buildtrees\fP
Include an element's build tree when pulling remote element artifacts
+.TP
+\fB\-\-cache\-buildtrees\fP [always|failure|never]
+Cache artifact build tree content on creation
.SH COMMANDS
.PP
\fBhelp\fP
diff --git a/tests/frontend/artifact.py b/tests/frontend/artifact.py
index c8301c529..b6f8fb91d 100644
--- a/tests/frontend/artifact.py
+++ b/tests/frontend/artifact.py
@@ -22,6 +22,7 @@ import os
import pytest
from buildstream.plugintestutils import cli
+from tests.testutils import create_artifact_share
# Project directory
@@ -62,3 +63,144 @@ def test_artifact_log(cli, datafiles):
assert result.exit_code == 0
# The artifact is cached under both a strong key and a weak key
assert (log + log) == result.output
+
+
+# Test that we can delete the artifact of the element which corresponds
+# to the current project state
+@pytest.mark.datafiles(DATA_DIR)
+def test_artifact_delete_element(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ element = 'target.bst'
+
+ # Build the element and ensure it's cached
+ result = cli.run(project=project, args=['build', element])
+ result.assert_success()
+ assert cli.get_element_state(project, element) == 'cached'
+
+ result = cli.run(project=project, args=['artifact', 'delete', element])
+ result.assert_success()
+ assert cli.get_element_state(project, element) != 'cached'
+
+
+# Test that we can delete an artifact by specifying its ref.
+@pytest.mark.datafiles(DATA_DIR)
+def test_artifact_delete_artifact(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ element = 'target.bst'
+
+ # Configure a local cache
+ local_cache = os.path.join(str(tmpdir), 'artifacts')
+ cli.configure({'cachedir': local_cache})
+
+ # First build an element so that we can find its artifact
+ result = cli.run(project=project, args=['build', element])
+ result.assert_success()
+
+ # Obtain the artifact ref
+ cache_key = cli.get_element_key(project, element)
+ artifact = os.path.join('test', os.path.splitext(element)[0], cache_key)
+
+ # Explicitly check that the ARTIFACT exists in the cache
+ assert os.path.exists(os.path.join(local_cache, 'cas', 'refs', 'heads', artifact))
+
+ # Delete the artifact
+ result = cli.run(project=project, args=['artifact', 'delete', artifact])
+ result.assert_success()
+
+ # Check that the ARTIFACT is no longer in the cache
+ assert not os.path.exists(os.path.join(local_cache, 'cas', 'refs', 'heads', artifact))
+
+
+# Test the `bst artifact delete` command with multiple, different arguments.
+@pytest.mark.datafiles(DATA_DIR)
+def test_artifact_delete_element_and_artifact(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ element = 'target.bst'
+ dep = 'compose-all.bst'
+
+ # Configure a local cache
+ local_cache = os.path.join(str(tmpdir), 'artifacts')
+ cli.configure({'cachedir': local_cache})
+
+ # First build an element so that we can find its artifact
+ result = cli.run(project=project, args=['build', element])
+ result.assert_success()
+ assert cli.get_element_state(project, element) == 'cached'
+ assert cli.get_element_state(project, dep) == 'cached'
+
+ # Obtain the artifact ref
+ cache_key = cli.get_element_key(project, element)
+ artifact = os.path.join('test', os.path.splitext(element)[0], cache_key)
+
+ # Explicitly check that the ARTIFACT exists in the cache
+ assert os.path.exists(os.path.join(local_cache, 'cas', 'refs', 'heads', artifact))
+
+ # Delete the artifact
+ result = cli.run(project=project, args=['artifact', 'delete', artifact, dep])
+ result.assert_success()
+
+ # Check that the ARTIFACT is no longer in the cache
+ assert not os.path.exists(os.path.join(local_cache, 'cas', 'refs', 'heads', artifact))
+
+ # Check that the dependency ELEMENT is no longer cached
+ assert cli.get_element_state(project, dep) != 'cached'
+
+
+# Test that we receive the appropriate stderr when we try to delete an artifact
+# that is not present in the cache.
+@pytest.mark.datafiles(DATA_DIR)
+def test_artifact_delete_unbuilt_artifact(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ element = 'target.bst'
+
+ # delete it, just in case it's there
+ _ = cli.run(project=project, args=['artifact', 'delete', element])
+
+ # Ensure the element is not cached
+ assert cli.get_element_state(project, element) != 'cached'
+
+ # Now try and remove it again (now we know its not there)
+ result = cli.run(project=project, args=['artifact', 'delete', element])
+
+ cache_key = cli.get_element_key(project, element)
+ artifact = os.path.join('test', os.path.splitext(element)[0], cache_key)
+ expected_err = "WARNING Could not find ref '{}'".format(artifact)
+ assert expected_err in result.stderr
+
+
+# Test that an artifact pulled from it's remote cache (without it's buildtree) will not
+# throw an Exception when trying to prune the cache.
+@pytest.mark.datafiles(DATA_DIR)
+def test_artifact_delete_pulled_artifact_without_buildtree(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ element = 'target.bst'
+
+ # Set up remote and local shares
+ local_cache = os.path.join(str(tmpdir), 'artifacts')
+ with create_artifact_share(os.path.join(str(tmpdir), 'remote')) as remote:
+ cli.configure({
+ 'artifacts': {'url': remote.repo, 'push': True},
+ 'cachedir': local_cache,
+ })
+
+ # Build the element
+ result = cli.run(project=project, args=['build', element])
+ result.assert_success()
+
+ # Make sure it's in the share
+ cache_key = cli.get_element_key(project, element)
+ assert remote.has_artifact('test', element, cache_key)
+
+ # Delete and then pull the artifact (without its buildtree)
+ result = cli.run(project=project, args=['artifact', 'delete', element])
+ result.assert_success()
+ assert cli.get_element_state(project, element) != 'cached'
+ result = cli.run(project=project, args=['artifact', 'pull', element])
+ result.assert_success()
+ assert cli.get_element_state(project, element) == 'cached'
+
+ # Now delete it again (it should have been pulled without the buildtree, but
+ # a digest of the buildtree is pointed to in the artifact's metadata
+ result = cli.run(project=project, args=['artifact', 'delete', element])
+ result.assert_success()
+ assert cli.get_element_state(project, element) != 'cached'
diff --git a/tests/frontend/completions.py b/tests/frontend/completions.py
index 1f29fdae6..7810a06d5 100644
--- a/tests/frontend/completions.py
+++ b/tests/frontend/completions.py
@@ -57,6 +57,7 @@ SOURCE_COMMANDS = [
ARTIFACT_COMMANDS = [
'checkout ',
+ 'delete ',
'push ',
'pull ',
'log ',