diff options
author | bst-marge-bot <marge-bot@buildstream.build> | 2019-08-16 15:01:19 +0000 |
---|---|---|
committer | bst-marge-bot <marge-bot@buildstream.build> | 2019-08-16 15:01:19 +0000 |
commit | 1f7020266ee794643e486e4a15fb83916f69fcfc (patch) | |
tree | 6dff2149af44292a83685821a35218d0acda90c1 | |
parent | c627b2203cad48881dbbab822b59464de5c7278f (diff) | |
parent | a654c659985db4916d06b15d916e553305f3cb41 (diff) | |
download | buildstream-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-- | NEWS | 4 | ||||
-rw-r--r-- | src/buildstream/_frontend/cli.py | 19 | ||||
-rw-r--r-- | src/buildstream/_frontend/widget.py | 33 | ||||
-rw-r--r-- | src/buildstream/_stream.py | 24 | ||||
-rw-r--r-- | src/buildstream/element.py | 10 | ||||
-rw-r--r-- | tests/frontend/artifact.py | 67 | ||||
-rw-r--r-- | tests/frontend/completions.py | 1 |
7 files changed, 158 insertions, 0 deletions
@@ -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 = [ |