From patchwork Wed Dec 31 11:46:31 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77828 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 800F2EE6441 for ; Wed, 31 Dec 2025 11:47:51 +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.msgproc02-g2.83932.1767181666334252814 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=rvrTuLzj; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-20251231114743a0f2243edd00020759-ypseai@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 20251231114743a0f2243edd00020759 for ; Wed, 31 Dec 2025 12:47:43 +0100 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=A0HE8m6R38DCujzH58JuvE2k69Oj86MboGi7xB5tQt0=; b=rvrTuLzjgrFusre2NvSTDH6iV29vhbAQmymeovRWllCA0BpvtX44QjUgBgRacxW+vOeGJ4 1A/Unng6UHPpgDKMZC8VzSmNU3cFJEMnvEf/X3dX0U0O+pNSr5JL5qUPG9jHZl8Tbu8AZRYj x6bmOc4olq71UUvLstZvOTZDkaX/iAUGnX3G7+1ZnSWHEuKOEJmAwCeRvfqpnSMljbJMI0U1 PkfZlGAP11aYWWE65LEiX+bzeRVmY9OPvd8+P9T177A+fyarNLCOgLhzYFsfRq5pUohLKIat oBUQe6O09SORfYu2HMoHcuGj0/AmUK7sTq6aLV+I9NktOKsZ5Hi4JOZw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 01/14] devtool: ide-sdk find bitbake-setup init-build-env Date: Wed, 31 Dec 2025 12:46:31 +0100 Message-ID: <20251231114718.4031606-2-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228742 From: Adrian Freihofer With poky the oe-init-build-env script from the top level directory of the layer with the higher priority is used to setup the build environment. This does no longer work with bitbake-setup. The directory layout changed and the script is now called init-build-env. Skip the old implementation if $TOPDIR/init-build-env exists and use it instead. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_sdk.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py index 87a4c13ec5..9df88454c7 100755 --- a/scripts/lib/devtool/ide_sdk.py +++ b/scripts/lib/devtool/ide_sdk.py @@ -269,6 +269,7 @@ class RecipeNotModified: class RecipeModified: """Handling of recipes in the workspace created by devtool modify""" OE_INIT_BUILD_ENV = 'oe-init-build-env' + INIT_BUILD_ENV = 'init-build-env' VALID_BASH_ENV_NAME_CHARS = re.compile(r"^[a-zA-Z0-9_]*$") @@ -743,7 +744,13 @@ class RecipeModified: @property def oe_init_build_env(self): - """Find the oe-init-build-env used for this setup""" + """Find the init-build-env used for this setup""" + # bitbake-setup mode + bb_setup_init = os.path.join(self.topdir, RecipeModified.INIT_BUILD_ENV) + if os.path.exists(bb_setup_init): + return os.path.abspath(bb_setup_init) + + # poky mode oe_init_dir = self.oe_init_dir if oe_init_dir: return os.path.join(oe_init_dir, RecipeModified.OE_INIT_BUILD_ENV) From patchwork Wed Dec 31 11:46:32 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77829 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 6F3A8EE6440 for ; Wed, 31 Dec 2025 11:47:51 +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.83937.1767181666334939260 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=pKjBzN/Z; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.226, mailfrom: fm-1329275-202512311147433c7da6b3b20002075d-if3kav@rts-flowmailer.siemens.com) Received: by mta-64-226.siemens.flowmailer.net with ESMTPSA id 202512311147433c7da6b3b20002075d for ; Wed, 31 Dec 2025 12:47:43 +0100 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=rpNB84JLDbz97FMf5o14lPfRC9uUMpX4uuAYleJnLBQ=; b=pKjBzN/ZBTW9yaS+nl5dNIG9kRCqGU5r6FAJL5Ko1x+o100xdTWcDiUT/2ldtvKqwZW3+H MLPVzkFybP6r/BKpJAmS6pnBZjT1BDr5xyDguYWfxtC1R/QgGPpnCcLGyrqLRkzDizitNfXI 1zDRDVxV0GCHh6lIyq9s9eBrsvrFW5EXpr+Nh4qGsq64r8duZ2btkMplN/ADfIgCQ3kJmAs+ D7ZdxtFK2bi/YRDH5QTe1tXDnisf4FWMgJtFyfic8gtQ2ti+Y/szbl/cR2GHNu7ZQBQKbRk+ zjhhNbKLL1Q5kXsBwQZIzNqSj0ssgvfNA6ylbXR6pburQBxoIcpT6LRg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 02/14] oe-selftest: devtool: DevtoolIdeSdkTests debug logging Date: Wed, 31 Dec 2025 12:46:32 +0100 Message-ID: <20251231114718.4031606-3-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228744 From: Adrian Freihofer Add optional debug logging to all runCmd calls in DevtoolIdeSdkTests to improve debugging capabilities when tests fail. The logging is only enabled when the test logger is set to DEBUG level. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 90 ++++++++++++++----------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index cf5ac6e9d7..9bbba2ec89 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -13,6 +13,7 @@ import glob import fnmatch import unittest import json +import logging from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer @@ -2524,6 +2525,13 @@ class DevtoolUpgradeTests(DevtoolBase): class DevtoolIdeSdkTests(DevtoolBase): + + def setUp(self): + super().setUp() + self._cmd_logger = None + if self.logger.isEnabledFor(logging.DEBUG): + self._cmd_logger = self.logger + def _write_bb_config(self, recipe_names): """Helper to write the bitbake local.conf file""" conf_lines = [ @@ -2563,7 +2571,7 @@ class DevtoolIdeSdkTests(DevtoolBase): self.track_for_cleanup(tempdir) self.add_command_to_tearDown('bitbake -c clean %s' % recipe_name) - result = runCmd('devtool modify %s -x %s --debug-build' % (recipe_name, tempdir)) + result = runCmd('devtool modify %s -x %s --debug-build' % (recipe_name, tempdir), output_log=self._cmd_logger) self.assertExists(os.path.join(tempdir, build_file), 'Extracted source could not be found') self.assertExists(os.path.join(self.workspacedir, 'conf', @@ -2573,7 +2581,7 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertTrue(matches, 'bbappend not created %s' % result.output) # Test devtool status - result = runCmd('devtool status') + result = runCmd('devtool status', output_log=self._cmd_logger) self.assertIn(recipe_name, result.output) self.assertIn(tempdir, result.output) self._check_src_repo(tempdir) @@ -2629,7 +2637,7 @@ class DevtoolIdeSdkTests(DevtoolBase): self._workspace_scripts_dir(recipe_name), i_and_d_script) self.assertExists(install_deploy_cmd, '%s script not found' % install_deploy_cmd) - runCmd(install_deploy_cmd) + runCmd(install_deploy_cmd, output_log=self._cmd_logger) MAGIC_STRING_ORIG = "Magic: 123456789" MAGIC_STRING_NEW = "Magic: 987654321" @@ -2662,7 +2670,7 @@ class DevtoolIdeSdkTests(DevtoolBase): cpp_code = cpp_code.replace(MAGIC_STRING_ORIG, MAGIC_STRING_NEW) with open(cpp_example_lib_hpp, 'w') as file: file.write(cpp_code) - runCmd(install_deploy_cmd, cwd=tempdir) + runCmd(install_deploy_cmd, cwd=tempdir, output_log=self._cmd_logger) # Verify the modified example prints the modified magic string status, output = qemu.run(example_exe) @@ -2689,7 +2697,7 @@ class DevtoolIdeSdkTests(DevtoolBase): native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", gdb_recipe) r = runCmd("%s --version" % gdb_binary, - native_sysroot=native_sysroot, target_sys=target_sys) + native_sysroot=native_sysroot, target_sys=target_sys, output_log=self._cmd_logger) self.assertEqual(r.status, 0) self.assertIn("GNU gdb", r.output) @@ -2717,18 +2725,18 @@ class DevtoolIdeSdkTests(DevtoolBase): recipe_name), 'gdb_1234_usr-bin-' + example_exe) # Start a gdbserver - r = runCmd(gdbserver_script) + 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')) + 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) # Check the pid file is correct test_cmd = "cat /proc/$(cat /tmp/gdbserver_1234_usr-bin-" + \ example_exe + "/pid)/cmdline" - r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, test_cmd)) + 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) @@ -2739,7 +2747,7 @@ class DevtoolIdeSdkTests(DevtoolBase): gdb_batch_cmd += " -ex 'print CppExample::test_string.compare(\"cpp-example-lib %saaa\")'" % magic_string gdb_batch_cmd += " -ex 'list cpp-example-lib.hpp:13,13'" gdb_batch_cmd += " -ex 'continue'" - r = runCmd(gdb_script + gdb_batch_cmd) + r = runCmd(gdb_script + gdb_batch_cmd, output_log=self._cmd_logger) self.logger.debug("%s %s returned: %s", gdb_script, gdb_batch_cmd, r.output) self.assertEqual(r.status, 0) @@ -2751,11 +2759,11 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertIn("exited normally", r.output) # Stop the gdbserver - r = runCmd(gdbserver_script + ' stop') + 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')) + 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) @@ -2776,29 +2784,29 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertExists(cmake_exe) # Verify the cmake preset generated by devtool ide-sdk is available - result = runCmd('%s --list-presets' % cmake_exe, cwd=tempdir) + 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('%s --build --preset %s' % - (cmake_exe, preset_name), cwd=tempdir) + (cmake_exe, preset_name), 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 --build --preset %s --target test' % - (cmake_exe, preset_name), cwd=tempdir) + (cmake_exe, preset_name), cwd=tempdir, output_log=self._cmd_logger) self.assertIn("100% tests passed", result.output) # Verify re-building and testing works again result = runCmd('%s --build --preset %s --target clean' % - (cmake_exe, preset_name), cwd=tempdir) + (cmake_exe, preset_name), cwd=tempdir, output_log=self._cmd_logger) self.assertIn("Cleaning", result.output) result = runCmd('%s --build --preset %s' % - (cmake_exe, preset_name), cwd=tempdir) + (cmake_exe, preset_name), cwd=tempdir, output_log=self._cmd_logger) self.assertIn("Building", result.output) self.assertIn("Linking", result.output) result = runCmd('%s --build --preset %s --target test' % - (cmake_exe, preset_name), cwd=tempdir) + (cmake_exe, preset_name), cwd=tempdir, output_log=self._cmd_logger) self.assertIn("Running tests...", result.output) self.assertIn("100% tests passed", result.output) @@ -2823,7 +2831,7 @@ class DevtoolIdeSdkTests(DevtoolBase): recipe_name, build_file, testimage) bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@%s -c --ide=none' % ( recipe_name, testimage, qemu.ip) - runCmd(bitbake_sdk_cmd) + runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger) self._gdb_cross() self._verify_cmake_preset(tempdir) self._devtool_ide_sdk_qemu(tempdir, qemu, recipe_name, example_exe) @@ -2839,7 +2847,7 @@ class DevtoolIdeSdkTests(DevtoolBase): recipe_name, build_file, testimage) bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@%s -c --ide=none' % ( recipe_name, testimage, qemu.ip) - runCmd(bitbake_sdk_cmd) + runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger) self._gdb_cross() self._devtool_ide_sdk_qemu(tempdir, qemu, recipe_name, example_exe) # Verify the oe-scripts sym-link is valid @@ -2858,7 +2866,7 @@ class DevtoolIdeSdkTests(DevtoolBase): recipe_name, build_file, testimage) bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@192.168.17.17 -c --ide=code' % ( recipe_name, testimage) - runCmd(bitbake_sdk_cmd) + runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger) self._verify_cmake_preset(tempdir) self._verify_install_script_code(tempdir, recipe_name) self._gdb_cross() @@ -2875,7 +2883,7 @@ class DevtoolIdeSdkTests(DevtoolBase): recipe_name, build_file, testimage) bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@192.168.17.17 -c --ide=code' % ( recipe_name, testimage) - runCmd(bitbake_sdk_cmd) + runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger) with open(os.path.join(tempdir, '.vscode', 'settings.json')) as settings_j: settings_d = json.load(settings_j) @@ -2887,20 +2895,22 @@ class DevtoolIdeSdkTests(DevtoolBase): # Verify meson re-uses the o files compiled by bitbake result = runCmd('%s compile -C %s' % - (meson_exe, meson_build_folder), cwd=tempdir) + (meson_exe, meson_build_folder), cwd=tempdir, output_log=self._cmd_logger) self.assertIn("ninja: no work to do.", result.output) # Verify the unit tests work (in Qemu) - runCmd('%s test -C %s' % (meson_exe, meson_build_folder), cwd=tempdir) + runCmd('%s test -C %s' % (meson_exe, meson_build_folder), cwd=tempdir, + output_log=self._cmd_logger) # Verify re-building and testing works again result = runCmd('%s compile -C %s --clean' % - (meson_exe, meson_build_folder), cwd=tempdir) + (meson_exe, meson_build_folder), cwd=tempdir, output_log=self._cmd_logger) self.assertIn("Cleaning...", result.output) result = runCmd('%s compile -C %s' % - (meson_exe, meson_build_folder), cwd=tempdir) + (meson_exe, meson_build_folder), cwd=tempdir, output_log=self._cmd_logger) self.assertIn("Linking target", result.output) - runCmd('%s test -C %s' % (meson_exe, meson_build_folder), cwd=tempdir) + runCmd('%s test -C %s' % (meson_exe, meson_build_folder), cwd=tempdir, + output_log=self._cmd_logger) self._verify_install_script_code(tempdir, recipe_name) self._gdb_cross() @@ -2912,7 +2922,8 @@ class DevtoolIdeSdkTests(DevtoolBase): self._check_workspace() result_init = runCmd( - 'devtool ide-sdk -m shared oe-selftest-image cmake-example meson-example --ide=code') + 'devtool ide-sdk -m shared oe-selftest-image cmake-example meson-example --ide=code', + output_log=self._cmd_logger) bb_vars = get_bb_vars( ['REAL_MULTIMACH_TARGET_SYS', 'DEPLOY_DIR_IMAGE', 'COREBASE'], "meta-ide-support") environment_script = 'environment-setup-%s' % bb_vars['REAL_MULTIMACH_TARGET_SYS'] @@ -2924,21 +2935,21 @@ class DevtoolIdeSdkTests(DevtoolBase): # Verify the cross environment script is available self.assertExists(environment_script_path) - def runCmdEnv(cmd, cwd): + def runCmdEnv(cmd, cwd, output_log=self._cmd_logger): cmd = '/bin/sh -c ". %s > /dev/null && %s"' % ( environment_script_path, cmd) - return runCmd(cmd, cwd) + return runCmd(cmd, cwd, output_log=output_log) # Verify building the C++ example works with CMake tempdir_cmake = tempfile.mkdtemp(prefix='devtoolqa') self.track_for_cleanup(tempdir_cmake) - result_cmake = runCmdEnv("which cmake", cwd=tempdir_cmake) + result_cmake = runCmdEnv("which cmake", cwd=tempdir_cmake, output_log=self._cmd_logger) cmake_native = os.path.normpath(result_cmake.output.strip()) self.assertExists(cmake_native) - runCmdEnv('cmake %s' % cpp_example_src, cwd=tempdir_cmake) - runCmdEnv('cmake --build %s' % tempdir_cmake, cwd=tempdir_cmake) + runCmdEnv('cmake %s' % cpp_example_src, cwd=tempdir_cmake, output_log=self._cmd_logger) + runCmdEnv('cmake --build %s' % tempdir_cmake, cwd=tempdir_cmake, output_log=self._cmd_logger) # Verify the printed note really referres to a cmake executable cmake_native_code = "" @@ -2954,12 +2965,12 @@ class DevtoolIdeSdkTests(DevtoolBase): tempdir_meson = tempfile.mkdtemp(prefix='devtoolqa') self.track_for_cleanup(tempdir_meson) - result_cmake = runCmdEnv("which meson", cwd=tempdir_meson) + result_cmake = runCmdEnv("which meson", cwd=tempdir_meson, output_log=self._cmd_logger) meson_native = os.path.normpath(result_cmake.output.strip()) self.assertExists(meson_native) - runCmdEnv('meson setup %s' % tempdir_meson, cwd=cpp_example_src) - runCmdEnv('meson compile', cwd=tempdir_meson) + 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) def test_devtool_ide_sdk_plugins(self): """Test that devtool ide-sdk can use plugins from other layers.""" @@ -2982,7 +2993,7 @@ class DevtoolIdeSdkTests(DevtoolBase): return m.group(1).split(',') # verify the default plugins are available but the foo plugin is not - result = runCmd('devtool ide-sdk -h') + result = runCmd('devtool ide-sdk -h', output_log=self._cmd_logger) found_ides = get_ides_from_help(result.output) self.assertIn('code', found_ides) self.assertIn('none', found_ides) @@ -3013,7 +3024,7 @@ class DevtoolIdeSdkTests(DevtoolBase): plugin_file.write(plugin_code) # Verify the foo plugin is available as well - result = runCmd('devtool ide-sdk -h') + result = runCmd('devtool ide-sdk -h', output_log=self._cmd_logger) found_ides = get_ides_from_help(result.output) self.assertIn('code', found_ides) self.assertIn('none', found_ides) @@ -3021,14 +3032,15 @@ class DevtoolIdeSdkTests(DevtoolBase): # Verify the foo plugin generates a shared config result = runCmd( - 'devtool ide-sdk -m shared --skip-bitbake --ide foo %s' % shared_recipe_name) + 'devtool ide-sdk -m shared --skip-bitbake --ide foo %s' % shared_recipe_name, + output_log=self._cmd_logger) with open(shared_config_file) as shared_config: shared_config_new = shared_config.read() self.assertEqual(shared_config_str, shared_config_new) # Verify the foo plugin generates a modified config result = runCmd('devtool ide-sdk --skip-bitbake --ide foo %s %s' % - (modified_recipe_name, testimage)) + (modified_recipe_name, testimage), output_log=self._cmd_logger) with open(modified_config_file) as modified_config: modified_config_new = modified_config.read() self.assertEqual(modified_config_str, modified_config_new) From patchwork Wed Dec 31 11:46:33 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77837 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 C38EFEE6458 for ; Wed, 31 Dec 2025 11:47:51 +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.83905.1767181666334114938 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=OIbmlTcD; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-2025123111474395ade3e369000207d5-ujxjdi@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 2025123111474395ade3e369000207d5 for ; Wed, 31 Dec 2025 12:47:43 +0100 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=cM6ChC1SB9juDKJdV2HYQPi+YHcHqccAC+KE/OBboAY=; b=OIbmlTcDIIlP3+mPd0qJ8TXQ3HCuKWdUYfu6AxO+0P2UvrrJUD/K6EKvYxwk5wEgCBOpoQ 5D1axJm3Hv6fCwZmPw4Jq3WnAXcFlwpofeh7AXet/MX8IFr3GnGrBsUvme9CKwheAeV8MUIJ CHBf7ZKihuzUDpnuB61KUcMQS2f8AwtOWMYc2DTqnnrLZMKPk2HVCB63Y9150mgLe4bY4am9 GDTSM45OsKwyva7Rodsfq/z6U2kJZNyvpeKlNsRZOX1DLC2G98lz1OTaup8RspimI0Uy2/XR j6dqIh/zxDc3ZjkBKra2V0GBUtHQDQ41smIreSysU9sOhG/mELfTOVSA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 03/14] cpp-example: run as a service Date: Wed, 31 Dec 2025 12:46:33 +0100 Message-ID: <20251231114718.4031606-4-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228734 From: Adrian Freihofer Extend the C++ example to run as systemd/SysV services This change adds service capability to the existing C++ example without modifying its original behavior. The example can now run either as: - One-shot executables (existing behavior) - Long-running services via systemd or SysV init The service runs as an unprivileged user/group, demonstrating security best practices for service development. This introduces additional complexity to the build process, particularly around proper pseudo usage in development builds. The implementation includes: - Service configuration files (systemd .service and SysV init script) - Dedicated user/group creation with appropriate permissions - JSON configuration file for runtime customization, owned by the service user - Command-line --endless flag to enable service mode - Full support for both CMake and Meson build systems This enhancement enables testing debugger configurations that attach to running processes, expanding the examples' utility for development tools. Signed-off-by: Adrian Freihofer --- .../recipes-test/cpp/cpp-example.inc | 52 +++++++++++- .../recipes-test/cpp/files/CMakeLists.txt | 14 +++- .../recipes-test/cpp/files/config.h.in | 10 +++ .../cpp/files/cpp-example-lib.cpp | 29 +++++++ .../cpp/files/cpp-example-lib.hpp | 3 + .../recipes-test/cpp/files/cpp-example.conf | 3 + .../recipes-test/cpp/files/cpp-example.cpp | 38 ++++++++- .../recipes-test/cpp/files/cpp-example.init | 84 +++++++++++++++++++ .../cpp/files/cpp-example.service | 12 +++ .../recipes-test/cpp/files/meson.build | 18 +++- .../cpp/files/test-cpp-example.cpp | 2 + .../recipes-test/cpp/meson-example.bb | 2 + meta/lib/oeqa/selftest/cases/devtool.py | 4 +- 13 files changed, 264 insertions(+), 7 deletions(-) create mode 100644 meta-selftest/recipes-test/cpp/files/config.h.in create mode 100644 meta-selftest/recipes-test/cpp/files/cpp-example.conf create mode 100644 meta-selftest/recipes-test/cpp/files/cpp-example.init create mode 100644 meta-selftest/recipes-test/cpp/files/cpp-example.service diff --git a/meta-selftest/recipes-test/cpp/cpp-example.inc b/meta-selftest/recipes-test/cpp/cpp-example.inc index 76ff64e87f..2653f45e90 100644 --- a/meta-selftest/recipes-test/cpp/cpp-example.inc +++ b/meta-selftest/recipes-test/cpp/cpp-example.inc @@ -16,9 +16,59 @@ SRC_URI = "\ file://cpp-example-lib.hpp \ file://cpp-example-lib.cpp \ file://test-cpp-example.cpp \ + file://cpp-example.conf \ + file://config.h.in \ + file://cpp-example.service \ + file://cpp-example.init \ file://run-ptest \ " S = "${UNPACKDIR}" -inherit ptest +inherit ptest useradd systemd update-rc.d + +# Systemd and SysV init support +SYSTEMD_SERVICE:${PN} = "${BPN}.service" + +INITSCRIPT_NAME = "${BPN}" +INITSCRIPT_PARAMS = "defaults 99" + +# Create cpp-example user and group +USERADD_PACKAGES = "${PN}" +GROUPADD_PARAM:${PN} = "--system ${BPN}" +USERADD_PARAM:${PN} = "--system --home /var/lib/${BPN} --no-create-home --shell /bin/false --gid ${BPN} ${BPN}" + +EX_BINARY_NAME ?= "${BPN}" + +do_install:append() { + # Install configuration file owned by unprivileged user + install -d ${D}${sysconfdir} + install -m 0644 -g ${BPN} -o ${BPN} ${S}/cpp-example.conf ${D}${sysconfdir}/${BPN}.conf + sed -i -e 's|@BINARY_NAME@|${BPN}|g' ${D}${sysconfdir}/${BPN}.conf + + # Install service files or init scripts and substitute placeholders in service files + if ${@bb.utils.contains('DISTRO_FEATURES', 'systemd', 'true', 'false', d)}; then + install -d ${D}${systemd_system_unitdir} + install -m 0644 ${S}/cpp-example.service ${D}${systemd_system_unitdir}/${BPN}.service + sed -i \ + -e 's|@BINDIR@|${bindir}|g' \ + -e 's|@BINARY_NAME@|${EX_BINARY_NAME}|g' \ + -e 's|@USER@|${BPN}|g' \ + -e 's|@GROUP@|${BPN}|g' \ + ${D}${systemd_system_unitdir}/${BPN}.service + else + install -d ${D}${sysconfdir}/init.d + install -m 0755 ${S}/cpp-example.init ${D}${sysconfdir}/init.d/${BPN} + sed -i \ + -e 's|@BINDIR@|${bindir}|g' \ + -e 's|@BINARY_NAME@|${EX_BINARY_NAME}|g' \ + -e 's|@USER@|${BPN}|g' \ + -e 's|@GROUP@|${BPN}|g' \ + ${D}${sysconfdir}/init.d/${BPN} + fi +} + +FILES:${PN} += " \ + ${systemd_system_unitdir}/${BPN}.service \ + ${sysconfdir}/${BPN}.conf \ +" diff --git a/meta-selftest/recipes-test/cpp/files/CMakeLists.txt b/meta-selftest/recipes-test/cpp/files/CMakeLists.txt index 6fa6917d89..e363f31af2 100644 --- a/meta-selftest/recipes-test/cpp/files/CMakeLists.txt +++ b/meta-selftest/recipes-test/cpp/files/CMakeLists.txt @@ -20,15 +20,25 @@ set(CMAKE_CXX_EXTENSIONS Off) include(GNUInstallDirs) +# Define the config file path as a constant +set(CPP_EXAMPLE_CONFIG_PATH "${CMAKE_INSTALL_FULL_SYSCONFDIR}/cmake-example.conf") + +# Generate config.h from config.h.in +configure_file(config.h.in config.h @ONLY) + # Linking a small library makes the example more useful for testing. find_package(json-c) # A simple library linking json-c library found by pkgconfig add_library(cmake-example-lib cpp-example-lib.cpp cpp-example-lib.hpp) -set_target_properties(cmake-example-lib PROPERTIES +set_target_properties(cmake-example-lib PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) + +# Add the build directory to include path for config.h +target_include_directories(cmake-example-lib PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + target_link_libraries(cmake-example-lib PRIVATE json-c::json-c) install(TARGETS cmake-example-lib @@ -39,6 +49,7 @@ install(TARGETS cmake-example-lib # A simple executable linking the library add_executable(cmake-example cpp-example.cpp) +target_include_directories(cmake-example PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(cmake-example PRIVATE cmake-example-lib) install(TARGETS cmake-example @@ -47,6 +58,7 @@ install(TARGETS cmake-example # A simple test executable for testing the library add_executable(test-cmake-example test-cpp-example.cpp) +target_include_directories(test-cmake-example PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(test-cmake-example PRIVATE cmake-example-lib) if (FAILING_TEST) diff --git a/meta-selftest/recipes-test/cpp/files/config.h.in b/meta-selftest/recipes-test/cpp/files/config.h.in new file mode 100644 index 0000000000..174e266847 --- /dev/null +++ b/meta-selftest/recipes-test/cpp/files/config.h.in @@ -0,0 +1,10 @@ +/* + * Copyright OpenEmbedded Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +/* Configuration file path */ +#define EXAMPLE_CONFIG_PATH "@CPP_EXAMPLE_CONFIG_PATH@" diff --git a/meta-selftest/recipes-test/cpp/files/cpp-example-lib.cpp b/meta-selftest/recipes-test/cpp/files/cpp-example-lib.cpp index d3dc976864..c510a13893 100644 --- a/meta-selftest/recipes-test/cpp/files/cpp-example-lib.cpp +++ b/meta-selftest/recipes-test/cpp/files/cpp-example-lib.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "cpp-example-lib.hpp" @@ -31,3 +32,31 @@ void CppExample::print_json() json_object_put(jobj); // Delete the json object } + +std::string CppExample::read_config_message(const std::string &config_path) +{ + std::ifstream config_file(config_path); + if (!config_file.is_open()) { + return "Error: Could not open config file: " + config_path; + } + + std::string config_content((std::istreambuf_iterator(config_file)), + std::istreambuf_iterator()); + config_file.close(); + + struct json_object *jobj = json_tokener_parse(config_content.c_str()); + if (!jobj) { + return "Error: Invalid JSON in config file"; + } + + struct json_object *message_obj; + if (json_object_object_get_ex(jobj, "hello_world_message", &message_obj)) { + const char *message = json_object_get_string(message_obj); + std::string result = message ? message : "Error: Invalid message format"; + json_object_put(jobj); + return result; + } + + json_object_put(jobj); + return "Error: 'hello_world_message' not found in config file"; +} diff --git a/meta-selftest/recipes-test/cpp/files/cpp-example-lib.hpp b/meta-selftest/recipes-test/cpp/files/cpp-example-lib.hpp index 0ad9e7b7b2..24dd0defb6 100644 --- a/meta-selftest/recipes-test/cpp/files/cpp-example-lib.hpp +++ b/meta-selftest/recipes-test/cpp/files/cpp-example-lib.hpp @@ -7,6 +7,7 @@ #pragma once #include +#include "config.h" struct CppExample { @@ -18,4 +19,6 @@ struct CppExample const char *get_json_c_version(); /* Call a more advanced function from a library */ void print_json(); + /* Read hello world message from config file */ + std::string read_config_message(const std::string &config_path = EXAMPLE_CONFIG_PATH); }; diff --git a/meta-selftest/recipes-test/cpp/files/cpp-example.conf b/meta-selftest/recipes-test/cpp/files/cpp-example.conf new file mode 100644 index 0000000000..4a666e5cdd --- /dev/null +++ b/meta-selftest/recipes-test/cpp/files/cpp-example.conf @@ -0,0 +1,3 @@ +{ + "hello_world_message": "Hello World from @BINARY_NAME@ example config file!" +} diff --git a/meta-selftest/recipes-test/cpp/files/cpp-example.cpp b/meta-selftest/recipes-test/cpp/files/cpp-example.cpp index 9889554e0c..dbf82f15d9 100644 --- a/meta-selftest/recipes-test/cpp/files/cpp-example.cpp +++ b/meta-selftest/recipes-test/cpp/files/cpp-example.cpp @@ -7,12 +7,48 @@ #include "cpp-example-lib.hpp" #include +#include +#include -int main() +int main(int argc, char* argv[]) { + bool endless_mode = false; + + // Parse command line arguments + for (int i = 1; i < argc; i++) { + if (std::string(argv[i]) == "--endless") { + endless_mode = true; + } else if (std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") { + std::cout << "Usage: " << argv[0] << " [OPTIONS]" << std::endl; + std::cout << "Options:" << std::endl; + std::cout << " --endless Run in endless loop mode (for service)" << std::endl; + std::cout << " --help, -h Show this help message" << std::endl; + return 0; + } + } + auto cpp_example = CppExample(); + + if (endless_mode) { + std::cout << "Starting cpp-example service in endless mode..." << std::endl; + } else { + std::cout << "Running cpp-example once..." << std::endl; + } + std::cout << "C++ example linking " << cpp_example.get_string() << std::endl; std::cout << "Linking json-c version " << cpp_example.get_json_c_version() << std::endl; cpp_example.print_json(); + + do { + // Read and print message from config file + std::string config_message = cpp_example.read_config_message(); + std::cout << "Config file message: " << config_message << std::endl; + + if (endless_mode) { + // Sleep for 1 second + sleep(1); + } + } while (endless_mode); + return 0; } diff --git a/meta-selftest/recipes-test/cpp/files/cpp-example.init b/meta-selftest/recipes-test/cpp/files/cpp-example.init new file mode 100644 index 0000000000..c154fd1126 --- /dev/null +++ b/meta-selftest/recipes-test/cpp/files/cpp-example.init @@ -0,0 +1,84 @@ +#!/bin/sh +# +# cpp-example C++ Example Service +# +# chkconfig: 35 99 99 +# description: C++ Example Service daemon +# + +USER="@USER@" +DAEMON="@BINARY_NAME@" +DAEMON_PATH="@BINDIR@/$DAEMON" +DAEMON_ARGS="--endless" +PIDFILE="/var/run/$DAEMON.pid" +LOCK_FILE="/var/lock/subsys/$DAEMON" + +start() { + if [ -f $PIDFILE ]; then + echo "$DAEMON is already running." + return 1 + fi + + echo -n "Starting $DAEMON: " + start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile \ + --background --chuid $USER --exec $DAEMON_PATH -- $DAEMON_ARGS + RETVAL=$? + if [ $RETVAL -eq 0 ]; then + echo "OK" + touch $LOCK_FILE + else + echo "FAILED" + fi + return $RETVAL +} + +stop() { + echo -n "Stopping $DAEMON: " + start-stop-daemon --stop --quiet --pidfile $PIDFILE + RETVAL=$? + if [ $RETVAL -eq 0 ]; then + echo "OK" + rm -f $PIDFILE $LOCK_FILE + else + echo "FAILED" + fi + return $RETVAL +} + +status() { + if [ -f $PIDFILE ]; then + PID=$(cat $PIDFILE) + if ps -p $PID > /dev/null 2>&1; then + echo "$DAEMON is running (PID: $PID)" + return 0 + else + echo "$DAEMON is not running (stale PID file)" + return 1 + fi + else + echo "$DAEMON is not running" + return 1 + fi +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; + status) + status + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 + ;; +esac + +exit $? diff --git a/meta-selftest/recipes-test/cpp/files/cpp-example.service b/meta-selftest/recipes-test/cpp/files/cpp-example.service new file mode 100644 index 0000000000..4022fa291a --- /dev/null +++ b/meta-selftest/recipes-test/cpp/files/cpp-example.service @@ -0,0 +1,12 @@ +[Unit] +Description=C++ Example Service +After=network.target + +[Service] +Type=simple +User=@USER@ +Group=@GROUP@ +ExecStart=@BINDIR@/@BINARY_NAME@ --endless + +[Install] +WantedBy=multi-user.target diff --git a/meta-selftest/recipes-test/cpp/files/meson.build b/meta-selftest/recipes-test/cpp/files/meson.build index 74a0e0173c..53248c4380 100644 --- a/meta-selftest/recipes-test/cpp/files/meson.build +++ b/meta-selftest/recipes-test/cpp/files/meson.build @@ -16,23 +16,37 @@ if get_option('FAILING_TEST').enabled() add_project_arguments('-DFAIL_COMPARISON_STR=foo', language: 'cpp') endif +# Generate config.h from config.h.in +config_path = get_option('sysconfdir') / 'meson-example.conf' +conf_data = configuration_data() +conf_data.set('CPP_EXAMPLE_CONFIG_PATH', config_path) +configure_file(input : 'config.h.in', + output : 'config.h', + configuration : conf_data) + +# Include the build directory for config.h +inc_dir = include_directories('.') + mesonexlib = shared_library('mesonexlib', 'cpp-example-lib.cpp', 'cpp-example-lib.hpp', - version: meson.project_version(), - soversion: meson.project_version().split('.')[0], + version: meson.project_version(), + soversion: meson.project_version().split('.')[0], dependencies : jsoncdep, + include_directories : inc_dir, install : true ) executable('mesonex', 'cpp-example.cpp', link_with : mesonexlib, + include_directories : inc_dir, install : true ) test_mesonex = executable('test-mesonex', 'test-cpp-example.cpp', link_with : mesonexlib, + include_directories : inc_dir, install : true ) diff --git a/meta-selftest/recipes-test/cpp/files/test-cpp-example.cpp b/meta-selftest/recipes-test/cpp/files/test-cpp-example.cpp index 83c9bfa844..e1909c3168 100644 --- a/meta-selftest/recipes-test/cpp/files/test-cpp-example.cpp +++ b/meta-selftest/recipes-test/cpp/files/test-cpp-example.cpp @@ -22,4 +22,6 @@ int main() { std::cout << "FAIL: " << ret_string << " != " << CppExample::test_string << std::endl; return 1; } + + return 0; } diff --git a/meta-selftest/recipes-test/cpp/meson-example.bb b/meta-selftest/recipes-test/cpp/meson-example.bb index 14a7ca8dc9..da0ea18376 100644 --- a/meta-selftest/recipes-test/cpp/meson-example.bb +++ b/meta-selftest/recipes-test/cpp/meson-example.bb @@ -25,3 +25,5 @@ do_run_tests () { do_run_tests[doc] = "Run meson test using qemu-user" addtask do_run_tests after do_compile + +EX_BINARY_NAME = "mesonex" diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 9bbba2ec89..681dcee08c 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2714,7 +2714,7 @@ class DevtoolIdeSdkTests(DevtoolBase): $1 = 0 print CppExample::test_string.compare("cpp-example-lib Magic: 123456789aaa") $2 = -3 - list cpp-example-lib.hpp:13,13 + list cpp-example-lib.hpp:14,14 13 inline static const std::string test_string = "cpp-example-lib Magic: 123456789"; continue """ @@ -2745,7 +2745,7 @@ class DevtoolIdeSdkTests(DevtoolBase): gdb_batch_cmd += " -ex 'break CppExample::print_json()' -ex 'continue'" gdb_batch_cmd += " -ex 'print CppExample::test_string.compare(\"cpp-example-lib %s\")'" % magic_string gdb_batch_cmd += " -ex 'print CppExample::test_string.compare(\"cpp-example-lib %saaa\")'" % magic_string - gdb_batch_cmd += " -ex 'list cpp-example-lib.hpp:13,13'" + gdb_batch_cmd += " -ex 'list cpp-example-lib.hpp:14,14'" gdb_batch_cmd += " -ex 'continue'" r = runCmd(gdb_script + gdb_batch_cmd, output_log=self._cmd_logger) self.logger.debug("%s %s returned: %s", gdb_script, From patchwork Wed Dec 31 11:46:34 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77833 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 AB15DEE644D for ; Wed, 31 Dec 2025 11:47:51 +0000 (UTC) Received: from mta-65-227.siemens.flowmailer.net (mta-65-227.siemens.flowmailer.net [185.136.65.227]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.83935.1767181666334514296 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=b5EUGw7i; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.227, mailfrom: fm-1329275-20251231114743628e3fe0440002079a-7u26k_@rts-flowmailer.siemens.com) Received: by mta-65-227.siemens.flowmailer.net with ESMTPSA id 20251231114743628e3fe0440002079a for ; Wed, 31 Dec 2025 12:47:43 +0100 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=a3XS/ZJR1NxGoapWsVY8Y2f+J7CRdSuy+y8wsy4MiIE=; b=b5EUGw7iQgkPxScQsykn3Y11tF5bNMJpIC7NGga/FVdPIuXNxuIcNTZjtnrfhSKiHGIo/2 +CbLortzkIB0FKO9h0sz7UOhgR7KT0v40RNoe8iyXg+5gjVUyxYApd/Ddx1Pb+Ju2uhfSKqO Z/HOo4I16A/cMKv1i2x+Eq9SWRQgv5okRJHGJrK2aRz+8UyACUWTGmsHBiX21veNdiuHcu9/ YmcPjnumHVSdaBcVFKz6fPIlZV0YyGIjkz1Z0NJTdpr9DjLqypWPShoLgnaGAOobLJrFWZOp 5EQkGZ3tkX2uUQk0aHS7kCXtxsuk7LO8PapWInTrIk0TkbAAVmsZJxiw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 04/14] oe-selftest: devtool: check example services are running Date: Wed, 31 Dec 2025 12:46:34 +0100 Message-ID: <20251231114718.4031606-5-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228737 From: Adrian Freihofer When running the devtool ide-sdk test with qemu, verify that the example services are actually running on the target by using pgrep to check for the example executable names. Also verify that the configuration files in /etc are owned by the proper user and group, both before and after the install_and_deploy scripts have run. This is also a check that the install_and_deploy scripts are working correctly with pseudo. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 681dcee08c..e78e6ed226 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2810,6 +2810,22 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertIn("Running tests...", result.output) self.assertIn("100% tests passed", result.output) + def _verify_service_running(self, qemu, service_name): + """Helper to verify a service is running in Qemu""" + 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""" + ls_cmd = "ls -l %s" % conf_file + status, output = qemu.run(ls_cmd) + self.assertEqual(status, 0, msg="Failed to ls %s: %s" % (conf_file, output)) + self.assertRegex(output, rf"^-.+ {owner} {group} .+ {re.escape(conf_file)}$", + msg="%s not owned by %s:%s: %s" % (conf_file, owner, group, output)) + @OETestTag("runqemu") def test_devtool_ide_sdk_none_qemu(self): """Start qemu-system and run tests for multiple recipes. ide=none is used.""" @@ -2826,7 +2842,16 @@ class DevtoolIdeSdkTests(DevtoolBase): # cmake-example recipe recipe_name = "cmake-example" example_exe = "cmake-example" + example_user_group = "cmake-example" + conf_file = "/etc/cmake-example.conf" build_file = "CMakeLists.txt" + + # Verify the cmake-example service is running on the target + self._verify_service_running(qemu, example_exe) + # Verify /etc/cmake-example.conf is owned by the cmake-example user + self._verify_conf_file(qemu, conf_file, example_user_group, example_user_group) + + # Setup the recipe with devtool ide-sdk cmake-example ... tempdir = self._devtool_ide_sdk_recipe( recipe_name, build_file, testimage) bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@%s -c --ide=none' % ( @@ -2835,14 +2860,29 @@ class DevtoolIdeSdkTests(DevtoolBase): self._gdb_cross() self._verify_cmake_preset(tempdir) self._devtool_ide_sdk_qemu(tempdir, qemu, recipe_name, example_exe) + # Verify the oe-scripts sym-link is valid self.assertEqual(self._workspace_scripts_dir( recipe_name), self._sources_scripts_dir(tempdir)) + # Verify /etc/meson-example.conf is still owned by the cmake-example user + # after the install and deploy scripts updated the file + self._verify_conf_file(qemu, conf_file, example_exe, example_exe) + + # meson-example recipe recipe_name = "meson-example" example_exe = "mesonex" + example_user_group = "meson-example" + conf_file = "/etc/meson-example.conf" build_file = "meson.build" + + # Verify the meson-example service is running on the target + self._verify_service_running(qemu, example_exe) + # Verify /etc/meson-example.conf is owned by the meson-example user + self._verify_conf_file(qemu, conf_file, example_user_group, example_user_group) + + # Setup the recipe with devtool ide-sdk meson-example ... tempdir = self._devtool_ide_sdk_recipe( recipe_name, build_file, testimage) bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@%s -c --ide=none' % ( @@ -2850,10 +2890,15 @@ class DevtoolIdeSdkTests(DevtoolBase): runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger) self._gdb_cross() self._devtool_ide_sdk_qemu(tempdir, qemu, recipe_name, example_exe) + # Verify the oe-scripts sym-link is valid self.assertEqual(self._workspace_scripts_dir( recipe_name), self._sources_scripts_dir(tempdir)) + # Verify /etc/meson-example.conf is still owned by the meson-example user + # after the install and deploy scripts updated the file + self._verify_conf_file(qemu, conf_file, example_user_group, example_user_group) + def test_devtool_ide_sdk_code_cmake(self): """Verify a cmake recipe works with ide=code mode""" recipe_name = "cmake-example" From patchwork Wed Dec 31 11:46:35 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77831 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 896D7EE6446 for ; Wed, 31 Dec 2025 11:47:51 +0000 (UTC) Received: from mta-65-227.siemens.flowmailer.net (mta-65-227.siemens.flowmailer.net [185.136.65.227]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.83904.1767181666333459073 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=FysocGvY; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.227, mailfrom: fm-1329275-20251231114743b30d09beb4000207a2-9bst02@rts-flowmailer.siemens.com) Received: by mta-65-227.siemens.flowmailer.net with ESMTPSA id 20251231114743b30d09beb4000207a2 for ; Wed, 31 Dec 2025 12:47:43 +0100 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=zzJ1YZuJ4WR+Ji/MIksmU7AyD0pCIRUDVTP8mTVhK8w=; b=FysocGvYRZn4as8qxvYgwpGlQcd2doL7FpdVvCrbyHTD4RcEvQ4GE7LPEOOAouZ8GXETcX EGUhBC7Qn/B/bCEYOcpGVg1TSJ47UwVWM6+NK98IiNb0Vj7PmHiD9pnTRS0rwWyCdoirZB3R wKJaT4xjXioHiZ9+24Saxikj1Q4n4JNKNn6YoOAfs+ngQqZf9o+XCFvopdiiJLbKvva0yB4U JihMg/4WH0xKXN7qOHF9ZENTbUpGk47ogX6ZWmU+K6BM7wlZLM6pfiwkvhm2tTAb8+f9JzgW 1kQv2LeudKHNdXJHXygFbjU7GKh+WK3uwqgbbmz+oHbJU3PtxBRn7JFg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 05/14] devtool: ide-sdk: add gdbserver attach mode support Date: Wed, 31 Dec 2025 12:46:35 +0100 Message-ID: <20251231114718.4031606-6-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228741 From: Adrian Freihofer Enhance remote debugging configuration to support multiple modes per executable binary. This adds support for gdbserver's attach mode as an additional debug configuration. When the binary is detected to run as a systemd service or SysV init script, an attach debug configuration is generated alongside the regular configuration that starts the process via gdbserver. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 16 +- scripts/lib/devtool/ide_plugins/__init__.py | 218 +++++++++++++------- scripts/lib/devtool/ide_plugins/ide_code.py | 99 ++++++--- scripts/lib/devtool/ide_plugins/ide_none.py | 14 +- scripts/lib/devtool/ide_sdk.py | 172 ++++++++++++++- 5 files changed, 406 insertions(+), 113 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index e78e6ed226..b40323c109 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2660,7 +2660,7 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertIn("PASS: cpp-example-lib", output) # Verify remote debugging works - self._gdb_cross_debugging( + self._gdb_cross_debugging_multi( qemu, recipe_name, example_exe, MAGIC_STRING_ORIG) # Replace the Magic String in the code, compile and deploy to Qemu @@ -2685,7 +2685,7 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertIn("PASS: cpp-example-lib", output) # Verify remote debugging works wit the modified magic string - self._gdb_cross_debugging( + self._gdb_cross_debugging_multi( qemu, recipe_name, example_exe, MAGIC_STRING_NEW) def _gdb_cross(self): @@ -2701,7 +2701,7 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertEqual(r.status, 0) self.assertIn("GNU gdb", r.output) - def _gdb_cross_debugging(self, qemu, recipe_name, example_exe, magic_string): + def _gdb_cross_debugging_multi(self, qemu, recipe_name, example_exe, magic_string): """Verify gdb-cross is working Test remote debugging: @@ -2720,7 +2720,7 @@ class DevtoolIdeSdkTests(DevtoolBase): """ 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 + '_m') + recipe_name), 'gdbserver_1234_usr-bin-' + example_exe + '_multi') gdb_script = os.path.join(self._workspace_scripts_dir( recipe_name), 'gdb_1234_usr-bin-' + example_exe) @@ -2734,11 +2734,13 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertIn("gdbserver ", r.output) # Check the pid file is correct - test_cmd = "cat /proc/$(cat /tmp/gdbserver_1234_usr-bin-" + \ - example_exe + "/pid)/cmdline" + 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) # Test remote debugging works gdb_batch_cmd = " --batch -ex 'break main' -ex 'run'" @@ -2747,6 +2749,8 @@ class DevtoolIdeSdkTests(DevtoolBase): gdb_batch_cmd += " -ex 'print CppExample::test_string.compare(\"cpp-example-lib %saaa\")'" % magic_string gdb_batch_cmd += " -ex 'list cpp-example-lib.hpp:14,14'" gdb_batch_cmd += " -ex 'continue'" + return gdb_batch_cmd + r = runCmd(gdb_script + gdb_batch_cmd, output_log=self._cmd_logger) self.logger.debug("%s %s returned: %s", gdb_script, gdb_batch_cmd, r.output) diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py index 19c2f61c5f..70f47d6e68 100644 --- a/scripts/lib/devtool/ide_plugins/__init__.py +++ b/scripts/lib/devtool/ide_plugins/__init__.py @@ -31,88 +31,155 @@ class BuildTool(Enum): return False +class GdbServerModes(Enum): + ONCE = auto() + ATTACH = auto() + MULTI = auto() + + class GdbCrossConfig: """Base class defining the GDB configuration generator interface Generate a GDB configuration for a binary on the target device. - Only one instance per binary is allowed. This allows to assign unique port - numbers for all gdbserver instances. """ _gdbserver_port_next = 1234 - _binaries = [] + _gdb_cross_configs = {} - def __init__(self, image_recipe, modified_recipe, binary, gdbserver_multi=True): + def __init__(self, image_recipe, modified_recipe, binary, gdbserver_default_mode): self.image_recipe = image_recipe self.modified_recipe = modified_recipe self.gdb_cross = modified_recipe.gdb_cross self.binary = binary - if binary in GdbCrossConfig._binaries: - raise DevtoolError( - "gdbserver config for binary %s is already generated" % binary) - GdbCrossConfig._binaries.append(binary) - self.script_dir = modified_recipe.ide_sdk_scripts_dir - self.gdbinit_dir = os.path.join(self.script_dir, 'gdbinit') - self.gdbserver_multi = gdbserver_multi - self.binary_pretty = self.binary.replace(os.sep, '-').lstrip('-') + self.gdbserver_default_mode = gdbserver_default_mode + self.binary_pretty = self.binary.binary_path.replace(os.sep, '-').lstrip('-') self.gdbserver_port = GdbCrossConfig._gdbserver_port_next GdbCrossConfig._gdbserver_port_next += 1 self.id_pretty = "%d_%s" % (self.gdbserver_port, self.binary_pretty) - # gdbserver start script - gdbserver_script_file = 'gdbserver_' + self.id_pretty - if self.gdbserver_multi: - gdbserver_script_file += "_m" - self.gdbserver_script = os.path.join( - self.script_dir, gdbserver_script_file) - # gdbinit file - self.gdbinit = os.path.join( + + # Track all generated gdbserver configs to avoid duplicates + if self.id_pretty in GdbCrossConfig._gdb_cross_configs: + raise DevtoolError( + "gdbserver config for binary %s is already generated" % binary) + GdbCrossConfig._gdb_cross_configs[self.id_pretty] = self + + def id_pretty_mode(self, gdbserver_mode): + return "%s_%s" % (self.id_pretty, gdbserver_mode.name.lower()) + + # GDB and gdbserver script on the host + @property + def script_dir(self): + return self.modified_recipe.ide_sdk_scripts_dir + + @property + def gdbinit_dir(self): + return os.path.join(self.script_dir, 'gdbinit') + + def gdbserver_script_file(self, gdbserver_mode): + return 'gdbserver_' + self.id_pretty_mode(gdbserver_mode) + + def gdbserver_script(self, gdbserver_mode): + return os.path.join(self.script_dir, self.gdbserver_script_file(gdbserver_mode)) + + @property + def gdbinit(self): + return os.path.join( self.gdbinit_dir, 'gdbinit_' + self.id_pretty) - # gdb start script - self.gdb_script = os.path.join( + + @property + def gdb_script(self): + return os.path.join( self.script_dir, 'gdb_' + self.id_pretty) - def _gen_gdbserver_start_script(self): - """Generate a shell command starting the gdbserver on the remote device via ssh + # gdbserver files on the target + def gdbserver_tmp_dir(self, gdbserver_mode): + return os.path.join('/tmp', 'gdbserver_%s' % self.id_pretty_mode(gdbserver_mode)) - GDB supports two modes: - multi: gdbserver remains running over several debug sessions - once: gdbserver terminates after the debugged process terminates + def gdbserver_pid_file(self, gdbserver_mode): + return os.path.join(self.gdbserver_tmp_dir(gdbserver_mode), 'gdbserver.pid') + + def gdbserver_log_file(self, gdbserver_mode): + return os.path.join(self.gdbserver_tmp_dir(gdbserver_mode), 'gdbserver.log') + + def _target_gdbserver_start_cmd(self, gdbserver_mode): + """Get the ssh command to start gdbserver on the target device + + returns something like: + "\"/bin/sh -c '/usr/bin/gdbserver --once :1234 /usr/bin/cmake-example'\"" + or for multi mode: + "\"/bin/sh -c 'if [ \"$1\" = \"stop\" ]; then ... else ... fi'\"" """ - cmd_lines = ['#!/bin/sh'] - if self.gdbserver_multi: - temp_dir = "TEMP_DIR=/tmp/gdbserver_%s; " % self.id_pretty - gdbserver_cmd_start = temp_dir - gdbserver_cmd_start += "test -f \\$TEMP_DIR/pid && exit 0; " - gdbserver_cmd_start += "mkdir -p \\$TEMP_DIR; " - gdbserver_cmd_start += "%s --multi :%s > \\$TEMP_DIR/log 2>&1 & " % ( - self.gdb_cross.gdbserver_path, self.gdbserver_port) - gdbserver_cmd_start += "echo \\$! > \\$TEMP_DIR/pid;" - - gdbserver_cmd_stop = temp_dir - gdbserver_cmd_stop += "test -f \\$TEMP_DIR/pid && kill \\$(cat \\$TEMP_DIR/pid); " - gdbserver_cmd_stop += "rm -rf \\$TEMP_DIR; " - - gdbserver_cmd_l = [] - gdbserver_cmd_l.append('if [ "$1" = "stop" ]; then') - gdbserver_cmd_l.append(' shift') - gdbserver_cmd_l.append(" %s %s %s %s 'sh -c \"%s\"'" % ( - self.gdb_cross.target_device.ssh_sshexec, self.gdb_cross.target_device.ssh_port, self.gdb_cross.target_device.extraoptions, self.gdb_cross.target_device.target, gdbserver_cmd_stop)) - gdbserver_cmd_l.append('else') - gdbserver_cmd_l.append(" %s %s %s %s 'sh -c \"%s\"'" % ( - self.gdb_cross.target_device.ssh_sshexec, self.gdb_cross.target_device.ssh_port, self.gdb_cross.target_device.extraoptions, self.gdb_cross.target_device.target, gdbserver_cmd_start)) - gdbserver_cmd_l.append('fi') - gdbserver_cmd = os.linesep.join(gdbserver_cmd_l) - else: + if gdbserver_mode == GdbServerModes.ONCE: gdbserver_cmd_start = "%s --once :%s %s" % ( - self.gdb_cross.gdbserver_path, self.gdbserver_port, self.binary) - gdbserver_cmd = "%s %s %s %s 'sh -c \"%s\"'" % ( - self.gdb_cross.target_device.ssh_sshexec, self.gdb_cross.target_device.ssh_port, self.gdb_cross.target_device.extraoptions, self.gdb_cross.target_device.target, gdbserver_cmd_start) - cmd_lines.append(gdbserver_cmd) - GdbCrossConfig.write_file(self.gdbserver_script, cmd_lines, True) + self.gdb_cross.gdbserver_path, self.gdbserver_port, self.binary.binary_path) + elif gdbserver_mode == GdbServerModes.ATTACH: + pid_command = self.binary.pid_command + if pid_command: + gdbserver_cmd_start = "%s --attach :%s \\$(%s)" % ( + self.gdb_cross.gdbserver_path, + self.gdbserver_port, + pid_command) + else: + raise DevtoolError("Cannot use gdbserver attach mode for binary %s. No PID found." % self.binary.binary_path) + elif gdbserver_mode == GdbServerModes.MULTI: + gdbserver_cmd_start = "test -f %s && exit 0; " % self.gdbserver_pid_file(gdbserver_mode) + gdbserver_cmd_start += "mkdir -p %s; " % self.gdbserver_tmp_dir(gdbserver_mode) + gdbserver_cmd_start += "%s --multi :%s > %s 2>&1 & " % ( + self.gdb_cross.gdbserver_path, self.gdbserver_port, self.gdbserver_log_file(gdbserver_mode)) + gdbserver_cmd_start += "echo \\$! > %s;" % self.gdbserver_pid_file(gdbserver_mode) + else: + raise DevtoolError("Unsupported gdbserver mode: %s" % gdbserver_mode) + return "\"/bin/sh -c '" + gdbserver_cmd_start + "'\"" - def _gen_gdbinit_config(self): + def _target_gdbserver_stop_cmd(self, gdbserver_mode): + """Kill a gdbserver process""" + # This is the usual behavior: gdbserver is stopped on demand + if gdbserver_mode == GdbServerModes.MULTI: + gdbserver_cmd_stop = "test -f %s && kill \\$(cat %s);" % ( + self.gdbserver_pid_file(gdbserver_mode), self.gdbserver_pid_file(gdbserver_mode)) + gdbserver_cmd_stop += " rm -rf %s" % self.gdbserver_tmp_dir(gdbserver_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" + return "\"/bin/sh -c '" + gdbserver_cmd_stop + "'\"" + + def _target_gdbserver_kill_cmd(self): + """Get the ssh command to kill gdbserver on the target device""" + return "\"kill \\$(pgrep -o -f 'gdbserver --attach :%s') 2>/dev/null || true\"" % self.gdbserver_port + + def _target_ssh_gdbserver_args(self): + ssh_args = [] + if self.gdb_cross.target_device.ssh_port: + ssh_args += ["-p", self.gdb_cross.target_device.ssh_port] + if self.gdb_cross.target_device.extraoptions: + ssh_args.extend(self.gdb_cross.target_device.extraoptions) + if self.gdb_cross.target_device.target: + ssh_args.append(self.gdb_cross.target_device.target) + return ssh_args + + def _gen_gdbserver_start_script(self, gdbserver_mode=None): + """Generate a shell script starting the gdbserver on the remote device via ssh""" + if gdbserver_mode is None: + gdbserver_mode = self.gdbserver_default_mode + gdbserver_cmd_start = self._target_gdbserver_start_cmd(gdbserver_mode) + gdbserver_cmd_stop = self._target_gdbserver_stop_cmd(gdbserver_mode) + remote_ssh = "%s %s" % (self.gdb_cross.target_device.ssh_sshexec, + " ".join(self._target_ssh_gdbserver_args())) + gdbserver_cmd = ['#!/bin/sh'] + gdbserver_cmd.append('if [ "$1" = "stop" ]; then') + gdbserver_cmd.append(' shift') + gdbserver_cmd.append(" %s %s" % (remote_ssh, gdbserver_cmd_stop)) + gdbserver_cmd.append('else') + gdbserver_cmd.append(" %s %s" % (remote_ssh, gdbserver_cmd_start)) + gdbserver_cmd.append('fi') + GdbCrossConfig.write_file(self.gdbserver_script(gdbserver_mode), gdbserver_cmd, True) + + def _gen_gdbinit_config(self, gdbserver_mode=None): """Generate a gdbinit file for this binary and the corresponding gdbserver configuration""" + if gdbserver_mode is None: + gdbserver_mode = self.gdbserver_default_mode gdbinit_lines = ['# This file is generated by devtool ide-sdk'] - if self.gdbserver_multi: + if gdbserver_mode == GdbServerModes.MULTI: target_help = '# gdbserver --multi :%d' % self.gdbserver_port remote_cmd = 'target extended-remote' else: @@ -125,15 +192,15 @@ class GdbCrossConfig: gdbinit_lines.append('# cd ' + self.modified_recipe.real_srctree) gdbinit_lines.append( '# ' + self.gdb_cross.gdb + ' -ix ' + self.gdbinit) - gdbinit_lines.append('set sysroot ' + self.modified_recipe.d) - gdbinit_lines.append('set substitute-path "/usr/include" "' + - os.path.join(self.modified_recipe.recipe_sysroot, 'usr', 'include') + '"') - # Disable debuginfod for now, the IDE configuration uses rootfs-dbg from the image workdir. - gdbinit_lines.append('set debuginfod enabled off') + if self.image_recipe.rootfs_dbg: gdbinit_lines.append( 'set solib-search-path "' + self.modified_recipe.solib_search_path_str(self.image_recipe) + '"') + + gdbinit_lines.append('set substitute-path "/usr/include" "' + + os.path.join(self.modified_recipe.recipe_sysroot, 'usr', 'include') + '"') + if self.image_recipe.rootfs_dbg: # First: Search for sources of this recipe in the workspace folder if self.modified_recipe.pn in self.modified_recipe.target_dbgsrc_dir: gdbinit_lines.append('set substitute-path "%s" "%s"' % @@ -151,11 +218,12 @@ class GdbCrossConfig: else: logger.warning( "Cannot setup debug symbols configuration for GDB. IMAGE_GEN_DEBUGFS is not enabled.") + # Disable debuginfod for now, the IDE configuration uses rootfs-dbg from the image workdir. + gdbinit_lines.append('set debuginfod enabled off') gdbinit_lines.append( '%s %s:%d' % (remote_cmd, self.gdb_cross.host, self.gdbserver_port)) - gdbinit_lines.append('set remote exec-file ' + self.binary) - gdbinit_lines.append( - 'run ' + os.path.join(self.modified_recipe.d, self.binary)) + gdbinit_lines.append('set remote exec-file ' + self.binary.binary_path) + gdbinit_lines.append('run ' + self.binary.binary_path) GdbCrossConfig.write_file(self.gdbinit, gdbinit_lines) @@ -169,9 +237,18 @@ class GdbCrossConfig: def initialize(self): self._gen_gdbserver_start_script() + if self.binary.runs_as_service and self.gdbserver_default_mode != GdbServerModes.ATTACH: + self._gen_gdbserver_start_script(GdbServerModes.ATTACH) self._gen_gdbinit_config() self._gen_gdb_start_script() + def gdbserver_modes(self): + """Get the list of gdbserver modes for which scripts are generated""" + modes = [self.gdbserver_default_mode] + if self.binary.runs_as_service and self.gdbserver_default_mode != GdbServerModes.ATTACH: + modes.append(GdbServerModes.ATTACH) + return modes + @staticmethod def write_file(script_file, cmd_lines, executable=False): script_dir = os.path.dirname(script_file) @@ -206,15 +283,14 @@ class IdeBase: self.ide_name) def initialize_gdb_cross_configs(self, image_recipe, modified_recipe, gdb_cross_config_class=GdbCrossConfig): - binaries = modified_recipe.find_installed_binaries() - for binary in binaries: + for _, exec_bin in modified_recipe.installed_binaries.items(): gdb_cross_config = gdb_cross_config_class( - image_recipe, modified_recipe, binary) + image_recipe, modified_recipe, exec_bin) gdb_cross_config.initialize() self.gdb_cross_configs.append(gdb_cross_config) @staticmethod - def gen_oe_scrtips_sym_link(modified_recipe): + def gen_oe_scripts_sym_link(modified_recipe): # create a sym-link from sources to the scripts directory if os.path.isdir(modified_recipe.ide_sdk_scripts_dir): IdeBase.symlink_force(modified_recipe.ide_sdk_scripts_dir, diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py index ee5bb57265..67bd341347 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -9,14 +9,26 @@ import json import logging import os import shutil -from devtool.ide_plugins import BuildTool, IdeBase, GdbCrossConfig, get_devtool_deploy_opts +from devtool.ide_plugins import BuildTool, IdeBase, GdbCrossConfig, GdbServerModes, get_devtool_deploy_opts logger = logging.getLogger('devtool') class GdbCrossConfigVSCode(GdbCrossConfig): - def __init__(self, image_recipe, modified_recipe, binary): - super().__init__(image_recipe, modified_recipe, binary, False) + def __init__(self, image_recipe, modified_recipe, binary, + gdbserver_default_mode=GdbServerModes.ONCE): + super().__init__(image_recipe, modified_recipe, binary, + gdbserver_default_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"'] + """ + return self._target_ssh_gdbserver_args() + [ + self._target_gdbserver_kill_cmd() + ] def initialize(self): self._gen_gdbserver_start_script() @@ -207,20 +219,20 @@ class IdeVSCode(IdeBase): IdeBase.update_json_file( self.dot_code_dir(modified_recipe), prop_file, properties_dicts) - def vscode_launch_bin_dbg(self, gdb_cross_config): + def vscode_launch_bin_dbg(self, gdb_cross_config, gdbserver_mode): modified_recipe = gdb_cross_config.modified_recipe launch_config = { - "name": gdb_cross_config.id_pretty, + "name": gdb_cross_config.id_pretty_mode(gdbserver_mode), "type": "cppdbg", "request": "launch", - "program": os.path.join(modified_recipe.d, gdb_cross_config.binary.lstrip('/')), + "program": gdb_cross_config.binary.binary_host_path, "stopAtEntry": True, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": False, "MIMode": "gdb", - "preLaunchTask": gdb_cross_config.id_pretty, + "preLaunchTask": gdb_cross_config.id_pretty_mode(gdbserver_mode), "miDebuggerPath": modified_recipe.gdb_cross.gdb, "miDebuggerServerAddress": "%s:%d" % (modified_recipe.gdb_cross.host, gdb_cross_config.gdbserver_port) } @@ -260,6 +272,12 @@ class IdeVSCode(IdeBase): launch_config['sourceFileMap'] = src_file_map launch_config['setupCommands'] = setup_commands + + # Add postDebugTask for attach mode to clean up gdbserver + if gdbserver_mode == GdbServerModes.ATTACH: + kill_task_label = "kill_gdbserver_" + gdb_cross_config.id_pretty_mode(gdbserver_mode) + launch_config["postDebugTask"] = kill_task_label + return launch_config def vscode_launch(self, modified_recipe): @@ -268,7 +286,8 @@ class IdeVSCode(IdeBase): configurations = [] for gdb_cross_config in self.gdb_cross_configs: if gdb_cross_config.modified_recipe is modified_recipe: - configurations.append(self.vscode_launch_bin_dbg(gdb_cross_config)) + for gdbserver_mode in gdb_cross_config.gdbserver_modes(): + configurations.append(self.vscode_launch_bin_dbg(gdb_cross_config, gdbserver_mode)) launch_dict = { "version": "0.2.0", "configurations": configurations @@ -294,15 +313,12 @@ class IdeVSCode(IdeBase): for gdb_cross_config in self.gdb_cross_configs: if gdb_cross_config.modified_recipe is not modified_recipe: continue - tasks_dict['tasks'].append( - { - "label": gdb_cross_config.id_pretty, + for gdbserver_mode in gdb_cross_config.gdbserver_modes(): + new_task = { + "label": gdb_cross_config.id_pretty_mode(gdbserver_mode), "type": "shell", "isBackground": True, - "dependsOn": [ - install_task_name - ], - "command": gdb_cross_config.gdbserver_script, + "command": gdb_cross_config.gdbserver_script(gdbserver_mode), "problemMatcher": [ { "pattern": [ @@ -320,7 +336,38 @@ class IdeVSCode(IdeBase): } } ] - }) + } + # Deploy the artifacts to the target before starting gdbserver if not already running + if gdbserver_mode != GdbServerModes.ATTACH: + new_task['dependsOn'] = [ + install_task_name + ] + + 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 + if gdbserver_mode == GdbServerModes.ATTACH: + new_task_kill_label = "kill_gdbserver_"+ gdb_cross_config.id_pretty_mode(gdbserver_mode) + new_task_kill = { + "label": new_task_kill_label, + "type": "shell", + "command": gdb_cross_config.gdb_cross.target_device.ssh_sshexec, + "args": gdb_cross_config.target_ssh_gdbserver_kill_args(), + "presentation": { + "close": True + }, + "problemMatcher": [] + } + tasks_dict['tasks'].append(new_task_kill) + tasks_file = 'tasks.json' IdeBase.update_json_file( self.dot_code_dir(modified_recipe), tasks_file, tasks_dict) @@ -410,15 +457,12 @@ class IdeVSCode(IdeBase): for gdb_cross_config in self.gdb_cross_configs: if gdb_cross_config.modified_recipe is not modified_recipe: continue - tasks_dict['tasks'].append( - { - "label": gdb_cross_config.id_pretty, + for gdbserver_mode in gdb_cross_config.gdbserver_modes(): + new_task = { + "label": gdb_cross_config.id_pretty(gdbserver_mode), "type": "shell", "isBackground": True, - "dependsOn": [ - dt_build_deploy_label - ], - "command": gdb_cross_config.gdbserver_script, + "command": gdb_cross_config.gdbserver_script(gdbserver_mode), "problemMatcher": [ { "pattern": [ @@ -436,7 +480,12 @@ class IdeVSCode(IdeBase): } } ] - }) + } + if gdbserver_mode != GdbServerModes.ATTACH: + new_task['dependsOn'] = [ + dt_build_deploy_label + ] + tasks_dict['tasks'].append(new_task) tasks_file = 'tasks.json' IdeBase.update_json_file( self.dot_code_dir(modified_recipe), tasks_file, tasks_dict) @@ -453,7 +502,7 @@ class IdeVSCode(IdeBase): self.vscode_c_cpp_properties(modified_recipe) if args.target: self.initialize_gdb_cross_configs( - image_recipe, modified_recipe, gdb_cross_config_class=GdbCrossConfigVSCode) + image_recipe, modified_recipe, GdbCrossConfigVSCode) self.vscode_launch(modified_recipe) self.vscode_tasks(args, modified_recipe) diff --git a/scripts/lib/devtool/ide_plugins/ide_none.py b/scripts/lib/devtool/ide_plugins/ide_none.py index f106c5a026..04677aba9d 100644 --- a/scripts/lib/devtool/ide_plugins/ide_none.py +++ b/scripts/lib/devtool/ide_plugins/ide_none.py @@ -7,11 +7,18 @@ import os import logging -from devtool.ide_plugins import IdeBase, GdbCrossConfig +from devtool.ide_plugins import IdeBase, GdbCrossConfig, GdbServerModes logger = logging.getLogger('devtool') +class GdbCrossConfigNone(GdbCrossConfig): + def __init__(self, image_recipe, modified_recipe, binary, + gdbserver_default_mode=GdbServerModes.MULTI): + super().__init__(image_recipe, modified_recipe, binary, + gdbserver_default_mode) + + class IdeNone(IdeBase): """Generate some generic helpers for other IDEs @@ -44,9 +51,10 @@ class IdeNone(IdeBase): script_path = modified_recipe.gen_install_deploy_script(args) logger.info("Created: %s" % script_path) - self.initialize_gdb_cross_configs(image_recipe, modified_recipe) + self.initialize_gdb_cross_configs( + image_recipe, modified_recipe, GdbCrossConfigNone) - IdeBase.gen_oe_scrtips_sym_link(modified_recipe) + IdeBase.gen_oe_scripts_sym_link(modified_recipe) def register_ide_plugin(ide_plugins): diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py index 9df88454c7..2af4dca256 100755 --- a/scripts/lib/devtool/ide_sdk.py +++ b/scripts/lib/devtool/ide_sdk.py @@ -46,17 +46,17 @@ class TargetDevice: """SSH remote login parameters""" def __init__(self, args): - self.extraoptions = '' + self.extraoptions = [] if args.no_host_check: - self.extraoptions += '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' + self.extraoptions += ['-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no'] self.ssh_sshexec = 'ssh' if args.ssh_exec: self.ssh_sshexec = args.ssh_exec self.ssh_port = '' if args.port: - self.ssh_port = "-p %s" % args.port + self.ssh_port = ['-p', args.port] if args.key: - self.extraoptions += ' -i %s' % args.key + self.extraoptions += ['-i', args.key] self.target = args.target target_sp = args.target.split('@') @@ -265,6 +265,112 @@ class RecipeNotModified: self.name = name self.bootstrap_tasks = [name + ':do_populate_sysroot'] +class ExecutableBinary: + """Represent an installed executable binary of a modified recipe""" + + def __init__(self, image_dir_d, binary_path, + systemd_services, init_scripts): + self.image_dir_d = image_dir_d + self.binary_path = binary_path + self.init_script = None + self.systemd_service = None + + self._init_service_for_binary(systemd_services) + self._init_init_script_for_binary(init_scripts) + + def _init_service_for_binary(self, systemd_services): + """Find systemd service file that handles this binary""" + service_dirs = [ + 'etc/systemd/system', + 'lib/systemd/system', + 'usr/lib/systemd/system' + ] + for _, services in systemd_services.items(): + for service in services: + for service_dir in service_dirs: + service_path = os.path.join(self.image_dir_d, service_dir, service) + if os.path.exists(service_path): + try: + with open(service_path, 'r') as f: + for line in f: + if line.strip().startswith('ExecStart='): + exec_start = line.strip()[10:].strip() # Remove 'ExecStart=' + # Remove any leading modifiers like '-' or '@' + exec_start = exec_start.lstrip('-@') + # Get the first word (the executable path) + exec_binary = exec_start.split()[0] if exec_start.split() else '' + if exec_binary == self.binary_path or exec_binary.endswith('/' + self.binary_path.lstrip('/')): + logger.debug("Found systemd service for binary %s: %s" % (self.binary_path, service)) + self.systemd_service = service + except (IOError, OSError): + continue + + def _init_init_script_for_binary(self, init_scripts): + """Find SysV init script that handles this binary""" + init_dirs = [ + 'etc/init.d', + 'etc/rc.d/init.d' + ] + for _, init_scripts in init_scripts.items(): + for init_script in init_scripts: + for init_dir in init_dirs: + init_path = os.path.join(self.image_dir_d, init_dir, init_script) + if os.path.exists(init_path): + init_script_path = os.path.join("/", init_dir, init_script) + binary_name = os.path.basename(self.binary_path) + # if the init script file name is equal to the binary file name, return it directly + if os.path.basename(init_script) == binary_name: + logger.debug("Found SysV init script for binary %s: %s" % (self.binary_path, init_script_path)) + self.init_script = init_script_path + # Otherwise check if the script containes a reference to the binary + try: + with open(init_path, 'r') as f: + content = f.read() + pattern = r'\b' + re.escape(binary_name) + r'\b' + if re.search(pattern, content): + logger.debug("Found SysV init script for binary %s: %s" % (self.binary_path, init_script_path)) + self.init_script = init_script_path + return + except (IOError, OSError): + continue + + @property + def binary_host_path(self): + """Get the absolute path of this binary on the host""" + return os.path.join(self.image_dir_d, self.binary_path.lstrip('/')) + + @property + def runs_as_service(self): + """Check if this binary is run by a service or init script""" + return self.systemd_service is not None or self.init_script is not None + + @property + def start_command(self): + """Get the command to start this binary""" + if self.systemd_service: + return "systemctl start %s" % self.systemd_service + if self.init_script: + return "%s start" % self.init_script + return None + + @property + def stop_command(self): + """Get the command to stop this binary""" + if self.systemd_service: + return "systemctl stop %s" % self.systemd_service + if self.init_script: + return "%s stop" % self.init_script + return None + + @property + def pid_command(self): + """Get the command to get the PID of this binary""" + if self.systemd_service: + return "systemctl show --property MainPID --value %s" % self.systemd_service + if self.init_script: + return "pidof %s" % os.path.basename(self.binary_path) + return None + class RecipeModified: """Handling of recipes in the workspace created by devtool modify""" @@ -308,6 +414,9 @@ class RecipeModified: self.target_dbgsrc_dir = None self.topdir = None self.workdir = None + # Service management + self.systemd_services = {} + self.init_scripts = {} # replicate bitbake build environment self.exported_vars = None self.cmd_compile = None @@ -324,6 +433,9 @@ class RecipeModified: self.extra_oemeson = None self.meson_cross_file = None + # Populated after bitbake built all the recipes + self._installed_binaries = None + def initialize(self, config, workspace, tinfoil): recipe_d = parse_recipe( config, tinfoil, self.name, appends=True, filter_workspace=False) @@ -378,6 +490,8 @@ class RecipeModified: self.workdir = os.path.realpath(recipe_d.getVar('WORKDIR')) self.__init_exported_variables(recipe_d) + self.__init_systemd_services(recipe_d) + self.__init_init_scripts(recipe_d) if bb.data.inherits_class('cmake', recipe_d): self.oecmake_generator = recipe_d.getVar('OECMAKE_GENERATOR') @@ -498,6 +612,41 @@ class RecipeModified: self.exported_vars = exported_vars + def __init_systemd_services(self, d): + """Find all systemd service files for the recipe.""" + services = {} + if bb.data.inherits_class('systemd', d): + systemd_packages = d.getVar('SYSTEMD_PACKAGES') + if systemd_packages: + for package in systemd_packages.split(): + services[package] = d.getVar('SYSTEMD_SERVICE:' + package).split() + self.systemd_services = services + + def __init_init_scripts(self, d): + """Find all SysV init scripts for the recipe.""" + init_scripts = {} + if bb.data.inherits_class('update-rc.d', d): + script_packages = d.getVar('INITSCRIPT_PACKAGES') + if script_packages: + for package in script_packages.split(): + initscript_name = d.getVar('INITSCRIPT_NAME:' + package) + if initscript_name: + # Handle both single script and multiple scripts + scripts = initscript_name.split() + if scripts: + init_scripts[package] = scripts + else: + # If INITSCRIPT_PACKAGES is not set, check for default INITSCRIPT_NAME + initscript_name = d.getVar('INITSCRIPT_NAME') + if initscript_name: + scripts = initscript_name.split() + if scripts: + # Use PN as the default package name when INITSCRIPT_PACKAGES is not set + pn = d.getVar('PN') + if pn: + init_scripts[pn] = scripts + self.init_scripts = init_scripts + def __init_cmake_preset_cache(self, d): """Get the arguments passed to cmake @@ -662,9 +811,12 @@ class RecipeModified: return True return False - def find_installed_binaries(self): + @property + def installed_binaries(self): """find all executable elf files in the image directory""" - binaries = [] + if self._installed_binaries: + return self._installed_binaries + binaries = {} d_len = len(self.d) re_so = re.compile(r'.*\.so[.0-9]*$') for root, _, files in os.walk(self.d, followlinks=False): @@ -675,8 +827,12 @@ class RecipeModified: continue abs_name = os.path.join(root, file) if os.access(abs_name, os.X_OK) and RecipeModified.is_elf_file(abs_name): - binaries.append(abs_name[d_len:]) - return sorted(binaries) + binary_path = abs_name[d_len:] + binary = ExecutableBinary(self.d, binary_path, + self.systemd_services, self.init_scripts) + binaries[binary_path] = binary + self._installed_binaries = dict(sorted(binaries.items())) + return self._installed_binaries def gen_deploy_target_script(self, args): """Generate a script which does what devtool deploy-target does From patchwork Wed Dec 31 11:46:36 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77825 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 60B25EE642F for ; Wed, 31 Dec 2025 11:47:51 +0000 (UTC) Received: from mta-65-226.siemens.flowmailer.net (mta-65-226.siemens.flowmailer.net [185.136.65.226]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.83906.1767181666334202040 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=X4fKfnWa; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.226, mailfrom: fm-1329275-2025123111474346fde2b3930002073d-mqrhb_@rts-flowmailer.siemens.com) Received: by mta-65-226.siemens.flowmailer.net with ESMTPSA id 2025123111474346fde2b3930002073d for ; Wed, 31 Dec 2025 12:47:43 +0100 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=4YkhFkhSFeT/siTvy3IM3+piO228DeZOkUt+Xd1BCIA=; b=X4fKfnWa9aY3UL0XyZv7RwnxG4zZzIYVqkkWPhQbXJvc8Bqq9Xv86YwgxV+PW/sl5hEYu2 3R+3ljh+Ml2uumPP+7uLS+2/EDr8UdH3WNK2/6/NQ9HFOUlpJAJfeucPXXVL0Kii/E6hCe6J 5NF9GdD2w7qvfH9tsAG3ToAM535DkQ24gp/PMCK4Kou1/M6Ucqo8RS7SPJ/mriTv4rfVN3Og HfpFGqZWqvtktwwn250S0rS8U1jucsfVPvnz7oDGvAPXgueHxt9qnhb1Am7G8WiQduJBYtvC tcNXqOHGeRPdQXZbJWuM/XHAAlOeEZSaiJy5EhsfCOL6T4/0Z188tZ8Q==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 06/14] devtool: ide-sdk: move code to ide_none Date: Wed, 31 Dec 2025 12:46:36 +0100 Message-ID: <20251231114718.4031606-7-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228732 From: Adrian Freihofer Move code which is used by the ide_none plugin from the base class to the ide_none plugin. This is just a refactoring, no functional change. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_plugins/__init__.py | 115 +------------------- scripts/lib/devtool/ide_plugins/ide_none.py | 112 +++++++++++++++++++ 2 files changed, 116 insertions(+), 111 deletions(-) diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py index 70f47d6e68..80dfc1e235 100644 --- a/scripts/lib/devtool/ide_plugins/__init__.py +++ b/scripts/lib/devtool/ide_plugins/__init__.py @@ -9,10 +9,8 @@ import errno import json import logging import os -import stat from enum import Enum, auto from devtool import DevtoolError -from bb.utils import mkdirhier logger = logging.getLogger('devtool') @@ -130,19 +128,6 @@ class GdbCrossConfig: raise DevtoolError("Unsupported gdbserver mode: %s" % gdbserver_mode) return "\"/bin/sh -c '" + gdbserver_cmd_start + "'\"" - def _target_gdbserver_stop_cmd(self, gdbserver_mode): - """Kill a gdbserver process""" - # This is the usual behavior: gdbserver is stopped on demand - if gdbserver_mode == GdbServerModes.MULTI: - gdbserver_cmd_stop = "test -f %s && kill \\$(cat %s);" % ( - self.gdbserver_pid_file(gdbserver_mode), self.gdbserver_pid_file(gdbserver_mode)) - gdbserver_cmd_stop += " rm -rf %s" % self.gdbserver_tmp_dir(gdbserver_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" - return "\"/bin/sh -c '" + gdbserver_cmd_stop + "'\"" - def _target_gdbserver_kill_cmd(self): """Get the ssh command to kill gdbserver on the target device""" return "\"kill \\$(pgrep -o -f 'gdbserver --attach :%s') 2>/dev/null || true\"" % self.gdbserver_port @@ -157,91 +142,6 @@ class GdbCrossConfig: ssh_args.append(self.gdb_cross.target_device.target) return ssh_args - def _gen_gdbserver_start_script(self, gdbserver_mode=None): - """Generate a shell script starting the gdbserver on the remote device via ssh""" - if gdbserver_mode is None: - gdbserver_mode = self.gdbserver_default_mode - gdbserver_cmd_start = self._target_gdbserver_start_cmd(gdbserver_mode) - gdbserver_cmd_stop = self._target_gdbserver_stop_cmd(gdbserver_mode) - remote_ssh = "%s %s" % (self.gdb_cross.target_device.ssh_sshexec, - " ".join(self._target_ssh_gdbserver_args())) - gdbserver_cmd = ['#!/bin/sh'] - gdbserver_cmd.append('if [ "$1" = "stop" ]; then') - gdbserver_cmd.append(' shift') - gdbserver_cmd.append(" %s %s" % (remote_ssh, gdbserver_cmd_stop)) - gdbserver_cmd.append('else') - gdbserver_cmd.append(" %s %s" % (remote_ssh, gdbserver_cmd_start)) - gdbserver_cmd.append('fi') - GdbCrossConfig.write_file(self.gdbserver_script(gdbserver_mode), gdbserver_cmd, True) - - def _gen_gdbinit_config(self, gdbserver_mode=None): - """Generate a gdbinit file for this binary and the corresponding gdbserver configuration""" - if gdbserver_mode is None: - gdbserver_mode = self.gdbserver_default_mode - gdbinit_lines = ['# This file is generated by devtool ide-sdk'] - if gdbserver_mode == GdbServerModes.MULTI: - target_help = '# gdbserver --multi :%d' % self.gdbserver_port - remote_cmd = 'target extended-remote' - else: - target_help = '# gdbserver :%d %s' % ( - self.gdbserver_port, self.binary) - remote_cmd = 'target remote' - gdbinit_lines.append('# On the remote target:') - gdbinit_lines.append(target_help) - gdbinit_lines.append('# On the build machine:') - gdbinit_lines.append('# cd ' + self.modified_recipe.real_srctree) - gdbinit_lines.append( - '# ' + self.gdb_cross.gdb + ' -ix ' + self.gdbinit) - gdbinit_lines.append('set sysroot ' + self.modified_recipe.d) - - if self.image_recipe.rootfs_dbg: - gdbinit_lines.append( - 'set solib-search-path "' + self.modified_recipe.solib_search_path_str(self.image_recipe) + '"') - - gdbinit_lines.append('set substitute-path "/usr/include" "' + - os.path.join(self.modified_recipe.recipe_sysroot, 'usr', 'include') + '"') - if self.image_recipe.rootfs_dbg: - # First: Search for sources of this recipe in the workspace folder - if self.modified_recipe.pn in self.modified_recipe.target_dbgsrc_dir: - gdbinit_lines.append('set substitute-path "%s" "%s"' % - (self.modified_recipe.target_dbgsrc_dir, self.modified_recipe.real_srctree)) - else: - logger.error( - "TARGET_DBGSRC_DIR must contain the recipe name PN.") - # Second: Search for sources of other recipes in the rootfs-dbg - if self.modified_recipe.target_dbgsrc_dir.startswith("/usr/src/debug"): - gdbinit_lines.append('set substitute-path "/usr/src/debug" "%s"' % os.path.join( - self.image_recipe.rootfs_dbg, "usr", "src", "debug")) - else: - logger.error( - "TARGET_DBGSRC_DIR must start with /usr/src/debug.") - else: - logger.warning( - "Cannot setup debug symbols configuration for GDB. IMAGE_GEN_DEBUGFS is not enabled.") - # Disable debuginfod for now, the IDE configuration uses rootfs-dbg from the image workdir. - gdbinit_lines.append('set debuginfod enabled off') - gdbinit_lines.append( - '%s %s:%d' % (remote_cmd, self.gdb_cross.host, self.gdbserver_port)) - gdbinit_lines.append('set remote exec-file ' + self.binary.binary_path) - gdbinit_lines.append('run ' + self.binary.binary_path) - - GdbCrossConfig.write_file(self.gdbinit, gdbinit_lines) - - def _gen_gdb_start_script(self): - """Generate a script starting GDB with the corresponding gdbinit configuration.""" - cmd_lines = ['#!/bin/sh'] - cmd_lines.append('cd ' + self.modified_recipe.real_srctree) - cmd_lines.append(self.gdb_cross.gdb + ' -ix ' + - self.gdbinit + ' "$@"') - GdbCrossConfig.write_file(self.gdb_script, cmd_lines, True) - - def initialize(self): - self._gen_gdbserver_start_script() - if self.binary.runs_as_service and self.gdbserver_default_mode != GdbServerModes.ATTACH: - self._gen_gdbserver_start_script(GdbServerModes.ATTACH) - self._gen_gdbinit_config() - self._gen_gdb_start_script() - def gdbserver_modes(self): """Get the list of gdbserver modes for which scripts are generated""" modes = [self.gdbserver_default_mode] @@ -249,17 +149,10 @@ class GdbCrossConfig: modes.append(GdbServerModes.ATTACH) return modes - @staticmethod - def write_file(script_file, cmd_lines, executable=False): - script_dir = os.path.dirname(script_file) - mkdirhier(script_dir) - with open(script_file, 'w') as script_f: - script_f.write(os.linesep.join(cmd_lines)) - script_f.write(os.linesep) - if executable: - st = os.stat(script_file) - os.chmod(script_file, st.st_mode | stat.S_IEXEC) - logger.info("Created: %s" % script_file) + def initialize(self): + """Interface function to initialize the gdb config generation""" + pass + class IdeBase: diff --git a/scripts/lib/devtool/ide_plugins/ide_none.py b/scripts/lib/devtool/ide_plugins/ide_none.py index 04677aba9d..8284c4e0a5 100644 --- a/scripts/lib/devtool/ide_plugins/ide_none.py +++ b/scripts/lib/devtool/ide_plugins/ide_none.py @@ -7,6 +7,8 @@ import os import logging +import stat +from bb.utils import mkdirhier from devtool.ide_plugins import IdeBase, GdbCrossConfig, GdbServerModes logger = logging.getLogger('devtool') @@ -18,6 +20,116 @@ class GdbCrossConfigNone(GdbCrossConfig): super().__init__(image_recipe, modified_recipe, binary, gdbserver_default_mode) + def _target_gdbserver_stop_cmd(self, gdbserver_mode): + """Kill a gdbserver process""" + # This is the usual behavior: gdbserver is stopped on demand + if gdbserver_mode == GdbServerModes.MULTI: + gdbserver_cmd_stop = "test -f %s && kill \\$(cat %s);" % ( + self.gdbserver_pid_file(gdbserver_mode), self.gdbserver_pid_file(gdbserver_mode)) + gdbserver_cmd_stop += " rm -rf %s" % self.gdbserver_tmp_dir(gdbserver_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" + return "\"/bin/sh -c '" + gdbserver_cmd_stop + "'\"" + + def _gen_gdbserver_start_script(self, gdbserver_mode=None): + """Generate a shell script starting the gdbserver on the remote device via ssh""" + if gdbserver_mode is None: + gdbserver_mode = self.gdbserver_default_mode + gdbserver_cmd_start = self._target_gdbserver_start_cmd(gdbserver_mode) + gdbserver_cmd_stop = self._target_gdbserver_stop_cmd(gdbserver_mode) + remote_ssh = "%s %s" % (self.gdb_cross.target_device.ssh_sshexec, + " ".join(self._target_ssh_gdbserver_args())) + gdbserver_cmd = ['#!/bin/sh'] + gdbserver_cmd.append('if [ "$1" = "stop" ]; then') + gdbserver_cmd.append(' shift') + gdbserver_cmd.append(" %s %s" % (remote_ssh, gdbserver_cmd_stop)) + gdbserver_cmd.append('else') + gdbserver_cmd.append(" %s %s" % (remote_ssh, gdbserver_cmd_start)) + gdbserver_cmd.append('fi') + GdbCrossConfigNone.write_file(self.gdbserver_script(gdbserver_mode), gdbserver_cmd, True) + + def _gen_gdbinit_config(self, gdbserver_mode=None): + """Generate a gdbinit file for this binary and the corresponding gdbserver configuration""" + if gdbserver_mode is None: + gdbserver_mode = self.gdbserver_default_mode + gdbinit_lines = ['# This file is generated by devtool ide-sdk'] + if gdbserver_mode == GdbServerModes.MULTI: + target_help = '# gdbserver --multi :%d' % self.gdbserver_port + remote_cmd = 'target extended-remote' + else: + target_help = '# gdbserver :%d %s' % ( + self.gdbserver_port, self.binary) + remote_cmd = 'target remote' + gdbinit_lines.append('# On the remote target:') + gdbinit_lines.append(target_help) + gdbinit_lines.append('# On the build machine:') + gdbinit_lines.append('# cd ' + self.modified_recipe.real_srctree) + gdbinit_lines.append( + '# ' + self.gdb_cross.gdb + ' -ix ' + self.gdbinit) + gdbinit_lines.append('set sysroot ' + self.modified_recipe.d) + + if self.image_recipe.rootfs_dbg: + gdbinit_lines.append( + 'set solib-search-path "' + self.modified_recipe.solib_search_path_str(self.image_recipe) + '"') + + gdbinit_lines.append('set substitute-path "/usr/include" "' + + os.path.join(self.modified_recipe.recipe_sysroot, 'usr', 'include') + '"') + if self.image_recipe.rootfs_dbg: + # First: Search for sources of this recipe in the workspace folder + if self.modified_recipe.pn in self.modified_recipe.target_dbgsrc_dir: + gdbinit_lines.append('set substitute-path "%s" "%s"' % + (self.modified_recipe.target_dbgsrc_dir, self.modified_recipe.real_srctree)) + else: + logger.error( + "TARGET_DBGSRC_DIR must contain the recipe name PN.") + # Second: Search for sources of other recipes in the rootfs-dbg + if self.modified_recipe.target_dbgsrc_dir.startswith("/usr/src/debug"): + gdbinit_lines.append('set substitute-path "/usr/src/debug" "%s"' % os.path.join( + self.image_recipe.rootfs_dbg, "usr", "src", "debug")) + else: + logger.error( + "TARGET_DBGSRC_DIR must start with /usr/src/debug.") + else: + logger.warning( + "Cannot setup debug symbols configuration for GDB. IMAGE_GEN_DEBUGFS is not enabled.") + # Disable debuginfod for now, the IDE configuration uses rootfs-dbg from the image workdir. + gdbinit_lines.append('set debuginfod enabled off') + gdbinit_lines.append( + '%s %s:%d' % (remote_cmd, self.gdb_cross.host, self.gdbserver_port)) + gdbinit_lines.append('set remote exec-file ' + self.binary.binary_path) + gdbinit_lines.append('run ' + self.binary.binary_path) + + GdbCrossConfigNone.write_file(self.gdbinit, gdbinit_lines) + + def _gen_gdb_start_script(self): + """Generate a script starting GDB with the corresponding gdbinit configuration.""" + cmd_lines = ['#!/bin/sh'] + cmd_lines.append('cd ' + self.modified_recipe.real_srctree) + cmd_lines.append(self.gdb_cross.gdb + ' -ix ' + + self.gdbinit + ' "$@"') + GdbCrossConfigNone.write_file(self.gdb_script, cmd_lines, True) + + def initialize(self): + self._gen_gdbserver_start_script() + if self.binary.runs_as_service and self.gdbserver_default_mode != GdbServerModes.ATTACH: + self._gen_gdbserver_start_script(GdbServerModes.ATTACH) + self._gen_gdbinit_config() + self._gen_gdb_start_script() + + @staticmethod + def write_file(script_file, cmd_lines, executable=False): + script_dir = os.path.dirname(script_file) + mkdirhier(script_dir) + with open(script_file, 'w') as script_f: + script_f.write(os.linesep.join(cmd_lines)) + script_f.write(os.linesep) + if executable: + st = os.stat(script_file) + os.chmod(script_file, st.st_mode | stat.S_IEXEC) + logger.info("Created: %s" % script_file) + class IdeNone(IdeBase): """Generate some generic helpers for other IDEs From patchwork Wed Dec 31 11:46:37 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77826 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 72BD4EE6432 for ; Wed, 31 Dec 2025 11:47:51 +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.83933.1767181666334343815 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=b1hmTM21; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.228, mailfrom: fm-1329275-20251231114743ea052dea6d00020770-apc3fi@rts-flowmailer.siemens.com) Received: by mta-64-228.siemens.flowmailer.net with ESMTPSA id 20251231114743ea052dea6d00020770 for ; Wed, 31 Dec 2025 12:47:43 +0100 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=zsG78H0SKqBOmxoDV4BYv2WG8ibBz5AkAdNOwu9Mn/E=; b=b1hmTM21sQHvTcHAOOCjxoHGIB4zWb7/l14iO5g/ebzptbszEKac0Pk+LBwbjNek5RSDSu m1Yt6V5z7g7kWE8YBOPk69mg/ky94w9e+2QhWnIGA1bypf9grpZdxVONW0KkNCSu9w2T3udF zgHhiSUpJYzQm9DkDq3UZc+vmqkv+GLCWRNyzE3+AV5lT1g6CHBcohCNsA0HwLOsHKFHkEne m/7gkmDQSrT9bwPpkxXtFu+wPNXBuCVOt47IGwtpMCs/TOXBSY6mEYVWBcRtyFvJzdooabqM nFbjve9mQBpBpZsc77IwQIADC4n6A8UVDO4aPYZh8LURyvlpeMwknfUg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 07/14] devtool: ide-sdk: make install_and_deploy script pass target arg Date: Wed, 31 Dec 2025 12:46:37 +0100 Message-ID: <20251231114718.4031606-8-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228743 From: Adrian Freihofer Previously, the target was hardcoded in the install_and_deploy script, limiting flexibility. This change allows passing the target as a command-line argument, enabling IDEs to configure the target dynamically rather than only at IDE configuration generation time. This is a first step towards making the target configurable from the IDE. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_plugins/ide_code.py | 4 ++++ scripts/lib/devtool/ide_sdk.py | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py index 67bd341347..3f8c1a44a3 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -306,6 +306,10 @@ class IdeVSCode(IdeBase): "label": install_task_name, "type": "shell", "command": run_install_deploy, + "args": [ + "--target", + args.target + ], "problemMatcher": [] } ] diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py index 2af4dca256..96d60ad4f1 100755 --- a/scripts/lib/devtool/ide_sdk.py +++ b/scripts/lib/devtool/ide_sdk.py @@ -859,6 +859,9 @@ class RecipeModified: cmd_lines.append(' for key in my_dict:') cmd_lines.append(' setattr(self, key, my_dict[key])') cmd_lines.append('filtered_args = Dict2Class(filtered_args_dict)') + cmd_lines.append('if len(sys.argv) > 2:') + cmd_lines.append(' if sys.argv[1] == "-t" or sys.argv[1] == "--target":') + cmd_lines.append(' setattr(filtered_args, "target", sys.argv[2])') cmd_lines.append( 'setattr(filtered_args, "recipename", "%s")' % self.bpn) cmd_lines.append('deploy_no_d("%s", "%s", "%s", "%s", "%s", "%s", %d, "%s", "%s", filtered_args)' % @@ -884,7 +887,7 @@ class RecipeModified: 'bitbake %s -c install --force || { echo "bitbake %s -c install --force failed"; exit 1; }' % (self.bpn, self.bpn)) # Self contained devtool deploy-target - cmd_lines.append(self.gen_deploy_target_script(args)) + cmd_lines.append(self.gen_deploy_target_script(args) + ' "$@"') return self.write_script(cmd_lines, 'install_and_deploy') From patchwork Wed Dec 31 11:46:38 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77824 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 5FBE1EE642E for ; Wed, 31 Dec 2025 11:47:51 +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.msgproc02-g2.83931.1767181666334053972 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=VetvnWZu; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.225, mailfrom: fm-1329275-20251231114743afc2315d1200020790-3608tq@rts-flowmailer.siemens.com) Received: by mta-65-225.siemens.flowmailer.net with ESMTPSA id 20251231114743afc2315d1200020790 for ; Wed, 31 Dec 2025 12:47:44 +0100 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=ytwbJK7YaFWEJvh6YC3Ry2SuqU/EV8WpuRR1qpAFcPA=; b=VetvnWZuTzzPSt20GtT+Ijrv02Jhx1HnIWnYyS8A/mRE/Yn7EkfTUsOrf9uGoN2C2MThS+ DoAGldgU4Zu1sYhiBRHlW/h4rBF6U1MtowhsKP0NYFZXK9wq98sIOebJvhtRl4wNiphz9jh7 84yzWTwYeQUg6fgugWPho1oNsPZuDUEThCvchMFMkGmY6VeQIFGEO3lBUpfoMow5Km+cUnkb XpZGBc3OkEKfovLPppWnMCAGc67A29IErxceqHl/C+H29ZxTn5yp/qe+MtMivrepWQO25BIB E2Y9VWWcYIoIvOAIzecx44lCKeS1tWUrfSr7dp4rgph6NNO1bFFJd5lw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 08/14] devtool: ide-sdk: vscode replace scripts Date: Wed, 31 Dec 2025 12:46:38 +0100 Message-ID: <20251231114718.4031606-9-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228745 From: Adrian Freihofer Write the ssh command to start gdbserver on target directly into the tasks.json. This avoids the need to create one more script file on the host. It also simplifies manual modifications of VSCode's standard tasks.json which is much more handy than modifying multiple proprietary scripts used to launch gdbserver. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_plugins/ide_code.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py index 3f8c1a44a3..05d513f160 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -20,6 +20,18 @@ class GdbCrossConfigVSCode(GdbCrossConfig): super().__init__(image_recipe, modified_recipe, binary, gdbserver_default_mode) + def target_ssh_gdbserver_start_args(self, gdbserver_mode=None): + """Get the ssh command arguments to start gdbserver on the target device + + returns something like: + ['-p', '2222', 'root@target', '"/bin/sh -c \'/usr/bin/gdbserver --once :1234 /usr/bin/cmake-example\'"'] + """ + if gdbserver_mode is None: + gdbserver_mode = self.gdbserver_default_mode + return self._target_ssh_gdbserver_args() + [ + self._target_gdbserver_start_cmd(gdbserver_mode) + ] + def target_ssh_gdbserver_kill_args(self): """Get the ssh command arguments to kill gdbserver on the target device @@ -31,7 +43,7 @@ class GdbCrossConfigVSCode(GdbCrossConfig): ] def initialize(self): - self._gen_gdbserver_start_script() + pass class IdeVSCode(IdeBase): @@ -322,7 +334,8 @@ class IdeVSCode(IdeBase): "label": gdb_cross_config.id_pretty_mode(gdbserver_mode), "type": "shell", "isBackground": True, - "command": gdb_cross_config.gdbserver_script(gdbserver_mode), + "command": gdb_cross_config.gdb_cross.target_device.ssh_sshexec, + "args": gdb_cross_config.target_ssh_gdbserver_start_args(gdbserver_mode), "problemMatcher": [ { "pattern": [ @@ -466,7 +479,8 @@ class IdeVSCode(IdeBase): "label": gdb_cross_config.id_pretty(gdbserver_mode), "type": "shell", "isBackground": True, - "command": gdb_cross_config.gdbserver_script(gdbserver_mode), + "command": gdb_cross_config.gdb_cross.target_device.ssh_sshexec, + "args": gdb_cross_config.target_ssh_gdbserver_start_args(gdbserver_mode), "problemMatcher": [ { "pattern": [ From patchwork Wed Dec 31 11:46:39 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77830 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 61BF0EE642B for ; Wed, 31 Dec 2025 11:47:51 +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.msgproc02-g2.83934.1767181666334449266 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=KEyeIcWK; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.225, mailfrom: fm-1329275-20251231114744c2127513c10002077b-ucyz2h@rts-flowmailer.siemens.com) Received: by mta-65-225.siemens.flowmailer.net with ESMTPSA id 20251231114744c2127513c10002077b for ; Wed, 31 Dec 2025 12:47:44 +0100 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=Iw0H6MeQQ5UK9rTtgl0uAB8+WIIx9WHV991TIS6Xrp4=; b=KEyeIcWKqtpLBn8sk9KWgmzDtCsqPOED2nc7b4fm69Vi2J18wGXTPmX3q7yq5cFGiI8/Mo ZE+lHbr0MWplQtZMk/DZimx8dkCtj3eS4pCMc2OkiWhD7ym6TRXqkTIB6D5sbA7HXou/BdaX BiBHiXkNY+5bL7oNnljGMvLo8rORMrRQd3OIZ5QpuWxY8AA1gxUv4uYl7VI+oWtBlRnD6wNm Ohp/Cv94v7Rh1nlnfQ+U8dXnIRmREmvIV6g791F/PWSaSppnj6aoJnTdukFPWdjfsZmADtQ8 Sucl9fZFe61s0JNeTn3/UF56dL2qjZkyxoCGzOFyxv0E8aJPbbm5x/9w==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 09/14] oe-selftest: devtool ide-sdk cover vscode remote debugging Date: Wed, 31 Dec 2025 12:46:39 +0100 Message-ID: <20251231114718.4031606-10-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228746 From: Adrian Freihofer This adds more test coverage for devtool ide-sdk, with VSCode. The cmake test case has now a full remote debugging test on Qemu. The test checks the generated launch.json and tasks.json files, starts gdbserver and connects to it. The test verifies breakpoints, variables and source file listing. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 325 ++++++++++++++++++++++-- 1 file changed, 297 insertions(+), 28 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index b40323c109..df5c863a85 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -9,20 +9,23 @@ import os import re import shutil import tempfile +import time import glob import fnmatch import unittest import json import logging +import shlex from oeqa.selftest.case import OESelftestTestCase -from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer +from oeqa.utils.commands import runCmd, Command, 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 bb.utils import mkdirhier, edit_bblayers_conf oldmetapath = None + def setUpModule(): global templayerdir templayerdir = tempfile.mkdtemp(prefix='devtoolqa') @@ -2524,8 +2527,23 @@ class DevtoolUpgradeTests(DevtoolBase): runCmd("grep %s %s" % (modconfopt, codeconfigfile)) + +class RunCmdBackground: + """Context manager to manage a background subprocess""" + def __init__(self, command, output_log=None, **options): + self.cmd = Command(command, bg=True, output_log=output_log, **options) + + def __enter__(self): + self.cmd.run() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.cmd.stop() + + class DevtoolIdeSdkTests(DevtoolBase): + MAGIC_STRING_ORIG = "Magic: 123456789" + def setUp(self): super().setUp() self._cmd_logger = None @@ -2639,7 +2657,6 @@ class DevtoolIdeSdkTests(DevtoolBase): '%s script not found' % install_deploy_cmd) runCmd(install_deploy_cmd, output_log=self._cmd_logger) - MAGIC_STRING_ORIG = "Magic: 123456789" MAGIC_STRING_NEW = "Magic: 987654321" ptest_cmd = "ptest-runner " + recipe_name @@ -2652,7 +2669,7 @@ class DevtoolIdeSdkTests(DevtoolBase): status, output = qemu.run(example_exe) self.assertEqual(status, 0, msg="%s failed: %s" % (example_exe, output)) - self.assertIn(MAGIC_STRING_ORIG, output) + self.assertIn(DevtoolIdeSdkTests.MAGIC_STRING_ORIG, output) # Verify the unmodified ptests work status, output = qemu.run(ptest_cmd) @@ -2661,13 +2678,13 @@ class DevtoolIdeSdkTests(DevtoolBase): # Verify remote debugging works self._gdb_cross_debugging_multi( - qemu, recipe_name, example_exe, MAGIC_STRING_ORIG) + qemu, recipe_name, example_exe, DevtoolIdeSdkTests.MAGIC_STRING_ORIG) # Replace the Magic String in the code, compile and deploy to Qemu cpp_example_lib_hpp = os.path.join(tempdir, 'cpp-example-lib.hpp') with open(cpp_example_lib_hpp, 'r') as file: cpp_code = file.read() - cpp_code = cpp_code.replace(MAGIC_STRING_ORIG, MAGIC_STRING_NEW) + cpp_code = cpp_code.replace(DevtoolIdeSdkTests.MAGIC_STRING_ORIG, MAGIC_STRING_NEW) with open(cpp_example_lib_hpp, 'w') as file: file.write(cpp_code) runCmd(install_deploy_cmd, cwd=tempdir, output_log=self._cmd_logger) @@ -2676,7 +2693,7 @@ class DevtoolIdeSdkTests(DevtoolBase): status, output = qemu.run(example_exe) self.assertEqual(status, 0, msg="%s failed: %s" % (example_exe, output)) - self.assertNotIn(MAGIC_STRING_ORIG, output) + self.assertNotIn(DevtoolIdeSdkTests.MAGIC_STRING_ORIG, output) self.assertIn(MAGIC_STRING_NEW, output) # Verify the modified example ptests work @@ -2701,6 +2718,24 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertEqual(r.status, 0) self.assertIn("GNU gdb", r.output) + def _gdb_debug_cpp_example(self, magic_string, gdb_start_cmd="run"): + """Get a series of gdb commands to debug the cpp-example-lib example""" + gdb_batch_cmd = " -ex 'break main' -ex '%s'" % gdb_start_cmd + gdb_batch_cmd += " -ex 'break CppExample::print_json()' -ex 'continue'" + gdb_batch_cmd += " -ex 'print CppExample::test_string.compare(\"cpp-example-lib %s\")'" % magic_string + gdb_batch_cmd += " -ex 'print CppExample::test_string.compare(\"cpp-example-lib %saaa\")'" % magic_string + gdb_batch_cmd += " -ex 'list cpp-example-lib.hpp:14,14'" + gdb_batch_cmd += " -ex 'continue'" + return gdb_batch_cmd + + def _gdb_debug_cpp_example_check(self, gdb_output, magic_string): + self.assertIn("Breakpoint 1, main", gdb_output) + self.assertIn("$1 = 0", gdb_output) # test.string.compare equal + self.assertIn("$2 = -3", gdb_output) # test.string.compare longer + self.assertIn( + 'inline static const std::string test_string = "cpp-example-lib %s";' % magic_string, gdb_output) + self.assertIn("exited normally", gdb_output) + def _gdb_cross_debugging_multi(self, qemu, recipe_name, example_exe, magic_string): """Verify gdb-cross is working @@ -2743,24 +2778,12 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertIn("1234", r.output) # Test remote debugging works - gdb_batch_cmd = " --batch -ex 'break main' -ex 'run'" - gdb_batch_cmd += " -ex 'break CppExample::print_json()' -ex 'continue'" - gdb_batch_cmd += " -ex 'print CppExample::test_string.compare(\"cpp-example-lib %s\")'" % magic_string - gdb_batch_cmd += " -ex 'print CppExample::test_string.compare(\"cpp-example-lib %saaa\")'" % magic_string - gdb_batch_cmd += " -ex 'list cpp-example-lib.hpp:14,14'" - gdb_batch_cmd += " -ex 'continue'" - return gdb_batch_cmd - + gdb_batch_cmd = " --batch " + self._gdb_debug_cpp_example(magic_string) r = runCmd(gdb_script + gdb_batch_cmd, output_log=self._cmd_logger) self.logger.debug("%s %s returned: %s", gdb_script, gdb_batch_cmd, r.output) self.assertEqual(r.status, 0) - self.assertIn("Breakpoint 1, main", r.output) - self.assertIn("$1 = 0", r.output) # test.string.compare equal - self.assertIn("$2 = -3", r.output) # test.string.compare longer - self.assertIn( - 'inline static const std::string test_string = "cpp-example-lib %s";' % magic_string, r.output) - self.assertIn("exited normally", r.output) + self._gdb_debug_cpp_example_check(r.output, magic_string=magic_string) # Stop the gdbserver r = runCmd(gdbserver_script + ' stop', output_log=self._cmd_logger) @@ -2861,6 +2884,7 @@ class DevtoolIdeSdkTests(DevtoolBase): bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@%s -c --ide=none' % ( recipe_name, testimage, qemu.ip) runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger) + self._gdb_cross() self._verify_cmake_preset(tempdir) self._devtool_ide_sdk_qemu(tempdir, qemu, recipe_name, example_exe) @@ -2892,6 +2916,7 @@ class DevtoolIdeSdkTests(DevtoolBase): bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@%s -c --ide=none' % ( recipe_name, testimage, qemu.ip) runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger) + self._gdb_cross() self._devtool_ide_sdk_qemu(tempdir, qemu, recipe_name, example_exe) @@ -2903,22 +2928,266 @@ class DevtoolIdeSdkTests(DevtoolBase): # after the install and deploy scripts updated the file self._verify_conf_file(qemu, conf_file, example_user_group, example_user_group) + 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') + self.assertTrue(os.path.exists(launch_json_path), "launch.json file should exist") + + with open(launch_json_path) as launch_j: + launch_d = json.load(launch_j) + + self.assertIn("configurations", launch_d) + configurations = launch_d["configurations"] + self.assertEqual(len(configurations), 3, "Should have exactly three debug configurations") + + # Track configurations found + once_configs = [] + attach_configs = [] + + for config in configurations: + # Verify required fields exist + required_fields = ["name", "type", "request", "program", "cwd", "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") + + # Verify program path is absolute and exists conceptually + program = config["program"] + self.assertTrue(program.startswith("/"), f"Configuration '{config['name']}' program path should be absolute: {program}") + self.assertIn("/image/usr/bin/", program, f"Configuration '{config['name']}' program should be in image/usr/bin") + + # Verify debugger path + debugger_path = config["miDebuggerPath"] + self.assertTrue(debugger_path.endswith("-gdb"), f"Configuration '{config['name']}' debugger should end with -gdb: {debugger_path}") + self.assertIn("/recipe-sysroot-native/usr/bin/", debugger_path, f"Configuration '{config['name']}' debugger should be in sysroot-native") + + # 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}") + + # Verify additional SO lib search path exists and contains debug paths + so_paths = config.get("additionalSOLibSearchPath", []) + self.assertIn("/.debug", so_paths, f"Configuration '{config['name']}' should include debug symbol paths") + self.assertIn("/rootfs-dbg/", so_paths, f"Configuration '{config['name']}' should include rootfs-dbg paths") + + # Verify source file mappings + source_map = config.get("sourceFileMap", {}) + self.assertIsInstance(source_map, dict, f"Configuration '{config['name']}' sourceFileMap should be a dictionary") + self.assertIn("/usr/src/debug", source_map, f"Configuration '{config['name']}' should map /usr/src/debug") + self.assertIn("${workspaceFolder}", str(source_map), f"Configuration '{config['name']}' should map to workspaceFolder") + + # Verify setup commands for sysroot + setup_commands = config.get("setupCommands", []) + self.assertTrue(len(setup_commands) >= 1, f"Configuration '{config['name']}' should have setup commands") + sysroot_cmd = setup_commands[0] + self.assertIn("sysroot", sysroot_cmd.get("text", ""), f"Configuration '{config['name']}' should set sysroot in setup commands") + + # Verify preLaunchTask exists and matches name pattern + task = config.get("preLaunchTask", "") + self.assertTrue(task, f"Configuration '{config['name']}' preLaunchTask should not be empty") + # Task should contain port number and executable name from config name + config_name = config["name"] + if "_once" in config_name: + self.assertIn("_once", task, f"once configuration '{config_name}' should have once preLaunchTask") + elif "_attach" in config_name: + 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") + + # Categorize configurations + config_name = config["name"] + if "_once" in config_name: + once_configs.append(config_name) + elif "_attach" in config_name: + attach_configs.append(config_name) + + # 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}") + + def _verify_launch_json_debugging(self, tempdir, qemu, recipe_name, example_exe): + """Verify remote debugging and deployment works using launch.json configurations + + 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:14,14' \ + -ex 'continue' + 3. Verifying debug output and stopping gdbserver + """ + with open(os.path.join(tempdir, '.vscode', 'launch.json')) as launch_j: + launch_d = json.load(launch_j) + with open(os.path.join(tempdir, '.vscode', 'tasks.json')) as tasks_j: + tasks_d = json.load(tasks_j) + + configurations = launch_d["configurations"] + tasks = tasks_d["tasks"] + + # Test one configuration for remote debugging + once_config_count = 0 + for config in configurations: + if f"usr-bin-{recipe_name}_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-{recipe_name}_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}") + + 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") + + # Extract configuration values for launch.json + debugger_path = launch_config["miDebuggerPath"] + server_addr = launch_config["miDebuggerServerAddress"] + prelaunch_task_name = launch_config["preLaunchTask"] + program = launch_config["program"] + additional_so_lib_search_path = launch_config["additionalSOLibSearchPath"] + source_file_map = launch_config["sourceFileMap"] + setup_commands = launch_config["setupCommands"] + + # Find the preLaunchTask in tasks.json + prelaunch_task = next( + (task for task in tasks if task["label"] == prelaunch_task_name), None) + self.assertIsNotNone(prelaunch_task, f"PreLaunchTask '{prelaunch_task_name}' not found in tasks.json") + + # Find the dependsOn task if exists (install and deploy-target) + if "dependsOn" in prelaunch_task: + depends_task_names = prelaunch_task["dependsOn"] + for depends_task_name in depends_task_names: + depends_task = next( + (task for task in tasks if task["label"] == depends_task_name), None) + self.assertIsNotNone(depends_task, f"DependsOn task '{depends_task_name}' not found in tasks.json") + # For simplicity, we assume the dependsOn task is a prerequisite and does not affect the main command + self.logger.debug(f"PreLaunchTask '{prelaunch_task_name}' depends on '{depends_task_name}'") + + # Extract command details from dependsOn task + depends_task_command = depends_task["command"] + depends_task_args = depends_task.get("args", []) + self.logger.debug(f"Would execute dependsOn task: {depends_task_command} {' '.join(depends_task_args)}") + runCmd(f"{depends_task_command} {' '.join(depends_task_args)}", output_log=self._cmd_logger) + + # Verify task structure and extract command details + self.assertEqual(prelaunch_task["type"], "shell", f"Task '{prelaunch_task_name}' should be shell type") + task_command = prelaunch_task["command"] + task_args = prelaunch_task["args"] + + # The command should be ssh for remote execution + 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") + + # 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 + 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 + 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}") + + if debug_func and debug_check_func: + # Do a gdb remote session using the once configuration + gdb_batch_cmd = debugger_path + " --batch" + for setup_command in setup_commands: + # What VSCode does, for tracing add "logging": {"engineLogging": true } to launch.json + setup_cmd = setup_command["text"].strip() + if setup_cmd.startswith("-"): + # Ignore commands starting with '-' as they are VSCode internal commands? + continue + else: + 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 remote %s'" % server_addr + # Add a basic set of command performing a simple debugging session + gdb_batch_cmd += debug_func(DevtoolIdeSdkTests.MAGIC_STRING_ORIG, "continue") + self.logger.debug(f"Starting gdb session with command: {gdb_batch_cmd}") + r = runCmd(gdb_batch_cmd, output_log=self._cmd_logger) + self.logger.debug("%s %s returned: %s", debugger_path, gdb_batch_cmd, r.output) + self.assertEqual(r.status, 0) + debug_check_func(r.output, DevtoolIdeSdkTests.MAGIC_STRING_ORIG) + + @OETestTag("runqemu") def test_devtool_ide_sdk_code_cmake(self): """Verify a cmake recipe works with ide=code mode""" recipe_name = "cmake-example" + example_exe = "cmake-example" build_file = "CMakeLists.txt" testimage = "oe-selftest-image" + build_file = "CMakeLists.txt" self._check_workspace() self._write_bb_config([recipe_name]) - tempdir = self._devtool_ide_sdk_recipe( - recipe_name, build_file, testimage) - bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@192.168.17.17 -c --ide=code' % ( - recipe_name, testimage) - runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger) - self._verify_cmake_preset(tempdir) - self._verify_install_script_code(tempdir, recipe_name) - self._gdb_cross() + + # Verify deployment to Qemu (system mode) works + self._check_runqemu_prerequisites() + bitbake(testimage) + with runqemu(testimage, runqemuparams="nographic") as qemu: + tempdir = self._devtool_ide_sdk_recipe( + recipe_name, build_file, testimage) + bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@%s -c --ide=code' % ( + recipe_name, testimage, qemu.ip) + runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger) + self._verify_cmake_preset(tempdir) + self._verify_install_script_code(tempdir, recipe_name) + self._gdb_cross() + + # Verify the launch.json file created is valid + self._verify_launch_json(tempdir) + + # Verify deployment and remote debugging works + self._verify_launch_json_debugging(tempdir, qemu, recipe_name, example_exe) def test_devtool_ide_sdk_code_meson(self): """Verify a meson recipe works with ide=code mode""" From patchwork Wed Dec 31 11:46:40 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77836 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 B65D2EE6454 for ; Wed, 31 Dec 2025 11:47:51 +0000 (UTC) Received: from mta-65-226.siemens.flowmailer.net (mta-65-226.siemens.flowmailer.net [185.136.65.226]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.83909.1767181666334674371 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=hLo92IvC; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.226, mailfrom: fm-1329275-20251231114744e6e4ae995f0002075e-qd6x8t@rts-flowmailer.siemens.com) Received: by mta-65-226.siemens.flowmailer.net with ESMTPSA id 20251231114744e6e4ae995f0002075e for ; Wed, 31 Dec 2025 12:47:44 +0100 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=FD/GiPLj6YlbUnW/TzRNOae6+QK0ApT19VPzQZ1jMxo=; b=hLo92IvC6qGNBRkLf5YEp/6g81Qk6lAB3kbUsv+AEE2iufPUgYutZ/SdKGa1jOdmqULXO+ LBSbGKHnu+eJRcDfaB2AkzpWMqEEsqnrixl6YH6CDBHovVsMjSu0bcVuBxfAzARobaWQR8cI JwFHrwogChpx/YAsoi6IWGw1AEIXT91SmaDYaHMdtpTo86IOACBqoumdzs0ifr1/2UL3lmMb 6qrOMJ+EEJ+22rOCjTV9Adync1MdGgZFHW6yvCQTU/hA2ZQwiGtiNR16J0JDMuKdy7EWmvoV QmYZua9iXLPbVfZa83yGMwFR98mXuUlAj0gD71Ip6w+j2zEG11qjDgIQ==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 10/14] devtool: ide-sdk: evaluate DEBUG_PREFIX_MAP Date: Wed, 31 Dec 2025 12:46:40 +0100 Message-ID: <20251231114718.4031606-11-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228735 From: Adrian Freihofer Improve the reverse mapping for searching the source files for remote debugging by taking the details from DEBUG_PREFIX_MAP into account when generating GDB's debug-file-directory mappings. This allows settings such as DEBUG_PREFIX_MAP = "" for modified recipes to be used to avoid any path remapping. Background: For packaged debug-symbols, the references to the source code need to be relocated to paths which are valid on the target system. By default, devtool ide-sdk tries to keep the relocated paths and configures the debugger to reverse map them back to the original source paths. The goal is to provide a debug setup which is a close as possible to a regular build. Usually this works well, but there are situations where the reverse mapping is not unambiguous. For example the default DEBUG_PREFIX_MAP DEBUG_PREFIX_MAP ?= "\ -ffile-prefix-map=${S}=${TARGET_DBGSRC_DIR} \ -ffile-prefix-map=${B}=${TARGET_DBGSRC_DIR} \ adds two different source paths (${S} and ${B}) to the same target path (${TARGET_DBGSRC_DIR}). If both source paths contain files with the same name, the debugger cannot determine which source file to use. For this example it is usually sufficient to only map ${S} to the target path. The source files in ${B} are probably a few generated files which are not that interesting for debugging. But depending on the project, the files in ${B} might also be relevant for debugging. Also add a hint to the generated local.conf snippet to use DEBUG_PREFIX_MAP = "" if the user wants to optimize the build for debugging. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_plugins/ide_code.py | 26 +++++--- scripts/lib/devtool/ide_plugins/ide_none.py | 28 ++++---- scripts/lib/devtool/ide_sdk.py | 73 ++++++++++++++++++++- scripts/lib/devtool/standard.py | 7 +- 4 files changed, 111 insertions(+), 23 deletions(-) diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py index 05d513f160..647a5b8cc6 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -266,18 +266,26 @@ class IdeVSCode(IdeBase): launch_config['additionalSOLibSearchPath'] = modified_recipe.solib_search_path_str( gdb_cross_config.image_recipe) # First: Search for sources of this recipe in the workspace folder - if modified_recipe.pn in modified_recipe.target_dbgsrc_dir: - src_file_map[modified_recipe.target_dbgsrc_dir] = "${workspaceFolder}" - else: - logger.error( - "TARGET_DBGSRC_DIR must contain the recipe name PN.") + # If compiled with DEBUG_PREFIX_MAP = "", no reverse map is is needed. The binaries + # contain the full path to the source files. But by default there is a reverse map. + for target_path, host_path in modified_recipe.reverse_debug_prefix_map.items(): + if host_path.startswith(modified_recipe.real_srctree): + src_file_map[target_path] = "${workspaceFolder}" + host_path[len(modified_recipe.real_srctree):] + else: + src_file_map[target_path] = host_path + # Second: Search for sources of other recipes in the rootfs-dbg - if modified_recipe.target_dbgsrc_dir.startswith("/usr/src/debug"): + # We expect that /usr/src/debug/${PN}/${PV} or no mapping is used for the recipes + # own sources and we can use the key "/usr/src/debug" for the rootfs-dbg. + if "/usr/src/debug" in src_file_map: + logger.error( + 'Key "/usr/src/debug" already exists in src_file_map. ' + 'Something with DEBUG_PREFIX_MAP looks unexpected and finding ' + 'sources in the rootfs-dbg will not work as expected.' + ) + else: src_file_map["/usr/src/debug"] = os.path.join( gdb_cross_config.image_recipe.rootfs_dbg, "usr", "src", "debug") - else: - logger.error( - "TARGET_DBGSRC_DIR must start with /usr/src/debug.") else: logger.warning( "Cannot setup debug symbols configuration for GDB. IMAGE_GEN_DEBUGFS is not enabled.") diff --git a/scripts/lib/devtool/ide_plugins/ide_none.py b/scripts/lib/devtool/ide_plugins/ide_none.py index 8284c4e0a5..ba65f6f7da 100644 --- a/scripts/lib/devtool/ide_plugins/ide_none.py +++ b/scripts/lib/devtool/ide_plugins/ide_none.py @@ -78,19 +78,25 @@ class GdbCrossConfigNone(GdbCrossConfig): os.path.join(self.modified_recipe.recipe_sysroot, 'usr', 'include') + '"') if self.image_recipe.rootfs_dbg: # First: Search for sources of this recipe in the workspace folder - if self.modified_recipe.pn in self.modified_recipe.target_dbgsrc_dir: - gdbinit_lines.append('set substitute-path "%s" "%s"' % - (self.modified_recipe.target_dbgsrc_dir, self.modified_recipe.real_srctree)) - else: - logger.error( - "TARGET_DBGSRC_DIR must contain the recipe name PN.") + # If compiled with DEBUG_PREFIX_MAP = "", no reverse map is is needed. The binaries + # contain the full path to the source files. But by default there is a reverse map. + src_file_map = dict(self.modified_recipe.reverse_debug_prefix_map) + # Second: Search for sources of other recipes in the rootfs-dbg - if self.modified_recipe.target_dbgsrc_dir.startswith("/usr/src/debug"): - gdbinit_lines.append('set substitute-path "/usr/src/debug" "%s"' % os.path.join( - self.image_recipe.rootfs_dbg, "usr", "src", "debug")) - else: + # We expect that /usr/src/debug/${PN}/${PV} or no mapping is used for the recipes + # own sources and we can use the key "/usr/src/debug" for the rootfs-dbg. + if "/usr/src/debug" in src_file_map: logger.error( - "TARGET_DBGSRC_DIR must start with /usr/src/debug.") + 'Key "/usr/src/debug" already exists in src_file_map. ' + 'Something with DEBUG_PREFIX_MAP looks unexpected and finding sources ' + 'in the rootfs-dbg will not work as expected.' + ) + else: + src_file_map["/usr/src/debug"] = os.path.join( + self.image_recipe.rootfs_dbg, "usr", "src", "debug") + + for target_path, host_path in src_file_map.items(): + gdbinit_lines.append('set substitute-path "%s" "%s"' % (target_path, host_path)) else: logger.warning( "Cannot setup debug symbols configuration for GDB. IMAGE_GEN_DEBUGFS is not enabled.") diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py index 96d60ad4f1..78d7ed78c6 100755 --- a/scripts/lib/devtool/ide_sdk.py +++ b/scripts/lib/devtool/ide_sdk.py @@ -14,6 +14,7 @@ import shutil import stat import subprocess import sys +import shlex from argparse import RawTextHelpFormatter from enum import Enum @@ -397,6 +398,7 @@ class RecipeModified: self.bpn = None self.d = None self.debug_build = None + self.reverse_debug_prefix_map = {} self.fakerootcmd = None self.fakerootenv = None self.libdir = None @@ -408,10 +410,10 @@ class RecipeModified: self.recipe_id = None self.recipe_sysroot = None self.recipe_sysroot_native = None + self.s = None self.staging_incdir = None self.strip_cmd = None self.target_arch = None - self.target_dbgsrc_dir = None self.topdir = None self.workdir = None # Service management @@ -479,13 +481,13 @@ class RecipeModified: recipe_d.getVar('RECIPE_SYSROOT')) self.recipe_sysroot_native = os.path.realpath( recipe_d.getVar('RECIPE_SYSROOT_NATIVE')) + self.s = recipe_d.getVar('S') self.staging_bindir_toolchain = os.path.realpath( recipe_d.getVar('STAGING_BINDIR_TOOLCHAIN')) self.staging_incdir = os.path.realpath( recipe_d.getVar('STAGING_INCDIR')) self.strip_cmd = recipe_d.getVar('STRIP') self.target_arch = recipe_d.getVar('TARGET_ARCH') - self.target_dbgsrc_dir = recipe_d.getVar('TARGET_DBGSRC_DIR') self.topdir = recipe_d.getVar('TOPDIR') self.workdir = os.path.realpath(recipe_d.getVar('WORKDIR')) @@ -504,6 +506,9 @@ class RecipeModified: self.meson_cross_file = recipe_d.getVar('MESON_CROSS_FILE') self.build_tool = BuildTool.MESON + self.reverse_debug_prefix_map = self._init_reverse_debug_prefix_map( + recipe_d.getVar('DEBUG_PREFIX_MAP')) + # Recipe ID is the identifier for IDE config sections self.recipe_id = self.bpn + "-" + self.package_arch self.recipe_id_pretty = self.bpn + ": " + self.package_arch @@ -563,6 +568,70 @@ class RecipeModified: """Return a : separated list of paths usable by GDB's set solib-search-path""" return ':'.join(self.solib_search_path(image)) + def _init_reverse_debug_prefix_map(self, debug_prefix_map): + """Parses GCC map options and returns a mapping of target to host paths. + + This function scans a string containing GCC options such as -fdebug-prefix-map, + -fmacro-prefix-map, and -ffile-prefix-map (in both '--option value' and '--option=value' + forms), extracting all mappings from target paths (used in debug info) to host source + paths. If multiple mappings for the same target path are found, the most suitable + host path is selected (preferring 'sources' over 'build' directories). + """ + prefixes = ("-fdebug-prefix-map", "-fmacro-prefix-map", "-ffile-prefix-map") + all_mappings = {} + args = shlex.split(debug_prefix_map) + i = 0 + + # Collect all mappings, storing potentially multiple host paths per target path + while i < len(args): + arg = args[i] + mapping = None + for prefix in prefixes: + if arg == prefix: + i += 1 + mapping = args[i] + break + elif arg.startswith(prefix + '='): + mapping = arg[len(prefix)+1:] + break + if mapping: + host_path, target_path = mapping.split('=', 1) + if target_path: + if target_path not in all_mappings: + all_mappings[target_path] = [] + all_mappings[target_path].append(os.path.realpath(host_path)) + i += 1 + + # Select the best host path for each target path (only 1:1 mappings are supported by GDB) + mappings = {} + unused_host_paths = [] + for target_path, host_paths in all_mappings.items(): + if len(host_paths) == 1: + mappings[target_path] = host_paths[0] + else: + # First priority path for sources is the source directory S + # Second priority path is any other directory + # Least priority is the build directory B, which probably contains only generated source files + sources_paths = [path for path in host_paths if os.path.realpath(path).startswith(os.path.realpath(self.s))] + if sources_paths: + mappings[target_path] = sources_paths[0] + unused_host_paths.extend([path for path in host_paths if path != sources_paths[0]]) + else: + # If no 'sources' path, prefer non-'build' paths + non_build_paths = [path for path in host_paths if not os.path.realpath(path).startswith(os.path.realpath(self.b))] + if non_build_paths: + mappings[target_path] = non_build_paths[0] + unused_host_paths.extend([path for path in host_paths if path != non_build_paths[0]]) + else: + # Fall back to first path if all are build paths + mappings[target_path] = host_paths[0] + unused_host_paths.extend(host_paths[1:]) + + if unused_host_paths: + logger.info("Some source directories mapped by -fdebug-prefix-map are not included in the debugger search paths. Ignored host paths: %s", unused_host_paths) + + return mappings + def __init_exported_variables(self, d): """Find all variables with export flag set. diff --git a/scripts/lib/devtool/standard.py b/scripts/lib/devtool/standard.py index 1fd5947c41..f4d5d7cd3f 100644 --- a/scripts/lib/devtool/standard.py +++ b/scripts/lib/devtool/standard.py @@ -971,7 +971,12 @@ def modify(args, config, basepath, workspace): continue f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch]))) if args.debug_build: - f.write('\nDEBUG_BUILD = "1"\n') + f.write('\n# Optimize for debugging. Use e.g. -Og instead of -O2\n') + f.write('DEBUG_BUILD = "1"\n') + f.write('\n# Keep the paths to the source files for remote debugging\n') + f.write('# DEBUG_PREFIX_MAP = ""\n') + f.write('# WARN_QA:remove = "buildpaths"\n') + f.write('# ERROR_QA:remove = "buildpaths"\n') update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn]) From patchwork Wed Dec 31 11:46:41 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77827 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 9A325EE644C for ; Wed, 31 Dec 2025 11:47:51 +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.83908.1767181666334371514 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=reRe9fVS; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.227, mailfrom: fm-1329275-202512311147449c6b4bc518000207f3-amr575@rts-flowmailer.siemens.com) Received: by mta-64-227.siemens.flowmailer.net with ESMTPSA id 202512311147449c6b4bc518000207f3 for ; Wed, 31 Dec 2025 12:47:44 +0100 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=NVAqGQRqbcEtQszMyuoNFUTh3Y+66wYlXcG2CjppYIc=; b=reRe9fVSxvlijP+spmqPFeHGRJMcVx9DMywANVPqhOSX+KXs+7Rc3RghblNMo8IwO8InMp atLgNxN50zmLPsP5hs1p27BOw7WJw00jTfQUl4QmdL3GKTJrNSmcLP8TtXWAJwNXcvktv0YF dTvjCkvs4Dq32dvTxXRNj9Xt7uSYbeHM9fkT+Ud6JXX4vZ0UKZS/cN/1YTUHGjI8Oy91l1K3 znG4809Orj/VEUqaizzuRd3i/4Ci5GofXRP8/nSg900ksTAfKsISfqLvWRlN2rd8fr2UoXrQ pmLTDnngckb5JuuJDuTTGWDpNo1JGCP5guYm/Pq1kf74UqTEhZN4D0Ew==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 11/14] cpp-example: Add std::vector example Date: Wed, 31 Dec 2025 12:46:41 +0100 Message-ID: <20251231114718.4031606-12-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228733 From: Adrian Freihofer Add a standard container (std::vector) to the C++ example program to demonstrate the debugger's capability to inspect and traverse STL containers during a debugging session. This requires enabling GDB's pretty-printing feature, which depends on Python scripts shipped with the compiler. Signed-off-by: Adrian Freihofer --- meta-selftest/recipes-test/cpp/files/cpp-example.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/meta-selftest/recipes-test/cpp/files/cpp-example.cpp b/meta-selftest/recipes-test/cpp/files/cpp-example.cpp index dbf82f15d9..23d7169092 100644 --- a/meta-selftest/recipes-test/cpp/files/cpp-example.cpp +++ b/meta-selftest/recipes-test/cpp/files/cpp-example.cpp @@ -9,6 +9,7 @@ #include #include #include +#include int main(int argc, char* argv[]) { @@ -50,5 +51,12 @@ int main(int argc, char* argv[]) } } while (endless_mode); + // Example: Demonstrate std::vector traversal for debugger inspection + std::vector numbers = {1, 2, 3}; + std::cout << "Traversing std::vector numbers:" << std::endl; + for (size_t i = 0; i < numbers.size(); ++i) { + std::cout << "numbers[" << i << "] = " << numbers[i] << std::endl; + } + return 0; } From patchwork Wed Dec 31 11:46:42 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77832 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 8D33CEE644A for ; Wed, 31 Dec 2025 11:47:51 +0000 (UTC) Received: from mta-65-227.siemens.flowmailer.net (mta-65-227.siemens.flowmailer.net [185.136.65.227]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.83938.1767181666335179993 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=UqHYf4f7; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.227, mailfrom: fm-1329275-20251231114744a3308d315100020701-ax3syb@rts-flowmailer.siemens.com) Received: by mta-65-227.siemens.flowmailer.net with ESMTPSA id 20251231114744a3308d315100020701 for ; Wed, 31 Dec 2025 12:47:44 +0100 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=Pb2CVcyi2Tbrb0594k8JqqK8GfbvknijF9X1OmF2Zjo=; b=UqHYf4f7TRarjZdlY5x/rdr5N+GBpyQiMcLyYImVAOs+kWqOzq88P9oSbPR3yDLKCdT0y2 /hyVu+w6YB8MsgsG80aYIPh8PWxUR/86CpGGO0+cZSoytMIPcx3gafhzzKgvxwkr82BjITSq JMWAcJuNHYaqZTQ4maaEL8ElMOlSMd4fNPeq4Zfst3mzYbggtwEMTf6zVhSPvDgOgOwMjsPe EYJDCXWHvQotkpXfd2hySZ+WonwQsChRQafAid88itzB8LztxBBHaImFzBauMp5J0Pb665+L Q6FQwo2jmgO1MmswhL3oZcbrUbvdE5J8TaW1C1WWEMuTvoJu/ITjZbcg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 12/14] devtool: ide-sdk: Support GDB pretty-printing for C++ STL types Date: Wed, 31 Dec 2025 12:46:42 +0100 Message-ID: <20251231114718.4031606-13-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228740 From: Adrian Freihofer GDB requires Python scripts provided by GCC to properly display C++ STL types. This commit adds support for configuring GDB to use these pretty-printers in the ide-sdk, covering both the ide_none and ide_code plugins. The implementation locates the GCC Python helper scripts in the sysroot and injects the necessary commands into the GDB initialization files and IDE debug configurations. This ensures that when debugging C++ applications, STL containers and other complex types are displayed in a readable format. Without this: (gdb) print numbers $1 = { >> = { _M_impl = {> = {> = {}, }, >::_Vector_impl_data> = {_M_start = 0x55555556c370, _M_finish = 0x55555556c37c, _M_end_of_storage = 0x55555556c37c}, }}, } With this: (gdb) print numbers $1 = std::vector of length 3, capacity 3 = {1, 2, 3} Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_plugins/ide_code.py | 14 +++++++++++ scripts/lib/devtool/ide_plugins/ide_none.py | 8 +++++++ scripts/lib/devtool/ide_sdk.py | 26 +++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py index 647a5b8cc6..c2ee9b91c6 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -290,6 +290,20 @@ class IdeVSCode(IdeBase): logger.warning( "Cannot setup debug symbols configuration for GDB. IMAGE_GEN_DEBUGFS is not enabled.") + # Enable pretty-printing for gdb for resolving STL types with help of python scripts + pretty_printing_cmd = modified_recipe.gdb_pretty_print_scripts + if pretty_printing_cmd: + setup_commands += [ + { + "description": "Enable pretty-printing for gdb", + "text": "python " +";".join(pretty_printing_cmd) + }, + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing" + } + ] + launch_config['sourceFileMap'] = src_file_map launch_config['setupCommands'] = setup_commands diff --git a/scripts/lib/devtool/ide_plugins/ide_none.py b/scripts/lib/devtool/ide_plugins/ide_none.py index ba65f6f7da..ed96afa33c 100644 --- a/scripts/lib/devtool/ide_plugins/ide_none.py +++ b/scripts/lib/devtool/ide_plugins/ide_none.py @@ -102,6 +102,14 @@ class GdbCrossConfigNone(GdbCrossConfig): "Cannot setup debug symbols configuration for GDB. IMAGE_GEN_DEBUGFS is not enabled.") # Disable debuginfod for now, the IDE configuration uses rootfs-dbg from the image workdir. gdbinit_lines.append('set debuginfod enabled off') + + # Enable pretty-printing for gdb for resolving STL types with help of python scripts + pretty_printing_cmd = self.modified_recipe.gdb_pretty_print_scripts + if pretty_printing_cmd: + gdbinit_lines.append(os.linesep +"python") + gdbinit_lines += pretty_printing_cmd + gdbinit_lines.append("end" + os.linesep) + gdbinit_lines.append( '%s %s:%d' % (remote_cmd, self.gdb_cross.host, self.gdbserver_port)) gdbinit_lines.append('set remote exec-file ' + self.binary.binary_path) diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py index 78d7ed78c6..ba225f20b9 100755 --- a/scripts/lib/devtool/ide_sdk.py +++ b/scripts/lib/devtool/ide_sdk.py @@ -15,6 +15,7 @@ import stat import subprocess import sys import shlex +import glob from argparse import RawTextHelpFormatter from enum import Enum @@ -414,6 +415,7 @@ class RecipeModified: self.staging_incdir = None self.strip_cmd = None self.target_arch = None + self.tcoverride = None self.topdir = None self.workdir = None # Service management @@ -437,6 +439,7 @@ class RecipeModified: # Populated after bitbake built all the recipes self._installed_binaries = None + self._gdb_pretty_print_scripts = None def initialize(self, config, workspace, tinfoil): recipe_d = parse_recipe( @@ -488,6 +491,7 @@ class RecipeModified: recipe_d.getVar('STAGING_INCDIR')) self.strip_cmd = recipe_d.getVar('STRIP') self.target_arch = recipe_d.getVar('TARGET_ARCH') + self.tcoverride = recipe_d.getVar('TCOVERRIDE') self.topdir = recipe_d.getVar('TOPDIR') self.workdir = os.path.realpath(recipe_d.getVar('WORKDIR')) @@ -632,6 +636,28 @@ class RecipeModified: return mappings + @property + def gdb_pretty_print_scripts(self): + if self._gdb_pretty_print_scripts is None: + if self.tcoverride == "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 gcc_python_helpers_dirs: + gcc_python_helpers = gcc_python_helpers_dirs[0] + else: + logger.warning("Could not find gcc python helpers directory matching: %s", gcc_python_helpers_pattern) + gcc_python_helpers = "" + pretty_print_scripts = [ + "import sys", + "sys.path.insert(0, '" + gcc_python_helpers + "')", + "from libstdcxx.v6.printers import register_libstdcxx_printers", + "register_libstdcxx_printers(None)" + ] + self._gdb_pretty_print_scripts = pretty_print_scripts + else: + self._gdb_pretty_print_scripts = "" + return self._gdb_pretty_print_scripts + def __init_exported_variables(self, d): """Find all variables with export flag set. From patchwork Wed Dec 31 11:46:43 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77834 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 C37D3EE6457 for ; Wed, 31 Dec 2025 11:47:51 +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.msgproc01-g2.83910.1767181666617258447 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=loPNAQns; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.228, mailfrom: fm-1329275-2025123111474406cda5672f000207df-4bjfrj@rts-flowmailer.siemens.com) Received: by mta-65-228.siemens.flowmailer.net with ESMTPSA id 2025123111474406cda5672f000207df for ; Wed, 31 Dec 2025 12:47:44 +0100 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=BC8cvzishBZYzKeIBuGOQ4nQtPXvLzhR6XuZe9jjlfE=; b=loPNAQnsiaVNDFnn5coPBt9M2ydd8v1EkF1CdfOArcgTtIxT1x82FUt/ZrplUr34NojuW7 rnaJ6xj8Q5otJivZmlRcMrTsjzEQyMx40iIludUVTnU47XtaWX4ZKBbbsSJ9wGa8P9e+fgP6 hWqiFuNO0q103M54fJFt0xqFTG6738tldVJgrXj8niaKIVCcTHR2cLRPDHMqdndkBFz7O2Eb MnKkprQEKeRB6XAVIvAlyAzGCGopHijFugiVu4S/RlV+bmI2L3tJVy/2XMzbVtes0oNIT5k3 1WEcMuC9HYx3L/FXhWNQyIDB4qKNV9X2w63xEOh3SMUC/4KhN3Rm3hQg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 13/14] oe-selftest: devtool: add test for gdb pretty-printing Date: Wed, 31 Dec 2025 12:46:43 +0100 Message-ID: <20251231114718.4031606-14-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228739 From: Adrian Freihofer This extends the existing devtool IDE SDK tests to verify that gdb pretty-printing is working correctly. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index df5c863a85..3f00ce8ffb 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2725,6 +2725,14 @@ class DevtoolIdeSdkTests(DevtoolBase): gdb_batch_cmd += " -ex 'print CppExample::test_string.compare(\"cpp-example-lib %s\")'" % magic_string gdb_batch_cmd += " -ex 'print CppExample::test_string.compare(\"cpp-example-lib %saaa\")'" % magic_string gdb_batch_cmd += " -ex 'list cpp-example-lib.hpp:14,14'" + + # check if resolving std::vector works with python scripts + # break at line 56, because the line which initializes the vector + # may be optimized out by the compiler + gdb_batch_cmd += " -ex 'list cpp-example.cpp:55,55'" + gdb_batch_cmd += " -ex 'break cpp-example.cpp:56'" + gdb_batch_cmd += " -ex 'continue'" + gdb_batch_cmd += " -ex 'print numbers'" gdb_batch_cmd += " -ex 'continue'" return gdb_batch_cmd @@ -2734,6 +2742,10 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertIn("$2 = -3", gdb_output) # test.string.compare longer self.assertIn( 'inline static const std::string test_string = "cpp-example-lib %s";' % magic_string, gdb_output) + + # check if resolving std::vector works with python scripts + self.assertRegex(gdb_output, r"55\s+std::vector numbers = \{1, 2, 3\};") + self.assertRegex(gdb_output, r"\$\d+ = std::vector of length 3, capacity 3 = \{1, 2, 3\}") self.assertIn("exited normally", gdb_output) def _gdb_cross_debugging_multi(self, qemu, recipe_name, example_exe, magic_string): From patchwork Wed Dec 31 11:46:44 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77835 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 B6249EE6453 for ; Wed, 31 Dec 2025 11:47:51 +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.msgproc02-g2.83936.1767181666334600467 for ; Wed, 31 Dec 2025 03:47:47 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=dIdHHTP4; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.225, mailfrom: fm-1329275-20251231114744e63ce839aa000207ad-_mifdb@rts-flowmailer.siemens.com) Received: by mta-65-225.siemens.flowmailer.net with ESMTPSA id 20251231114744e63ce839aa000207ad for ; Wed, 31 Dec 2025 12:47:44 +0100 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=38cE4Hy99EkwyvcFQuVpDPTsnaWHlZY19sQvJ4dJAqI=; b=dIdHHTP46iLjm3Vg1Aa4Lb3bo9joQA4kJkTPlASUXYmwuXZWAweD+V5+j0C8AzhddnIOF7 rjs+Vs39EcJViavgCvBfq5RY+7XN7cdU3odlPBVWWJQ9T+/u/KH/2CixBppjEKqR7gQnXm/8 /ysz9CIhZljInzp0i2I3Wwrfh1ZGkYmNcRYSDO/EH1KCB7HYPSL4rOjDWCh1LIHjBfjWI31T mlpjyPjAA+znVwXMHog8Q6ZLrLtarLkdEBwEgnivbTz4+o36W2w2Y37eaku/jRay0q0Fd98I k1HArGIKnOafvWrCGeOAQLWtyAIV4Lx9XyjNjdLLS+wZLX2cmQsom6pA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH v2 14/14] oe-selftest: devtool: add compile step in ide-sdk tests Date: Wed, 31 Dec 2025 12:46:44 +0100 Message-ID: <20251231114718.4031606-15-adrian.freihofer@siemens.com> In-Reply-To: <20251231114718.4031606-1-adrian.freihofer@siemens.com> References: <20251231114718.4031606-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, 31 Dec 2025 11:47:51 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228736 From: Adrian Freihofer Add explicit compile step to the ide-sdk test workflow. The current implementation relies on calling bitbake -c install to perform the install step, which also triggers a build. But this will change when bitbake will support task execution without handling dependencies. To make the tests future-proof, add an explicit compile step after modifying the source code. This also improves the test coverage for meson based recipes, as the compile step is now explicitly tested. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 65 +++++++++++++++++++------ 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 3f00ce8ffb..73282111d5 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2574,6 +2574,9 @@ class DevtoolIdeSdkTests(DevtoolBase): def _sources_scripts_dir(self, src_dir): return os.path.realpath(os.path.join(src_dir, 'oe-scripts')) + def _sources_workdir_dir(self, src_dir): + return os.path.realpath(os.path.join(src_dir, 'oe-workdir')) + def _workspace_gdbinit_dir(self, recipe_name): return os.path.realpath(os.path.join(self.builddir, 'workspace', 'ide-sdk', recipe_name, 'scripts', 'gdbinit')) @@ -2642,7 +2645,7 @@ class DevtoolIdeSdkTests(DevtoolBase): self._workspace_scripts_dir(recipe_name), i_and_d_script) self.assertExists(i_and_d_script_path) - def _devtool_ide_sdk_qemu(self, tempdir, qemu, recipe_name, example_exe): + def _devtool_ide_sdk_qemu(self, tempdir, qemu, recipe_name, example_exe, compile_cmd): """Verify deployment and execution in Qemu system work for one recipe. This function checks the entire SDK workflow: changing the code, recompiling @@ -2687,6 +2690,7 @@ class DevtoolIdeSdkTests(DevtoolBase): cpp_code = cpp_code.replace(DevtoolIdeSdkTests.MAGIC_STRING_ORIG, MAGIC_STRING_NEW) with open(cpp_example_lib_hpp, 'w') as file: file.write(cpp_code) + runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) runCmd(install_deploy_cmd, cwd=tempdir, output_log=self._cmd_logger) # Verify the modified example prints the modified magic string @@ -2818,6 +2822,7 @@ class DevtoolIdeSdkTests(DevtoolBase): 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) @@ -2827,28 +2832,59 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertIn(preset_name, result.output) # Verify cmake re-uses the o files compiled by bitbake - result = runCmd('%s --build --preset %s' % - (cmake_exe, preset_name), cwd=tempdir, output_log=self._cmd_logger) + 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 --build --preset %s --target test' % - (cmake_exe, preset_name), cwd=tempdir, output_log=self._cmd_logger) + 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 --build --preset %s --target clean' % - (cmake_exe, preset_name), cwd=tempdir, output_log=self._cmd_logger) + result = runCmd('%s --target clean' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) self.assertIn("Cleaning", result.output) - result = runCmd('%s --build --preset %s' % - (cmake_exe, preset_name), cwd=tempdir, output_log=self._cmd_logger) + result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) self.assertIn("Building", result.output) self.assertIn("Linking", result.output) - result = runCmd('%s --build --preset %s --target test' % - (cmake_exe, preset_name), cwd=tempdir, output_log=self._cmd_logger) + 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.assertIn("Fail: 0", result.output) + + # 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.assertIn("Fail: 0", result.output) + + return compile_cmd + def _verify_service_running(self, qemu, service_name): """Helper to verify a service is running in Qemu""" status, output = qemu.run("pgrep %s" % service_name) @@ -2898,8 +2934,8 @@ class DevtoolIdeSdkTests(DevtoolBase): runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger) self._gdb_cross() - self._verify_cmake_preset(tempdir) - self._devtool_ide_sdk_qemu(tempdir, qemu, recipe_name, example_exe) + compile_cmd = self._verify_cmake_preset(tempdir) + self._devtool_ide_sdk_qemu(tempdir, qemu, recipe_name, example_exe, compile_cmd) # Verify the oe-scripts sym-link is valid self.assertEqual(self._workspace_scripts_dir( @@ -2930,7 +2966,8 @@ class DevtoolIdeSdkTests(DevtoolBase): runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger) self._gdb_cross() - self._devtool_ide_sdk_qemu(tempdir, qemu, recipe_name, example_exe) + compile_cmd = self._verify_meson_build(tempdir, recipe_name) + self._devtool_ide_sdk_qemu(tempdir, qemu, recipe_name, example_exe, compile_cmd) # Verify the oe-scripts sym-link is valid self.assertEqual(self._workspace_scripts_dir(