From patchwork Wed Aug 19 22:00:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95843 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 DF455C5DF8D for ; Wed, 19 Aug 2026 22:01:53 +0000 (UTC) Received: from mta-64-226.siemens.flowmailer.net (mta-64-226.siemens.flowmailer.net [185.136.64.226]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.1129.1787176907031573518 for ; Wed, 19 Aug 2026 15:01:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=cjUL+D5F; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.226, mailfrom: fm-1329275-20260819220143955fd15c4400020702-przrnz@rts-flowmailer.siemens.com) Received: by mta-64-226.siemens.flowmailer.net with ESMTPSA id 20260819220143955fd15c4400020702 for ; Thu, 20 Aug 2026 00:01:43 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=5VQ2D8P3tTzFlOGYHLE+KG4twPBVxqhSmR48g+AKgM4=; b=cjUL+D5FU3QukpKbhEzPlG6cFr2Rkj/TdeTmQIeNAyDMcSzWMCeJFfVzqkQYJCDvuEV1kN /ugoeAR/DU2HlEpRi7HMBhgi4aVwXG/EVks1GUQnpUoEUzDu8Rjr1XlO0SET/zz9LX3SwdkG /TJJ7IAppVuiPF1OrZBbb4QJam7mS94HwSNuuoS3GZyB92JAJLHUggVEUFpHZFXl+5jwqxSW r6/sMG32LdyL8t6xjVtJMlnT5xQfQ/rwWMwNzRqTZyFTX6C6r2M4jiwa7wKUKAlCRAPfXyjk 5lwCqezCyMlEEZdLcVx+EEgHww4e/ZtGJbh5rebRp+mIMAErEY7QCjxw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 01/14] devtool: deploy-target: pass tar -m to avoid future timestamp warnings Date: Thu, 20 Aug 2026 00:00:08 +0200 Message-ID: <20260819220138.4095398-2-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:53 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243786 From: Adrian Freihofer devtool deploy-target tars up the build output and pipes it through ssh to the target, where it's extracted with "tar xv". If the target's clock is behind the host, tar prints a "time stamp ... is ... in the future" warning per file, since the archived mtimes come from the host. Add -m (--touch) so tar sets mtimes to the current time on extraction instead of restoring them from the archive, avoiding the warning. Supported identically by GNU tar and BusyBox tar. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/deploy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/lib/devtool/deploy.py b/scripts/lib/devtool/deploy.py index 8228d9914f..35ec0b1b4d 100644 --- a/scripts/lib/devtool/deploy.py +++ b/scripts/lib/devtool/deploy.py @@ -108,9 +108,10 @@ def _prepare_remote_script(deploy, destdir='/', verbose=False, dryrun=False, und lines.append('mkdir -p `dirname $manifest`') lines.append('mkdir -p $2') if verbose: - lines.append(' tar xv -C $2 -f - | tee $manifest') + # -m avoids "time stamp is in the future" warnings if the target's clock is behind + lines.append(' tar xvm -C $2 -f - | tee $manifest') else: - lines.append(' tar xv -C $2 -f - > $manifest') + lines.append(' tar xvm -C $2 -f - > $manifest') lines.append('sed -i "s!^./!$2!" $manifest') elif not dryrun: # Put any preserved files back From patchwork Wed Aug 19 22:00:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95839 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 CE91CC5DF81 for ; Wed, 19 Aug 2026 22:01:52 +0000 (UTC) Received: from mta-64-225.siemens.flowmailer.net (mta-64-225.siemens.flowmailer.net [185.136.64.225]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.1132.1787176907030115728 for ; Wed, 19 Aug 2026 15:01:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=GwEDg/NN; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-20260819220143a61fcf37b8000207f5-nabovq@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 20260819220143a61fcf37b8000207f5 for ; Thu, 20 Aug 2026 00:01:43 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=0LME96prFOIM/uXU+8jGo4MT5gpK2xxLXqqh7BMRT2Y=; b=GwEDg/NNPoWJn4zaAL+AxLXPCjWhTKP+mdmAYBGW834ZxPU2j9meBGtpru6h7VjxJXXB3P 5TfE9dwjCnkpZos1U7OgkDaQ5HlFOzbq2lDnPLiWoGTdMwgwNjhCQc+/QiW2f7Gi5bX3Empe 7k0ogrLd05F0tksCNos7bc4mYVueyz1srdu6/Ls7DZvIBfb7OuEn8rodqZRdNQ5Lw+TklzAN NPTNTyLjbKCUQMWw5875uOt+7Cw1/X5MEp8/9Y2lzdIdjVwcPL540qiPjKjD6dpjb5qLe1F4 GIjE+PEnsTIATRAdNn5cgNCIydyUpdfRFg+srnCb4mr/IPxHDErCNJYg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 02/14] oe-selftest: devtool deploy: use QEMU target commands Date: Thu, 20 Aug 2026 00:00:09 +0200 Message-ID: <20260819220138.4095398-3-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:52 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243790 From: Adrian Freihofer Use the QEMU target API for target-side verification in devtool deploy selftests instead of invoking SSH directly. Copy the generated file list through the target API before comparing deployed file metadata. There are more such instances in the devtool ide-sdk selftests that could be converted to use the QEMU target API, but this will be done by follow-up patches with more changes than just this refactoring. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index df0910aaa6..b0c7835134 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2070,8 +2070,8 @@ class DevtoolDeployTargetTests(DevtoolBase): self.logger.debug(deploy_cmd) result = runCmd(deploy_cmd) # Run a test command to see if it was installed properly - sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' - result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand)) + status, _ = qemu.run(testcommand) + self.assertEqual(status, 0) # Check if it deployed all of the files with the right ownership/perms # First look on the host - need to do this under pseudo to get the correct ownership/perms bb_vars = get_bb_vars(['D', 'FAKEROOTENV', 'FAKEROOTCMD', 'PATH'], testrecipe) @@ -2090,15 +2090,21 @@ class DevtoolDeployTargetTests(DevtoolBase): for line in filelist1: splitline = line.split() f.write(splitline[-1] + '\n') - result = runCmd('cat %s | ssh -q %s root@%s \'xargs ls -l\'' % (tmpfilelist, sshargs, qemu.ip)) - filelist2 = self._process_ls_output(result.output) + remotefilelist = '/tmp/%s' % os.path.basename(tmpfilelist) + status, _ = qemu.copy_to(tmpfilelist, remotefilelist) + self.assertEqual(status, 0) + status, output = qemu.run( + 'xargs ls -l < %s; status=$?; rm -f %s; exit $status' % ( + remotefilelist, remotefilelist)) + self.assertEqual(status, 0) + filelist2 = self._process_ls_output(output) filelist1.sort(key=lambda item: item.split()[-1]) filelist2.sort(key=lambda item: item.split()[-1]) self.assertEqual(filelist1, filelist2) # Test undeploy-target result = runCmd('devtool undeploy-target -c %s root@%s' % (testrecipe, qemu.ip)) - result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand), ignore_status=True) - self.assertNotEqual(result, 0, 'undeploy-target did not remove command as it should have') + status, _ = qemu.run(testcommand) + self.assertNotEqual(status, 0, 'undeploy-target did not remove command as it should have') class DevtoolBuildImageTests(DevtoolBase): From patchwork Wed Aug 19 22:00:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95846 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 1E1D1C5DF8C for ; Wed, 19 Aug 2026 22:01:54 +0000 (UTC) Received: from mta-64-227.siemens.flowmailer.net (mta-64-227.siemens.flowmailer.net [185.136.64.227]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.1134.1787176907030572053 for ; Wed, 19 Aug 2026 15:01:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=MCsX/7/U; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.227, mailfrom: fm-1329275-202608192201431b485499c100020739-txpve2@rts-flowmailer.siemens.com) Received: by mta-64-227.siemens.flowmailer.net with ESMTPSA id 202608192201431b485499c100020739 for ; Thu, 20 Aug 2026 00:01:43 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=UNgIJ1oVQJ0La/+N24u6MB56rijVPnMWsiNDrcz02BY=; b=MCsX/7/UcO9XavcjFmG5RsniYSXkeD27QqhVSnYKEnO6aRwY0CGnx+CqdZj34yVfV2q+9w ZjmeptDpC2aSxPjk0PG/6a5OzAhYiCN34moNbYWrnj1CumgYvU4MO94qGIW4nTYhxk5r99e/ u8S1wju3LodZ9P3dGkqjitT3L2KyZ2w8iqpb5KGd1XmiKgEiPgZGHB56O175Tvsu9SO/A4mX X6PGgCVVZixhgg3oUiCBNEnZ0squklJY7Npbp+Mi9OUfOZZRSDsfCptZz+YcmKVOQZ5P6K4g wd2fKpbZ8GCTCkcVq8W5uAVbE4cZhcrlIo/otG6tUYbmHePVdYMXNN3w==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 03/14] oe-selftest: devtool ide-sdk: Add new test classes for ide-sdk tests Date: Thu, 20 Aug 2026 00:00:10 +0200 Message-ID: <20260819220138.4095398-4-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:54 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243789 From: Adrian Freihofer This allows to run the ide-sdk tests with different toolchains (gcc, clang) and build systems (cmake, meson) in parallel. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 211 +++++++++++++----------- 1 file changed, 111 insertions(+), 100 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index b0c7835134..051be713b0 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -3063,6 +3063,105 @@ class DevtoolIdeSdkTests(DevtoolBase): exe_break_line=63 + LINE_SHIFT, exe_list_line=55 + LINE_SHIFT, hpp_break_line=21 + LINE_SHIFT, lib_break_line=31 + LINE_SHIFT) + def _verify_cmake_preset(self, tempdir): + """Verify the generated cmake preset works as expected + + Check if compiling works + Check if unit tests can be executed in qemu (not qemu-system) + """ + with open(os.path.join(tempdir, 'CMakeUserPresets.json')) as cmake_preset_j: + cmake_preset_d = json.load(cmake_preset_j) + config_presets = cmake_preset_d["configurePresets"] + self.assertEqual(len(config_presets), 1) + cmake_exe = config_presets[0]["cmakeExecutable"] + preset_name = config_presets[0]["name"] + compile_cmd = '%s --build --preset %s' % (cmake_exe, preset_name) + + # Verify the wrapper for cmake native is available + self.assertExists(cmake_exe) + + # Verify the cmake preset generated by devtool ide-sdk is available + result = runCmd('%s --list-presets' % cmake_exe, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn(preset_name, result.output) + + # Verify cmake re-uses the o files compiled by bitbake + result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("ninja: no work to do.", result.output) + + # Verify the unit tests work (in Qemu user mode) + result = runCmd('%s --target test' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("100% tests passed", result.output) + + # Verify re-building and testing works again + result = runCmd('%s --target clean' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("Cleaning", result.output) + result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("Building", result.output) + self.assertIn("Linking", result.output) + result = runCmd('%s --target test' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("Running tests...", result.output) + self.assertIn("100% tests passed", result.output) + + return compile_cmd + + def _verify_meson_build(self, tempdir, recipe_name): + """Verify meson works as expected + + Check if compiling works + Check if unit tests can be executed in qemu (not qemu-system) + """ + meson_exe = os.path.join(self._workspace_scripts_dir(recipe_name), "meson") + self.assertExists(meson_exe) + build_dir = os.path.join(self._sources_workdir_dir(tempdir), recipe_name + "-1.0") + compile_cmd = '%s compile -C %s' % (meson_exe, build_dir) + + # Verify meson re-uses the o files compiled by bitbake + result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("ninja: no work to do.", result.output) + + # Verify the unit tests work (in Qemu user mode) + result = runCmd('%s test -C %s' % (meson_exe, build_dir), + cwd=tempdir, output_log=self._cmd_logger) + self.assertEqual(result.status, 0) + self.assertRegex(result.output, r"Fail:\s+0") + + # Verify re-building and testing works again + result = runCmd('%s compile -C %s --clean' % (meson_exe, build_dir), + cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("Cleaning...", result.output) + result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("Linking target", result.output) + result = runCmd('%s test -C %s' % (meson_exe, build_dir), + cwd=tempdir, output_log=self._cmd_logger) + self.assertEqual(result.status, 0) + self.assertRegex(result.output, r"Fail:\s+0") + + return compile_cmd + + def _verify_service_running(self, qemu, service_name): + """Helper to verify a service is running in Qemu""" + # Use anchored regex (^name$) instead of pgrep -x because the target + # may have busybox pgrep which does not support the -x flag. + status, output = qemu.run("pgrep '^%s$'" % service_name) + self.assertEqual(status, 0, msg="%s service not running: %s" % + (service_name, output)) + self.assertTrue(output.strip().isdigit(), + f"pgrep output should be a PID integer, got: {output.strip()}") + + def _verify_conf_file(self, qemu, conf_file, owner, group): + """Helper to verify a configuration file is owned by the proper user and group""" + stat_cmd = "stat -c '%%U %%G' %s" % conf_file + status, output = qemu.run(stat_cmd) + self.assertEqual(status, 0, msg="Failed to stat %s: %s" % (conf_file, output)) + actual_owner, actual_group = output.strip().split() + self.assertEqual(actual_owner, owner, + msg="%s not owned by user %s: got %s" % (conf_file, owner, actual_owner)) + self.assertEqual(actual_group, group, + msg="%s not owned by group %s: got %s" % (conf_file, group, actual_group)) + + +class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): + def _gdb_cross(self): """Verify gdb-cross is provided by devtool ide-sdk""" target_arch = self.td["TARGET_ARCH"] @@ -3207,102 +3306,6 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertEqual(r.status, 0) self.assertNotIn("gdbserver ", r.output) - def _verify_cmake_preset(self, tempdir): - """Verify the generated cmake preset works as expected - - Check if compiling works - Check if unit tests can be executed in qemu (not qemu-system) - """ - with open(os.path.join(tempdir, 'CMakeUserPresets.json')) as cmake_preset_j: - cmake_preset_d = json.load(cmake_preset_j) - config_presets = cmake_preset_d["configurePresets"] - self.assertEqual(len(config_presets), 1) - cmake_exe = config_presets[0]["cmakeExecutable"] - preset_name = config_presets[0]["name"] - compile_cmd = '%s --build --preset %s' % (cmake_exe, preset_name) - - # Verify the wrapper for cmake native is available - self.assertExists(cmake_exe) - - # Verify the cmake preset generated by devtool ide-sdk is available - result = runCmd('%s --list-presets' % cmake_exe, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn(preset_name, result.output) - - # Verify cmake re-uses the o files compiled by bitbake - result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("ninja: no work to do.", result.output) - - # Verify the unit tests work (in Qemu user mode) - result = runCmd('%s --target test' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("100% tests passed", result.output) - - # Verify re-building and testing works again - result = runCmd('%s --target clean' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("Cleaning", result.output) - result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("Building", result.output) - self.assertIn("Linking", result.output) - result = runCmd('%s --target test' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("Running tests...", result.output) - self.assertIn("100% tests passed", result.output) - - return compile_cmd - - def _verify_meson_build(self, tempdir, recipe_name): - """Verify meson works as expected - - Check if compiling works - Check if unit tests can be executed in qemu (not qemu-system) - """ - meson_exe = os.path.join(self._workspace_scripts_dir(recipe_name), "meson") - self.assertExists(meson_exe) - build_dir = os.path.join(self._sources_workdir_dir(tempdir), recipe_name + "-1.0") - compile_cmd = '%s compile -C %s' % (meson_exe, build_dir) - - # Verify meson re-uses the o files compiled by bitbake - result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("ninja: no work to do.", result.output) - - # Verify the unit tests work (in Qemu user mode) - result = runCmd('%s test -C %s' % (meson_exe, build_dir), - cwd=tempdir, output_log=self._cmd_logger) - self.assertEqual(result.status, 0) - self.assertRegex(result.output, r"Fail:\s+0") - - # Verify re-building and testing works again - result = runCmd('%s compile -C %s --clean' % (meson_exe, build_dir), - cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("Cleaning...", result.output) - result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("Linking target", result.output) - result = runCmd('%s test -C %s' % (meson_exe, build_dir), - cwd=tempdir, output_log=self._cmd_logger) - self.assertEqual(result.status, 0) - self.assertRegex(result.output, r"Fail:\s+0") - - return compile_cmd - - def _verify_service_running(self, qemu, service_name): - """Helper to verify a service is running in Qemu""" - # Use anchored regex (^name$) instead of pgrep -x because the target - # may have busybox pgrep which does not support the -x flag. - status, output = qemu.run("pgrep '^%s$'" % service_name) - self.assertEqual(status, 0, msg="%s service not running: %s" % - (service_name, output)) - self.assertTrue(output.strip().isdigit(), - f"pgrep output should be a PID integer, got: {output.strip()}") - - def _verify_conf_file(self, qemu, conf_file, owner, group): - """Helper to verify a configuration file is owned by the proper user and group""" - stat_cmd = "stat -c '%%U %%G' %s" % conf_file - status, output = qemu.run(stat_cmd) - self.assertEqual(status, 0, msg="Failed to stat %s: %s" % (conf_file, output)) - actual_owner, actual_group = output.strip().split() - self.assertEqual(actual_owner, owner, - msg="%s not owned by user %s: got %s" % (conf_file, owner, actual_owner)) - self.assertEqual(actual_group, group, - msg="%s not owned by group %s: got %s" % (conf_file, group, actual_group)) - @OETestTag("runqemu") def test_devtool_ide_sdk_none_qemu(self): """Start qemu-system and run tests for multiple recipes. ide=none is used.""" @@ -3701,6 +3704,8 @@ class DevtoolIdeSdkTests(DevtoolBase): # Verify deployment and remote debugging works self._verify_launch_json_debugging(tempdir, qemu, example_exe) +class DevtoolIdeSdkKernelTests(DevtoolIdeSdkTests): + @OETestTag("runqemu") def test_devtool_ide_sdk_code_kernel_module(self): """Verify a kernel module recipe works with ide=code mode @@ -3916,6 +3921,8 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertIn(MAGIC_STRING_NEW, output, 'New magic string not found in sysfs after rebuild: %s' % output) +class DevtoolIdeSdkSharedTests(DevtoolIdeSdkTests): + def test_devtool_ide_sdk_shared_sysroots(self): """Verify the shared sysroot SDK""" @@ -3977,6 +3984,8 @@ class DevtoolIdeSdkTests(DevtoolBase): runCmdEnv('meson setup %s' % tempdir_meson, cwd=cpp_example_src, output_log=self._cmd_logger) runCmdEnv('meson compile', cwd=tempdir_meson, output_log=self._cmd_logger) +class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests): + def _verify_launch_json_lldb(self, tempdir): """Verify the launch.json file contains valid CodeLLDB (type: lldb) configurations.""" launch_json_path = os.path.join(tempdir, '.vscode', 'launch.json') @@ -4320,7 +4329,7 @@ class DevtoolIdeSdkTests(DevtoolBase): self._lldb_debug_cpp_example_check(r.output, magic_string) @OETestTag("runqemu") - def test_devtool_ide_sdk_code_cmake_clang(self): + def test_devtool_ide_sdk_code_cmake(self): """Verify a cmake recipe built with clang works with ide=code (CodeLLDB debugging). This test uses the cmake-example-clang recipe which is a cmake-example variant @@ -4375,7 +4384,7 @@ class DevtoolIdeSdkTests(DevtoolBase): tempdir, qemu, magic_string)) @OETestTag("runqemu") - def test_devtool_ide_sdk_code_meson_clang(self): + def test_devtool_ide_sdk_code_meson(self): """Verify a meson recipe built with clang works with ide=code (CodeLLDB debugging). This is the meson/ninja counterpart of test_devtool_ide_sdk_code_cmake_clang. @@ -4498,7 +4507,7 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertNotIn('lldb-server', r.output) @OETestTag("runqemu") - def test_devtool_ide_sdk_none_cmake_clang(self): + def test_devtool_ide_sdk_none_cmake(self): """Verify ide=none generates correct LLDB scripts for a clang cmake recipe. Uses cmake-example-clang (TOOLCHAIN = "clang") which is built with the @@ -4561,7 +4570,7 @@ class DevtoolIdeSdkTests(DevtoolBase): tempdir, qemu, recipe_name, example_exe, magic_string)) @OETestTag("runqemu") - def test_devtool_ide_sdk_none_meson_clang(self): + def test_devtool_ide_sdk_none_meson(self): """Verify ide=none generates correct LLDB scripts for a clang meson recipe. This is the meson/ninja counterpart of test_devtool_ide_sdk_none_cmake_clang. @@ -4629,6 +4638,8 @@ class DevtoolIdeSdkTests(DevtoolBase): lambda magic_string: self._lldb_none_debugging_multi( tempdir, qemu, recipe_name, example_exe, magic_string)) +class DevtoolIdeSdkMiscTests(DevtoolIdeSdkTests): + def test_devtool_ide_sdk_plugins(self): """Test that devtool ide-sdk can use plugins from other layers.""" From patchwork Wed Aug 19 22:00:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95849 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 547A1C5DF91 for ; Wed, 19 Aug 2026 22:01:54 +0000 (UTC) Received: from mta-64-226.siemens.flowmailer.net (mta-64-226.siemens.flowmailer.net [185.136.64.226]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.1138.1787176907031018028 for ; Wed, 19 Aug 2026 15:01:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=FGCI1rZE; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.226, mailfrom: fm-1329275-20260819220143cb3a1ee85d00020758-olpryo@rts-flowmailer.siemens.com) Received: by mta-64-226.siemens.flowmailer.net with ESMTPSA id 20260819220143cb3a1ee85d00020758 for ; Thu, 20 Aug 2026 00:01:44 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=G7LVVwYWMZiWTFPbGoHBnWd++oOlopoo8Yx8tzuzJps=; b=FGCI1rZENrQWFNhPg+xCmmQUjEM+FWs+xmB71BCuW/yb8pxQD8tSkN26wb63S2VXjTs+TX ZeQsDEnqoEOzVzg2y4GNcDYHQXGLYD3RspDoXwkJRM/Ie3Rjo4nzENJCpSCpsM5kwXw3TN8J tWDLS91n5pxVTUR2qRyozmRaEJVWFSn7ZysJyLJl4UDc1NBiqdnt8Pj3UJAe4hZVgF8i369s DRUysCCjR9l+6j6GDU325TPtj0+fPYOPtFrX/PGdukWN+N2rHiQtj2EQ6UrZJaF7m771BsUV AyhHr1N0yL2lbvyoyF0iY8yKRPw6V4v+nxSVFz56+m2FG03NgRpBGcTA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 04/14] devtool: ide-sdk: fix meson IntelliSense for gcc toolchain Date: Thu, 20 Aug 2026 00:00:11 +0200 Message-ID: <20260819220138.4095398-5-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:54 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243794 From: Adrian Freihofer The configurationProvider = "mesonbuild.mesonbuild" approach in c_cpp_properties.json worked for meson recipes built with clang (the __gen_meson_absolute_cross_file workaround absolutized the clang binary path in an extra cross-file so the mesonbuild extension could find it), but failed for the gcc toolchain: the extension could not resolve the bare compiler name in the meson.cross file and silently fell back to the host compiler for IntelliSense. The sysroot was also absent, so built-in headers were resolved against the host system. Replace the extension-based approach with explicit settings that work for both gcc and clang toolchains: - compilerPath: absolute path to the cross CXX binary in staging_bindir_toolchain, so cpptools finds the right compiler - compilerArgs: ["--sysroot="], so built-in includes are resolved against the target sysroot headers - compileCommands: path to compile_commands.json in the meson build directory B, which meson/ninja generates automatically with the exact per-file flags (include paths, defines, -march= etc.) - intelliSenseMode: derived from TARGET_ARCH and TOOLCHAIN (linux-gcc-arm64 / linux-clang-x64, etc.) With compileCommands pointing at the real compilation database, the mesonbuild extension is no longer needed for IntelliSense, so the __gen_meson_absolute_cross_file workaround is also removed. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_plugins/ide_code.py | 66 +++++++-------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py index 9faba3f2d1..d190858d44 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -147,45 +147,6 @@ class IdeVSCode(IdeBase): def dot_code_dir(self, modified_recipe): return os.path.join(modified_recipe.srctree, '.vscode') - def __gen_meson_absolute_cross_file(self, modified_recipe): - """Generate an extra cross file overriding c/cpp binaries with absolute paths. - - The recipe's generated meson.cross references the toolchain binaries - (e.g. "aarch64-poky-linux-clang++") by bare name, relying on PATH - being set up by the meson wrapper script at build time. Meson stores - that command exactly as configured (it never resolves it to an - absolute path itself), so it ends up unresolved in - compile_commands.json and meson-info/intro-compilers.json. cpptools - (via the mesonbuild extension) resolves compilerPath using its own - process PATH, which does not include the toolchain directory, and - silently falls back to a host compiler for IntelliSense. - - Real builds are unaffected since the wrapper script sets up PATH, so - the recipe's meson.cross is left untouched. Instead, an extra - --cross-file is layered on top with only the c/cpp [binaries] - entries absolutized, reusing the exact same flags as CC/CXX to avoid - any behavior drift. Meson merges multiple machine files, with later - files overriding matching keys from earlier ones. - """ - def absolutize(cmd): - args = cmd.split() - args[0] = os.path.join( - modified_recipe.staging_bindir_toolchain, args[0]) - return repr(args) - - lines = ["[binaries]"] - if modified_recipe.cc: - lines.append("c = %s" % absolutize(modified_recipe.cc)) - if modified_recipe.cxx: - lines.append("cpp = %s" % absolutize(modified_recipe.cxx)) - - os.makedirs(modified_recipe.ide_sdk_dir, exist_ok=True) - cross_file = os.path.join( - modified_recipe.ide_sdk_dir, 'meson-absolute-toolchain.cross') - with open(cross_file, 'w') as f: - f.write(os.linesep.join(lines) + os.linesep) - return ['--cross-file', cross_file] - def __vscode_settings_meson(self, settings_dict, modified_recipe): if modified_recipe.build_tool is not BuildTool.MESON: return @@ -193,11 +154,16 @@ class IdeVSCode(IdeBase): confopts = modified_recipe.mesonopts.split() confopts += modified_recipe.meson_cross_file.split() - if modified_recipe.meson_cross_file: - confopts += self.__gen_meson_absolute_cross_file(modified_recipe) confopts += modified_recipe.extra_oemeson.split() settings_dict["mesonbuild.configureOptions"] = confopts settings_dict["mesonbuild.buildFolder"] = modified_recipe.b + # Prevent the extension from writing C_Cpp.default.configurationProvider + # for itself once meson-info.json appears, which would take precedence + # over the compileCommands set in c_cpp_properties.json. + settings_dict["mesonbuild.modifySettings"] = False + # Clear any such value a previous run (or extension version without + # modifySettings support) already wrote to this workspace's settings. + settings_dict["C_Cpp.default.configurationProvider"] = "" def __vscode_settings_cmake(self, settings_dict, modified_recipe): """Add cmake specific settings to settings.json. @@ -328,8 +294,22 @@ class IdeVSCode(IdeBase): if modified_recipe.build_tool is BuildTool.CMAKE: properties_dict["configurationProvider"] = "ms-vscode.cmake-tools" elif modified_recipe.build_tool is BuildTool.MESON: - properties_dict["configurationProvider"] = "mesonbuild.mesonbuild" - properties_dict["compilerPath"] = os.path.join(modified_recipe.staging_bindir_toolchain, modified_recipe.cxx.split()[0]) + # configurationProvider = "mesonbuild.mesonbuild" does not work because + # the cross-compiler is not in PATH and omits the sysroot by default. + compiler_type = 'clang' if modified_recipe.toolchain == 'clang' else 'gcc' + arch_map = { + 'aarch64': 'arm64', 'x86_64': 'x64', 'arm': 'arm', + 'i686': 'x86', 'i586': 'x86', 'riscv64': 'x64', 'riscv32': 'x86', + } + intelli_arch = arch_map.get(modified_recipe.target_arch, 'x64') + properties_dict["compilerPath"] = os.path.join( + modified_recipe.staging_bindir_toolchain, + modified_recipe.cxx.split()[0]) + properties_dict["compilerArgs"] = [ + "--sysroot=" + modified_recipe.recipe_sysroot] + properties_dict["compileCommands"] = os.path.join( + modified_recipe.b, 'compile_commands.json') + properties_dict["intelliSenseMode"] = "linux-%s-%s" % (compiler_type, intelli_arch) elif modified_recipe.build_tool is BuildTool.KERNEL_MODULE: # Using e.g. configurationProvider = "ms-vscode.makefile-tools" was not successful properties_dict["compilerPath"] = os.path.join(modified_recipe.staging_bindir_toolchain, modified_recipe.kernel_cc.split()[0]) From patchwork Wed Aug 19 22:00:12 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95842 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 D7B00C5DF8B for ; Wed, 19 Aug 2026 22:01:53 +0000 (UTC) Received: from mta-64-225.siemens.flowmailer.net (mta-64-225.siemens.flowmailer.net [185.136.64.225]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.1133.1787176907030495202 for ; Wed, 19 Aug 2026 15:01:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=P8TLC/tD; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-20260819220144d0e0e5c9f80002079a-febe_5@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 20260819220144d0e0e5c9f80002079a for ; Thu, 20 Aug 2026 00:01:44 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=0d7wl/WvSmWGtaa/PKeuZVaOtcmBCwvhFH5fBtTFSbQ=; b=P8TLC/tDKL3vRfRwyWgm1By/l+FBmstwAXLxo2rEDNOGwEq4CQSvFdmvKP99XIUbvFN95k DSEr+8K5hf9oM6CNn1XXlGwL7+itQETx6t/uwxPoxtaXIxQJEOSN3/j7i7iV6ILu/FRtwTrB 2w3Ms0KibsRoJx28axpHNW/5Jl/zqidNjA0ILev6YTsdALmXjTwp+7u23LiiZfawsk3IvNeC LSD3rYBZOhiqBx7vIh8xLa5US1az5d9nnjR3qK+UmqNOWZq6pOWFiuwEYfqBRn0hIWuHkQW9 Ye/AHVyxaxPRZWJlo7X0B9zG+5iIKPqLGDD/TcCxy6gJxNRvI0c86Bww==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 05/14] ccp-example: fix PID file handling Date: Thu, 20 Aug 2026 00:00:12 +0200 Message-ID: <20260819220138.4095398-6-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:53 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243792 From: Adrian Freihofer Remove the PID file if the process is not running anymore. This can happen when a debugger kills the process it debugs. Signed-off-by: Adrian Freihofer --- meta-selftest/recipes-test/cpp/files/cpp-example.init | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/meta-selftest/recipes-test/cpp/files/cpp-example.init b/meta-selftest/recipes-test/cpp/files/cpp-example.init index c154fd1126..30b8486eeb 100644 --- a/meta-selftest/recipes-test/cpp/files/cpp-example.init +++ b/meta-selftest/recipes-test/cpp/files/cpp-example.init @@ -14,6 +14,11 @@ PIDFILE="/var/run/$DAEMON.pid" LOCK_FILE="/var/lock/subsys/$DAEMON" start() { + # A debugger which kills the process it debugs leaves the PID file behind + if [ -f $PIDFILE ] && ! kill -0 $(cat $PIDFILE) 2>/dev/null; then + rm -f $PIDFILE $LOCK_FILE + fi + if [ -f $PIDFILE ]; then echo "$DAEMON is already running." return 1 From patchwork Wed Aug 19 22:00:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95848 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 78E79C5DF93 for ; Wed, 19 Aug 2026 22:01:54 +0000 (UTC) Received: from mta-64-227.siemens.flowmailer.net (mta-64-227.siemens.flowmailer.net [185.136.64.227]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.1137.1787176907030942877 for ; Wed, 19 Aug 2026 15:01:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=hjnkT9NG; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.227, mailfrom: fm-1329275-20260819220144a612fc68540002076b-ioyol4@rts-flowmailer.siemens.com) Received: by mta-64-227.siemens.flowmailer.net with ESMTPSA id 20260819220144a612fc68540002076b for ; Thu, 20 Aug 2026 00:01:44 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=tF9kmAJxWcRoTEXgu2vR8LvSN5T/1sdatjBT61iZ390=; b=hjnkT9NG7I39BG7Cp8wc7T6qHa36BnC3J1z7hGsbrphx8Nx4hXj1JVu4YZVmwEiZxvTsh1 MVl1Igs6a3qV5h+pGXauu6i+ZfsmMCbFuDolh93YGvsI6Ho8xkwcE09XMsPADk9NdQTbt6Fl LuqC/IqaTUhUFFSL2e17huQwGbhoQOhJM71O83t7ZKFBppbiKssFtU6YgvqJyUQ9ol1SsaoH T7U2KAQqx4g4minuXrIrjC9hCEZWiLLGbd9LFzQeAWM7fND8R7hP39MeJuykXJDe6Ma6FJBl W6qazpBTeipVrnpfr1Wk2xrTz7tJ8z0Cwif3ChaC4pepP+pQYxHZfmag==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 06/14] devtool: ide-sdk: default to all modified recipes Date: Thu, 20 Aug 2026 00:00:13 +0200 Message-ID: <20260819220138.4095398-7-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:54 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243788 From: Adrian Freihofer Simplify ide-sdk usage in modified mode: keep the image recipe explicit, but when no modified recipe is passed, take all modified recipes from /appends/*.bbappend. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_sdk.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py index 42b6e381c5..ab2bba5d39 100755 --- a/scripts/lib/devtool/ide_sdk.py +++ b/scripts/lib/devtool/ide_sdk.py @@ -1258,6 +1258,15 @@ def ide_setup(args, config, basepath, workspace): recipes_modified_names)) invalid_params = True if args.mode == DevtoolIdeMode.modified: + if not recipes_modified_names: + appends_dir = os.path.join(config.workspace_path, 'appends') + recipes_modified_names = sorted( + os.path.splitext(os.path.basename(path))[0] + for path in glob.glob(os.path.join(appends_dir, '*.bbappend'))) + if recipes_modified_names: + logger.info( + "No modified recipes specified, using workspace bbappends from %s: %s", + appends_dir, ', '.join(recipes_modified_names)) if len(recipes_other_names): logger.error("Only in shared sysroots mode not modified recipes %s can be handled." % str( recipes_other_names)) @@ -1266,6 +1275,11 @@ def ide_setup(args, config, basepath, workspace): logger.error( "One image recipe is required as the rootfs for the remote development.") invalid_params = True + if not recipes_modified_names: + logger.error( + "At least one modified recipe is required or must be guessable from %s." % + os.path.join(config.workspace_path, 'appends')) + invalid_params = True for modified_recipe_name in recipes_modified_names: if modified_recipe_name.startswith('nativesdk-') or modified_recipe_name.endswith('-native'): logger.error( @@ -1422,7 +1436,9 @@ def register_commands(subparsers, context): help='Setup the SDK and configure the IDE') parser_ide_sdk.add_argument( 'recipenames', nargs='+', help='Generate an IDE configuration suitable to work on the given recipes.\n' - 'Depending on the --mode parameter different types of SDKs and IDE configurations are generated.') + 'Depending on the --mode parameter different types of SDKs and IDE configurations are generated.\n' + 'In modified mode at least the image recipe is required; if no modified recipe is passed, ' + 'all modified recipes are taken from /appends/*.bbappend.') parser_ide_sdk.add_argument( '-m', '--mode', type=DevtoolIdeMode, default=DevtoolIdeMode.modified, help='Different SDK types are supported:\n' From patchwork Wed Aug 19 22:00:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95847 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 679BBC5DF94 for ; Wed, 19 Aug 2026 22:01:54 +0000 (UTC) Received: from mta-64-228.siemens.flowmailer.net (mta-64-228.siemens.flowmailer.net [185.136.64.228]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.1128.1787176907030634526 for ; Wed, 19 Aug 2026 15:01:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=cfFr0tRp; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.228, mailfrom: fm-1329275-202608192201442bd4f68318000207b0-2qig7l@rts-flowmailer.siemens.com) Received: by mta-64-228.siemens.flowmailer.net with ESMTPSA id 202608192201442bd4f68318000207b0 for ; Thu, 20 Aug 2026 00:01:44 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=UB7eVCqupIConBytoa/cucXR0nlNYoUrSRPxeIrccyk=; b=cfFr0tRpERcePlLcChFEYuVx4RrG5baDquj/6KyfzXLc9kpYqfGwyk41oZz4ahONjdT7bw V+qZfop+k4kNp+SD/EB/hxAGpJ0Vwm1YfErjotUX+7qt8JDBYs0/0VwuLCtoJKCmHbX3voBa OOzELC0aYngPJ7jSGrC8xG3fXLcABFhOkPdYkjE+9YEpcLaCezk9hmh1NDmWxwLo0jhMG6DT Lwy737ri0g8vwXMGl/wn04odo1/Q39dWnr3F00321aZtGxa8s2WDqb6E/YgbZ+p7t3VpKboW UU42Z1E6jg+9z3Jte+qv6dcMr1n3g0+5zOsuQo9M+Bm2QcceGqcnA81w==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 07/14] devtool: ide-sdk: let gdbserver reuse the shared debug server wait helper Date: Thu, 20 Aug 2026 00:00:14 +0200 Message-ID: <20260819220138.4095398-8-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:54 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243784 From: Adrian Freihofer GdbCrossConfig open coded its own copy of the loop which waits for the debug server to listen on its port, while LldbServerConfig already used the _target_wait_for_tcp_port_cmd() helper of the common base class. Use the helper for gdbserver too, which requires capturing the gdbserver pid in a shell variable so the helper can refer to it, and defining DEBUG_SERVER_NAME for GdbCrossConfig. Beside removing the duplication this also gives gdbserver the two things the helper already did for lldb-server: the server is killed instead of left behind when it never starts listening, and the reason is printed rather than exiting silently. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_plugins/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py index 63e9682e73..5211df5806 100644 --- a/scripts/lib/devtool/ide_plugins/__init__.py +++ b/scripts/lib/devtool/ide_plugins/__init__.py @@ -139,6 +139,7 @@ class GdbCrossConfig(DebuggerCrossConfig): gdbinit / gdb wrapper scripts used by ide=none as well as the target-side tmp/pid/log paths consumed by the gdbserver start command. """ + DEBUG_SERVER_NAME = "gdbserver" def __init__(self, image_recipe, modified_recipe, binary, default_mode=DebuggerServerModes.MULTI): @@ -193,14 +194,13 @@ class GdbCrossConfig(DebuggerCrossConfig): else: raise DevtoolError("Cannot use gdbserver attach mode for binary %s. No PID found." % self.binary.binary_path) elif server_mode == DebuggerServerModes.MULTI: - hex_port = "%04X" % self.debug_server_port - gdbserver_cmd_start = "grep -q :%s /proc/net/tcp /proc/net/tcp6 2>/dev/null && exit 0; " % hex_port + gdbserver_cmd_start = self._target_tcp_port_check_cmd() + " && exit 0; " gdbserver_cmd_start += "mkdir -p %s; " % self._gdbserver_tmp_dir(server_mode) - gdbserver_cmd_start += "%s --multi :%s > %s 2>&1 & " % ( - self.debugger_cross.debug_server_path, self.debug_server_port, self._gdbserver_log_file(server_mode)) - gdbserver_cmd_start += "echo \\$! > %s; " % self._gdbserver_pid_file(server_mode) - gdbserver_cmd_start += "_w=0; while ! grep -q :%s /proc/net/tcp /proc/net/tcp6 2>/dev/null; " % hex_port - gdbserver_cmd_start += "do _w=\\$((_w+1)); [ \\$_w -lt 100 ] || exit 1; sleep 0.1; done;" + gdbserver_cmd_start += "%s --multi :%s > %s 2>&1 & _gdbserver_pid=\\$!; " % ( + self.debugger_cross.debug_server_path, self.debug_server_port, + self._gdbserver_log_file(server_mode)) + gdbserver_cmd_start += "echo \\$_gdbserver_pid > %s; " % self._gdbserver_pid_file(server_mode) + gdbserver_cmd_start += self._target_wait_for_tcp_port_cmd("gdbserver_pid") else: raise DevtoolError("Unsupported gdbserver mode: %s" % server_mode) return "\"/bin/sh -c '" + gdbserver_cmd_start + "'\"" From patchwork Wed Aug 19 22:00:15 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95840 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 92524C5DF89 for ; Wed, 19 Aug 2026 22:01:53 +0000 (UTC) Received: from mta-64-227.siemens.flowmailer.net (mta-64-227.siemens.flowmailer.net [185.136.64.227]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.1135.1787176907030647504 for ; Wed, 19 Aug 2026 15:01:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=G30CVyxo; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.227, mailfrom: fm-1329275-20260819220144190cce97710002077a-yfn2qk@rts-flowmailer.siemens.com) Received: by mta-64-227.siemens.flowmailer.net with ESMTPSA id 20260819220144190cce97710002077a for ; Thu, 20 Aug 2026 00:01:44 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=QW3KRaFj9rp9zsOiIYwZFBHMfJ+1Fz1xy7506IUrunI=; b=G30CVyxoFRfXUWn0k96Frs86ZjyhMui2oAdUIHsxNR1Fm+PZs8FFp4y6I859ZHdpMtXc0/ TKbYfgc11LYOEpTiH4ut079NVtHvygFgg+tK+x6W5YUrLtbpUXhTXVabrP/zQVfDazapMztw UGmEfNTmMFh0Jea273QNscYiwRuxizbR7pf3hch5K9Z0zZgwsFUAZc+JPnt0l3Kab9XKaipl zvNCzCyij8ReuXnT3u7s+DIn1dM77pFQgutrRvtXxPTenQI4sCp3Sye4xnTO+2ZBX8f8qQN+ gBN8G4RR+2WByXF4XGaHiWKyB18RyhG5g0F84DCaaZX7pR4QaU1sW6Mw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 08/14] oe-selftest: devtool ide-sdk: support $ in gdbserver task command Date: Thu, 20 Aug 2026 00:00:15 +0200 Message-ID: <20260819220138.4095398-9-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:53 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243791 From: Adrian Freihofer This is a preparation commit for the next commit that will add a new a variable to the tasks.json file to support gdbserver commands. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 051be713b0..0791422461 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -3576,10 +3576,11 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): # Start gdbserver on target using the task command (keep the ssh connection open while debugging) ssh_gdbserver_cmd = [task_command] + task_args - # Fix shell command escaping - remove extra quotes from the last argument - # The task_args likely contains a quoted shell command that needs to be unquoted + # The tasks.json argument is formatted for an intermediate shell. Strip + # its quotes and restore dollar expansions before passing it directly to + # SSH via subprocess. if len(ssh_gdbserver_cmd) > 0 and ssh_gdbserver_cmd[-1].startswith('"') and ssh_gdbserver_cmd[-1].endswith('"'): - ssh_gdbserver_cmd[-1] = ssh_gdbserver_cmd[-1][1:-1] # Remove surrounding quotes + ssh_gdbserver_cmd[-1] = ssh_gdbserver_cmd[-1][1:-1].replace('\\$', '$') # Remove surrounding quotes self.logger.debug(f"Starting gdbserver with command: {' '.join(ssh_gdbserver_cmd)}") with RunCmdBackground(ssh_gdbserver_cmd, output_log=self._cmd_logger): # Give gdbserver a moment to start From patchwork Wed Aug 19 22:00:16 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95850 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 3831FC5DF8F for ; Wed, 19 Aug 2026 22:01:54 +0000 (UTC) Received: from mta-64-225.siemens.flowmailer.net (mta-64-225.siemens.flowmailer.net [185.136.64.225]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.1139.1787176907730668893 for ; Wed, 19 Aug 2026 15:01:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=LJseACLd; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-202608192201442e19c648060002078b-95jnn9@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 202608192201442e19c648060002078b for ; Thu, 20 Aug 2026 00:01:44 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=Lp4qr8ZkeRFl5i/3d+XcsYqI0A0WKP9Yb4xuX+di3Q4=; b=LJseACLdbzuW7O8aCxzp6BnU4sOk6zMOuJjTNMXjjpLcgTatsl/UF0scUL+QrPrgZ2CgqA wHOHNfrwbzgsFmTcPaR/gymYb/QZB+P6pobjXdijRMOlgw6xGsyW+d3hlP2jjtYvX2MUQQMo Dd/bkCWdjTFMQlIKLkdh9kHGNm43SwNmeMAauEjEkncojenMBCzMnCqyPgIAIhUmrXm2SE4q 7a0mc8gy3QyVVO4pOT20bafUOtuxQ1Z52dILQL0myB7yWksjexiYrsY/51GSh+tlyDkjcd4U HKeqyhQGm0ZC3hW+naS2a6wKhtTTmD/RtbUg3XTYQBEtVp2U0zh4UnWQ==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 09/14] devtool: ide-sdk: attach via extended-remote Date: Thu, 20 Aug 2026 00:00:16 +0200 Message-ID: <20260819220138.4095398-10-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:54 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243796 From: Adrian Freihofer The old ATTACH mode started gdbserver with "--attach : $(pidof )" and used a plain "request": "launch" config. This is the classic cpptools attach setup, which is fragile and has several long-standing, unfixed bugs, e.g. https://github.com/microsoft/vscode-cpptools/issues/4166. { "name": "Attach with GDB", "type": "cppdbg", "request": "launch", "program": "", "stopAtEntry": true, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "miDebuggerPath": "/usr/bin/gdb", "miDebuggerServerAddress": ":", "postDebugTask": "kill_gdbserver_..." } cppdbg also supports attaching over the extended-remote protocol: { "name": "Attach with GDB", "type": "cppdbg", "request": "attach", "program": "", "MIMode": "gdb", "miDebuggerPath": "/usr/bin/gdb", "miDebuggerServerAddress": ":", "useExtendedRemote": true } Switch to this instead: merge ATTACH into the same persistent "gdbserver --multi" server already used for MULTI mode, since both are now extended-remote sessions that only differ on the client side, and stop it by PID instead of pgrep/killall. ONCE mode gets the same readiness synchronization MULTI already had, since gdbserver now always writes a PID file that the stop commands wait on. Allocate a separate debug server port for each mode. This prevents the persistent attach endpoint from colliding with a once session for the same binary. Until https://github.com/microsoft/vscode-cpptools/pull/14684 is merged, this comes with one downside (which was the blocker for using extended-remote attach mode until now): the user is prompted to pick the process to attach to, instead of it being selected automatically. But this is a temporary limitation, and the new setup is more robust and fixes several long-standing issues with the old ATTACH mode. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 23 +++- scripts/lib/devtool/ide_plugins/__init__.py | 110 +++++++++++++------- scripts/lib/devtool/ide_plugins/ide_code.py | 94 +++++++++-------- scripts/lib/devtool/ide_plugins/ide_none.py | 44 ++++---- 4 files changed, 172 insertions(+), 99 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 0791422461..35abc6a48f 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -3397,21 +3397,31 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): # Track configurations found once_configs = [] attach_configs = [] + server_addresses = [] for config in configurations: # Verify required fields exist - required_fields = ["name", "type", "request", "program", "cwd", "MIMode", + required_fields = ["name", "type", "request", "program", "MIMode", "miDebuggerPath", "miDebuggerServerAddress"] for field in required_fields: self.assertIn(field, config, f"Configuration '{config.get('name', 'Unknown')}' missing required field: {field}") # Verify common configuration values self.assertEqual(config["type"], "cppdbg", f"Configuration '{config['name']}' should use cppdbg type") - self.assertEqual(config["request"], "launch", f"Configuration '{config['name']}' should be launch type") - self.assertEqual(config["cwd"], "${workspaceFolder}", f"Configuration '{config['name']}' should use workspaceFolder as cwd") self.assertEqual(config["MIMode"], "gdb", f"Configuration '{config['name']}' should use gdb MIMode") - self.assertEqual(config.get("externalConsole", False), False, f"Configuration '{config['name']}' should not use external console") - self.assertEqual(config.get("stopAtEntry", True), True, f"Configuration '{config['name']}' should stop at entry") + + if config["request"] == "launch": + self.assertEqual(config["cwd"], "${workspaceFolder}", f"Configuration '{config['name']}' should use workspaceFolder as cwd") + self.assertEqual(config.get("externalConsole", False), False, f"Configuration '{config['name']}' should not use external console") + self.assertEqual(config.get("stopAtEntry", True), True, f"Configuration '{config['name']}' should stop at entry") + elif config["request"] == "attach": + # Attaching to a process running on the target requires the + # extended-remote protocol. Stopping the session then detaches + # from the process instead of killing it. + self.assertTrue(config.get("useExtendedRemote"), f"Configuration '{config['name']}' should use useExtendedRemote") + self.assertNotIn("cwd", config, f"Configuration '{config['name']}' should not set cwd in attach mode") + else: + self.fail(f"Configuration '{config['name']}' has unexpected request type: {config['request']}") # Verify program path is absolute and exists conceptually program = config["program"] @@ -3426,6 +3436,7 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): # Verify server address format server_addr = config["miDebuggerServerAddress"] self.assertRegex(server_addr, r"^\d+\.\d+\.\d+\.\d+:\d+$", f"Configuration '{config['name']}' server address should be IP:PORT format: {server_addr}") + server_addresses.append(server_addr) # Verify additional SO lib search path exists and contains debug paths so_paths = config.get("additionalSOLibSearchPath", []) @@ -3466,6 +3477,8 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): # Verify we have expected configuration types self.assertEqual(len(once_configs), 2, f"Should have two once configuration, found: {once_configs}") self.assertEqual(len(attach_configs), 1, f"Should have one attach configuration, found: {attach_configs}") + self.assertEqual(len(server_addresses), len(set(server_addresses)), + "Each debug configuration should use a distinct server address") def _verify_launch_json_debugging(self, tempdir, qemu, example_exe): """Verify remote debugging and deployment works using launch.json configurations diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py index 5211df5806..136ddd8914 100644 --- a/scripts/lib/devtool/ide_plugins/__init__.py +++ b/scripts/lib/devtool/ide_plugins/__init__.py @@ -59,8 +59,11 @@ class DebuggerCrossConfig: self.binary = binary self.default_mode = default_mode self.binary_pretty = self.binary.binary_path.replace(os.sep, '-').lstrip('-') - self.debug_server_port = DebuggerCrossConfig._port_next - DebuggerCrossConfig._port_next += 1 + self.debug_server_ports = {} + for mode in self.server_modes(): + self.debug_server_ports[mode] = DebuggerCrossConfig._port_next + DebuggerCrossConfig._port_next += 1 + self.debug_server_port = self.debug_server_ports[self.default_mode] self.id_pretty = "%d_%s" % (self.debug_server_port, self.binary_pretty) if self.id_pretty in DebuggerCrossConfig._configs: @@ -68,8 +71,14 @@ class DebuggerCrossConfig: "debugger config for binary %s is already generated" % binary) DebuggerCrossConfig._configs[self.id_pretty] = self + def port(self, mode=None): + """Return the debug server port allocated for a server mode.""" + if mode is None: + mode = self.default_mode + return self.debug_server_ports[mode] + def id_pretty_mode(self, mode): - return "%s_%s" % (self.id_pretty, mode.name.lower()) + return "%d_%s_%s" % (self.port(mode), self.binary_pretty, mode.name.lower()) # Host-side script paths @property @@ -100,15 +109,16 @@ class DebuggerCrossConfig: # Re-tries in 0.1s Example: 300 * 0.1s, i.e. ~30s. TARGET_START_RETRIES = 300 - def _target_tcp_port_check_cmd(self): - hex_port = "%04X" % self.debug_server_port + def _target_tcp_port_check_cmd(self, mode=None): + hex_port = "%04X" % self.port(mode) return "grep -q :%s /proc/net/tcp /proc/net/tcp6 2>/dev/null" % hex_port - def _target_wait_for_tcp_port_cmd(self, pid_var=None, log_file=None): + def _target_wait_for_tcp_port_cmd(self, pid_var=None, log_file=None, mode=None): """Shell fragment waiting until the debug server listens on its port. The server log is dumped to stderr when giving up. """ + port = self.port(mode) dump_log = "cat %s >&2; " % log_file if log_file else "" cleanup = "" if pid_var: @@ -117,8 +127,15 @@ class DebuggerCrossConfig: "_w=0; while ! %s; do _w=\\$((_w+1)); [ \\$_w -lt %d ] || { " "%secho %s did not start on port %s after \\$_w retries >&2; %sexit 1; }; " "sleep 0.1; done;" - % (self._target_tcp_port_check_cmd(), self.TARGET_START_RETRIES, - cleanup, self.DEBUG_SERVER_NAME, self.debug_server_port, dump_log)) + % (self._target_tcp_port_check_cmd(mode), self.TARGET_START_RETRIES, + cleanup, self.DEBUG_SERVER_NAME, port, dump_log)) + + def _target_wait_for_process_exit_cmd(self, pid_var): + return ( + "_w=0; while kill -0 \\$_%s 2>/dev/null; do _w=\\$((_w+1)); " + "[ \\$_w -lt 100 ] || { echo %s did not stop >&2; exit 1; }; " + "sleep 0.1; done;" + % (pid_var, self.DEBUG_SERVER_NAME)) def initialize(self): """Called after construction to generate any required config files.""" @@ -128,7 +145,7 @@ class DebuggerCrossConfig: def _target_start_cmd(self, mode): raise NotImplementedError - def _target_kill_cmd(self): + def _target_stop_cmd(self, mode): raise NotImplementedError @@ -181,33 +198,59 @@ class GdbCrossConfig(DebuggerCrossConfig): Returns something like: "\"/bin/sh -c '/usr/bin/gdbserver --once :1234 /usr/bin/cmake-example'\"" """ + port = self.port(server_mode) if server_mode == DebuggerServerModes.ONCE: + # gdbserver runs directly in the foreground for the whole debug + # session and exits by itself once it ends. Its own "Listening + # on port ..." message (matched by get_debug_server_ready_pattern()) + # is the readiness signal, so no backgrounding, PID file or + # separate polling loop is needed here. gdbserver_cmd_start = "%s --once :%s %s" % ( - self.debugger_cross.debug_server_path, self.debug_server_port, self.binary.binary_path) - elif server_mode == DebuggerServerModes.ATTACH: - pid_command = self.binary.pid_command - if pid_command: - gdbserver_cmd_start = "%s --attach :%s \\$(%s)" % ( - self.debugger_cross.debug_server_path, - self.debug_server_port, - pid_command) - else: - raise DevtoolError("Cannot use gdbserver attach mode for binary %s. No PID found." % self.binary.binary_path) - elif server_mode == DebuggerServerModes.MULTI: - gdbserver_cmd_start = self._target_tcp_port_check_cmd() + " && exit 0; " + self.debugger_cross.debug_server_path, port, self.binary.binary_path) + elif server_mode in (DebuggerServerModes.ATTACH, DebuggerServerModes.MULTI): + # Both modes run a persistent server speaking the extended-remote + # protocol. They differ on the client side only: ATTACH attaches to + # a process that is already running on the target. + pid_file = self._gdbserver_pid_file(server_mode) + log_file = self._gdbserver_log_file(server_mode) + # Reuse an already-running server for this configuration instead + # of starting a second one on the same port. + gdbserver_cmd_start = "if test -f %s && kill -0 \\$(cat %s) 2>/dev/null; then exit 0; fi; " % ( + pid_file, pid_file) gdbserver_cmd_start += "mkdir -p %s; " % self._gdbserver_tmp_dir(server_mode) gdbserver_cmd_start += "%s --multi :%s > %s 2>&1 & _gdbserver_pid=\\$!; " % ( - self.debugger_cross.debug_server_path, self.debug_server_port, - self._gdbserver_log_file(server_mode)) - gdbserver_cmd_start += "echo \\$_gdbserver_pid > %s; " % self._gdbserver_pid_file(server_mode) - gdbserver_cmd_start += self._target_wait_for_tcp_port_cmd("gdbserver_pid") + self.debugger_cross.debug_server_path, port, log_file) + gdbserver_cmd_start += "echo \\$_gdbserver_pid > %s; " % pid_file + gdbserver_cmd_start += self._target_wait_for_tcp_port_cmd( + "gdbserver_pid", mode=server_mode) else: raise DevtoolError("Unsupported gdbserver mode: %s" % server_mode) return "\"/bin/sh -c '" + gdbserver_cmd_start + "'\"" - def _target_kill_cmd(self): - """SSH command to kill gdbserver on the target device.""" - return "\"kill \\$(pgrep -o -f 'gdbserver --attach :%s') 2>/dev/null || true\"" % self.debug_server_port + def get_debug_server_ready_pattern(self, mode=None): + """Regex matching gdbserver's own "Listening on port N" startup message. + + Used as the problemMatcher endsPattern for VS Code and by selftests to + detect readiness directly from gdbserver's output, instead of a + separate polling probe. + """ + return r"^Listening on port %d$" % self.port(mode) + + def _target_stop_cmd(self, server_mode): + """SSH command to stop gdbserver on the target device. + + Stopping is based on the PID file written by the start command. Other + debug sessions run their own gdbserver on the target, so anything + matching by process name would hit them as well. + """ + pid_file = self._gdbserver_pid_file(server_mode) + gdbserver_cmd_stop = "if test -f %s; then _gdbserver_pid=\\$(cat %s); " % ( + pid_file, pid_file) + gdbserver_cmd_stop += "kill \\$_gdbserver_pid 2>/dev/null; " + gdbserver_cmd_stop += self._target_wait_for_process_exit_cmd( + "gdbserver_pid") + gdbserver_cmd_stop += " fi; rm -rf %s" % self._gdbserver_tmp_dir(server_mode) + return "\"/bin/sh -c '" + gdbserver_cmd_stop + "'\"" class LldbServerConfig(DebuggerCrossConfig): @@ -244,10 +287,7 @@ class LldbServerConfig(DebuggerCrossConfig): # lldb-server 21.x and the remote lldb client connects from the host. # Start from /tmp because lldb-server creates temp files in its cwd and # the SSH default cwd (/home/root) may not exist on a minimal image. - if mode == DebuggerServerModes.ONCE: - cmd = "cd /tmp && %s platform --one-shot --server --listen *:%s" % ( - lldb_server, self.debug_server_port) - elif mode == DebuggerServerModes.MULTI: + if mode == DebuggerServerModes.MULTI: pid_file = self._lldb_server_pid_file(mode) tmp_dir = self._lldb_server_tmp_dir(mode) log_file = self._lldb_server_log_file(mode) @@ -261,11 +301,11 @@ class LldbServerConfig(DebuggerCrossConfig): "lldb_server_pid", log_file) else: raise DevtoolError( - "lldb-server does not support mode %s " - "(ATTACH is handled client-side with 'process attach')" % mode) + "lldb-server only supports MULTI mode; " + "ATTACH is handled client-side with 'process attach': %s" % mode) return "\"/bin/sh -c '" + cmd + "'\"" - def _target_kill_cmd(self): + def _target_stop_cmd(self, server_mode): """SSH command to stop a MULTI-mode lldb-server on the target.""" pid_file = self._lldb_server_pid_file(DebuggerServerModes.MULTI) tmp_dir = self._lldb_server_tmp_dir(DebuggerServerModes.MULTI) diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py index d190858d44..1a8a79f623 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -32,14 +32,12 @@ class GdbCrossConfigVSCode(GdbCrossConfig): self._target_start_cmd(mode) ] - def target_ssh_gdbserver_kill_args(self): - """Get the ssh command arguments to kill gdbserver on the target device - - returns something like: - ['-p', '2222', 'root@target', '"kill $(pgrep -o -f \'gdbserver --attach :1234\') 2>/dev/null || true"'] - """ + def target_ssh_gdbserver_stop_args(self, mode=None): + """Get the ssh command arguments to stop gdbserver on the target device""" + if mode is None: + mode = self.default_mode return self._target_ssh_args() + [ - self._target_kill_cmd() + self._target_stop_cmd(mode) ] @@ -59,10 +57,12 @@ class LldbServerConfigVSCode(LldbServerConfig): self._target_start_cmd(mode) ] - def target_ssh_gdbserver_kill_args(self): + def target_ssh_gdbserver_stop_args(self, mode=None): """SSH argument list to stop a running MULTI-mode lldb-server""" + if mode is None: + mode = self.default_mode return self._target_ssh_args() + [ - self._target_kill_cmd() + self._target_stop_cmd(mode) ] class IdeVSCode(IdeBase): @@ -338,25 +338,43 @@ class IdeVSCode(IdeBase): return self._vscode_launch_bin_dbg_lldb(cross_debug_config, server_mode) return self._vscode_launch_bin_dbg_gdb(cross_debug_config, server_mode) + @staticmethod + def _stop_task_label(cross_debug_config, server_mode): + return "stop_%s_%s" % (cross_debug_config.DEBUG_SERVER_NAME, + cross_debug_config.id_pretty_mode(server_mode)) + def _vscode_launch_bin_dbg_gdb(self, cross_debug_config, server_mode): """Generate a cppdbg (GDB) launch configuration entry for launch.json.""" modified_recipe = cross_debug_config.modified_recipe + is_attach = server_mode == DebuggerServerModes.ATTACH + launch_config = { "name": cross_debug_config.id_pretty_mode(server_mode), "type": "cppdbg", - "request": "launch", + "request": "attach" if is_attach else "launch", "program": cross_debug_config.binary.binary_host_path, - "stopAtEntry": True, - "cwd": "${workspaceFolder}", - "environment": [], - "externalConsole": False, "MIMode": "gdb", "preLaunchTask": cross_debug_config.id_pretty_mode(server_mode), "miDebuggerPath": modified_recipe.debugger_cross.gdb, - "miDebuggerServerAddress": "%s:%d" % (modified_recipe.debugger_cross.host, cross_debug_config.debug_server_port) + "miDebuggerServerAddress": "%s:%d" % (modified_recipe.debugger_cross.host, cross_debug_config.port(server_mode)) } + if is_attach: + # Without useExtendedRemote, cppdbg rejects attaching to a remote + # target. It also makes cppdbg offer a picker listing the processes + # running on the target, so the PID does not have to be known when + # this configuration is generated. Stopping the session detaches + # from the process instead of killing it. + launch_config["useExtendedRemote"] = True + else: + # cwd, environment and externalConsole configure the process the + # debugger starts, they are not part of the attach schema. + launch_config["cwd"] = "${workspaceFolder}" + launch_config["environment"] = [] + launch_config["externalConsole"] = False + launch_config["stopAtEntry"] = True + # Search for header files in recipe-sysroot. src_file_map = { "/usr/include": os.path.join(modified_recipe.recipe_sysroot, "usr", "include") @@ -415,10 +433,10 @@ class IdeVSCode(IdeBase): launch_config['sourceFileMap'] = src_file_map launch_config['setupCommands'] = setup_commands - # Add postDebugTask for attach mode to clean up gdbserver - if server_mode == DebuggerServerModes.ATTACH: - kill_task_label = "kill_gdbserver_" + cross_debug_config.id_pretty_mode(server_mode) - launch_config["postDebugTask"] = kill_task_label + if is_attach: + # The extended-remote server outlives the debug session + launch_config["postDebugTask"] = self._stop_task_label( + cross_debug_config, server_mode) return launch_config @@ -574,8 +592,9 @@ class IdeVSCode(IdeBase): if cross_debug_config.modified_recipe is not modified_recipe: continue for server_mode in cross_debug_config.server_modes(): - if server_mode == DebuggerServerModes.MULTI: - # MULTI mode: the SSH command blocks until the port is ready + if server_mode in (DebuggerServerModes.MULTI, + DebuggerServerModes.ATTACH): + # The SSH command blocks until the port is ready # (wait loop in _target_start_cmd), so VSCode treats this as # a regular non-background task. new_task = { @@ -586,7 +605,7 @@ class IdeVSCode(IdeBase): "problemMatcher": [] } else: - # ONCE / ATTACH: gdbserver runs in the foreground for the + # ONCE: gdbserver runs in the foreground for the # whole session, so VSCode needs isBackground + a pattern # matcher to avoid waiting for the task to exit. new_task = { @@ -608,7 +627,7 @@ class IdeVSCode(IdeBase): "background": { "activeOnStart": True, "beginsPattern": ".", - "endsPattern": ".", + "endsPattern": cross_debug_config.get_debug_server_ready_pattern(server_mode), } } ] @@ -621,28 +640,20 @@ class IdeVSCode(IdeBase): tasks_dict['tasks'].append(new_task) - # For attach mode, add a kill task to stop a previously running gdbserver - # This is a known issue with gdbserver --attach that it does not terminate - # after detaching. With this helper task, it is possible to: - # 1. Start debugging in attach mode - # 2. Add breakpoints, step, continue, etc. - # 3. Press the Continue button - # 4. Press the Stop button which detaches gdbserver from the debugged process - # 5. Start debugging again in attach mode - # Without this kill task, step 5 would fail because gdbserver is still running + # The extended-remote server used by attach mode keeps running + # after the debug session, launch.json refers to this task as + # postDebugTask. if server_mode == DebuggerServerModes.ATTACH: - new_task_kill_label = "kill_gdbserver_"+ cross_debug_config.id_pretty_mode(server_mode) - new_task_kill = { - "label": new_task_kill_label, + tasks_dict['tasks'].append({ + "label": self._stop_task_label(cross_debug_config, server_mode), "type": "shell", "command": cross_debug_config.debugger_cross.target_device.ssh_sshexec, - "args": cross_debug_config.target_ssh_gdbserver_kill_args(), + "args": cross_debug_config.target_ssh_gdbserver_stop_args(server_mode), "presentation": { "close": True }, "problemMatcher": [] - } - tasks_dict['tasks'].append(new_task_kill) + }) tasks_file = 'tasks.json' IdeBase.update_json_file( @@ -804,8 +815,9 @@ class IdeVSCode(IdeBase): if cross_debug_config.modified_recipe is not modified_recipe: continue for server_mode in cross_debug_config.server_modes(): - if server_mode == DebuggerServerModes.MULTI: - # MULTI mode: SSH command blocks until port is ready, treat as + if server_mode in (DebuggerServerModes.MULTI, + DebuggerServerModes.ATTACH): + # SSH command blocks until port is ready, treat as # a regular non-background task (same as vscode_tasks_cpp). new_task = { "label": cross_debug_config.id_pretty_mode(server_mode), @@ -815,7 +827,7 @@ class IdeVSCode(IdeBase): "problemMatcher": [] } else: - # ONCE / ATTACH: server runs for the whole session, needs + # ONCE: server runs for the whole session, needs # isBackground so VSCode does not wait for the task to exit. new_task = { "label": cross_debug_config.id_pretty_mode(server_mode), diff --git a/scripts/lib/devtool/ide_plugins/ide_none.py b/scripts/lib/devtool/ide_plugins/ide_none.py index a8ddc3f39f..959140cedb 100644 --- a/scripts/lib/devtool/ide_plugins/ide_none.py +++ b/scripts/lib/devtool/ide_plugins/ide_none.py @@ -21,16 +21,19 @@ class GdbCrossConfigNone(GdbCrossConfig): default_mode) def _target_gdbserver_stop_cmd(self, server_mode): - """Kill a gdbserver process""" - # This is the usual behavior: gdbserver is stopped on demand - if server_mode == DebuggerServerModes.MULTI: - gdbserver_cmd_stop = "test -f %s && kill \\$(cat %s);" % ( - self._gdbserver_pid_file(server_mode), self._gdbserver_pid_file(server_mode)) - gdbserver_cmd_stop += " rm -rf %s" % self._gdbserver_tmp_dir(server_mode) - # This is unexpected since gdbserver should terminate after each debug session - # Just kill all gdbserver instances to keep it simple - else: - gdbserver_cmd_stop = "killall gdbserver" + """Kill a gdbserver process + + Stopping is based on the PID file written by the start command. Other + debug sessions run their own gdbserver on the target, so anything + matching by process name would hit them as well. + """ + pid_file = self._gdbserver_pid_file(server_mode) + gdbserver_cmd_stop = "if test -f %s; then _gdbserver_pid=\\$(cat %s); " % ( + pid_file, pid_file) + gdbserver_cmd_stop += "kill \\$_gdbserver_pid 2>/dev/null; " + gdbserver_cmd_stop += self._target_wait_for_process_exit_cmd( + "gdbserver_pid") + gdbserver_cmd_stop += " fi; rm -rf %s" % self._gdbserver_tmp_dir(server_mode) return "\"/bin/sh -c '" + gdbserver_cmd_stop + "'\"" def _gen_gdbserver_start_script(self, server_mode=None): @@ -165,14 +168,19 @@ class LldbServerConfigNone(LldbServerConfig): return os.path.join(self.script_dir, 'lldb_' + self.id_pretty) def _target_lldb_server_stop_cmd(self, server_mode): - """SSH command to stop lldb-server on the target.""" - if server_mode == DebuggerServerModes.MULTI: - pid_file = self._lldb_server_pid_file(server_mode) - tmp_dir = self._lldb_server_tmp_dir(server_mode) - cmd = ("test -f %(pf)s && kill \\$(cat %(pf)s) 2>/dev/null; rm -rf %(td)s" - % {'pf': pid_file, 'td': tmp_dir}) - else: - cmd = "killall lldb-server 2>/dev/null || true" + """SSH command to stop lldb-server on the target. + + Stopping is based on the PID file written by the start command. Other + debug sessions run their own lldb-server on the target, so anything + matching by process name would hit them as well. + """ + pid_file = self._lldb_server_pid_file(server_mode) + tmp_dir = self._lldb_server_tmp_dir(server_mode) + cmd = "if test -f %s; then _lldb_server_pid=\\$(cat %s); " % ( + pid_file, pid_file) + cmd += "kill \\$_lldb_server_pid 2>/dev/null; " + cmd += self._target_wait_for_process_exit_cmd("lldb_server_pid") + cmd += " fi; rm -rf %s" % tmp_dir return "\"/bin/sh -c '" + cmd + "'\"" def _gen_lldb_server_start_script(self, server_mode=None): From patchwork Wed Aug 19 22:00:17 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95844 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 22460C5DF90 for ; Wed, 19 Aug 2026 22:01:54 +0000 (UTC) Received: from mta-64-225.siemens.flowmailer.net (mta-64-225.siemens.flowmailer.net [185.136.64.225]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.1132.1787176907030115728 for ; Wed, 19 Aug 2026 15:01:49 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=CBsQKN9X; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-2026081922014496e7fcb54b000207d8-owcwhf@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 2026081922014496e7fcb54b000207d8 for ; Thu, 20 Aug 2026 00:01:45 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=Qai1DHqi9kGoKWfXhURQZyjKw424V1USx7ruMcyVSAs=; b=CBsQKN9Xs2y/aDCzALphUvxJu61ef5/eF9bpmL8ufZpTeYomvbkkDLy15zeNb9N5hEs6Es w91Nr3vfVI4vrRUaltj47WzUZytm4hsN6elR2XTo6kM4yIEvwfJDnWLH205HK3zJxFj6Vlap qjq7qqaDrmt9XvMGwiU3qVTEC9Um2eobZnlcGgjx0g0kveXJCXz2w8ZZVC8Wbr3GaBkQT61A A2sguZSOiXBTtw3aiv3qYvayJCgm5XSH6/P6481OwnlU35NiX228lpgTkmE0tNqAlR5OeTX2 C9AR+n5b275Ji1Ff2d35dojZGAlmRR1snWreewQ1yOaA/1zp7A86LQqw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 10/14] oe-selftest: devtool ide-sdk: wait for gdbserver readiness Date: Thu, 20 Aug 2026 00:00:17 +0200 Message-ID: <20260819220138.4095398-11-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:54 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243798 From: Adrian Freihofer Replace the fixed delay and process-list probe for GDB background tasks with a wait for the readiness marker generated in tasks.json. This verifies the same background-task contract VS Code uses before starting the debugger session. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 93 +++++++++++++++++++++---- 1 file changed, 78 insertions(+), 15 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 35abc6a48f..7c2ca6afb2 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -8,7 +8,9 @@ import errno import os import re import shutil +import subprocess import tempfile +import threading import time import glob import fnmatch @@ -18,7 +20,7 @@ import logging import shlex from oeqa.selftest.case import OESelftestTestCase -from oeqa.utils.commands import runCmd, Command, bitbake, get_bb_var, create_temp_layer +from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer from oeqa.utils.commands import get_bb_vars, runqemu, runqemu_check_taps, get_test_layer from oeqa.core.decorator import OETestTag from oeqa.core.decorator.data import skipIfNotFeature @@ -2805,15 +2807,76 @@ class DevtoolUpgradeTests(DevtoolBase): class RunCmdBackground: - """Context manager to manage a background subprocess""" + """Context manager running a command in the background + + This mirrors what VS Code itself does with a task's "isBackground" + + "problemMatcher": ["background"]["endsPattern"] (see the generated + tasks.json and how _verify_launch_config() reads + prelaunch_task["problemMatcher"][0]["background"]["endsPattern"]): VS + Code also watches the task's output incrementally and considers the + background task "ready" as soon as a line matches endsPattern, rather + than waiting for the task to exit or polling on an interval. + """ def __init__(self, command, output_log=None, **options): - self.cmd = Command(command, bg=True, output_log=output_log, **options) + self.command = command + self.output_log = output_log + self.options = options + self.process = None + self._reader_thread = None + self._cond = threading.Condition() + self._chunks = [] def __enter__(self): - self.cmd.run() + popen_options = dict(self.options) + popen_options.setdefault("stdout", subprocess.PIPE) + popen_options.setdefault("stderr", subprocess.STDOUT) + popen_options.setdefault("shell", isinstance(self.command, str)) + self.process = subprocess.Popen(self.command, **popen_options) + self._reader_thread = threading.Thread(target=self._read_output, daemon=True) + self._reader_thread.start() + return self + + def _read_output(self): + for line in self.process.stdout: + text = line.decode("utf-8", errors="replace") + if self.output_log: + self.output_log.info(text.rstrip()) + with self._cond: + self._chunks.append(text) + self._cond.notify_all() + # Wake up a waiter still blocked once stdout closes, in case the + # process exited without ever producing the awaited pattern. + with self._cond: + self._cond.notify_all() + + def output(self): + with self._cond: + return "".join(self._chunks) + + def wait_for_output(self, pattern, timeout): + """Block until pattern appears in the output, the process exits, or timeout elapses.""" + pattern = re.compile(pattern, re.MULTILINE) + deadline = time.monotonic() + timeout + with self._cond: + while True: + if pattern.search("".join(self._chunks)): + return True + if self.process.poll() is not None: + return False + remaining = deadline - time.monotonic() + if remaining <= 0: + return False + self._cond.wait(remaining) def __exit__(self, exc_type, exc_val, exc_tb): - self.cmd.stop() + if self.process.poll() is None: + self.process.terminate() + try: + self.process.wait(timeout=5) + except subprocess.TimeoutExpired: + self.process.kill() + self.process.wait() + self._reader_thread.join(timeout=5) class DevtoolIdeSdkTests(DevtoolBase): @@ -3595,16 +3658,16 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): if len(ssh_gdbserver_cmd) > 0 and ssh_gdbserver_cmd[-1].startswith('"') and ssh_gdbserver_cmd[-1].endswith('"'): ssh_gdbserver_cmd[-1] = ssh_gdbserver_cmd[-1][1:-1].replace('\\$', '$') # Remove surrounding quotes self.logger.debug(f"Starting gdbserver with command: {' '.join(ssh_gdbserver_cmd)}") - with RunCmdBackground(ssh_gdbserver_cmd, output_log=self._cmd_logger): - # Give gdbserver a moment to start - time.sleep(1) - - # Verify gdbserver is running on target and listening on expected port - result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'ps'), output_log=self._cmd_logger) - self.assertEqual(result.status, 0, "Failed to check processes on target") - self.assertIn("gdbserver", result.output, "gdbserver should be running on target") - _, server_port = server_addr.split(':') - self.assertIn(server_port, result.output, f"gdbserver should be listening on port {server_port}") + _, server_port = server_addr.split(':') + with RunCmdBackground(ssh_gdbserver_cmd, output_log=self._cmd_logger) as gdbserver: + ready_pattern = prelaunch_task["problemMatcher"][0]["background"]["endsPattern"] + # Must exceed the target side budget (TARGET_START_RETRIES * 0.1s), + # otherwise this gives up while the target is still waiting and its + # diagnostics never make it into the failure. + self.assertTrue( + gdbserver.wait_for_output(ready_pattern, timeout=60), + "gdbserver did not report readiness on port %s:\n%s" % + (server_port, gdbserver.output())) if debug_func and debug_check_func: # Do a gdb remote session using the once configuration From patchwork Wed Aug 19 22:00:18 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95838 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 9FF38C5B572 for ; Wed, 19 Aug 2026 22:01:52 +0000 (UTC) Received: from mta-64-228.siemens.flowmailer.net (mta-64-228.siemens.flowmailer.net [185.136.64.228]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.1130.1787176907559735133 for ; Wed, 19 Aug 2026 15:01:49 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=eMmcuHud; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.228, mailfrom: fm-1329275-2026081922014559c5f0207500020721-xerhnt@rts-flowmailer.siemens.com) Received: by mta-64-228.siemens.flowmailer.net with ESMTPSA id 2026081922014559c5f0207500020721 for ; Thu, 20 Aug 2026 00:01:45 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=yzuIkvYb2zYO1NmLwX4RfLhs4EAR4vWj6e2G4dUhdqY=; b=eMmcuHudqQjjoCMuLjh1ZSblr6pANnuRz7Zc93gZiHwDWBsfDVJB0vlNrHa17sFED5Vd0U mxCG+N2l+pAVFVVMdXKcJQhdHl98uz/ZhdkrjXDD49J9hidJgnRbip+6CURmkYUTGO3Vhexf G3XAkwxXi4Eg4N3ghP9JDVZibisBhv8CdNW2H+FzkOWZCjgW0kDvg7XGltx/QE0jqRp+g/SG ZBEjtvlgEKEDUb958a0YZDCXsF/8+j2HXFRFgyrhWpWk+MfHvTtpia8Z0cqRwrAJkn6YlpUk 00Lz8q8MljniU7iYDCrd2hX9deGXMMnCYBXFsO8NmLqdyfEwF1i7T7wA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 11/14] oe-selftest: devtool ide-sdk: verify debugger shutdown Date: Thu, 20 Aug 2026 00:00:18 +0200 Message-ID: <20260819220138.4095398-12-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:52 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243797 From: Adrian Freihofer Use the QEMU target command interface to inspect the generated ide=none server PID files and process command lines. Verify that the stop helpers remove both the tracked process and its PID file, without relying on broad ps output. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 61 +++++++++++-------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 7c2ca6afb2..0f9ee471b3 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -3325,7 +3325,6 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): numbers after recompiling, to prove the breakpoints resolve via the freshly rebuilt debug info. """ - sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' gdbserver_script = os.path.join(self._workspace_scripts_dir( recipe_name), 'gdbserver_1234_usr-bin-' + example_exe + '_multi') gdb_script = os.path.join(self._workspace_scripts_dir( @@ -3335,19 +3334,18 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): r = runCmd(gdbserver_script, output_log=self._cmd_logger) self.assertEqual(r.status, 0) - # Check there is a gdbserver running - r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'ps'), output_log=self._cmd_logger) - self.assertEqual(r.status, 0) - self.assertIn("gdbserver ", r.output) + pid_file = '/tmp/gdbserver_1234_usr-bin-%s_multi/gdbserver.pid' % example_exe + status, output = qemu.run('cat %s' % pid_file) + self.assertEqual(status, 0) + gdbserver_pid = output.strip() + self.assertRegex(gdbserver_pid, r'^\d+$') - # Check the pid file is correct - test_cmd = "'cat /proc/$(cat /tmp/gdbserver_1234_usr-bin-" + \ - example_exe + "_multi/gdbserver.pid)/cmdline'" - r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, test_cmd), output_log=self._cmd_logger) - self.assertEqual(r.status, 0) - self.assertIn("gdbserver", r.output) - self.assertIn("--multi", r.output) - self.assertIn("1234", r.output) + # Check the pid file identifies the expected gdbserver process + status, output = qemu.run('cat /proc/%s/cmdline' % gdbserver_pid) + self.assertEqual(status, 0) + self.assertIn("gdbserver", output) + self.assertIn("--multi", output) + self.assertIn("1234", output) # Test remote debugging works gdb_batch_cmd = " --batch " + self._gdb_debug_cpp_example( @@ -3364,10 +3362,10 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): r = runCmd(gdbserver_script + ' stop', output_log=self._cmd_logger) self.assertEqual(r.status, 0) - # Check there is no gdbserver running - r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'ps'), output_log=self._cmd_logger) - self.assertEqual(r.status, 0) - self.assertNotIn("gdbserver ", r.output) + # The stop script waits for its recorded PID before it succeeds. + status, _ = qemu.run('test ! -d /proc/%s && test ! -e %s' % ( + gdbserver_pid, pid_file)) + self.assertEqual(status, 0) @OETestTag("runqemu") def test_devtool_ide_sdk_none_qemu(self): @@ -4536,7 +4534,6 @@ class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests): lldbinit source map / debug-file-search-paths setup for the library's own debug info specifically. """ - sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' scripts_dir = self._workspace_scripts_dir(recipe_name) binary_pretty = 'usr-bin-' + example_exe lldb_server_script = os.path.join( @@ -4550,19 +4547,16 @@ class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests): r = runCmd(lldb_server_script, output_log=self._cmd_logger) self.assertEqual(r.status, 0) - # Verify lldb-server is running on the target - r = runCmd('ssh %s root@%s ps' % (sshargs, qemu.ip), - output_log=self._cmd_logger) - self.assertEqual(r.status, 0) - self.assertIn('lldb-server', r.output) + pid_file = '/tmp/lldb_server_1234_%s_multi/lldb_server.pid' % binary_pretty + status, output = qemu.run('cat %s' % pid_file) + self.assertEqual(status, 0) + lldb_server_pid = output.strip() + self.assertRegex(lldb_server_pid, r'^\d+$') # Verify the pid file points at the running lldb-server process - pid_file = '/tmp/lldb_server_1234_%s_multi/lldb_server.pid' % binary_pretty - test_cmd = "'cat /proc/$(cat %s)/cmdline'" % pid_file - r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, test_cmd), - output_log=self._cmd_logger) - self.assertEqual(r.status, 0) - self.assertIn('lldb-server', r.output) + status, output = qemu.run('cat /proc/%s/cmdline' % lldb_server_pid) + self.assertEqual(status, 0) + self.assertIn('lldb-server', output) # Run an lldb batch session covering the executable, library and # header breakpoints, then continue to completion @@ -4577,11 +4571,10 @@ class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests): r = runCmd(lldb_server_script + ' stop', output_log=self._cmd_logger) self.assertEqual(r.status, 0) - # Verify lldb-server is no longer running - r = runCmd('ssh %s root@%s ps' % (sshargs, qemu.ip), - output_log=self._cmd_logger) - self.assertEqual(r.status, 0) - self.assertNotIn('lldb-server', r.output) + # The stop script waits for its recorded PID before it succeeds. + status, _ = qemu.run('test ! -d /proc/%s && test ! -e %s' % ( + lldb_server_pid, pid_file)) + self.assertEqual(status, 0) @OETestTag("runqemu") def test_devtool_ide_sdk_none_cmake(self): From patchwork Wed Aug 19 22:00:19 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95837 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 AED08C5DF86 for ; Wed, 19 Aug 2026 22:01:52 +0000 (UTC) Received: from mta-64-228.siemens.flowmailer.net (mta-64-228.siemens.flowmailer.net [185.136.64.228]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.1130.1787176907559735133 for ; Wed, 19 Aug 2026 15:01:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=EQxdLxFw; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.228, mailfrom: fm-1329275-20260819220145af624df637000207a3-7eiooa@rts-flowmailer.siemens.com) Received: by mta-64-228.siemens.flowmailer.net with ESMTPSA id 20260819220145af624df637000207a3 for ; Thu, 20 Aug 2026 00:01:45 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=s7NA2PUceXl6nZkiPCFLJ+u+avZZRqT/F0XDf8cpH6g=; b=EQxdLxFw/J+xSoZfrn93wEsOx79+v/D4mPaEWsTBAR5+3Q9+yxwakpzzXdgoeHyhe0mz/1 s0Lxc0PGk2osq1e5cUW3nprdm3LYRKOFj7ndyw8NRoLDyCeKrTwV9/V/SdC1ZIUOX9Ckwf0H qg1su3aVfoYvQnM6pMCmIvpivUUo3ZeAbjoXoy7S2tKIcganb44n2JkbU8ZLpQ+1/Dnn0WVE +wEYtUXLqL+xZWvYUX99/KIcFdiYTZ5Oa5+d35XS+qz9GlFYL7XGEXjiRHbSTvs/2+eQZ7TQ 6R0D5YkMuBm+o12tV+iWk+ZpgV5KhkdvM55u9swdT+uzLJt2IoiWGGmg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 12/14] oe-selftest: devtool ide-sdk: use qemu.run for target checks Date: Thu, 20 Aug 2026 00:00:19 +0200 Message-ID: <20260819220138.4095398-13-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:52 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243785 From: Adrian Freihofer Use QEMU target commands instead of spawning a separate ssh process for simple target-side executable checks in the ide=code selftest helper. This keeps the test consistent with the rest of the qemu-based cleanup and avoids duplicating SSH option handling in the test itself. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 0f9ee471b3..53ddf28e57 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -3640,13 +3640,10 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): self.assertEqual(task_command, "ssh", f"Task '{prelaunch_task_name}' should use ssh command") self.assertTrue(len(task_args) >= 2, f"Task '{prelaunch_task_name}' should have at least 2 args (ssh options and remote command)") - sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' - result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'test -x /usr/bin/gdbserver'), - output_log=self._cmd_logger) - self.assertEqual(result.status, 0, "gdbserver should be installed on target") - result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'test -x ' + os.path.join('/usr/bin', example_exe)), - output_log=self._cmd_logger) - self.assertEqual(result.status, 0, "Example binary should be installed on target") + status, _ = qemu.run('test -x /usr/bin/gdbserver') + self.assertEqual(status, 0, "gdbserver should be installed on target") + status, _ = qemu.run('test -x ' + os.path.join('/usr/bin', example_exe)) + self.assertEqual(status, 0, "Example binary should be installed on target") # Start gdbserver on target using the task command (keep the ssh connection open while debugging) ssh_gdbserver_cmd = [task_command] + task_args From patchwork Wed Aug 19 22:00:20 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95841 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 27504C5DF88 for ; Wed, 19 Aug 2026 22:01:53 +0000 (UTC) Received: from mta-65-225.siemens.flowmailer.net (mta-65-225.siemens.flowmailer.net [185.136.65.225]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.1140.1787176907892610623 for ; Wed, 19 Aug 2026 15:01:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=Uqsel3px; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.225, mailfrom: fm-1329275-202608192201457169d16be10002075f-fvhlr3@rts-flowmailer.siemens.com) Received: by mta-65-225.siemens.flowmailer.net with ESMTPSA id 202608192201457169d16be10002075f for ; Thu, 20 Aug 2026 00:01:45 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=BDNeyYrv9EtFt+na4JeHqhGoDB/P5niHbykJa9n1mYE=; b=Uqsel3pxcjD0vKMqkyxu3Nk/NdMzmWmHUyVjweSIvce4Zp3UZboNfRctls64XeCCHW9uE0 Mn31Sr48LppPcERnqmPqXY8vEfAbq5mWY5BwmxY+pgPf/ueTj6Qvn8F3cJCMgibWYVyq4HWt +L9w6bVSv7S3NnVvEG+bw84TBl7reSlShqGL78Mao0B4W97Z02Qa6NSj+xhh2+8XAI9IYtSg upqKqFAPp/ir3ADScxnYMei8UM9NP49Xf+1xerhA3/TfI/rxS2bjJELujZjRNJAC13kV7dUw nZt7yTnxCEBF52SfukElfFvMmRjwULFQR2knt5O+aTzJXh6xViLGb4wA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 13/14] oe-selftest: devtool ide-sdk: verify attach debug workflow Date: Thu, 20 Aug 2026 00:00:20 +0200 Message-ID: <20260819220138.4095398-14-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:53 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243795 From: Adrian Freihofer Add attach-mode debug verification in ide=code selftests and document the intended execution flow: - Verify attach postDebugTask labels against tasks.json. - Run attach checks before once checks to avoid Build-ID mismatch from once/deploy side effects on the still-running service process. - In attach mode, set a temporary breakpoint on sleep(), continue once to hit it, then detach immediately. This verifies debugger control flow against the already-running loop without fragile inferior expression evaluation. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 152 +++++++++++++++++++----- 1 file changed, 119 insertions(+), 33 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 53ddf28e57..7c989f9dc2 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -3446,10 +3446,19 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): def _verify_launch_json(self, tempdir): """Verify the launch.json file created is valid and contains proper debug configurations""" launch_json_path = os.path.join(tempdir, '.vscode', 'launch.json') + tasks_json_path = os.path.join(tempdir, '.vscode', 'tasks.json') self.assertTrue(os.path.exists(launch_json_path), "launch.json file should exist") + self.assertTrue(os.path.exists(tasks_json_path), "tasks.json file should exist") with open(launch_json_path) as launch_j: launch_d = json.load(launch_j) + with open(tasks_json_path) as tasks_j: + tasks_d = json.load(tasks_j) + task_labels = { + task.get("label") + for task in tasks_d.get("tasks", []) + if task.get("label") + } self.assertIn("configurations", launch_d) configurations = launch_d["configurations"] @@ -3527,6 +3536,8 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): self.assertIn("_attach", task, f"attach configuration '{config_name}' should have attach preLaunchTask") # Verify postDebugTask exists for attach configurations (kill gdbserver) self.assertIn("postDebugTask", config, f"attach configuration '{config_name}' should have postDebugTask") + self.assertIn(config["postDebugTask"], task_labels, + f"attach configuration '{config_name}' postDebugTask should exist in tasks.json") # Categorize configurations config_name = config["name"] @@ -3542,34 +3553,11 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): "Each debug configuration should use a distinct server address") def _verify_launch_json_debugging(self, tempdir, qemu, example_exe): - """Verify remote debugging and deployment works using launch.json configurations + """Verify launch.json debugging configs for one target executable. - This method tests the VSCode debug configurations by: - 1. Starting gdbserver on target using tasks.json commands - 2. Running gdb debugging session with batch commands - $BUILDDIR/tmp/work/x86_64-linux/gdb-cross-x86_64/16.3/recipe-sysroot-native/usr/bin/x86_64-poky-linux/x86_64-poky-linux-gdb --batch \ - -ex 'set sysroot $BUILDDIR/tmp/work/x86-64-v3-poky-linux/cmake-example/1.0/image' \ - -ex 'set substitute-path /usr/include $BUILDDIR/tmp/work/x86-64-v3-poky-linux/cmake-example/1.0/recipe-sysroot/usr/include' \ - -ex 'set substitute-path /usr/src/debug/cmake-example/1.0 $BUILDDIR/workspace/sources/cmake-example' \ - -ex 'set substitute-path /usr/src/debug $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/usr/src/debug' \ - -ex 'set solib-search-path $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/lib/.debug:\ - $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/usr/lib/.debug:\ - $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/usr/lib/debug:\ - $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/lib:\ - $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/usr/lib:\ - $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs/lib:\ - $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs/usr/lib' \ - -ex 'file $BUILDDIR/tmp/work/x86-64-v3-poky-linux/cmake-example/1.0/image/usr/bin/cmake-example' \ - -ex 'target remote 192.168.7.2:1234' \ - -ex 'break main' \ - -ex 'continue' \ - -ex 'break CppExample::print_json()' \ - -ex 'continue' \ - -ex 'print CppExample::test_string.compare("cpp-example-lib Magic: 123456789")' \ - -ex 'print CppExample::test_string.compare("cpp-example-lib Magic: 123456789aaa")' \ - -ex 'list cpp-example-lib.hpp:15,15' \ - -ex 'continue' - 3. Verifying debug output and stopping gdbserver + Runs attach-mode first against the already-running service process, + then validates the once configuration. This ordering avoids Build-ID + mismatch from once/deploy side effects before attach is tested. """ with open(os.path.join(tempdir, '.vscode', 'launch.json')) as launch_j: launch_d = json.load(launch_j) @@ -3585,18 +3573,116 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): # as the recipe name (e.g. meson-example installs a binary named # "mesonex"). once_config_count = 0 + attach_config_count = 0 + + # Run attach first. The attach test targets the already-running + # service process in the image; running a once configuration first can + # deploy/update binaries and trigger Build-ID mismatch warnings against + # that still-running process. + for config in configurations: + if f"usr-bin-{example_exe}_attach" in config["name"]: + attach_config_count += 1 + self._verify_launch_config_attach(tempdir, config, qemu, example_exe) + for config in configurations: if f"usr-bin-{example_exe}_once" in config["name"]: once_config_count += 1 self._verify_launch_config(tempdir, config, tasks, qemu, example_exe, self._gdb_debug_cpp_example, self._gdb_debug_cpp_example_check) - # It works but is not 100% reliable in VSCode - # This one: https://github.com/microsoft/vscode-cpptools/issues/4243 ? - # elif f"usr-bin-{example_exe}_attach" in config["name"] - # self._verify_launch_config(tempdir, config, tasks, qemu, example_exe) - else: - continue self.assertEqual(once_config_count, 1, f"Should have one once configuration, found: {once_config_count}") + self.assertEqual(attach_config_count, 1, f"Should have one attach configuration, found: {attach_config_count}") + + def _verify_launch_config_attach(self, tempdir, launch_config, qemu, example_exe): + """Verify attach-mode debugging by exercising debugger control flow. + + The attached service runs an endless loop, and each loop iteration + executes sleep(). We break on sleep() to prove that attach-mode gdb is + controlling the already-running process at a point that is expected to + be reached repeatedly during normal service execution, then detach. + """ + self.assertEqual(launch_config.get("request"), "attach") + + tasks_path = os.path.join(tempdir, '.vscode', 'tasks.json') + self.assertExists(tasks_path, 'tasks.json not found at %s' % tasks_path) + with open(tasks_path) as tasks_j: + tasks_d = json.load(tasks_j) + task_by_label = { + task.get('label'): task + for task in tasks_d.get('tasks', []) + if task.get('label') + } + + debugger_path = launch_config["miDebuggerPath"] + server_addr = launch_config["miDebuggerServerAddress"] + prelaunch_task_name = launch_config["preLaunchTask"] + post_debug_task_name = launch_config.get("postDebugTask") + program = launch_config["program"] + additional_so_lib_search_path = launch_config["additionalSOLibSearchPath"] + source_file_map = launch_config["sourceFileMap"] + setup_commands = launch_config["setupCommands"] + + self.assertTrue(post_debug_task_name, + "attach configuration should define postDebugTask") + self.assertIn(prelaunch_task_name, task_by_label) + self.assertIn(post_debug_task_name, task_by_label) + + status, _ = qemu.run('test -x /usr/bin/gdbserver') + self.assertEqual(status, 0, "gdbserver should be installed on target") + status, _ = qemu.run('test -x ' + os.path.join('/usr/bin', example_exe)) + self.assertEqual(status, 0, "Example binary should be installed on target") + + # The attach preLaunchTask has no dependsOn (it attaches to an already + # running service instead of building/deploying), so it can be run + # directly without a dependency chain runner. + prelaunch_task = task_by_label[prelaunch_task_name] + prelaunch_cmd = [prelaunch_task["command"]] + [str(arg) + for arg in prelaunch_task.get("args", [])] + # The tasks.json command is shell-quoted and escapes '$' for an + # intermediate shell, so undo that before passing argv to subprocess. + if prelaunch_cmd[-1].startswith('"') and prelaunch_cmd[-1].endswith('"'): + prelaunch_cmd[-1] = prelaunch_cmd[-1][1:-1].replace('\\$', '$') + runCmd(prelaunch_cmd, output_log=self._cmd_logger) + + # Attach to the service process currently running on the target. + status, output = qemu.run("pgrep '^%s$'" % example_exe) + self.assertEqual(status, 0, msg="%s service not running: %s" % + (example_exe, output)) + service_pid = output.strip() + self.assertRegex(service_pid, r'^\d+$') + + gdb_batch_cmd = debugger_path + " --batch" + for setup_command in setup_commands: + setup_cmd = setup_command["text"].strip() + if setup_cmd.startswith("-"): + continue + gdb_batch_cmd += ' -ex ' + shlex.quote(setup_cmd) + for k, v in source_file_map.items(): + gdb_batch_cmd += " -ex 'set substitute-path %s %s'" % (k, v.replace("${workspaceFolder}", tempdir)) + gdb_batch_cmd += " -ex 'set solib-search-path %s'" % additional_so_lib_search_path + gdb_batch_cmd += " -ex 'file %s'" % program + gdb_batch_cmd += " -ex 'target extended-remote %s'" % server_addr + gdb_batch_cmd += " -ex 'attach %s'" % service_pid + gdb_batch_cmd += " -ex 'set breakpoint pending on'" + gdb_batch_cmd += " -ex 'tbreak sleep'" + gdb_batch_cmd += " -ex 'continue'" + gdb_batch_cmd += " -ex 'detach'" + self.logger.debug(f"Starting attach gdb session with command: {gdb_batch_cmd}") + + r = runCmd(gdb_batch_cmd, output_log=self._cmd_logger) + self.assertEqual(r.status, 0) + self.assertIn("sleep", r.output) + + # Stop the persistent attach server and verify the service survives. + post_debug_task = task_by_label[post_debug_task_name] + post_debug_cmd = [post_debug_task["command"]] + [str(arg) + for arg in post_debug_task.get("args", [])] + if post_debug_cmd[-1].startswith('"') and post_debug_cmd[-1].endswith('"'): + post_debug_cmd[-1] = post_debug_cmd[-1][1:-1].replace('\\$', '$') + runCmd(post_debug_cmd, output_log=self._cmd_logger) + + status, output = qemu.run("pgrep '^%s$'" % example_exe) + self.assertEqual(status, 0, msg="%s service should still be running after attach stop: %s" % + (example_exe, output)) def _verify_launch_config(self, tempdir, launch_config, tasks, qemu, example_exe, debug_func=None, debug_check_func=None): self.assertIsNotNone(launch_config, "Should have at least one launch debug configuration") From patchwork Wed Aug 19 22:00:21 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95845 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 60548C5DF92 for ; Wed, 19 Aug 2026 22:01:54 +0000 (UTC) Received: from mta-65-228.siemens.flowmailer.net (mta-65-228.siemens.flowmailer.net [185.136.65.228]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.1131.1787176907685326352 for ; Wed, 19 Aug 2026 15:01:48 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=BZFpZPBd; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.228, mailfrom: fm-1329275-20260819220145a72556402d00020782-jtnbrp@rts-flowmailer.siemens.com) Received: by mta-65-228.siemens.flowmailer.net with ESMTPSA id 20260819220145a72556402d00020782 for ; Thu, 20 Aug 2026 00:01:45 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=p/ec2LtNGNrvKSCOsSFcbQRrt4bd//u1SHWVrgoM9ww=; b=BZFpZPBdWrKl5lgY/t87+zJYRjwBWb4av3vSbmlsVEOAPORa19L6WVvrnD4mZSPgPx0Z04 UbNRUvhPbcnuDaCXu1mKJ7CTh3adxgny5RFuaDRaIajo3vaJJwu3B13si5CneFtsY2dEtb35 EYSLyDIh83AnwQoob81eGGfH9Kuq+7BfrLlxZAcwYL7hRNu7oQ4tJeB1fYrb2YKaPPourBEy TiWo+UmbwMAe5K37IE9yD5N9UExVRko8sJlzhVVHSLknBYUp2ZXmm5+t5qPU93TzJ1XcKvcF gUALaiUBoeY5+5gGQm73Ub5dcMiV6NDJk+5iUo7p5HobBqjQyFgi/vYg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v3 14/14] devtool: ide-sdk: detect stale gcc python helpers in the recipe sysroot Date: Thu, 20 Aug 2026 00:00:21 +0200 Message-ID: <20260819220138.4095398-15-adrian.freihofer@siemens.com> In-Reply-To: <20260819220138.4095398-1-adrian.freihofer@siemens.com> References: <20260819220138.4095398-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 22:01:54 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243787 From: Adrian Freihofer RECIPE_SYSROOT can retain a gcc-/python directory left over from before a gcc version bump, since sysroot cleanup does not always remove it. gdb_pretty_print_scripts globbed usr/share/gcc-*/python and silently picked the first match, which could be the stale, no longer matching version, breaking libstdc++ pretty-printing in gdb (register_libstdcxx_printers) without an obvious cause. Raise a DevtoolError when the glob matches more than one directory instead of guessing, and tell the user to remove tmp/work and rebuild the recipe. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_sdk.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py index ab2bba5d39..4fdeeec24d 100755 --- a/scripts/lib/devtool/ide_sdk.py +++ b/scripts/lib/devtool/ide_sdk.py @@ -817,6 +817,11 @@ class RecipeModified: if self.toolchain == "gcc": gcc_python_helpers_pattern = os.path.join(self.recipe_sysroot, "usr", "share", "gcc-*", "python") gcc_python_helpers_dirs = glob.glob(gcc_python_helpers_pattern) + if len(gcc_python_helpers_dirs) > 1: + raise DevtoolError( + "Found multiple gcc python helpers directories matching %s: %s. " + "The recipe sysroot likely has stale files from a previous gcc version, " + "remove tmp/work and rebuild the recipe." % (gcc_python_helpers_pattern, gcc_python_helpers_dirs)) if gcc_python_helpers_dirs: gcc_python_helpers = gcc_python_helpers_dirs[0] else: