Control: tags 962197 + pending
Control: tags 962205 + patch
Control: tags 962205 + pending
Dear maintainer,
I've prepared an NMU for buildstream (versioned as 1.4.3-0.1) and
uploaded it to DELAYED/15. Please feel free to tell me if I
should cancel it.
cu
Adrian
diff -Nru buildstream-1.4.1/buildstream/_artifactcache/cascache.py buildstream-1.4.3/buildstream/_artifactcache/cascache.py
--- buildstream-1.4.1/buildstream/_artifactcache/cascache.py 2019-09-02 15:01:36.000000000 +0300
+++ buildstream-1.4.3/buildstream/_artifactcache/cascache.py 2020-05-12 11:35:41.000000000 +0300
@@ -49,6 +49,46 @@
_MAX_PAYLOAD_BYTES = 1024 * 1024
+class _Attempt():
+
+ def __init__(self, last_attempt=False):
+ self.__passed = None
+ self.__last_attempt = last_attempt
+
+ def passed(self):
+ return self.__passed
+
+ def __enter__(self):
+ pass
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ try:
+ if exc_type is None:
+ self.__passed = True
+ else:
+ self.__passed = False
+ if exc_value is not None:
+ raise exc_value
+ except grpc.RpcError as e:
+ if e.code() == grpc.StatusCode.UNAVAILABLE:
+ return not self.__last_attempt
+ elif e.code() == grpc.StatusCode.ABORTED:
+ raise CASRemoteError("grpc aborted: {}".format(str(e)),
+ detail=e.details(),
+ temporary=True) from e
+ else:
+ return False
+ return False
+
+
+def _retry(tries=5):
+ for a in range(tries):
+ attempt = _Attempt(last_attempt=(a == tries - 1))
+ yield attempt
+ if attempt.passed():
+ break
+
+
class BlobNotFound(ArtifactError):
def __init__(self, blob, msg):
@@ -248,7 +288,9 @@
request = buildstream_pb2.GetReferenceRequest()
request.key = ref
- response = remote.ref_storage.GetReference(request)
+ for attempt in _retry():
+ with attempt:
+ response = remote.ref_storage.GetReference(request)
tree = remote_execution_pb2.Digest()
tree.hash = response.digest.hash
@@ -296,7 +338,9 @@
try:
request = buildstream_pb2.GetReferenceRequest()
request.key = ref
- response = remote.ref_storage.GetReference(request)
+ for attempt in _retry():
+ with attempt:
+ response = remote.ref_storage.GetReference(request)
if response.digest.hash == tree.hash and response.digest.size_bytes == tree.size_bytes:
# ref is already on the server with the same tree
@@ -313,7 +357,9 @@
request.keys.append(ref)
request.digest.hash = tree.hash
request.digest.size_bytes = tree.size_bytes
- remote.ref_storage.UpdateReference(request)
+ for attempt in _retry():
+ with attempt:
+ remote.ref_storage.UpdateReference(request)
skipped_remote = False
except grpc.RpcError as e:
@@ -786,7 +832,9 @@
remote.init()
request = buildstream_pb2.StatusRequest()
- response = remote.ref_storage.Status(request)
+ for attempt in _retry():
+ with attempt:
+ response = remote.ref_storage.Status(request)
if remote_spec.push and not response.allow_updates:
q.put('Artifact server does not allow push')
@@ -986,7 +1034,9 @@
offset += chunk_size
finished = request.finish_write
- response = remote.bytestream.Write(request_stream(resource_name, stream))
+ for attempt in _retry():
+ with attempt:
+ response = remote.bytestream.Write(request_stream(resource_name, stream))
assert response.committed_size == digest.size_bytes
@@ -1003,7 +1053,9 @@
d.hash = required_digest.hash
d.size_bytes = required_digest.size_bytes
- response = remote.cas.FindMissingBlobs(request)
+ for attempt in _retry():
+ with attempt:
+ response = remote.cas.FindMissingBlobs(request)
for missing_digest in response.missing_blob_digests:
d = remote_execution_pb2.Digest()
d.hash = missing_digest.hash
@@ -1089,7 +1141,9 @@
self.max_batch_total_size_bytes = _MAX_PAYLOAD_BYTES
try:
request = remote_execution_pb2.GetCapabilitiesRequest()
- response = self.capabilities.GetCapabilities(request)
+ for attempt in _retry():
+ with attempt:
+ response = self.capabilities.GetCapabilities(request)
server_max_batch_total_size_bytes = response.cache_capabilities.max_batch_total_size_bytes
if 0 < server_max_batch_total_size_bytes < self.max_batch_total_size_bytes:
self.max_batch_total_size_bytes = server_max_batch_total_size_bytes
@@ -1102,7 +1156,9 @@
self.batch_read_supported = False
try:
request = remote_execution_pb2.BatchReadBlobsRequest()
- response = self.cas.BatchReadBlobs(request)
+ for attempt in _retry():
+ with attempt:
+ response = self.cas.BatchReadBlobs(request)
self.batch_read_supported = True
except grpc.RpcError as e:
if e.code() != grpc.StatusCode.UNIMPLEMENTED:
@@ -1112,7 +1168,9 @@
self.batch_update_supported = False
try:
request = remote_execution_pb2.BatchUpdateBlobsRequest()
- response = self.cas.BatchUpdateBlobs(request)
+ for attempt in _retry():
+ with attempt:
+ response = self.cas.BatchUpdateBlobs(request)
self.batch_update_supported = True
except grpc.RpcError as e:
if (e.code() != grpc.StatusCode.UNIMPLEMENTED and
@@ -1153,7 +1211,9 @@
if len(self._request.digests) == 0:
return
- batch_response = self._remote.cas.BatchReadBlobs(self._request)
+ for attempt in _retry():
+ with attempt:
+ batch_response = self._remote.cas.BatchReadBlobs(self._request)
for response in batch_response.responses:
if response.status.code == grpc.StatusCode.NOT_FOUND.value[0]:
@@ -1201,7 +1261,9 @@
if len(self._request.requests) == 0:
return
- batch_response = self._remote.cas.BatchUpdateBlobs(self._request)
+ for attempt in _retry():
+ with attempt:
+ batch_response = self._remote.cas.BatchUpdateBlobs(self._request)
for response in batch_response.responses:
if response.status.code != grpc.StatusCode.OK.value[0]:
diff -Nru buildstream-1.4.1/buildstream/element.py buildstream-1.4.3/buildstream/element.py
--- buildstream-1.4.1/buildstream/element.py 2019-09-03 12:15:20.000000000 +0300
+++ buildstream-1.4.3/buildstream/element.py 2020-05-12 11:35:41.000000000 +0300
@@ -2362,7 +2362,7 @@
if include_file and not exclude_file:
yield filename.lstrip(os.sep)
- def __file_is_whitelisted(self, pattern):
+ def __file_is_whitelisted(self, path):
# Considered storing the whitelist regex for re-use, but public data
# can be altered mid-build.
# Public data is not guaranteed to stay the same for the duration of
@@ -2374,7 +2374,7 @@
whitelist_expressions = [utils._glob2re(self.__variables.subst(exp.strip())) for exp in whitelist]
expression = ('^(?:' + '|'.join(whitelist_expressions) + ')$')
self.__whitelist_regex = re.compile(expression)
- return self.__whitelist_regex.match(pattern)
+ return self.__whitelist_regex.match(path) or self.__whitelist_regex.match(os.path.join(os.sep, path))
# __extract():
#
diff -Nru buildstream-1.4.1/buildstream/_frontend/app.py buildstream-1.4.3/buildstream/_frontend/app.py
--- buildstream-1.4.1/buildstream/_frontend/app.py 2019-09-03 12:15:20.000000000 +0300
+++ buildstream-1.4.3/buildstream/_frontend/app.py 2020-05-12 11:35:41.000000000 +0300
@@ -504,7 +504,10 @@
choice = click.prompt("Choice:",
value_proc=_prefix_choice_value_proc(['continue', 'quit', 'terminate']),
default='continue', err=True)
- except click.Abort:
+ except (click.Abort, SystemError):
+ # In some cases, the readline buffer underlying the prompt gets corrupted on the second CTRL+C
+ # This throws a SystemError, which doesn't seem to be problematic for the rest of the program
+
# Ensure a newline after automatically printed '^C'
click.echo("", err=True)
choice = 'terminate'
@@ -599,7 +602,10 @@
try:
choice = click.prompt("Choice:", default='continue', err=True,
value_proc=_prefix_choice_value_proc(choices))
- except click.Abort:
+ except (click.Abort, SystemError):
+ # In some cases, the readline buffer underlying the prompt gets corrupted on the second CTRL+C
+ # This throws a SystemError, which doesn't seem to be problematic for the rest of the program
+
# Ensure a newline after automatically printed '^C'
click.echo("", err=True)
choice = 'terminate'
diff -Nru buildstream-1.4.1/buildstream/_frontend/complete.py buildstream-1.4.3/buildstream/_frontend/complete.py
--- buildstream-1.4.1/buildstream/_frontend/complete.py 2019-09-02 15:01:36.000000000 +0300
+++ buildstream-1.4.3/buildstream/_frontend/complete.py 2020-05-12 11:35:41.000000000 +0300
@@ -31,7 +31,7 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-import collections
+import collections.abc
import copy
import os
@@ -218,7 +218,7 @@
return True
if cmd_param.nargs == -1:
return True
- if isinstance(current_param_values, collections.Iterable) \
+ if isinstance(current_param_values, collections.abc.Iterable) \
and cmd_param.nargs > 1 and len(current_param_values) < cmd_param.nargs:
return True
return False
diff -Nru buildstream-1.4.1/buildstream/_fuse/mount.py buildstream-1.4.3/buildstream/_fuse/mount.py
--- buildstream-1.4.1/buildstream/_fuse/mount.py 2019-09-02 15:01:36.000000000 +0300
+++ buildstream-1.4.3/buildstream/_fuse/mount.py 2020-05-12 11:35:41.000000000 +0300
@@ -184,7 +184,7 @@
# Run fuse in foreground in this child process, internally libfuse
# will handle SIGTERM and gracefully exit it's own little main loop.
#
- FUSE(self.__operations, self.__mountpoint, nothreads=True, foreground=True, nonempty=True)
+ FUSE(self.__operations, self.__mountpoint, nothreads=True, foreground=True)
# Explicit 0 exit code, if the operations crashed for some reason, the exit
# code will not be 0, and we want to know about it.
diff -Nru buildstream-1.4.1/buildstream/_ostree.py buildstream-1.4.3/buildstream/_ostree.py
--- buildstream-1.4.1/buildstream/_ostree.py 2019-09-02 15:01:36.000000000 +0300
+++ buildstream-1.4.3/buildstream/_ostree.py 2020-05-12 11:35:41.000000000 +0300
@@ -271,6 +271,20 @@
try:
gfile = Gio.File.new_for_uri(key_url)
stream = gfile.read()
- repo.remote_gpg_import(remote, stream, None, 0, None)
+
+ # In ostree commit `v2019.2-10-gaa5df899`, the python
+ # facing API was changed by way of modifying the
+ # instrospection annotations.
+ #
+ # This means we need to call this API in two different
+ # ways depending on which ostree version is installed.
+ #
+ try:
+ # New API
+ repo.remote_gpg_import(remote, stream, None, None)
+ except TypeError:
+ # Old API
+ repo.remote_gpg_import(remote, stream, None, 0, None)
+
except GLib.GError as e:
raise OSTreeError("Failed to add gpg key from url '{}': {}".format(key_url, e.message)) from e
diff -Nru buildstream-1.4.1/buildstream/plugins/elements/cmake.yaml buildstream-1.4.3/buildstream/plugins/elements/cmake.yaml
--- buildstream-1.4.1/buildstream/plugins/elements/cmake.yaml 2019-09-02 15:01:36.000000000 +0300
+++ buildstream-1.4.3/buildstream/plugins/elements/cmake.yaml 2020-05-12 11:35:41.000000000 +0300
@@ -19,7 +19,7 @@
cmake-args: |
-DCMAKE_INSTALL_PREFIX:PATH="%{prefix}" \
- -DCMAKE_INSTALL_LIBDIR=%{lib} %{cmake-extra} %{cmake-global} %{cmake-local}
+ -DCMAKE_INSTALL_LIBDIR:PATH="%{lib}" %{cmake-extra} %{cmake-global} %{cmake-local}
cmake: |
diff -Nru buildstream-1.4.1/buildstream/plugins/sources/_downloadablefilesource.py buildstream-1.4.3/buildstream/plugins/sources/_downloadablefilesource.py
--- buildstream-1.4.1/buildstream/plugins/sources/_downloadablefilesource.py 2019-09-02 15:01:36.000000000 +0300
+++ buildstream-1.4.3/buildstream/plugins/sources/_downloadablefilesource.py 2020-05-12 11:35:41.000000000 +0300
@@ -107,6 +107,7 @@
default_name = os.path.basename(self.url)
request = urllib.request.Request(self.url)
request.add_header('Accept', '*/*')
+ request.add_header('User-Agent', 'BuildStream/1')
# We do not use etag in case what we have in cache is
# not matching ref in order to be able to recover from
@@ -121,7 +122,11 @@
with contextlib.closing(urllib.request.urlopen(request)) as response:
info = response.info()
- etag = info['ETag'] if 'ETag' in info else None
+ # some servers don't honor the 'If-None-Match' header
+ if self.ref and etag and info["ETag"] == etag:
+ return self.ref
+
+ etag = info["ETag"]
filename = info.get_filename(default_name)
filename = os.path.basename(filename)
diff -Nru buildstream-1.4.1/buildstream/plugins/sources/pip.py buildstream-1.4.3/buildstream/plugins/sources/pip.py
--- buildstream-1.4.1/buildstream/plugins/sources/pip.py 2019-09-03 12:15:20.000000000 +0300
+++ buildstream-1.4.3/buildstream/plugins/sources/pip.py 2020-05-12 11:35:41.000000000 +0300
@@ -90,6 +90,7 @@
'python3.5',
'python3.6',
'python3.7',
+ 'python3.8',
]
# List of allowed extensions taken from
diff -Nru buildstream-1.4.1/buildstream/_project.py buildstream-1.4.3/buildstream/_project.py
--- buildstream-1.4.1/buildstream/_project.py 2019-09-03 12:15:20.000000000 +0300
+++ buildstream-1.4.3/buildstream/_project.py 2020-05-13 13:19:40.000000000 +0300
@@ -562,15 +562,6 @@
def _load_pass(self, config, output, *,
ignore_unknown=False):
- # Element and Source type configurations will be composited later onto
- # element/source types, so we delete it from here and run our final
- # assertion after.
- output.element_overrides = _yaml.node_get(config, Mapping, 'elements', default_value={})
- output.source_overrides = _yaml.node_get(config, Mapping, 'sources', default_value={})
- config.pop('elements', None)
- config.pop('sources', None)
- _yaml.node_final_assertions(config)
-
self._load_plugin_factories(config, output)
# Load project options
@@ -594,11 +585,16 @@
# Now resolve any conditionals in the remaining configuration,
# any conditionals specified for project option declarations,
# or conditionally specifying the project name; will be ignored.
- #
- # Don't forget to also resolve options in the element and source overrides.
output.options.process_node(config)
- output.options.process_node(output.element_overrides)
- output.options.process_node(output.source_overrides)
+
+ # Element and Source type configurations will be composited later onto
+ # element/source types, so we delete it from here and run our final
+ # assertion after.
+ output.element_overrides = _yaml.node_get(config, Mapping, 'elements', default_value={})
+ output.source_overrides = _yaml.node_get(config, Mapping, 'sources', default_value={})
+ config.pop('elements', None)
+ config.pop('sources', None)
+ _yaml.node_final_assertions(config)
# Load base variables
output.base_variables = _yaml.node_get(config, Mapping, 'variables')
diff -Nru buildstream-1.4.1/buildstream/_scheduler/jobs/job.py buildstream-1.4.3/buildstream/_scheduler/jobs/job.py
--- buildstream-1.4.1/buildstream/_scheduler/jobs/job.py 2019-09-02 15:01:36.000000000 +0300
+++ buildstream-1.4.3/buildstream/_scheduler/jobs/job.py 2020-05-12 11:35:41.000000000 +0300
@@ -1,5 +1,6 @@
#
# Copyright (C) 2018 Codethink Limited
+# Copyright (C) 2019 Bloomberg Finance LP
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -32,6 +33,7 @@
from ..._exceptions import ImplError, BstError, set_last_task_error, SkipJob
from ..._message import Message, MessageType, unconditional_messages
from ... import _signals, utils
+from .. import _multiprocessing
# Return code values shutdown of job handling child processes
#
@@ -64,15 +66,6 @@
self.message = message
-# Process class that doesn't call waitpid on its own.
-# This prevents conflicts with the asyncio child watcher.
-class Process(multiprocessing.Process):
- # pylint: disable=attribute-defined-outside-init
- def start(self):
- self._popen = self._Popen(self)
- self._sentinel = self._popen.sentinel
-
-
# Job()
#
# The Job object represents a parallel task, when calling Job.spawn(),
@@ -127,39 +120,23 @@
self._parent_start_listening()
# Spawn the process
- self._process = Process(target=self._child_action, args=[self._queue])
+ self._process = _multiprocessing.AsyncioSafeProcess(target=self._child_action, args=[self._queue])
# Block signals which are handled in the main process such that
# the child process does not inherit the parent's state, but the main
# process will be notified of any signal after we launch the child.
#
with _signals.blocked([signal.SIGINT, signal.SIGTSTP, signal.SIGTERM], ignore=False):
- self._process.start()
+ with asyncio.get_child_watcher() as watcher:
+ self._process.start()
+ # Register the process to call `_parent_child_completed` once it is done
+
+ # Here we delay the call to the next loop tick. This is in order to be running
+ # in the main thread, as the callback itself must be thread safe.
+ def on_completion(pid, returncode):
+ asyncio.get_event_loop().call_soon(self._parent_child_completed, pid, returncode)
- # Wait for the child task to complete.
- #
- # This is a tricky part of python which doesnt seem to
- # make it to the online docs:
- #
- # o asyncio.get_child_watcher() will return a SafeChildWatcher() instance
- # which is the default type of watcher, and the instance belongs to the
- # "event loop policy" in use (so there is only one in the main process).
- #
- # o SafeChildWatcher() will register a SIGCHLD handler with the asyncio
- # loop, and will selectively reap any child pids which have been
- # terminated.
- #
- # o At registration time, the process will immediately be checked with
- # `os.waitpid()` and will be reaped immediately, before add_child_handler()
- # returns.
- #
- # The self._parent_child_completed callback passed here will normally
- # be called after the child task has been reaped with `os.waitpid()`, in
- # an event loop callback. Otherwise, if the job completes too fast, then
- # the callback is called immediately.
- #
- self._watcher = asyncio.get_child_watcher()
- self._watcher.add_child_handler(self._process.pid, self._parent_child_completed)
+ watcher.add_child_handler(self._process.pid, on_completion)
# terminate()
#
@@ -182,21 +159,15 @@
self._terminated = True
- # terminate_wait()
+ # get_terminated()
#
- # Wait for terminated jobs to complete
- #
- # Args:
- # timeout (float): Seconds to wait
+ # Check if a job has been terminated.
#
# Returns:
- # (bool): True if the process terminated cleanly, otherwise False
+ # (bool): True in the main process if Job.terminate() was called.
#
- def terminate_wait(self, timeout):
-
- # Join the child process after sending SIGTERM
- self._process.join(timeout)
- return self._process.exitcode is not None
+ def get_terminated(self):
+ return self._terminated
# kill()
#
diff -Nru buildstream-1.4.1/buildstream/_scheduler/_multiprocessing.py buildstream-1.4.3/buildstream/_scheduler/_multiprocessing.py
--- buildstream-1.4.1/buildstream/_scheduler/_multiprocessing.py 1970-01-01 02:00:00.000000000 +0200
+++ buildstream-1.4.3/buildstream/_scheduler/_multiprocessing.py 2020-05-12 11:35:41.000000000 +0300
@@ -0,0 +1,79 @@
+#
+# Copyright (C) 2019 Bloomberg Finance LP
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# TLDR:
+# ALWAYS use `.AsyncioSafeProcess` when you have an asyncio event loop running and need a `multiprocessing.Process`
+#
+#
+# The upstream asyncio library doesn't play well with forking subprocesses while an event loop is running.
+#
+# The main problem that affects us is that the parent and the child will share some file handlers.
+# The most important one for us is the sig_handler_fd, which the loop uses to buffer signals received
+# by the app so that the asyncio loop can treat them afterwards.
+#
+# This sharing means that when we send a signal to the child, the sighandler in the child will write
+# it back to the parent sig_handler_fd, making the parent have to treat it too.
+# This is a problem for example when we sigterm the process. The scheduler will send sigterms to all its children,
+# which in turn will make the scheduler receive N SIGTERMs (one per child). Which in turn will send sigterms to
+# the children...
+#
+# We therefore provide a `AsyncioSafeProcess` derived from multiprocessing.Process that automatically
+# tries to cleanup the loop and never calls `waitpid` on the child process, which breaks our child watchers.
+#
+#
+# Relevant issues:
+# - Asyncio: support fork (https://bugs.python.org/issue21998)
+# - Asyncio: support multiprocessing (support fork) (https://bugs.python.org/issue22087)
+# - Signal delivered to a subprocess triggers parent's handler (https://bugs.python.org/issue31489)
+#
+#
+
+import multiprocessing
+import signal
+import sys
+from asyncio import set_event_loop_policy
+
+
+# _AsyncioSafeForkAwareProcess()
+#
+# Process class that doesn't call waitpid on its own.
+# This prevents conflicts with the asyncio child watcher.
+#
+# Also automatically close any running asyncio loop before calling
+# the actual run target
+#
+class _AsyncioSafeForkAwareProcess(multiprocessing.Process):
+ # pylint: disable=attribute-defined-outside-init
+ def start(self):
+ self._popen = self._Popen(self)
+ self._sentinel = self._popen.sentinel
+
+ def run(self):
+ signal.set_wakeup_fd(-1)
+ set_event_loop_policy(None)
+
+ super().run()
+
+
+if sys.platform != "win32":
+ # Set the default event loop policy to automatically close our asyncio loop in child processes
+ AsyncioSafeProcess = _AsyncioSafeForkAwareProcess
+
+else:
+ # Windows doesn't support ChildWatcher that way anyways, we'll need another
+ # implementation if we want it
+ AsyncioSafeProcess = multiprocessing.Process
diff -Nru buildstream-1.4.1/buildstream/_scheduler/scheduler.py buildstream-1.4.3/buildstream/_scheduler/scheduler.py
--- buildstream-1.4.1/buildstream/_scheduler/scheduler.py 2019-09-02 15:01:36.000000000 +0300
+++ buildstream-1.4.3/buildstream/_scheduler/scheduler.py 2020-05-12 11:35:41.000000000 +0300
@@ -137,6 +137,12 @@
# Hold on to the queues to process
self.queues = queues
+ # NOTE: Enforce use of `SafeChildWatcher` as we generally don't want
+ # background threads.
+ # In Python 3.8+, `ThreadedChildWatcher` is the default watcher, and
+ # not `SafeChildWatcher`.
+ asyncio.set_child_watcher(asyncio.SafeChildWatcher())
+
# Ensure that we have a fresh new event loop, in case we want
# to run another test in this thread.
self.loop = asyncio.new_event_loop()
@@ -516,21 +522,15 @@
self.loop.remove_signal_handler(signal.SIGTERM)
def _terminate_jobs_real(self):
- # 20 seconds is a long time, it can take a while and sometimes
- # we still fail, need to look deeper into this again.
- wait_start = datetime.datetime.now()
- wait_limit = 20.0
+ def kill_jobs():
+ for job_ in self._active_jobs:
+ job_.kill()
- # First tell all jobs to terminate
- for job in self._active_jobs:
- job.terminate()
+ # Schedule all jobs to be killed if they have not exited in 20 sec
+ self.loop.call_later(20, kill_jobs)
- # Now wait for them to really terminate
for job in self._active_jobs:
- elapsed = datetime.datetime.now() - wait_start
- timeout = max(wait_limit - elapsed.total_seconds(), 0.0)
- if not job.terminate_wait(timeout):
- job.kill()
+ job.terminate()
# Regular timeout for driving status in the UI
def _tick(self):
diff -Nru buildstream-1.4.1/buildstream/_version.py buildstream-1.4.3/buildstream/_version.py
--- buildstream-1.4.1/buildstream/_version.py 2019-09-03 14:07:12.000000000 +0300
+++ buildstream-1.4.3/buildstream/_version.py 2020-05-13 13:38:33.000000000 +0300
@@ -8,11 +8,11 @@
version_json = '''
{
- "date": "2019-09-03T11:05:36+0000",
+ "date": "2020-05-13T19:31:51+0900",
"dirty": false,
"error": null,
- "full-revisionid": "2ece1287f75c3a1eaf4bd1a9f4f9c7b895fe3993",
- "version": "1.4.1"
+ "full-revisionid": "89765b759230c8d47e27fd52527cf3df61be9ae1",
+ "version": "1.4.3"
}
''' # END VERSION_JSON
diff -Nru buildstream-1.4.1/buildstream/_yaml.py buildstream-1.4.3/buildstream/_yaml.py
--- buildstream-1.4.1/buildstream/_yaml.py 2019-09-02 15:01:36.000000000 +0300
+++ buildstream-1.4.3/buildstream/_yaml.py 2020-05-12 11:35:41.000000000 +0300
@@ -280,7 +280,7 @@
provenance.members[key] = member
target_value = target.get(key)
- if isinstance(value, collections.Mapping):
+ if isinstance(value, collections.abc.Mapping):
node_decorate_dict(filename, target_value, value, toplevel)
elif isinstance(value, list):
member.elements = node_decorate_list(filename, target_value, value, toplevel)
@@ -295,7 +295,7 @@
target_item = target[idx]
element = ElementProvenance(filename, source, idx, toplevel)
- if isinstance(item, collections.Mapping):
+ if isinstance(item, collections.abc.Mapping):
node_decorate_dict(filename, target_item, item, toplevel)
elif isinstance(item, list):
element.elements = node_decorate_list(filename, target_item, item, toplevel)
@@ -569,7 +569,7 @@
#
def is_composite_list(node):
- if isinstance(node, collections.Mapping):
+ if isinstance(node, collections.abc.Mapping):
has_directives = False
has_keys = False
@@ -838,7 +838,7 @@
target_value = target.get(key)
- if isinstance(source_value, collections.Mapping):
+ if isinstance(source_value, collections.abc.Mapping):
# Handle creating new dicts on target side
if target_value is None:
@@ -853,7 +853,7 @@
# Add a new provenance member element to the containing dict
target_provenance.members[key] = source_provenance.members[key]
- if not isinstance(target_value, collections.Mapping):
+ if not isinstance(target_value, collections.abc.Mapping):
raise CompositeTypeError(thispath, type(target_value), type(source_value))
# Recurse into matching dictionary
@@ -914,7 +914,7 @@
#
def node_sanitize(node):
- if isinstance(node, collections.Mapping):
+ if isinstance(node, collections.abc.Mapping):
result = SanitizedDict()
@@ -1052,7 +1052,7 @@
def node_chain_copy(source):
copy = ChainMap({}, source)
for key, value in source.items():
- if isinstance(value, collections.Mapping):
+ if isinstance(value, collections.abc.Mapping):
copy[key] = node_chain_copy(value)
elif isinstance(value, list):
copy[key] = list_chain_copy(value)
@@ -1065,7 +1065,7 @@
def list_chain_copy(source):
copy = []
for item in source:
- if isinstance(item, collections.Mapping):
+ if isinstance(item, collections.abc.Mapping):
copy.append(node_chain_copy(item))
elif isinstance(item, list):
copy.append(list_chain_copy(item))
@@ -1080,7 +1080,7 @@
def node_copy(source):
copy = {}
for key, value in source.items():
- if isinstance(value, collections.Mapping):
+ if isinstance(value, collections.abc.Mapping):
copy[key] = node_copy(value)
elif isinstance(value, list):
copy[key] = list_copy(value)
@@ -1097,7 +1097,7 @@
def list_copy(source):
copy = []
for item in source:
- if isinstance(item, collections.Mapping):
+ if isinstance(item, collections.abc.Mapping):
copy.append(node_copy(item))
elif isinstance(item, list):
copy.append(list_copy(item))
@@ -1132,7 +1132,7 @@
raise LoadError(LoadErrorReason.TRAILING_LIST_DIRECTIVE,
"{}: Attempt to override non-existing list".format(provenance))
- if isinstance(value, collections.Mapping):
+ if isinstance(value, collections.abc.Mapping):
node_final_assertions(value)
elif isinstance(value, list):
list_final_assertions(value)
@@ -1140,7 +1140,7 @@
def list_final_assertions(values):
for value in values:
- if isinstance(value, collections.Mapping):
+ if isinstance(value, collections.abc.Mapping):
node_final_assertions(value)
elif isinstance(value, list):
list_final_assertions(value)
diff -Nru buildstream-1.4.1/BuildStream.egg-info/PKG-INFO buildstream-1.4.3/BuildStream.egg-info/PKG-INFO
--- buildstream-1.4.1/BuildStream.egg-info/PKG-INFO 2019-09-03 14:07:12.000000000 +0300
+++ buildstream-1.4.3/BuildStream.egg-info/PKG-INFO 2020-05-13 13:38:33.000000000 +0300
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: BuildStream
-Version: 1.4.1
+Version: 1.4.3
Summary: A framework for modelling build pipelines in YAML
Home-page: https://gitlab.com/BuildStream/buildstream
Author: BuildStream Developers
diff -Nru buildstream-1.4.1/BuildStream.egg-info/SOURCES.txt buildstream-1.4.3/BuildStream.egg-info/SOURCES.txt
--- buildstream-1.4.1/BuildStream.egg-info/SOURCES.txt 2019-09-03 14:07:12.000000000 +0300
+++ buildstream-1.4.3/BuildStream.egg-info/SOURCES.txt 2020-05-13 13:38:33.000000000 +0300
@@ -118,6 +118,7 @@
buildstream/_protos/google/rpc/status_pb2.py
buildstream/_protos/google/rpc/status_pb2_grpc.py
buildstream/_scheduler/__init__.py
+buildstream/_scheduler/_multiprocessing.py
buildstream/_scheduler/resources.py
buildstream/_scheduler/scheduler.py
buildstream/_scheduler/jobs/__init__.py
@@ -323,6 +324,7 @@
tests/format/include.py
tests/format/include_composition.py
tests/format/listdirectiveerrors.py
+tests/format/option-list-directive.py
tests/format/optionarch.py
tests/format/optionbool.py
tests/format/optioneltmask.py
@@ -403,6 +405,7 @@
tests/format/option-flags/project.conf
tests/format/option-flags-missing/element.bst
tests/format/option-flags-missing/project.conf
+tests/format/option-list-directive/project.conf
tests/format/option-overrides/element.bst
tests/format/option-overrides/project.conf
tests/format/options/compound-and-condition/element.bst
@@ -528,13 +531,17 @@
tests/frontend/consistencyerror/plugins/__init__.py
tests/frontend/consistencyerror/plugins/consistencybug.py
tests/frontend/consistencyerror/plugins/consistencyerror.py
+tests/frontend/overlaps/a-whitelisted-abs.bst
tests/frontend/overlaps/a-whitelisted.bst
tests/frontend/overlaps/a.bst
+tests/frontend/overlaps/b-whitelisted-abs.bst
tests/frontend/overlaps/b-whitelisted.bst
tests/frontend/overlaps/b.bst
+tests/frontend/overlaps/c-whitelisted-abs.bst
tests/frontend/overlaps/c-whitelisted.bst
tests/frontend/overlaps/c.bst
tests/frontend/overlaps/collect-partially-whitelisted.bst
+tests/frontend/overlaps/collect-whitelisted-abs.bst
tests/frontend/overlaps/collect-whitelisted.bst
tests/frontend/overlaps/collect.bst
tests/frontend/overlaps/script.bst
diff -Nru buildstream-1.4.1/debian/changelog buildstream-1.4.3/debian/changelog
--- buildstream-1.4.1/debian/changelog 2019-09-29 01:31:09.000000000 +0300
+++ buildstream-1.4.3/debian/changelog 2020-07-18 10:03:58.000000000 +0300
@@ -1,3 +1,12 @@
+buildstream (1.4.3-0.1) unstable; urgency=low
+
+ * Non-maintainer upload.
+ * New upstream release.
+ - Works with Python 3.8. (Closes: #962197)
+ * Add the missing dependency on bubblewrap. (Closes: #962205)
+
+ -- Adrian Bunk <[email protected]> Sat, 18 Jul 2020 10:03:58 +0300
+
buildstream (1.4.1-1) unstable; urgency=medium
* New upstream release (Closes: #933395)
diff -Nru buildstream-1.4.1/debian/control buildstream-1.4.3/debian/control
--- buildstream-1.4.1/debian/control 2019-09-29 01:31:09.000000000 +0300
+++ buildstream-1.4.3/debian/control 2020-07-18 10:03:58.000000000 +0300
@@ -63,6 +63,7 @@
Section: python
Architecture: all
Depends: ${misc:Depends},
+ bubblewrap,
${python3:Depends},
python3-fuse
Description: Python library for BuildStream toolset
diff -Nru buildstream-1.4.1/debian/control.in buildstream-1.4.3/debian/control.in
--- buildstream-1.4.1/debian/control.in 2019-09-29 01:31:09.000000000 +0300
+++ buildstream-1.4.3/debian/control.in 2020-07-18 10:03:58.000000000 +0300
@@ -59,6 +59,7 @@
Section: python
Architecture: all
Depends: ${misc:Depends},
+ bubblewrap,
${python3:Depends},
python3-fuse
Description: Python library for BuildStream toolset
diff -Nru buildstream-1.4.1/NEWS buildstream-1.4.3/NEWS
--- buildstream-1.4.1/NEWS 2019-09-03 14:05:52.000000000 +0300
+++ buildstream-1.4.3/NEWS 2020-05-13 13:31:19.000000000 +0300
@@ -1,4 +1,38 @@
=================
+buildstream 1.4.3
+=================
+
+ o Fix support for conditional list append/prepend in project.conf,
+ Merge request !1857
+
+ o Fix internal imports to import from "collections" instead
+ of "collections.abc", this improves support for Python 3.8,
+ see issue #831
+
+ o Fix some downloads from gitlab.com by setting custom user agent,
+ fixes issue #1285
+
+ o Work around python API break from ostree's repo.remote_gpg_import(),
+ this was changed in ostree commit v2019.2-10-gaa5df899, and we now
+ have a fallback to support both versions of the API, see merge request !1917.
+
+=================
+buildstream 1.4.2
+=================
+
+ o Support for python 3.8
+
+ o Fix a stacktrace with a hang we can experience when we CTRL-C a job twice.
+
+ o Workaround some servers which do not honor the 'If-None-Match' HTTP header
+ and avoid downloading files redundantly in these cases.
+
+ o Allow specifying absolute paths in overlap-whitelist (issue #721)
+
+ o Support systems with fuse3 (avoid passing unsupported argument
+ to fusermount3)
+
+=================
buildstream 1.4.1
=================
diff -Nru buildstream-1.4.1/PKG-INFO buildstream-1.4.3/PKG-INFO
--- buildstream-1.4.1/PKG-INFO 2019-09-03 14:07:12.000000000 +0300
+++ buildstream-1.4.3/PKG-INFO 2020-05-13 13:38:33.000000000 +0300
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: BuildStream
-Version: 1.4.1
+Version: 1.4.3
Summary: A framework for modelling build pipelines in YAML
Home-page: https://gitlab.com/BuildStream/buildstream
Author: BuildStream Developers
diff -Nru buildstream-1.4.1/requirements/dev-requirements.txt buildstream-1.4.3/requirements/dev-requirements.txt
--- buildstream-1.4.1/requirements/dev-requirements.txt 2019-09-03 13:23:08.000000000 +0300
+++ buildstream-1.4.3/requirements/dev-requirements.txt 2020-05-12 11:35:41.000000000 +0300
@@ -19,7 +19,7 @@
mccabe==0.6.1
more-itertools==7.2.0
packaging==19.1
-pluggy==0.12.0
+pluggy==0.13.1
py==1.8.0
pyparsing==2.4.2
pytest-cache==1.0
diff -Nru buildstream-1.4.1/tests/format/option-list-directive/project.conf buildstream-1.4.3/tests/format/option-list-directive/project.conf
--- buildstream-1.4.1/tests/format/option-list-directive/project.conf 1970-01-01 02:00:00.000000000 +0200
+++ buildstream-1.4.3/tests/format/option-list-directive/project.conf 2020-05-12 11:35:41.000000000 +0300
@@ -0,0 +1,18 @@
+name: test
+
+options:
+ shell_mount_devices:
+ type: bool
+ description: whether to mount devices in the shell
+ default: false
+
+shell:
+ host-files:
+ - '/etc/passwd'
+ - '/etc/group'
+
+ (?):
+ - shell_mount_devices:
+ host-files:
+ (>):
+ - '/dev/dri'
diff -Nru buildstream-1.4.1/tests/format/option-list-directive.py buildstream-1.4.3/tests/format/option-list-directive.py
--- buildstream-1.4.1/tests/format/option-list-directive.py 1970-01-01 02:00:00.000000000 +0200
+++ buildstream-1.4.3/tests/format/option-list-directive.py 2020-05-12 11:35:41.000000000 +0300
@@ -0,0 +1,16 @@
+import os
+import pytest
+from tests.testutils.runcli import cli
+
+# Project directory
+DATA_DIR = os.path.dirname(os.path.realpath(__file__))
+
+
[email protected](DATA_DIR)
[email protected]("mount_devices", [("true"), ("false")])
+def test_override(cli, datafiles, mount_devices):
+ project = os.path.join(datafiles.dirname, datafiles.basename, "option-list-directive")
+
+ bst_args = ["--option", "shell_mount_devices", mount_devices, "build"]
+ result = cli.run(project=project, silent=True, args=bst_args)
+ result.assert_success()
diff -Nru buildstream-1.4.1/tests/format/variables.py buildstream-1.4.3/tests/format/variables.py
--- buildstream-1.4.1/tests/format/variables.py 2019-09-02 15:01:36.000000000 +0300
+++ buildstream-1.4.3/tests/format/variables.py 2020-05-12 11:35:41.000000000 +0300
@@ -20,7 +20,7 @@
('autotools.bst', 'make-install', "make -j1 DESTDIR=\"/buildstream-install\" install"),
('cmake.bst', 'cmake',
"cmake -B_builddir -H. -G\"Unix Makefiles\" -DCMAKE_INSTALL_PREFIX:PATH=\"/usr\" \\\n" +
- "-DCMAKE_INSTALL_LIBDIR=lib "),
+ "-DCMAKE_INSTALL_LIBDIR:PATH=\"lib\" "),
('distutils.bst', 'python-install',
"python3 setup.py install --prefix \"/usr\" \\\n" +
"--root \"/buildstream-install\""),
@@ -46,7 +46,7 @@
('autotools.bst', 'make-install', "make -j1 DESTDIR=\"/custom/install/root\" install"),
('cmake.bst', 'cmake',
"cmake -B_builddir -H. -G\"Ninja\" -DCMAKE_INSTALL_PREFIX:PATH=\"/opt\" \\\n" +
- "-DCMAKE_INSTALL_LIBDIR=lib "),
+ "-DCMAKE_INSTALL_LIBDIR:PATH=\"lib\" "),
('distutils.bst', 'python-install',
"python3 setup.py install --prefix \"/opt\" \\\n" +
"--root \"/custom/install/root\""),
diff -Nru buildstream-1.4.1/tests/frontend/overlaps/a-whitelisted-abs.bst buildstream-1.4.3/tests/frontend/overlaps/a-whitelisted-abs.bst
--- buildstream-1.4.1/tests/frontend/overlaps/a-whitelisted-abs.bst 1970-01-01 02:00:00.000000000 +0200
+++ buildstream-1.4.3/tests/frontend/overlaps/a-whitelisted-abs.bst 2020-05-12 11:35:41.000000000 +0300
@@ -0,0 +1,13 @@
+kind: import
+config:
+ source: /
+ target: /
+depends:
+- b-whitelisted.bst
+sources:
+- kind: local
+ path: "a"
+public:
+ bst:
+ overlap-whitelist:
+ - "/file*"
diff -Nru buildstream-1.4.1/tests/frontend/overlaps/b-whitelisted-abs.bst buildstream-1.4.3/tests/frontend/overlaps/b-whitelisted-abs.bst
--- buildstream-1.4.1/tests/frontend/overlaps/b-whitelisted-abs.bst 1970-01-01 02:00:00.000000000 +0200
+++ buildstream-1.4.3/tests/frontend/overlaps/b-whitelisted-abs.bst 2020-05-12 11:35:41.000000000 +0300
@@ -0,0 +1,16 @@
+kind: import
+config:
+ source: /
+ target: /
+depends:
+- c.bst
+sources:
+- kind: local
+ path: "b"
+variables:
+ FILE: /file
+public:
+ bst:
+ overlap-whitelist:
+ - /file2
+ - "%{FILE}3"
diff -Nru buildstream-1.4.1/tests/frontend/overlaps/collect-whitelisted-abs.bst buildstream-1.4.3/tests/frontend/overlaps/collect-whitelisted-abs.bst
--- buildstream-1.4.1/tests/frontend/overlaps/collect-whitelisted-abs.bst 1970-01-01 02:00:00.000000000 +0200
+++ buildstream-1.4.3/tests/frontend/overlaps/collect-whitelisted-abs.bst 2020-05-12 11:35:41.000000000 +0300
@@ -0,0 +1,9 @@
+kind: compose
+
+depends:
+- filename: a-whitelisted-abs.bst
+ type: build
+- filename: b-whitelisted-abs.bst
+ type: build
+- filename: c.bst
+ type: build
diff -Nru buildstream-1.4.1/tests/frontend/overlaps/c-whitelisted-abs.bst buildstream-1.4.3/tests/frontend/overlaps/c-whitelisted-abs.bst
--- buildstream-1.4.1/tests/frontend/overlaps/c-whitelisted-abs.bst 1970-01-01 02:00:00.000000000 +0200
+++ buildstream-1.4.3/tests/frontend/overlaps/c-whitelisted-abs.bst 2020-05-12 11:35:41.000000000 +0300
@@ -0,0 +1,11 @@
+kind: import
+config:
+ source: /
+ target: /
+sources:
+- kind: local
+ path: "c"
+public:
+ bst:
+ overlap-whitelist:
+ - "/file*"
diff -Nru buildstream-1.4.1/tests/frontend/overlaps.py buildstream-1.4.3/tests/frontend/overlaps.py
--- buildstream-1.4.1/tests/frontend/overlaps.py 2019-09-03 12:15:20.000000000 +0300
+++ buildstream-1.4.3/tests/frontend/overlaps.py 2020-05-12 11:35:41.000000000 +0300
@@ -47,20 +47,22 @@
@pytest.mark.datafiles(DATA_DIR)
-def test_overlaps_whitelist(cli, datafiles):
[email protected]("element", ["collect-whitelisted.bst", "collect-whitelisted-abs.bst"])
+def test_overlaps_whitelist(cli, datafiles, element):
project_dir = str(datafiles)
gen_project(project_dir, True)
result = cli.run(project=project_dir, silent=True, args=[
- 'build', 'collect-whitelisted.bst'])
+ 'build', element])
result.assert_success()
@pytest.mark.datafiles(DATA_DIR)
-def test_overlaps_whitelist_ignored(cli, datafiles):
[email protected]("element", ["collect-whitelisted.bst", "collect-whitelisted-abs.bst"])
+def test_overlaps_whitelist_ignored(cli, datafiles, element):
project_dir = str(datafiles)
gen_project(project_dir, False)
result = cli.run(project=project_dir, silent=True, args=[
- 'build', 'collect-whitelisted.bst'])
+ 'build', element])
result.assert_success()
diff -Nru buildstream-1.4.1/tests/sources/ostree.py buildstream-1.4.3/tests/sources/ostree.py
--- buildstream-1.4.1/tests/sources/ostree.py 2019-09-02 15:01:36.000000000 +0300
+++ buildstream-1.4.3/tests/sources/ostree.py 2020-05-12 11:35:41.000000000 +0300
@@ -55,3 +55,40 @@
result = cli.run(project=project, args=['show', 'target.bst'])
result.assert_main_error(ErrorDomain.SOURCE, "missing-track-and-ref")
result.assert_task_error(None, None)
+
+
[email protected](os.path.join(DATA_DIR, 'template'))
+def test_fetch_gpg_verify(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+
+ gpg_homedir = os.path.join(DATA_DIR, "gpghome")
+
+ # Create the repo from 'repofiles' subdir
+ repo = create_repo('ostree', str(tmpdir))
+ ref = repo.create(
+ os.path.join(project, 'repofiles'),
+ gpg_sign="FFFF54C070353B52D046DEB087FA0F41A6EFD9E9",
+ gpg_homedir=gpg_homedir
+ )
+
+ # Write out our test target
+ ostreesource = repo.source_config(ref=ref, gpg_key='test.gpg')
+ element = {
+ 'kind': 'import',
+ 'sources': [
+ ostreesource
+ ]
+ }
+
+ _yaml.dump(element, os.path.join(project, 'target.bst'))
+
+ # Assert that a fetch is needed
+ assert cli.get_element_state(project, 'target.bst') == 'fetch needed'
+
+ # Now try to fetch it
+ result = cli.run(project=project, args=['fetch', 'target.bst'])
+ result.assert_success()
+
+ # Assert that we are now buildable because the source is
+ # now cached.
+ assert cli.get_element_state(project, 'target.bst') == 'buildable'
diff -Nru buildstream-1.4.1/tests/testutils/repo/ostree.py buildstream-1.4.3/tests/testutils/repo/ostree.py
--- buildstream-1.4.1/tests/testutils/repo/ostree.py 2019-09-02 15:01:36.000000000 +0300
+++ buildstream-1.4.3/tests/testutils/repo/ostree.py 2020-05-12 11:35:41.000000000 +0300
@@ -13,21 +13,31 @@
super(OSTree, self).__init__(directory, subdir)
- def create(self, directory):
+ def create(self, directory, *, gpg_sign=None, gpg_homedir=None):
subprocess.call(['ostree', 'init',
'--repo', self.repo,
'--mode', 'archive-z2'])
- subprocess.call(['ostree', 'commit',
- '--repo', self.repo,
- '--branch', 'master',
- '--subject', 'Initial commit',
- directory])
+
+ commit_args = ['ostree', 'commit',
+ '--repo', self.repo,
+ '--branch', 'master',
+ '--subject', 'Initial commit']
+
+ if gpg_sign and gpg_homedir:
+ commit_args += [
+ '--gpg-sign={}'.format(gpg_sign),
+ '--gpg-homedir={}'.format(gpg_homedir)
+ ]
+
+ commit_args += [directory]
+
+ subprocess.call(commit_args)
latest = self.latest_commit()
return latest
- def source_config(self, ref=None):
+ def source_config(self, ref=None, *, gpg_key=None):
config = {
'kind': 'ostree',
'url': 'file://' + self.repo,
@@ -35,6 +45,8 @@
}
if ref is not None:
config['ref'] = ref
+ if gpg_key is not None:
+ config['gpg-key'] = gpg_key
return config