From patchwork Tue Dec 30 08: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: 77659 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 862BCE9413B for ; Tue, 30 Dec 2025 08:47:39 +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.60913.1767084455798393690 for ; Tue, 30 Dec 2025 00:47:36 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=Aee1+/x1; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.227, mailfrom: fm-1329275-2025123008473236b561346000020759-4qacbd@rts-flowmailer.siemens.com) Received: by mta-65-227.siemens.flowmailer.net with ESMTPSA id 2025123008473236b561346000020759 for ; Tue, 30 Dec 2025 09:47:33 +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=Aee1+/x1uJkune073qUyWkny1eGqRv/prFMiaMTJS47DqiVOKd854xQW3PoQLC8dfVX6fD d2oEwBT5tnOR0K1dWe/xrCvW1ozxQBcx6YBZbya5XcvYyqicQSfgMt/6CFUFKjORD2PJMaL6 2rjovVT8iQGomz3Y5epj1hJTivDYfXnW2vhjjFBQ5WotDIwIGnvLl994cbxjaLOIxmqmzsm1 AC9IcRKZM0J2HofOtsmQxmcQQvrZFccQqSUd7G7iGhsklGEJVLQpQ+OZwjd0yeKuShVIfSOW 2LZvOmf0ic8EnNrteutESoh1Zy9qLqlmTy5S5WfGhtOE6B2TGcBuyDdw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 01/14] devtool: ide-sdk find bitbake-setup init-build-env Date: Tue, 30 Dec 2025 09:46:41 +0100 Message-ID: <20251230084720.2371227-2-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228629 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 Tue Dec 30 08: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: 77655 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 79F70E94137 for ; Tue, 30 Dec 2025 08:47:39 +0000 (UTC) Received: from mta-65-228.siemens.flowmailer.net (mta-65-228.siemens.flowmailer.net [185.136.65.228]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.60920.1767084455915226615 for ; Tue, 30 Dec 2025 00:47:36 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=k09Toc4T; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.228, mailfrom: fm-1329275-202512300847322f41c47d04000207cd-e9uosr@rts-flowmailer.siemens.com) Received: by mta-65-228.siemens.flowmailer.net with ESMTPSA id 202512300847322f41c47d04000207cd for ; Tue, 30 Dec 2025 09:47:33 +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=k09Toc4TwvocJVJHAMWDiu6vZzNOMsnc8n4Abaj/iyvLVVVlTYqUb0Z8DyLNiTuRzrC+BI YVxPnJYWLpbDJuUbc9UCa27oqk7MGIMgFneQlpVb8zNqdFzFC+XJtJ6/nLYkyoWf5s3UO0cW Np+pCapic6kiRU9cyvZD6PKMc1AAfR+1q0O6SllvDylPwWcmYWgNmY3tn1pIZDsUBC1+dyCk RhCOUTYILtaKKCJ7sIa/EMHjBSiRYq913n8IAtFhnUzW7oL2f3tHW/JRlS3Xr81cg7KXWeoo MMuhYrLsWw7ne1neDMpKrm+iXh/VSymHdy4xDlQTwGq9rU0cd+ukeYfg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 02/14] oe-selftest: devtool: DevtoolIdeSdkTests debug logging Date: Tue, 30 Dec 2025 09:46:42 +0100 Message-ID: <20251230084720.2371227-3-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228638 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 Tue Dec 30 08: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: 77662 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 931C9E9413E for ; Tue, 30 Dec 2025 08:47:39 +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.60914.1767084455798481830 for ; Tue, 30 Dec 2025 00:47:36 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=caXyPNed; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-20251230084733d2445eef390002079d-ojqj_n@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 20251230084733d2445eef390002079d for ; Tue, 30 Dec 2025 09:47:33 +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=caXyPNedZucvsJzc3Xa4tD2n2b3zBIOtr4lEOANCNYYrLswdjj5QyB+YW3jPJST55M3wB0 +e1rhSMQepE8xKy8QIq+UT2ubq1zKlTiVfAtlPr67lbrGtM8YRhmXRtoGD4piJ3CfEUyvBxo 4TzDIo/Cu7zgTiK+ViMG1nOaY6FHu5jQntKUq71bx7Wa0B0zrlbaRC65daWsK0qUUDNDRBje wpVASwGHCJLueUPpST4As5Sd7jdJ1GVWAPcE8U5K0agqm3QqsYtYCD6txg+ZwARNfsrr6Kda hjPX+mttimDqjvh7Wmsra/ZjUVPY6MQwH6xthhP9BZPm4yTYJ8sH2KGw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 03/14] cpp-example: run as a service Date: Tue, 30 Dec 2025 09:46:43 +0100 Message-ID: <20251230084720.2371227-4-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228634 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 Tue Dec 30 08: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: 77663 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 B2A12E95A65 for ; Tue, 30 Dec 2025 08:47:39 +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.60915.1767084455798607082 for ; Tue, 30 Dec 2025 00:47:36 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=F/imONfv; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.227, mailfrom: fm-1329275-20251230084733fef51fa0e50002074b-ekubud@rts-flowmailer.siemens.com) Received: by mta-65-227.siemens.flowmailer.net with ESMTPSA id 20251230084733fef51fa0e50002074b for ; Tue, 30 Dec 2025 09:47:33 +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=F/imONfvLLVGSZkWz1V1K7ioyzuqZi96UboVGA4AKaHFbid1dT8xbdpxL5a7Rh2SBwQdz7 XoKJdCWMcDWZmMlk8Lpnt6q5wHdypeMZtb1qsAM/wMeFjlrTTqTJdrViFkd7KxdGA4aqHvy8 gwwBOOznaRXFsFBSDariCP/2ZHGZO8rNqMSfB4nCEGMQavpml3nm8VDEMfnhSJX00OpYznYh y9VLoPWjjqLpoXVxebM7oPtXKQrEK6pkVFpN6zE6BOClnBIRgiIO36BwelcyyAzqsCcUP2BR JmhTIqtG5o7N1vCAyumyOGt4QLRX0BnjHBBT9cVQcY2s6nGy+Sgth12A==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 04/14] oe-selftest: devtool: check example services are running Date: Tue, 30 Dec 2025 09:46:44 +0100 Message-ID: <20251230084720.2371227-5-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228630 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 Tue Dec 30 08:46:45 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77657 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 79EF3E94134 for ; Tue, 30 Dec 2025 08:47:39 +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.msgproc02-g2.60919.1767084455887739947 for ; Tue, 30 Dec 2025 00:47:36 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=qM2CKDmJ; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.226, mailfrom: fm-1329275-20251230084733237baf5a2300020799-r3vjw_@rts-flowmailer.siemens.com) Received: by mta-65-226.siemens.flowmailer.net with ESMTPSA id 20251230084733237baf5a2300020799 for ; Tue, 30 Dec 2025 09:47:33 +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=qM2CKDmJdZQZ+NvBZsXEo49SfBAukKQTd9wLV22t69Juw8Qe3dJSZ+x24NsQp4TVwnvhx8 et0sp7NMSLGMOezLzzsmApzXdHwEhTII2+/HHS6Lo8q5pwqcgBIYMtuYaee0a4s9I5psrSIG J6Q7b2XGCLy2trMrMY1ORLSQdQOhWFIvRAq/5Xg1/RiF+tThhPdPhO6CaMTEBBmk4rSljyuP QLlccVMtMjXodAVZc3cVO+MsXFL/zUIV3ycrVb2ey7jn3RUeKkgAX9QX5e8OXvnzuQkvLzn/ DX0cFWy8NdZZsfQVBhW9BV/1BuiZMmhItR4JOv7+B0eeVLHZzU3ffnsQ==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 05/14] devtool: ide-sdk: add gdbserver attach mode support Date: Tue, 30 Dec 2025 09:46:45 +0100 Message-ID: <20251230084720.2371227-6-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228637 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 Tue Dec 30 08:46:46 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77656 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 69E31E94133 for ; Tue, 30 Dec 2025 08:47:39 +0000 (UTC) Received: from mta-65-225.siemens.flowmailer.net (mta-65-225.siemens.flowmailer.net [185.136.65.225]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.60939.1767084455796713945 for ; Tue, 30 Dec 2025 00:47:37 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=Bb8rZDjS; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.225, mailfrom: fm-1329275-202512300847331024b71b65000207a9-noikk3@rts-flowmailer.siemens.com) Received: by mta-65-225.siemens.flowmailer.net with ESMTPSA id 202512300847331024b71b65000207a9 for ; Tue, 30 Dec 2025 09:47:33 +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=Bb8rZDjS2HD77rOFm8kig+pHxkzw5fN0VQIJqTLon/dMDctjSr3BXFEPoW2yRBY1FjgdPI snzo/PC5/Lok0MV4Nv/Z00UAcjzcCGdhzO1dDgeB0/euysHl1fRs6TSRDo/oBdZbc8attNW6 gGtaWWGtY5B6QSaJloe0VjLKnQcf6Qgieqtzgc6daSzWgDDL60hL4u2aaFFL0aqnm2lYR+Mh U9LVfIFCs1jHcwIBUOvddRdsnfZVeKzKx6cdtehGH3DRJlmGDjP71RD6YEiPOemUkGeylzxd vWWOrrfXq6d68xv+tR6IkRbI7T+rO1QOA7K51dnBhDzo+F1VvFHjqozw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 06/14] devtool: ide-sdk: move code to ide_none Date: Tue, 30 Dec 2025 09:46:46 +0100 Message-ID: <20251230084720.2371227-7-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228639 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 Tue Dec 30 08:46:47 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77653 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 69C75E94132 for ; Tue, 30 Dec 2025 08:47:39 +0000 (UTC) Received: from mta-65-225.siemens.flowmailer.net (mta-65-225.siemens.flowmailer.net [185.136.65.225]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.60941.1767084455796930130 for ; Tue, 30 Dec 2025 00:47:37 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=SD8OxEQo; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.225, mailfrom: fm-1329275-2025123008473329bcf07d48000207f7-tlw669@rts-flowmailer.siemens.com) Received: by mta-65-225.siemens.flowmailer.net with ESMTPSA id 2025123008473329bcf07d48000207f7 for ; Tue, 30 Dec 2025 09:47:33 +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=SD8OxEQoHvUOxzadpaTrWanHFs+kFp2kyq1vbQobYiDZVUncm9ffOIrTgUwEERXDQu8HfP SeDk/VAzI1tdbAJTI+S8Rk+Ao0cK+UQ6xSDd/Knun9KLiWaLNVylDMM7rX/D+6dgJp09lUP7 xnHv1FMH/YedLSW+KrD8q44+ZICgQdXgD6Yzr3msNg3lJ+R/sLMQbEgJJ4WdX4QaFvbLbwbL 1peimzvRnyyKOOi37qYUZiVI7DDe8PRSFP+dk66eOZLUkR6jAG/RQ5aSkgdqfzh4Uc8saxAJ PvXQoX3Y8ELbfr1ybPeU89OlbshaFkBmFiop1iUm/4oJtq1zu7q8Libg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 07/14] devtool: ide-sdk: make install_and_deploy script pass target arg Date: Tue, 30 Dec 2025 09:46:47 +0100 Message-ID: <20251230084720.2371227-8-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228640 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 Tue Dec 30 08:46:48 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77654 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 5D378E94120 for ; Tue, 30 Dec 2025 08:47:39 +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.60942.1767084455913685628 for ; Tue, 30 Dec 2025 00:47:37 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=X9WFGKUr; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.228, mailfrom: fm-1329275-202512300847339f1a4fb438000207cf-_wwxqf@rts-flowmailer.siemens.com) Received: by mta-65-228.siemens.flowmailer.net with ESMTPSA id 202512300847339f1a4fb438000207cf for ; Tue, 30 Dec 2025 09:47:33 +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=X9WFGKUrwQF6p/qPzapmpSCub4jhuJ0d4U7Chkd8/+gD4zJcQ2DEri8WS0Ys/5vkmav5qG /PWxKUVLUJ8yTO8t9kWbr57OQYfQwokHjHjCqL5+o1ZI/wMM5FGDWT6d7qvZa9zMTLEUl+nr eBi9NHgU+3Gcm0QI/lXRlbE3c9nDUNlJZih2ZbfVi8It4rePx7F3a2cG1Ec1RAFZnBxW3yur 5t+znaLNgVovfaqYbHAvFDd9HAPlYCMSKiRde+NBC6MJy1sSp986kduxTnvtPt4rHBSOg08E Q87P+8+MbE9MzbeI/YXU3o1X2PXCKT57ZIlrRDGjYcohWUu0AjPE1a1g==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 08/14] devtool: ide-sdk: vscode replace scripts Date: Tue, 30 Dec 2025 09:46:48 +0100 Message-ID: <20251230084720.2371227-9-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228641 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 Tue Dec 30 08:46:49 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77665 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 BF046E95A68 for ; Tue, 30 Dec 2025 08:47:39 +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.60912.1767084455798151347 for ; Tue, 30 Dec 2025 00:47:36 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=ET+ueoDs; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-202512300847337bcd0fa87600020761-jzgjmw@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 202512300847337bcd0fa87600020761 for ; Tue, 30 Dec 2025 09:47:33 +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=ET+ueoDsERR98YMfd6pSV/hJ1z+2DbTrzOinLMBar9H078HkToZo7ZwMvH9whRWrmKJawo 5LaxOGxw7vWwAFe5SMud6LRlNa1xIetdl0JXmvxXmqP7+DvAPnekNyNYyRFxRgqnzXyjmAK1 Uhemm42f9gSJiTsnkclGTJwl/g92BD3qp3NxvsM5MKUm6sGt2MoMHjZZCIE3rwOo+/dSgiYg vhVTLM8XuknSeS/J3cEoDMGY19Ktf+zMtUyVGVL4a7xc1RP0YNupMvtMdINE4LR6otIqja8b gnw6szPqGqxBBz5Uls7Tm0rtSX80v5BrxoACTR8LLMkA7rsieZcApEbg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 09/14] oe-selftest: devtool ide-sdk cover vscode remote debugging Date: Tue, 30 Dec 2025 09:46:49 +0100 Message-ID: <20251230084720.2371227-10-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228635 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 Tue Dec 30 08:46:50 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77664 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 A245AE95A62 for ; Tue, 30 Dec 2025 08:47:39 +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.60916.1767084455798713423 for ; Tue, 30 Dec 2025 00:47:36 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=HsA0EfMz; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.225, mailfrom: fm-1329275-20251230084733ba141674810002073d-xi2loq@rts-flowmailer.siemens.com) Received: by mta-65-225.siemens.flowmailer.net with ESMTPSA id 20251230084733ba141674810002073d for ; Tue, 30 Dec 2025 09:47:33 +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=HsA0EfMz2aCZPGmYOtYX0mnYMxmzhkmapHrRCf819fNExRkNYH2SEJtZf0bzW/G0ij2MQq mnbclfXpBgwnJG7JhlKO/0wChKnNjKGdhkivOpHQx0F/cRH4bR2Vfuo4YY1nHyDLsoxZpXBh TZKSRY3wfo8nc8+9wfjJRGjGIKLYnpKm8REPDV9hqf13/pAtUPWJrk9i8YO/E1lttdTya7km gSv57xEAm0k4QZL3DwkGrEe4Zxel0khuVdPfR0np8txqkJnCJzKVG37SOcxZJA4PmPafM7VB YbzNP7OHXHOyABnmphoVnO23pY5f7OXd9VTYgSOM/DgQhpjtoFIB+AjA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 10/14] devtool: ide-sdk: evaluate DEBUG_PREFIX_MAP Date: Tue, 30 Dec 2025 09:46:50 +0100 Message-ID: <20251230084720.2371227-11-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228632 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 Tue Dec 30 08:46:51 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77652 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 59CFCE9412E for ; Tue, 30 Dec 2025 08:47:39 +0000 (UTC) Received: from mta-64-226.siemens.flowmailer.net (mta-64-226.siemens.flowmailer.net [185.136.64.226]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.60940.1767084455796884409 for ; Tue, 30 Dec 2025 00:47:37 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=J9xQLN8A; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.226, mailfrom: fm-1329275-202512300847336cd5eec59700020731-knhjpi@rts-flowmailer.siemens.com) Received: by mta-64-226.siemens.flowmailer.net with ESMTPSA id 202512300847336cd5eec59700020731 for ; Tue, 30 Dec 2025 09:47:34 +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=J9xQLN8AGhrHGR1ErXIeaddy6rojdjk609Bgj05lrgQLip4zZDkQ9Qj/aiY1qoNjy7bHg9 sWPmgXYmoNlxdfHzWACE8BK9WuyI7dHa6tWvPKlZ9U6h9J2k3BE1MD2VxJDwk5vajUse6wzQ 1P5XhB7fFWJ0i/L2I5Yl8Asm6XKvkcbBS3jF8irTA0ZYCzJTUvXMAWcezH3u+oHSkqi3E729 2Bp/8yaUIhWDWk4RkMLhg5c3/mwrKpln5L3mpLInB3HetNMSggPmHVBzAyFgF+HTNRgYRPvY RXRLjT4YSrQONzEH+6GOszr1zM2HmbTFUhgYJo0fLSBpVfshO5YQBL2Q==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 11/14] cpp-example: Add std::vector example Date: Tue, 30 Dec 2025 09:46:51 +0100 Message-ID: <20251230084720.2371227-12-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228642 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 Tue Dec 30 08:46:52 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77661 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 A7DBFE95A60 for ; Tue, 30 Dec 2025 08:47:39 +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.msgproc02-g2.60918.1767084455887591795 for ; Tue, 30 Dec 2025 00:47:36 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=mA17Zyek; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.226, mailfrom: fm-1329275-20251230084734ade2407d3d00020761-zvxsa7@rts-flowmailer.siemens.com) Received: by mta-65-226.siemens.flowmailer.net with ESMTPSA id 20251230084734ade2407d3d00020761 for ; Tue, 30 Dec 2025 09:47:34 +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=mA17ZyekozfnKtGNoT486Kq0GrX2/fmHUmxjzB9OQdQNnz8eTZcmK+Lmwigm6OsoIbCIeB KgTEWe25mBaUD+WHOCkFwTQcGQtABt/rMi/mfMmXnSqdVDPfaj6Zb0VjV0mT6ao5A2NzCPOV NlWPdXje9A3VXHY0njD+ZAObr7I6xhwc7SM8QuRFHC6L+OJWlojvAGLjXVVTU5aFechr/UQL JN77RiBCblbftW6Da47nicqsJ0d67vtr2Pww7CuxbawLdqgI5LByVfwMAmpQrTZowe3RBtoh uEJ2eL/PqihnfFlT5mtgCfQW3IcqgYV/a0jf+rPtbxG6RX93JvPRc7pA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 12/14] devtool: ide-sdk: Support GDB pretty-printing for C++ STL types Date: Tue, 30 Dec 2025 09:46:52 +0100 Message-ID: <20251230084720.2371227-13-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228631 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 Tue Dec 30 08:46:53 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77660 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 975D7E9413C for ; Tue, 30 Dec 2025 08:47:39 +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.msgproc02-g2.60921.1767084455942101399 for ; Tue, 30 Dec 2025 00:47:36 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=Ziv0U3Wq; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.226, mailfrom: fm-1329275-2025123008473417184df6590002070a-pegow5@rts-flowmailer.siemens.com) Received: by mta-65-226.siemens.flowmailer.net with ESMTPSA id 2025123008473417184df6590002070a for ; Tue, 30 Dec 2025 09:47:34 +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=QkgPYa6oIRStytE7j5F1QHlhs5dQlxOvVoR78o4a8Zk=; b=Ziv0U3WqxTpBZc1jXJZs+r0vXTN/ZXSOUjIK0KwjXclUQfJ/whJyagA3RGLp9S3EQZNzCP /a0tzFLKUhuaMWoZeATXTL4WVGmcWejVfJS1E7CBzWWIoyQczStnHCnfSlovNSzLXeKuRZjI /o4McfmKF3knYIkeVki2itVDvhuYO7oHvam24TFMKxliYZyPsqeyS2oB5nXkqBBDW2lKuJk+ UR5Ja33tzqCtMYe/yoXEkaDsojZdLTqYpNvunuAL2sdIElRbjexVnV7vbBg3kCCLmv14px8d gVKmGTCw4YrxZYZF8f+0I08Lf6vhlbNJLcUn+6kZmntt87lsHTo1ydWg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 13/14] oe-selftest: devtool: add test for gdb pretty-printing Date: Tue, 30 Dec 2025 09:46:53 +0100 Message-ID: <20251230084720.2371227-14-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228633 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 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index df5c863a85..092ffd5e9e 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2725,6 +2725,12 @@ 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 + gdb_batch_cmd += " -ex 'list cpp-example.cpp:55,55'" + gdb_batch_cmd += " -ex 'break cpp-example.cpp:55'" + gdb_batch_cmd += " -ex 'continue'" + gdb_batch_cmd += " -ex 'print numbers'" gdb_batch_cmd += " -ex 'continue'" return gdb_batch_cmd @@ -2734,6 +2740,11 @@ 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.assertIn("$3 = std::vector of length 3, capacity 3 = {1, 2, 3}", gdb_output) + self.assertIn("exited normally", gdb_output) def _gdb_cross_debugging_multi(self, qemu, recipe_name, example_exe, magic_string): From patchwork Tue Dec 30 08:46:54 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 77658 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 8531DE9413A for ; Tue, 30 Dec 2025 08:47:39 +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.60922.1767084455973752649 for ; Tue, 30 Dec 2025 00:47:36 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=exqJMUJl; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.226, mailfrom: fm-1329275-20251230084734715d90367f0002079d-cas2ek@rts-flowmailer.siemens.com) Received: by mta-64-226.siemens.flowmailer.net with ESMTPSA id 20251230084734715d90367f0002079d for ; Tue, 30 Dec 2025 09:47:34 +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=zFY7iwpeCYRnLCURU7AmoVu6wXWgmkiyAFaFWBi6jKE=; b=exqJMUJlhMlaW0Leu5LdN13sqGXq9nzZ2zKM5CnbaGGx8efsR0YjZk5VX0ZRK4LX648/cJ IUgr89r0bh5ji4qROAkywqxOuicFi4ohS3eIwFz7uxvi4pCGY+qv9MM9NISYb3HYX0NxA8Ro 1iRJVVpF9+BVEM8dA5irKgRsAIdAoB1PLO0zcttatccdd53TPweV9ScrH65ydRbAf7vVCxCk TCnUeYtCW/m+BgQMkrfW3gyomhh2MlB+GGPzkE9wg1Nvf6cSVpVHDinv8msKsP7su4xoD5Gu dgZjtbEyCDaWM+0mkV3NA4i41vWQiqhGqr9kwI5oQNSDDkaa3iLre9Ww==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 14/14] oe-selftest: devtool: add compile step in ide-sdk tests Date: Tue, 30 Dec 2025 09:46:54 +0100 Message-ID: <20251230084720.2371227-15-adrian.freihofer@siemens.com> In-Reply-To: <20251230084720.2371227-1-adrian.freihofer@siemens.com> References: <20251230084720.2371227-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 ; Tue, 30 Dec 2025 08:47:39 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/228636 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 092ffd5e9e..6d107df778 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 @@ -2817,6 +2821,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) @@ -2826,28 +2831,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) @@ -2897,8 +2933,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( @@ -2929,7 +2965,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(