Tom Pollard pushed to branch tpollard/752 at BuildStream / buildstream
Commits:
-
da735e56
by Richard Maw at 2018-11-14T13:30:34Z
-
e7633500
by Richard Maw at 2018-11-14T13:30:34Z
-
e9e08823
by Richard Maw at 2018-11-14T13:30:34Z
-
90ca007e
by Richard Maw at 2018-11-14T13:30:34Z
-
327b19dd
by richardmaw-codethink at 2018-11-14T13:59:16Z
-
aca7c222
by Tom Pollard at 2018-11-14T17:48:55Z
-
f0203b99
by Tom Pollard at 2018-11-14T17:48:55Z
16 changed files:
- NEWS
- buildstream/_artifactcache/artifactcache.py
- buildstream/_context.py
- buildstream/_frontend/app.py
- buildstream/_frontend/cli.py
- buildstream/_platform/linux.py
- buildstream/_site.py
- buildstream/sandbox/_sandboxbwrap.py
- tests/completions/completions.py
- tests/frontend/pull.py
- tests/frontend/push.py
- + tests/integration/project/elements/sandbox-bwrap/break-shell.bst
- + tests/integration/project/elements/sandbox-bwrap/command-exit-42.bst
- + tests/integration/project/elements/sandbox-bwrap/non-executable-shell.bst
- tests/integration/sandbox-bwrap.py
- tests/testutils/site.py
Changes:
| ... | ... | @@ -45,6 +45,15 @@ buildstream 1.3.1 |
| 45 | 45 |
instead of just a specially-formatted build-root with a `root` and `scratch`
|
| 46 | 46 |
subdirectory.
|
| 47 | 47 |
|
| 48 |
+ o bst interaction with defined artifact servers can be controlled more granularly.
|
|
| 49 |
+ This can be done via the user configuration option `useremotes` or via the bst cli
|
|
| 50 |
+ main option '--use-remotes'. This can be set as 'none', 'user' or the default value
|
|
| 51 |
+ 'all'. Unless specifically overriden, when considering wether to pull or push to
|
|
| 52 |
+ available artifact servers (be it user or project defined) this optional config option
|
|
| 53 |
+ will be used. Setting this value to 'user' for example and performing a build would
|
|
| 54 |
+ lead to any project or junction defined artifact server to be ignored, whilst still
|
|
| 55 |
+ attempting to any user defined remotes.
|
|
| 56 |
+ |
|
| 48 | 57 |
|
| 49 | 58 |
=================
|
| 50 | 59 |
buildstream 1.1.5
|
| ... | ... | @@ -156,7 +156,7 @@ class ArtifactCache(): |
| 156 | 156 |
# Sets up which remotes to use
|
| 157 | 157 |
#
|
| 158 | 158 |
# Args:
|
| 159 |
- # use_config (bool): Whether to use project configuration
|
|
| 159 |
+ # use_config (bool): Whether to use configuration
|
|
| 160 | 160 |
# remote_url (str): Remote artifact cache URL
|
| 161 | 161 |
#
|
| 162 | 162 |
# This requires that all of the projects which are to be processed in the session
|
| ... | ... | @@ -175,11 +175,16 @@ class ArtifactCache(): |
| 175 | 175 |
self._set_remotes([ArtifactCacheSpec(remote_url, push=True)])
|
| 176 | 176 |
has_remote_caches = True
|
| 177 | 177 |
if use_config:
|
| 178 |
- for project in self.context.get_projects():
|
|
| 179 |
- artifact_caches = _configured_remote_artifact_cache_specs(self.context, project)
|
|
| 180 |
- if artifact_caches: # artifact_caches is a list of ArtifactCacheSpec instances
|
|
| 181 |
- self._set_remotes(artifact_caches, project=project)
|
|
| 182 |
- has_remote_caches = True
|
|
| 178 |
+ if self.context.use_remotes == 'all':
|
|
| 179 |
+ for project in self.context.get_projects():
|
|
| 180 |
+ artifact_caches = _configured_remote_artifact_cache_specs(self.context, project)
|
|
| 181 |
+ if artifact_caches: # artifact_caches is a list of ArtifactCacheSpec instances
|
|
| 182 |
+ self._set_remotes(artifact_caches, project=project)
|
|
| 183 |
+ has_remote_caches = True
|
|
| 184 |
+ # If configured to only use user configured remotes, pass existing user cache spec
|
|
| 185 |
+ elif self.context.use_remotes == 'user' and self.context.artifact_cache_specs:
|
|
| 186 |
+ self._set_remotes(self.context.artifact_cache_specs)
|
|
| 187 |
+ has_remote_caches = True
|
|
| 183 | 188 |
if has_remote_caches:
|
| 184 | 189 |
self._initialize_remotes()
|
| 185 | 190 |
|
| ... | ... | @@ -110,6 +110,9 @@ class Context(): |
| 110 | 110 |
# Make sure the XDG vars are set in the environment before loading anything
|
| 111 | 111 |
self._init_xdg()
|
| 112 | 112 |
|
| 113 |
+ # Which remote artifact servers to interact with. all, user or none
|
|
| 114 |
+ self.use_remotes = 'all'
|
|
| 115 |
+ |
|
| 113 | 116 |
# Private variables
|
| 114 | 117 |
self._cache_key = None
|
| 115 | 118 |
self._message_handler = None
|
| ... | ... | @@ -160,7 +163,7 @@ class Context(): |
| 160 | 163 |
_yaml.node_validate(defaults, [
|
| 161 | 164 |
'sourcedir', 'builddir', 'artifactdir', 'logdir',
|
| 162 | 165 |
'scheduler', 'artifacts', 'logging', 'projects',
|
| 163 |
- 'cache'
|
|
| 166 |
+ 'cache', 'useremotes'
|
|
| 164 | 167 |
])
|
| 165 | 168 |
|
| 166 | 169 |
for directory in ['sourcedir', 'builddir', 'artifactdir', 'logdir']:
|
| ... | ... | @@ -185,6 +188,13 @@ class Context(): |
| 185 | 188 |
# Load artifact share configuration
|
| 186 | 189 |
self.artifact_cache_specs = ArtifactCache.specs_from_config_node(defaults)
|
| 187 | 190 |
|
| 191 |
+ # Load remote artifact server usage
|
|
| 192 |
+ self.use_remotes = _yaml.node_get(defaults, str, 'useremotes', default_value='all')
|
|
| 193 |
+ valid_actions = ['all', 'user', 'none']
|
|
| 194 |
+ if self.use_remotes not in valid_actions:
|
|
| 195 |
+ raise LoadError(LoadErrorReason.INVALID_DATA,
|
|
| 196 |
+ "useremotes should be one of: {}".format(", ".join(valid_actions)))
|
|
| 197 |
+ |
|
| 188 | 198 |
# Load logging config
|
| 189 | 199 |
logging = _yaml.node_get(defaults, Mapping, 'logging')
|
| 190 | 200 |
_yaml.node_validate(logging, [
|
| ... | ... | @@ -182,7 +182,8 @@ class App(): |
| 182 | 182 |
'fetchers': 'sched_fetchers',
|
| 183 | 183 |
'builders': 'sched_builders',
|
| 184 | 184 |
'pushers': 'sched_pushers',
|
| 185 |
- 'network_retries': 'sched_network_retries'
|
|
| 185 |
+ 'network_retries': 'sched_network_retries',
|
|
| 186 |
+ 'use_remotes': 'use_remotes'
|
|
| 186 | 187 |
}
|
| 187 | 188 |
for cli_option, context_attr in override_map.items():
|
| 188 | 189 |
option_value = self._main_options.get(cli_option)
|
| ... | ... | @@ -219,6 +219,9 @@ def print_version(ctx, param, value): |
| 219 | 219 |
help="Specify a project option")
|
| 220 | 220 |
@click.option('--default-mirror', default=None,
|
| 221 | 221 |
help="The mirror to fetch from first, before attempting other mirrors")
|
| 222 |
+@click.option('--use-remotes', default='all',
|
|
| 223 |
+ type=click.Choice(['all', 'user', 'none']),
|
|
| 224 |
+ help='The remote artifact caches to interact with (default: all)')
|
|
| 222 | 225 |
@click.pass_context
|
| 223 | 226 |
def cli(context, **kwargs):
|
| 224 | 227 |
"""Build and manipulate BuildStream projects
|
| ... | ... | @@ -18,9 +18,9 @@ |
| 18 | 18 |
# Tristan Maat <tristan maat codethink co uk>
|
| 19 | 19 |
|
| 20 | 20 |
import os
|
| 21 |
-import shutil
|
|
| 22 | 21 |
import subprocess
|
| 23 | 22 |
|
| 23 |
+from .. import _site
|
|
| 24 | 24 |
from .. import utils
|
| 25 | 25 |
from ..sandbox import SandboxDummy
|
| 26 | 26 |
|
| ... | ... | @@ -38,16 +38,18 @@ class Linux(Platform): |
| 38 | 38 |
|
| 39 | 39 |
self._have_fuse = os.path.exists("/dev/fuse")
|
| 40 | 40 |
|
| 41 |
- bwrap_version = self._get_bwrap_version()
|
|
| 41 |
+ bwrap_version = _site.get_bwrap_version()
|
|
| 42 | 42 |
|
| 43 | 43 |
if bwrap_version is None:
|
| 44 | 44 |
self._bwrap_exists = False
|
| 45 | 45 |
self._have_good_bwrap = False
|
| 46 | 46 |
self._die_with_parent_available = False
|
| 47 |
+ self._json_status_available = False
|
|
| 47 | 48 |
else:
|
| 48 | 49 |
self._bwrap_exists = True
|
| 49 | 50 |
self._have_good_bwrap = (0, 1, 2) <= bwrap_version
|
| 50 | 51 |
self._die_with_parent_available = (0, 1, 8) <= bwrap_version
|
| 52 |
+ self._json_status_available = (0, 3, 2) <= bwrap_version
|
|
| 51 | 53 |
|
| 52 | 54 |
self._local_sandbox_available = self._have_fuse and self._have_good_bwrap
|
| 53 | 55 |
|
| ... | ... | @@ -97,6 +99,7 @@ class Linux(Platform): |
| 97 | 99 |
# Inform the bubblewrap sandbox as to whether it can use user namespaces or not
|
| 98 | 100 |
kwargs['user_ns_available'] = self._user_ns_available
|
| 99 | 101 |
kwargs['die_with_parent_available'] = self._die_with_parent_available
|
| 102 |
+ kwargs['json_status_available'] = self._json_status_available
|
|
| 100 | 103 |
return SandboxBwrap(*args, **kwargs)
|
| 101 | 104 |
|
| 102 | 105 |
def _check_user_ns_available(self):
|
| ... | ... | @@ -119,21 +122,3 @@ class Linux(Platform): |
| 119 | 122 |
output = ''
|
| 120 | 123 |
|
| 121 | 124 |
return output == 'root'
|
| 122 |
- |
|
| 123 |
- def _get_bwrap_version(self):
|
|
| 124 |
- # Get the current bwrap version
|
|
| 125 |
- #
|
|
| 126 |
- # returns None if no bwrap was found
|
|
| 127 |
- # otherwise returns a tuple of 3 int: major, minor, patch
|
|
| 128 |
- bwrap_path = shutil.which('bwrap')
|
|
| 129 |
- |
|
| 130 |
- if not bwrap_path:
|
|
| 131 |
- return None
|
|
| 132 |
- |
|
| 133 |
- cmd = [bwrap_path, "--version"]
|
|
| 134 |
- try:
|
|
| 135 |
- version = str(subprocess.check_output(cmd).split()[1], "utf-8")
|
|
| 136 |
- except subprocess.CalledProcessError:
|
|
| 137 |
- return None
|
|
| 138 |
- |
|
| 139 |
- return tuple(int(x) for x in version.split("."))
|
| ... | ... | @@ -18,6 +18,8 @@ |
| 18 | 18 |
# Tristan Van Berkom <tristan vanberkom codethink co uk>
|
| 19 | 19 |
|
| 20 | 20 |
import os
|
| 21 |
+import shutil
|
|
| 22 |
+import subprocess
|
|
| 21 | 23 |
|
| 22 | 24 |
#
|
| 23 | 25 |
# Private module declaring some info about where the buildstream
|
| ... | ... | @@ -44,3 +46,22 @@ build_all_template = os.path.join(root, 'data', 'build-all.sh.in') |
| 44 | 46 |
|
| 45 | 47 |
# Module building script template
|
| 46 | 48 |
build_module_template = os.path.join(root, 'data', 'build-module.sh.in')
|
| 49 |
+ |
|
| 50 |
+ |
|
| 51 |
+def get_bwrap_version():
|
|
| 52 |
+ # Get the current bwrap version
|
|
| 53 |
+ #
|
|
| 54 |
+ # returns None if no bwrap was found
|
|
| 55 |
+ # otherwise returns a tuple of 3 int: major, minor, patch
|
|
| 56 |
+ bwrap_path = shutil.which('bwrap')
|
|
| 57 |
+ |
|
| 58 |
+ if not bwrap_path:
|
|
| 59 |
+ return None
|
|
| 60 |
+ |
|
| 61 |
+ cmd = [bwrap_path, "--version"]
|
|
| 62 |
+ try:
|
|
| 63 |
+ version = str(subprocess.check_output(cmd).split()[1], "utf-8")
|
|
| 64 |
+ except subprocess.CalledProcessError:
|
|
| 65 |
+ return None
|
|
| 66 |
+ |
|
| 67 |
+ return tuple(int(x) for x in version.split("."))
|
| ... | ... | @@ -17,6 +17,8 @@ |
| 17 | 17 |
# Authors:
|
| 18 | 18 |
# Andrew Leeming <andrew leeming codethink co uk>
|
| 19 | 19 |
# Tristan Van Berkom <tristan vanberkom codethink co uk>
|
| 20 |
+import collections
|
|
| 21 |
+import json
|
|
| 20 | 22 |
import os
|
| 21 | 23 |
import sys
|
| 22 | 24 |
import time
|
| ... | ... | @@ -24,7 +26,8 @@ import errno |
| 24 | 26 |
import signal
|
| 25 | 27 |
import subprocess
|
| 26 | 28 |
import shutil
|
| 27 |
-from contextlib import ExitStack
|
|
| 29 |
+from contextlib import ExitStack, suppress
|
|
| 30 |
+from tempfile import TemporaryFile
|
|
| 28 | 31 |
|
| 29 | 32 |
import psutil
|
| 30 | 33 |
|
| ... | ... | @@ -53,6 +56,7 @@ class SandboxBwrap(Sandbox): |
| 53 | 56 |
super().__init__(*args, **kwargs)
|
| 54 | 57 |
self.user_ns_available = kwargs['user_ns_available']
|
| 55 | 58 |
self.die_with_parent_available = kwargs['die_with_parent_available']
|
| 59 |
+ self.json_status_available = kwargs['json_status_available']
|
|
| 56 | 60 |
|
| 57 | 61 |
def run(self, command, flags, *, cwd=None, env=None):
|
| 58 | 62 |
stdout, stderr = self._get_output()
|
| ... | ... | @@ -160,24 +164,31 @@ class SandboxBwrap(Sandbox): |
| 160 | 164 |
gid = self._get_config().build_gid
|
| 161 | 165 |
bwrap_command += ['--uid', str(uid), '--gid', str(gid)]
|
| 162 | 166 |
|
| 163 |
- # Add the command
|
|
| 164 |
- bwrap_command += command
|
|
| 165 |
- |
|
| 166 |
- # bwrap might create some directories while being suid
|
|
| 167 |
- # and may give them to root gid, if it does, we'll want
|
|
| 168 |
- # to clean them up after, so record what we already had
|
|
| 169 |
- # there just in case so that we can safely cleanup the debris.
|
|
| 170 |
- #
|
|
| 171 |
- existing_basedirs = {
|
|
| 172 |
- directory: os.path.exists(os.path.join(root_directory, directory))
|
|
| 173 |
- for directory in ['tmp', 'dev', 'proc']
|
|
| 174 |
- }
|
|
| 175 |
- |
|
| 176 |
- # Use the MountMap context manager to ensure that any redirected
|
|
| 177 |
- # mounts through fuse layers are in context and ready for bwrap
|
|
| 178 |
- # to mount them from.
|
|
| 179 |
- #
|
|
| 180 | 167 |
with ExitStack() as stack:
|
| 168 |
+ pass_fds = ()
|
|
| 169 |
+ # Improve error reporting with json-status if available
|
|
| 170 |
+ if self.json_status_available:
|
|
| 171 |
+ json_status_file = stack.enter_context(TemporaryFile())
|
|
| 172 |
+ pass_fds = (json_status_file.fileno(),)
|
|
| 173 |
+ bwrap_command += ['--json-status-fd', str(json_status_file.fileno())]
|
|
| 174 |
+ |
|
| 175 |
+ # Add the command
|
|
| 176 |
+ bwrap_command += command
|
|
| 177 |
+ |
|
| 178 |
+ # bwrap might create some directories while being suid
|
|
| 179 |
+ # and may give them to root gid, if it does, we'll want
|
|
| 180 |
+ # to clean them up after, so record what we already had
|
|
| 181 |
+ # there just in case so that we can safely cleanup the debris.
|
|
| 182 |
+ #
|
|
| 183 |
+ existing_basedirs = {
|
|
| 184 |
+ directory: os.path.exists(os.path.join(root_directory, directory))
|
|
| 185 |
+ for directory in ['tmp', 'dev', 'proc']
|
|
| 186 |
+ }
|
|
| 187 |
+ |
|
| 188 |
+ # Use the MountMap context manager to ensure that any redirected
|
|
| 189 |
+ # mounts through fuse layers are in context and ready for bwrap
|
|
| 190 |
+ # to mount them from.
|
|
| 191 |
+ #
|
|
| 181 | 192 |
stack.enter_context(mount_map.mounted(self))
|
| 182 | 193 |
|
| 183 | 194 |
# If we're interactive, we want to inherit our stdin,
|
| ... | ... | @@ -190,7 +201,7 @@ class SandboxBwrap(Sandbox): |
| 190 | 201 |
|
| 191 | 202 |
# Run bubblewrap !
|
| 192 | 203 |
exit_code = self.run_bwrap(bwrap_command, stdin, stdout, stderr,
|
| 193 |
- (flags & SandboxFlags.INTERACTIVE))
|
|
| 204 |
+ (flags & SandboxFlags.INTERACTIVE), pass_fds)
|
|
| 194 | 205 |
|
| 195 | 206 |
# Cleanup things which bwrap might have left behind, while
|
| 196 | 207 |
# everything is still mounted because bwrap can be creating
|
| ... | ... | @@ -238,10 +249,27 @@ class SandboxBwrap(Sandbox): |
| 238 | 249 |
# a bug, bwrap mounted a tempfs here and when it exits, that better be empty.
|
| 239 | 250 |
pass
|
| 240 | 251 |
|
| 252 |
+ if self.json_status_available:
|
|
| 253 |
+ json_status_file.seek(0, 0)
|
|
| 254 |
+ child_exit_code = None
|
|
| 255 |
+ # The JSON status file's output is a JSON object per line
|
|
| 256 |
+ # with the keys present identifying the type of message.
|
|
| 257 |
+ # The only message relevant to us now is the exit-code of the subprocess.
|
|
| 258 |
+ for line in json_status_file:
|
|
| 259 |
+ with suppress(json.decoder.JSONDecodeError):
|
|
| 260 |
+ o = json.loads(line)
|
|
| 261 |
+ if isinstance(o, collections.abc.Mapping) and 'exit-code' in o:
|
|
| 262 |
+ child_exit_code = o['exit-code']
|
|
| 263 |
+ break
|
|
| 264 |
+ if child_exit_code is None:
|
|
| 265 |
+ raise SandboxError("`bwrap' terminated during sandbox setup with exitcode {}".format(exit_code),
|
|
| 266 |
+ reason="bwrap-sandbox-fail")
|
|
| 267 |
+ exit_code = child_exit_code
|
|
| 268 |
+ |
|
| 241 | 269 |
self._vdir._mark_changed()
|
| 242 | 270 |
return exit_code
|
| 243 | 271 |
|
| 244 |
- def run_bwrap(self, argv, stdin, stdout, stderr, interactive):
|
|
| 272 |
+ def run_bwrap(self, argv, stdin, stdout, stderr, interactive, pass_fds):
|
|
| 245 | 273 |
# Wrapper around subprocess.Popen() with common settings.
|
| 246 | 274 |
#
|
| 247 | 275 |
# This function blocks until the subprocess has terminated.
|
| ... | ... | @@ -317,6 +345,7 @@ class SandboxBwrap(Sandbox): |
| 317 | 345 |
# The default is to share file descriptors from the parent process
|
| 318 | 346 |
# to the subprocess, which is rarely good for sandboxing.
|
| 319 | 347 |
close_fds=True,
|
| 348 |
+ pass_fds=pass_fds,
|
|
| 320 | 349 |
stdin=stdin,
|
| 321 | 350 |
stdout=stdout,
|
| 322 | 351 |
stderr=stderr,
|
| ... | ... | @@ -44,6 +44,7 @@ MAIN_OPTIONS = [ |
| 44 | 44 |
"--on-error ",
|
| 45 | 45 |
"--pushers ",
|
| 46 | 46 |
"--strict ",
|
| 47 |
+ "--use-remotes ",
|
|
| 47 | 48 |
"--verbose ",
|
| 48 | 49 |
"--version ",
|
| 49 | 50 |
]
|
| ... | ... | @@ -117,6 +118,7 @@ def test_options(cli, cmd, word_idx, expected): |
| 117 | 118 |
|
| 118 | 119 |
@pytest.mark.parametrize("cmd,word_idx,expected", [
|
| 119 | 120 |
('bst --on-error ', 2, ['continue ', 'quit ', 'terminate ']),
|
| 121 |
+ ('bst --use-remotes ', 2, ['all ', 'user ', 'none ']),
|
|
| 120 | 122 |
('bst show --deps ', 3, ['all ', 'build ', 'none ', 'plan ', 'run ']),
|
| 121 | 123 |
('bst show --deps=', 2, ['all ', 'build ', 'none ', 'plan ', 'run ']),
|
| 122 | 124 |
('bst show --deps b', 3, ['build ']),
|
| ... | ... | @@ -358,3 +358,75 @@ def test_pull_missing_notifies_user(caplog, cli, tmpdir, datafiles): |
| 358 | 358 |
|
| 359 | 359 |
assert "INFO Remote ({}) does not have".format(share.repo) in result.stderr
|
| 360 | 360 |
assert "SKIPPED Pull" in result.stderr
|
| 361 |
+ |
|
| 362 |
+ |
|
| 363 |
+# Tests that:
|
|
| 364 |
+#
|
|
| 365 |
+# * The bst main option --use-remotes limits remote action
|
|
| 366 |
+# as expected for pull jobs
|
|
| 367 |
+#
|
|
| 368 |
+@pytest.mark.datafiles(DATA_DIR)
|
|
| 369 |
+def test_useremotes_cli_options(cli, tmpdir, datafiles):
|
|
| 370 |
+ project = os.path.join(datafiles.dirname, datafiles.basename)
|
|
| 371 |
+ |
|
| 372 |
+ with create_artifact_share(os.path.join(str(tmpdir), 'artifactshare1')) as shareuser,\
|
|
| 373 |
+ create_artifact_share(os.path.join(str(tmpdir), 'artifactshare2')) as shareproject:
|
|
| 374 |
+ |
|
| 375 |
+ # Add shareproject repo url to project.conf
|
|
| 376 |
+ with open(os.path.join(project, "project.conf"), "a") as projconf:
|
|
| 377 |
+ projconf.write("artifacts:\n url: {}\n push: True".format(shareproject.repo))
|
|
| 378 |
+ |
|
| 379 |
+ # First build the target element and push to the remotes.
|
|
| 380 |
+ # We need the artifact available in the remotes to test against.
|
|
| 381 |
+ cli.configure({
|
|
| 382 |
+ 'artifacts': {'url': shareuser.repo, 'push': True}
|
|
| 383 |
+ })
|
|
| 384 |
+ result = cli.run(project=project, args=['build', 'target.bst'])
|
|
| 385 |
+ result.assert_success()
|
|
| 386 |
+ assert cli.get_element_state(project, 'target.bst') == 'cached'
|
|
| 387 |
+ |
|
| 388 |
+ # Assert that everything is now cached in the remotes.
|
|
| 389 |
+ all_elements = ['target.bst', 'import-bin.bst', 'compose-all.bst']
|
|
| 390 |
+ for element_name in all_elements:
|
|
| 391 |
+ assert_shared(cli, shareuser, project, element_name)
|
|
| 392 |
+ assert_shared(cli, shareproject, project, element_name)
|
|
| 393 |
+ |
|
| 394 |
+ # Now we've pushed, delete the user's local artifact cache
|
|
| 395 |
+ artifacts = os.path.join(cli.directory, 'artifacts')
|
|
| 396 |
+ shutil.rmtree(artifacts)
|
|
| 397 |
+ |
|
| 398 |
+ # Assert that nothing is cached locally anymore
|
|
| 399 |
+ for element_name in all_elements:
|
|
| 400 |
+ assert cli.get_element_state(project, element_name) != 'cached'
|
|
| 401 |
+ |
|
| 402 |
+ # Attempt bst build with --use-remotes set as none, this should lead to
|
|
| 403 |
+ # a complete rebuild without pulling from either artifact remote cache
|
|
| 404 |
+ result = cli.run(project=project, args=['--use-remotes', 'none', 'build', 'target.bst'])
|
|
| 405 |
+ result.assert_success()
|
|
| 406 |
+ for element_name in all_elements:
|
|
| 407 |
+ assert element_name not in result.get_pulled_elements()
|
|
| 408 |
+ |
|
| 409 |
+ # Delete local cache again
|
|
| 410 |
+ artifacts = os.path.join(cli.directory, 'artifacts')
|
|
| 411 |
+ shutil.rmtree(artifacts)
|
|
| 412 |
+ |
|
| 413 |
+ # Attempt bst build with --use-remotes set as user, as the shareuser is
|
|
| 414 |
+ # passed in as user config and not via a project, assert project remote
|
|
| 415 |
+ # was not attempted by it not being in the output
|
|
| 416 |
+ result = cli.run(project=project, args=['--use-remotes', 'user', 'build', 'target.bst'])
|
|
| 417 |
+ result.assert_success()
|
|
| 418 |
+ for element_name in all_elements:
|
|
| 419 |
+ assert element_name in result.get_pulled_elements()
|
|
| 420 |
+ assert shareproject.repo not in result.stderr
|
|
| 421 |
+ |
|
| 422 |
+ # Delete local cache again
|
|
| 423 |
+ artifacts = os.path.join(cli.directory, 'artifacts')
|
|
| 424 |
+ shutil.rmtree(artifacts)
|
|
| 425 |
+ |
|
| 426 |
+ # Attempt bst build with --use-remotes set as all, this time
|
|
| 427 |
+ # assert that project remote is attempted and in the output
|
|
| 428 |
+ result = cli.run(project=project, args=['--use-remotes', 'all', 'build', 'target.bst'])
|
|
| 429 |
+ result.assert_success()
|
|
| 430 |
+ for element_name in all_elements:
|
|
| 431 |
+ assert element_name in result.get_pulled_elements()
|
|
| 432 |
+ assert shareproject.repo in result.stderr
|
| ... | ... | @@ -409,3 +409,68 @@ def test_push_already_cached(caplog, cli, tmpdir, datafiles): |
| 409 | 409 |
assert not result.get_pushed_elements(), "No elements should have been pushed since the cache was populated"
|
| 410 | 410 |
assert "INFO Remote ({}) already has ".format(share.repo) in result.stderr
|
| 411 | 411 |
assert "SKIPPED Push" in result.stderr
|
| 412 |
+ |
|
| 413 |
+ |
|
| 414 |
+# Tests that:
|
|
| 415 |
+#
|
|
| 416 |
+# * The bst main option --use-remotes limits remote action
|
|
| 417 |
+# as expected for push jobs
|
|
| 418 |
+#
|
|
| 419 |
+@pytest.mark.datafiles(DATA_DIR)
|
|
| 420 |
+def test_useremotes_cli_options(cli, tmpdir, datafiles):
|
|
| 421 |
+ project = os.path.join(datafiles.dirname, datafiles.basename)
|
|
| 422 |
+ |
|
| 423 |
+ with create_artifact_share(os.path.join(str(tmpdir), 'artifactshare1')) as shareuser,\
|
|
| 424 |
+ create_artifact_share(os.path.join(str(tmpdir), 'artifactshare2')) as shareproject:
|
|
| 425 |
+ |
|
| 426 |
+ # Add shareproject repo url to project.conf
|
|
| 427 |
+ with open(os.path.join(project, "project.conf"), "a") as projconf:
|
|
| 428 |
+ projconf.write("artifacts:\n url: {}\n push: True".format(shareproject.repo))
|
|
| 429 |
+ |
|
| 430 |
+ # Configure shareuser remote in user conf
|
|
| 431 |
+ cli.configure({
|
|
| 432 |
+ 'artifacts': {'url': shareuser.repo, 'push': True}
|
|
| 433 |
+ })
|
|
| 434 |
+ |
|
| 435 |
+ # First build the target element with --use-remotes set as none.
|
|
| 436 |
+ # This should lead to a complete build without pushing to either artifact
|
|
| 437 |
+ # remote cache
|
|
| 438 |
+ result = cli.run(project=project, args=['--use-remotes', 'none', 'build', 'target.bst'])
|
|
| 439 |
+ result.assert_success()
|
|
| 440 |
+ assert not result.get_pushed_elements()
|
|
| 441 |
+ assert cli.get_element_state(project, 'target.bst') == 'cached'
|
|
| 442 |
+ |
|
| 443 |
+ # Delete the artifacts from the local artifact cache
|
|
| 444 |
+ all_elements = ['target.bst', 'import-bin.bst', 'compose-all.bst']
|
|
| 445 |
+ for element_name in all_elements:
|
|
| 446 |
+ cli.remove_artifact_from_cache(project, element_name)
|
|
| 447 |
+ |
|
| 448 |
+ # Assert that nothing is cached locally anymore
|
|
| 449 |
+ for element_name in all_elements:
|
|
| 450 |
+ assert cli.get_element_state(project, element_name) != 'cached'
|
|
| 451 |
+ |
|
| 452 |
+ # Attempt bst build with --use-remotes set as user, this should lead to
|
|
| 453 |
+ # a complete rebuild, with artifacts pushed to the shareuser remote artifact cache
|
|
| 454 |
+ # only. Assert project remote was not attempted by it not being in the output
|
|
| 455 |
+ result = cli.run(project=project, args=['--use-remotes', 'user', 'build', 'target.bst'])
|
|
| 456 |
+ result.assert_success()
|
|
| 457 |
+ for element_name in all_elements:
|
|
| 458 |
+ assert element_name in result.get_pushed_elements()
|
|
| 459 |
+ for element_name in all_elements:
|
|
| 460 |
+ assert_shared(cli, shareuser, project, element_name)
|
|
| 461 |
+ assert shareproject.repo not in result.stderr
|
|
| 462 |
+ |
|
| 463 |
+ # Delete the artifacts from the local artifact cache
|
|
| 464 |
+ all_elements = ['target.bst', 'import-bin.bst', 'compose-all.bst']
|
|
| 465 |
+ for element_name in all_elements:
|
|
| 466 |
+ cli.remove_artifact_from_cache(project, element_name)
|
|
| 467 |
+ |
|
| 468 |
+ # Attempt bst build with --use-remotes set as all, this should lead to
|
|
| 469 |
+ # a complete rebuild, with artifacts pushed to both the shareuser and
|
|
| 470 |
+ # shareproject remote artifacts caches
|
|
| 471 |
+ result = cli.run(project=project, args=['--use-remotes', 'all', 'build', 'target.bst'])
|
|
| 472 |
+ result.assert_success()
|
|
| 473 |
+ for element_name in all_elements:
|
|
| 474 |
+ assert element_name in result.get_pushed_elements()
|
|
| 475 |
+ for element_name in all_elements:
|
|
| 476 |
+ assert_shared(cli, shareproject, project, element_name)
|
| 1 |
+kind: manual
|
|
| 2 |
+depends:
|
|
| 3 |
+ - base/base-alpine.bst
|
|
| 4 |
+ |
|
| 5 |
+public:
|
|
| 6 |
+ bst:
|
|
| 7 |
+ integration-commands:
|
|
| 8 |
+ - |
|
|
| 9 |
+ chmod a-x /bin/sh
|
| 1 |
+kind: manual
|
|
| 2 |
+depends:
|
|
| 3 |
+ - base/base-alpine.bst
|
|
| 4 |
+ |
|
| 5 |
+config:
|
|
| 6 |
+ build-commands:
|
|
| 7 |
+ - |
|
|
| 8 |
+ exit 42
|
| 1 |
+kind: manual
|
|
| 2 |
+ |
|
| 3 |
+depends:
|
|
| 4 |
+ - sandbox-bwrap/break-shell.bst
|
|
| 5 |
+ |
|
| 6 |
+config:
|
|
| 7 |
+ build-commands:
|
|
| 8 |
+ - |
|
|
| 9 |
+ exit 42
|
| 1 | 1 |
import os
|
| 2 | 2 |
import pytest
|
| 3 | 3 |
|
| 4 |
+from buildstream._exceptions import ErrorDomain
|
|
| 5 |
+ |
|
| 4 | 6 |
from tests.testutils import cli_integration as cli
|
| 5 | 7 |
from tests.testutils.integration import assert_contains
|
| 6 |
-from tests.testutils.site import HAVE_BWRAP
|
|
| 8 |
+from tests.testutils.site import HAVE_BWRAP, HAVE_BWRAP_JSON_STATUS
|
|
| 7 | 9 |
|
| 8 | 10 |
|
| 9 | 11 |
pytestmark = pytest.mark.integration
|
| ... | ... | @@ -29,3 +31,32 @@ def test_sandbox_bwrap_cleanup_build(cli, tmpdir, datafiles): |
| 29 | 31 |
# Here, BuildStream should not attempt any rmdir etc.
|
| 30 | 32 |
result = cli.run(project=project, args=['build', element_name])
|
| 31 | 33 |
assert result.exit_code == 0
|
| 34 |
+ |
|
| 35 |
+ |
|
| 36 |
+@pytest.mark.skipif(not HAVE_BWRAP, reason='Only available with bubblewrap')
|
|
| 37 |
+@pytest.mark.skipif(not HAVE_BWRAP_JSON_STATUS, reason='Only available with bubblewrap supporting --json-status-fd')
|
|
| 38 |
+@pytest.mark.datafiles(DATA_DIR)
|
|
| 39 |
+def test_sandbox_bwrap_distinguish_setup_error(cli, tmpdir, datafiles):
|
|
| 40 |
+ project = os.path.join(datafiles.dirname, datafiles.basename)
|
|
| 41 |
+ element_name = 'sandbox-bwrap/non-executable-shell.bst'
|
|
| 42 |
+ |
|
| 43 |
+ result = cli.run(project=project, args=['build', element_name])
|
|
| 44 |
+ result.assert_task_error(error_domain=ErrorDomain.SANDBOX, error_reason="bwrap-sandbox-fail")
|
|
| 45 |
+ |
|
| 46 |
+ |
|
| 47 |
+@pytest.mark.integration
|
|
| 48 |
+@pytest.mark.skipif(not HAVE_BWRAP, reason='Only available with bubblewrap')
|
|
| 49 |
+@pytest.mark.datafiles(DATA_DIR)
|
|
| 50 |
+def test_sandbox_bwrap_return_subprocess(cli, tmpdir, datafiles):
|
|
| 51 |
+ project = os.path.join(datafiles.dirname, datafiles.basename)
|
|
| 52 |
+ element_name = 'sandbox-bwrap/command-exit-42.bst'
|
|
| 53 |
+ |
|
| 54 |
+ cli.configure({
|
|
| 55 |
+ "logging": {
|
|
| 56 |
+ "message-format": "%{element}|%{message}",
|
|
| 57 |
+ },
|
|
| 58 |
+ })
|
|
| 59 |
+ |
|
| 60 |
+ result = cli.run(project=project, args=['build', element_name])
|
|
| 61 |
+ result.assert_task_error(error_domain=ErrorDomain.ELEMENT, error_reason=None)
|
|
| 62 |
+ assert "sandbox-bwrap/command-exit-42.bst|Command 'exit 42' failed with exitcode 42" in result.stderr
|
| ... | ... | @@ -4,7 +4,7 @@ |
| 4 | 4 |
import os
|
| 5 | 5 |
import sys
|
| 6 | 6 |
|
| 7 |
-from buildstream import utils, ProgramNotFoundError
|
|
| 7 |
+from buildstream import _site, utils, ProgramNotFoundError
|
|
| 8 | 8 |
|
| 9 | 9 |
try:
|
| 10 | 10 |
utils.get_host_tool('bzr')
|
| ... | ... | @@ -33,8 +33,10 @@ except (ImportError, ValueError): |
| 33 | 33 |
try:
|
| 34 | 34 |
utils.get_host_tool('bwrap')
|
| 35 | 35 |
HAVE_BWRAP = True
|
| 36 |
+ HAVE_BWRAP_JSON_STATUS = _site.get_bwrap_version() >= (0, 3, 2)
|
|
| 36 | 37 |
except ProgramNotFoundError:
|
| 37 | 38 |
HAVE_BWRAP = False
|
| 39 |
+ HAVE_BWRAP_JSON_STATUS = False
|
|
| 38 | 40 |
|
| 39 | 41 |
try:
|
| 40 | 42 |
utils.get_host_tool('lzip')
|
