From patchwork Thu Sep 18 21:07:03 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70532 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 C7DACCAC59A for ; Thu, 18 Sep 2025 21:08:20 +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.web11.272.1758229693678711923 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=LeatWZRu; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.226, mailfrom: fm-1329275-2025091821081141ec2e213e00020795-465u0p@rts-flowmailer.siemens.com) Received: by mta-65-226.siemens.flowmailer.net with ESMTPSA id 2025091821081141ec2e213e00020795 for ; Thu, 18 Sep 2025 23:08:11 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=xIS3rbVE2szwqIXaoq8AjT6E4BvnEDcpuBitHLnwjVo=; b=LeatWZRuLMQiMQKUCFV2kcyUEzcf8Q7wJa1cIacoONtlDJtKDR5mkXKyPQCKK/Np+Wal4t YHZGnwyFA24T9KUzDha8jQ0tScAzGoDUVKJFT1CqJ5agsozPzotTx0GPLhpDInBwB/nonuig 7ROdG46LglSfiAA8WiJWSG/J4rrD7Rp7lne8Y7FToY3N4w1sv60o8oLYjVANxH+e00PAnniZ sfkAJcVy0t4TOk2dgVMuxj8ymPZCwp+vbPYo94d3GP0ZmZGoV5dbxWjGntE63MI6OPGVtlrR IiTVrVz+BSXmLyu22dYzmTXDJG7lL1MXxTtAFwIEqnVzvnjD+Y69Qu2A==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 01/19] fedora_essential.sh: add util-linux-script package Date: Thu, 18 Sep 2025 23:07:03 +0200 Message-ID: <20250918210754.477049-2-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:20 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223659 From: Adrian Freihofer The util-linux-script package provides the 'script' command which is required by devtool for terminal session recording functionality. Without this package this error can occur on Fedora 42: /bin/sh: line 1: script: command not found Traceback (most recent call last): File "/home/adrian/projets/oss/poky/scripts/devtool", line 352, in ret = main() File "/home/adrian/projets/oss/poky/scripts/devtool", line 338, in main ret = args.func(args, config, basepath, workspace) File "/home/adrian/projets/oss/poky/scripts/lib/devtool/ide_sdk.py", line 980, in ide_setup exec_build_env_command( ~~~~~~~~~~~~~~~~~~~~~~^ config.init_path, basepath, bb_cmd_early, watch=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/adrian/projets/oss/poky/scripts/lib/devtool/__init__.py", line 47, in exec_build_env_command return exec_watch('%s%s' % (init_prefix, cmd), **options) File "/home/adrian/projets/oss/poky/scripts/lib/devtool/__init__.py", line 73, in exec_watch raise bb.process.ExecutionError(cmd, process.returncode, buf, None) bb.process.ExecutionError: Execution of 'script -e -q -c "bitbake oe-selftest-image:do_build \ cmake-example:do_install gdb-cross-x86_64:do_addto_recipe_sysroot meson-example:do_install" « /dev/null' failed with exit code 127: /bin/sh: line 1: script: command not found Signed-off-by: Adrian Freihofer --- documentation/tools/host_packages_scripts/fedora_essential.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/tools/host_packages_scripts/fedora_essential.sh b/documentation/tools/host_packages_scripts/fedora_essential.sh index ea14134398b..f65b0494e85 100644 --- a/documentation/tools/host_packages_scripts/fedora_essential.sh +++ b/documentation/tools/host_packages_scripts/fedora_essential.sh @@ -1 +1 @@ -sudo dnf install bzip2 ccache chrpath cpio cpp diffstat diffutils file findutils gawk gcc gcc-c++ git glibc-devel glibc-langpack-en gzip hostname libacl make patch perl perl-Data-Dumper perl-File-Compare perl-File-Copy perl-FindBin perl-Text-ParseWords perl-Thread-Queue perl-bignum perl-locale python python3 python3-GitPython python3-jinja2 python3-pexpect python3-pip rpcgen socat tar texinfo unzip wget which xz zstd +sudo dnf install bzip2 ccache chrpath cpio cpp diffstat diffutils file findutils gawk gcc gcc-c++ git glibc-devel glibc-langpack-en gzip hostname libacl make patch perl perl-Data-Dumper perl-File-Compare perl-File-Copy perl-FindBin perl-Text-ParseWords perl-Thread-Queue perl-bignum perl-locale python python3 python3-GitPython python3-jinja2 python3-pexpect python3-pip rpcgen socat tar texinfo unzip util-linux-script wget which xz zstd From patchwork Thu Sep 18 21:07:04 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70537 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 07369CAC5AF for ; Thu, 18 Sep 2025 21:08:21 +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.web11.273.1758229693715013407 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=e/mhWKRo; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.227, mailfrom: fm-1329275-2025091821081175b0c802a7000207da-fjbz2w@rts-flowmailer.siemens.com) Received: by mta-65-227.siemens.flowmailer.net with ESMTPSA id 2025091821081175b0c802a7000207da for ; Thu, 18 Sep 2025 23:08:11 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=mBv4YH3kw6M5NcYb0EldzR7fnI5fUgqrL7iIHC8HWDA=; b=e/mhWKRopH2RscPKrXjRI1yUBa+TwG+EOJcy+sJqvlSnWszgXQFph1MqjZ2PkB0upojzxp ps34pjDtHelxuvvHrFCJkrAOM3E06KiaMw7hrHpWOuLi1oFy685QkBQ6JTpV1BsoTWISmbPt 7ef5Emn6YfQ3D5Nee7iIXqVcOhXaTT7o/pC2VqmDE0W7Kb/dv/a6oB4nD4sBGVhA8CIygMuu ZbKcQxfjSLfNO2t3YKCiTZzlROmlPffhgIyY+waz5OWYOSGLGQCtgeG51mpxMrq87PEdNDbv +p7AQTPF2uR21jripSe3GkVryHIsSYw1pS4RsSO4T6JtkL0xq0Evteog==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 02/19] oe-selftest: cpp-example meson version warning Date: Thu, 18 Sep 2025 23:07:04 +0200 Message-ID: <20250918210754.477049-3-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223662 From: Adrian Freihofer Newer versions of meson throw a warning if newer features are used without explicit declaration of the required meson version. Signed-off-by: Adrian Freihofer --- meta-selftest/recipes-test/cpp/files/meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meta-selftest/recipes-test/cpp/files/meson.build b/meta-selftest/recipes-test/cpp/files/meson.build index 0e2b55f3a2b..74a0e0173ce 100644 --- a/meta-selftest/recipes-test/cpp/files/meson.build +++ b/meta-selftest/recipes-test/cpp/files/meson.build @@ -6,7 +6,8 @@ project('meson-example', 'cpp', version: '1.0.0', - default_options: ['cpp_std=c++17'] + default_options: ['cpp_std=c++17'], + meson_version: '>=1.1.0' ) jsoncdep = dependency('json-c') From patchwork Thu Sep 18 21:07:05 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70546 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 5D087CAC5BA for ; Thu, 18 Sep 2025 21:08:21 +0000 (UTC) Received: from mta-64-228.siemens.flowmailer.net (mta-64-228.siemens.flowmailer.net [185.136.64.228]) by mx.groups.io with SMTP id smtpd.web10.305.1758229693813018085 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=j3+AEDqc; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.228, mailfrom: fm-1329275-20250918210811f03bd141f90002077d-ygx63a@rts-flowmailer.siemens.com) Received: by mta-64-228.siemens.flowmailer.net with ESMTPSA id 20250918210811f03bd141f90002077d for ; Thu, 18 Sep 2025 23:08:11 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=jHuoZ1vmCMrzN1A70z6651NN2iQBJrCokS/381CBZx4=; b=j3+AEDqc26S5GgAH7J6zQv6sl4nYGor/+THyX1m47xHShsrtCGce41Dgbdswy5lhISn+Kd VdYSreCbXIQ4C1R/PKLBtxdkSvjxOSGEywQERPGPJOUrdcrz/iJdRVuDGU7luF+wiyAx+WOK izdcllrzh4sZowqKQcnLHpQ2y4ljRYaXIv9kFtqu1FA9YD+Bh7kZEcHcGfGjxmmzophbb3im Oe4NYZ40XKBnM4/ZrapPRMkJZWFfr3sYVJHZ1cN6c5CODUQMCS03EAvk/MxM2kOpPk5m6BvC m7kOVJhiFZ+uo3nlIN/9eG1/g2Lu6gsrCp5SnAO5byIk8Ciw/q7vdNhw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 03/19] oe-selftest: devtool: add missing imports Date: Thu, 18 Sep 2025 23:07:05 +0200 Message-ID: <20250918210754.477049-4-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223660 From: Adrian Freihofer Cleanup some indirect imports. This does not solve a real problem, but it fixes some issues with IDEs that do not properly resolve indirect imports. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 19a205912ba..b92f017b811 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -18,12 +18,11 @@ from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer from oeqa.utils.commands import get_bb_vars, runqemu, runqemu_check_taps, get_test_layer from oeqa.core.decorator import OETestTag +from bb.utils import mkdirhier, edit_bblayers_conf oldmetapath = None def setUpModule(): - import bb.utils - global templayerdir templayerdir = tempfile.mkdtemp(prefix='devtoolqa') corecopydir = os.path.join(templayerdir, 'core-copy') @@ -79,12 +78,12 @@ def setUpModule(): shutil.copytree(pth, destdir, ignore=shutil.ignore_patterns('*.pyc', '__pycache__')) else: destdir = os.path.join(corecopydir, os.path.dirname(relpth)) - bb.utils.mkdirhier(destdir) + mkdirhier(destdir) shutil.copy2(pth, destdir) return newmetapath else: return layerpath - bb.utils.edit_bblayers_conf(bblayers_conf, None, None, bblayers_edit_cb) + edit_bblayers_conf(bblayers_conf, None, None, bblayers_edit_cb) def tearDownModule(): if oldmetapath: @@ -96,7 +95,7 @@ def tearDownModule(): else: return layerpath bblayers_conf = os.path.join(os.environ['BUILDDIR'], 'conf', 'bblayers.conf') - bb.utils.edit_bblayers_conf(bblayers_conf, None, None, bblayers_edit_cb) + edit_bblayers_conf(bblayers_conf, None, None, bblayers_edit_cb) shutil.rmtree(templayerdir) class DevtoolTestCase(OESelftestTestCase): @@ -404,7 +403,7 @@ class DevtoolAddTests(DevtoolBase): test_file_content = "TEST CONTENT" test_file_package_root = os.path.join(tempdir, pn) test_file_dir_full = os.path.join(test_file_package_root, test_file_dir) - bb.utils.mkdirhier(test_file_dir_full) + mkdirhier(test_file_dir_full) with open(os.path.join(test_file_dir_full, test_file_name), "w") as f: f.write(test_file_content) bin_package_path = os.path.join(tempdir, "%s.tar.gz" % pn) From patchwork Thu Sep 18 21:07:06 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70538 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 27818CAC5B0 for ; Thu, 18 Sep 2025 21:08:21 +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.web11.276.1758229693986857871 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=pcjPZcxi; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.226, mailfrom: fm-1329275-2025091821081163abda4191000207c0-hcpsbl@rts-flowmailer.siemens.com) Received: by mta-64-226.siemens.flowmailer.net with ESMTPSA id 2025091821081163abda4191000207c0 for ; Thu, 18 Sep 2025 23:08:11 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=6b4hKb8rHzWV1fYmCcbbh9GsQZ4QbFFp8rLDBmuHjTo=; b=pcjPZcxiXNJU141VFbQhlj3NLWSEg2mC6kpXKByG/IiQrCp3lAYZWZPxUkFR2s2YtcgWdn NBr7dsbbnDrYm5hLNW4d9dYkXXzU1o0KDJFmcReY/av8VSxXMp29q1mhJPY3zxI4JxSd0mMC 68Y8ZKcy7hxdZDvZmrtDsJz3XpdD8Ter4Op5ewlzkLz23JU4E5EUBX03MshXXwwJYxd2cUoN zGaaNCD5Biej83U4V4TB9vYAQF6h9m0rFKQO3jy7MUOUycE+H+4qf/kJib6BsTk9o2gJiF66 BhyJLxHMcC3+ueQ09NDJRWSBFW5bvQr0DrXJHQ/SJfZTQahUJ8ff70hQ==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 04/19] meta-skeleton: fix spaces in assignment Date: Thu, 18 Sep 2025 23:07:06 +0200 Message-ID: <20250918210754.477049-5-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223668 From: Adrian Freihofer There is now a warning for = without spaces. Fix it also in meta-skeleton. Signed-off-by: Adrian Freihofer --- meta-skeleton/recipes-kernel/linux/linux-yocto-custom.bb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-skeleton/recipes-kernel/linux/linux-yocto-custom.bb b/meta-skeleton/recipes-kernel/linux/linux-yocto-custom.bb index 0879bb17b91..0ceb6ed5845 100644 --- a/meta-skeleton/recipes-kernel/linux/linux-yocto-custom.bb +++ b/meta-skeleton/recipes-kernel/linux/linux-yocto-custom.bb @@ -62,7 +62,7 @@ LINUX_VERSION_EXTENSION:append = "-custom" # Modify SRCREV to a different commit hash in a copy of this recipe to # build a different release of the Linux kernel. # tag: v4.2 64291f7db5bd8150a74ad2036f1037e6a0428df2 -SRCREV_machine="64291f7db5bd8150a74ad2036f1037e6a0428df2" +SRCREV_machine = "64291f7db5bd8150a74ad2036f1037e6a0428df2" PV = "${LINUX_VERSION}+git" From patchwork Thu Sep 18 21:07:07 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70534 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 DE65CCAC5AA for ; Thu, 18 Sep 2025 21:08:20 +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.web10.307.1758229694012141509 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=JFLF5Q0d; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.226, mailfrom: fm-1329275-20250918210811dfb089dfba00020786-uzxpm9@rts-flowmailer.siemens.com) Received: by mta-64-226.siemens.flowmailer.net with ESMTPSA id 20250918210811dfb089dfba00020786 for ; Thu, 18 Sep 2025 23:08:11 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=MCOKuvODp83b+76LeJ5NqkYcvabU1izJdajQ6T3uVBM=; b=JFLF5Q0dvNcV6X0FtJiMTXTX5jeyQCkGIlgYKLFD1F5Ym/uT7HPSci5hD3kPiAndl/k65t +nnEmO05+MM5cJmosQ7/mktxLeL0RfXHQaja14xJMzGhNT1TgKPFflF8umE/SkqPsO0KcQ2n pHw5jP2g+7qitGzBL4qVT8orfFN85AbkXEjKvbNfeUVFpk8mPTK4LBq74VmufxX8Tmd6NN8q azoWr9+z9SeMiIwiLmgxI5ObeLfui0kpwImQ/xlTly3yejTV8dupDMvmIa+Q3q8LYu9bjKAi IIXIYEJduiCLlT66G55Tjumufo26xN3dvbbE8Ebmu9iYNSasN+j69kxg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 05/19] devtool: ide-sdk deploy-target without bitbake Date: Thu, 18 Sep 2025 23:07:07 +0200 Message-ID: <20250918210754.477049-6-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:20 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223669 From: Adrian Freihofer The main goal of devtool ide-sdk is to use bitbake for generating SDKs and consistent IDE configurations for working on the source code of one or more recipes in workspaces created by devtool modify. When the IDE is configured for a build system such as CMake or Meson, the IDE must not invoke bitbake. But bitbake can be called explicitly whenever something on the recipes changes and the image, the sysroots or the IDE configuration needs to be updated. A cross development workflow usually requires the following steps with clear separation of responsibilities between the IDE and bitbake: - Setup the complete build environment (bitbake-setup) - Get the layers: - Initialize the build configuration - Choose some recipes to work on (devtool modify) - Setup the image and the SDK (devtool ide-sdk, shared sstate-cache) - Work on the recipe in the workspace (IDE) - (re-)configure - compile - run unit tests (on the host with Qemu) - remote debuggging on the target device which includes - do_install (IDE,pseudo) - deploying the artifacts to the target device (IDE,pseudo) - start gdbserver on the target device - start GDB on the host device - Update the SDK and image (bitbake, devtool) For the configure, compile and unit test tasks there is already full IDE integration, without any call to bitbake. But for the remote debugging or more precisely for the deployment to the target device, bitbake gets called which has several disadvantages: - Bitbake runs a work queue for all tasks of the recipe. There is no way to run only the do_install task. Bitbake always tries to find out if a previous task needs to be executed and automatically does so. Since bitbake has no information about which steps have already been executed by the IDE, there is a great chance that bitbake does much more than necessary. It could also happen that bitbake ignores changes done by the developer in the IDE and overrules some manual steps. This leads to a very bad user experience. Therefore it is really important to delegate all steps for the application development workflow to the IDE and use bitbake only for providing and updating the SDK and the image. An example of this is that bitbake recompiles everything when a file listed in the CONFIGURE_FILES variable is changed. This makes no sense in the IDE context and can be extremely tedious. The complexity of resolving many dependencies is simply not part of an application development workflow. So there is no argument for invoking a powerful but heavy tool. Bitbake adds complexity that application developers usually don't want to understand or debug. As soon as bitbake is needed for daily work, most application developers have a defensive attitude towards the overall solution. Parsing all recipes, creating task queues and running tasks which do not have to be executed makes it very slow, which is another major disadvantage. - bitbake -T (mem-res server mode) could avoid re-parsing. But when bitbake is needed to update the SDK or build an image, reparsing all recipes is essential. Since inotify has been removed from bitbake, the -T option is by design no longer usable to run a bitbake server as an IDE backend. Starting bitbake with -T requires manually stopping bitbake whenever reparsing is expected. That's not intuitive at all. - Bitbake -b is very quick but ignores bbappends. This is a bug which probably could be fixed. But it does not look like the solution for the overall problem here anyway. Therefore a much better user experience can be achieved if all tasks listed above are calls to a build tool such as cmake or self-contained scripts which are under control of the IDE. This commit refactors the code to no longer invoke bitbake for the target-deploy step. The script which is used by the IDE for the deployment of the artifacts gets improved to become a self-contained script which uses pseudo but does not call bitbake anymore. The script re-implements some bitbake internals and needs to be kept in sync with bitbake which is not perfect. But refactoring bitbake's code to become reusable for this script as well looks not feasible at all. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 6 +- scripts/lib/devtool/ide_sdk.py | 82 ++++++++++++++++++++----- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index b92f017b811..f262e0e214c 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2599,7 +2599,7 @@ class DevtoolIdeSdkTests(DevtoolBase): """Verify the scripts referred by the tasks.json file are fine. This function does not depend on Qemu. Therefore it verifies the scripts - exists and the delete step works as expected. But it does not try to + exists and the install step works as expected. But it does not try to deploy to Qemu. """ recipe_id, recipe_id_pretty = self._get_recipe_ids(recipe_name) @@ -2614,6 +2614,10 @@ class DevtoolIdeSdkTests(DevtoolBase): i_and_d_script_path = os.path.join( self._workspace_scripts_dir(recipe_name), i_and_d_script) self.assertExists(i_and_d_script_path) + i_script = "bb_run_do_install_" + recipe_id + install_cmd_path = i_and_d_script_path.replace(i_and_d_script, i_script) + self.assertExists(install_cmd_path) + runCmd(install_cmd_path, cwd=tempdir) def _devtool_ide_sdk_qemu(self, tempdir, qemu, recipe_name, example_exe): """Verify deployment and execution in Qemu system work for one recipe. diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py index 931408fa74e..8b347c904e1 100755 --- a/scripts/lib/devtool/ide_sdk.py +++ b/scripts/lib/devtool/ide_sdk.py @@ -306,6 +306,9 @@ class RecipeModified: self.topdir = None self.workdir = None self.recipe_id = None + # recipe variables from d.getVarFlags + self.f_do_install_cleandirs = None + self.f_do_install_dirs = None # replicate bitbake build environment self.exported_vars = None self.cmd_compile = None @@ -374,6 +377,11 @@ class RecipeModified: self.topdir = recipe_d.getVar('TOPDIR') self.workdir = os.path.realpath(recipe_d.getVar('WORKDIR')) + self.f_do_install_cleandirs = recipe_d.getVarFlag( + 'do_install', 'cleandirs').split() + self.f_do_install_dirs = recipe_d.getVarFlag( + 'do_install', 'dirs').split() + self.__init_exported_variables(recipe_d) if bb.data.inherits_class('cmake', recipe_d): @@ -675,6 +683,63 @@ class RecipeModified: binaries.append(abs_name[d_len:]) return sorted(binaries) + def gen_fakeroot_install_script(self): + """Generate a helper script to execute make install with pseudo + + For the deployment to the target device the do_install task must be + executed out of the IDE as well. This function generates a script which + runs the run.do_install script from bitbake under pseudo so that it picks + up the appropriate file permissions. Generating a self-contained script + is much quicker than calling bitbake or devtool build from an IDE. + This also avoids that bitbake is calling other tasks which might interfere + with the users goals. + """ + cmd_lines = ['#!/bin/sh'] + + # Ensure the do compile step gets always executed without pseudo before do install + if self.cmd_compile: + cmd_compile = "( cd %s && %s)" % ( + self.real_srctree, self.cmd_compile) + cmd_lines.append(cmd_compile) + + # Check run.do_install script is available + if not os.access(self.fakerootcmd, os.X_OK): + raise DevtoolError( + "pseudo executable %s could not be found" % self.fakerootcmd) + run_do_install = os.path.join(self.workdir, 'temp', 'run.do_install') + if not os.access(run_do_install, os.X_OK): + raise DevtoolError( + "run script does not exists: %s" % run_do_install) + + # Set up the appropriate environment + newenv = dict(os.environ) + for varvalue in self.fakerootenv.split(): + if '=' in varvalue: + splitval = varvalue.split('=', 1) + newenv[splitval[0]] = splitval[1] + + # Replicate the environment variables from bitbake + for var, val in newenv.items(): + if not RecipeModified.is_valid_shell_variable(var): + continue + cmd_lines.append('%s="%s"' % (var, val)) + cmd_lines.append('export %s' % var) + + # Setup the task environment as bitbake would do it based on the varFlags + for d in self.f_do_install_cleandirs: + cmd_lines.append('%s rm -rf %s' % (self.fakerootcmd, d)) + for d in self.f_do_install_dirs: + cmd_lines.append('%s mkdir -p %s' % (self.fakerootcmd, d)) + if len(self.f_do_install_dirs) > 0: + cmd = "cd %s" % self.f_do_install_dirs[-1] + cmd_lines.append('%s || { "%s failed"; exit 1; }' % (cmd, cmd)) + + # Finally call run.do_install on pseudo + cmd = "%s %s" % (self.fakerootcmd, run_do_install) + cmd_lines.append('%s || { echo "%s failed"; exit 1; }' % (cmd, cmd)) + + return self.write_script(cmd_lines, 'bb_run_do_install') + def gen_deploy_target_script(self, args): """Generate a script which does what devtool deploy-target does @@ -710,22 +775,9 @@ class RecipeModified: def gen_install_deploy_script(self, args): """Generate a script which does install and deploy""" - cmd_lines = ['#!/bin/bash'] - - # . oe-init-build-env $BUILDDIR - # Note: Sourcing scripts with arguments requires bash - cmd_lines.append('cd "%s" || { echo "cd %s failed"; exit 1; }' % ( - self.oe_init_dir, self.oe_init_dir)) - cmd_lines.append('. "%s" "%s" || { echo ". %s %s failed"; exit 1; }' % ( - self.oe_init_build_env, self.topdir, self.oe_init_build_env, self.topdir)) - - # bitbake -c install - cmd_lines.append( - '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 = ['#!/bin/sh -e'] + cmd_lines.append(self.gen_fakeroot_install_script()) cmd_lines.append(self.gen_deploy_target_script(args)) - return self.write_script(cmd_lines, 'install_and_deploy') def write_script(self, cmd_lines, script_name): From patchwork Thu Sep 18 21:07:08 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70549 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 3F3A2CAC5B7 for ; Thu, 18 Sep 2025 21:08:21 +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.web11.274.1758229693806732493 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=ly3hq1kM; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-202509182108111c95df7ac50002073b-5xv5gs@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 202509182108111c95df7ac50002073b for ; Thu, 18 Sep 2025 23:08:11 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=W4lI11m6dg1yIwwLtXgjXaPUaXI1NLrbFD3tmysIrbc=; b=ly3hq1kMNUsXOG5lP7HqImQN1GLX/j+gsgXafNolcEcQSWCxaRwHuxEmHFTC0265UfEPew OiVR21Asa8j79ieeNAPDQsKKfpbdTT8/4YUrJDxl+FZrCsBanqK8088hj5Zj19UIjy2N0TVo V8SLDMejI9vh7kBW64zMZJPukBXjqc4MaFQxYz3Xrxkt8/5Bt6uAEscVMcP5tcmuxxSpGvee zkJ1UVIq7gL+sucB+GQck8Kf+OsQxaV3x7AmFSao9C2svMOYgO8ozOVtssFfhhQMz9LtH3zl ZS4AKouUDP2b29lSR4Rw5SekYbfxn3S75hFVnZm6UvEusNdZxMUh4Wwg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 06/19] oe-selftest: devtool: DevtoolIdeSdkTests debug logging Date: Thu, 18 Sep 2025 23:07:08 +0200 Message-ID: <20250918210754.477049-7-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223664 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 | 92 ++++++++++++++----------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index f262e0e214c..c9d03cfcf51 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 @@ -2523,6 +2524,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 = [ @@ -2562,7 +2570,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', @@ -2572,7 +2580,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) @@ -2617,7 +2625,7 @@ class DevtoolIdeSdkTests(DevtoolBase): i_script = "bb_run_do_install_" + recipe_id install_cmd_path = i_and_d_script_path.replace(i_and_d_script, i_script) self.assertExists(install_cmd_path) - runCmd(install_cmd_path, cwd=tempdir) + runCmd(install_cmd_path, cwd=tempdir, output_log=self._cmd_logger) def _devtool_ide_sdk_qemu(self, tempdir, qemu, recipe_name, example_exe): """Verify deployment and execution in Qemu system work for one recipe. @@ -2632,7 +2640,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" @@ -2665,7 +2673,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) @@ -2692,7 +2700,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) @@ -2720,18 +2728,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) @@ -2742,7 +2750,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) @@ -2754,11 +2762,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) @@ -2779,29 +2787,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) @@ -2826,7 +2834,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) @@ -2842,7 +2850,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 @@ -2861,7 +2869,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() @@ -2878,7 +2886,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) @@ -2890,20 +2898,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() @@ -2915,7 +2925,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'] @@ -2927,21 +2938,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 = "" @@ -2957,12 +2968,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.""" @@ -2985,7 +2996,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) @@ -3016,7 +3027,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) @@ -3024,14 +3035,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 Thu Sep 18 21:07:09 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70550 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 553EECAC5B8 for ; Thu, 18 Sep 2025 21:08:21 +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.web10.306.1758229693840009071 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=Iw+90N42; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.225, mailfrom: fm-1329275-2025091821081117dda0e9ec00020764-lyed7r@rts-flowmailer.siemens.com) Received: by mta-65-225.siemens.flowmailer.net with ESMTPSA id 2025091821081117dda0e9ec00020764 for ; Thu, 18 Sep 2025 23:08:11 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=Mg6S7gLojFlfbqSEm6HXb19Ic02W0gZ8mbPmBFrEbdY=; b=Iw+90N428V+rmEPm5EVN3GsBMaM46s5LuvjwHenZ59U+kViutNJVKMUOP9J3zKrHhBZT8f rVE5SzTzAO4cNhYfptZ9NDuExKQ2WdazACGdM4Cs57mC4mNpYwtKlY0yVDe3kAZaAbfVtm3I 9stt5dP8ap2M/nAV/LOsHET1IrR4n+4GV/E0+0NDQ0S23ZD6ntyn5ILQZ27BnfoJ3b9Q7Mvl I3rYIFbywgVeK32a7ULqGz3jFTR4zvzmvKJzwC43QxjJBkJR6dEx3+HqjpN8B7dn40CrqovQ vNs4y5f/AwIKk0nAC84/Y5biE0lhDlfc8dc9XDt14SMfVNopoFu/eNAQ==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 07/19] cpp-example: run as a service Date: Thu, 18 Sep 2025 23:07:09 +0200 Message-ID: <20250918210754.477049-8-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223666 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 76ff64e87f5..2653f45e901 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 6fa6917d89b..e363f31af2a 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 00000000000..174e266847c --- /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 d3dc976864b..c510a13893c 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 0ad9e7b7b2d..24dd0defb6f 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 00000000000..4a666e5cdd5 --- /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 9889554e0cb..dbf82f15d97 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 00000000000..c154fd11265 --- /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 00000000000..4022fa291a3 --- /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 74a0e0173ce..53248c43803 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 83c9bfa8444..e1909c31687 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 14a7ca8dc91..da0ea183760 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 c9d03cfcf51..36a1819bd89 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2717,7 +2717,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 """ @@ -2748,7 +2748,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 Thu Sep 18 21:07:10 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70547 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 4B1EBCAC5B6 for ; Thu, 18 Sep 2025 21:08:21 +0000 (UTC) Received: from mta-64-227.siemens.flowmailer.net (mta-64-227.siemens.flowmailer.net [185.136.64.227]) by mx.groups.io with SMTP id smtpd.web11.277.1758229694000737135 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=PxfMTf9I; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.227, mailfrom: fm-1329275-202509182108119bfcde076700020746-2wyeqn@rts-flowmailer.siemens.com) Received: by mta-64-227.siemens.flowmailer.net with ESMTPSA id 202509182108119bfcde076700020746 for ; Thu, 18 Sep 2025 23:08:11 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=WaJl1pxjSBadel/9/PjabqJd9zOeNESv10TUwVvnzpg=; b=PxfMTf9Igd0X8kIMxIyAG4dTVlxCeQsYfc/AjEASfc/WL7enHyHOzQ/9yr8j+1Sy1C4ugl 9ISoVKGtozAl+9XYac8XJ5ZQgBjmjUSXfWljgxL6Cso/pvY40tehX/zcIElC7Ij546ga535m 3AJvUIO2I0FeXlvY7o1A+y+NsqYe083hVXaGptBovwS9dephpRpGbNDuwa3owMUtWmeNusvO cMztQwkwukunD9pghM1aRXUXt7eAEij5IdH9CYva5R5oztMyv6hoQRKj0HXEJnEsGSLe9t7L tP+WcV+Plkn9Wid0e5S7QrmQvqNeFCkw2Fn99y+3AbJ1dKCrRPEB6RVw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 08/19] oe-selftest: devtool: check example services are running Date: Thu, 18 Sep 2025 23:07:10 +0200 Message-ID: <20250918210754.477049-9-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223665 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 36a1819bd89..82ac821cba6 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2813,6 +2813,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.""" @@ -2829,7 +2845,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' % ( @@ -2838,14 +2863,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' % ( @@ -2853,10 +2893,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 Thu Sep 18 21:07:11 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70533 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 C8DE3CAC597 for ; Thu, 18 Sep 2025 21:08:20 +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.web11.271.1758229693677780863 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=G7DKUuqP; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.227, mailfrom: fm-1329275-20250918210811beba1295ba0002077b-xkaosh@rts-flowmailer.siemens.com) Received: by mta-65-227.siemens.flowmailer.net with ESMTPSA id 20250918210811beba1295ba0002077b for ; Thu, 18 Sep 2025 23:08:11 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=r4NXcAd9HhbmM4c391tI3V3uKRZWu/YFr1kmgMtGe04=; b=G7DKUuqPejvzDNq1NarX3npmnf9JQjY03YwtKTQaRB+6jOO4c6q20WmjUX1T/ll6Rt7Obd bEI8MKraJOVcCbAl6H6STUpW/HR8RWyW4zyb60AlAd+gparNcdHIc7ai57n7sFTxOyFGYIgf grbJ++/eVMiGB2RztkBgS4SCxQla2xkaNCeQODRQbVBynZ4+AKuugS0yux6rhU1d4XFjEaq+ 7OXEGllFf98Jv8rwMB/GVj3cA27WKdRpRSknRy2BhJCRmeDS9WaOpoxT/N3mhEgplyvsD9jt /c32Anvnv3/NyC8Hx1DTmvO+nG5vtEUEGvXS58khd83YlryEn+gpASlA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 09/19] devtool: ide-sdk: make install_and_deploy script pass target arg Date: Thu, 18 Sep 2025 23:07:11 +0200 Message-ID: <20250918210754.477049-10-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:20 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223661 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 ee5bb57265b..8b08add2b17 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -287,6 +287,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 8b347c904e1..a829386faf1 100755 --- a/scripts/lib/devtool/ide_sdk.py +++ b/scripts/lib/devtool/ide_sdk.py @@ -765,6 +765,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)' % @@ -777,7 +780,7 @@ class RecipeModified: """Generate a script which does install and deploy""" cmd_lines = ['#!/bin/sh -e'] cmd_lines.append(self.gen_fakeroot_install_script()) - 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') def write_script(self, cmd_lines, script_name): From patchwork Thu Sep 18 21:07:12 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70545 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 15E22CAC5AE for ; Thu, 18 Sep 2025 21:08:21 +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.web11.275.1758229693846405680 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=sG0Q3WmU; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.225, mailfrom: fm-1329275-20250918210811835d139fd100020736-vg2d19@rts-flowmailer.siemens.com) Received: by mta-65-225.siemens.flowmailer.net with ESMTPSA id 20250918210811835d139fd100020736 for ; Thu, 18 Sep 2025 23:08:12 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=3oPuUgVJix6pNxVr8Ug4jKspS/2dI0iM6cdncG0v0xE=; b=sG0Q3WmUZpOl8HJojIwKmRjIWUiwTxymajHGSZIwYLUFDkas3Z0KjRj/w+VL8WwdZW8V4J MRnLJ4n4k8btbUaySYvhP7PeutaYguA5Eo2vshb25uPeoN0HBokbKJLn/F9N5IZiJARUfXzT ExRtVV7os0oQJ/ASEQnoYHykHyWtjcWlZKlGkT6UW/9e32U2ub8q718uejMtVHv9tjqEheV9 zBygeKThy9KrBm/Oi8MMlMJmb8ftT7bdtYmovczEvZyQcyoxyWyIbkP+CNSN7/UYpSjZnfLV NdhLt2b3RTP9WTtL0cCQjvqtWS43gYSfWtBp4o6ClTCBXyzOUbOiQWGg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 10/19] devtool: ide-sdk: add gdbserver attach mode support Date: Thu, 18 Sep 2025 23:07:12 +0200 Message-ID: <20250918210754.477049-11-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223663 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. Note: The recommended approach remains the regular gdbserver configuration using --once mode to start the debugged process. Attach mode works but may require manual cleanup between debug sessions. VSCode's debugger integration also has some limitations that can cause unexpected behavior: https://github.com/microsoft/vscode-cpptools/issues/4243 Despite these caveats, attach mode is a useful addition to the debugging workflow. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 12 +- scripts/lib/devtool/ide_plugins/__init__.py | 214 +++++++++++++------- scripts/lib/devtool/ide_plugins/ide_code.py | 58 +++--- scripts/lib/devtool/ide_plugins/ide_none.py | 14 +- scripts/lib/devtool/ide_sdk.py | 171 +++++++++++++++- 5 files changed, 357 insertions(+), 112 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 82ac821cba6..8bfb73a927c 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2663,7 +2663,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 @@ -2688,7 +2688,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): @@ -2704,7 +2704,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: @@ -2723,7 +2723,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) @@ -2738,7 +2738,7 @@ class DevtoolIdeSdkTests(DevtoolBase): # Check the pid file is correct test_cmd = "cat /proc/$(cat /tmp/gdbserver_1234_usr-bin-" + \ - example_exe + "/pid)/cmdline" + example_exe + "/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) @@ -2750,6 +2750,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 19c2f61c5fd..5f106c2e6b6 100644 --- a/scripts/lib/devtool/ide_plugins/__init__.py +++ b/scripts/lib/devtool/ide_plugins/__init__.py @@ -31,88 +31,151 @@ 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_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 +188,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 +214,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 +233,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 +279,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 8b08add2b17..8306b7318bd 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -9,14 +9,16 @@ 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 initialize(self): self._gen_gdbserver_start_script() @@ -207,20 +209,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) } @@ -268,7 +270,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 @@ -298,15 +301,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": [ @@ -324,7 +324,13 @@ 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) tasks_file = 'tasks.json' IdeBase.update_json_file( self.dot_code_dir(modified_recipe), tasks_file, tasks_dict) @@ -414,15 +420,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": [ @@ -440,7 +443,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) @@ -457,7 +465,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 f106c5a0269..04677aba9d9 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 a829386faf1..3d25848467c 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,111 @@ 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)) + return init_script_path + 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.init_script) + return None + class RecipeModified: """Handling of recipes in the workspace created by devtool modify""" @@ -306,6 +411,9 @@ class RecipeModified: self.topdir = None self.workdir = None self.recipe_id = None + # Service management + self.systemd_services = {} + self.init_scripts = {} # recipe variables from d.getVarFlags self.f_do_install_cleandirs = None self.f_do_install_dirs = None @@ -325,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) @@ -383,6 +494,8 @@ class RecipeModified: 'do_install', 'dirs').split() 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') @@ -503,6 +616,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 @@ -667,9 +815,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): @@ -680,8 +831,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_fakeroot_install_script(self): """Generate a helper script to execute make install with pseudo From patchwork Thu Sep 18 21:07:13 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70548 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 60658CAC5B9 for ; Thu, 18 Sep 2025 21:08:21 +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.web11.278.1758229694014882754 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=uDkMSnSS; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-20250918210812bfe585d0da000207db-kzn0qn@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 20250918210812bfe585d0da000207db for ; Thu, 18 Sep 2025 23:08:12 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=HBelkkWMbUBi7zqOClOQ9ZDDZItSMuOFlFfKEMWNOLs=; b=uDkMSnSSVnQWs8RISo/Do0pSJcNG35AUa69Zl3P9kQYm3kBwC/XV/PdAkxR8M5m2JFOFeE 8cLVqPEd7eFr7jWigZEcP7coIw9pabLhy9PgdfPxKXV0hFQhIDVpE1uQN3z2j860usZlIt8G Fk8Zhls8V1MNKMhuWsYl+j4tC9M5YDmhq6g5JsQov16pLDNgGnSXTo8/KhfwHABvqOmVh7Au L0hXX5DIKbWvFzGKQ2htwMdLnU4nItTH9ON2VH54ch0iPWA3oGKI+1dM6dtyoHNcF5YnnQD8 w16Rdp+HKv7APSc1E0H8iVi3JevzeAbPdZHfbxrOVyjgGtkKkHsuHHzA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 11/19] devtool: ide-sdk: vscode remove scripts Date: Thu, 18 Sep 2025 23:07:13 +0200 Message-ID: <20250918210754.477049-12-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223667 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 | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py index 8306b7318bd..ef49ac71dc8 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -20,9 +20,20 @@ class GdbCrossConfigVSCode(GdbCrossConfig): super().__init__(image_recipe, modified_recipe, binary, gdbserver_default_mode) - def initialize(self): - self._gen_gdbserver_start_script() + 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 initialize(self): + pass class IdeVSCode(IdeBase): """Manage IDE configurations for VSCode @@ -306,7 +317,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": [ @@ -425,7 +437,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 Thu Sep 18 21:07:14 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70542 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 2DAC4CAC5B1 for ; Thu, 18 Sep 2025 21:08:21 +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.web11.280.1758229694215534592 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=DIDLQPis; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.228, mailfrom: fm-1329275-20250918210812dd66616e7a00020733-2wov1b@rts-flowmailer.siemens.com) Received: by mta-65-228.siemens.flowmailer.net with ESMTPSA id 20250918210812dd66616e7a00020733 for ; Thu, 18 Sep 2025 23:08:12 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=MRvQ8FIDRQWWaZZh2eKQdzs5nGoQQX+7b+1ZV7SJjeo=; b=DIDLQPisEghEm1zBVM6somFZ/lpY4wt614o0r95s96UqipIyH4JkUctZw2dJOzsD2Lzwwp sgigns9BK5qVbselOxNsWstr0cLg4fqOcgUAVEH1Zh54LB7B/JMXjt9G6fcg7mhHpX/FJRti nDyBCTFnNwNNwcOcaAzNLN7kKpgWgLu2oMS8XYIEZP0tfS5qn0fNSJFVV8hJJJ2nhidfi+eX +Xj4NUfgH9gaEl8JZNw99aVkyFw76SDJwPuVs84lI/j0zUAPO9cGOosZuyVBrGZOcjBMWs6+ n2AfTxJ1Imff22VHr/NfiWlRNWdogczy58IO/QzRQaq3s5tMu/5PLnuw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 12/19] devtool: ide-sdk: move code to ide_none Date: Thu, 18 Sep 2025 23:07:14 +0200 Message-ID: <20250918210754.477049-13-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223673 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 5f106c2e6b6..f9eeaec6608 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_ssh_gdbserver_args(self): ssh_args = [] if self.gdb_cross.target_device.ssh_port: @@ -153,91 +138,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] @@ -245,17 +145,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 04677aba9d9..8284c4e0a52 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 Thu Sep 18 21:07:15 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70539 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 05F28CAC5AD for ; Thu, 18 Sep 2025 21:08:21 +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.web10.309.1758229694131406126 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=CIAwDDir; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.227, mailfrom: fm-1329275-20250918210812e64d66f2ab00020710-ld_xnk@rts-flowmailer.siemens.com) Received: by mta-65-227.siemens.flowmailer.net with ESMTPSA id 20250918210812e64d66f2ab00020710 for ; Thu, 18 Sep 2025 23:08:12 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=Q+5OsQ3ocEKYoch1SfnZtDZ4QbokjJc+NY6ZFqkEK9E=; b=CIAwDDiruswxT8eZQLlZBb8hDSOKCqwSOn2ATXFJ1duubquYV6t2k544+7wiHAz4dtROSq 3uhLztSlkg+TMR+RYY/pLUv4IJEiRYBo1Rbj8WvAmqwzWY38zDvZpY4qiASrosLMUEycpkhC sTeg1RGu1976Ci9lMs80TOW3dGjcmF9rWn4JPKeDpSQHd1QpYe9q9fjt6Vt+Thsr/iW+IhHT C0xPGYIlpdoN1THyZXZme8pw6/QKZg7fTE4BdXM8Hdy1o9/uCTwetfnarrH7y/6frLMYFRem RxrxuRpXusiBqZxbny6lvA10aR6dJDRWx2pU7C8TnXq2Ec1tY+abidXA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 13/19] oe-selftest: devtool ide-sdk cover vscode remote debugging Date: Thu, 18 Sep 2025 23:07:15 +0200 Message-ID: <20250918210754.477049-14-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223671 From: Adrian Freihofer This adds more tests to 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 | 323 ++++++++++++++++++++++-- 1 file changed, 295 insertions(+), 28 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 8bfb73a927c..5f861fd3ecf 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') @@ -2523,8 +2526,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 @@ -2642,7 +2660,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 @@ -2655,7 +2672,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) @@ -2664,13 +2681,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) @@ -2679,7 +2696,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 @@ -2704,6 +2721,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 @@ -2744,24 +2779,12 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertIn("gdbserver", 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) @@ -2862,6 +2885,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) @@ -2893,6 +2917,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) @@ -2904,22 +2929,264 @@ 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") + + # 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 Thu Sep 18 21:07:16 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70536 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 EBAE3CAC5AC for ; Thu, 18 Sep 2025 21:08:20 +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.web11.279.1758229694174521765 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=Uvsnu7/x; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-20250918210812f0eaf6faf5000207b3-o4qfwz@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 20250918210812f0eaf6faf5000207b3 for ; Thu, 18 Sep 2025 23:08:12 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=o4/yh9tFKNNFH4YdFPRPNE9g66Oj9gM9Ed3Xl8Nbs7s=; b=Uvsnu7/xr1FRdiuQRi3xNGyspmO1wW81gxNv6nyUUBrdBu3IBt2idcp5cRs709IIUlduBP ZVEqU73WTl6C8GPiQfaAKF3t+rUtU9Rumzii/h6cD5CJngsJUJKjQtdJFC0PFSbeFpKQRwnT VNAdR5Seek119Gm7ATdOrPojAw9+F0mIOs5ICxF08eSm0vlXEZy4Eo3Dti2od6gncZvgROhI hgWRbSWSiuj5qRYZYDurW/tcWgxlwmfja+3dDbiDFM+PFvJF82X8s6/BC3FU+keuePdvEANG I4vrVy7TO6ZzDJox9mYdH99EGXCVKdEw3QcdT7K5dICeOeBtXMfyxOWA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 14/19] devtool: ide-sdk: evaluate DEBUG_PREFIX_MAP Date: Thu, 18 Sep 2025 23:07:16 +0200 Message-ID: <20250918210754.477049-15-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:20 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223672 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 | 76 ++++++++++++++++++++- scripts/lib/devtool/standard.py | 7 +- 4 files changed, 114 insertions(+), 23 deletions(-) diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py index ef49ac71dc8..7085e9bd267 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -255,18 +255,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 8284c4e0a52..ba65f6f7dae 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 3d25848467c..aa228a265ff 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 @@ -394,6 +395,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 @@ -404,10 +406,10 @@ class RecipeModified: self.pn = 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 self.recipe_id = None @@ -478,13 +480,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')) @@ -493,6 +495,9 @@ class RecipeModified: self.f_do_install_dirs = recipe_d.getVarFlag( 'do_install', 'dirs').split() + self.reverse_debug_prefix_map = self.extract_debug_prefix_map_paths( + recipe_d.getVar('DEBUG_PREFIX_MAP')) + self.__init_exported_variables(recipe_d) self.__init_systemd_services(recipe_d) self.__init_init_scripts(recipe_d) @@ -508,6 +513,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 @@ -567,6 +575,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 1fd5947c411..f4d5d7cd3f0 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 Thu Sep 18 21:07:17 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70540 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 39A0FCAC5B4 for ; Thu, 18 Sep 2025 21:08:21 +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.web11.281.1758229694417277864 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=TQLbZIdD; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.225, mailfrom: fm-1329275-20250918210812fe75ccf81a00020725-qz2qw1@rts-flowmailer.siemens.com) Received: by mta-65-225.siemens.flowmailer.net with ESMTPSA id 20250918210812fe75ccf81a00020725 for ; Thu, 18 Sep 2025 23:08:12 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=rRAmRzifvRZ1dxwPlW3uHb7eXC4gexpUrrVNFBOdtGk=; b=TQLbZIdD2U8tre1rqeTV4yu+fnvi3zHYZ3ojGyhfkOiTEADhbobBefVooqIefqOBhmOM17 FxjMAMPvRTIl47xzung0TDwcxekP+VTytI8uQ9Imdq8aLq6CefS8Xo5BjYQo52PfSECEzXkF MHTVuteeQO7vZXGTrgk9D6XcKewAsQw9EPjSsbf+jsJrBweggtlcbDr1IujXcY7LRNySJokP V5hbci/vCczav4d7j1tZjAAoyrCU7Hfa8bMzKIjLGB+fCMtMZIKFXbIpWjan7tvBNLAm5lJ5 3e+ie1W2qYHtkplwCy+b/qhjZMsnrvH9wA4n2oUpc2gPum1ZAYrw1dyg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 15/19] cpp-example: Add std::vector example Date: Thu, 18 Sep 2025 23:07:17 +0200 Message-ID: <20250918210754.477049-16-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223674 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 dbf82f15d97..23d7169092b 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 Thu Sep 18 21:07:18 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70544 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 3F321CAC5B5 for ; Thu, 18 Sep 2025 21:08:21 +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.web11.282.1758229694658717523 for ; Thu, 18 Sep 2025 14:08:14 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=k8keUdv2; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.227, mailfrom: fm-1329275-20250918210812dcf390ec5200020700-mixszz@rts-flowmailer.siemens.com) Received: by mta-65-227.siemens.flowmailer.net with ESMTPSA id 20250918210812dcf390ec5200020700 for ; Thu, 18 Sep 2025 23:08:12 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=iTxii7yekKUTXD5ju7T7nslgOgqmY8gAIxJoRvu4/ZA=; b=k8keUdv20vmyl1N/EehtETdiQzdB7buh4IEY57T+evNcYkFVx520X12agmfuNEExVVcLvu SWPFN0cZ7QvxsoDyQxnpDmR8CY7saUC5+X6A1zE6rMMFd9I9kxOJOSpcYbeLQA9eSR6o/QRr J4TNyX/HjDteoDq9Mu+6RawdueJ63SoUKYhsY+phR1v196BzJm8+cne0ADVpvm1G8H7oTajS 2FO3pKOEigFCyewpsCsdZjQ+VsPYeKVUsDkEYcN6FQe230QbcCd1rYDpqzCw7feTM3YXC8cA qycZaLIGlrpUhCsJy1HPEylQRI1IT2KMT9EBL84baiyGvdWxIPSGlOyQ==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 16/19] devtool: ide-sdk: Support GDB pretty-printing for C++ STL types Date: Thu, 18 Sep 2025 23:07:18 +0200 Message-ID: <20250918210754.477049-17-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223675 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 7085e9bd267..d69da3862ff 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -279,6 +279,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 return launch_config diff --git a/scripts/lib/devtool/ide_plugins/ide_none.py b/scripts/lib/devtool/ide_plugins/ide_none.py index ba65f6f7dae..ed96afa33c3 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 aa228a265ff..432dba89d9b 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 @@ -410,6 +411,7 @@ class RecipeModified: self.staging_incdir = None self.strip_cmd = None self.target_arch = None + self.tcoverride = None self.topdir = None self.workdir = None self.recipe_id = None @@ -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( @@ -487,6 +490,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')) @@ -639,6 +643,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 Thu Sep 18 21:07:19 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70543 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 3382BCAC5B2 for ; Thu, 18 Sep 2025 21:08:21 +0000 (UTC) Received: from mta-64-228.siemens.flowmailer.net (mta-64-228.siemens.flowmailer.net [185.136.64.228]) by mx.groups.io with SMTP id smtpd.web10.311.1758229694888563036 for ; Thu, 18 Sep 2025 14:08:15 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=QiJ/sYKS; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.228, mailfrom: fm-1329275-20250918210812fadd977925000207ff-2djerd@rts-flowmailer.siemens.com) Received: by mta-64-228.siemens.flowmailer.net with ESMTPSA id 20250918210812fadd977925000207ff for ; Thu, 18 Sep 2025 23:08:12 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=NthMQ6FLd1D7slJbDwQcT2ulwgwAWH3GhMW8ZwuLVcw=; b=QiJ/sYKSCbvU4e5otJ3U711UhsmN50lZk4Sy1BAO5GmNxeGnN5u3oqpZj6md5HHceYrvnI tF3eftpzUoNpt6ZFp2EiatsG4rpSSbfjNY2nsHNC2rDSLbShKDoHIu0SBPGFUEu2yvyoUhpU GoTcDR/QHu0imvwbLX6Vx4uC1pYa+6BBPBGnOy6a9x1gTuxgdYOiX4nRdlrsLT+bB/isnc3N T9CUnltpjI8Cv035PfEif/Y3JUBnoCk680kPaNXb0KD/5ycPSiO1lxmySaNqQPdmtAtzKum7 5X3m8O5DtIGp4RxjeO9pbCr0hJxm/OnKujW/fStmuaFVH8yWx3OWi/3g==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 17/19] oe-selftest: devtool: add test for gdb pretty-printing Date: Thu, 18 Sep 2025 23:07:19 +0200 Message-ID: <20250918210754.477049-18-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223677 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 5f861fd3ecf..2c9660ef3a2 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2728,6 +2728,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 @@ -2737,6 +2743,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 Thu Sep 18 21:07:20 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70535 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 D4360CAC5A7 for ; Thu, 18 Sep 2025 21:08:20 +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.web10.310.1758229694851914021 for ; Thu, 18 Sep 2025 14:08:15 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=h0VrNadm; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.225, mailfrom: fm-1329275-20250918210812042ff41a2c00020703-suwyyr@rts-flowmailer.siemens.com) Received: by mta-65-225.siemens.flowmailer.net with ESMTPSA id 20250918210812042ff41a2c00020703 for ; Thu, 18 Sep 2025 23:08:12 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=1moeDp1RSORs4bGQi61R5RMqDKPFxlRoZmLbcUvkzxs=; b=h0VrNadmFdMNwwd0Me6QOBpxdGXjia78bFbhU2ZH4nKeE1r2uYYjl2gERpEPgE+LwvKZCk ujuRS1AC+QqBeFFYPD3Mqj5QfAf/encExTplfe7Ja1W5dER39TnqhI4KUNi7DVnekX4rST1P N/Vy3Q+ylBQJkTyInDocMI6bLBw7apwpqkVYCO/J6fSGKn4+bEBO06qBDALBuU0+qtqpOaGM 6KH+b3TVJUEK7LNZIhDqbCYQu7K5UTB1X+y1HgATsPaK6C1XF1tAbrO6RkCBOP9xlvRc3938 hVIev2uWuE029ToWrj8TRRd6jWDDHh5v8hX2u84AJ3sIoXriFtkRVBgw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 18/19] module.bbclass: move environment setup to kernel_module.py Date: Thu, 18 Sep 2025 23:07:20 +0200 Message-ID: <20250918210754.477049-19-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:20 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223676 From: Adrian Freihofer Refactor: move kernel module environment setup from do_devshell to kernel_module.py Extract the kernel module environment variable setup from do_devshell into oe.kernel_module.kernel_module_os_env(). This enables code reuse for future features such as devtool ide-sdk. Note: it would also be possible to e.g. bb.utils.py. But when every such a widely used utility function gets changed, bitbake needs to recompile a lot of code. Therefore it's probably better to put it into a separate file. It also is a very specific function, so oe.kernel_module seems to be a good place. Signed-off-by: Adrian Freihofer --- meta/classes-recipe/module.bbclass | 21 ++------------------- meta/lib/oe/kernel_module.py | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 19 deletions(-) create mode 100644 meta/lib/oe/kernel_module.py diff --git a/meta/classes-recipe/module.bbclass b/meta/classes-recipe/module.bbclass index 4948e995c5d..ca2dcba43e0 100644 --- a/meta/classes-recipe/module.bbclass +++ b/meta/classes-recipe/module.bbclass @@ -25,25 +25,8 @@ python do_package:prepend () { } python do_devshell:prepend () { - os.environ['CFLAGS'] = '' - os.environ['CPPFLAGS'] = '' - os.environ['CXXFLAGS'] = '' - os.environ['LDFLAGS'] = '' - - os.environ['KERNEL_PATH'] = d.getVar('STAGING_KERNEL_DIR') - os.environ['KERNEL_SRC'] = d.getVar('STAGING_KERNEL_DIR') - os.environ['KERNEL_VERSION'] = d.getVar('KERNEL_VERSION') - os.environ['CC'] = d.getVar('KERNEL_CC') - os.environ['LD'] = d.getVar('KERNEL_LD') - os.environ['AR'] = d.getVar('KERNEL_AR') - os.environ['OBJCOPY'] = d.getVar('KERNEL_OBJCOPY') - os.environ['STRIP'] = d.getVar('KERNEL_STRIP') - os.environ['O'] = d.getVar('STAGING_KERNEL_BUILDDIR') - kbuild_extra_symbols = d.getVar('KBUILD_EXTRA_SYMBOLS') - if kbuild_extra_symbols: - os.environ['KBUILD_EXTRA_SYMBOLS'] = kbuild_extra_symbols - else: - os.environ['KBUILD_EXTRA_SYMBOLS'] = '' + import oe.kernel_module + oe.kernel_module.kernel_module_os_env(d, os.environ) } module_do_compile() { diff --git a/meta/lib/oe/kernel_module.py b/meta/lib/oe/kernel_module.py new file mode 100644 index 00000000000..678f7de03b4 --- /dev/null +++ b/meta/lib/oe/kernel_module.py @@ -0,0 +1,22 @@ + +# Set up the environment for building kernel modules +def kernel_module_os_env(d, env_dict): + env_dict['CFLAGS'] = '' + env_dict['CPPFLAGS'] = '' + env_dict['CXXFLAGS'] = '' + env_dict['LDFLAGS'] = '' + + env_dict['KERNEL_PATH'] = d.getVar('STAGING_KERNEL_DIR') + env_dict['KERNEL_SRC'] = d.getVar('STAGING_KERNEL_DIR') + env_dict['KERNEL_VERSION'] = d.getVar('KERNEL_VERSION') + env_dict['CC'] = d.getVar('KERNEL_CC') + env_dict['LD'] = d.getVar('KERNEL_LD') + env_dict['AR'] = d.getVar('KERNEL_AR') + env_dict['OBJCOPY'] = d.getVar('KERNEL_OBJCOPY') + env_dict['STRIP'] = d.getVar('KERNEL_STRIP') + env_dict['O'] = d.getVar('STAGING_KERNEL_BUILDDIR') + kbuild_extra_symbols = d.getVar('KBUILD_EXTRA_SYMBOLS') + if kbuild_extra_symbols: + env_dict['KBUILD_EXTRA_SYMBOLS'] = kbuild_extra_symbols + else: + env_dict['KBUILD_EXTRA_SYMBOLS'] = '' From patchwork Thu Sep 18 21:07:21 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 70541 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 2DAFFCAC5B3 for ; Thu, 18 Sep 2025 21:08:21 +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.web11.283.1758229695077333227 for ; Thu, 18 Sep 2025 14:08:15 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm1 header.b=g5ZwR9/I; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.226, mailfrom: fm-1329275-2025091821081261536d7b1d0002076a-4ytd7s@rts-flowmailer.siemens.com) Received: by mta-64-226.siemens.flowmailer.net with ESMTPSA id 2025091821081261536d7b1d0002076a for ; Thu, 18 Sep 2025 23:08:13 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; 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=JKVlmVnLKwpwvjQ4vIkqcyeq2JSrGNHs6tXhQ2vyxuA=; b=g5ZwR9/Iiv3e2cihdhRLiCtsUnBwS1nJlKujtOEvFWgVKiq/cseVnfAcEhv2WbkaC3Msbs j2NAAlzxVcyN7HbObGTfBK3eUgk81pPIPqtCMa1sPpg3PzKdgc/7eOefG12TDlz9pVymWSrm y1waReeU85BHJO6rp47j0nbMnuNShFHZ6WtEtq+Gp1zntBXTTf6NOrlPhthPw5D+E3tV1bO6 o+ryCb1EQPqdbKN9HYKYpX7/NXvQeBWJdm5xGdDTHHSXff3V5dWKbKm+H7bZ5DMS67UGXM6+ 2/8B6qsg5RTSnv/7R+ggPGNqA5ugMGG7GnVgz/AKkFxOI9jzMaEW7P6g==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 19/19] devtool: ide-sdk: support kernel module development Date: Thu, 18 Sep 2025 23:07:21 +0200 Message-ID: <20250918210754.477049-20-adrian.freihofer@siemens.com> In-Reply-To: <20250918210754.477049-1-adrian.freihofer@siemens.com> References: <20250918210754.477049-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 li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 18 Sep 2025 21:08:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/223678 From: Adrian Freihofer This add very basic support for kernel module development with devtool ide-sdk. It exports the kernel build environment and sets up tasks for building and cleaning the module. But it does not yet support install, deploy, and debug tasks. It looks like possible to offer the same level of support as for CMake and Meson based projects, but that requires more work. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_plugins/__init__.py | 7 ++ scripts/lib/devtool/ide_plugins/ide_code.py | 103 +++++++++++++++----- scripts/lib/devtool/ide_sdk.py | 34 ++++++- 3 files changed, 117 insertions(+), 27 deletions(-) diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py index f9eeaec6608..2f645b205a2 100644 --- a/scripts/lib/devtool/ide_plugins/__init__.py +++ b/scripts/lib/devtool/ide_plugins/__init__.py @@ -19,6 +19,7 @@ class BuildTool(Enum): UNDEFINED = auto() CMAKE = auto() MESON = auto() + KERNEL_MODULE = auto() @property def is_c_ccp(self): @@ -28,6 +29,12 @@ class BuildTool(Enum): return True return False + @property + def is_c_cpp_kernel(self): + if self.is_c_ccp or self is BuildTool.KERNEL_MODULE: + return True + return False + class GdbServerModes(Enum): ONCE = auto() diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py index d69da3862ff..38fa4dcff98 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -138,6 +138,59 @@ class IdeVSCode(IdeBase): settings_dict["cmake.configureOnOpen"] = True settings_dict["cmake.sourceDirectory"] = modified_recipe.real_srctree + def __vscode_settings_kernel_module(self, settings_dict, modified_recipe): + if modified_recipe.build_tool is not BuildTool.KERNEL_MODULE: + return + + # Define kernel exclude patterns once + kernel_exclude_patterns = [ + "**/.*.cmd", + "**/.*.d", + "**/.*.S", + "**/.tmp*", + "**/*.tmp", + "**/*.o", + "**/*.a", + "**/*.builtin", + "**/*.order", + "**/*.orig", + "**/*.symvers", + "**/*.modinfo", + "**/*.map", + "*.cache/**" + ] + files_excludes_kernel = {pattern: True for pattern in kernel_exclude_patterns} + + settings_dict["files.exclude"].update(files_excludes_kernel) + settings_dict["files.watcherExclude"].update(files_excludes_kernel) + settings_dict["python.analysis.exclude"] += kernel_exclude_patterns + + # protect the kernel sources + settings_dict["files.readonlyInclude"][modified_recipe.staging_kernel_dir + '/**'] = True + + # Export the complete cross-build environment + settings_dict["terminal.integrated.env.linux"] = modified_recipe.exported_vars + + # and the make configuration + make_executable = os.path.join( + modified_recipe.recipe_sysroot_native, 'usr', 'bin', 'make') + settings_dict["makefile.configurations"] = [ + { + "name": ' '.join(modified_recipe.make_targets), + "makePath": make_executable, + "makeDirectory": modified_recipe.srctree, + "makefilePath": os.path.join(modified_recipe.srctree, "Makefile"), + "makeArgs": modified_recipe.extra_oemake + modified_recipe.make_targets + }, + { + "name": "clean", + "makePath": make_executable, + "makeDirectory": modified_recipe.srctree, + "makefilePath": os.path.join(modified_recipe.srctree, "Makefile"), + "makeArgs": modified_recipe.extra_oemake + ["clean"] + } + ] + def vscode_settings(self, modified_recipe, image_recipe): files_excludes = { "**/.git/**": True, @@ -165,35 +218,27 @@ class IdeVSCode(IdeBase): } self.__vscode_settings_cmake(settings_dict, modified_recipe) self.__vscode_settings_meson(settings_dict, modified_recipe) + self.__vscode_settings_kernel_module(settings_dict, modified_recipe) settings_file = 'settings.json' IdeBase.update_json_file( self.dot_code_dir(modified_recipe), settings_file, settings_dict) - def __vscode_extensions_cmake(self, modified_recipe, recommendations): - if modified_recipe.build_tool is not BuildTool.CMAKE: - return - recommendations += [ - "ms-vscode.cmake-tools", - "ms-vscode.cpptools", - "ms-vscode.cpptools-extension-pack", - "ms-vscode.cpptools-themes" - ] - - def __vscode_extensions_meson(self, modified_recipe, recommendations): - if modified_recipe.build_tool is not BuildTool.MESON: - return - recommendations += [ - 'mesonbuild.mesonbuild', - "ms-vscode.cpptools", - "ms-vscode.cpptools-extension-pack", - "ms-vscode.cpptools-themes" - ] - def vscode_extensions(self, modified_recipe): recommendations = [] - self.__vscode_extensions_cmake(modified_recipe, recommendations) - self.__vscode_extensions_meson(modified_recipe, recommendations) + if modified_recipe.build_tool.is_c_cpp_kernel: + recommendations += [ + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.cpptools-themes" + ] + if modified_recipe.build_tool is BuildTool.CMAKE: + recommendations.append("ms-vscode.cmake-tools") + if modified_recipe.build_tool is BuildTool.MESON: + recommendations.append("mesonbuild.mesonbuild") + if modified_recipe.build_tool is BuildTool.KERNEL_MODULE: + recommendations.append("ms-vscode.makefile-tools") + extensions_file = 'extensions.json' IdeBase.update_json_file( self.dot_code_dir(modified_recipe), extensions_file, {"recommendations": recommendations}) @@ -207,6 +252,15 @@ class IdeVSCode(IdeBase): elif modified_recipe.build_tool is BuildTool.MESON: properties_dict["configurationProvider"] = "mesonbuild.mesonbuild" properties_dict["compilerPath"] = os.path.join(modified_recipe.staging_bindir_toolchain, modified_recipe.cxx.split()[0]) + elif modified_recipe.build_tool is BuildTool.KERNEL_MODULE: + # Using e.g. configurationProvider = "ms-vscode.makefile-tools" was not successful + properties_dict["compilerPath"] = os.path.join(modified_recipe.staging_bindir_toolchain, modified_recipe.kernel_cc.split()[0]) + properties_dict["includePath"] = [ + "${workspaceFolder}/**", + os.path.join(modified_recipe.staging_kernel_dir, "include", "**") + ] + # https://www.kernel.org/doc/html/next/process/programming-language.html + properties_dict["cStandard"] = "gnu11" else: # no C/C++ build return @@ -369,6 +423,9 @@ class IdeVSCode(IdeBase): IdeBase.update_json_file( self.dot_code_dir(modified_recipe), tasks_file, tasks_dict) + def vscode_tasks_kernel_module(self, args, modified_recipe): + pass + def vscode_tasks_fallback(self, args, modified_recipe): oe_init_dir = modified_recipe.oe_init_dir oe_init = ". %s %s > /dev/null && " % (modified_recipe.oe_init_build_env, modified_recipe.topdir) @@ -491,6 +548,8 @@ class IdeVSCode(IdeBase): def vscode_tasks(self, args, modified_recipe): if modified_recipe.build_tool.is_c_ccp: self.vscode_tasks_cpp(args, modified_recipe) + elif modified_recipe.build_tool == BuildTool.KERNEL_MODULE: + self.vscode_tasks_kernel_module(args, modified_recipe) else: self.vscode_tasks_fallback(args, modified_recipe) diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py index 432dba89d9b..f38ef90be68 100755 --- a/scripts/lib/devtool/ide_sdk.py +++ b/scripts/lib/devtool/ide_sdk.py @@ -24,6 +24,7 @@ import bb from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, DevtoolError, parse_recipe from devtool.standard import get_real_srctree from devtool.ide_plugins import BuildTool +from oe.kernel_module import kernel_module_os_env logger = logging.getLogger('devtool') @@ -436,6 +437,11 @@ class RecipeModified: self.mesonopts = None self.extra_oemeson = None self.meson_cross_file = None + # kernel module + self.make_targets = None + self.extra_oemake = None + self.kernel_cc = None + self.staging_kernel_dir = None # Populated after bitbake built all the recipes self._installed_binaries = None @@ -499,9 +505,6 @@ class RecipeModified: self.f_do_install_dirs = recipe_d.getVarFlag( 'do_install', 'dirs').split() - self.reverse_debug_prefix_map = self.extract_debug_prefix_map_paths( - recipe_d.getVar('DEBUG_PREFIX_MAP')) - self.__init_exported_variables(recipe_d) self.__init_systemd_services(recipe_d) self.__init_init_scripts(recipe_d) @@ -516,9 +519,30 @@ class RecipeModified: self.extra_oemeson = recipe_d.getVar('EXTRA_OEMESON') self.meson_cross_file = recipe_d.getVar('MESON_CROSS_FILE') self.build_tool = BuildTool.MESON + elif bb.data.inherits_class('module', recipe_d): + self.build_tool = BuildTool.KERNEL_MODULE + make_targets = recipe_d.getVar('MAKE_TARGETS') + if make_targets: + self.make_targets = shlex.split(make_targets) + else: + self.make_targets = ["all"] + extra_oemake = recipe_d.getVar('EXTRA_OEMAKE') + if extra_oemake: + self.extra_oemake = shlex.split(extra_oemake) + else: + self.extra_oemake = [] + self.kernel_cc = recipe_d.getVar('KERNEL_CC') + self.staging_kernel_dir = recipe_d.getVar('STAGING_KERNEL_DIR') + # Export up the environment for building kernel modules + kernel_module_os_env(recipe_d, self.exported_vars) - self.reverse_debug_prefix_map = self._init_reverse_debug_prefix_map( - recipe_d.getVar('DEBUG_PREFIX_MAP')) + # For the kernel the KERNEL_CC variable contains the prefix-map arguments + if self.build_tool is BuildTool.KERNEL_MODULE: + self.reverse_debug_prefix_map = self._init_reverse_debug_prefix_map( + self.kernel_cc) + else: + 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