* [PATCH 1/9] oe-selftest: devtool: use stat for reading user/group names in ide-sdk tests
2026-03-18 22:36 [PATCH 0/9] devtool: ide-sdk clang/LLDB support and minor fixes AdrianF
@ 2026-03-18 22:36 ` AdrianF
2026-03-18 22:36 ` [PATCH 2/9] oe-selftest: devtool: GDB breakpoint after std::vector is constructed AdrianF
` (7 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: AdrianF @ 2026-03-18 22:36 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
On some systems, ls truncates long user and group names, which causes the
ownership check to fail. For example:
AssertionError: Regex didn't match:
'^-.+ cmake-example cmake-example .+ /etc/cmake\\-example\\.conf$' not found in
'-rw-r--r-- 1 cmake-ex cmake-ex 83 Mar 9 2018 /etc/cmake-example.conf'
Use "stat -c '%U %G'" instead, which always returns the full user and group
names regardless of terminal width or system configuration.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Mon Mar 2 23:32:13 2026 +0100
#
# interactive rebase in progress; onto c1fb515f2a
# Last commands done (3 commands done):
# pick 08cf9a6fc0 # Revert "devtool: ide-sdk deploy-target without bitbake"
# reword 2ab466cf23 # oe-selftest: devtool: use stat for reading user names in ide-sdk tests
# Next commands to do (3 remaining commands):
# reword 81562bd21a # oe-selftest/cpp-example: fix conf file ownership with static UIDs/GIDs
# reword 1f6fd56a04 # oe-selftest: devtool: GDB breakpoint after std::vector is constructed
# You are currently editing a commit while rebasing branch 'adrianf/ide-sdk-improvements' on 'c1fb515f2a'.
#
# Changes to be committed:
# modified: meta/lib/oeqa/selftest/cases/devtool.py
#
# ------------------------ >8 ------------------------
# Do not modify or remove the line above.
# Everything below it will be ignored.
diff --git c/meta/lib/oeqa/selftest/cases/devtool.py i/meta/lib/oeqa/selftest/cases/devtool.py
index 5f25c4803b..9d8ffcc786 100644
--- c/meta/lib/oeqa/selftest/cases/devtool.py
+++ i/meta/lib/oeqa/selftest/cases/devtool.py
@@ -2934,11 +2934,14 @@ class DevtoolIdeSdkTests(DevtoolBase):
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))
+ stat_cmd = "stat -c '%%U %%G' %s" % conf_file
+ status, output = qemu.run(stat_cmd)
+ self.assertEqual(status, 0, msg="Failed to stat %s: %s" % (conf_file, output))
+ actual_owner, actual_group = output.strip().split()
+ self.assertEqual(actual_owner, owner,
+ msg="%s not owned by user %s: got %s" % (conf_file, owner, actual_owner))
+ self.assertEqual(actual_group, group,
+ msg="%s not owned by group %s: got %s" % (conf_file, group, actual_group))
@OETestTag("runqemu")
def test_devtool_ide_sdk_none_qemu(self):
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 5f25c4803b..9d8ffcc786 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -2934,11 +2934,14 @@ class DevtoolIdeSdkTests(DevtoolBase):
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))
+ stat_cmd = "stat -c '%%U %%G' %s" % conf_file
+ status, output = qemu.run(stat_cmd)
+ self.assertEqual(status, 0, msg="Failed to stat %s: %s" % (conf_file, output))
+ actual_owner, actual_group = output.strip().split()
+ self.assertEqual(actual_owner, owner,
+ msg="%s not owned by user %s: got %s" % (conf_file, owner, actual_owner))
+ self.assertEqual(actual_group, group,
+ msg="%s not owned by group %s: got %s" % (conf_file, group, actual_group))
@OETestTag("runqemu")
def test_devtool_ide_sdk_none_qemu(self):
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 2/9] oe-selftest: devtool: GDB breakpoint after std::vector is constructed
2026-03-18 22:36 [PATCH 0/9] devtool: ide-sdk clang/LLDB support and minor fixes AdrianF
2026-03-18 22:36 ` [PATCH 1/9] oe-selftest: devtool: use stat for reading user/group names in ide-sdk tests AdrianF
@ 2026-03-18 22:36 ` AdrianF
2026-03-18 22:36 ` [PATCH 3/9] oe-selftest: devtool: use assertRegex to match test output for meson AdrianF
` (6 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: AdrianF @ 2026-03-18 22:36 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Change the GDB breakpoint from line 55 to 56 in cpp-example.cpp so that
the std::vector constructor has already executed when GDB stops. This
ensures that inspecting the vector with GDB works as intended also with
older GDB versions (e.g. on scarthgap).
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 9d8ffcc786..297dda7457 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -2772,7 +2772,9 @@ class DevtoolIdeSdkTests(DevtoolBase):
# 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'"
+ # Break on line 56 (the std::cout after the declaration) so the vector
+ # constructor on line 55 has already run when GDB stops.
+ gdb_batch_cmd += " -ex 'break cpp-example.cpp:56'"
gdb_batch_cmd += " -ex 'continue'"
gdb_batch_cmd += " -ex 'print numbers'"
gdb_batch_cmd += " -ex 'continue'"
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 3/9] oe-selftest: devtool: use assertRegex to match test output for meson
2026-03-18 22:36 [PATCH 0/9] devtool: ide-sdk clang/LLDB support and minor fixes AdrianF
2026-03-18 22:36 ` [PATCH 1/9] oe-selftest: devtool: use stat for reading user/group names in ide-sdk tests AdrianF
2026-03-18 22:36 ` [PATCH 2/9] oe-selftest: devtool: GDB breakpoint after std::vector is constructed AdrianF
@ 2026-03-18 22:36 ` AdrianF
2026-03-18 22:36 ` [PATCH 4/9] oe-selftest/cpp-example: fix conf file ownership with static UIDs/GIDs AdrianF
` (5 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: AdrianF @ 2026-03-18 22:36 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Replace strict string matching with assertRegex to allow for flexible
whitespace in the "Fail: 0" output from meson tests. This improves test
robustness against formatting changes.
This issue was discovered with scarthgap.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 297dda7457..dc83d406fd 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -2911,7 +2911,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
result = runCmd('%s test -C %s' % (meson_exe, build_dir),
cwd=tempdir, output_log=self._cmd_logger)
self.assertEqual(result.status, 0)
- self.assertIn("Fail: 0", result.output)
+ self.assertRegex(result.output, r"Fail:\s+0")
# Verify re-building and testing works again
result = runCmd('%s compile -C %s --clean' % (meson_exe, build_dir),
@@ -2922,7 +2922,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
result = runCmd('%s test -C %s' % (meson_exe, build_dir),
cwd=tempdir, output_log=self._cmd_logger)
self.assertEqual(result.status, 0)
- self.assertIn("Fail: 0", result.output)
+ self.assertRegex(result.output, r"Fail:\s+0")
return compile_cmd
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 4/9] oe-selftest/cpp-example: fix conf file ownership with static UIDs/GIDs
2026-03-18 22:36 [PATCH 0/9] devtool: ide-sdk clang/LLDB support and minor fixes AdrianF
` (2 preceding siblings ...)
2026-03-18 22:36 ` [PATCH 3/9] oe-selftest: devtool: use assertRegex to match test output for meson AdrianF
@ 2026-03-18 22:36 ` AdrianF
2026-03-18 22:36 ` [PATCH 5/9] devtool: ide-sdk: use TOOLCHAIN not TCOVERRIDE AdrianF
` (4 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: AdrianF @ 2026-03-18 22:36 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
test_devtool_ide_sdk_none_qemu builds an image containing both
cmake-example and meson-example, starts a QEMU instance, then uses
devtool ide-sdk + devtool deploy-target to rebuild and redeploy each
recipe in turn. The test verifies that /etc/<recipe>.conf is owned by
the matching user both before and after each deploy cycle.
The test was failing with:
/etc/meson-example.conf not owned by user meson-example: got cmake-example
Root cause: both recipes call
install -m 0644 -o ${BPN} -g ${BPN} ... ${D}${sysconfdir}/${BPN}.conf
During do_install, pseudo resolves ${BPN} to a UID by looking up
/etc/passwd in the recipe's own isolated RECIPE_SYSROOT. Since the
sysroots are independent, both cmake-example and meson-example each
see themselves as the first --system user and get the same UID (e.g.
100). Both ${D} trees therefore contain files with UID 100. In the
final rootfs cmake-example is allocated UID 100 and meson-example UID
101. Files packaged for meson-example still carry UID 100, so stat
reports them as owned by cmake-example.
A pkg_postinst chown would fix the rootfs, but devtool deploy-target
is a plain tar pipe over SSH with no package-manager involvement - it
never runs pkg_postinst. Whatever UID is embedded in ${D} is what
lands on the target. Not sure how this could be fixed with dynamic UIDs.
A clean solution is to make every recipe sysroot and the final image
agree on the same UIDs from the start, i.e. static IDs.
Fix:
- Enable USERADDEXTENSION = "useradd-staticids" in _write_bb_config so
the test builds with static IDs for the duration of the test.
- Add cmake-example (UID/GID 533) and meson-example (UID/GID 534) to
meta-selftest/files/static-passwd and static-group.
- Expand the comment in cpp-example.inc's do_install to document the
static-ID requirement so future readers understand why the -o/-g
flags work correctly only under useradd-staticids.
- Fix a copy-paste error in the in-test comment (said
"meson-example.conf ... cmake-example user" for the cmake block).
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta-selftest/files/static-group | 2 ++
meta-selftest/files/static-passwd | 2 ++
meta-selftest/recipes-test/cpp/cpp-example.inc | 6 +++++-
meta/lib/oeqa/selftest/cases/devtool.py | 12 +++++++++++-
4 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/meta-selftest/files/static-group b/meta-selftest/files/static-group
index 3fca4aa5c9..adf436f310 100644
--- a/meta-selftest/files/static-group
+++ b/meta-selftest/files/static-group
@@ -28,4 +28,6 @@ ptest:x:529:
xuser:x:530:
seat:x:531:
audio:x:532:
+cmake-example:x:533:
+meson-example:x:534:
nogroup:x:65534:
diff --git a/meta-selftest/files/static-passwd b/meta-selftest/files/static-passwd
index cc6c5acd5c..8d9f149b9f 100644
--- a/meta-selftest/files/static-passwd
+++ b/meta-selftest/files/static-passwd
@@ -19,3 +19,5 @@ _apt:x:523:523::/:/bin/nologin
weston:x:525:525::/:/bin/nologin
ptest:x:529:529::/:/bin/nologin
xuser:x:530:530::/:/bin/nologin
+cmake-example:x:533:533::/var/lib/cmake-example:/bin/false
+meson-example:x:534:534::/var/lib/meson-example:/bin/false
diff --git a/meta-selftest/recipes-test/cpp/cpp-example.inc b/meta-selftest/recipes-test/cpp/cpp-example.inc
index 2653f45e90..0671824d1c 100644
--- a/meta-selftest/recipes-test/cpp/cpp-example.inc
+++ b/meta-selftest/recipes-test/cpp/cpp-example.inc
@@ -41,7 +41,11 @@ USERADD_PARAM:${PN} = "--system --home /var/lib/${BPN} --no-create-home --shell
EX_BINARY_NAME ?= "${BPN}"
do_install:append() {
- # Install configuration file owned by unprivileged user
+ # Install configuration file owned by the recipe's unprivileged user.
+ # Note: this requires static UIDs/GIDs (USERADDEXTENSION = "useradd-staticids")
+ # so that the UID embedded by pseudo during do_install matches the UID assigned
+ # in the final image. devtool deploy-target is a raw file copy and does not run
+ # pkg_postinst, so the UID in ${D} must already be correct.
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
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index dc83d406fd..6c6f22a667 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -2598,6 +2598,16 @@ class DevtoolIdeSdkTests(DevtoolBase):
'IMAGE_INSTALL:append = " gdbserver %s"' % ' '.join(
[r + '-ptest' for r in recipe_names]),
'DISTRO_FEATURES:append = " ptest"'
+ # Static UIDs/GIDs are required so that files installed via
+ # "install -o ${BPN}" in do_install embed the same UID that gets
+ # assigned in the final image. Without this, each recipe's isolated
+ # sysroot allocates UIDs independently (both start at the first free
+ # system UID), so files end up with colliding UIDs in the image.
+ # devtool deploy-target is a raw file copy and does not run
+ # pkg_postinst, so ownership must be correct already in ${D}.
+ 'USERADDEXTENSION = "useradd-staticids"',
+ 'USERADD_UID_TABLES += "files/static-passwd"',
+ 'USERADD_GID_TABLES += "files/static-group"',
]
self.write_config("\n".join(conf_lines))
@@ -2985,7 +2995,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
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
+ # Verify /etc/cmake-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)
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 5/9] devtool: ide-sdk: use TOOLCHAIN not TCOVERRIDE
2026-03-18 22:36 [PATCH 0/9] devtool: ide-sdk clang/LLDB support and minor fixes AdrianF
` (3 preceding siblings ...)
2026-03-18 22:36 ` [PATCH 4/9] oe-selftest/cpp-example: fix conf file ownership with static UIDs/GIDs AdrianF
@ 2026-03-18 22:36 ` AdrianF
2026-03-18 22:36 ` [PATCH 6/9] devtool: ide-sdk debugger back-end abstraction AdrianF
` (3 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: AdrianF @ 2026-03-18 22:36 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Looks like TOOLCHAIN is the correct variable to determine the toolchain
used by a recipe, not TCOVERRIDE.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
scripts/lib/devtool/ide_sdk.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py
index 9bccd76f0c..07f5552758 100755
--- a/scripts/lib/devtool/ide_sdk.py
+++ b/scripts/lib/devtool/ide_sdk.py
@@ -416,7 +416,7 @@ class RecipeModified:
self.staging_incdir = None
self.strip_cmd = None
self.target_arch = None
- self.tcoverride = None
+ self.toolchain = None
self.topdir = None
self.workdir = None
# Service management
@@ -502,7 +502,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.toolchain = recipe_d.getVar('TOOLCHAIN')
self.topdir = recipe_d.getVar('TOPDIR')
self.workdir = os.path.realpath(recipe_d.getVar('WORKDIR'))
@@ -673,7 +673,7 @@ class RecipeModified:
@property
def gdb_pretty_print_scripts(self):
if self._gdb_pretty_print_scripts is None:
- if self.tcoverride == "toolchain-gcc":
+ if self.toolchain == "gcc":
gcc_python_helpers_pattern = os.path.join(self.recipe_sysroot, "usr", "share", "gcc-*", "python")
gcc_python_helpers_dirs = glob.glob(gcc_python_helpers_pattern)
if gcc_python_helpers_dirs:
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 6/9] devtool: ide-sdk debugger back-end abstraction
2026-03-18 22:36 [PATCH 0/9] devtool: ide-sdk clang/LLDB support and minor fixes AdrianF
` (4 preceding siblings ...)
2026-03-18 22:36 ` [PATCH 5/9] devtool: ide-sdk: use TOOLCHAIN not TCOVERRIDE AdrianF
@ 2026-03-18 22:36 ` AdrianF
2026-03-18 22:36 ` [PATCH 7/9] devtool: ide-sdk add LLDB support for clang toolchain AdrianF
` (2 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: AdrianF @ 2026-03-18 22:36 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
This is a refactoring of the devtool ide-sdk support for remote
debugging with gdbserver. The main goal is to cleanly separate the
generation of the host-side debugger configuration (gdbinit, wrapper
scripts) from the IDE-specific launch/task config generation, and to
provide a common interface for supporting multiple debug server
back-ends (gdbserver, lldb-server) in the future.
Also fix a typo in the GDB configuration generator where the property
was named "is_c_ccp" instead of "is_c_cpp".
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
scripts/lib/devtool/ide_plugins/__init__.py | 195 +++++++++++---------
scripts/lib/devtool/ide_plugins/ide_code.py | 38 ++--
scripts/lib/devtool/ide_plugins/ide_none.py | 26 +--
scripts/lib/devtool/ide_sdk.py | 21 ++-
4 files changed, 154 insertions(+), 126 deletions(-)
diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py
index eaf88e78cd..8c41afc640 100644
--- a/scripts/lib/devtool/ide_plugins/__init__.py
+++ b/scripts/lib/devtool/ide_plugins/__init__.py
@@ -22,7 +22,7 @@ class BuildTool(Enum):
KERNEL_MODULE = auto()
@property
- def is_c_ccp(self):
+ def is_c_cpp(self):
if self is BuildTool.CMAKE:
return True
if self is BuildTool.MESON:
@@ -31,7 +31,7 @@ class BuildTool(Enum):
@property
def is_c_cpp_kernel(self):
- if self.is_c_ccp or self is BuildTool.KERNEL_MODULE:
+ if self.is_c_cpp or self is BuildTool.KERNEL_MODULE:
return True
return False
@@ -42,104 +42,48 @@ class GdbServerModes(Enum):
MULTI = auto()
-class GdbCrossConfig:
- """Base class defining the GDB configuration generator interface
+class DebuggerCrossConfig:
+ """Base class defining the cross-debugger configuration generator interface.
- Generate a GDB configuration for a binary on the target device.
+ Manages the per-binary port assignment, script paths, and SSH argument
+ construction that are common to all debugger back-ends (GDB, LLDB).
+ Concrete subclasses provide the back-end-specific remote start/kill commands.
"""
- _gdbserver_port_next = 1234
- _gdb_cross_configs = {}
+ _port_next = 1234
+ _configs = {}
- def __init__(self, image_recipe, modified_recipe, binary, gdbserver_default_mode):
+ def __init__(self, image_recipe, modified_recipe, binary, default_mode):
self.image_recipe = image_recipe
self.modified_recipe = modified_recipe
self.gdb_cross = modified_recipe.gdb_cross
self.binary = binary
- self.gdbserver_default_mode = gdbserver_default_mode
+ self.default_mode = 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)
+ self.port = DebuggerCrossConfig._port_next
+ DebuggerCrossConfig._port_next += 1
+ self.id_pretty = "%d_%s" % (self.port, self.binary_pretty)
- # Track all generated gdbserver configs to avoid duplicates
- if self.id_pretty in GdbCrossConfig._gdb_cross_configs:
+ if self.id_pretty in DebuggerCrossConfig._configs:
raise DevtoolError(
- "gdbserver config for binary %s is already generated" % binary)
- GdbCrossConfig._gdb_cross_configs[self.id_pretty] = self
+ "debugger config for binary %s is already generated" % binary)
+ DebuggerCrossConfig._configs[self.id_pretty] = self
- def id_pretty_mode(self, gdbserver_mode):
- return "%s_%s" % (self.id_pretty, gdbserver_mode.name.lower())
+ def id_pretty_mode(self, mode):
+ return "%s_%s" % (self.id_pretty, mode.name.lower())
- # GDB and gdbserver script on the host
+ # Host-side script paths
@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 server_script_file(self, mode):
+ return 'gdbserver_' + self.id_pretty_mode(mode)
- def gdbserver_script_file(self, gdbserver_mode):
- return 'gdbserver_' + self.id_pretty_mode(gdbserver_mode)
+ def server_script(self, mode):
+ return os.path.join(self.script_dir, self.server_script_file(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)
-
- @property
- def gdb_script(self):
- return os.path.join(
- self.script_dir, 'gdb_' + self.id_pretty)
-
- # 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))
-
- 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'\""
- """
- if gdbserver_mode == GdbServerModes.ONCE:
- gdbserver_cmd_start = "%s --once :%s %s" % (
- 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 _target_gdbserver_kill_cmd(self):
- """Get the ssh command to kill gdbserver on the target device"""
- return "\"kill \\$(pgrep -o -f 'gdbserver --attach :%s') 2>/dev/null || true\"" % self.gdbserver_port
-
- def _target_ssh_gdbserver_args(self):
+ # SSH argument helpers
+ def _target_ssh_args(self):
ssh_args = []
if self.gdb_cross.target_device.ssh_port:
ssh_args += ["-p", self.gdb_cross.target_device.ssh_port]
@@ -149,17 +93,94 @@ class GdbCrossConfig:
ssh_args.append(self.gdb_cross.target_device.target)
return ssh_args
- 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:
+ def server_modes(self):
+ """List of debug-server modes for which scripts are generated."""
+ modes = [self.default_mode]
+ if self.binary.runs_as_service and self.default_mode != GdbServerModes.ATTACH:
modes.append(GdbServerModes.ATTACH)
return modes
def initialize(self):
- """Interface function to initialize the gdb config generation"""
+ """Called after construction to generate any required config files."""
pass
+ # Abstract — subclasses must implement
+ def _target_start_cmd(self, mode):
+ raise NotImplementedError
+
+ def _target_kill_cmd(self):
+ raise NotImplementedError
+
+
+class GdbCrossConfig(DebuggerCrossConfig):
+ """GDB-specific cross-debugging configuration.
+
+ Manages gdbserver on the target and gdb-cross on the host. Provides
+ gdbinit / gdb wrapper scripts used by ide=none as well as the
+ target-side tmp/pid/log paths consumed by the gdbserver start command.
+ """
+
+ def __init__(self, image_recipe, modified_recipe, binary,
+ default_mode=GdbServerModes.MULTI):
+ super().__init__(image_recipe, modified_recipe, binary,
+ default_mode)
+
+ # GDB-specific host paths
+ @property
+ def gdbinit_dir(self):
+ return os.path.join(self.script_dir, 'gdbinit')
+
+ @property
+ def gdbinit(self):
+ return os.path.join(self.gdbinit_dir, 'gdbinit_' + self.id_pretty)
+
+ @property
+ def gdb_script(self):
+ return os.path.join(self.script_dir, 'gdb_' + self.id_pretty)
+
+ # gdbserver files on the target
+ def _gdbserver_tmp_dir(self, mode):
+ return os.path.join('/tmp', 'gdbserver_%s' % self.id_pretty_mode(mode))
+
+ def _gdbserver_pid_file(self, mode):
+ return os.path.join(self._gdbserver_tmp_dir(mode), 'gdbserver.pid')
+
+ def _gdbserver_log_file(self, mode):
+ return os.path.join(self._gdbserver_tmp_dir(mode), 'gdbserver.log')
+
+ def _target_start_cmd(self, gdbserver_mode):
+ """SSH command to start gdbserver on the target device.
+
+ Returns something like:
+ "\"/bin/sh -c '/usr/bin/gdbserver --once :1234 /usr/bin/cmake-example'\""
+ """
+ if gdbserver_mode == GdbServerModes.ONCE:
+ gdbserver_cmd_start = "%s --once :%s %s" % (
+ self.gdb_cross.gdbserver_path, self.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.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.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 _target_kill_cmd(self):
+ """SSH command to kill gdbserver on the target device."""
+ return "\"kill \\$(pgrep -o -f 'gdbserver --attach :%s') 2>/dev/null || true\"" % self.port
+
+
class IdeBase:
diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py
index 603d3cecf3..7fe5a40eb1 100644
--- a/scripts/lib/devtool/ide_plugins/ide_code.py
+++ b/scripts/lib/devtool/ide_plugins/ide_code.py
@@ -9,27 +9,27 @@ import json
import logging
import os
import shutil
-from devtool.ide_plugins import BuildTool, IdeBase, GdbCrossConfig, GdbServerModes, get_devtool_deploy_opts
+from devtool.ide_plugins import BuildTool, IdeBase, DebuggerCrossConfig, GdbCrossConfig, GdbServerModes, get_devtool_deploy_opts
logger = logging.getLogger('devtool')
class GdbCrossConfigVSCode(GdbCrossConfig):
def __init__(self, image_recipe, modified_recipe, binary,
- gdbserver_default_mode=GdbServerModes.ONCE):
+ default_mode=GdbServerModes.ONCE):
super().__init__(image_recipe, modified_recipe, binary,
- gdbserver_default_mode)
+ default_mode)
- def target_ssh_gdbserver_start_args(self, gdbserver_mode=None):
+ def target_ssh_gdbserver_start_args(self, 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)
+ if mode is None:
+ mode = self.default_mode
+ return self._target_ssh_args() + [
+ self._target_start_cmd(mode)
]
def target_ssh_gdbserver_kill_args(self):
@@ -38,13 +38,10 @@ class GdbCrossConfigVSCode(GdbCrossConfig):
returns something like:
['-p', '2222', 'root@target', '"kill $(pgrep -o -f \'gdbserver --attach :1234\') 2>/dev/null || true"']
"""
- return self._target_ssh_gdbserver_args() + [
- self._target_gdbserver_kill_cmd()
+ return self._target_ssh_args() + [
+ self._target_kill_cmd()
]
- def initialize(self):
- pass
-
class IdeVSCode(IdeBase):
"""Manage IDE configurations for VSCode
@@ -289,6 +286,11 @@ class IdeVSCode(IdeBase):
self.dot_code_dir(modified_recipe), prop_file, properties_dicts)
def vscode_launch_bin_dbg(self, gdb_cross_config, gdbserver_mode):
+ """Dispatch to the GDB launch config generator."""
+ return self._vscode_launch_bin_dbg_gdb(gdb_cross_config, gdbserver_mode)
+
+ def _vscode_launch_bin_dbg_gdb(self, gdb_cross_config, gdbserver_mode):
+ """Generate a cppdbg (GDB) launch configuration entry for launch.json."""
modified_recipe = gdb_cross_config.modified_recipe
launch_config = {
@@ -303,7 +305,7 @@ class IdeVSCode(IdeBase):
"MIMode": "gdb",
"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)
+ "miDebuggerServerAddress": "%s:%d" % (modified_recipe.gdb_cross.host, gdb_cross_config.port)
}
# Search for header files in recipe-sysroot.
@@ -384,7 +386,7 @@ class IdeVSCode(IdeBase):
configurations = []
for gdb_cross_config in self.gdb_cross_configs:
if gdb_cross_config.modified_recipe is modified_recipe:
- for gdbserver_mode in gdb_cross_config.gdbserver_modes():
+ for gdbserver_mode in gdb_cross_config.server_modes():
configurations.append(self.vscode_launch_bin_dbg(gdb_cross_config, gdbserver_mode))
launch_dict = {
"version": "0.2.0",
@@ -415,7 +417,7 @@ class IdeVSCode(IdeBase):
for gdb_cross_config in self.gdb_cross_configs:
if gdb_cross_config.modified_recipe is not modified_recipe:
continue
- for gdbserver_mode in gdb_cross_config.gdbserver_modes():
+ for gdbserver_mode in gdb_cross_config.server_modes():
new_task = {
"label": gdb_cross_config.id_pretty_mode(gdbserver_mode),
"type": "shell",
@@ -633,7 +635,7 @@ class IdeVSCode(IdeBase):
for gdb_cross_config in self.gdb_cross_configs:
if gdb_cross_config.modified_recipe is not modified_recipe:
continue
- for gdbserver_mode in gdb_cross_config.gdbserver_modes():
+ for gdbserver_mode in gdb_cross_config.server_modes():
new_task = {
"label": gdb_cross_config.id_pretty(gdbserver_mode),
"type": "shell",
@@ -668,7 +670,7 @@ class IdeVSCode(IdeBase):
self.dot_code_dir(modified_recipe), tasks_file, tasks_dict)
def vscode_tasks(self, args, modified_recipe):
- if modified_recipe.build_tool.is_c_ccp:
+ if modified_recipe.build_tool.is_c_cpp:
self.vscode_tasks_cpp(args, modified_recipe)
elif modified_recipe.build_tool == BuildTool.KERNEL_MODULE:
self.vscode_tasks_kernel_module(args, modified_recipe)
diff --git a/scripts/lib/devtool/ide_plugins/ide_none.py b/scripts/lib/devtool/ide_plugins/ide_none.py
index ed96afa33c..781e832ee8 100644
--- a/scripts/lib/devtool/ide_plugins/ide_none.py
+++ b/scripts/lib/devtool/ide_plugins/ide_none.py
@@ -16,17 +16,17 @@ logger = logging.getLogger('devtool')
class GdbCrossConfigNone(GdbCrossConfig):
def __init__(self, image_recipe, modified_recipe, binary,
- gdbserver_default_mode=GdbServerModes.MULTI):
+ default_mode=GdbServerModes.MULTI):
super().__init__(image_recipe, modified_recipe, binary,
- gdbserver_default_mode)
+ 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)
+ 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:
@@ -36,11 +36,11 @@ class GdbCrossConfigNone(GdbCrossConfig):
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_mode = self.default_mode
+ gdbserver_cmd_start = self._target_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()))
+ " ".join(self._target_ssh_args()))
gdbserver_cmd = ['#!/bin/sh']
gdbserver_cmd.append('if [ "$1" = "stop" ]; then')
gdbserver_cmd.append(' shift')
@@ -48,19 +48,19 @@ class GdbCrossConfigNone(GdbCrossConfig):
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)
+ GdbCrossConfigNone.write_file(self.server_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
+ gdbserver_mode = self.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
+ target_help = '# gdbserver --multi :%d' % self.port
remote_cmd = 'target extended-remote'
else:
target_help = '# gdbserver :%d %s' % (
- self.gdbserver_port, self.binary)
+ self.port, self.binary)
remote_cmd = 'target remote'
gdbinit_lines.append('# On the remote target:')
gdbinit_lines.append(target_help)
@@ -111,7 +111,7 @@ class GdbCrossConfigNone(GdbCrossConfig):
gdbinit_lines.append("end" + os.linesep)
gdbinit_lines.append(
- '%s %s:%d' % (remote_cmd, self.gdb_cross.host, self.gdbserver_port))
+ '%s %s:%d' % (remote_cmd, self.gdb_cross.host, self.port))
gdbinit_lines.append('set remote exec-file ' + self.binary.binary_path)
gdbinit_lines.append('run ' + self.binary.binary_path)
@@ -127,7 +127,7 @@ class GdbCrossConfigNone(GdbCrossConfig):
def initialize(self):
self._gen_gdbserver_start_script()
- if self.binary.runs_as_service and self.gdbserver_default_mode != GdbServerModes.ATTACH:
+ if self.binary.runs_as_service and self.default_mode != GdbServerModes.ATTACH:
self._gen_gdbserver_start_script(GdbServerModes.ATTACH)
self._gen_gdbinit_config()
self._gen_gdb_start_script()
diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py
index 07f5552758..76cbccf618 100755
--- a/scripts/lib/devtool/ide_sdk.py
+++ b/scripts/lib/devtool/ide_sdk.py
@@ -1159,21 +1159,25 @@ def ide_setup(args, config, basepath, workspace):
if args.mode == DevtoolIdeMode.modified:
logger.info("Setting up workspaces for modified recipe: %s" %
str(recipes_modified_names))
- gdbs_cross = {}
+ debuggers = {}
for recipe_name in recipes_modified_names:
recipe_modified = RecipeModified(recipe_name)
recipe_modified.initialize(config, workspace, tinfoil)
bootstrap_tasks += recipe_modified.bootstrap_tasks
recipes_modified.append(recipe_modified)
- if recipe_modified.target_arch not in gdbs_cross:
+ # Key by (arch, toolchain) so recipes with different toolchains
+ # targeting the same arch each get the right debugger.
+ debugger_key = (recipe_modified.target_arch,
+ recipe_modified.toolchain or '')
+ if debugger_key not in debuggers:
target_device = TargetDevice(args)
- gdb_cross = RecipeGdbCross(
+ debugger = RecipeGdbCross(
args, recipe_modified.target_arch, target_device)
- gdb_cross.initialize(config, workspace, tinfoil)
- bootstrap_tasks += gdb_cross.bootstrap_tasks
- gdbs_cross[recipe_modified.target_arch] = gdb_cross
- recipe_modified.gdb_cross = gdbs_cross[recipe_modified.target_arch]
+ debugger.initialize(config, workspace, tinfoil)
+ bootstrap_tasks += debugger.bootstrap_tasks
+ debuggers[debugger_key] = debugger
+ recipe_modified.gdb_cross = debuggers[debugger_key]
finally:
tinfoil.shutdown()
@@ -1191,7 +1195,8 @@ def ide_setup(args, config, basepath, workspace):
config.init_path, basepath, bb_cmd_late, watch=True)
wants_gdbserver = any(
- r.wants_gdbserver for r in recipes_modified)
+ r.wants_gdbserver and r.toolchain == 'gcc'
+ for r in recipes_modified)
for recipe_image in recipes_images:
if wants_gdbserver and recipe_image.gdbserver_missing:
logger.warning(
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 7/9] devtool: ide-sdk add LLDB support for clang toolchain
2026-03-18 22:36 [PATCH 0/9] devtool: ide-sdk clang/LLDB support and minor fixes AdrianF
` (5 preceding siblings ...)
2026-03-18 22:36 ` [PATCH 6/9] devtool: ide-sdk debugger back-end abstraction AdrianF
@ 2026-03-18 22:36 ` AdrianF
2026-03-18 22:36 ` [PATCH 8/9] meta-selftest: refactor cpp examples into .inc files and add clang variants AdrianF
2026-03-18 22:36 ` [PATCH 9/9] oe-selftest: devtool ide-sdk: add clang/LLDB test AdrianF
8 siblings, 0 replies; 13+ messages in thread
From: AdrianF @ 2026-03-18 22:36 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Add support for LLDB (CodeLLDB) remote debugging in VSCode when using
the clang toolchain. This includes:
- New LldbServerConfig class for configuring lldb-server on the target
- LldbServerConfigVSCode for VSCode-specific LLDB configuration
- RecipeLldbNative to handle lldb-native (architecture-agnostic) on the
host
- CodeLLDB VSCode extension recommendation for clang toolchain
- Launch configuration generator for LLDB debugging
- Proper handling of source maps and debug symbol paths for LLDB
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
scripts/lib/devtool/ide_plugins/__init__.py | 63 +++++++++++
scripts/lib/devtool/ide_plugins/ide_code.py | 114 +++++++++++++++++++-
scripts/lib/devtool/ide_sdk.py | 61 ++++++++++-
3 files changed, 229 insertions(+), 9 deletions(-)
diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py
index 8c41afc640..d7d06567ee 100644
--- a/scripts/lib/devtool/ide_plugins/__init__.py
+++ b/scripts/lib/devtool/ide_plugins/__init__.py
@@ -181,6 +181,69 @@ class GdbCrossConfig(DebuggerCrossConfig):
return "\"kill \\$(pgrep -o -f 'gdbserver --attach :%s') 2>/dev/null || true\"" % self.port
+class LldbServerConfig(DebuggerCrossConfig):
+ """Configure lldb-server (platform mode) on the target for CodeLLDB remote debugging.
+
+ Unlike gdbserver, lldb-server platform mode is architecture-agnostic on the host
+ side: a single lldb-native binary handles all target architectures via the
+ LLDB platform protocol that CodeLLDB speaks natively.
+
+ The ATTACH mode is not supported because lldb-server platform does not take a
+ PID argument; attaching is done client-side via 'process attach'.
+ """
+
+ def __init__(self, image_recipe, modified_recipe, binary,
+ default_mode=GdbServerModes.MULTI):
+ super().__init__(image_recipe, modified_recipe, binary,
+ default_mode)
+
+ def _lldb_server_tmp_dir(self, mode):
+ return os.path.join('/tmp', 'lldb_server_%s' % self.id_pretty_mode(mode))
+
+ def _lldb_server_pid_file(self, mode):
+ return os.path.join(self._lldb_server_tmp_dir(mode), 'lldb_server.pid')
+
+ def _lldb_server_log_file(self, mode):
+ return os.path.join(self._lldb_server_tmp_dir(mode), 'lldb_server.log')
+
+ def _target_start_cmd(self, mode):
+ """SSH command to start lldb-server in platform mode on the target."""
+ lldb_server = self.gdb_cross.gdbserver_path
+ # Use '*:<port>' so lldb-server binds on all interfaces (0.0.0.0), not
+ # just loopback. The bare ':<port>' form only binds to 127.0.0.1 in
+ # lldb-server 21.x and the remote lldb client connects from the host.
+ # Start from /tmp because lldb-server creates temp files in its cwd and
+ # the SSH default cwd (/home/root) may not exist on a minimal image.
+ if mode == GdbServerModes.ONCE:
+ cmd = "cd /tmp && %s platform --one-shot --server --listen *:%s" % (
+ lldb_server, self.port)
+ elif mode == GdbServerModes.MULTI:
+ pid_file = self._lldb_server_pid_file(mode)
+ tmp_dir = self._lldb_server_tmp_dir(mode)
+ log_file = self._lldb_server_log_file(mode)
+ cmd = "test -f %s && exit 0; " % pid_file
+ cmd += "mkdir -p %s; " % tmp_dir
+ cmd += "cd %s; " % tmp_dir
+ cmd += "%s platform --server --listen *:%s > %s 2>&1 & " % (
+ lldb_server, self.port, log_file)
+ cmd += "echo \\$! > %s;" % pid_file
+ else:
+ raise DevtoolError(
+ "lldb-server does not support mode %s "
+ "(ATTACH is handled client-side with 'process attach')" % mode)
+ return "\"/bin/sh -c '" + cmd + "'\""
+
+ def _target_kill_cmd(self):
+ """SSH command to stop a MULTI-mode lldb-server on the target."""
+ pid_file = self._lldb_server_pid_file(GdbServerModes.MULTI)
+ tmp_dir = self._lldb_server_tmp_dir(GdbServerModes.MULTI)
+ cmd = ("test -f %(pf)s && kill \\$(cat %(pf)s) 2>/dev/null; rm -rf %(td)s"
+ % {'pf': pid_file, 'td': tmp_dir})
+ return "\"/bin/sh -c '" + cmd + "'\""
+
+ def server_modes(self):
+ """ATTACH mode is not applicable for lldb-server platform."""
+ return [self.default_mode]
class IdeBase:
diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py
index 7fe5a40eb1..1b8434ab1c 100644
--- a/scripts/lib/devtool/ide_plugins/ide_code.py
+++ b/scripts/lib/devtool/ide_plugins/ide_code.py
@@ -9,7 +9,7 @@ import json
import logging
import os
import shutil
-from devtool.ide_plugins import BuildTool, IdeBase, DebuggerCrossConfig, GdbCrossConfig, GdbServerModes, get_devtool_deploy_opts
+from devtool.ide_plugins import BuildTool, IdeBase, DebuggerCrossConfig, GdbCrossConfig, GdbServerModes, LldbServerConfig, get_devtool_deploy_opts
logger = logging.getLogger('devtool')
@@ -43,6 +43,28 @@ class GdbCrossConfigVSCode(GdbCrossConfig):
]
+class LldbServerConfigVSCode(LldbServerConfig):
+ """VSCode-specific lldb-server configuration for CodeLLDB remote debugging."""
+
+ def __init__(self, image_recipe, modified_recipe, binary,
+ default_mode=GdbServerModes.MULTI):
+ super().__init__(image_recipe, modified_recipe, binary,
+ default_mode)
+
+ def target_ssh_gdbserver_start_args(self, mode=None):
+ """SSH argument list to start lldb-server on the target"""
+ if mode is None:
+ mode = self.default_mode
+ return self._target_ssh_args() + [
+ self._target_start_cmd(mode)
+ ]
+
+ def target_ssh_gdbserver_kill_args(self):
+ """SSH argument list to stop a running MULTI-mode lldb-server"""
+ return self._target_ssh_args() + [
+ self._target_kill_cmd()
+ ]
+
class IdeVSCode(IdeBase):
"""Manage IDE configurations for VSCode
@@ -243,6 +265,10 @@ class IdeVSCode(IdeBase):
"ms-vscode.cpptools-extension-pack",
"ms-vscode.cpptools-themes"
]
+ # For clang toolchain, CodeLLDB provides native LLDB debugging in VSCode
+ if (modified_recipe.toolchain == 'clang'
+ and modified_recipe.build_tool.is_c_cpp):
+ recommendations.append("vadimcn.vscode-lldb")
if modified_recipe.build_tool is BuildTool.CMAKE:
recommendations.append("ms-vscode.cmake-tools")
if modified_recipe.build_tool is BuildTool.MESON:
@@ -286,7 +312,9 @@ class IdeVSCode(IdeBase):
self.dot_code_dir(modified_recipe), prop_file, properties_dicts)
def vscode_launch_bin_dbg(self, gdb_cross_config, gdbserver_mode):
- """Dispatch to the GDB launch config generator."""
+ """Dispatch to the GDB or LLDB launch config generator."""
+ if isinstance(gdb_cross_config, LldbServerConfig):
+ return self._vscode_launch_bin_dbg_lldb(gdb_cross_config, gdbserver_mode)
return self._vscode_launch_bin_dbg_gdb(gdb_cross_config, gdbserver_mode)
def _vscode_launch_bin_dbg_gdb(self, gdb_cross_config, gdbserver_mode):
@@ -373,6 +401,80 @@ class IdeVSCode(IdeBase):
return launch_config
+ def _vscode_launch_bin_dbg_lldb(self, lldb_config, gdbserver_mode):
+ """Generate a CodeLLDB (type: lldb) launch configuration entry for launch.json.
+
+ CodeLLDB connects to lldb-server via the LLDB platform protocol. The
+ initCommands select the remote platform and open the connection before
+ the process is launched, so CodeLLDB can inspect and control it.
+ """
+ modified_recipe = lldb_config.modified_recipe
+ gdb_cross = modified_recipe.gdb_cross
+
+ init_commands = [
+ "platform select remote-linux",
+ "platform connect connect://%s:%d" % (gdb_cross.host, lldb_config.port),
+ # Clear the default step-avoid-regexp so std:: and other library
+ # namespaces are not silently skipped on step-in. (default is "std::" in LLDB 15+)
+ "settings set target.process.thread.step-avoid-regexp \"\"",
+ ]
+ # Point LLDB at the installed files in ${D} so it resolves shared
+ # library paths automatically (equivalent of GDB's 'set sysroot').
+ # target.sysroot is not a valid LLDB setting; use the module search
+ # path substitution instead, which requires a target to exist and
+ # therefore must run in preRunCommands, not initCommands.
+ pre_run_commands = [
+ "target modules search-paths add / %s" % modified_recipe.d,
+ ]
+
+ # Search for header files in recipe-sysroot (same as GDB sourceFileMap).
+ source_map = {
+ "/usr/include": os.path.join(modified_recipe.recipe_sysroot, "usr", "include")
+ }
+ if lldb_config.image_recipe.rootfs_dbg:
+ # Map build-time paths back to the workspace source tree.
+ for target_path, host_path in modified_recipe.reverse_debug_prefix_map.items():
+ if host_path.startswith(modified_recipe.real_srctree):
+ source_map[target_path] = (
+ "${workspaceFolder}"
+ + host_path[len(modified_recipe.real_srctree):])
+ else:
+ source_map[target_path] = host_path
+ if "/usr/src/debug" in source_map:
+ logger.error(
+ 'Key "/usr/src/debug" already exists in source_map. '
+ 'Something with DEBUG_PREFIX_MAP looks unexpected and finding '
+ 'sources in the rootfs-dbg will not work as expected.')
+ else:
+ source_map["/usr/src/debug"] = os.path.join(
+ lldb_config.image_recipe.rootfs_dbg, "usr", "src", "debug")
+
+ # Point LLDB at the .debug directories in rootfs-dbg.
+ debug_search_paths = " ".join(
+ modified_recipe.solib_search_path(lldb_config.image_recipe))
+ init_commands.append(
+ "settings set target.debug-file-search-paths %s" % debug_search_paths)
+ else:
+ logger.warning(
+ "Cannot setup debug symbols configuration for LLDB. "
+ "IMAGE_GEN_DEBUGFS is not enabled.")
+
+ launch_config = {
+ "name": lldb_config.id_pretty_mode(gdbserver_mode),
+ "type": "lldb",
+ "request": "launch",
+ "program": lldb_config.binary.binary_host_path,
+ "stopOnEntry": False,
+ "cwd": "/tmp",
+ "preLaunchTask": lldb_config.id_pretty_mode(gdbserver_mode),
+ "initCommands": init_commands,
+ "preRunCommands": pre_run_commands,
+ }
+ if source_map:
+ launch_config["sourceMap"] = source_map
+
+ return launch_config
+
def vscode_launch(self, args, modified_recipe):
"""GDB launch configurations for user-space binaries.
@@ -682,8 +784,12 @@ class IdeVSCode(IdeBase):
self.vscode_extensions(modified_recipe)
self.vscode_c_cpp_properties(modified_recipe)
if args.target:
- self.initialize_gdb_cross_configs(
- image_recipe, modified_recipe, GdbCrossConfigVSCode)
+ if modified_recipe.toolchain == 'clang':
+ self.initialize_gdb_cross_configs(
+ image_recipe, modified_recipe, LldbServerConfigVSCode)
+ else:
+ self.initialize_gdb_cross_configs(
+ image_recipe, modified_recipe, GdbCrossConfigVSCode)
self.vscode_launch(args, modified_recipe)
self.vscode_tasks(args, modified_recipe)
diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py
index 76cbccf618..7c461e6b0e 100755
--- a/scripts/lib/devtool/ide_sdk.py
+++ b/scripts/lib/devtool/ide_sdk.py
@@ -137,6 +137,45 @@ class RecipeGdbCross(RecipeNative):
return self.target_device.host
+class RecipeLldbNative(RecipeNative):
+ """Handle lldb on the host and lldb-server on the target device.
+
+ Unlike GDB which requires a per-architecture gdb-cross-<arch> binary, LLDB
+ is architecture-agnostic: a single lldb-native installation can debug any
+ target architecture via the LLDB platform protocol.
+
+ On the target side, lldb-server (the ${PN}-server sub-package from the lldb
+ recipe) provides the platform server that CodeLLDB connects to.
+ """
+
+ def __init__(self, args, target_device):
+ super().__init__('lldb-native')
+ self.target_device = target_device
+ self._lldb = None
+ self._lldb_server_path = None
+
+ def __find_lldb_server(self, config, tinfoil):
+ """Absolute path of lldb-server on the target (from the lldb recipe)."""
+ recipe_d_lldb = parse_recipe(
+ config, tinfoil, 'lldb', appends=True, filter_workspace=False)
+ if not recipe_d_lldb:
+ raise DevtoolError("Parsing lldb recipe failed")
+ return os.path.join(recipe_d_lldb.getVar('bindir'), 'lldb-server')
+
+ def initialize(self, config, workspace, tinfoil):
+ super()._initialize(config, workspace, tinfoil)
+ self._lldb = os.path.join(self.staging_bindir_native, 'lldb')
+ self._lldb_server_path = self.__find_lldb_server(config, tinfoil)
+
+ @property
+ def gdbserver_path(self):
+ return self._lldb_server_path
+
+ @property
+ def host(self):
+ return self.target_device.host
+
+
class RecipeImage:
"""Handle some image recipe related properties
@@ -169,8 +208,9 @@ class RecipeImage:
if image_d.getVar('IMAGE_GEN_DEBUGFS') == "1":
self.__rootfs_dbg = os.path.join(workdir, 'rootfs-dbg')
- self.gdbserver_missing = 'gdbserver' not in image_d.getVar(
- 'IMAGE_INSTALL') and 'tools-debug' not in image_d.getVar('IMAGE_FEATURES')
+ package_install = image_d.getVar('PACKAGE_INSTALL').split()
+ self.gdbserver_missing = 'gdbserver' not in package_install
+ self.lldb_server_missing = 'lldb-server' not in package_install
@property
def debug_support(self):
@@ -1172,8 +1212,11 @@ def ide_setup(args, config, basepath, workspace):
recipe_modified.toolchain or '')
if debugger_key not in debuggers:
target_device = TargetDevice(args)
- debugger = RecipeGdbCross(
- args, recipe_modified.target_arch, target_device)
+ if recipe_modified.toolchain == 'clang':
+ debugger = RecipeLldbNative(args, target_device)
+ else:
+ debugger = RecipeGdbCross(
+ args, recipe_modified.target_arch, target_device)
debugger.initialize(config, workspace, tinfoil)
bootstrap_tasks += debugger.bootstrap_tasks
debuggers[debugger_key] = debugger
@@ -1197,12 +1240,20 @@ def ide_setup(args, config, basepath, workspace):
wants_gdbserver = any(
r.wants_gdbserver and r.toolchain == 'gcc'
for r in recipes_modified)
+ wants_lldb_server = any(
+ r.wants_gdbserver and r.toolchain == 'clang'
+ for r in recipes_modified)
for recipe_image in recipes_images:
if wants_gdbserver and recipe_image.gdbserver_missing:
logger.warning(
"gdbserver not installed in image %s. Remote debugging will not be available" % recipe_image)
+ if wants_lldb_server and recipe_image.lldb_server_missing:
+ logger.warning(
+ "lldb-server not installed in image %s. "
+ "Remote debugging with LLDB (CodeLLDB) will not be available. "
+ "Add 'lldb-server' to IMAGE_INSTALL." % recipe_image)
- if wants_gdbserver and recipe_image.combine_dbg_image is False:
+ if (wants_gdbserver or wants_lldb_server) and recipe_image.combine_dbg_image is False:
logger.warning(
'IMAGE_CLASSES += "image-combined-dbg" is missing for image %s. Remote debugging will not find debug symbols from rootfs-dbg.' % recipe_image)
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 8/9] meta-selftest: refactor cpp examples into .inc files and add clang variants
2026-03-18 22:36 [PATCH 0/9] devtool: ide-sdk clang/LLDB support and minor fixes AdrianF
` (6 preceding siblings ...)
2026-03-18 22:36 ` [PATCH 7/9] devtool: ide-sdk add LLDB support for clang toolchain AdrianF
@ 2026-03-18 22:36 ` AdrianF
2026-03-18 22:36 ` [PATCH 9/9] oe-selftest: devtool ide-sdk: add clang/LLDB test AdrianF
8 siblings, 0 replies; 13+ messages in thread
From: AdrianF @ 2026-03-18 22:36 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Refactor cmake-example.bb and meson-example.bb to extract common
build logic into separate .inc files. Add clang variants of both
examples to enable testing with alternative toolchains.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
.../recipes-test/cpp/cmake-example-clang.bb | 13 +++++++
.../recipes-test/cpp/cmake-example.bb | 20 ++---------
.../recipes-test/cpp/cmake-example.inc | 29 +++++++++++++++
.../recipes-test/cpp/cmake-example/run-ptest | 10 ------
.../recipes-test/cpp/cpp-example.inc | 20 +++++++----
.../recipes-test/cpp/files/CMakeLists.txt | 35 +++++++++---------
.../recipes-test/cpp/files/meson.build | 17 +++++----
.../recipes-test/cpp/files/meson.options | 6 ++++
.../cpp/{meson-example => files}/run-ptest | 4 +--
.../recipes-test/cpp/meson-example-clang.bb | 13 +++++++
.../recipes-test/cpp/meson-example.bb | 24 ++-----------
.../recipes-test/cpp/meson-example.inc | 36 +++++++++++++++++++
12 files changed, 147 insertions(+), 80 deletions(-)
create mode 100644 meta-selftest/recipes-test/cpp/cmake-example-clang.bb
create mode 100644 meta-selftest/recipes-test/cpp/cmake-example.inc
delete mode 100644 meta-selftest/recipes-test/cpp/cmake-example/run-ptest
rename meta-selftest/recipes-test/cpp/{meson-example => files}/run-ptest (51%)
create mode 100644 meta-selftest/recipes-test/cpp/meson-example-clang.bb
create mode 100644 meta-selftest/recipes-test/cpp/meson-example.inc
diff --git a/meta-selftest/recipes-test/cpp/cmake-example-clang.bb b/meta-selftest/recipes-test/cpp/cmake-example-clang.bb
new file mode 100644
index 0000000000..bf20b94e14
--- /dev/null
+++ b/meta-selftest/recipes-test/cpp/cmake-example-clang.bb
@@ -0,0 +1,13 @@
+#
+# Copyright OpenEmbedded Contributors
+#
+# SPDX-License-Identifier: MIT
+#
+
+SUMMARY = "A C++ example compiled with cmake and clang."
+
+require cmake-example.inc
+
+TOOLCHAIN = "clang"
+EX_BINARY_NAME = "${BPN}"
+EX_SERVICE_USER = "cmake-example"
diff --git a/meta-selftest/recipes-test/cpp/cmake-example.bb b/meta-selftest/recipes-test/cpp/cmake-example.bb
index aecfcf780a..19d056fdd8 100644
--- a/meta-selftest/recipes-test/cpp/cmake-example.bb
+++ b/meta-selftest/recipes-test/cpp/cmake-example.bb
@@ -4,22 +4,8 @@
# SPDX-License-Identifier: MIT
#
-SUMMARY = "A C++ example compiled with cmake."
+SUMMARY = "A C++ example compiled with cmake and GCC."
-require cpp-example.inc
+require cmake-example.inc
-SRC_URI += "file://CMakeLists.txt"
-
-inherit cmake-qemu
-
-PACKAGECONFIG[failing_test] = "-DFAILING_TEST=ON"
-
-FILES:${PN}-ptest += "${bindir}/test-cmake-example"
-
-do_run_tests () {
- bbnote ${DESTDIR:+DESTDIR=${DESTDIR} }${CMAKE_VERBOSE} cmake --build '${B}' --target test -- ${EXTRA_OECMAKE_BUILD}
- eval ${DESTDIR:+DESTDIR=${DESTDIR} }${CMAKE_VERBOSE} cmake --build '${B}' --target test -- ${EXTRA_OECMAKE_BUILD}
-}
-do_run_tests[doc] = "Run cmake --target=test using qemu-user"
-
-addtask do_run_tests after do_compile
+TOOLCHAIN = "gcc"
diff --git a/meta-selftest/recipes-test/cpp/cmake-example.inc b/meta-selftest/recipes-test/cpp/cmake-example.inc
new file mode 100644
index 0000000000..eb023d389a
--- /dev/null
+++ b/meta-selftest/recipes-test/cpp/cmake-example.inc
@@ -0,0 +1,29 @@
+#
+# Copyright OpenEmbedded Contributors
+#
+# SPDX-License-Identifier: MIT
+#
+
+require cpp-example.inc
+
+SRC_URI += "file://CMakeLists.txt"
+
+inherit cmake-qemu
+
+PACKAGECONFIG[failing_test] = "-DFAILING_TEST=ON"
+
+# Support installing all recipe variants in parallel
+EXTRA_OECMAKE += "\
+ -DBINARY_NAME=${EX_BINARY_NAME} \
+ -DTEST_BINARY_NAME=${EX_TEST_BINARY_NAME} \
+"
+
+FILES:${PN}-ptest += "${bindir}/${EX_TEST_BINARY_NAME}"
+
+do_run_tests () {
+ bbnote ${DESTDIR:+DESTDIR=${DESTDIR} }${CMAKE_VERBOSE} cmake --build '${B}' --target test -- ${EXTRA_OECMAKE_BUILD}
+ eval ${DESTDIR:+DESTDIR=${DESTDIR} }${CMAKE_VERBOSE} cmake --build '${B}' --target test -- ${EXTRA_OECMAKE_BUILD}
+}
+do_run_tests[doc] = "Run cmake --target=test using qemu-user"
+
+addtask do_run_tests after do_compile
diff --git a/meta-selftest/recipes-test/cpp/cmake-example/run-ptest b/meta-selftest/recipes-test/cpp/cmake-example/run-ptest
deleted file mode 100644
index 94b620a198..0000000000
--- a/meta-selftest/recipes-test/cpp/cmake-example/run-ptest
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/sh
-#
-# Copyright OpenEmbedded Contributors
-#
-# SPDX-License-Identifier: MIT
-#
-
-test-cmake-example
-
-# Note: run-ptests exits with exit value from test-cmake-example
diff --git a/meta-selftest/recipes-test/cpp/cpp-example.inc b/meta-selftest/recipes-test/cpp/cpp-example.inc
index 0671824d1c..0070d17201 100644
--- a/meta-selftest/recipes-test/cpp/cpp-example.inc
+++ b/meta-selftest/recipes-test/cpp/cpp-example.inc
@@ -35,10 +35,16 @@ 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}"
+GROUPADD_PARAM:${PN} = "--system ${EX_SERVICE_USER}"
+USERADD_PARAM:${PN} = "--system --home /var/lib/${EX_SERVICE_USER} --no-create-home --shell /bin/false --gid ${EX_SERVICE_USER} ${EX_SERVICE_USER}"
+EX_SERVICE_USER ?= "${BPN}"
EX_BINARY_NAME ?= "${BPN}"
+EX_TEST_BINARY_NAME ?= "test-${EX_BINARY_NAME}"
+
+do_install_ptest() {
+ sed -i -e 's|@TEST_BINARY_NAME@|${EX_TEST_BINARY_NAME}|g' ${D}${PTEST_PATH}/run-ptest
+}
do_install:append() {
# Install configuration file owned by the recipe's unprivileged user.
@@ -47,7 +53,7 @@ do_install:append() {
# in the final image. devtool deploy-target is a raw file copy and does not run
# pkg_postinst, so the UID in ${D} must already be correct.
install -d ${D}${sysconfdir}
- install -m 0644 -g ${BPN} -o ${BPN} ${S}/cpp-example.conf ${D}${sysconfdir}/${BPN}.conf
+ install -m 0644 -g ${EX_SERVICE_USER} -o ${EX_SERVICE_USER} ${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
@@ -57,8 +63,8 @@ do_install:append() {
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' \
+ -e 's|@USER@|${EX_SERVICE_USER}|g' \
+ -e 's|@GROUP@|${EX_SERVICE_USER}|g' \
${D}${systemd_system_unitdir}/${BPN}.service
else
install -d ${D}${sysconfdir}/init.d
@@ -66,8 +72,8 @@ do_install:append() {
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' \
+ -e 's|@USER@|${EX_SERVICE_USER}|g' \
+ -e 's|@GROUP@|${EX_SERVICE_USER}|g' \
${D}${sysconfdir}/init.d/${BPN}
fi
}
diff --git a/meta-selftest/recipes-test/cpp/files/CMakeLists.txt b/meta-selftest/recipes-test/cpp/files/CMakeLists.txt
index e363f31af2..8802839702 100644
--- a/meta-selftest/recipes-test/cpp/files/CMakeLists.txt
+++ b/meta-selftest/recipes-test/cpp/files/CMakeLists.txt
@@ -14,6 +14,9 @@ project(cmake-example
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
option(FAILING_TEST "Compile a failing unit test to test the test infrastructure" OFF)
+set(BINARY_NAME "cmake-example" CACHE STRING "Name of the installed executable and library prefix")
+set(TEST_BINARY_NAME "test-cmake-example" CACHE STRING "Name of the installed test executable")
+
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED On)
set(CMAKE_CXX_EXTENSIONS Off)
@@ -21,7 +24,7 @@ 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")
+set(CPP_EXAMPLE_CONFIG_PATH "${CMAKE_INSTALL_FULL_SYSCONFDIR}/${BINARY_NAME}.conf")
# Generate config.h from config.h.in
configure_file(config.h.in config.h @ONLY)
@@ -30,44 +33,44 @@ configure_file(config.h.in config.h @ONLY)
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
+add_library(${BINARY_NAME}-lib cpp-example-lib.cpp cpp-example-lib.hpp)
+set_target_properties(${BINARY_NAME}-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_include_directories(${BINARY_NAME}-lib PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
-target_link_libraries(cmake-example-lib PRIVATE json-c::json-c)
+target_link_libraries(${BINARY_NAME}-lib PRIVATE json-c::json-c)
-install(TARGETS cmake-example-lib
+install(TARGETS ${BINARY_NAME}-lib
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
# 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)
+add_executable(${BINARY_NAME} cpp-example.cpp)
+target_include_directories(${BINARY_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+target_link_libraries(${BINARY_NAME} PRIVATE ${BINARY_NAME}-lib)
-install(TARGETS cmake-example
+install(TARGETS ${BINARY_NAME}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
# 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)
+add_executable(${TEST_BINARY_NAME} test-cpp-example.cpp)
+target_include_directories(${TEST_BINARY_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+target_link_libraries(${TEST_BINARY_NAME} PRIVATE ${BINARY_NAME}-lib)
if (FAILING_TEST)
- target_compile_definitions(test-cmake-example PRIVATE FAIL_COMPARISON_STR="foo")
+ target_compile_definitions(${TEST_BINARY_NAME} PRIVATE FAIL_COMPARISON_STR="foo")
endif(FAILING_TEST)
-install(TARGETS test-cmake-example
+install(TARGETS ${TEST_BINARY_NAME}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
include(CTest)
-add_test(NAME test-cmake-example COMMAND test-cmake-example)
+add_test(NAME ${TEST_BINARY_NAME} COMMAND ${TEST_BINARY_NAME})
diff --git a/meta-selftest/recipes-test/cpp/files/meson.build b/meta-selftest/recipes-test/cpp/files/meson.build
index 53248c4380..3cb4669dfa 100644
--- a/meta-selftest/recipes-test/cpp/files/meson.build
+++ b/meta-selftest/recipes-test/cpp/files/meson.build
@@ -16,8 +16,11 @@ if get_option('FAILING_TEST').enabled()
add_project_arguments('-DFAIL_COMPARISON_STR=foo', language: 'cpp')
endif
+binary_name = get_option('BINARY_NAME')
+test_binary_name = get_option('TEST_BINARY_NAME')
+
# Generate config.h from config.h.in
-config_path = get_option('sysconfdir') / 'meson-example.conf'
+config_path = get_option('sysconfdir') / get_option('CONFIG_FILE_NAME')
conf_data = configuration_data()
conf_data.set('CPP_EXAMPLE_CONFIG_PATH', config_path)
configure_file(input : 'config.h.in',
@@ -27,7 +30,7 @@ configure_file(input : 'config.h.in',
# Include the build directory for config.h
inc_dir = include_directories('.')
-mesonexlib = shared_library('mesonexlib',
+exlib = shared_library(binary_name + 'lib',
'cpp-example-lib.cpp', 'cpp-example-lib.hpp',
version: meson.project_version(),
soversion: meson.project_version().split('.')[0],
@@ -36,18 +39,18 @@ mesonexlib = shared_library('mesonexlib',
install : true
)
-executable('mesonex',
+executable(binary_name,
'cpp-example.cpp',
- link_with : mesonexlib,
+ link_with : exlib,
include_directories : inc_dir,
install : true
)
-test_mesonex = executable('test-mesonex',
+test_exe = executable(test_binary_name,
'test-cpp-example.cpp',
- link_with : mesonexlib,
+ link_with : exlib,
include_directories : inc_dir,
install : true
)
-test('meson example test', test_mesonex)
+test('meson example test', test_exe)
diff --git a/meta-selftest/recipes-test/cpp/files/meson.options b/meta-selftest/recipes-test/cpp/files/meson.options
index 58a0bf9e61..374e346197 100644
--- a/meta-selftest/recipes-test/cpp/files/meson.options
+++ b/meta-selftest/recipes-test/cpp/files/meson.options
@@ -1,3 +1,9 @@
option('FAILING_TEST', type : 'feature', value : 'disabled',
description : 'Compile a failing unit test to test the test infrastructure')
+option('BINARY_NAME', type : 'string', value : 'mesonex',
+ description : 'Name of the installed executable')
+option('TEST_BINARY_NAME', type : 'string', value : 'test-mesonex',
+ description : 'Name of the installed test executable')
+option('CONFIG_FILE_NAME', type : 'string', value : 'meson-example.conf',
+ description : 'Configuration file name in sysconfdir')
diff --git a/meta-selftest/recipes-test/cpp/meson-example/run-ptest b/meta-selftest/recipes-test/cpp/files/run-ptest
similarity index 51%
rename from meta-selftest/recipes-test/cpp/meson-example/run-ptest
rename to meta-selftest/recipes-test/cpp/files/run-ptest
index b1804f0096..62c24db04f 100644
--- a/meta-selftest/recipes-test/cpp/meson-example/run-ptest
+++ b/meta-selftest/recipes-test/cpp/files/run-ptest
@@ -5,6 +5,6 @@
# SPDX-License-Identifier: MIT
#
-test-mesonex
+@TEST_BINARY_NAME@
-# Note: run-ptests exits with exit value from test-mesonex
+# Note: run-ptest exits with exit value from @TEST_BINARY_NAME@
diff --git a/meta-selftest/recipes-test/cpp/meson-example-clang.bb b/meta-selftest/recipes-test/cpp/meson-example-clang.bb
new file mode 100644
index 0000000000..341ade21f9
--- /dev/null
+++ b/meta-selftest/recipes-test/cpp/meson-example-clang.bb
@@ -0,0 +1,13 @@
+#
+# Copyright OpenEmbedded Contributors
+#
+# SPDX-License-Identifier: MIT
+#
+
+SUMMARY = "A C++ example compiled with meson and clang."
+
+require meson-example.inc
+
+TOOLCHAIN = "clang"
+EX_BINARY_NAME = "mesonex-clang"
+EX_SERVICE_USER = "meson-example"
diff --git a/meta-selftest/recipes-test/cpp/meson-example.bb b/meta-selftest/recipes-test/cpp/meson-example.bb
index da0ea18376..b2335f5c26 100644
--- a/meta-selftest/recipes-test/cpp/meson-example.bb
+++ b/meta-selftest/recipes-test/cpp/meson-example.bb
@@ -4,26 +4,8 @@
# SPDX-License-Identifier: MIT
#
-SUMMARY = "A C++ example compiled with meson."
+SUMMARY = "A C++ example compiled with meson and GCC."
-require cpp-example.inc
+require meson-example.inc
-SRC_URI += "\
- file://meson.build \
- file://meson.options \
-"
-
-inherit pkgconfig meson
-
-PACKAGECONFIG[failing_test] = "-DFAILING_TEST=enabled"
-
-FILES:${PN}-ptest += "${bindir}/test-mesonex"
-
-do_run_tests () {
- meson test -C "${B}" --no-rebuild
-}
-do_run_tests[doc] = "Run meson test using qemu-user"
-
-addtask do_run_tests after do_compile
-
-EX_BINARY_NAME = "mesonex"
+TOOLCHAIN = "gcc"
diff --git a/meta-selftest/recipes-test/cpp/meson-example.inc b/meta-selftest/recipes-test/cpp/meson-example.inc
new file mode 100644
index 0000000000..2937be27f8
--- /dev/null
+++ b/meta-selftest/recipes-test/cpp/meson-example.inc
@@ -0,0 +1,36 @@
+#
+# Copyright OpenEmbedded Contributors
+#
+# SPDX-License-Identifier: MIT
+#
+
+SUMMARY = "A C++ example compiled with meson."
+
+require cpp-example.inc
+
+SRC_URI += "\
+ file://meson.build \
+ file://meson.options \
+"
+
+inherit pkgconfig meson
+
+PACKAGECONFIG[failing_test] = "-DFAILING_TEST=enabled"
+
+# Support installing all recipes variants in parallel
+EXTRA_OEMESON += "\
+ -DBINARY_NAME=${EX_BINARY_NAME} \
+ -DTEST_BINARY_NAME=${EX_TEST_BINARY_NAME} \
+ -DCONFIG_FILE_NAME=${BPN}.conf \
+"
+
+FILES:${PN}-ptest += "${bindir}/${EX_TEST_BINARY_NAME}"
+
+do_run_tests () {
+ meson test -C "${B}" --no-rebuild
+}
+do_run_tests[doc] = "Run meson test using qemu-user"
+
+addtask do_run_tests after do_compile
+
+EX_BINARY_NAME = "mesonex"
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 9/9] oe-selftest: devtool ide-sdk: add clang/LLDB test
2026-03-18 22:36 [PATCH 0/9] devtool: ide-sdk clang/LLDB support and minor fixes AdrianF
` (7 preceding siblings ...)
2026-03-18 22:36 ` [PATCH 8/9] meta-selftest: refactor cpp examples into .inc files and add clang variants AdrianF
@ 2026-03-18 22:36 ` AdrianF
2026-03-20 7:12 ` [OE-core] " Mathieu Dubois-Briand
8 siblings, 1 reply; 13+ messages in thread
From: AdrianF @ 2026-03-18 22:36 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Add test_devtool_ide_sdk_code_cmake_clang to verify the full devtool
ide-sdk workflow for a cmake recipe built with clang. Unlike the gcc
variant the clang recipe uses lldb-server for remote debugging and
CodeLLDB (vadimcn.vscode-lldb) as the VS Code debug adapter.
The test covers:
- devtool modify + devtool ide-sdk with ide=code
- cmake preset compilation and CTest execution (same as the gcc test)
- extensions.json recommends vadimcn.vscode-lldb
- launch.json uses "type": "lldb" (CodeLLDB) instead of "type": "cppdbg"
- End-to-end lldb --batch remote debugging session via lldb-server
platform mode running on qemu
Supporting changes:
- _write_bb_config: accept optional extra_packages parameter so the
clang test can add lldb-server to IMAGE_INSTALL
- _verify_launch_json_lldb: new helper that validates the CodeLLDB
launch.json structure (type, initCommands, program, cwd, preLaunchTask)
- _lldb_server_debugging_once: new helper that reads the preLaunchTask
SSH command from tasks.json, starts lldb-server on the target, and
runs lldb --batch to verify a breakpoint at main is hit
- _verify_service_running: use pgrep with exact regex (^name$) for exact
process name matching; without that, pgrep would also match
cmake-example-clang (truncated to 'cmake-example-c' in
/proc/pid/comm) when checking for cmake-example, returning two PIDs
and failing the isdigit() assertion
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 196 +++++++++++++++++++++++-
1 file changed, 192 insertions(+), 4 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 6c6f22a667..2cd03e68d6 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -2590,13 +2590,15 @@ class DevtoolIdeSdkTests(DevtoolBase):
if self.logger.isEnabledFor(logging.DEBUG):
self._cmd_logger = self.logger
- def _write_bb_config(self, recipe_names):
+ def _write_bb_config(self, recipe_names, extra_packages=None):
"""Helper to write the bitbake local.conf file"""
+ image_install = 'gdbserver ' + ' '.join([r + '-ptest' for r in recipe_names])
+ if extra_packages:
+ image_install += ' ' + ' '.join(extra_packages)
conf_lines = [
'IMAGE_CLASSES += "image-combined-dbg"',
'IMAGE_GEN_DEBUGFS = "1"',
- 'IMAGE_INSTALL:append = " gdbserver %s"' % ' '.join(
- [r + '-ptest' for r in recipe_names]),
+ 'IMAGE_INSTALL:append = " %s"' % image_install,
'DISTRO_FEATURES:append = " ptest"'
# Static UIDs/GIDs are required so that files installed via
# "install -o ${BPN}" in do_install embed the same UID that gets
@@ -2938,7 +2940,9 @@ class DevtoolIdeSdkTests(DevtoolBase):
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)
+ # Use anchored regex (^name$) instead of pgrep -x because the target
+ # may have busybox pgrep which does not support the -x flag.
+ status, output = qemu.run("pgrep '^%s$'" % service_name)
self.assertEqual(status, 0, msg="%s service not running: %s" %
(service_name, output))
self.assertTrue(output.strip().isdigit(),
@@ -3612,6 +3616,190 @@ class DevtoolIdeSdkTests(DevtoolBase):
runCmdEnv('meson setup %s' % tempdir_meson, cwd=cpp_example_src, output_log=self._cmd_logger)
runCmdEnv('meson compile', cwd=tempdir_meson, output_log=self._cmd_logger)
+ def _verify_launch_json_lldb(self, tempdir):
+ """Verify the launch.json file contains valid CodeLLDB (type: lldb) configurations."""
+ launch_json_path = os.path.join(tempdir, '.vscode', 'launch.json')
+ 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.assertGreater(len(configurations), 0,
+ "Should have at least one debug configuration")
+
+ for config in configurations:
+ config_name = config.get("name", "Unknown")
+ # CodeLLDB configs use "type": "lldb", not "type": "cppdbg"
+ self.assertEqual(config["type"], "lldb",
+ f"Configuration '{config_name}' should use lldb type (CodeLLDB)")
+ self.assertNotIn("MIMode", config,
+ f"Configuration '{config_name}' should not have MIMode (CodeLLDB)")
+ self.assertNotIn("miDebuggerPath", config,
+ f"Configuration '{config_name}' should not have miDebuggerPath")
+ self.assertEqual(config["request"], "launch",
+ f"Configuration '{config_name}' should be launch type")
+ self.assertEqual(config["cwd"], "/tmp",
+ f"Configuration '{config_name}' cwd should be /tmp (writable on target)")
+
+ # Verify initCommands contain the platform connect sequence
+ init_commands = config.get("initCommands", [])
+ self.assertTrue(any("platform select remote-linux" in cmd
+ for cmd in init_commands),
+ f"Configuration '{config_name}' should select remote-linux platform")
+ self.assertTrue(any("platform connect" in cmd for cmd in init_commands),
+ f"Configuration '{config_name}' should connect to remote platform")
+
+ # Verify program path points into the image directory
+ program = config.get("program", "")
+ self.assertTrue(program.startswith("/"),
+ f"Configuration '{config_name}' program should be an absolute path")
+ self.assertIn("/image/", program,
+ f"Configuration '{config_name}' program should be in image directory")
+
+ # Verify preLaunchTask referencing the lldb-server start task
+ task = config.get("preLaunchTask", "")
+ self.assertTrue(task,
+ f"Configuration '{config_name}' preLaunchTask should not be empty")
+
+ def _lldb_server_debugging_once(self, tempdir, qemu, recipe_name, magic_string):
+ """Verify lldb-server (platform mode) + lldb batch debugging works end-to-end.
+
+ Reads the preLaunchTask SSH command from tasks.json to start lldb-server
+ on the target, then runs lldb --batch to perform a minimal debugging
+ session and checks that the expected magic string is visible.
+ """
+ sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
+
+ with open(os.path.join(tempdir, '.vscode', 'launch.json')) as f:
+ launch_d = json.load(f)
+ with open(os.path.join(tempdir, '.vscode', 'tasks.json')) as f:
+ tasks_d = json.load(f)
+
+ # Find the first *_once or *_multi config
+ lldb_config = next(
+ (c for c in launch_d["configurations"]
+ if "_once" in c["name"] or "_multi" in c["name"]), None)
+ self.assertIsNotNone(lldb_config, "Should have at least one lldb debug configuration")
+
+ prelaunch_task_name = lldb_config["preLaunchTask"]
+ prelaunch_task = next(
+ (t for t in tasks_d["tasks"] if t["label"] == prelaunch_task_name), None)
+ self.assertIsNotNone(prelaunch_task,
+ "preLaunchTask '%s' not found in tasks.json" % prelaunch_task_name)
+
+ # Extract the SSH command and start lldb-server on the target
+ task_command = prelaunch_task["command"]
+ task_args = prelaunch_task["args"]
+ self.assertEqual(task_command, "ssh",
+ "preLaunchTask should use ssh to start lldb-server")
+ ssh_cmd = [task_command] + task_args
+ if ssh_cmd[-1].startswith('"') and ssh_cmd[-1].endswith('"'):
+ ssh_cmd[-1] = ssh_cmd[-1][1:-1]
+
+ # Extract connection details from initCommands
+ init_commands = lldb_config["initCommands"]
+ connect_cmd = next((c for c in init_commands if "platform connect" in c), None)
+ self.assertIsNotNone(connect_cmd, "initCommands should contain a platform connect command")
+
+ # Find lldb binary from lldb-native sysroot
+ lldb_native_sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'lldb-native')
+ lldb_binary = os.path.join(lldb_native_sysroot, 'usr', 'bin', 'lldb')
+ self.assertExists(lldb_binary, "lldb binary should exist in lldb-native sysroot")
+
+ with RunCmdBackground(ssh_cmd, output_log=self._cmd_logger):
+ time.sleep(1)
+
+ # Verify lldb-server is running on the target
+ r = runCmd('ssh %s root@%s ps' % (sshargs, qemu.ip),
+ output_log=self._cmd_logger)
+ self.assertIn("lldb-server", r.output,
+ "lldb-server should be running on target")
+
+ # Run lldb --batch: connect to platform, set a breakpoint on main, run
+ program = lldb_config["program"]
+ source_map = lldb_config.get("sourceMap", {})
+
+ pre_run_commands = lldb_config.get("preRunCommands", [])
+
+ lldb_batch = [lldb_binary, "--batch"]
+ for cmd in init_commands:
+ lldb_batch += ["-o", cmd]
+ lldb_batch += ["-o", "target create %s" % program]
+ for k, v in source_map.items():
+ v_resolved = v.replace("${workspaceFolder}", tempdir)
+ lldb_batch += ["-o", "settings set target.source-map %s %s" % (k, v_resolved)]
+ for cmd in pre_run_commands:
+ lldb_batch += ["-o", cmd]
+ lldb_batch += [
+ "-o", "b main",
+ "-o", "run",
+ "-o", "continue",
+ "-o", "exit"
+ ]
+ r = runCmd(lldb_batch, output_log=self._cmd_logger)
+ self.assertEqual(r.status, 0, "lldb batch session failed: %s" % r.output)
+ self.assertIn("stop reason = breakpoint", r.output,
+ "lldb should have stopped at main breakpoint")
+
+ @OETestTag("runqemu")
+ def test_devtool_ide_sdk_code_cmake_clang(self):
+ """Verify a cmake recipe built with clang works with ide=code (CodeLLDB debugging).
+
+ This test uses the cmake-example-clang recipe which is a cmake-example variant
+ built with clang. It installs a separate binary (cmake-example-clang) so all four
+ recipe variants (cmake/meson x gcc/clang) can be installed in the same image
+ without conflicts. It is configured to use lldb-server for debugging instead of
+ gdbserver. The test flow is similar to test_devtool_ide_sdk_code_cmake but with
+ additional checks related to lldb:
+ - devtool ide-sdk selects lldb-native / lldb-server instead of gdb-cross
+ - launch.json uses "type": "lldb" (CodeLLDB) instead of "type": "cppdbg"
+ - extensions.json recommends vadimcn.vscode-lldb
+ - A basic lldb --batch remote debugging session succeeds against the
+ lldb-server platform running on the Qemu target
+ """
+ recipe_name = "cmake-example-clang"
+ build_file = "CMakeLists.txt"
+ testimage = "oe-selftest-image"
+
+ self._check_workspace()
+ self._write_bb_config([recipe_name], extra_packages=['lldb-server'])
+
+ 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)
+
+ # Verify the cmake preset still works (build system unchanged)
+ compile_cmd = self._verify_cmake_preset(tempdir)
+
+ # Verify the install && deploy-target task script exists
+ self._verify_install_script_code(tempdir, recipe_name)
+
+ # Verify extensions.json recommends CodeLLDB instead of / alongside cpptools
+ with open(os.path.join(tempdir, '.vscode', 'extensions.json')) as ext_j:
+ ext_d = json.load(ext_j)
+ recommendations = ext_d.get('recommendations', [])
+ self.assertIn('vadimcn.vscode-lldb', recommendations,
+ 'vadimcn.vscode-lldb should be recommended for clang recipes')
+
+ # Verify launch.json uses CodeLLDB format
+ self._verify_launch_json_lldb(tempdir)
+
+ # Verify deployment and lldb batch remote debugging work end-to-end
+ recipe_id, _ = self._get_recipe_ids(recipe_name)
+ install_deploy_cmd = os.path.join(
+ self._workspace_scripts_dir(recipe_name),
+ 'install_and_deploy_' + recipe_id)
+ runCmd(install_deploy_cmd, output_log=self._cmd_logger)
+
+ self._lldb_server_debugging_once(tempdir, qemu, recipe_name,
+ DevtoolIdeSdkTests.MAGIC_STRING_ORIG)
+
def test_devtool_ide_sdk_plugins(self):
"""Test that devtool ide-sdk can use plugins from other layers."""
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [OE-core] [PATCH 9/9] oe-selftest: devtool ide-sdk: add clang/LLDB test
2026-03-18 22:36 ` [PATCH 9/9] oe-selftest: devtool ide-sdk: add clang/LLDB test AdrianF
@ 2026-03-20 7:12 ` Mathieu Dubois-Briand
2026-03-20 9:11 ` adrian.freihofer
0 siblings, 1 reply; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2026-03-20 7:12 UTC (permalink / raw)
To: adrian.freihofer, openembedded-core
On Wed Mar 18, 2026 at 11:36 PM CET, Adrian Freihofer via lists.openembedded.org wrote:
> From: Adrian Freihofer <adrian.freihofer@siemens.com>
>
> Add test_devtool_ide_sdk_code_cmake_clang to verify the full devtool
> ide-sdk workflow for a cmake recipe built with clang. Unlike the gcc
> variant the clang recipe uses lldb-server for remote debugging and
> CodeLLDB (vadimcn.vscode-lldb) as the VS Code debug adapter.
>
> The test covers:
> - devtool modify + devtool ide-sdk with ide=code
> - cmake preset compilation and CTest execution (same as the gcc test)
> - extensions.json recommends vadimcn.vscode-lldb
> - launch.json uses "type": "lldb" (CodeLLDB) instead of "type": "cppdbg"
> - End-to-end lldb --batch remote debugging session via lldb-server
> platform mode running on qemu
>
> Supporting changes:
> - _write_bb_config: accept optional extra_packages parameter so the
> clang test can add lldb-server to IMAGE_INSTALL
> - _verify_launch_json_lldb: new helper that validates the CodeLLDB
> launch.json structure (type, initCommands, program, cwd, preLaunchTask)
> - _lldb_server_debugging_once: new helper that reads the preLaunchTask
> SSH command from tasks.json, starts lldb-server on the target, and
> runs lldb --batch to verify a breakpoint at main is hit
> - _verify_service_running: use pgrep with exact regex (^name$) for exact
> process name matching; without that, pgrep would also match
> cmake-example-clang (truncated to 'cmake-example-c' in
> /proc/pid/comm) when checking for cmake-example, returning two PIDs
> and failing the isdigit() assertion
>
> Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
> ---
Hi Adrian,
Thanks for your patch.
It looks like the added test_devtool_ide_sdk_code_cmake_clang test is
failing on the autobuilder:
2026-03-19 19:34:48,650 - oe-selftest - INFO - devtool.DevtoolIdeSdkTests.test_devtool_ide_sdk_code_cmake_clang (subunit.RemotedTestCase)
2026-03-19 19:34:48,651 - oe-selftest - INFO - ... FAIL
...
2026-03-19 19:34:48,651 - oe-selftest - INFO - 11: 3/39 214/681 (495.87s) (0 failed) (devtool.DevtoolIdeSdkTests.test_devtool_ide_sdk_code_cmake_clang)
2026-03-19 19:34:48,651 - oe-selftest - INFO - testtools.testresult.real._StringException: Traceback (most recent call last):
File "/srv/pokybuild/yocto-worker/oe-selftest-armhost/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 3862, in test_devtool_ide_sdk_code_cmake_clang
runCmd(install_deploy_cmd, output_log=self._cmd_logger)
File "/srv/pokybuild/yocto-worker/oe-selftest-armhost/build/layers/openembedded-core/meta/lib/oeqa/utils/commands.py", line 214, in runCmd
raise AssertionError("Command '%s' returned non-zero exit status %d:\n%s" % (command, result.status, exc_output))
AssertionError: Command '/srv/pokybuild/yocto-worker/oe-selftest-armhost/build/build-st-273901/workspace/ide-sdk/cmake-example-clang/scripts/install_and_deploy_cmake-example-clang-cortexa57' returned non-zero exit status 1:
https://autobuilder.yoctoproject.org/valkyrie/#/builders/23/builds/3579
https://autobuilder.yoctoproject.org/valkyrie/#/builders/35/builds/3474
https://autobuilder.yoctoproject.org/valkyrie/#/builders/48/builds/3360
Can you have a look at the issue?
Thanks,
Mathieu
--
Mathieu Dubois-Briand, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [OE-core] [PATCH 9/9] oe-selftest: devtool ide-sdk: add clang/LLDB test
2026-03-20 7:12 ` [OE-core] " Mathieu Dubois-Briand
@ 2026-03-20 9:11 ` adrian.freihofer
2026-03-20 10:18 ` Mathieu Dubois-Briand
0 siblings, 1 reply; 13+ messages in thread
From: adrian.freihofer @ 2026-03-20 9:11 UTC (permalink / raw)
To: mathieu.dubois-briand, adrian.freihofer, openembedded-core
On Fri, 2026-03-20 at 08:12 +0100, Mathieu Dubois-Briand via
lists.openembedded.org wrote:
> On Wed Mar 18, 2026 at 11:36 PM CET, Adrian Freihofer via
> lists.openembedded.org wrote:
> > From: Adrian Freihofer <adrian.freihofer@siemens.com>
> >
> > Add test_devtool_ide_sdk_code_cmake_clang to verify the full
> > devtool
> > ide-sdk workflow for a cmake recipe built with clang. Unlike the
> > gcc
> > variant the clang recipe uses lldb-server for remote debugging and
> > CodeLLDB (vadimcn.vscode-lldb) as the VS Code debug adapter.
> >
> > The test covers:
> > - devtool modify + devtool ide-sdk with ide=code
> > - cmake preset compilation and CTest execution (same as the gcc
> > test)
> > - extensions.json recommends vadimcn.vscode-lldb
> > - launch.json uses "type": "lldb" (CodeLLDB) instead of "type":
> > "cppdbg"
> > - End-to-end lldb --batch remote debugging session via lldb-server
> > platform mode running on qemu
> >
> > Supporting changes:
> > - _write_bb_config: accept optional extra_packages parameter so the
> > clang test can add lldb-server to IMAGE_INSTALL
> > - _verify_launch_json_lldb: new helper that validates the CodeLLDB
> > launch.json structure (type, initCommands, program, cwd,
> > preLaunchTask)
> > - _lldb_server_debugging_once: new helper that reads the
> > preLaunchTask
> > SSH command from tasks.json, starts lldb-server on the target,
> > and
> > runs lldb --batch to verify a breakpoint at main is hit
> > - _verify_service_running: use pgrep with exact regex (^name$) for
> > exact
> > process name matching; without that, pgrep would also match
> > cmake-example-clang (truncated to 'cmake-example-c' in
> > /proc/pid/comm) when checking for cmake-example, returning two
> > PIDs
> > and failing the isdigit() assertion
> >
> > Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
> > ---
>
> Hi Adrian,
>
> Thanks for your patch.
>
> It looks like the added test_devtool_ide_sdk_code_cmake_clang test is
> failing on the autobuilder:
>
> 2026-03-19 19:34:48,650 - oe-selftest - INFO -
> devtool.DevtoolIdeSdkTests.test_devtool_ide_sdk_code_cmake_clang
> (subunit.RemotedTestCase)
> 2026-03-19 19:34:48,651 - oe-selftest - INFO - ... FAIL
> ...
> 2026-03-19 19:34:48,651 - oe-selftest - INFO - 11: 3/39 214/681
> (495.87s) (0 failed)
> (devtool.DevtoolIdeSdkTests.test_devtool_ide_sdk_code_cmake_clang)
> 2026-03-19 19:34:48,651 - oe-selftest - INFO -
> testtools.testresult.real._StringException: Traceback (most recent
> call last):
> File "/srv/pokybuild/yocto-worker/oe-selftest-
> armhost/build/layers/openembedded-
> core/meta/lib/oeqa/selftest/cases/devtool.py", line 3862, in
> test_devtool_ide_sdk_code_cmake_clang
> runCmd(install_deploy_cmd, output_log=self._cmd_logger)
> File "/srv/pokybuild/yocto-worker/oe-selftest-
> armhost/build/layers/openembedded-
> core/meta/lib/oeqa/utils/commands.py", line 214, in runCmd
> raise AssertionError("Command '%s' returned non-zero exit status
> %d:\n%s" % (command, result.status, exc_output))
> AssertionError: Command '/srv/pokybuild/yocto-worker/oe-selftest-
> armhost/build/build-st-273901/workspace/ide-sdk/cmake-example-
> clang/scripts/install_and_deploy_cmake-example-clang-cortexa57'
> returned non-zero exit status 1:
>
> https://autobuilder.yoctoproject.org/valkyrie/#/builders/23/builds/3579
> https://autobuilder.yoctoproject.org/valkyrie/#/builders/35/builds/3474
> https://autobuilder.yoctoproject.org/valkyrie/#/builders/48/builds/3360
>
> Can you have a look at the issue?
>
Thank you for the feedback.
Looks like the summary is: Pseudo crashes on ARM hosts (but not on x86-
64 hosts).
Is it possible to get this file
/srv/pokybuild/yocto-worker/oe-selftest-armhost/build/build-st-
273901/tmp/work/cortexa57-poky-linux/cmake-example-
clang/1.0/pseudo//pseudo.log ?
Relevant section from the logs is:
Summary: There was 1 WARNING message.
abort()ing pseudo client by server request. See
https://wiki.yoctoproject.org/wiki/Pseudo_Abort for more details on
this.
Check logfile: /srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/work/cortexa57-poky-linux/cmake-
example-clang/1.0/pseudo//pseudo.log
Aborted (core dumped)
tar: Unexpected EOF in archive
tar: Unexpected EOF in archive
tar: Error is not recoverable: exiting now
Traceback (most recent call last):
File "/srv/pokybuild/yocto-worker/oe-selftest-armhost/build/build-st-
273901/workspace/ide-sdk/cmake-example-
clang/scripts/deploy_target_cmake-example-clang-cortexa57", line 19, in
<module>
deploy_no_d("/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/work/cortexa57-poky-linux/cmake-
example-clang/1.0/image", "/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/work/cortexa57-poky-linux/cmake-
example-clang/1.0", "/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/sysroots-uninative/aarch64-
linux/usr/bin:/tmp/devtoolqalpx2fc25/core-
copy/scripts:/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/work/cortexa57-poky-linux/cmake-
example-clang/1.0/recipe-sysroot-native/usr/bin/aarch64-poky-
linux:/srv/pokybuild/yocto-worker/oe-selftest-armhost/build/build-st-
273901/tmp/work/cortexa57-poky-linux/cmake-example-clang/1.0/recipe-
sysroot/usr/bin/crossscripts:/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/work/cortexa57-poky-linux/cmake-
example-clang/1.0/recipe-sysroot-native/usr/sbin:/srv/pokybuild/yocto-
worker/oe-selftest-armhost/build/build-st-273901/tmp/work/cortexa57-
poky-linux/cmake-example-clang/1.0/recipe-sysroot-
native/usr/bin:/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/work/cortexa57-poky-linux/cmake-
example-clang/1.0/recipe-sysroot-native/sbin:/srv/pokybuild/yocto-
worker/oe-selftest-armhost/build/build-st-273901/tmp/work/cortexa57-
poky-linux/cmake-example-clang/1.0/recipe-sysroot-
native/bin:/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/layers/bitbake/bin:/srv/pokybuild/yocto-worker/oe-
selftest-armhost/build/build-st-273901/tmp/hosttools", "aarch64-poky-
linux-llvm-strip", "/usr/lib", "/lib", 16, "/srv/pokybuild/yocto-
worker/oe-selftest-armhost/build/build-st-273901/tmp/sysroots-
components/aarch64/pseudo-native/usr/bin/pseudo",
"PSEUDO_PREFIX=/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/sysroots-components/aarch64/pseudo-
native/usr PSEUDO_LOCALSTATEDIR=/srv/pokybuild/yocto-worker/oe-
selftest-armhost/build/build-st-273901/tmp/work/cortexa57-poky-
linux/cmake-example-clang/1.0/pseudo/
PSEUDO_PASSWD=/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/work/cortexa57-poky-linux/cmake-
example-clang/1.0/recipe-sysroot:/srv/pokybuild/yocto-worker/oe-
selftest-armhost/build/build-st-273901/tmp/sysroots-
components/aarch64/pseudo-native PSEUDO_NOSYMLINKEXP=1
PSEUDO_INCLUDE_PATHS=/proc,/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/work/cortexa57-poky-linux/cmake-
example-clang/1.0/image,/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/work/cortexa57-poky-linux/cmake-
example-clang/1.0/package,/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/work/cortexa57-poky-linux/cmake-
example-clang/1.0/rootfs,/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/work/cortexa57-poky-linux/cmake-
example-clang/1.0/sstate-build-package/,/srv/pokybuild/yocto-worker/oe-
selftest-armhost/build/build-st-273901/tmp/work/cortexa57-poky-
linux/cmake-example-clang/1.0/sstate-install-
package/,/srv/pokybuild/yocto-worker/oe-selftest-armhost/build/build-
st-273901/tmp/work/cortexa57-poky-linux/cmake-example-
clang/1.0/pkgdata,/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/build-st-273901/tmp/work/cortexa57-poky-linux/cmake-
example-clang/1.0/minidebuginfo,/srv/pokybuild/yocto-worker/oe-
selftest-armhost/build/build-st-273901/tmp/work/cortexa57-poky-
linux/cmake-example-clang/1.0/devtool-deploy-target-stripped
PSEUDO_DISABLED=0", filtered_args)
File "/srv/pokybuild/yocto-worker/oe-selftest-
armhost/build/layers/openembedded-core/scripts/lib/devtool/deploy.py",
line 274, in deploy_no_d
raise DevtoolError('Deploy failed - rerun with -s to get a complete
'
devtool.DevtoolError: Deploy failed - rerun with -s to get a complete
error message
Thanks,
Adrian
> Thanks,
> Mathieu
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#233580):
> https://lists.openembedded.org/g/openembedded-core/message/233580
> Mute This Topic: https://lists.openembedded.org/mt/118391915/4454582
> Group Owner: openembedded-core+owner@lists.openembedded.org
> Unsubscribe:
> https://lists.openembedded.org/g/openembedded-core/unsub [
> adrian.freihofer@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [OE-core] [PATCH 9/9] oe-selftest: devtool ide-sdk: add clang/LLDB test
2026-03-20 9:11 ` adrian.freihofer
@ 2026-03-20 10:18 ` Mathieu Dubois-Briand
0 siblings, 0 replies; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2026-03-20 10:18 UTC (permalink / raw)
To: adrian.freihofer, adrian.freihofer, openembedded-core
On Fri Mar 20, 2026 at 10:11 AM CET, Adrian Freihofer via lists.openembedded.org wrote:
> On Fri, 2026-03-20 at 08:12 +0100, Mathieu Dubois-Briand via
>>
>> https://autobuilder.yoctoproject.org/valkyrie/#/builders/23/builds/3579
>> https://autobuilder.yoctoproject.org/valkyrie/#/builders/35/builds/3474
>> https://autobuilder.yoctoproject.org/valkyrie/#/builders/48/builds/3360
>>
>> Can you have a look at the issue?
>>
>
> Thank you for the feedback.
>
> Looks like the summary is: Pseudo crashes on ARM hosts (but not on x86-
> 64 hosts).
>
Hi Adrian,
Just to be sure: the second and third links above point to two x86-64
builds.
Thanks,
Mathieu
--
Mathieu Dubois-Briand, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 13+ messages in thread