[Notes] [Git][BuildStream/buildstream][richardmaw/shell-multi-stage] 4 commits: cli: Support multiple elements in `bst shell`



Title: GitLab

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

Commits:

6 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/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,
    

  • 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]