summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRebecca Grayson <becky.grayson1@hotmail.co.uk>2019-08-20 17:07:29 +0100
committerDaniel Silverstone <daniel.silverstone@codethink.co.uk>2019-09-02 10:02:24 +0100
commitd4011901434b53259b2f30b90b7ab045474596f2 (patch)
tree0c0a7e8ec87d6e0e89e30ae861cba445a24ba220
parent61468553543589056f70297cb8779200e398548a (diff)
downloadbuildstream-becky/list_contents_long_option.tar.gz
Addition of --long option to list-contents:becky/list_contents_long_option
--long or -l will provide the user with extra information about the contents of the artifacts, including permission mode, file type, size and name. In order for this to work, the way in which list-contents works has been modified. A test and NEWS entry have also been added within this commit
-rw-r--r--NEWS3
-rw-r--r--src/buildstream/_frontend/cli.py12
-rw-r--r--src/buildstream/_frontend/widget.py50
-rw-r--r--src/buildstream/_stream.py11
-rw-r--r--src/buildstream/element.py11
-rw-r--r--src/buildstream/storage/_casbaseddirectory.py40
-rw-r--r--tests/frontend/artifact_list_contents.py42
7 files changed, 147 insertions, 22 deletions
diff --git a/NEWS b/NEWS
index 5006dadce..cde99c7be 100644
--- a/NEWS
+++ b/NEWS
@@ -20,7 +20,8 @@ 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.
+ or by direct artifact reference. --long option can be used to display more
+ information; file type and size.
o BREAKING CHANGE: Reverted the default behaviour of junctions. Subproject
elements will no longer interact with the parent project's remote (by
diff --git a/src/buildstream/_frontend/cli.py b/src/buildstream/_frontend/cli.py
index c1b06b2d1..6a9f11c70 100644
--- a/src/buildstream/_frontend/cli.py
+++ b/src/buildstream/_frontend/cli.py
@@ -1265,9 +1265,11 @@ def artifact_log(app, artifacts, out):
# Artifact List-Contents Command #
################################################################
@artifact.command(name='list-contents', short_help="List the contents of an artifact")
+@click.option('--long', '-l', 'long_', default=False, is_flag=True,
+ help="Provides more information about the contents of the artifact.")
@click.argument('artifacts', type=click.Path(), nargs=-1)
@click.pass_obj
-def artifact_list_contents(app, artifacts):
+def artifact_list_contents(app, artifacts, long_):
"""List the contents of an artifact.
Note that 'artifacts' can be element names, which must end in '.bst',
@@ -1276,8 +1278,12 @@ def artifact_list_contents(app, artifacts):
"""
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)
+ if not elements_to_files:
+ click.echo("None of the specified artifacts are cached.", err=True)
+ sys.exit(1)
+ else:
+ click.echo(app.logger._pretty_print_dictionary(elements_to_files, long_))
+ sys.exit(0)
###################################################################
diff --git a/src/buildstream/_frontend/widget.py b/src/buildstream/_frontend/widget.py
index e8299868c..181ee7d2e 100644
--- a/src/buildstream/_frontend/widget.py
+++ b/src/buildstream/_frontend/widget.py
@@ -31,7 +31,7 @@ from .. import Consistency, Scope
from .. import __version__ as bst_version
from .._exceptions import ImplError
from .._message import MessageType
-
+from ..storage.directory import _FileType
# These messages are printed a bit differently
ERROR_MESSAGES = [MessageType.FAIL, MessageType.ERROR, MessageType.BUG]
@@ -802,14 +802,18 @@ class LogLine(Widget):
# Args:
# values: A dictionary
# style_value: Whether to use the content profile for the values
+ # list_long (Bool): whether to display verbose information about artifacts
#
# Returns:
# (str): The formatted values
#
- def _pretty_print_dictionary(self, values, style_value=True):
+ def _pretty_print_dictionary(self, values, long_=False, style_value=True):
text = ''
max_key_len = 0
- max_key_len = max(len(key) for key in values.keys())
+ try:
+ max_key_len = max(len(key) for key in values.keys())
+ except ValueError:
+ text = ''
for key, value in values.items():
if isinstance(value, str) and '\n' in value:
@@ -819,8 +823,14 @@ class LogLine(Widget):
text += self.format_profile.fmt(" {}:{}".format(key, ' ' * (max_key_len - len(key))))
- value_list = "\n\t" + "\n\t".join(value)
- if style_value:
+ value_list = "\n\t" + "\n\t".join((self._get_filestats(v, list_long=long_) for v in value))
+ if value == []:
+ message = "\n\tThis element has no associated artifacts"
+ if style_value:
+ text += self.content_profile.fmt(message)
+ else:
+ text += message
+ elif style_value:
text += self.content_profile.fmt(value_list)
else:
text += value_list
@@ -864,3 +874,33 @@ class LogLine(Widget):
report += line + '\n'
return report
+
+ # _get_filestats()
+ #
+ # Gets the necessary information from a dictionary
+ #
+ # Args:
+ # entry: A dictionary of info about the element
+ # list_long (Bool): whether to display verbose information about artifacts
+ #
+ # Returns:
+ # (str): The information about the element
+ #
+ def _get_filestats(self, entry, list_long=False):
+ if list_long:
+ size = str(entry["size"])
+ # Support files up to 99G, meaning maximum characters is 11
+ max_v_len = 11
+ if entry["type"] == _FileType.DIRECTORY:
+ return "drwxr-xr-x dir {}".format(entry["size"]) +\
+ "{} ".format(' ' * (max_v_len - len(size))) + "{}".format(entry["name"])
+ elif entry["type"] == _FileType.SYMLINK:
+ return "lrwxrwxrwx link {}".format(entry["size"]) +\
+ "{} ".format(' ' * (max_v_len - len(size))) + "{} -> {}".format(entry["name"], entry["target"])
+ elif entry["executable"]:
+ return "-rwxr-xr-x exe {}".format(entry["size"]) +\
+ "{} ".format(' ' * (max_v_len - len(size))) + "{}".format(entry["name"])
+ else:
+ return "-rw-r--r-- reg {}".format(entry["size"]) +\
+ "{} ".format(' ' * (max_v_len - len(size))) + "{}".format(entry["name"])
+ return entry["name"]
diff --git a/src/buildstream/_stream.py b/src/buildstream/_stream.py
index 95b306b47..9ff93fdc3 100644
--- a/src/buildstream/_stream.py
+++ b/src/buildstream/_stream.py
@@ -678,11 +678,14 @@ class Stream():
elements_to_files = {}
for obj in target_objects:
+ ref = obj.get_artifact_name()
+ if not obj._cached():
+ self._message(MessageType.WARN, "{} is not cached".format(ref))
+ obj.name = {ref: "No artifact cached"}
+ continue
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"]
+ obj.name = ref
+ files = [f for f in obj._walk_artifact_files()]
elements_to_files[obj.name] = files
return elements_to_files
diff --git a/src/buildstream/element.py b/src/buildstream/element.py
index 633e29c88..950334695 100644
--- a/src/buildstream/element.py
+++ b/src/buildstream/element.py
@@ -2361,15 +2361,8 @@ 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()]
+ def _walk_artifact_files(self):
+ yield from self.__artifact.get_files().walk()
# _get_artifact()
#
diff --git a/src/buildstream/storage/_casbaseddirectory.py b/src/buildstream/storage/_casbaseddirectory.py
index 424b7ef63..66b7a7259 100644
--- a/src/buildstream/storage/_casbaseddirectory.py
+++ b/src/buildstream/storage/_casbaseddirectory.py
@@ -574,6 +574,46 @@ class CasBasedDirectory(Directory):
subdir = v.get_directory(self)
yield from subdir._list_prefixed_relative_paths(prefix=os.path.join(prefix, k))
+ def walk(self):
+ """Provide a list of dictionaries containing information about the files.
+
+ Yields:
+ info (dict) - a dictionary containing name, type and size of the files.
+
+ """
+ yield from self._walk()
+
+ def _walk(self, prefix=""):
+ """ Walk through the files, collecting the required data
+
+ Arguments:
+ prefix (str): an optional prefix to the relative paths, this is
+ also emitted by itself.
+
+ Yields:
+ info (dict) - a dictionary containing name, type and size of the files.
+
+ """
+ for leaf in sorted(self.index.keys()):
+ entry = self.index[leaf]
+ info = {
+ "name": os.path.join(prefix, leaf),
+ "type": entry.type
+ }
+ if entry.type == _FileType.REGULAR_FILE:
+ info["executable"] = entry.is_executable
+ info["size"] = self.get_size()
+ elif entry.type == _FileType.SYMLINK:
+ info["target"] = entry.target
+ info["size"] = len(entry.target)
+ if entry.type == _FileType.DIRECTORY:
+ directory = entry.get_directory(self)
+ info["size"] = len(directory.index)
+ yield info
+ yield from directory._walk(os.path.join(prefix, leaf))
+ else:
+ yield info
+
def get_size(self):
digest = self._get_digest()
total = digest.size_bytes
diff --git a/tests/frontend/artifact_list_contents.py b/tests/frontend/artifact_list_contents.py
index 5bb08e3fa..626eb3fa7 100644
--- a/tests/frontend/artifact_list_contents.py
+++ b/tests/frontend/artifact_list_contents.py
@@ -96,3 +96,45 @@ def test_artifact_list_exact_contents_glob(cli, datafiles):
for artifact in expected_artifacts:
assert artifact in result.output
+
+
+@pytest.mark.datafiles(DATA_DIR)
+def test_artifact_list_exact_contents_element_long(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', '--long', 'import-bin.bst'])
+ assert result.exit_code == 0
+ expected_output = ("import-bin.bst:\n"
+ "\tdrwxr-xr-x dir 1 usr\n"
+ "\tdrwxr-xr-x dir 1 usr/bin\n"
+ "\t-rw-r--r-- reg 107 usr/bin/hello\n\n")
+
+ assert expected_output in result.output
+
+
+@pytest.mark.datafiles(DATA_DIR)
+def test_artifact_list_exact_contents_ref_long(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', '-l', 'test/import-bin/' + key])
+ assert result.exit_code == 0
+
+ expected_output = (" test/import-bin/" + key + ":\n"
+ "\tdrwxr-xr-x dir 1 usr\n"
+ "\tdrwxr-xr-x dir 1 usr/bin\n"
+ "\t-rw-r--r-- reg 107 usr/bin/hello\n\n")
+
+ assert expected_output in result.output