From patchwork Fri Jun 27 13:48:37 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ross Burton X-Patchwork-Id: 65739 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9554AC77B7F for ; Fri, 27 Jun 2025 13:48:47 +0000 (UTC) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mx.groups.io with SMTP id smtpd.web11.14123.1751032124653785191 for ; Fri, 27 Jun 2025 06:48:44 -0700 Authentication-Results: mx.groups.io; dkim=none (message not signed); spf=pass (domain: arm.com, ip: 217.140.110.172, mailfrom: ross.burton@arm.com) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id B30141A00 for ; Fri, 27 Jun 2025 06:48:26 -0700 (PDT) Received: from cesw-amp-gbt-1s-m12830-04.lab.cambridge.arm.com (usa-sjc-imap-foss1.foss.arm.com [10.121.207.14]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 9273E3F66E for ; Fri, 27 Jun 2025 06:48:43 -0700 (PDT) From: Ross Burton To: bitbake-devel@lists.openembedded.org Subject: [PATCH v2] tinfoil: add wait_for decorator and build_file_sync() helper Date: Fri, 27 Jun 2025 14:48:37 +0100 Message-ID: <20250627134837.151783-1-ross.burton@arm.com> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 List-Id: X-Webhook-Received: from li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Fri, 27 Jun 2025 13:48:47 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/bitbake-devel/message/17720 The bitbake worker/server IPC is asynchronous, but tinfoil only has functionality to wait for a response on the build_targets() call. Extract the bulk of the "wait for events and handle errors" logic to a standalone wait_for wrapper, which is the build_targets code without the extra_events or event_callback arguments (for now). Then use this to create a build_file_sync() helper that just wraps the existing build_file() with @wait_for. Signed-off-by: Ross Burton --- bitbake/lib/bb/tinfoil.py | 135 +++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/bitbake/lib/bb/tinfoil.py b/bitbake/lib/bb/tinfoil.py index f48baeb3342..e7fbcbca0a6 100644 --- a/bitbake/lib/bb/tinfoil.py +++ b/bitbake/lib/bb/tinfoil.py @@ -14,7 +14,7 @@ import time import atexit import re from collections import OrderedDict, defaultdict -from functools import partial +from functools import partial, wraps from contextlib import contextmanager import bb.cache @@ -27,6 +27,135 @@ import bb.remotedata from bb.main import setup_bitbake, BitBakeConfigParameters import bb.fetch2 +def wait_for(f): + """ + Wrap a function that makes an asynchronous tinfoil call using + self.run_command() and wait for events to say that the call has been + successful, or an error has occurred. + """ + @wraps(f) + def wrapper(self, *args, handle_events=True, extra_events=None, event_callback=None, **kwargs): + if handle_events: + # A reasonable set of default events matching up with those we handle below + eventmask = [ + 'bb.event.BuildStarted', + 'bb.event.BuildCompleted', + 'logging.LogRecord', + 'bb.event.NoProvider', + 'bb.command.CommandCompleted', + 'bb.command.CommandFailed', + 'bb.build.TaskStarted', + 'bb.build.TaskFailed', + 'bb.build.TaskSucceeded', + 'bb.build.TaskFailedSilent', + 'bb.build.TaskProgress', + 'bb.runqueue.runQueueTaskStarted', + 'bb.runqueue.sceneQueueTaskStarted', + 'bb.event.ProcessStarted', + 'bb.event.ProcessProgress', + 'bb.event.ProcessFinished', + ] + if extra_events: + eventmask.extend(extra_events) + ret = self.set_event_mask(eventmask) + + includelogs = self.config_data.getVar('BBINCLUDELOGS') + loglines = self.config_data.getVar('BBINCLUDELOGS_LINES') + + # Call actual function + ret = f(self, *args, **kwargs) + + if handle_events: + lastevent = time.time() + result = False + # Borrowed from knotty, instead somewhat hackily we use the helper + # as the object to store "shutdown" on + helper = bb.ui.uihelper.BBUIHelper() + helper.shutdown = 0 + parseprogress = None + termfilter = bb.ui.knotty.TerminalFilter(helper, helper, self.logger.handlers, quiet=self.quiet) + try: + while True: + try: + event = self.wait_event(0.25) + if event: + lastevent = time.time() + if event_callback and event_callback(event): + continue + if helper.eventHandler(event): + if isinstance(event, bb.build.TaskFailedSilent): + self.logger.warning("Logfile for failed setscene task is %s" % event.logfile) + elif isinstance(event, bb.build.TaskFailed): + bb.ui.knotty.print_event_log(event, includelogs, loglines, termfilter) + continue + if isinstance(event, bb.event.ProcessStarted): + if self.quiet > 1: + continue + parseprogress = bb.ui.knotty.new_progress(event.processname, event.total) + parseprogress.start(False) + continue + if isinstance(event, bb.event.ProcessProgress): + if self.quiet > 1: + continue + if parseprogress: + parseprogress.update(event.progress) + else: + bb.warn("Got ProcessProgress event for something that never started?") + continue + if isinstance(event, bb.event.ProcessFinished): + if self.quiet > 1: + continue + if parseprogress: + parseprogress.finish() + parseprogress = None + continue + if isinstance(event, bb.command.CommandCompleted): + result = True + break + if isinstance(event, (bb.command.CommandFailed, bb.command.CommandExit)): + self.logger.error(str(event)) + result = False + break + if isinstance(event, logging.LogRecord): + if event.taskpid == 0 or event.levelno > logging.INFO: + self.logger.handle(event) + continue + if isinstance(event, bb.event.NoProvider): + self.logger.error(str(event)) + result = False + break + elif helper.shutdown > 1: + break + termfilter.updateFooter() + if time.time() > (lastevent + (3*60)): + if not self.run_command('ping', handle_events=False): + print("\nUnable to ping server and no events, closing down...\n") + return False + except KeyboardInterrupt: + termfilter.clearFooter() + if helper.shutdown == 1: + print("\nSecond Keyboard Interrupt, stopping...\n") + ret = self.run_command("stateForceShutdown") + if ret and ret[2]: + self.logger.error("Unable to cleanly stop: %s" % ret[2]) + elif helper.shutdown == 0: + print("\nKeyboard Interrupt, closing down...\n") + interrupted = True + ret = self.run_command("stateShutdown") + if ret and ret[2]: + self.logger.error("Unable to cleanly shutdown: %s" % ret[2]) + helper.shutdown = helper.shutdown + 1 + termfilter.clearFooter() + finally: + termfilter.finish() + if helper.failed_tasks: + result = False + return result + else: + return ret + + return wrapper + # We need this in order to shut down the connection to the bitbake server, # otherwise the process will never properly exit @@ -700,6 +829,10 @@ class Tinfoil: """ return self.run_command('buildFile', buildfile, task, internal) + @wait_for + def build_file_sync(self, *args): + self.build_file(*args) + def build_targets(self, targets, task=None, handle_events=True, extra_events=None, event_callback=None): """ Builds the specified targets. This is equivalent to a normal invocation