richardmaw-codethink pushed to branch richardmaw/shell-multi-stage at BuildStream / buildstream
Commits:
-
d3dc60e6
by Richard Maw at 2018-12-10T17:59:49Z
-
99e05e6e
by Richard Maw at 2018-12-10T18:00:03Z
-
5e5a9a4c
by Richard Maw at 2018-12-10T18:00:03Z
-
e2a216f6
by Richard Maw at 2018-12-10T18:00:03Z
6 changed files:
- NEWS
- buildstream/_frontend/cli.py
- + tests/integration/project/elements/shell/adds-bar.bst
- + tests/integration/project/elements/shell/adds-foo.bst
- tests/integration/project/elements/integration.bst → tests/integration/project/elements/shell/integration.bst
- tests/integration/shell.py
Changes:
| ... | ... | @@ -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.
|
| ... | ... | @@ -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,
|
| 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
|
| 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
|
| ... | ... | @@ -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 == ''
|
