richardmaw-codethink pushed to branch richardmaw/shell-multi-stage at BuildStream / buildstream
Commits:
-
3509e195
by Richard Maw at 2018-10-30T11:18:53Z
-
d834e869
by Richard Maw at 2018-10-30T11:18:53Z
-
a9e3c07e
by Richard Maw at 2018-10-30T11:18:54Z
-
808881c4
by Richard Maw at 2018-10-30T11:18:54Z
-
5dfa96dd
by Richard Maw at 2018-10-30T11:18:54Z
-
be6b6445
by Richard Maw at 2018-10-30T11:18:54Z
-
7b47a3e9
by Richard Maw at 2018-10-30T11:18:54Z
-
13ea23e8
by Richard Maw at 2018-10-30T11:18:54Z
24 changed files:
- NEWS
- buildstream/_frontend/cli.py
- buildstream/_stream.py
- buildstream/buildelement.py
- buildstream/element.py
- buildstream/plugins/elements/autotools.py
- buildstream/plugins/elements/cmake.py
- buildstream/plugins/elements/compose.py
- buildstream/plugins/elements/distutils.py
- buildstream/plugins/elements/import.py
- buildstream/plugins/elements/make.py
- buildstream/plugins/elements/makemaker.py
- buildstream/plugins/elements/manual.py
- buildstream/plugins/elements/meson.py
- buildstream/plugins/elements/modulebuild.py
- buildstream/plugins/elements/pip.py
- buildstream/plugins/elements/qmake.py
- buildstream/plugins/elements/script.py
- buildstream/plugins/elements/stack.py
- buildstream/scriptelement.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:
| 1 |
+===============
|
|
| 2 |
+buildstream 1.4
|
|
| 3 |
+===============
|
|
| 4 |
+ |
|
| 5 |
+ o `bst shell` learned the `-e` option for staging multiple elements
|
|
| 6 |
+ provided the element's kind implements `BST_GRANULAR_STAGE`.
|
|
| 7 |
+ |
|
| 1 | 8 |
=================
|
| 2 | 9 |
buildstream 1.3.1
|
| 3 | 10 |
=================
|
| ... | ... | @@ -572,11 +572,13 @@ def show(app, elements, deps, except_, order, format_): |
| 572 | 572 |
help="Mount a file or directory into the sandbox")
|
| 573 | 573 |
@click.option('--isolate', is_flag=True, default=False,
|
| 574 | 574 |
help='Create an isolated build sandbox')
|
| 575 |
+@click.option('--element', '-e', 'elements', multiple=True,
|
|
| 576 |
+ type=click.Path(readable=False), required=False)
|
|
| 575 | 577 |
@click.argument('element',
|
| 576 |
- type=click.Path(readable=False))
|
|
| 578 |
+ type=click.Path(readable=False), required=False)
|
|
| 577 | 579 |
@click.argument('command', type=click.STRING, nargs=-1)
|
| 578 | 580 |
@click.pass_obj
|
| 579 |
-def shell(app, element, sysroot, mount, isolate, build_, command):
|
|
| 581 |
+def shell(app, element, elements, sysroot, mount, isolate, build_, command):
|
|
| 580 | 582 |
"""Run a command in the target element's sandbox environment
|
| 581 | 583 |
|
| 582 | 584 |
This will stage a temporary sysroot for running the target
|
| ... | ... | @@ -597,13 +599,21 @@ def shell(app, element, sysroot, mount, isolate, build_, command): |
| 597 | 599 |
from .._project import HostMount
|
| 598 | 600 |
from .._pipeline import PipelineSelection
|
| 599 | 601 |
|
| 602 |
+ if elements and element is not None:
|
|
| 603 |
+ command = [element] + list(command)
|
|
| 604 |
+ element = None
|
|
| 605 |
+ if not elements and element is not None:
|
|
| 606 |
+ elements = [element]
|
|
| 607 |
+ if not elements:
|
|
| 608 |
+ raise AppError('No elemenets specified to open a shell in')
|
|
| 609 |
+ |
|
| 600 | 610 |
if build_:
|
| 601 | 611 |
scope = Scope.BUILD
|
| 602 | 612 |
else:
|
| 603 | 613 |
scope = Scope.RUN
|
| 604 | 614 |
|
| 605 | 615 |
with app.initialized():
|
| 606 |
- dependencies = app.stream.load_selection((element,), selection=PipelineSelection.NONE)
|
|
| 616 |
+ dependencies = app.stream.load_selection(elements, selection=PipelineSelection.NONE)
|
|
| 607 | 617 |
element = dependencies[0]
|
| 608 | 618 |
prompt = app.shell_prompt(element)
|
| 609 | 619 |
mounts = [
|
| ... | ... | @@ -611,7 +621,7 @@ def shell(app, element, sysroot, mount, isolate, build_, command): |
| 611 | 621 |
for host_path, path in mount
|
| 612 | 622 |
]
|
| 613 | 623 |
try:
|
| 614 |
- exitcode = app.stream.shell(element, scope, prompt,
|
|
| 624 |
+ exitcode = app.stream.shell(dependencies, scope, prompt,
|
|
| 615 | 625 |
directory=sysroot,
|
| 616 | 626 |
mounts=mounts,
|
| 617 | 627 |
isolate=isolate,
|
| ... | ... | @@ -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,7 +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
|
|
| 122 |
+ # elements (List of Element): Elements to run the shell for
|
|
| 121 | 123 |
# scope (Scope): The scope for the shell (Scope.BUILD or Scope.RUN)
|
| 122 | 124 |
# prompt (str): The prompt to display in the shell
|
| 123 | 125 |
# directory (str): A directory where an existing prestaged sysroot is expected, or None
|
| ... | ... | @@ -128,7 +130,7 @@ class Stream(): |
| 128 | 130 |
# Returns:
|
| 129 | 131 |
# (int): The exit code of the launched shell
|
| 130 | 132 |
#
|
| 131 |
- def shell(self, element, scope, prompt, *,
|
|
| 133 |
+ def shell(self, elements, scope, prompt, *,
|
|
| 132 | 134 |
directory=None,
|
| 133 | 135 |
mounts=None,
|
| 134 | 136 |
isolate=False,
|
| ... | ... | @@ -140,14 +142,103 @@ class Stream(): |
| 140 | 142 |
if directory is None:
|
| 141 | 143 |
missing_deps = [
|
| 142 | 144 |
dep._get_full_name()
|
| 143 |
- for dep in self._pipeline.dependencies([element], scope)
|
|
| 145 |
+ for dep in self._pipeline.dependencies(elements, scope)
|
|
| 144 | 146 |
if not dep._cached()
|
| 145 | 147 |
]
|
| 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 |
+ with ExitStack() as stack:
|
|
| 153 |
+ # Creation logic duplicated from Element.__sandbox
|
|
| 154 |
+ # since most of it is creating the tmpdir
|
|
| 155 |
+ # and deciding whether to make a remote sandbox,
|
|
| 156 |
+ # which we don't want to.
|
|
| 157 |
+ if directory is None:
|
|
| 158 |
+ os.makedirs(self._context.builddir, exist_ok=True)
|
|
| 159 |
+ rootdir = stack.enter_context(TemporaryDirectory(dir=self._context.builddir))
|
|
| 160 |
+ else:
|
|
| 161 |
+ rootdir = directory
|
|
| 162 |
+ |
|
| 163 |
+ # SandboxConfig comes from project, element defaults and MetaElement sandbox config
|
|
| 164 |
+ # In the absence of it being exposed to other APIs and a merging strategy
|
|
| 165 |
+ # just make it from the project sandbox config.
|
|
| 166 |
+ sandbox_config = SandboxConfig(_yaml.node_get(self._project._sandbox, int, 'build-uid'),
|
|
| 167 |
+ _yaml.node_get(self._project._sandbox, int, 'build-gid'))
|
|
| 168 |
+ platform = Platform.get_platform()
|
|
| 169 |
+ sandbox = platform.create_sandbox(context=self._context,
|
|
| 170 |
+ project=self._project,
|
|
| 171 |
+ directory=rootdir,
|
|
| 172 |
+ stdout=None, stderr=None, config=sandbox_config,
|
|
| 173 |
+ allow_real_directory=not all(e.BST_VIRTUAL_DIRECTORY
|
|
| 174 |
+ for e in elements))
|
|
| 175 |
+ |
|
| 176 |
+ # Configure the sandbox with the last element taking precedence for config.
|
|
| 177 |
+ for e in elements:
|
|
| 178 |
+ e.configure_sandbox(sandbox)
|
|
| 179 |
+ |
|
| 180 |
+ # Stage contents if not passed --sysroot
|
|
| 181 |
+ if not directory:
|
|
| 182 |
+ if not all(e.BST_GRANULAR_STAGE for e in elements):
|
|
| 183 |
+ if len(elements) > 1:
|
|
| 184 |
+ raise StreamError(
|
|
| 185 |
+ "Elements do not support multiple-element staging",
|
|
| 186 |
+ detail=("Elements {} do not support multi-element staging " +
|
|
| 187 |
+ " because element kinds {} do not support BST_GRANULAR_STAGE").format(
|
|
| 188 |
+ ', '.join(e.name for e in elements if not e.BST_GRANULAR_STAGE),
|
|
| 189 |
+ ', '.join(set(e.get_kind() for e in elements))))
|
|
| 190 |
+ elements[0].stage(sandbox)
|
|
| 191 |
+ else:
|
|
| 192 |
+ visited = {}
|
|
| 193 |
+ for e in elements:
|
|
| 194 |
+ e.stage_dependency_artifacts(sandbox, scope, visited=visited)
|
|
| 195 |
+ |
|
| 196 |
+ visited = {}
|
|
| 197 |
+ for e in elements:
|
|
| 198 |
+ e.integrate_dependencies(sandbox, scope, visited=visited)
|
|
| 199 |
+ |
|
| 200 |
+ for e in elements:
|
|
| 201 |
+ e.post_integration_staging(sandbox)
|
|
| 202 |
+ |
|
| 203 |
+ environment = {}
|
|
| 204 |
+ for e in elements:
|
|
| 205 |
+ environment.update(e.get_environment())
|
|
| 206 |
+ flags = SandboxFlags.INTERACTIVE | SandboxFlags.ROOT_READ_ONLY
|
|
| 207 |
+ shell_command, shell_environment, shell_host_files = self._project.get_shell_config()
|
|
| 208 |
+ environment['PS1'] = prompt
|
|
| 209 |
+ # Special configurations for non-isolated sandboxes
|
|
| 210 |
+ if not isolate:
|
|
| 211 |
+ |
|
| 212 |
+ # Open the network, and reuse calling uid/gid
|
|
| 213 |
+ #
|
|
| 214 |
+ flags |= SandboxFlags.NETWORK_ENABLED | SandboxFlags.INHERIT_UID
|
|
| 215 |
+ |
|
| 216 |
+ # Apply project defined environment vars to set for a shell
|
|
| 217 |
+ for key, value in _yaml.node_items(shell_environment):
|
|
| 218 |
+ environment[key] = value
|
|
| 219 |
+ |
|
| 220 |
+ # Setup any requested bind mounts
|
|
| 221 |
+ if mounts is None:
|
|
| 222 |
+ mounts = []
|
|
| 223 |
+ |
|
| 224 |
+ for mount in shell_host_files + mounts:
|
|
| 225 |
+ if not os.path.exists(mount.host_path):
|
|
| 226 |
+ if not mount.optional:
|
|
| 227 |
+ self._message(MessageType.WARN,
|
|
| 228 |
+ "Not mounting non-existing host file: {}".format(mount.host_path))
|
|
| 229 |
+ else:
|
|
| 230 |
+ sandbox.mark_directory(mount.path)
|
|
| 231 |
+ sandbox._set_mount_source(mount.path, mount.host_path)
|
|
| 232 |
+ |
|
| 233 |
+ if command:
|
|
| 234 |
+ argv = [arg for arg in command]
|
|
| 235 |
+ else:
|
|
| 236 |
+ argv = shell_command
|
|
| 237 |
+ |
|
| 238 |
+ self._message(MessageType.STATUS, "Running command", detail=" ".join(argv))
|
|
| 239 |
+ |
|
| 240 |
+ # Run shells with network enabled and readonly root.
|
|
| 241 |
+ return sandbox.run(argv, flags, env=environment)
|
|
| 151 | 242 |
|
| 152 | 243 |
# build()
|
| 153 | 244 |
#
|
| ... | ... | @@ -208,7 +208,6 @@ class BuildElement(Element): |
| 208 | 208 |
sandbox.set_environment(self.get_environment())
|
| 209 | 209 |
|
| 210 | 210 |
def stage(self, sandbox):
|
| 211 |
- |
|
| 212 | 211 |
# Stage deps in the sandbox root
|
| 213 | 212 |
with self.timed_activity("Staging dependencies", silent_nested=True):
|
| 214 | 213 |
self.stage_dependency_artifacts(sandbox, Scope.BUILD)
|
| ... | ... | @@ -216,9 +215,11 @@ class BuildElement(Element): |
| 216 | 215 |
# Run any integration commands provided by the dependencies
|
| 217 | 216 |
# once they are all staged and ready
|
| 218 | 217 |
with self.timed_activity("Integrating sandbox"):
|
| 219 |
- for dep in self.dependencies(Scope.BUILD):
|
|
| 220 |
- dep.integrate(sandbox)
|
|
| 218 |
+ self.integrate_dependencies(sandbox, Scope.BUILD)
|
|
| 219 |
+ |
|
| 220 |
+ self.post_integration_staging(sandbox)
|
|
| 221 | 221 |
|
| 222 |
+ def post_integration_staging(self, sandbox):
|
|
| 222 | 223 |
# Stage sources in the build root
|
| 223 | 224 |
self.stage_sources(sandbox, self.get_variable('build-root'))
|
| 224 | 225 |
|
| ... | ... | @@ -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 |
from contextlib import contextmanager
|
| ... | ... | @@ -88,7 +87,6 @@ from ._versions import BST_CORE_ARTIFACT_VERSION |
| 88 | 87 |
from ._exceptions import BstError, LoadError, LoadErrorReason, ImplError, ErrorDomain
|
| 89 | 88 |
from .utils import UtilError
|
| 90 | 89 |
from . import Plugin, Consistency, Scope
|
| 91 |
-from . import SandboxFlags
|
|
| 92 | 90 |
from . import utils
|
| 93 | 91 |
from . import _cachekey
|
| 94 | 92 |
from . import _signals
|
| ... | ... | @@ -1872,71 +1870,6 @@ class Element(Plugin): |
| 1872 | 1870 |
# Notify successful upload
|
| 1873 | 1871 |
return True
|
| 1874 | 1872 |
|
| 1875 |
- # _shell():
|
|
| 1876 |
- #
|
|
| 1877 |
- # Connects the terminal with a shell running in a staged
|
|
| 1878 |
- # environment
|
|
| 1879 |
- #
|
|
| 1880 |
- # Args:
|
|
| 1881 |
- # scope (Scope): Either BUILD or RUN scopes are valid, or None
|
|
| 1882 |
- # directory (str): A directory to an existing sandbox, or None
|
|
| 1883 |
- # mounts (list): A list of (str, str) tuples, representing host/target paths to mount
|
|
| 1884 |
- # isolate (bool): Whether to isolate the environment like we do in builds
|
|
| 1885 |
- # prompt (str): A suitable prompt string for PS1
|
|
| 1886 |
- # command (list): An argv to launch in the sandbox
|
|
| 1887 |
- #
|
|
| 1888 |
- # Returns: Exit code
|
|
| 1889 |
- #
|
|
| 1890 |
- # If directory is not specified, one will be staged using scope
|
|
| 1891 |
- def _shell(self, scope=None, directory=None, *, mounts=None, isolate=False, prompt=None, command=None):
|
|
| 1892 |
- |
|
| 1893 |
- with self._prepare_sandbox(scope, directory) as sandbox:
|
|
| 1894 |
- environment = self.get_environment()
|
|
| 1895 |
- environment = copy.copy(environment)
|
|
| 1896 |
- flags = SandboxFlags.INTERACTIVE | SandboxFlags.ROOT_READ_ONLY
|
|
| 1897 |
- |
|
| 1898 |
- # Fetch the main toplevel project, in case this is a junctioned
|
|
| 1899 |
- # subproject, we want to use the rules defined by the main one.
|
|
| 1900 |
- context = self._get_context()
|
|
| 1901 |
- project = context.get_toplevel_project()
|
|
| 1902 |
- shell_command, shell_environment, shell_host_files = project.get_shell_config()
|
|
| 1903 |
- |
|
| 1904 |
- if prompt is not None:
|
|
| 1905 |
- environment['PS1'] = prompt
|
|
| 1906 |
- |
|
| 1907 |
- # Special configurations for non-isolated sandboxes
|
|
| 1908 |
- if not isolate:
|
|
| 1909 |
- |
|
| 1910 |
- # Open the network, and reuse calling uid/gid
|
|
| 1911 |
- #
|
|
| 1912 |
- flags |= SandboxFlags.NETWORK_ENABLED | SandboxFlags.INHERIT_UID
|
|
| 1913 |
- |
|
| 1914 |
- # Apply project defined environment vars to set for a shell
|
|
| 1915 |
- for key, value in _yaml.node_items(shell_environment):
|
|
| 1916 |
- environment[key] = value
|
|
| 1917 |
- |
|
| 1918 |
- # Setup any requested bind mounts
|
|
| 1919 |
- if mounts is None:
|
|
| 1920 |
- mounts = []
|
|
| 1921 |
- |
|
| 1922 |
- for mount in shell_host_files + mounts:
|
|
| 1923 |
- if not os.path.exists(mount.host_path):
|
|
| 1924 |
- if not mount.optional:
|
|
| 1925 |
- self.warn("Not mounting non-existing host file: {}".format(mount.host_path))
|
|
| 1926 |
- else:
|
|
| 1927 |
- sandbox.mark_directory(mount.path)
|
|
| 1928 |
- sandbox._set_mount_source(mount.path, mount.host_path)
|
|
| 1929 |
- |
|
| 1930 |
- if command:
|
|
| 1931 |
- argv = [arg for arg in command]
|
|
| 1932 |
- else:
|
|
| 1933 |
- argv = shell_command
|
|
| 1934 |
- |
|
| 1935 |
- self.status("Running command", detail=" ".join(argv))
|
|
| 1936 |
- |
|
| 1937 |
- # Run shells with network enabled and readonly root.
|
|
| 1938 |
- return sandbox.run(argv, flags, env=environment)
|
|
| 1939 |
- |
|
| 1940 | 1873 |
# _open_workspace():
|
| 1941 | 1874 |
#
|
| 1942 | 1875 |
# "Open" a workspace for this element
|
| ... | ... | @@ -62,6 +62,8 @@ from buildstream import BuildElement |
| 62 | 62 |
class AutotoolsElement(BuildElement):
|
| 63 | 63 |
# Supports virtual directories (required for remote execution)
|
| 64 | 64 |
BST_VIRTUAL_DIRECTORY = True
|
| 65 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 66 |
+ BST_GRANULAR_STAGE = True
|
|
| 65 | 67 |
|
| 66 | 68 |
|
| 67 | 69 |
# Plugin entry point
|
| ... | ... | @@ -61,6 +61,8 @@ from buildstream import BuildElement |
| 61 | 61 |
class CMakeElement(BuildElement):
|
| 62 | 62 |
# Supports virtual directories (required for remote execution)
|
| 63 | 63 |
BST_VIRTUAL_DIRECTORY = True
|
| 64 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 65 |
+ BST_GRANULAR_STAGE = True
|
|
| 64 | 66 |
|
| 65 | 67 |
|
| 66 | 68 |
# Plugin entry point
|
| ... | ... | @@ -58,6 +58,9 @@ class ComposeElement(Element): |
| 58 | 58 |
# This plugin has been modified to avoid the use of Sandbox.get_directory
|
| 59 | 59 |
BST_VIRTUAL_DIRECTORY = True
|
| 60 | 60 |
|
| 61 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 62 |
+ BST_GRANULAR_STAGE = True
|
|
| 63 |
+ |
|
| 61 | 64 |
def configure(self, node):
|
| 62 | 65 |
self.node_validate(node, [
|
| 63 | 66 |
'integrate', 'include', 'exclude', 'include-orphans'
|
| ... | ... | @@ -36,7 +36,8 @@ from buildstream import BuildElement |
| 36 | 36 |
|
| 37 | 37 |
# Element implementation for the python 'distutils' kind.
|
| 38 | 38 |
class DistutilsElement(BuildElement):
|
| 39 |
- pass
|
|
| 39 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 40 |
+ BST_GRANULAR_STAGE = True
|
|
| 40 | 41 |
|
| 41 | 42 |
|
| 42 | 43 |
# Plugin entry point
|
| ... | ... | @@ -43,6 +43,8 @@ class ImportElement(BuildElement): |
| 43 | 43 |
|
| 44 | 44 |
# This plugin has been modified to avoid the use of Sandbox.get_directory
|
| 45 | 45 |
BST_VIRTUAL_DIRECTORY = True
|
| 46 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 47 |
+ BST_GRANULAR_STAGE = True
|
|
| 46 | 48 |
|
| 47 | 49 |
def configure(self, node):
|
| 48 | 50 |
self.source = self.node_subst_member(node, 'source')
|
| ... | ... | @@ -67,6 +69,9 @@ class ImportElement(BuildElement): |
| 67 | 69 |
def stage(self, sandbox):
|
| 68 | 70 |
pass
|
| 69 | 71 |
|
| 72 |
+ def post_integration_staging(self, sandbox):
|
|
| 73 |
+ pass
|
|
| 74 |
+ |
|
| 70 | 75 |
def assemble(self, sandbox):
|
| 71 | 76 |
|
| 72 | 77 |
# Stage sources into the input directory
|
| ... | ... | @@ -43,6 +43,8 @@ from buildstream import BuildElement |
| 43 | 43 |
class MakeElement(BuildElement):
|
| 44 | 44 |
# Supports virtual directories (required for remote execution)
|
| 45 | 45 |
BST_VIRTUAL_DIRECTORY = True
|
| 46 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 47 |
+ BST_GRANULAR_STAGE = True
|
|
| 46 | 48 |
|
| 47 | 49 |
|
| 48 | 50 |
# Plugin entry point
|
| ... | ... | @@ -36,7 +36,8 @@ from buildstream import BuildElement |
| 36 | 36 |
|
| 37 | 37 |
# Element implementation for the 'makemaker' kind.
|
| 38 | 38 |
class MakeMakerElement(BuildElement):
|
| 39 |
- pass
|
|
| 39 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 40 |
+ BST_GRANULAR_STAGE = True
|
|
| 40 | 41 |
|
| 41 | 42 |
|
| 42 | 43 |
# Plugin entry point
|
| ... | ... | @@ -36,7 +36,8 @@ from buildstream import BuildElement |
| 36 | 36 |
|
| 37 | 37 |
# Element implementation for the 'manual' kind.
|
| 38 | 38 |
class ManualElement(BuildElement):
|
| 39 |
- pass
|
|
| 39 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 40 |
+ BST_GRANULAR_STAGE = True
|
|
| 40 | 41 |
|
| 41 | 42 |
|
| 42 | 43 |
# Plugin entry point
|
| ... | ... | @@ -58,6 +58,8 @@ from buildstream import BuildElement |
| 58 | 58 |
class MesonElement(BuildElement):
|
| 59 | 59 |
# Supports virtual directories (required for remote execution)
|
| 60 | 60 |
BST_VIRTUAL_DIRECTORY = True
|
| 61 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 62 |
+ BST_GRANULAR_STAGE = True
|
|
| 61 | 63 |
|
| 62 | 64 |
|
| 63 | 65 |
# Plugin entry point
|
| ... | ... | @@ -36,7 +36,8 @@ from buildstream import BuildElement |
| 36 | 36 |
|
| 37 | 37 |
# Element implementation for the 'modulebuild' kind.
|
| 38 | 38 |
class ModuleBuildElement(BuildElement):
|
| 39 |
- pass
|
|
| 39 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 40 |
+ BST_GRANULAR_STAGE = True
|
|
| 40 | 41 |
|
| 41 | 42 |
|
| 42 | 43 |
# Plugin entry point
|
| ... | ... | @@ -36,7 +36,8 @@ from buildstream import BuildElement |
| 36 | 36 |
|
| 37 | 37 |
# Element implementation for the 'pip' kind.
|
| 38 | 38 |
class PipElement(BuildElement):
|
| 39 |
- pass
|
|
| 39 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 40 |
+ BST_GRANULAR_STAGE = True
|
|
| 40 | 41 |
|
| 41 | 42 |
|
| 42 | 43 |
# Plugin entry point
|
| ... | ... | @@ -38,6 +38,8 @@ from buildstream import BuildElement |
| 38 | 38 |
class QMakeElement(BuildElement):
|
| 39 | 39 |
# Supports virtual directories (required for remote execution)
|
| 40 | 40 |
BST_VIRTUAL_DIRECTORY = True
|
| 41 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 42 |
+ BST_GRANULAR_STAGE = True
|
|
| 41 | 43 |
|
| 42 | 44 |
|
| 43 | 45 |
# Plugin entry point
|
| ... | ... | @@ -42,6 +42,9 @@ import buildstream |
| 42 | 42 |
class ScriptElement(buildstream.ScriptElement):
|
| 43 | 43 |
# pylint: disable=attribute-defined-outside-init
|
| 44 | 44 |
|
| 45 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 46 |
+ BST_GRANULAR_STAGE = True
|
|
| 47 |
+ |
|
| 45 | 48 |
def configure(self, node):
|
| 46 | 49 |
for n in self.node_get_member(node, list, 'layout', []):
|
| 47 | 50 |
dst = self.node_subst_member(n, 'destination')
|
| ... | ... | @@ -33,6 +33,9 @@ class StackElement(Element): |
| 33 | 33 |
# This plugin has been modified to avoid the use of Sandbox.get_directory
|
| 34 | 34 |
BST_VIRTUAL_DIRECTORY = True
|
| 35 | 35 |
|
| 36 |
+ # This plugin has been modified to permit splitting stage, integration and post-stage
|
|
| 37 |
+ BST_GRANULAR_STAGE = True
|
|
| 38 |
+ |
|
| 36 | 39 |
def configure(self, node):
|
| 37 | 40 |
pass
|
| 38 | 41 |
|
| ... | ... | @@ -215,17 +215,32 @@ class ScriptElement(Element): |
| 215 | 215 |
def stage(self, sandbox):
|
| 216 | 216 |
|
| 217 | 217 |
# Stage the elements, and run integration commands where appropriate.
|
| 218 |
- if not self.__layout:
|
|
| 219 |
- # if no layout set, stage all dependencies into /
|
|
| 220 |
- for build_dep in self.dependencies(Scope.BUILD, recurse=False):
|
|
| 221 |
- with self.timed_activity("Staging {} at /"
|
|
| 222 |
- .format(build_dep.name), silent_nested=True):
|
|
| 223 |
- build_dep.stage_dependency_artifacts(sandbox, Scope.RUN, path="/")
|
|
| 218 |
+ self.stage_dependency_artifacts(sandbox, Scope.BUILD)
|
|
| 219 |
+ self.integrate_dependencies(sandbox, Scope.BUILD)
|
|
| 220 |
+ |
|
| 221 |
+ self.post_integration_staging(sandbox)
|
|
| 222 |
+ |
|
| 223 |
+ def stage_dependency_artifacts(self, sandbox, scope, *, path=None,
|
|
| 224 |
+ include=None, exclude=None, orphans=True, visited=None):
|
|
| 225 |
+ if scope is not Scope.BUILD:
|
|
| 226 |
+ super().stage_dependency_artifacts(sandbox, scope, path=path,
|
|
| 227 |
+ include=include,
|
|
| 228 |
+ exclude=exclude,
|
|
| 229 |
+ orphans=orphans,
|
|
| 230 |
+ visited=visited)
|
|
| 231 |
+ return
|
|
| 232 |
+ |
|
| 233 |
+ if path is None:
|
|
| 234 |
+ path = "/"
|
|
| 235 |
+ if visited is None:
|
|
| 236 |
+ visited = {}
|
|
| 224 | 237 |
|
| 238 |
+ if not self.__layout:
|
|
| 239 |
+ # if no layout set, stage all dependencies into path
|
|
| 225 | 240 |
for build_dep in self.dependencies(Scope.BUILD, recurse=False):
|
| 226 |
- with self.timed_activity("Integrating {}".format(build_dep.name), silent_nested=True):
|
|
| 227 |
- for dep in build_dep.dependencies(Scope.RUN):
|
|
| 228 |
- dep.integrate(sandbox)
|
|
| 241 |
+ with self.timed_activity("Staging {} at {}"
|
|
| 242 |
+ .format(build_dep.name, path), silent_nested=True):
|
|
| 243 |
+ build_dep.stage_dependency_artifacts(sandbox, Scope.RUN, path=path, visited=visited)
|
|
| 229 | 244 |
else:
|
| 230 | 245 |
# If layout, follow its rules.
|
| 231 | 246 |
for item in self.__layout:
|
| ... | ... | @@ -236,17 +251,33 @@ class ScriptElement(Element): |
| 236 | 251 |
|
| 237 | 252 |
element = self.search(Scope.BUILD, item['element'])
|
| 238 | 253 |
if item['destination'] == '/':
|
| 239 |
- with self.timed_activity("Staging {} at /".format(element.name),
|
|
| 254 |
+ with self.timed_activity("Staging {} at {}".format(element.name, path),
|
|
| 240 | 255 |
silent_nested=True):
|
| 241 |
- element.stage_dependency_artifacts(sandbox, Scope.RUN)
|
|
| 256 |
+ element.stage_dependency_artifacts(sandbox, Scope.RUN, path=path, visited=visited)
|
|
| 242 | 257 |
else:
|
| 258 |
+ subpath = os.path.join(path, item['destination'])
|
|
| 243 | 259 |
with self.timed_activity("Staging {} at {}"
|
| 244 |
- .format(element.name, item['destination']),
|
|
| 260 |
+ .format(element.name, subpath),
|
|
| 245 | 261 |
silent_nested=True):
|
| 246 | 262 |
virtual_dstdir = sandbox.get_virtual_directory()
|
| 247 |
- virtual_dstdir.descend(item['destination'].lstrip(os.sep).split(os.sep), create=True)
|
|
| 248 |
- element.stage_dependency_artifacts(sandbox, Scope.RUN, path=item['destination'])
|
|
| 263 |
+ virtual_dstdir.descend(subpath.lstrip(os.sep).split(os.sep), create=True)
|
|
| 264 |
+ element.stage_dependency_artifacts(sandbox, Scope.RUN, path=subpath, visited=visited)
|
|
| 265 |
+ |
|
| 266 |
+ def integrate_dependencies(self, sandbox, scope, *, visited=None):
|
|
| 267 |
+ if scope is not Scope.BUILD:
|
|
| 268 |
+ super().integrate_dependencies(sandbox, scope, visited=visited)
|
|
| 269 |
+ return
|
|
| 270 |
+ |
|
| 271 |
+ if visited is None:
|
|
| 272 |
+ visited = {}
|
|
| 249 | 273 |
|
| 274 |
+ if not self.__layout:
|
|
| 275 |
+ for build_dep in self.dependencies(Scope.BUILD, recurse=False):
|
|
| 276 |
+ with self.timed_activity("Integrating {}".format(build_dep.name), silent_nested=True):
|
|
| 277 |
+ for dep in build_dep.dependencies(Scope.RUN, visited=visited):
|
|
| 278 |
+ dep.integrate(sandbox)
|
|
| 279 |
+ else:
|
|
| 280 |
+ # If layout, follow its rules.
|
|
| 250 | 281 |
for item in self.__layout:
|
| 251 | 282 |
|
| 252 | 283 |
# Skip layout members which dont stage an element
|
| ... | ... | @@ -259,9 +290,10 @@ class ScriptElement(Element): |
| 259 | 290 |
if item['destination'] == '/':
|
| 260 | 291 |
with self.timed_activity("Integrating {}".format(element.name),
|
| 261 | 292 |
silent_nested=True):
|
| 262 |
- for dep in element.dependencies(Scope.RUN):
|
|
| 293 |
+ for dep in element.dependencies(Scope.RUN, visited=visited):
|
|
| 263 | 294 |
dep.integrate(sandbox)
|
| 264 | 295 |
|
| 296 |
+ def post_integration_staging(self, sandbox):
|
|
| 265 | 297 |
install_root_path_components = self.__install_root.lstrip(os.sep).split(os.sep)
|
| 266 | 298 |
sandbox.get_virtual_directory().descend(install_root_path_components, create=True)
|
| 267 | 299 |
|
| 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
|
| ... | ... | @@ -29,9 +29,11 @@ DATA_DIR = os.path.join( |
| 29 | 29 |
# element (str): The element to build and run a shell with
|
| 30 | 30 |
# isolate (bool): Whether to pass --isolate to `bst shell`
|
| 31 | 31 |
#
|
| 32 |
-def execute_shell(cli, project, command, *, config=None, mount=None, element='base.bst', isolate=False):
|
|
| 32 |
+def execute_shell(cli, project, command, *, config=None, mount=None, element='base.bst', elements=None, isolate=False):
|
|
| 33 | 33 |
# Ensure the element is built
|
| 34 |
- result = cli.run(project=project, project_config=config, args=['build', element])
|
|
| 34 |
+ if elements is None:
|
|
| 35 |
+ elements = [element]
|
|
| 36 |
+ result = cli.run(project=project, project_config=config, args=['build'] + elements)
|
|
| 35 | 37 |
assert result.exit_code == 0
|
| 36 | 38 |
|
| 37 | 39 |
args = ['shell']
|
| ... | ... | @@ -40,7 +42,7 @@ def execute_shell(cli, project, command, *, config=None, mount=None, element='ba |
| 40 | 42 |
if mount is not None:
|
| 41 | 43 |
host_path, target_path = mount
|
| 42 | 44 |
args += ['--mount', host_path, target_path]
|
| 43 |
- args += [element, '--'] + command
|
|
| 45 |
+ args += ["-e" + e for e in elements] + ['--'] + command
|
|
| 44 | 46 |
|
| 45 | 47 |
return cli.run(project=project, project_config=config, args=args)
|
| 46 | 48 |
|
| ... | ... | @@ -345,10 +347,51 @@ def test_sysroot_workspace_visible(cli, tmpdir, datafiles): |
| 345 | 347 |
|
| 346 | 348 |
|
| 347 | 349 |
# Test system integration commands can access devices in /dev
|
| 350 |
+@pytest.mark.integration
|
|
| 348 | 351 |
@pytest.mark.datafiles(DATA_DIR)
|
| 349 | 352 |
def test_integration_devices(cli, tmpdir, datafiles):
|
| 350 | 353 |
project = os.path.join(datafiles.dirname, datafiles.basename)
|
| 351 |
- element_name = 'integration.bst'
|
|
| 354 |
+ element_name = 'shell/integration.bst'
|
|
| 352 | 355 |
|
| 353 | 356 |
result = execute_shell(cli, project, ["true"], element=element_name)
|
| 354 | 357 |
assert result.exit_code == 0
|
| 358 |
+ |
|
| 359 |
+ |
|
| 360 |
+# Test multiple element shell
|
|
| 361 |
+@pytest.mark.integration
|
|
| 362 |
+@pytest.mark.datafiles(DATA_DIR)
|
|
| 363 |
+def test_shell_multiple_elements(cli, tmpdir, datafiles):
|
|
| 364 |
+ project = os.path.join(datafiles.dirname, datafiles.basename)
|
|
| 365 |
+ |
|
| 366 |
+ result = execute_shell(cli, project, ["sh", "-c", "foo && bar"],
|
|
| 367 |
+ elements=["shell/adds-foo.bst", "shell/adds-bar.bst"])
|
|
| 368 |
+ assert result.exit_code == 0
|
|
| 369 |
+ |
|
| 370 |
+ |
|
| 371 |
+# Test multiple element build shell
|
|
| 372 |
+@pytest.mark.integration
|
|
| 373 |
+@pytest.mark.datafiles(DATA_DIR)
|
|
| 374 |
+def test_shell_multiple_workspace(cli, tmpdir, datafiles):
|
|
| 375 |
+ project = os.path.join(datafiles.dirname, datafiles.basename)
|
|
| 376 |
+ elements = {'workspace/workspace-mount.bst': os.path.join(cli.directory, 'workspace-mount'),
|
|
| 377 |
+ 'make/makehello.bst': os.path.join(cli.directory, 'makehello')}
|
|
| 378 |
+ |
|
| 379 |
+ for element, workspace in elements.items():
|
|
| 380 |
+ res = cli.run(project=project, args=['workspace', 'open', element, workspace])
|
|
| 381 |
+ assert res.exit_code == 0
|
|
| 382 |
+ |
|
| 383 |
+ for workspace in elements.values():
|
|
| 384 |
+ with open(os.path.join(workspace, "workspace-exists"), "w") as f:
|
|
| 385 |
+ pass
|
|
| 386 |
+ |
|
| 387 |
+ # Ensure the dependencies of our build failing element are built
|
|
| 388 |
+ result = cli.run(project=project, args=['build', 'base.bst'])
|
|
| 389 |
+ assert result.exit_code == 0
|
|
| 390 |
+ |
|
| 391 |
+ args = ['shell', '--build'] + ['-e' + e for e in elements]
|
|
| 392 |
+ args += ['--', 'sh', '-c',
|
|
| 393 |
+ 'test -e /buildstream/test/workspace/workspace-mount.bst/workspace-exists && \
|
|
| 394 |
+ test -e /buildstream/test/make/makehello.bst/workspace-exists']
|
|
| 395 |
+ result = cli.run(project=project, args=args)
|
|
| 396 |
+ assert result.exit_code == 0
|
|
| 397 |
+ assert result.output == ''
|
