[Notes] [Git][BuildStream/buildstream][richardmaw/shell-multi-stage] 6 commits: _stream: Move shell logic from Element and support multiple elements



Title: GitLab

richardmaw-codethink pushed to branch richardmaw/shell-multi-stage at BuildStream / buildstream

Commits:

9 changed files:

Changes:

  • NEWS
    ... ... @@ -2,6 +2,12 @@
    2 2
     buildstream 1.3.1
    
    3 3
     =================
    
    4 4
     
    
    5
    +  o `bst shell` learned to accept multiple elements for staging
    
    6
    +    provided the element's kind flags `BST_STAGE_INTEGRATES` as false.
    
    7
    +
    
    8
    +    As a side-effect it is no longer possible to intersperse options and flags
    
    9
    +    with arguments in the `bst shell` command-line.
    
    10
    +
    
    5 11
       o All elements must now be suffixed with `.bst`
    
    6 12
         Attempting to use an element that does not have the `.bst` extension,
    
    7 13
         will result in a warning.
    

  • buildstream/_frontend/app.py
    ... ... @@ -599,7 +599,7 @@ class App():
    599 599
                         click.echo("\nDropping into an interactive shell in the failed build sandbox\n", err=True)
    
    600 600
                         try:
    
    601 601
                             prompt = self.shell_prompt(element)
    
    602
    -                        self.stream.shell(element, Scope.BUILD, prompt, isolate=True)
    
    602
    +                        self.stream.shell([(element, Scope.BUILD)], prompt, isolate=True)
    
    603 603
                         except BstError as e:
    
    604 604
                             click.echo("Error while attempting to create interactive shell: {}".format(e), err=True)
    
    605 605
                     elif choice == 'log':
    

  • buildstream/_frontend/cli.py
    ... ... @@ -573,7 +573,7 @@ def show(app, elements, deps, except_, order, format_):
    573 573
     ##################################################################
    
    574 574
     @cli.command(short_help="Shell into an element's sandbox environment")
    
    575 575
     @click.option('--build', '-b', 'build_', is_flag=True, default=False,
    
    576
    -              help='Stage dependencies and sources to build')
    
    576
    +              help='Stage dependencies and sources to build the first element')
    
    577 577
     @click.option('--sysroot', '-s', default=None,
    
    578 578
                   type=click.Path(exists=True, file_okay=False, readable=True),
    
    579 579
                   help="An existing sysroot")
    
    ... ... @@ -582,11 +582,11 @@ def show(app, elements, deps, except_, order, format_):
    582 582
                   help="Mount a file or directory into the sandbox")
    
    583 583
     @click.option('--isolate', is_flag=True, default=False,
    
    584 584
                   help='Create an isolated build sandbox')
    
    585
    -@click.argument('element',
    
    586
    -                type=click.Path(readable=False))
    
    587
    -@click.argument('command', type=click.STRING, nargs=-1)
    
    585
    +@click.argument('elements',
    
    586
    +                type=click.Path(readable=False), nargs=-1,
    
    587
    +                metavar="[ELEMENT]... [--] [COMMAND]...")
    
    588 588
     @click.pass_obj
    
    589
    -def shell(app, element, sysroot, mount, isolate, build_, command):
    
    589
    +def shell(app, elements, sysroot, mount, isolate, build_):
    
    590 590
         """Run a command in the target element's sandbox environment
    
    591 591
     
    
    592 592
         This will stage a temporary sysroot for running the target
    
    ... ... @@ -600,6 +600,15 @@ def shell(app, element, sysroot, mount, isolate, build_, command):
    600 600
         directory or with a checkout of the given target, in order
    
    601 601
         to use a specific sysroot.
    
    602 602
     
    
    603
    +    Multiple element target names ending in .bst may be provided,
    
    604
    +    optionally followed by the command to run in the shell.
    
    605
    +
    
    606
    +    The first argument that doesn't end in .bst is assumed to be the beginning of COMMAND.
    
    607
    +
    
    608
    +    A -- argument may be used to disambiguate between element target names and command arguments
    
    609
    +    and should be used when being driven as part of a script, to prevent mis-parsing,
    
    610
    +    in addition to a -- before the element list.
    
    611
    +
    
    603 612
         If no COMMAND is specified, the default is to attempt
    
    604 613
         to run an interactive shell.
    
    605 614
         """
    
    ... ... @@ -607,13 +616,22 @@ def shell(app, element, sysroot, mount, isolate, build_, command):
    607 616
         from .._project import HostMount
    
    608 617
         from .._pipeline import PipelineSelection
    
    609 618
     
    
    610
    -    if build_:
    
    611
    -        scope = Scope.BUILD
    
    612
    -    else:
    
    613
    -        scope = Scope.RUN
    
    619
    +    targets = []
    
    620
    +    command = []
    
    621
    +    for i, arg in enumerate(elements):
    
    622
    +        if arg == '--':
    
    623
    +            command = elements[i + 1:]
    
    624
    +            break
    
    625
    +        if not arg.endswith('.bst'):
    
    626
    +            command = elements[i:]
    
    627
    +            break
    
    628
    +        targets.append(arg)
    
    629
    +
    
    630
    +    if not targets:
    
    631
    +        raise AppError('No elements specified to open a shell in')
    
    614 632
     
    
    615 633
         with app.initialized():
    
    616
    -        dependencies = app.stream.load_selection((element,), selection=PipelineSelection.NONE)
    
    634
    +        dependencies = app.stream.load_selection(targets, selection=PipelineSelection.NONE)
    
    617 635
             element = dependencies[0]
    
    618 636
             prompt = app.shell_prompt(element)
    
    619 637
             mounts = [
    
    ... ... @@ -621,7 +639,9 @@ def shell(app, element, sysroot, mount, isolate, build_, command):
    621 639
                 for host_path, path in mount
    
    622 640
             ]
    
    623 641
             try:
    
    624
    -            exitcode = app.stream.shell(element, scope, prompt,
    
    642
    +            elements = tuple((e, Scope.BUILD if i == 0 and build_ else Scope.RUN)
    
    643
    +                             for (i, e) in enumerate(dependencies))
    
    644
    +            exitcode = app.stream.shell(elements, prompt,
    
    625 645
                                             directory=sysroot,
    
    626 646
                                             mounts=mounts,
    
    627 647
                                             isolate=isolate,
    

  • buildstream/_stream.py
    ... ... @@ -25,15 +25,17 @@ import stat
    25 25
     import shlex
    
    26 26
     import shutil
    
    27 27
     import tarfile
    
    28
    -from contextlib import contextmanager
    
    28
    +from contextlib import contextmanager, ExitStack
    
    29 29
     from tempfile import TemporaryDirectory
    
    30 30
     
    
    31 31
     from ._exceptions import StreamError, ImplError, BstError, set_last_task_error
    
    32 32
     from ._message import Message, MessageType
    
    33
    -from ._scheduler import Scheduler, SchedStatus, TrackQueue, FetchQueue, BuildQueue, PullQueue, PushQueue
    
    34 33
     from ._pipeline import Pipeline, PipelineSelection
    
    34
    +from ._platform import Platform
    
    35
    +from .sandbox._config import SandboxConfig
    
    36
    +from ._scheduler import Scheduler, SchedStatus, TrackQueue, FetchQueue, BuildQueue, PullQueue, PushQueue
    
    35 37
     from . import utils, _yaml, _site
    
    36
    -from . import Scope, Consistency
    
    38
    +from . import SandboxFlags, Scope, Consistency
    
    37 39
     
    
    38 40
     
    
    39 41
     # Stream()
    
    ... ... @@ -117,8 +119,7 @@ class Stream():
    117 119
         # Run a shell
    
    118 120
         #
    
    119 121
         # Args:
    
    120
    -    #    element (Element): An Element object to run the shell for
    
    121
    -    #    scope (Scope): The scope for the shell (Scope.BUILD or Scope.RUN)
    
    122
    +    #    elements (List of (Element, Scope) tuples): Elements to run the shell for and their dependency scopes.
    
    122 123
         #    prompt (str): The prompt to display in the shell
    
    123 124
         #    directory (str): A directory where an existing prestaged sysroot is expected, or None
    
    124 125
         #    mounts (list of HostMount): Additional directories to mount into the sandbox
    
    ... ... @@ -128,7 +129,7 @@ class Stream():
    128 129
         # Returns:
    
    129 130
         #    (int): The exit code of the launched shell
    
    130 131
         #
    
    131
    -    def shell(self, element, scope, prompt, *,
    
    132
    +    def shell(self, elements, prompt, *,
    
    132 133
                   directory=None,
    
    133 134
                   mounts=None,
    
    134 135
                   isolate=False,
    
    ... ... @@ -138,16 +139,118 @@ class Stream():
    138 139
             # in which case we just blindly trust the directory, using the element
    
    139 140
             # definitions to control the execution environment only.
    
    140 141
             if directory is None:
    
    141
    -            missing_deps = [
    
    142
    -                dep._get_full_name()
    
    143
    -                for dep in self._pipeline.dependencies([element], scope)
    
    144
    -                if not dep._cached()
    
    145
    -            ]
    
    142
    +            missing_deps = []
    
    143
    +            for element, scope in elements:
    
    144
    +                missing_deps.extend(
    
    145
    +                    dep._get_full_name()
    
    146
    +                    for dep in self._pipeline.dependencies((element,), scope)
    
    147
    +                    if not dep._cached())
    
    146 148
                 if missing_deps:
    
    147 149
                     raise StreamError("Elements need to be built or downloaded before staging a shell environment",
    
    148 150
                                       detail="\n".join(missing_deps))
    
    149 151
     
    
    150
    -        return element._shell(scope, directory, mounts=mounts, isolate=isolate, prompt=prompt, command=command)
    
    152
    +        # Assert we're not mixing virtual directory compatible
    
    153
    +        # and non-virtual directory compatible elements
    
    154
    +        if (any(e.BST_VIRTUAL_DIRECTORY for (e, _) in elements) and
    
    155
    +                not all(e.BST_VIRTUAL_DIRECTORY for (e, _) in elements)):
    
    156
    +            raise StreamError(
    
    157
    +                "Elements do not support multiple-element staging",
    
    158
    +                detail=("Multi-element staging is not supported" +
    
    159
    +                        " because elements {} support BST_VIRTUAL_DIRECTORY and {} do not.").format(
    
    160
    +                            ', '.join(e.name for (e, _) in elements if e.BST_VIRTUAL_DIRECTORY),
    
    161
    +                            ', '.join(e.name for (e, _) in elements if not e.BST_VIRTUAL_DIRECTORY)))
    
    162
    +
    
    163
    +        with ExitStack() as stack:
    
    164
    +            # Creation logic duplicated from Element.__sandbox
    
    165
    +            # since most of it is creating the tmpdir
    
    166
    +            # and deciding whether to make a remote sandbox,
    
    167
    +            # which we don't want to.
    
    168
    +            if directory is None:
    
    169
    +                os.makedirs(self._context.builddir, exist_ok=True)
    
    170
    +                rootdir = stack.enter_context(TemporaryDirectory(dir=self._context.builddir))
    
    171
    +            else:
    
    172
    +                rootdir = directory
    
    173
    +
    
    174
    +            # SandboxConfig comes from project, element defaults and MetaElement sandbox config
    
    175
    +            # In the absence of it being exposed to other APIs and a merging strategy
    
    176
    +            # just make it from the project sandbox config.
    
    177
    +            sandbox_config = SandboxConfig(_yaml.node_get(self._project._sandbox, int, 'build-uid'),
    
    178
    +                                           _yaml.node_get(self._project._sandbox, int, 'build-gid'))
    
    179
    +            platform = Platform.get_platform()
    
    180
    +            sandbox = platform.create_sandbox(context=self._context,
    
    181
    +                                              project=self._project,
    
    182
    +                                              directory=rootdir,
    
    183
    +                                              stdout=None, stderr=None, config=sandbox_config,
    
    184
    +                                              bare_directory=directory is not None,
    
    185
    +                                              allow_real_directory=not any(e.BST_VIRTUAL_DIRECTORY
    
    186
    +                                                                           for (e, _) in elements))
    
    187
    +
    
    188
    +            # Configure the sandbox with the last element taking precedence for config.
    
    189
    +            for e, _ in elements:
    
    190
    +                e.configure_sandbox(sandbox)
    
    191
    +
    
    192
    +            # Stage contents if not passed --sysroot
    
    193
    +            if not directory:
    
    194
    +                if any(e.BST_STAGE_INTEGRATES for (e, _) in elements):
    
    195
    +                    if len(elements) > 1:
    
    196
    +                        raise StreamError(
    
    197
    +                            "Elements do not support multiple-element staging",
    
    198
    +                            detail=("Elements {} do not support multi-element staging " +
    
    199
    +                                    " because element kinds {} set BST_STAGE_INTEGRATES").format(
    
    200
    +                                        ', '.join(e.name for (e, _) in elements if e.BST_STAGE_INTEGRATES),
    
    201
    +                                        ', '.join(set(e.get_kind() for (e, _) in elements))))
    
    202
    +                    elements[0][0].stage(sandbox)
    
    203
    +                else:
    
    204
    +                    visited = {}
    
    205
    +                    for e, scope in elements:
    
    206
    +                        if scope is Scope.BUILD:
    
    207
    +                            e.stage(sandbox, visited=visited)
    
    208
    +                        else:
    
    209
    +                            e.stage_dependency_artifacts(sandbox, scope, visited=visited)
    
    210
    +
    
    211
    +                    visited = {}
    
    212
    +                    for e, scope in elements:
    
    213
    +                        e.integrate_dependency_artifacts(sandbox, scope, visited=visited)
    
    214
    +
    
    215
    +            environment = {}
    
    216
    +            for e, _ in elements:
    
    217
    +                environment.update(e.get_environment())
    
    218
    +            flags = SandboxFlags.INTERACTIVE | SandboxFlags.ROOT_READ_ONLY
    
    219
    +            shell_command, shell_environment, shell_host_files = self._project.get_shell_config()
    
    220
    +            environment['PS1'] = prompt
    
    221
    +            # Special configurations for non-isolated sandboxes
    
    222
    +            if not isolate:
    
    223
    +
    
    224
    +                # Open the network, and reuse calling uid/gid
    
    225
    +                #
    
    226
    +                flags |= SandboxFlags.NETWORK_ENABLED | SandboxFlags.INHERIT_UID
    
    227
    +
    
    228
    +                # Apply project defined environment vars to set for a shell
    
    229
    +                for key, value in _yaml.node_items(shell_environment):
    
    230
    +                    environment[key] = value
    
    231
    +
    
    232
    +                # Setup any requested bind mounts
    
    233
    +                if mounts is None:
    
    234
    +                    mounts = []
    
    235
    +
    
    236
    +                for mount in shell_host_files + mounts:
    
    237
    +                    if not os.path.exists(mount.host_path):
    
    238
    +                        if not mount.optional:
    
    239
    +                            self._message(MessageType.WARN,
    
    240
    +                                          "Not mounting non-existing host file: {}".format(mount.host_path))
    
    241
    +                    else:
    
    242
    +                        sandbox.mark_directory(mount.path)
    
    243
    +                        sandbox._set_mount_source(mount.path, mount.host_path)
    
    244
    +
    
    245
    +            if command:
    
    246
    +                argv = list(command)
    
    247
    +            else:
    
    248
    +                argv = shell_command
    
    249
    +
    
    250
    +            self._message(MessageType.STATUS, "Running command", detail=" ".join(argv))
    
    251
    +
    
    252
    +            # Run shells with network enabled and readonly root.
    
    253
    +            return sandbox.run(argv, flags, env=environment)
    
    151 254
     
    
    152 255
         # build()
    
    153 256
         #
    

  • buildstream/element.py
    ... ... @@ -75,7 +75,6 @@ Class Reference
    75 75
     import os
    
    76 76
     import re
    
    77 77
     import stat
    
    78
    -import copy
    
    79 78
     from collections import OrderedDict
    
    80 79
     from collections.abc import Mapping
    
    81 80
     import contextlib
    
    ... ... @@ -1382,7 +1381,7 @@ class Element(Plugin):
    1382 1381
         # is used to stage things by the `bst checkout` codepath
    
    1383 1382
         #
    
    1384 1383
         @contextmanager
    
    1385
    -    def _prepare_sandbox(self, scope, directory, shell=False, integrate=True):
    
    1384
    +    def _prepare_sandbox(self, scope, directory, integrate=True):
    
    1386 1385
             # bst shell and bst checkout require a local sandbox.
    
    1387 1386
             bare_directory = True if directory else False
    
    1388 1387
             with self.__sandbox(directory, config=self.__sandbox_config, allow_remote=False,
    
    ... ... @@ -1393,20 +1392,15 @@ class Element(Plugin):
    1393 1392
     
    
    1394 1393
                 # Stage something if we need it
    
    1395 1394
                 if not directory:
    
    1396
    -                if shell and scope == Scope.BUILD:
    
    1397
    -                    self.stage(sandbox)
    
    1398
    -                    if not self.BST_STAGE_INTEGRATES:
    
    1395
    +                # Stage deps in the sandbox root
    
    1396
    +                with self.timed_activity("Staging dependencies", silent_nested=True):
    
    1397
    +                    self.stage_dependency_artifacts(sandbox, scope)
    
    1398
    +
    
    1399
    +                # Run any integration commands provided by the dependencies
    
    1400
    +                # once they are all staged and ready
    
    1401
    +                if integrate:
    
    1402
    +                    with self.timed_activity("Integrating sandbox"):
    
    1399 1403
                             self.integrate_dependency_artifacts(sandbox, scope)
    
    1400
    -                else:
    
    1401
    -                    # Stage deps in the sandbox root
    
    1402
    -                    with self.timed_activity("Staging dependencies", silent_nested=True):
    
    1403
    -                        self.stage_dependency_artifacts(sandbox, scope)
    
    1404
    -
    
    1405
    -                    # Run any integration commands provided by the dependencies
    
    1406
    -                    # once they are all staged and ready
    
    1407
    -                    if integrate:
    
    1408
    -                        with self.timed_activity("Integrating sandbox"):
    
    1409
    -                            self.integrate_dependency_artifacts(sandbox, scope)
    
    1410 1404
     
    
    1411 1405
                 yield sandbox
    
    1412 1406
     
    
    ... ... @@ -1885,71 +1879,6 @@ class Element(Plugin):
    1885 1879
             # Notify successful upload
    
    1886 1880
             return True
    
    1887 1881
     
    
    1888
    -    # _shell():
    
    1889
    -    #
    
    1890
    -    # Connects the terminal with a shell running in a staged
    
    1891
    -    # environment
    
    1892
    -    #
    
    1893
    -    # Args:
    
    1894
    -    #    scope (Scope): Either BUILD or RUN scopes are valid, or None
    
    1895
    -    #    directory (str): A directory to an existing sandbox, or None
    
    1896
    -    #    mounts (list): A list of (str, str) tuples, representing host/target paths to mount
    
    1897
    -    #    isolate (bool): Whether to isolate the environment like we do in builds
    
    1898
    -    #    prompt (str): A suitable prompt string for PS1
    
    1899
    -    #    command (list): An argv to launch in the sandbox
    
    1900
    -    #
    
    1901
    -    # Returns: Exit code
    
    1902
    -    #
    
    1903
    -    # If directory is not specified, one will be staged using scope
    
    1904
    -    def _shell(self, scope=None, directory=None, *, mounts=None, isolate=False, prompt=None, command=None):
    
    1905
    -
    
    1906
    -        with self._prepare_sandbox(scope, directory, shell=True) as sandbox:
    
    1907
    -            environment = self.get_environment()
    
    1908
    -            environment = copy.copy(environment)
    
    1909
    -            flags = SandboxFlags.INTERACTIVE | SandboxFlags.ROOT_READ_ONLY
    
    1910
    -
    
    1911
    -            # Fetch the main toplevel project, in case this is a junctioned
    
    1912
    -            # subproject, we want to use the rules defined by the main one.
    
    1913
    -            context = self._get_context()
    
    1914
    -            project = context.get_toplevel_project()
    
    1915
    -            shell_command, shell_environment, shell_host_files = project.get_shell_config()
    
    1916
    -
    
    1917
    -            if prompt is not None:
    
    1918
    -                environment['PS1'] = prompt
    
    1919
    -
    
    1920
    -            # Special configurations for non-isolated sandboxes
    
    1921
    -            if not isolate:
    
    1922
    -
    
    1923
    -                # Open the network, and reuse calling uid/gid
    
    1924
    -                #
    
    1925
    -                flags |= SandboxFlags.NETWORK_ENABLED | SandboxFlags.INHERIT_UID
    
    1926
    -
    
    1927
    -                # Apply project defined environment vars to set for a shell
    
    1928
    -                for key, value in _yaml.node_items(shell_environment):
    
    1929
    -                    environment[key] = value
    
    1930
    -
    
    1931
    -                # Setup any requested bind mounts
    
    1932
    -                if mounts is None:
    
    1933
    -                    mounts = []
    
    1934
    -
    
    1935
    -                for mount in shell_host_files + mounts:
    
    1936
    -                    if not os.path.exists(mount.host_path):
    
    1937
    -                        if not mount.optional:
    
    1938
    -                            self.warn("Not mounting non-existing host file: {}".format(mount.host_path))
    
    1939
    -                    else:
    
    1940
    -                        sandbox.mark_directory(mount.path)
    
    1941
    -                        sandbox._set_mount_source(mount.path, mount.host_path)
    
    1942
    -
    
    1943
    -            if command:
    
    1944
    -                argv = [arg for arg in command]
    
    1945
    -            else:
    
    1946
    -                argv = shell_command
    
    1947
    -
    
    1948
    -            self.status("Running command", detail=" ".join(argv))
    
    1949
    -
    
    1950
    -            # Run shells with network enabled and readonly root.
    
    1951
    -            return sandbox.run(argv, flags, env=environment)
    
    1952
    -
    
    1953 1882
         # _open_workspace():
    
    1954 1883
         #
    
    1955 1884
         # "Open" a workspace for this element
    

  • tests/integration/project/elements/shell/adds-bar.bst
    1
    +kind: manual
    
    2
    +depends:
    
    3
    +- base.bst
    
    4
    +
    
    5
    +config:
    
    6
    +  install-commands:
    
    7
    +  - |
    
    8
    +    install -D -m775 /proc/self/fd/0 %{install-root}%{bindir}/bar <<\EOF
    
    9
    +    #!/bin/sh
    
    10
    +    echo bar
    
    11
    +    EOF

  • tests/integration/project/elements/shell/adds-foo.bst
    1
    +kind: manual
    
    2
    +depends:
    
    3
    +- base.bst
    
    4
    +
    
    5
    +config:
    
    6
    +  install-commands:
    
    7
    +  - |
    
    8
    +    install -D -m775 /proc/self/fd/0 %{install-root}%{bindir}/foo <<\EOF
    
    9
    +    #!/bin/sh
    
    10
    +    echo foo
    
    11
    +    EOF

  • tests/integration/project/elements/integration.bsttests/integration/project/elements/shell/integration.bst

  • tests/integration/shell.py
    ... ... @@ -28,11 +28,17 @@ DATA_DIR = os.path.join(
    28 28
     #    config (dict): A project.conf dictionary to composite over the default
    
    29 29
     #    mount (tuple): A (host, target) tuple for the `--mount` option
    
    30 30
     #    element (str): The element to build and run a shell with
    
    31
    +#    elements (list): Other elements to build and run a shell with
    
    31 32
     #    isolate (bool): Whether to pass --isolate to `bst shell`
    
    32 33
     #
    
    33
    -def execute_shell(cli, project, command, *, config=None, mount=None, element='base.bst', isolate=False):
    
    34
    +def execute_shell(cli, project, command, *, config=None, mount=None, elements=None, isolate=False):
    
    34 35
         # Ensure the element is built
    
    35
    -    result = cli.run(project=project, project_config=config, args=['build', element])
    
    36
    +    if elements is None:
    
    37
    +        elements = ('base.bst',)
    
    38
    +
    
    39
    +    args = ['build', '--']
    
    40
    +    args.extend(elements)
    
    41
    +    result = cli.run(project=project, project_config=config, args=args)
    
    36 42
         assert result.exit_code == 0
    
    37 43
     
    
    38 44
         args = ['shell']
    
    ... ... @@ -41,7 +47,9 @@ def execute_shell(cli, project, command, *, config=None, mount=None, element='ba
    41 47
         if mount is not None:
    
    42 48
             host_path, target_path = mount
    
    43 49
             args += ['--mount', host_path, target_path]
    
    44
    -    args += [element, '--'] + command
    
    50
    +    args.append('--')
    
    51
    +    args.extend(elements)
    
    52
    +    args += ['--'] + command
    
    45 53
     
    
    46 54
         return cli.run(project=project, project_config=config, args=args)
    
    47 55
     
    
    ... ... @@ -158,7 +166,7 @@ def test_no_shell(cli, tmpdir, datafiles):
    158 166
         os.makedirs(os.path.dirname(os.path.join(element_path, element_name)), exist_ok=True)
    
    159 167
         _yaml.dump(element, os.path.join(element_path, element_name))
    
    160 168
     
    
    161
    -    result = execute_shell(cli, project, ['/bin/echo', 'Pegasissies!'], element=element_name)
    
    169
    +    result = execute_shell(cli, project, ['/bin/echo', 'Pegasissies!'], elements=(element_name,))
    
    162 170
         assert result.exit_code == 0
    
    163 171
         assert result.output == "Pegasissies!\n"
    
    164 172
     
    
    ... ... @@ -345,11 +353,56 @@ def test_sysroot(cli, tmpdir, datafiles):
    345 353
     
    
    346 354
     
    
    347 355
     # Test system integration commands can access devices in /dev
    
    356
    +@pytest.mark.integration
    
    348 357
     @pytest.mark.datafiles(DATA_DIR)
    
    349 358
     @pytest.mark.skipif(IS_LINUX and not HAVE_BWRAP, reason='Only available with bubblewrap on Linux')
    
    350 359
     def test_integration_devices(cli, tmpdir, datafiles):
    
    351 360
         project = os.path.join(datafiles.dirname, datafiles.basename)
    
    352
    -    element_name = 'integration.bst'
    
    361
    +    element_name = 'shell/integration.bst'
    
    362
    +
    
    363
    +    result = execute_shell(cli, project, ["true"], elements=(element_name,))
    
    364
    +    assert result.exit_code == 0
    
    365
    +
    
    366
    +
    
    367
    +# Test multiple element shell
    
    368
    +@pytest.mark.integration
    
    369
    +@pytest.mark.datafiles(DATA_DIR)
    
    370
    +@pytest.mark.skipif(IS_LINUX and not HAVE_BWRAP, reason='Only available with bubblewrap on Linux')
    
    371
    +def test_shell_multiple_elements(cli, tmpdir, datafiles):
    
    372
    +    project = os.path.join(datafiles.dirname, datafiles.basename)
    
    373
    +
    
    374
    +    result = execute_shell(cli, project, ["sh", "-c", "foo && bar"],
    
    375
    +                           elements=["shell/adds-foo.bst", "shell/adds-bar.bst"])
    
    376
    +    assert result.exit_code == 0
    
    377
    +
    
    378
    +
    
    379
    +# Test multiple element build shell
    
    380
    +@pytest.mark.integration
    
    381
    +@pytest.mark.datafiles(DATA_DIR)
    
    382
    +@pytest.mark.skipif(IS_LINUX and not HAVE_BWRAP, reason='Only available with bubblewrap on Linux')
    
    383
    +def test_shell_multiple_workspace(cli, tmpdir, datafiles):
    
    384
    +    project = os.path.join(datafiles.dirname, datafiles.basename)
    
    385
    +    elements = {'workspace/workspace-mount.bst': os.path.join(cli.directory, 'workspace-mount'),
    
    386
    +                'make/makehello.bst': os.path.join(cli.directory, 'makehello')}
    
    387
    +
    
    388
    +    for element, workspace in elements.items():
    
    389
    +        res = cli.run(project=project, args=['workspace', 'open', element, '--directory', workspace])
    
    390
    +        assert res.exit_code == 0
    
    391
    +
    
    392
    +    for workspace in elements.values():
    
    393
    +        with open(os.path.join(workspace, "workspace-exists"), "w") as f:
    
    394
    +            pass
    
    395
    +
    
    396
    +    # Ensure the dependencies of our build failing element are built
    
    397
    +    result = cli.run(project=project, args=['build', 'base.bst', 'make/makehello.bst'])
    
    398
    +    assert result.exit_code == 0
    
    353 399
     
    
    354
    -    result = execute_shell(cli, project, ["true"], element=element_name)
    
    400
    +    # Test that only the first workspace exists, since only the first element may be staged for build,
    
    401
    +    # additional elements may only be staged as extra dependencies.
    
    402
    +    args = ['shell', '--build', '--'] + list(elements)
    
    403
    +    args += ['--', 'sh', '-c',
    
    404
    +             'test -e /buildstream/test/workspace/workspace-mount.bst/workspace-exists && \
    
    405
    +              test ! -e /buildstream/test/make/makehello.bst/workspace-exists']
    
    406
    +    result = cli.run(project=project, args=args)
    
    355 407
         assert result.exit_code == 0
    
    408
    +    assert result.output == ''



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