From 20a8f9e812c9f7dc2ad9c44325afcc1732c1de52 Mon Sep 17 00:00:00 2001 From: Rebecca Grayson Date: Tue, 20 Aug 2019 09:55:54 +0100 Subject: Addition of --out option to bst artifact log: A --out option has been added, allowing an artifact log to be written to a logfile. This is particularly useful when more than one artifact's log is wanting to be read; It will write a file for each log. A test and NEWS entry have also been added. --- src/buildstream/_frontend/cli.py | 43 ++++++++++++++++++++++++++++++++-------- src/buildstream/_stream.py | 6 +++--- tests/frontend/artifact.py | 35 ++++++++++++++++++++++++++++++-- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/buildstream/_frontend/cli.py b/src/buildstream/_frontend/cli.py index 661e39575..c1b06b2d1 100644 --- a/src/buildstream/_frontend/cli.py +++ b/src/buildstream/_frontend/cli.py @@ -4,6 +4,7 @@ import sys from functools import partial import fcntl +import shutil import click from .. import _yaml from .._exceptions import BstError, LoadError, AppError @@ -1219,19 +1220,45 @@ def artifact_push(app, elements, deps, remote): # Artifact Log Command # ################################################################ @artifact.command(name='log', short_help="Show logs of artifacts") +@click.option('--out', + type=click.Path(file_okay=True, writable=True), + help="Output logs to individual files in the specified path. If absent, logs are written to stdout.") @click.argument('artifacts', type=click.Path(), nargs=-1) @click.pass_obj -def artifact_log(app, artifacts): +def artifact_log(app, artifacts, out): """Show build logs of artifacts""" - with app.initialized(): - log_file_paths = app.stream.artifact_log(artifacts) - - for log in log_file_paths: - with open(log) as f: - data = f.read() + artifact_logs = app.stream.artifact_log(artifacts) + + if not out: + try: + for log in list(artifact_logs.values()): + with open(log[0], 'r') as f: + data = f.read() + click.echo_via_pager(data) + except (OSError, FileNotFoundError): + click.echo("Error: file cannot be opened", err=True) + sys.exit(1) - click.echo_via_pager(data) + else: + try: + os.mkdir(out) + except FileExistsError: + click.echo("Error: {} already exists".format(out), err=True) + sys.exit(1) + + for name, log_files in artifact_logs.items(): + if len(log_files) > 1: + os.mkdir(name) + for log in log_files: + dest = os.path.join(out, name, log) + shutil.copy(log, dest) + # make a dir and write in log files + else: + log_name = os.path.splitext(name)[0] + '.log' + dest = os.path.join(out, log_name) + shutil.copy(log_files[0], dest) + # write a log file ################################################################ diff --git a/src/buildstream/_stream.py b/src/buildstream/_stream.py index 554debaae..95b306b47 100644 --- a/src/buildstream/_stream.py +++ b/src/buildstream/_stream.py @@ -648,7 +648,7 @@ class Stream(): # Return list of Element and/or ArtifactElement objects target_objects = self.load_selection(targets, selection=PipelineSelection.NONE, load_refs=True) - log_file_paths = [] + artifact_logs = {} for obj in target_objects: ref = obj.get_artifact_name() if not obj._cached(): @@ -658,9 +658,9 @@ class Stream(): self._message(MessageType.WARN, "{} is cached without log files".format(ref)) continue - log_file_paths.extend(obj.get_logs()) + artifact_logs[obj.name] = obj.get_logs() - return log_file_paths + return artifact_logs # artifact_list_contents() # diff --git a/tests/frontend/artifact.py b/tests/frontend/artifact.py index cbc9ab022..9ad03909e 100644 --- a/tests/frontend/artifact.py +++ b/tests/frontend/artifact.py @@ -69,8 +69,7 @@ def test_artifact_log(cli, datafiles): # Read the log via glob result = cli.run(project=project, args=['artifact', 'log', 'test/target/*']) assert result.exit_code == 0 - # The artifact is cached under both a strong key and a weak key - assert (log + log) == result.output + assert log == result.output @pytest.mark.datafiles(DATA_DIR) @@ -140,6 +139,38 @@ def test_artifact_list_exact_contents_glob(cli, datafiles): assert artifact in result.output +@pytest.mark.datafiles(DATA_DIR) +def test_artifact_log_files(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 + + logfiles = os.path.join(project, "logfiles") + target = os.path.join(project, logfiles, "target.log") + import_bin = os.path.join(project, logfiles, "import-bin.log") + # Ensure the logfile doesn't exist before the command is run + assert not os.path.exists(logfiles) + assert not os.path.exists(target) + assert not os.path.exists(import_bin) + + # Run the command and ensure the file now exists + result = cli.run(project=project, args=['artifact', 'log', '--out', logfiles, 'target.bst', 'import-bin.bst']) + assert result.exit_code == 0 + assert os.path.exists(logfiles) + assert os.path.exists(target) + assert os.path.exists(import_bin) + + # Ensure the file contains the logs by checking for the LOG line + with open(target, 'r') as f: + data = f.read() + assert "LOG target.bst" in data + with open(import_bin, 'r') as f: + data = f.read() + assert "LOG import-bin.bst" in data + + # Test that we can delete the artifact of the element which corresponds # to the current project state @pytest.mark.datafiles(DATA_DIR) -- cgit v1.2.1