summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbst-marge-bot <marge-bot@buildstream.build>2019-08-16 15:01:19 +0000
committerbst-marge-bot <marge-bot@buildstream.build>2019-08-16 15:01:19 +0000
commit1f7020266ee794643e486e4a15fb83916f69fcfc (patch)
tree6dff2149af44292a83685821a35218d0acda90c1
parentc627b2203cad48881dbbab822b59464de5c7278f (diff)
parenta654c659985db4916d06b15d916e553305f3cb41 (diff)
downloadbuildstream-1f7020266ee794643e486e4a15fb83916f69fcfc.tar.gz
Merge branch 'becky/artifact_list_contents' into 'master'
Addition of bst artifact list-contents See merge request BuildStream/buildstream!1529
-rw-r--r--NEWS4
-rw-r--r--src/buildstream/_frontend/cli.py19
-rw-r--r--src/buildstream/_frontend/widget.py33
-rw-r--r--src/buildstream/_stream.py24
-rw-r--r--src/buildstream/element.py10
-rw-r--r--tests/frontend/artifact.py67
-rw-r--r--tests/frontend/completions.py1
7 files changed, 158 insertions, 0 deletions
diff --git a/NEWS b/NEWS
index 6c93dde94..88cc8601a 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@
buildstream 1.3.1
=================
+ o Added `bst artifact list-contents` subcommand which can display the names
+ of files in artifacts in your artifact cache, either by element name
+ or by direct artifact reference.
+
o BREAKING CHANGE: Reverted the default behaviour of junctions. Subproject
elements will no longer interact with the parent project's remote (by
default). To enable this behaviour, a new "cache-junction-elements" boolean
diff --git a/src/buildstream/_frontend/cli.py b/src/buildstream/_frontend/cli.py
index 220d10477..5a585b276 100644
--- a/src/buildstream/_frontend/cli.py
+++ b/src/buildstream/_frontend/cli.py
@@ -1203,6 +1203,25 @@ def artifact_log(app, artifacts):
click.echo_via_pager(data)
+################################################################
+# Artifact List-Contents Command #
+################################################################
+@artifact.command(name='list-contents', short_help="List the contents of an artifact")
+@click.argument('artifacts', type=click.Path(), nargs=-1)
+@click.pass_obj
+def artifact_list_contents(app, artifacts):
+ """List the contents of an artifact.
+
+ Note that 'artifacts' can be element names, which must end in '.bst',
+ or artifact references, which must be in the format `<project_name>/<element>/<key>`.
+
+ """
+ with app.initialized():
+ elements_to_files = app.stream.artifact_list_contents(artifacts)
+ click.echo(app.logger._pretty_print_dictionary(elements_to_files))
+ sys.exit(0)
+
+
###################################################################
# Artifact Delete Command #
###################################################################
diff --git a/src/buildstream/_frontend/widget.py b/src/buildstream/_frontend/widget.py
index 31f69a539..20f5d1767 100644
--- a/src/buildstream/_frontend/widget.py
+++ b/src/buildstream/_frontend/widget.py
@@ -795,3 +795,36 @@ class LogLine(Widget):
text += '\n'
return text
+
+ # _pretty_print_dictionary()
+ #
+ # Formats a dictionary so it can be easily read by the user
+ #
+ # Args:
+ # values: A dictionary
+ # style_value: Whether to use the content profile for the values
+ #
+ # Returns:
+ # (str): The formatted values
+ #
+ def _pretty_print_dictionary(self, values, style_value=True):
+ text = ''
+ max_key_len = 0
+ max_key_len = max(len(key) for key in values.keys())
+
+ for key, value in values.items():
+ if isinstance(value, str) and '\n' in value:
+ text += self.format_profile.fmt(" {}:".format(key))
+ text += textwrap.indent(value, self._indent)
+ continue
+
+ text += self.format_profile.fmt(" {}:{}".format(key, ' ' * (max_key_len - len(key))))
+
+ value_list = "\n\t" + "\n\t".join(value)
+ if style_value:
+ text += self.content_profile.fmt(value_list)
+ else:
+ text += value_list
+ text += '\n'
+
+ return text
diff --git a/src/buildstream/_stream.py b/src/buildstream/_stream.py
index c54fee1a7..453670ad1 100644
--- a/src/buildstream/_stream.py
+++ b/src/buildstream/_stream.py
@@ -632,6 +632,30 @@ class Stream():
return log_file_paths
+ # artifact_list_contents()
+ #
+ # Show a list of content of an artifact
+ #
+ # Args:
+ # targets (str): Targets to view the contents of
+ #
+ # Returns:
+ # elements_to_files (list): A list of tuples of the artifact name and it's contents
+ #
+ def artifact_list_contents(self, targets):
+ # Return list of Element and/or ArtifactElement objects
+ target_objects = self.load_selection(targets, selection=PipelineSelection.NONE, load_refs=True)
+
+ elements_to_files = {}
+ for obj in target_objects:
+ if isinstance(obj, ArtifactElement):
+ obj.name = obj.get_artifact_name()
+ files = obj._get_artifact_relative_file_paths()
+ if files == []:
+ files = ["This element has no associated artifacts"]
+ elements_to_files[obj.name] = files
+ return elements_to_files
+
# artifact_delete()
#
# Remove artifacts from the local cache
diff --git a/src/buildstream/element.py b/src/buildstream/element.py
index bc8cde311..f28f482ba 100644
--- a/src/buildstream/element.py
+++ b/src/buildstream/element.py
@@ -2360,6 +2360,16 @@ class Element(Plugin):
factory = self._get_project().config.element_factory
return factory, self.__meta_kind, state
+ # _get_artifact_relative_path_files()
+ #
+ # Gets the file paths in the artifact and return them in a list
+ #
+ # Returns:
+ # (list): A list of the file paths in the artifact
+ def _get_artifact_relative_file_paths(self):
+ casbd = self.__artifact.get_files()
+ return [f for f in casbd.list_relative_paths()]
+
#############################################################
# Private Local Methods #
#############################################################
diff --git a/tests/frontend/artifact.py b/tests/frontend/artifact.py
index 177be8c30..fed0b1aaa 100644
--- a/tests/frontend/artifact.py
+++ b/tests/frontend/artifact.py
@@ -71,6 +71,73 @@ def test_artifact_log(cli, datafiles):
assert (log + log) == result.output
+@pytest.mark.datafiles(DATA_DIR)
+def test_artifact_list_exact_contents_element(cli, datafiles):
+ project = str(datafiles)
+
+ # Ensure we have an artifact to read
+ result = cli.run(project=project, args=['build', 'import-bin.bst'])
+ assert result.exit_code == 0
+
+ # List the contents via the element name
+ result = cli.run(project=project, args=['artifact', 'list-contents', 'import-bin.bst'])
+ assert result.exit_code == 0
+ expected_output = ("import-bin.bst:\n"
+ "\tusr\n"
+ "\tusr/bin\n"
+ "\tusr/bin/hello\n\n")
+ assert expected_output in result.output
+
+
+@pytest.mark.datafiles(DATA_DIR)
+def test_artifact_list_exact_contents_ref(cli, datafiles):
+ project = str(datafiles)
+
+ # Get the cache key of our test element
+ key = cli.get_element_key(project, 'import-bin.bst')
+
+ # Ensure we have an artifact to read
+ result = cli.run(project=project, args=['build', 'import-bin.bst'])
+ assert result.exit_code == 0
+
+ # List the contents via the key
+ result = cli.run(project=project, args=['artifact', 'list-contents', 'test/import-bin/' + key])
+ assert result.exit_code == 0
+
+ expected_output = ("test/import-bin/" + key + ":\n"
+ "\tusr\n"
+ "\tusr/bin\n"
+ "\tusr/bin/hello\n\n")
+ assert expected_output in result.output
+
+
+@pytest.mark.datafiles(DATA_DIR)
+def test_artifact_list_exact_contents_glob(cli, datafiles):
+ project = str(datafiles)
+
+ # Ensure we have an artifact to read
+ result = cli.run(project=project, args=['build', 'target.bst'])
+ assert result.exit_code == 0
+
+ # List the contents via glob
+ result = cli.run(project=project, args=['artifact', 'list-contents', 'test/*'])
+ assert result.exit_code == 0
+
+ # get the cahe keys for each element in the glob
+ import_bin_key = cli.get_element_key(project, 'import-bin.bst')
+ import_dev_key = cli.get_element_key(project, 'import-dev.bst')
+ compose_all_key = cli.get_element_key(project, 'compose-all.bst')
+ target_key = cli.get_element_key(project, 'target.bst')
+
+ expected_artifacts = ["test/import-bin/" + import_bin_key,
+ "test/import-dev/" + import_dev_key,
+ "test/compose-all/" + compose_all_key,
+ "test/target/" + target_key]
+
+ for artifact in expected_artifacts:
+ assert artifact in result.output
+
+
# Test that we can delete the artifact of the element which corresponds
# to the current project state
@pytest.mark.datafiles(DATA_DIR)
diff --git a/tests/frontend/completions.py b/tests/frontend/completions.py
index 3619242ac..e9fa25b73 100644
--- a/tests/frontend/completions.py
+++ b/tests/frontend/completions.py
@@ -65,6 +65,7 @@ ARTIFACT_COMMANDS = [
'push ',
'pull ',
'log ',
+ 'list-contents ',
]
WORKSPACE_COMMANDS = [