[Notes] [Git][BuildStream/buildstream][tlater/message-lines] 9 commits: cli.py: add a hint about '--' to 'bst shell' help



Title: GitLab

Tristan Van Berkom pushed to branch tlater/message-lines at BuildStream / buildstream

Commits:

7 changed files:

Changes:

  • buildstream/_cas/cascache.py
    ... ... @@ -21,7 +21,7 @@ import hashlib
    21 21
     import itertools
    
    22 22
     import os
    
    23 23
     import stat
    
    24
    -import tempfile
    
    24
    +import errno
    
    25 25
     import uuid
    
    26 26
     import contextlib
    
    27 27
     
    
    ... ... @@ -129,7 +129,7 @@ class CASCache():
    129 129
                 else:
    
    130 130
                     return dest
    
    131 131
     
    
    132
    -        with tempfile.TemporaryDirectory(prefix='tmp', dir=self.tmpdir) as tmpdir:
    
    132
    +        with utils._tempdir(prefix='tmp', dir=self.tmpdir) as tmpdir:
    
    133 133
                 checkoutdir = os.path.join(tmpdir, ref)
    
    134 134
                 self._checkout(checkoutdir, tree)
    
    135 135
     
    
    ... ... @@ -374,7 +374,7 @@ class CASCache():
    374 374
                         for chunk in iter(lambda: tmp.read(4096), b""):
    
    375 375
                             h.update(chunk)
    
    376 376
                     else:
    
    377
    -                    tmp = stack.enter_context(tempfile.NamedTemporaryFile(dir=self.tmpdir))
    
    377
    +                    tmp = stack.enter_context(utils._tempnamedfile(dir=self.tmpdir))
    
    378 378
                         # Set mode bits to 0644
    
    379 379
                         os.chmod(tmp.name, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
    
    380 380
     
    
    ... ... @@ -545,11 +545,7 @@ class CASCache():
    545 545
         def remove(self, ref, *, defer_prune=False):
    
    546 546
     
    
    547 547
             # Remove cache ref
    
    548
    -        refpath = self._refpath(ref)
    
    549
    -        if not os.path.exists(refpath):
    
    550
    -            raise CASCacheError("Could not find ref '{}'".format(ref))
    
    551
    -
    
    552
    -        os.unlink(refpath)
    
    548
    +        self._remove_ref(ref)
    
    553 549
     
    
    554 550
             if not defer_prune:
    
    555 551
                 pruned = self.prune()
    
    ... ... @@ -626,6 +622,55 @@ class CASCache():
    626 622
         def _refpath(self, ref):
    
    627 623
             return os.path.join(self.casdir, 'refs', 'heads', ref)
    
    628 624
     
    
    625
    +    # _remove_ref()
    
    626
    +    #
    
    627
    +    # Removes a ref.
    
    628
    +    #
    
    629
    +    # This also takes care of pruning away directories which can
    
    630
    +    # be removed after having removed the given ref.
    
    631
    +    #
    
    632
    +    # Args:
    
    633
    +    #    ref (str): The ref to remove
    
    634
    +    #
    
    635
    +    # Raises:
    
    636
    +    #    (CASCacheError): If the ref didnt exist, or a system error
    
    637
    +    #                     occurred while removing it
    
    638
    +    #
    
    639
    +    def _remove_ref(self, ref):
    
    640
    +
    
    641
    +        # Remove the ref itself
    
    642
    +        refpath = self._refpath(ref)
    
    643
    +        try:
    
    644
    +            os.unlink(refpath)
    
    645
    +        except FileNotFoundError as e:
    
    646
    +            raise CASCacheError("Could not find ref '{}'".format(ref)) from e
    
    647
    +
    
    648
    +        # Now remove any leading directories
    
    649
    +        basedir = os.path.join(self.casdir, 'refs', 'heads')
    
    650
    +        components = list(os.path.split(ref))
    
    651
    +        while components:
    
    652
    +            components.pop()
    
    653
    +            refdir = os.path.join(basedir, *components)
    
    654
    +
    
    655
    +            # Break out once we reach the base
    
    656
    +            if refdir == basedir:
    
    657
    +                break
    
    658
    +
    
    659
    +            try:
    
    660
    +                os.rmdir(refdir)
    
    661
    +            except FileNotFoundError:
    
    662
    +                # The parent directory did not exist, but it's
    
    663
    +                # parent directory might still be ready to prune
    
    664
    +                pass
    
    665
    +            except OSError as e:
    
    666
    +                if e.errno == errno.ENOTEMPTY:
    
    667
    +                    # The parent directory was not empty, so we
    
    668
    +                    # cannot prune directories beyond this point
    
    669
    +                    break
    
    670
    +
    
    671
    +                # Something went wrong here
    
    672
    +                raise CASCacheError("System error while removing ref '{}': {}".format(ref, e)) from e
    
    673
    +
    
    629 674
         # _commit_directory():
    
    630 675
         #
    
    631 676
         # Adds local directory to content addressable store.
    
    ... ... @@ -797,7 +842,7 @@ class CASCache():
    797 842
                 # already in local repository
    
    798 843
                 return objpath
    
    799 844
     
    
    800
    -        with tempfile.NamedTemporaryFile(dir=self.tmpdir) as f:
    
    845
    +        with utils._tempnamedfile(dir=self.tmpdir) as f:
    
    801 846
                 remote._fetch_blob(digest, f)
    
    802 847
     
    
    803 848
                 added_digest = self.add_object(path=f.name, link_directly=True)
    
    ... ... @@ -807,7 +852,7 @@ class CASCache():
    807 852
     
    
    808 853
         def _batch_download_complete(self, batch):
    
    809 854
             for digest, data in batch.send():
    
    810
    -            with tempfile.NamedTemporaryFile(dir=self.tmpdir) as f:
    
    855
    +            with utils._tempnamedfile(dir=self.tmpdir) as f:
    
    811 856
                     f.write(data)
    
    812 857
                     f.flush()
    
    813 858
     
    
    ... ... @@ -904,7 +949,7 @@ class CASCache():
    904 949
     
    
    905 950
         def _fetch_tree(self, remote, digest):
    
    906 951
             # download but do not store the Tree object
    
    907
    -        with tempfile.NamedTemporaryFile(dir=self.tmpdir) as out:
    
    952
    +        with utils._tempnamedfile(dir=self.tmpdir) as out:
    
    908 953
                 remote._fetch_blob(digest, out)
    
    909 954
     
    
    910 955
                 tree = remote_execution_pb2.Tree()
    

  • buildstream/_frontend/cli.py
    ... ... @@ -554,6 +554,12 @@ def shell(app, element, sysroot, mount, isolate, build_, cli_buildtree, command)
    554 554
         element, assuming it has already been built and all required
    
    555 555
         artifacts are in the local cache.
    
    556 556
     
    
    557
    +    Use '--' to separate a command from the options to bst,
    
    558
    +    otherwise bst may respond to them instead. e.g.
    
    559
    +
    
    560
    +    \b
    
    561
    +        bst shell example.bst -- df -h
    
    562
    +
    
    557 563
         Use the --build option to create a temporary sysroot for
    
    558 564
         building the element instead.
    
    559 565
     
    

  • buildstream/_frontend/widget.py
    ... ... @@ -647,8 +647,9 @@ class LogLine(Widget):
    647 647
                 abbrev = False
    
    648 648
                 if message.message_type not in ERROR_MESSAGES \
    
    649 649
                    and not frontend_message and n_lines > self._message_lines:
    
    650
    -                abbrev = True
    
    651 650
                     lines = lines[0:self._message_lines]
    
    651
    +                if self._message_lines > 0:
    
    652
    +                    abbrev = True
    
    652 653
                 else:
    
    653 654
                     lines[n_lines - 1] = lines[n_lines - 1].rstrip('\n')
    
    654 655
     
    
    ... ... @@ -674,7 +675,7 @@ class LogLine(Widget):
    674 675
                 if self.context is not None and not self.context.log_verbose:
    
    675 676
                     text += self._indent + self._err_profile.fmt("Log file: ")
    
    676 677
                     text += self._indent + self._logfile_widget.render(message) + '\n'
    
    677
    -            else:
    
    678
    +            elif self._log_lines > 0:
    
    678 679
                     text += self._indent + self._err_profile.fmt("Printing the last {} lines from log file:"
    
    679 680
                                                                  .format(self._log_lines)) + '\n'
    
    680 681
                     text += self._indent + self._logfile_widget.render(message, abbrev=False) + '\n'
    

  • buildstream/utils.py
    ... ... @@ -1032,6 +1032,36 @@ def _tempdir(suffix="", prefix="tmp", dir=None): # pylint: disable=redefined-bu
    1032 1032
             cleanup_tempdir()
    
    1033 1033
     
    
    1034 1034
     
    
    1035
    +# _tempnamedfile()
    
    1036
    +#
    
    1037
    +# A context manager for doing work on an open temporary file
    
    1038
    +# which is guaranteed to be named and have an entry in the filesystem.
    
    1039
    +#
    
    1040
    +# Args:
    
    1041
    +#    dir (str): A path to a parent directory for the temporary file
    
    1042
    +#    suffix (str): A suffix for the temproary file name
    
    1043
    +#    prefix (str): A prefix for the temporary file name
    
    1044
    +#
    
    1045
    +# Yields:
    
    1046
    +#    (str): The temporary file handle
    
    1047
    +#
    
    1048
    +# Do not use tempfile.NamedTemporaryFile() directly, as this will
    
    1049
    +# leak files on the filesystem when BuildStream exits a process
    
    1050
    +# on SIGTERM.
    
    1051
    +#
    
    1052
    +@contextmanager
    
    1053
    +def _tempnamedfile(suffix="", prefix="tmp", dir=None):  # pylint: disable=redefined-builtin
    
    1054
    +    temp = None
    
    1055
    +
    
    1056
    +    def close_tempfile():
    
    1057
    +        if temp is not None:
    
    1058
    +            temp.close()
    
    1059
    +
    
    1060
    +    with _signals.terminator(close_tempfile), \
    
    1061
    +        tempfile.NamedTemporaryFile(suffix=suffix, prefix=prefix, dir=dir) as temp:
    
    1062
    +        yield temp
    
    1063
    +
    
    1064
    +
    
    1035 1065
     # _kill_process_tree()
    
    1036 1066
     #
    
    1037 1067
     # Brutally murder a process and all of its children
    

  • tests/artifactcache/expiry.py
    ... ... @@ -382,6 +382,7 @@ def test_extract_expiry(cli, datafiles, tmpdir):
    382 382
         res = cli.run(project=project, args=['checkout', 'target.bst', os.path.join(str(tmpdir), 'checkout')])
    
    383 383
         res.assert_success()
    
    384 384
     
    
    385
    +    # Get a snapshot of the extracts in advance
    
    385 386
         extractdir = os.path.join(project, 'cache', 'artifacts', 'extract', 'test', 'target')
    
    386 387
         extracts = os.listdir(extractdir)
    
    387 388
         assert(len(extracts) == 1)
    
    ... ... @@ -395,3 +396,16 @@ def test_extract_expiry(cli, datafiles, tmpdir):
    395 396
     
    
    396 397
         # Now the extract should be removed.
    
    397 398
         assert not os.path.exists(extract)
    
    399
    +
    
    400
    +    # As an added bonus, let's ensure that no directories have been left behind
    
    401
    +    #
    
    402
    +    # Now we should have a directory for the cached target2.bst, which
    
    403
    +    # replaced target.bst in the cache, we should not have a directory
    
    404
    +    # for the target.bst
    
    405
    +    refsdir = os.path.join(project, 'cache', 'artifacts', 'cas', 'refs', 'heads')
    
    406
    +    refsdirtest = os.path.join(refsdir, 'test')
    
    407
    +    refsdirtarget = os.path.join(refsdirtest, 'target')
    
    408
    +    refsdirtarget2 = os.path.join(refsdirtest, 'target2')
    
    409
    +
    
    410
    +    assert os.path.isdir(refsdirtarget2)
    
    411
    +    assert not os.path.exists(refsdirtarget)

  • tests/integration/messages.py
    1
    +#
    
    2
    +#  Copyright (C) 2018 Codethink Limited
    
    3
    +#
    
    4
    +#  This program is free software; you can redistribute it and/or
    
    5
    +#  modify it under the terms of the GNU Lesser General Public
    
    6
    +#  License as published by the Free Software Foundation; either
    
    7
    +#  version 2 of the License, or (at your option) any later version.
    
    8
    +#
    
    9
    +#  This library is distributed in the hope that it will be useful,
    
    10
    +#  but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    11
    +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    
    12
    +#  Lesser General Public License for more details.
    
    13
    +#
    
    14
    +#  You should have received a copy of the GNU Lesser General Public
    
    15
    +#  License along with this library. If not, see <http://www.gnu.org/licenses/>.
    
    16
    +#
    
    17
    +#  Authors: Tristan Maat <tristan maat codethink co uk>
    
    18
    +#
    
    19
    +
    
    20
    +import os
    
    21
    +import pytest
    
    22
    +
    
    23
    +from buildstream import _yaml
    
    24
    +from buildstream._exceptions import ErrorDomain
    
    25
    +
    
    26
    +from tests.testutils import cli_integration as cli
    
    27
    +from tests.testutils.site import HAVE_BWRAP, IS_LINUX
    
    28
    +
    
    29
    +
    
    30
    +pytestmark = pytest.mark.integration
    
    31
    +
    
    32
    +
    
    33
    +# Project directory
    
    34
    +DATA_DIR = os.path.join(
    
    35
    +    os.path.dirname(os.path.realpath(__file__)),
    
    36
    +    "project",
    
    37
    +)
    
    38
    +
    
    39
    +
    
    40
    +@pytest.mark.integration
    
    41
    +@pytest.mark.datafiles(DATA_DIR)
    
    42
    +@pytest.mark.skipif(IS_LINUX and not HAVE_BWRAP, reason='Only available with bubblewrap on Linux')
    
    43
    +def test_disable_message_lines(cli, tmpdir, datafiles):
    
    44
    +    project = os.path.join(datafiles.dirname, datafiles.basename)
    
    45
    +    element_path = os.path.join(project, 'elements')
    
    46
    +    element_name = 'message.bst'
    
    47
    +
    
    48
    +    element = {
    
    49
    +        'kind': 'manual',
    
    50
    +        'depends': [{
    
    51
    +            'filename': 'base.bst'
    
    52
    +        }],
    
    53
    +        'config': {
    
    54
    +            'build-commands':
    
    55
    +            ['echo "Silly message"'],
    
    56
    +            'strip-commands': []
    
    57
    +        }
    
    58
    +    }
    
    59
    +
    
    60
    +    os.makedirs(os.path.dirname(os.path.join(element_path, element_name)), exist_ok=True)
    
    61
    +    _yaml.dump(element, os.path.join(element_path, element_name))
    
    62
    +
    
    63
    +    # First we check that we get the "Silly message"
    
    64
    +    result = cli.run(project=project, args=["build", element_name])
    
    65
    +    result.assert_success()
    
    66
    +    assert 'echo "Silly message"' in result.stderr
    
    67
    +
    
    68
    +    # Let's now build it again, but with --message-lines 0
    
    69
    +    cli.remove_artifact_from_cache(project, element_name)
    
    70
    +    result = cli.run(project=project, args=["--message-lines", "0",
    
    71
    +                                            "build", element_name])
    
    72
    +    result.assert_success()
    
    73
    +    assert "Message contains " not in result.stderr
    
    74
    +
    
    75
    +
    
    76
    +@pytest.mark.integration
    
    77
    +@pytest.mark.datafiles(DATA_DIR)
    
    78
    +@pytest.mark.skipif(IS_LINUX and not HAVE_BWRAP, reason='Only available with bubblewrap on Linux')
    
    79
    +def test_disable_error_lines(cli, tmpdir, datafiles):
    
    80
    +    project = os.path.join(datafiles.dirname, datafiles.basename)
    
    81
    +    element_path = os.path.join(project, 'elements')
    
    82
    +    element_name = 'message.bst'
    
    83
    +
    
    84
    +    element = {
    
    85
    +        'kind': 'manual',
    
    86
    +        'depends': [{
    
    87
    +            'filename': 'base.bst'
    
    88
    +        }],
    
    89
    +        'config': {
    
    90
    +            'build-commands':
    
    91
    +            ['This is a syntax error > >'],
    
    92
    +            'strip-commands': []
    
    93
    +        }
    
    94
    +    }
    
    95
    +
    
    96
    +    os.makedirs(os.path.dirname(os.path.join(element_path, element_name)), exist_ok=True)
    
    97
    +    _yaml.dump(element, os.path.join(element_path, element_name))
    
    98
    +
    
    99
    +    # First we check that we get the syntax error
    
    100
    +    result = cli.run(project=project, args=["--error-lines", "0",
    
    101
    +                                            "build", element_name])
    
    102
    +    result.assert_main_error(ErrorDomain.STREAM, None)
    
    103
    +    assert "This is a syntax error" in result.stderr
    
    104
    +
    
    105
    +    # Let's now build it again, but with --error-lines 0
    
    106
    +    cli.remove_artifact_from_cache(project, element_name)
    
    107
    +    result = cli.run(project=project, args=["--error-lines", "0",
    
    108
    +                                            "build", element_name])
    
    109
    +    result.assert_main_error(ErrorDomain.STREAM, None)
    
    110
    +    assert "Printing the last" not in result.stderr

  • tests/testutils/runcli.py
    ... ... @@ -245,8 +245,14 @@ class Cli():
    245 245
     
    
    246 246
         def remove_artifact_from_cache(self, project, element_name,
    
    247 247
                                        *, cache_dir=None):
    
    248
    +        # Read configuration to figure out where artifacts are stored
    
    248 249
             if not cache_dir:
    
    249
    -            cache_dir = os.path.join(project, 'cache', 'artifacts')
    
    250
    +            default = os.path.join(project, 'cache', 'artifacts')
    
    251
    +
    
    252
    +            if self.config is not None:
    
    253
    +                cache_dir = self.config.get('artifactdir', default)
    
    254
    +            else:
    
    255
    +                cache_dir = default
    
    250 256
     
    
    251 257
             cache_dir = os.path.join(cache_dir, 'cas', 'refs', 'heads')
    
    252 258
     
    



  • [Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]