summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRebecca Grayson <becky.grayson1@hotmail.co.uk>2019-08-08 10:48:52 +0100
committerRebecca Grayson <becky.grayson1@hotmail.co.uk>2019-08-16 11:52:25 +0100
commit6a5529504db7e28c7af12ddf28d5ac9200334eee (patch)
tree9ce233aace35db5bc71e4e30a18947195fbda297
parent2d670f1963f83ffbf146e90500b517e77b24db62 (diff)
downloadbuildstream-becky/artifact_list_contents.tar.gz
Addition of bst artifact list-contents:becky/artifact_list_contents
this commit introduces the bst artifact list-contents command. When used it provides the user with a list of the contents within the artifact. Tests and a NEWS entry have also been added for the command.
-rw-r--r--NEWS5
-rw-r--r--src/buildstream/_frontend/cli.py24
-rw-r--r--src/buildstream/_frontend/widget.py36
-rw-r--r--src/buildstream/_stream.py24
-rw-r--r--src/buildstream/element.py12
-rw-r--r--tests/frontend/artifact.py67
-rw-r--r--tests/frontend/completions.py1
7 files changed, 168 insertions, 1 deletions
diff --git a/NEWS b/NEWS
index 6c93dde94..99d4cb7fc 100644
--- a/NEWS
+++ b/NEWS
@@ -46,7 +46,6 @@ buildstream 1.3.1
to this, `--tar` is no longer a flag, it is a mutually incompatible option
to `--directory`. For example, `bst artifact checkout foo.bst --tar foo.tar.gz`.
-
o Added `bst artifact log` subcommand for viewing build logs.
o BREAKING CHANGE: The bst source-bundle command has been removed. The
@@ -182,6 +181,10 @@ buildstream 1.3.1
o BREAKING CHANGE: Overlap whitelists now require absolute paths. This allows
use of variables such as %{prefix} and matches the documentation.
+ 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.
+
=================
buildstream 1.1.5
diff --git a/src/buildstream/_frontend/cli.py b/src/buildstream/_frontend/cli.py
index 276f81a6a..36383b3dd 100644
--- a/src/buildstream/_frontend/cli.py
+++ b/src/buildstream/_frontend/cli.py
@@ -1218,6 +1218,30 @@ 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>`.
+
+ """
+ # Note that the backticks in the above docstring are important for the
+ # generated docs. When sphinx is generating rst output from the help output
+ # of this command, the asterisks will be interpreted as emphasis tokens if
+ # they are not somehow escaped.
+
+ 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 fbde249a9..b0472bd45 100644
--- a/src/buildstream/_frontend/widget.py
+++ b/src/buildstream/_frontend/widget.py
@@ -801,3 +801,39 @@ 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
+ for key, value in values.items():
+ max_key_len = max(len(key), max_key_len)
+
+ 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 = ''
+ for item in value:
+ value_list += "\n\t{}".format(item)
+ 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 cbd635af7..ad5bb89f7 100644
--- a/src/buildstream/_stream.py
+++ b/src/buildstream/_stream.py
@@ -617,6 +617,30 @@ class Stream():
return logsdirs
+ # artifact_list_contents()
+ #
+ # Show a list of contents 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 21c38bc1a..9aea442c3 100644
--- a/src/buildstream/element.py
+++ b/src/buildstream/element.py
@@ -925,6 +925,18 @@ class Element(Plugin):
self.__batch_prepare_assemble_flags = flags
self.__batch_prepare_assemble_collect = collect
+ def get_artifact_relative_file_paths(self):
+ """ Gets the file paths in the artifact and return them in a list
+
+ Args:
+ targets (str): The name of the artifact to get files of
+
+ Returns:
+ (list): A list of the file paths in the artifact
+ """
+ casbd = self.__artifact.get_files()
+ return [f for f in casbd.list_relative_paths()]
+
#############################################################
# Private Methods used in BuildStream #
#############################################################
diff --git a/tests/frontend/artifact.py b/tests/frontend/artifact.py
index eb187a168..97dea9a50 100644
--- a/tests/frontend/artifact.py
+++ b/tests/frontend/artifact.py
@@ -68,6 +68,73 @@ def test_artifact_log(cli, datafiles):
assert (log + log) == result.output
+@pytest.mark.datafiles(DATA_DIR)
+def test_artifact_list_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_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_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 = [