Skip to contentSkip to conversation

devtool: ide-sdk: add support for Rust recipes

PATCH In review Enguerrand de Ribaucourt 4 participants 22 messages Nov 9 – Nov 11

lore
Share & tools
lore
#0 lore raw

v2 cover devtool: ide-sdk: add support for Rust recipes

Hello, this series introduces support for Rust ide-sdk VS Code configuration against the Yocto cross-compilation toolchain:

- editor symbol resolution and linting
- build task
- deploy task
- debugging

It introduces a cargo-example recipe following the model used for cmake and meson.

The generated configurations can be portable to standalone SDK installations by manually updating the paths.

C/C++ IntelliSense could provide navigation for native code compiled by Cargo build scripts, using the recipe compiler and sysroot. This first series defers it because no mixed-language fixture is available to validate that workflow.

BitBake generates the Cargo configuration consumed by ide-sdk. This series updates the parsed configuration and writes its supported TOML values without adding a new dependency.

I reproduced the selftest coverage expected from other recipe classes, including configuration analysis up to actual debugging symbol resolution.

I largely used AI generation to write this stack. After many iterations, I am finally happy with the code and commit history quality of this series. I also have a documentation commit ready for the manual if this gets approved.

Any feedback is appreciated. Hope this will help lots of Rust developers working with Yocto distros :).

Cheers,
Enguerrand de Ribaucourt

---
Changes in v2:
- Replace Cargo config in-place update with a simple TOML serializer.
- Add Cargo config parser coverage.
- Generate Cargo config for all IDE modes.
- Route ide=none Cargo debugging through LLDB and explain GDB limitations.
- Configure the rustc problems matcher.
- Map Rust sources with STAGING_LIBDIR_NATIVE.
- Discard C/C++ IntelliSense mixed-language feature.
- Fix bbappend always being modified on re-runs
- Fix '=' ordering in remap
- Fix install task duplication
- Link to v1: https://patch.msgid.link/20261026-devtool-ide-sdk-cargo-v1-0-d4504e124d07@savoirfairelinux.com

Thank you very much for the detailed review Adrian, it's been really helpful!

---
Enguerrand de Ribaucourt (18):
cargo-example: add minimal selftest fixture
devtool: ide-sdk: capture Cargo cross environment
devtool: ide-sdk: export recipe Cargo configuration oeqa/selftest: verify Cargo config serialization
devtool: ide-sdk: recommend Rust Analyzer for Cargo
devtool: ide-sdk: configure Rust Analyzer for Cargo
devtool: ide-sdk: generalize binary install task name
devtool: ide-sdk: extract install task definition
devtool: ide-sdk: order install after optional build task
devtool: ide-sdk: add Cargo build task
devtool: ide-sdk: enforce LLDB for Cargo
devtool: ide-sdk: remap Cargo sources for BitBake builds
devtool: ide-sdk: map Cargo workspace sources for LLDB
devtool: ide-sdk: map libstd-rs sources to Rust SDK oeqa/selftest: share source breakpoint lookup oeqa/selftest: run IDE tasks with configured environment oeqa/selftest: debug Cargo example over QEMU slirp oeqa/selftest: verify Cargo rebuild and redeploy

 meta-selftest/recipes-test/rust/cargo-example.bb   |  15 +
 .../recipes-test/rust/cargo-example/Cargo.lock     |   5 +
 .../recipes-test/rust/cargo-example/Cargo.toml     |   8 +
 .../recipes-test/rust/cargo-example/build.rs       |  20 ++
 .../recipes-test/rust/cargo-example/src/main.rs    |  11 +
 meta/lib/oeqa/selftest/cases/devtool.py            | 347 ++++++++++++++++++++-
 scripts/lib/devtool/ide_plugins/__init__.py        |   1 +
 scripts/lib/devtool/ide_plugins/ide_code.py        |  83 ++++-
 scripts/lib/devtool/ide_plugins/ide_none.py        |   4 +-
 scripts/lib/devtool/ide_sdk.py                     | 249 +++++++++++++--
 10 files changed, 688 insertions(+), 55 deletions(-)

---
base-commit: 39b27e524f3c092e5018cbed23ddf1829390ae65
change-id: 20261022-devtool-ide-sdk-cargo-d003158ae2f1

Best regards,

Signature · 1 line
Enguerrand de Ribaucourt <[email protected]>
#1 lore raw

PATCH v2 1/18 cargo-example: add minimal selftest fixture

Add a minimal Cargo recipe to demonstrate the ide-sdk configuration for Rust in the selftests.

AI-Generated: Uses GitHub Copilot

---
 meta-selftest/recipes-test/rust/cargo-example.bb     | 15 +++++++++++++++
 .../recipes-test/rust/cargo-example/Cargo.lock       |  5 +++++
 .../recipes-test/rust/cargo-example/Cargo.toml       |  8 ++++++++
 .../recipes-test/rust/cargo-example/build.rs         | 20 ++++++++++++++++++++
 .../recipes-test/rust/cargo-example/src/main.rs      | 11 +++++++++++
 5 files changed, 59 insertions(+)
meta-selftest/recipes-test/rust/cargo-example.bb new file +15 -0
new file mode 100644
index 0000000000..cca56d8d81
@@ -0,0 +1,15 @@
+SUMMARY = "Minimal Cargo application for devtool ide-sdk selftests"
+LICENSE = "MIT"
+LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
+
+SRC_URI = "file://Cargo.toml \
+           file://Cargo.lock \
+           file://build.rs \
+           file://src/main.rs \
+          "
+
+S = "${UNPACKDIR}"
+
+inherit cargo
+
+CARGO_BUILD_FLAGS:append = " --features ide-sdk-selftest"
meta-selftest/recipes-test/rust/cargo-example/Cargo.lock new file +5 -0
new file mode 100644
index 0000000000..27cb8ffd40
@@ -0,0 +1,5 @@
+version = 3
+
+[[package]]
+name = "cargo-example"
+version = "0.1.0"
meta-selftest/recipes-test/rust/cargo-example/Cargo.toml new file +8 -0
new file mode 100644
index 0000000000..e8a2e1dde4
@@ -0,0 +1,8 @@
+[package]
+name = "cargo-example"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+# Used only to verify that ide-sdk forwards recipe Cargo features to its task.
+ide-sdk-selftest = []
meta-selftest/recipes-test/rust/cargo-example/build.rs new file +20 -0
new file mode 100644
index 0000000000..f0733b3d38
@@ -0,0 +1,20 @@
+use std::env;
+
+// Fail the selftest build if Cargo does not receive BitBake's cross environment.
+fn main() {
+    let target_cc = env::var("CC").expect("Cargo config should set target CC");
+    assert!(target_cc.ends_with("/target-rust-cc"), "unexpected CC: {target_cc}");
+
+    let host_cc = env::var("HOST_CC").expect("Cargo config should set host CC");
+    assert!(host_cc.ends_with("/build-rust-cc"), "unexpected HOST_CC: {host_cc}");
+
+    assert!(
+        env::var_os("RUST_TARGET_PATH").is_some(),
+        "Cargo config should set the OE Rust target path",
+    );
+    assert_eq!(
+        env::var("PKG_CONFIG_ALLOW_CROSS").as_deref(),
+        Ok("1"),
+        "Cargo config should allow cross pkg-config",
+    );
+}
meta-selftest/recipes-test/rust/cargo-example/src/main.rs new file +11 -0
new file mode 100644
index 0000000000..7066010f51
@@ -0,0 +1,11 @@
+fn fibonacci(n: u32) -> u32 {
+    match n {
+        0 => 0,
+        1 => 1,
+        _ => fibonacci(n - 1) + fibonacci(n - 2),
+    }
+}
+
+fn main() {
+    println!("fibonacci={}", fibonacci(4));
+}
Signature · 1 line
2.53.0
#2 lore raw

PATCH v2 2/18 devtool: ide-sdk: capture Cargo cross environment

In reply to Enguerrand de Ribaucourt

Capture Cargo target and host compiler settings, recipe Cargo state, and package configuration to generate IDE settings. Rust source paths use Cargo RUSTFLAGS and TARGET_DBGSRC_DIR because Rust does not use compiler DEBUG_PREFIX_MAP.

Mirror the -sys and pkg-config exports from cargo_common.bbclass so Cargo config matches OE's system-library behavior.

AI-Generated: Uses GitHub Copilot

---
 scripts/lib/devtool/ide_plugins/__init__.py |  1 +
 scripts/lib/devtool/ide_sdk.py              | 57 +++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+)
scripts/lib/devtool/ide_plugins/__init__.py +1 -0
index 7841bc37b9..10fcde471f 100644
@@ -33,6 +33,7 @@ class BuildTool(Enum):
     UNDEFINED = auto()
     CMAKE = auto()
     MESON = auto()
+    CARGO = auto()
     KERNEL_MODULE = auto()
 
     @property
scripts/lib/devtool/ide_sdk.py +57 -0

69 lines not on this page · raw message

Signature · 1 line
2.53.0
#3 lore raw

PATCH v2 3/18 devtool: ide-sdk: export recipe Cargo configuration

In reply to Enguerrand de Ribaucourt

Export recipe Cargo config to the devtool workspace so Cargo and rust-analyzer reuse target, linker, rustflags, and dependency settings instead of duplicating them in settings.json and tasks.json. Other IDE integrations can reuse the generated config too.

The original input is generated by cargo_common.bbclass. Updating it in-place would require a Python TOML writer dependency, so regenerate it with a bounded writer helper.

AI-Generated: Uses GitHub Copilot

---
Changes in v2:
- Replace in-place Cargo config updates with a helper that rewrites the full file.
- Generate Cargo config for all IDE modes.

---
 scripts/lib/devtool/ide_sdk.py | 102 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 102 insertions(+)
scripts/lib/devtool/ide_sdk.py +102 -0
index e686dbcff8..98e39703e7 100755
@@ -22,6 +22,7 @@ from pathlib import Path
 
 import scriptutils
 import bb
+from bb._vendor import tomli
 from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, DevtoolError, parse_recipe
 from devtool.standard import get_real_srctree
 from devtool.deploy import parse_packages_arg
@@ -787,6 +788,41 @@ class ExecutableBinary:
         return None
 
 
+def _toml_dumps(config):
+    """Serialize TOML values and tables."""
+    def format_value(value):
+        if isinstance(value, str):
+            return json.dumps(value)
+        if isinstance(value, bool):
+            return str(value).lower()
+        if isinstance(value, (int, float)):
+            return repr(value)
+        if isinstance(value, list):
+            return '[%s]' % ', '.join(format_value(item) for item in value)
+        if isinstance(value, dict):
+            return '{ %s }' % ', '.join(
+                '%s = %s' % (json.dumps(key), format_value(item))
+                for key, item in value.items())
+        raise TypeError('Unsupported TOML value: %s' % type(value).__name__)
+
+    lines = []
+
+    def write_table(table, path=()):
+        if path:
+            if lines:
+                lines.append('')
+            lines.append('[%s]' % '.'.join(json.dumps(key) for key in path))
+        for key, value in table.items():
+            if not isinstance(value, dict):
+                lines.append('%s = %s' % (json.dumps(key), format_value(value)))
+        for key, value in table.items():
+            if isinstance(value, dict):
+                write_table(value, path + (key,))
+
+    write_table(config)
+    return '\n'.join(lines) + ('\n' if lines else '')
+
+
 class RecipeModified:
     """Handling of recipes in the workspace created by devtool modify"""
     OE_INIT_BUILD_ENV = 'oe-init-build-env'
@@ -1122,6 +1158,70 @@ class RecipeModified:
         self.recipe_id = self.bpn + "-" + self.package_arch
         self.recipe_id_pretty = self.bpn + ": " + self.package_arch
 
+    @property
+    def cargo_config_path(self):
+        return os.path.join(self.ide_sdk_dir, 'cargo-config.toml')
+
+    def update_cargo_config(self):
+        """Export the recipe Cargo config to the devtool workspace."""
+        source_config = os.path.join(self.cargo_home, 'config.toml')
+        try:
+            with open(source_config, 'r') as config_file:
+                cargo_config = tomli.loads(config_file.read())
+        except FileNotFoundError as exc:
+            raise DevtoolError(
+                'Cargo config not found at %s; build the recipe first' % source_config) from exc
+
+        if 'build' not in cargo_config:
+            cargo_config['build'] = {}
+        cargo_config['build']['target'] = self.cargo_target
+
+        if 'target' not in cargo_config:
+            cargo_config['target'] = {}
+        target_config = cargo_config['target']
+        if self.cargo_target not in target_config:
+            target_config[self.cargo_target] = {}
+        target_config = target_config[self.cargo_target]
+        target_config['linker'] = self.cargo_linker
+        target_config['rustflags'] = shlex.split(self.cargo_rustflags)
+
+        if 'env' not in cargo_config:
+            cargo_config['env'] = {}
+        env_config = cargo_config['env']
+        env_names = (
+            'CC', 'CXX', 'CFLAGS', 'CXXFLAGS', 'AR',
+            'TARGET_CC', 'TARGET_CXX', 'TARGET_CFLAGS', 'TARGET_CXXFLAGS', 'TARGET_AR',
+            'HOST_CC', 'HOST_CXX', 'HOST_CFLAGS', 'HOST_CXXFLAGS', 'HOST_AR',
+            'RUST_TARGET_PATH', 'RUST_BACKTRACE', 'SOURCE_DATE_EPOCH',
+            'PKG_CONFIG', 'PKG_CONFIG_FOR_BUILD', 'PKG_CONFIG_FOR_TARGET',
+            'PKG_CONFIG_SYSROOT_DIR', 'PKG_CONFIG_PATH', 'PKG_CONFIG_LIBDIR',
+            'LIBGIT2_NO_VENDOR', 'LIBSSH2_SYS_USE_PKG_CONFIG',
+            'LIBSQLITE3_SYS_USE_PKG_CONFIG', 'OPENSSL_NO_VENDOR',
+            'PKG_CONFIG_ALLOW_CROSS', 'PKG_CONFIG_ALL_DYNAMIC',
+            'SYSTEM_DEPS_BUILD_INTERNAL', 'ZSTD_SYS_USE_PKG_CONFIG',
+        )
+        for name in env_names:
+            value = self.cargo_env.get(name)
+            if value is not None:
+                env_config[name] = {'value': str(value), 'force': True}
+
+        content = (
+            '# Generated by devtool ide-sdk for the active build environment.\n'
+            '\n'
+            + _toml_dumps(cargo_config))
+
+        config_path = self.cargo_config_path
+        os.makedirs(os.path.dirname(config_path), exist_ok=True)
+        try:
+            with open(config_path, 'r') as config_file:
+                current = config_file.read()
+        except FileNotFoundError:
+            current = None
+        if current != content:
+            with open(config_path, 'w') as config_file:
+                config_file.write(content)
+        return config_path
+
     @staticmethod
     def is_valid_shell_variable(var):
         """Skip strange shell variables like systemd
@@ -2019,6 +2119,8 @@ def ide_setup(args, config, basepath, workspace):
                 image.extract_nfs_rootfs(nfs_export_base_dir, args.nfs, args.target)
     if DevtoolIdeMode.modified in modes:
         for recipe_modified in recipes_modified:
+            if recipe_modified.build_tool is BuildTool.CARGO:
+                recipe_modified.update_cargo_config()
             if recipe_modified.build_tool is BuildTool.CMAKE:
                 recipe_modified.cmake_preset()
             if recipe_modified.build_tool is BuildTool.MESON:
Signature · 1 line
2.53.0
#4 lore raw

PATCH v2 4/18 oeqa/selftest: verify Cargo config serialization

In reply to Enguerrand de Ribaucourt

Parse the config exported by devtool and round-trip the TOML writer's supported tables and values.

AI-Generated: Uses GitHub Copilot

---
Changes in v2:
- Commit added to verify the generated Cargo config.

---
 meta/lib/oeqa/selftest/cases/devtool.py | 63 +++++++++++++++++++++++++++++++++
 1 file changed, 63 insertions(+)
meta/lib/oeqa/selftest/cases/devtool.py +63 -0
index 265f2147a4..cab49b4c02 100644
@@ -5242,3 +5242,66 @@ class DevtoolIdeSdkMiscTests(DevtoolIdeSdkTests):
         with open(modified_config_file) as modified_config:
             modified_config_new = modified_config.read()
         self.assertEqual(modified_config_str, modified_config_new)
+
+
+class DevtoolIdeSdkCargoConfigTests(DevtoolIdeSdkTests):
+
+    def test_cargo_toml_serialization(self):
+        import importlib
+        import sys
+
+        from bb._vendor import tomli
+
+        config = {
+            'paths': ['/work/crate', '/work/quoted "crate"\\dir'],
+            'source': {'bitbake': {'directory': '/downloads/cargo'}},
+            'patch': {
+                'https://example.com/repo': {
+                    'example': {'path': '/work/patched-crate'}
+                }
+            },
+            'env': {
+                'CC': {'value': '/tool/cc --flag="quoted"\\path', 'force': True}
+            },
+            'target': {
+                'x86_64-poky-linux': {
+                    'linker': '/tool/ld',
+                    'rustflags': ['--sysroot', '/sysroot path'],
+                }
+            },
+            'term': {'progress': {'when': 'always', 'width': 80}},
+        }
+
+        scripts_lib = os.path.join(get_bb_var('COREBASE'), 'scripts', 'lib')
+        sys.path.insert(0, scripts_lib)
+        oeqa_devtool = sys.modules.pop('devtool')
+        try:
+            ide_sdk = importlib.import_module('devtool.ide_sdk')
+            self.assertEqual(tomli.loads(ide_sdk._toml_dumps(config)), config)
+        finally:
+            for name in tuple(sys.modules):
+                if name == 'devtool' or name.startswith('devtool.'):
+                    del sys.modules[name]
+            sys.modules['devtool'] = oeqa_devtool
+            sys.path.remove(scripts_lib)
+
+    def test_cargo_config_is_generated_and_valid(self):
+        from bb._vendor import tomli
+
+        recipe_name = 'cargo-example'
+        self._check_workspace()
+        self._write_bb_config()
+        self._devtool_ide_sdk_recipe(recipe_name, 'Cargo.toml', None)
+        runCmd('devtool ide-sdk %s oe-selftest-image --ide=code' % recipe_name,
+               output_log=self._cmd_logger)
+
+        cargo_config = os.path.join(
+            self.workspacedir, 'ide-sdk', recipe_name, 'cargo-config.toml')
+        self.assertExists(cargo_config, 'Cargo config was not generated')
+        with open(cargo_config) as cargo_config_file:
+            config = tomli.loads(cargo_config_file.read())
+        rust_target = get_bb_var('RUST_HOST_SYS', recipe_name)
+        self.assertEqual(config['build']['target'], rust_target)
+        self.assertEqual(
+            config['target'][rust_target]['linker'],
+            get_bb_var('CARGO_RUST_TARGET_CCLD', recipe_name))
Signature · 1 line
2.53.0
#5 lore raw

PATCH v2 5/18 devtool: ide-sdk: recommend Rust Analyzer for Cargo

In reply to Enguerrand de Ribaucourt

Recommend rust-analyzer for Cargo recipes and verify the extension appears in the generated VS Code recommendations file.

AI-Generated: Uses GitHub Copilot

---
 meta/lib/oeqa/selftest/cases/devtool.py     | 14 ++++++++++++++
 scripts/lib/devtool/ide_plugins/ide_code.py |  2 ++
 2 files changed, 16 insertions(+)
meta/lib/oeqa/selftest/cases/devtool.py +14 -0
index cab49b4c02..8e90dba7d0 100644
@@ -4339,6 +4339,20 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
         self._test_code_and_none_qemu(slirp=True)
 
 
+class DevtoolIdeSdkRustTests(DevtoolIdeSdkTests):
+
+    def test_code_cargo_rust_analyzer_recommendation(self):
+        recipe_name = "cargo-example"
+        self._check_workspace()
+        self._write_bb_config()
+        tempdir = self._devtool_ide_sdk_recipe(recipe_name, "Cargo.toml", None)
+        runCmd('devtool ide-sdk %s oe-selftest-image --ide=code' % recipe_name,
+               output_log=self._cmd_logger)
+
+        with open(os.path.join(tempdir, '.vscode', 'extensions.json')) as extensions_file:
+            extensions = json.load(extensions_file)
+        self.assertIn('rust-lang.rust-analyzer', extensions['recommendations'])
+
 class DevtoolIdeSdkKernelTests(DevtoolIdeSdkTests):
 
     @OETestTag("runqemu")
scripts/lib/devtool/ide_plugins/ide_code.py +2 -0
index 081dc08657..cf7f81293c 100644
@@ -303,6 +303,8 @@ class IdeVSCode(IdeBase):
 
     def vscode_extensions(self, modified_recipe):
         recommendations = []
+        if modified_recipe.build_tool is BuildTool.CARGO:
+            recommendations.append("rust-lang.rust-analyzer")
         # clangd (matched to the recipe's real cross compiler via
         # compile_commands.json) replaces cpptools as the IntelliSense provider
         # when selected via IDE_SDK_INTELLISENSE (defaults to clangd for the
Signature · 1 line
2.53.0
#6 lore raw

PATCH v2 6/18 devtool: ide-sdk: configure Rust Analyzer for Cargo

In reply to Enguerrand de Ribaucourt

Point Rust Analyzer at the generated Cargo config and target, while keeping its process environment limited to the recipe PATH. Verify the workspace config path and target in the Cargo selftest.

AI-Generated: Uses GitHub Copilot

---
Changes in v2:
- Use the Cargo config path generated during common recipe setup.

---
 meta/lib/oeqa/selftest/cases/devtool.py     | 12 +++++++++++-
 scripts/lib/devtool/ide_plugins/ide_code.py | 10 ++++++++++
 2 files changed, 21 insertions(+), 1 deletion(-)
meta/lib/oeqa/selftest/cases/devtool.py +11 -1
index 8e90dba7d0..ee055c218a 100644
@@ -4341,7 +4341,7 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
 
 class DevtoolIdeSdkRustTests(DevtoolIdeSdkTests):
 
-    def test_code_cargo_rust_analyzer_recommendation(self):
+    def test_code_cargo_rust_analyzer_configuration(self):
         recipe_name = "cargo-example"
         self._check_workspace()
         self._write_bb_config()
@@ -4349,8 +4349,18 @@ class DevtoolIdeSdkRustTests(DevtoolIdeSdkTests):
         runCmd('devtool ide-sdk %s oe-selftest-image --ide=code' % recipe_name,
                output_log=self._cmd_logger)
 
+        with open(os.path.join(tempdir, '.vscode', 'settings.json')) as settings_file:
+            settings = json.load(settings_file)
         with open(os.path.join(tempdir, '.vscode', 'extensions.json')) as extensions_file:
             extensions = json.load(extensions_file)
+        cargo_config = os.path.join(
+            self.workspacedir, 'ide-sdk', recipe_name, 'cargo-config.toml')
+        self.assertExists(cargo_config, 'Cargo config was not generated')
+        self.assertEqual(settings['rust-analyzer.cargo.configPath'], cargo_config)
+        self.assertEqual(
+            settings['rust-analyzer.cargo.target'],
+            get_bb_var('RUST_HOST_SYS', recipe_name))
+        self.assertNotIn('rust-analyzer.cargo.extraEnv', settings)
         self.assertIn('rust-lang.rust-analyzer', extensions['recommendations'])
 
 class DevtoolIdeSdkKernelTests(DevtoolIdeSdkTests):
scripts/lib/devtool/ide_plugins/ide_code.py +10 -0
index cf7f81293c..3cb3a486a2 100644
@@ -228,6 +228,15 @@ class IdeVSCode(IdeBase):
             }
         ]
 
+    def __vscode_settings_cargo(self, settings_dict, modified_recipe):
+        if modified_recipe.build_tool is not BuildTool.CARGO:
+            return
+        settings_dict["rust-analyzer.server.extraEnv"] = {
+            "PATH": modified_recipe.path
+        }
+        settings_dict["rust-analyzer.cargo.target"] = modified_recipe.cargo_target
+        settings_dict["rust-analyzer.cargo.configPath"] = modified_recipe.cargo_config_path
+
     def __vscode_settings_clangd(self, settings_dict, modified_recipe):
         if not (modified_recipe.ide_sdk_intellisense == 'clangd' and modified_recipe.build_tool.is_c_cpp):
             return
@@ -294,6 +303,7 @@ class IdeVSCode(IdeBase):
         self.__vscode_settings_cmake(settings_dict, modified_recipe)
         self.__vscode_settings_meson(settings_dict, modified_recipe)
         self.__vscode_settings_kernel_module(settings_dict, modified_recipe)
+        self.__vscode_settings_cargo(settings_dict, modified_recipe)
         self.__vscode_settings_clangd(settings_dict, modified_recipe)
         self.__vscode_settings_format(settings_dict, modified_recipe)
 
Signature · 1 line
2.53.0
#7 lore raw

PATCH v2 7/18 devtool: ide-sdk: generalize binary install task name

In reply to Enguerrand de Ribaucourt

Rename the C++-specific generator to describe its install/deploy behavior. The upcoming Cargo task builds outside BitBake and will reuse this helper to install and deploy the resulting recipe artifacts.

AI-Generated: Uses GitHub Copilot

---
 scripts/lib/devtool/ide_plugins/ide_code.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
scripts/lib/devtool/ide_plugins/ide_code.py +2 -2
index 3cb3a486a2..9ee403f506 100644
@@ -661,7 +661,7 @@ class IdeVSCode(IdeBase):
         IdeBase.update_json_file(
             self.dot_code_dir(modified_recipe), launch_file, launch_dict)
 
-    def vscode_tasks_cpp(self, args, image_recipe, modified_recipe):
+    def vscode_tasks_binary(self, args, image_recipe, modified_recipe):
         run_install_deploy = modified_recipe.gen_install_deploy_script(args, image_recipe.nfs_deploy_dir)
         install_task_name = "install && deploy-target %s" % modified_recipe.recipe_id_pretty
         deploy_args = ["--target", resolve_deploy_target(args, image_recipe.nfs_deploy_dir)]
@@ -963,7 +963,7 @@ class IdeVSCode(IdeBase):
 
     def vscode_tasks(self, args, image_recipe, modified_recipe):
         if modified_recipe.build_tool.is_c_cpp:
-            self.vscode_tasks_cpp(args, image_recipe, modified_recipe)
+            self.vscode_tasks_binary(args, image_recipe, modified_recipe)
         elif modified_recipe.build_tool == BuildTool.KERNEL_MODULE:
             self.vscode_tasks_kernel_module(args, image_recipe, modified_recipe)
         else:
Signature · 1 line
2.53.0
#8 lore raw

PATCH v2 8/18 devtool: ide-sdk: extract install task definition

In reply to Enguerrand de Ribaucourt

Extract the install/deploy task object before adding optional dependencies. This preserves existing task output and provides a shared insertion point for Cargo build/deploy ordering.

AI-Generated: Uses GitHub Copilot

---
 scripts/lib/devtool/ide_plugins/ide_code.py | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)
scripts/lib/devtool/ide_plugins/ide_code.py +8 -9
index 9ee403f506..491e74f764 100644
@@ -669,17 +669,16 @@ class IdeVSCode(IdeBase):
             deploy_args += ["--port", args.port]
         for package in args.package or []:
             deploy_args += ["--package", package]
+        install_task = {
+            "label": install_task_name,
+            "type": "shell",
+            "command": run_install_deploy,
+            "args": deploy_args,
+            "problemMatcher": []
+        }
         tasks_dict = {
             "version": "2.0.0",
-            "tasks": [
-                {
-                    "label": install_task_name,
-                    "type": "shell",
-                    "command": run_install_deploy,
-                    "args": deploy_args,
-                    "problemMatcher": []
-                }
-            ]
+            "tasks": [install_task]
         }
         for cross_debug_config in self.cross_debug_configs:
             if cross_debug_config.modified_recipe is not modified_recipe:
Signature · 1 line
2.53.0
#9 lore raw

PATCH v2 9/18 devtool: ide-sdk: order install after optional build task

In reply to Enguerrand de Ribaucourt

Let binary task generation optionally insert a build task and make install/deploy depend on it. Cargo builds outside BitBake, so this ordering ensures its artifacts exist before do_install and deploy-target run.

AI-Generated: Uses GitHub Copilot

---
Changes in v2:
- Fix install-task duplication while adding build-before-install ordering.

---
 scripts/lib/devtool/ide_plugins/ide_code.py | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)
scripts/lib/devtool/ide_plugins/ide_code.py +7 -2
index 491e74f764..5d35be4f52 100644
@@ -661,7 +661,7 @@ class IdeVSCode(IdeBase):
         IdeBase.update_json_file(
             self.dot_code_dir(modified_recipe), launch_file, launch_dict)
 
-    def vscode_tasks_binary(self, args, image_recipe, modified_recipe):
+    def vscode_tasks_binary(self, args, image_recipe, modified_recipe, build_task=None):
         run_install_deploy = modified_recipe.gen_install_deploy_script(args, image_recipe.nfs_deploy_dir)
         install_task_name = "install && deploy-target %s" % modified_recipe.recipe_id_pretty
         deploy_args = ["--target", resolve_deploy_target(args, image_recipe.nfs_deploy_dir)]
@@ -669,6 +669,7 @@ class IdeVSCode(IdeBase):
             deploy_args += ["--port", args.port]
         for package in args.package or []:
             deploy_args += ["--package", package]
+        tasks = [build_task] if build_task else []
         install_task = {
             "label": install_task_name,
             "type": "shell",
@@ -676,9 +677,13 @@ class IdeVSCode(IdeBase):
             "args": deploy_args,
             "problemMatcher": []
         }
+        if build_task:
+            install_task["dependsOn"] = [build_task["label"]]
+            install_task["dependsOrder"] = "sequence"
+        tasks.append(install_task)
         tasks_dict = {
             "version": "2.0.0",
-            "tasks": [install_task]
+            "tasks": tasks
         }
         for cross_debug_config in self.cross_debug_configs:
             if cross_debug_config.modified_recipe is not modified_recipe:
Signature · 1 line
2.53.0
#10 lore raw

PATCH v2 10/18 devtool: ide-sdk: add Cargo build task

In reply to Enguerrand de Ribaucourt

Generate a Cargo build task that uses the exported recipe config and build flags. Run it in the recipe source tree with only the SDK PATH so Cargo uses the cross environment captured for the recipe.

AI-Generated: Uses GitHub Copilot

---
Changes in v2:
- Use the rustc problem matcher for Cargo build diagnostics.

---
 meta/lib/oeqa/selftest/cases/devtool.py     | 38 +++++++++++++++++++++++++++++
 scripts/lib/devtool/ide_plugins/ide_code.py | 25 +++++++++++++++++++
 2 files changed, 63 insertions(+)
meta/lib/oeqa/selftest/cases/devtool.py +38 -0
index ee055c218a..ef13eaa09a 100644
@@ -4363,6 +4363,44 @@ class DevtoolIdeSdkRustTests(DevtoolIdeSdkTests):
         self.assertNotIn('rust-analyzer.cargo.extraEnv', settings)
         self.assertIn('rust-lang.rust-analyzer', extensions['recommendations'])
 
+    def test_code_cargo_build_task(self):
+        recipe_name = "cargo-example"
+        self._check_workspace()
+        self._write_bb_config()
+        tempdir = self._devtool_ide_sdk_recipe(recipe_name, "Cargo.toml", None)
+        runCmd('devtool ide-sdk %s oe-selftest-image --ide=code '
+               '--target root@localhost --no-host-check' % recipe_name,
+               output_log=self._cmd_logger)
+
+        with open(os.path.join(tempdir, '.vscode', 'tasks.json')) as tasks_file:
+            tasks = json.load(tasks_file)['tasks']
+        build_task = next(task for task in tasks
+                          if task['label'].startswith('cargo build '))
+        cargo_config = os.path.join(
+            self.workspacedir, 'ide-sdk', recipe_name, 'cargo-config.toml')
+        self.assertEqual(build_task['command'], get_bb_var('CARGO', recipe_name))
+        self.assertEqual(
+            build_task['args'][:3], ['--config', cargo_config, 'build'])
+        self.assertIn('--features', build_task['args'])
+        self.assertIn('ide-sdk-selftest', build_task['args'])
+        self.assertEqual(set(build_task['options']['env']), {'PATH'})
+        self.assertEqual(build_task['problemMatcher'], '$rustc')
+
+        build_env = os.environ.copy()
+        build_env.update(build_task['options']['env'])
+        result = subprocess.run(
+            [build_task['command']] + build_task['args'],
+            cwd=build_task['options']['cwd'], env=build_env,
+            stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+            universal_newlines=True)
+        self.assertEqual(result.returncode, 0, result.stdout)
+
+        install_task = next(
+            task for task in tasks
+            if task['label'].startswith('install && deploy-target '))
+        self.assertEqual(install_task['dependsOn'], [build_task['label']])
+
+
 class DevtoolIdeSdkKernelTests(DevtoolIdeSdkTests):
 
     @OETestTag("runqemu")
scripts/lib/devtool/ide_plugins/ide_code.py +25 -0
index 5d35be4f52..cb54948ce1 100644
@@ -8,6 +8,7 @@
 import json
 import logging
 import os
+import shlex
 import shutil
 from devtool.ide_plugins import BuildTool, IdeBase, GdbCrossConfig, DebuggerServerModes, LldbServerConfig, get_devtool_deploy_opts, resolve_deploy_target
 
@@ -832,6 +833,28 @@ class IdeVSCode(IdeBase):
         IdeBase.update_json_file(
             self.dot_code_dir(modified_recipe), tasks_file, tasks_dict)
 
+    def vscode_tasks_cargo(self, args, image_recipe, modified_recipe):
+        build_task_name = "cargo build %s" % modified_recipe.recipe_id_pretty
+        cargo_config = modified_recipe.cargo_config_path
+        build_task = {
+            "label": build_task_name,
+            "type": "shell",
+            "command": modified_recipe.cargo,
+            "args": ["--config", cargo_config, "build"] +
+                     shlex.split(modified_recipe.cargo_build_flags) +
+                     shlex.split(modified_recipe.cargo_packageconfig_confargs),
+            "options": {
+                "cwd": modified_recipe.real_srctree,
+                "env": {"PATH": modified_recipe.path},
+            },
+            "group": {
+                "kind": "build",
+                "isDefault": True
+            },
+            "problemMatcher": "$rustc"
+        }
+        self.vscode_tasks_binary(args, image_recipe, modified_recipe, build_task)
+
     def vscode_tasks_fallback(self, args, image_recipe, modified_recipe):
         oe_init_dir = modified_recipe.oe_init_dir
         oe_init = ". %s %s > /dev/null && " % (modified_recipe.oe_init_build_env, modified_recipe.topdir)
@@ -970,6 +993,8 @@ class IdeVSCode(IdeBase):
             self.vscode_tasks_binary(args, image_recipe, modified_recipe)
         elif modified_recipe.build_tool == BuildTool.KERNEL_MODULE:
             self.vscode_tasks_kernel_module(args, image_recipe, modified_recipe)
+        elif modified_recipe.build_tool == BuildTool.CARGO:
+            self.vscode_tasks_cargo(args, image_recipe, modified_recipe)
         else:
             self.vscode_tasks_fallback(args, image_recipe, modified_recipe)
 
Signature · 1 line
2.53.0
#11 lore raw

PATCH v2 11/18 devtool: ide-sdk: enforce LLDB for Cargo

In reply to Enguerrand de Ribaucourt

Route Cargo debugging through CodeLLDB and lldb-server because GDB/cppdbg does not resolve Rust function breakpoints in the VS Code Rust workflow.

Cargo debugging always uses LLDB, regardless of the recipe C/C++ toolchain. Key shared debugger instances by backend rather than TOOLCHAIN so Rust recipes use the correct server while ordinary GCC recipes retain GDB. Cargo C++ IntelliSense still uses the recipe compiler.

This follows VS Code's recommended Rust workflow:
https://code.visualstudio.com/docs/languages/rust#_debugging

AI-Generated: Uses GitHub Copilot

---
Changes in v2:
- Cover LLDB configuration when using --ide=none.
- Explain GDB's Rust function breakpoint limitations.

---
 meta/lib/oeqa/selftest/cases/devtool.py     | 35 +++++++++++++++++++++++++++
 scripts/lib/devtool/ide_plugins/ide_code.py |  7 ++++--
 scripts/lib/devtool/ide_plugins/ide_none.py |  4 ++--
 scripts/lib/devtool/ide_sdk.py              | 37 +++++++++++++++++++----------
 4 files changed, 66 insertions(+), 17 deletions(-)
meta/lib/oeqa/selftest/cases/devtool.py +35 -0
index ef13eaa09a..3b80d6a4a8 100644
@@ -4363,6 +4363,41 @@ class DevtoolIdeSdkRustTests(DevtoolIdeSdkTests):
         self.assertNotIn('rust-analyzer.cargo.extraEnv', settings)
         self.assertIn('rust-lang.rust-analyzer', extensions['recommendations'])
 
+    def test_cargo_lldb_configuration(self):
+        recipe_name = "cargo-example"
+        self._check_workspace()
+        self._write_bb_config()
+        tempdir = self._devtool_ide_sdk_recipe(recipe_name, "Cargo.toml", None)
+        runCmd('devtool ide-sdk %s oe-selftest-image --ide=code --ide=none '
+               '--target root@localhost --no-host-check' % recipe_name,
+               output_log=self._cmd_logger)
+
+        with open(os.path.join(tempdir, '.vscode', 'extensions.json')) as extensions_file:
+            extensions = json.load(extensions_file)
+        with open(os.path.join(tempdir, '.vscode', 'launch.json')) as launch_file:
+            launch = json.load(launch_file)['configurations'][0]
+        with open(os.path.join(tempdir, '.vscode', 'tasks.json')) as tasks_file:
+            tasks = json.load(tasks_file)['tasks']
+
+        self.assertIn('vadimcn.vscode-lldb', extensions['recommendations'])
+        self.assertEqual(launch['type'], 'lldb')
+
+        scripts_dir = self._workspace_scripts_dir(recipe_name)
+        scripts = os.listdir(scripts_dir)
+        self.assertTrue(any(name.startswith('lldb_server_') for name in scripts))
+        self.assertFalse(any(name.startswith('gdbserver_') for name in scripts))
+        self._verify_install_script_code(tempdir, recipe_name)
+        cargo_config = os.path.join(
+            self.workspacedir, 'ide-sdk', recipe_name, 'cargo-config.toml')
+        self.assertExists(
+            cargo_config, 'Cargo config was not generated for --ide=none')
+        target_dbgsrc_dir = get_bb_var('TARGET_DBGSRC_DIR', recipe_name)
+        self.assertEqual(
+            launch['sourceMap'][target_dbgsrc_dir], os.path.realpath(tempdir))
+        server_task = next(
+            task for task in tasks if task['label'] == launch['preLaunchTask'])
+        self.assertIn('lldb-server platform --server', server_task['args'][-1])
+
     def test_code_cargo_build_task(self):
         recipe_name = "cargo-example"
         self._check_workspace()
scripts/lib/devtool/ide_plugins/ide_code.py +5 -2
index cb54948ce1..a7bb3221e0 100644
@@ -315,7 +315,10 @@ class IdeVSCode(IdeBase):
     def vscode_extensions(self, modified_recipe):
         recommendations = []
         if modified_recipe.build_tool is BuildTool.CARGO:
-            recommendations.append("rust-lang.rust-analyzer")
+            recommendations += [
+                "rust-lang.rust-analyzer",
+                "vadimcn.vscode-lldb"
+            ]
         # clangd (matched to the recipe's real cross compiler via
         # compile_commands.json) replaces cpptools as the IntelliSense provider
         # when selected via IDE_SDK_INTELLISENSE (defaults to clangd for the
@@ -1003,7 +1006,7 @@ class IdeVSCode(IdeBase):
         self.vscode_extensions(modified_recipe)
         self.vscode_c_cpp_properties(modified_recipe, image_recipe)
         if args.target:
-            if modified_recipe.toolchain == 'clang':
+            if modified_recipe.uses_lldb:
                 self.initialize_cross_debug_configs(
                     image_recipe, modified_recipe, LldbServerConfigVSCode)
             else:
scripts/lib/devtool/ide_plugins/ide_none.py +2 -2
index eba576bfdb..d2f66ade8d 100644
@@ -306,13 +306,13 @@ class IdeNone(IdeBase):
 
         - Execute the do_install task
         - Execute devtool deploy-target
-        - Generate a gdbinit file per executable
+        - Generate debugger configuration for each executable
         - Generate the oe-scripts sym-link
         """
         script_path = modified_recipe.gen_install_deploy_script(args, image_recipe.nfs_deploy_dir)
         logger.info("Created: %s" % script_path)
 
-        if modified_recipe.toolchain == 'clang':
+        if modified_recipe.uses_lldb:
             self.initialize_cross_debug_configs(
                 image_recipe, modified_recipe, LldbServerConfigNone)
         else:
scripts/lib/devtool/ide_sdk.py +24 -13
index 98e39703e7..0ce2a7780e 100755
@@ -406,11 +406,11 @@ class RecipeImage:
         resulting bbappend content actually differs from what was on disk
         when initialize() ran, False if it is left exactly as it was.
         """
-        wants_gdbserver = any(
-            r.wants_gdbserver and r.toolchain != 'clang'
-            for r in recipes_modified)
         wants_lldb_server = any(
-            r.wants_gdbserver and r.toolchain == 'clang'
+            r.wants_gdbserver and r.uses_lldb
+            for r in recipes_modified)
+        wants_gdbserver = any(
+            r.wants_gdbserver and not r.uses_lldb
             for r in recipes_modified)
 
         # Only add what the base configuration (e.g. local.conf) does not
@@ -1158,6 +1158,16 @@ class RecipeModified:
         self.recipe_id = self.bpn + "-" + self.package_arch
         self.recipe_id_pretty = self.bpn + ": " + self.package_arch
 
+    @property
+    def uses_lldb(self):
+        # VS Code's Rust debugging guide recommends CodeLLDB:
+        # https://code.visualstudio.com/docs/languages/rust#_debugging
+        # ide-sdk supports Rust debugging only through CodeLLDB; cppdbg/GDB
+        # does not resolve Rust function breakpoints from the VS Code UI because
+        # GDB does not fully understand Rust's LLVM-generated function layout
+        # and name mangling.
+        return self.toolchain == 'clang' or self.build_tool is BuildTool.CARGO
+
     @property
     def cargo_config_path(self):
         return os.path.join(self.ide_sdk_dir, 'cargo-config.toml')
@@ -2048,20 +2058,21 @@ def ide_setup(args, config, basepath, workspace):
                 bootstrap_tasks += recipe_modified.bootstrap_tasks
                 recipes_modified.append(recipe_modified)
 
-                # Key by (arch, toolchain) so recipes with different toolchains
-                # targeting the same arch each get the right debugger.
+                # Key by (arch, debugger) so recipes sharing an architecture
+                # but using different debugger backends each get the right one.
+                debugger = 'lldb' if recipe_modified.uses_lldb else 'gdb'
                 debugger_key = (recipe_modified.target_arch,
-                                recipe_modified.toolchain or '')
+                                debugger)
                 if debugger_key not in debuggers:
                     target_device = TargetDevice(args)
-                    if recipe_modified.toolchain == 'clang':
-                        debugger = RecipeLldbNative(args, target_device)
+                    if recipe_modified.uses_lldb:
+                        debugger_config = RecipeLldbNative(args, target_device)
                     else:
-                        debugger = RecipeGdbCross(
+                        debugger_config = RecipeGdbCross(
                             args, recipe_modified.target_arch, target_device)
-                    debugger.initialize(config, workspace, tinfoil)
-                    bootstrap_tasks += debugger.bootstrap_tasks
-                    debuggers[debugger_key] = debugger
+                    debugger_config.initialize(config, workspace, tinfoil)
+                    bootstrap_tasks += debugger_config.bootstrap_tasks
+                    debuggers[debugger_key] = debugger_config
                 recipe_modified.debugger_cross = debuggers[debugger_key]
 
     finally:
Signature · 1 line
2.53.0
#12 lore raw

PATCH v2 12/18 devtool: ide-sdk: remap Cargo sources for BitBake builds

In reply to Enguerrand de Ribaucourt

With devtool modify, Cargo sources can live outside WORKDIR, so the standard Rust remaps leave the workspace path in DWARF and package QA rejects the debug package. Append a workspace-specific remap to RUST_DEBUG_REMAP in a managed bbappend section so repeated ide-sdk runs restore and reparse it.

AI-Generated: Uses GitHub Copilot

---

Changes in v2:

- Revert bbappend comparison after stripping managed sections to avoid reparse when the ide-sdk section is not modified. I think there is a pre-existing bug with that logic however. An ide-sdk re-run leaves the stripped bbappend for clang and cargo classes. As a consequence, I simplified the Qemu selftest to no longer run ide-sdk twice.

---
 scripts/lib/devtool/ide_sdk.py | 34 +++++++++++++++++++++++++---------
 1 file changed, 25 insertions(+), 9 deletions(-)
scripts/lib/devtool/ide_sdk.py +25 -9
index 0ce2a7780e..5096297b32 100755
@@ -831,6 +831,7 @@ class RecipeModified:
     VALID_BASH_ENV_NAME_CHARS = re.compile(r"^[a-zA-Z0-9_]*$")
 
     MARKER = '# devtool ide-sdk: clangd toolchain support'
+    CARGO_REMAP_MARKER = '# devtool ide-sdk: Cargo source debug remap'
 
     # PROVIDES of recipes that are deployed via do_deploy (e.g. to
     # DEPLOY_DIR_IMAGE) rather than installed into the rootfs, so adding them
@@ -951,13 +952,14 @@ class RecipeModified:
                 content = f.read()
             originals[name] = content
             stripped = cls._strip_marker_section(content, cls.MARKER)
+            stripped = cls._strip_marker_section(stripped, cls.CARGO_REMAP_MARKER)
             if stripped != content:
                 with open(bbappend, 'w') as f:
                     f.write(stripped)
         return originals
 
     def update_bbappend(self):
-        """Add DEPENDS on clang-native when clangd is used as only the IntelliSense engine"""
+        """Add workspace-only clang dependencies or Cargo debug path remaps."""
         wants_clang_native = (
             self.build_tool.is_c_cpp
             and self.ide_sdk_intellisense == 'clangd'
@@ -970,17 +972,27 @@ class RecipeModified:
         else:
             parsed_content = ''
 
-        if not wants_clang_native:
+        new_sections = []
+        if wants_clang_native:
+            new_sections.append(
+                self.MARKER + '\nDEPENDS:append = " clang-native"\n')
+        if self.build_tool is BuildTool.CARGO:
+            new_sections.append(
+                self.CARGO_REMAP_MARKER
+                + '\nRUST_DEBUG_REMAP:append = " '
+                '--remap-path-prefix=${S}=${TARGET_DBGSRC_DIR}"\n')
+
+        if not new_sections:
             if self.MARKER in original_content:
                 logger.info(
                     "Removed clangd toolchain support from %s: no longer needed", self.bbappend)
             return False
 
-        new_section = self.MARKER + '\nDEPENDS:append = " clang-native"\n'
         new_content = parsed_content
-        if new_content and not new_content.endswith('\n'):
-            new_content += '\n'
-        new_content += new_section
+        for section in new_sections:
+            if new_content and not new_content.endswith('\n'):
+                new_content += '\n'
+            new_content += section
 
         if new_content == original_content:
             return False
@@ -989,9 +1001,13 @@ class RecipeModified:
         os.makedirs(appends_dir, exist_ok=True)
         with open(self.bbappend, 'w') as f:
             f.write(new_content)
-        logger.info(
-            "Updated %s: added DEPENDS on clang-native, needed to stage clangd "
-            "for IDE_SDK_INTELLISENSE=\"clangd\"", self.bbappend)
+        if wants_clang_native:
+            logger.info(
+                "Updated %s: added DEPENDS on clang-native, needed to stage clangd "
+                "for IDE_SDK_INTELLISENSE=\"clangd\"", self.bbappend)
+        if self.build_tool is BuildTool.CARGO:
+            logger.info(
+                "Updated %s: remapped Cargo external sources for debug info", self.bbappend)
         return True
 
     def initialize(self, config, workspace, tinfoil):
Signature · 1 line
2.53.0
#13 lore raw

Patchtest results for [PATCH v2 12/18] devtool: ide-sdk: remap Cargo sources for BitBake builds

Thank you for your submission. Patchtest identified one or more issues with the patch. Please see the log below for more information:

---
Testing patch /home/patchtest/share/mboxes/v2-12-18-devtool-ide-sdk-remap-Cargo-sources-for-BitBake-builds.patch

FAIL: test Signed-off-by presence: Mbox is missing Signed-off-by. Add it manually or with "git commit --amend -s" (test_mbox.TestMbox.test_signed_off_by_presence)

PASS: pretest pylint (test_python_pylint.PyLint.pretest_pylint)
PASS: test auh changelog truncation notice (test_mbox.TestMbox.test_auh_changelog_truncation_notice)
PASS: test author valid (test_mbox.TestMbox.test_author_valid)
PASS: test commit message presence (test_mbox.TestMbox.test_commit_message_presence)
PASS: test commit message user tags (test_mbox.TestMbox.test_commit_message_user_tags)
PASS: test max line length (test_metadata.TestMetadata.test_max_line_length)
PASS: test mbox format (test_mbox.TestMbox.test_mbox_format)
PASS: test non-AUH upgrade (test_mbox.TestMbox.test_non_auh_upgrade)
PASS: test pylint (test_python_pylint.PyLint.test_pylint)
PASS: test shortlog format (test_mbox.TestMbox.test_shortlog_format)
PASS: test shortlog length (test_mbox.TestMbox.test_shortlog_length)
PASS: test target mailing list (test_mbox.TestMbox.test_target_mailing_list)

SKIP: pretest src uri left files: No modified recipes, skipping pretest (test_metadata.TestMetadata.pretest_src_uri_left_files)
SKIP: test CVE check ignore: No modified recipes or older target branch, skipping test (test_metadata.TestMetadata.test_cve_check_ignore)
SKIP: test CVE tag format: No new source patches introduced (test_patch.TestPatch.test_cve_tag_format)
SKIP: test Signed-off-by presence: No new source patches introduced (test_patch.TestPatch.test_signed_off_by_presence)
SKIP: test Upstream-Status presence: No new source patches introduced (test_patch.TestPatch.test_upstream_status_presence_format)
SKIP: test bugzilla entry format: No bug ID found (test_mbox.TestMbox.test_bugzilla_entry_format)
SKIP: test lic files chksum modified not mentioned: No modified recipes, skipping test (test_metadata.TestMetadata.test_lic_files_chksum_modified_not_mentioned)
SKIP: test lic files chksum presence: No added recipes, skipping test (test_metadata.TestMetadata.test_lic_files_chksum_presence)
SKIP: test license presence: No added recipes, skipping test (test_metadata.TestMetadata.test_license_presence)
SKIP: test series merge on head: Merge test is disabled for now (test_mbox.TestMbox.test_series_merge_on_head)
SKIP: test src uri left files: No modified recipes, skipping test (test_metadata.TestMetadata.test_src_uri_left_files)
SKIP: test summary presence: No added recipes, skipping test (test_metadata.TestMetadata.test_summary_presence)

---

Please address the issues identified and
submit a new revision of the patch, or alternatively, reply to this email with an explanation of why the patch should be accepted. If you believe these results are due to an error in patchtest, please submit a bug at https://bugzilla.yoctoproject.org/ (use the 'Patchtest' category under 'Yocto Project Subprojects'). For more information on specific failures, see: https://wiki.yoctoproject.org/wiki/Patchtest. Thank you!

#14 lore raw

PATCH v2 13/18 devtool: ide-sdk: map Cargo workspace sources for LLDB

In reply to Enguerrand de Ribaucourt

Cargo debug info remaps the modified source tree to TARGET_DBGSRC_DIR. Include this reverse map in CodeLLDB launch configs even when rootfs-dbg is disabled. Use the Cargo workspace as its source root while preserving the existing rootfs-debug mappings for other recipes.

AI-Generated: Uses GitHub Copilot

---
Changes in v2:
- Move source-map verification to the LLDB configuration test.

---
 scripts/lib/devtool/ide_plugins/ide_code.py | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)
scripts/lib/devtool/ide_plugins/ide_code.py +10 -3
index a7bb3221e0..6312988db1 100644
@@ -555,15 +555,22 @@ class IdeVSCode(IdeBase):
         source_map = {
             "/usr/include": os.path.join(modified_recipe.recipe_sysroot, "usr", "include")
         }
-        if lldb_config.image_recipe.rootfs_dbg:
+        rootfs_dbg = lldb_config.image_recipe.rootfs_dbg
+        is_cargo = modified_recipe.build_tool is BuildTool.CARGO
+        if is_cargo or rootfs_dbg:
+            source_root = (
+                modified_recipe.real_srctree
+                if is_cargo else "${workspaceFolder}")
             # 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}"
+                        source_root
                         + host_path[len(modified_recipe.real_srctree):])
-                else:
+                elif rootfs_dbg:
                     source_map[target_path] = host_path
+
+        if rootfs_dbg:
             if "/usr/src/debug" in source_map:
                 logger.error(
                     'Key "/usr/src/debug" already exists in source_map. '
Signature · 1 line
2.53.0
#15 lore raw

PATCH v2 14/18 devtool: ide-sdk: map libstd-rs sources to Rust SDK

In reply to Enguerrand de Ribaucourt

libstd-rs DWARF paths use a source prefix under work-shared, while rust-native stages the standard-library sources in the recipe native sysroot. Derive the target prefix from BitBake metadata and map it to the SDK source tree so LLDB runtime frames resolve without host Rust sources.

AI-Generated: Uses GitHub Copilot

---
Changes in v2:
- Use STAGING_LIBDIR_NATIVE for multilib-correct Rust source paths.

---
 scripts/lib/devtool/ide_plugins/ide_code.py |  2 ++
 scripts/lib/devtool/ide_sdk.py              | 19 +++++++++++++++++++
 2 files changed, 21 insertions(+)
scripts/lib/devtool/ide_plugins/ide_code.py +2 -0
index 6312988db1..8d5a0710fa 100644
@@ -557,6 +557,8 @@ class IdeVSCode(IdeBase):
         }
         rootfs_dbg = lldb_config.image_recipe.rootfs_dbg
         is_cargo = modified_recipe.build_tool is BuildTool.CARGO
+        if is_cargo:
+            source_map.update(modified_recipe.cargo_std_source_map)
         if is_cargo or rootfs_dbg:
             source_root = (
                 modified_recipe.real_srctree
scripts/lib/devtool/ide_sdk.py +19 -0
index 5096297b32..17209a8bde 100755
@@ -923,6 +923,7 @@ class RecipeModified:
         self.cargo_packageconfig_confargs = None
         self.cargo_rustflags = None
         self.cargo_env = None
+        self.cargo_std_source_map = {}
 
         # Populated after bitbake built all the recipes
         self._installed_binaries = None
@@ -1140,6 +1141,24 @@ class RecipeModified:
                 'SYSTEM_DEPS_BUILD_INTERNAL': 'never',
                 'ZSTD_SYS_USE_PKG_CONFIG': '1',
             })
+            # libstd-rs DWARF paths map to sources staged by rust-native.
+            libstd_recipe_d = parse_recipe(
+                config, tinfoil, 'libstd-rs', appends=True,
+                filter_workspace=False)
+            if libstd_recipe_d:
+                libstd_rustsrc = libstd_recipe_d.getVar('RUSTSRC')
+                target_dbgsrc_dir = libstd_recipe_d.getVar('TARGET_DBGSRC_DIR')
+                libstd_tmpdir = libstd_recipe_d.getVar('TMPDIR')
+                if libstd_rustsrc and target_dbgsrc_dir and libstd_tmpdir:
+                    shared_source_dir = os.path.join(libstd_tmpdir, 'work-shared')
+                    target_source_root = os.path.join(
+                        target_dbgsrc_dir,
+                        os.path.relpath(libstd_rustsrc, shared_source_dir),
+                        'library')
+                    sdk_source_root = os.path.join(
+                        recipe_d.getVar('STAGING_LIBDIR_NATIVE'),
+                        'rustlib', 'src', 'rust', 'library')
+                    self.cargo_std_source_map[target_source_root] = sdk_source_root
             self.build_tool = BuildTool.CARGO
         elif bb.data.inherits_class('module', recipe_d):
             self.build_tool = BuildTool.KERNEL_MODULE
Signature · 1 line
2.53.0
#16 lore raw

PATCH v2 15/18 oeqa/selftest: share source breakpoint lookup

In reply to Enguerrand de Ribaucourt

Move the stable source-line helper to the common ide-sdk test base so the Rust CodeLLDB integration can reuse it. Preserve the rationale for using marker-based lines instead of brittle hard-coded numbers.

AI-Generated: Uses GitHub Copilot

---
 meta/lib/oeqa/selftest/cases/devtool.py | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)
meta/lib/oeqa/selftest/cases/devtool.py +14 -14
index 3b80d6a4a8..f0bfa5ce27 100644
@@ -3133,6 +3133,20 @@ class DevtoolIdeSdkTests(DevtoolBase):
     def _sources_workdir_dir(self, src_dir):
         return os.path.realpath(os.path.join(src_dir, 'oe-workdir'))
 
+    def _find_source_break_line(self, tempdir, source_file, marker):
+        """Find the 1-based line number of `marker` in tempdir/source_file
+
+        Used to set a source-level (file:line) breakpoint at a stable,
+        self-documenting location instead of hard-coding a line number that
+        would silently go stale whenever the example source changes.
+        """
+        source_path = os.path.join(tempdir, source_file)
+        with open(source_path) as f:
+            for lineno, line in enumerate(f, start=1):
+                if marker in line:
+                    return lineno
+        self.fail("Could not find marker %r in %s" % (marker, source_path))
+
     def _find_multi_script_port(self, recipe_name, script_prefix, example_exe):
         """Discover the port assigned to a generated 'multi' mode debug-server
         script (gdbserver_*/lldb_server_*), since each --ide plugin allocates
@@ -4860,20 +4874,6 @@ class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests):
             self.assertTrue(task,
                             f"Configuration '{config_name}' preLaunchTask should not be empty")
 
-    def _find_source_break_line(self, tempdir, source_file, marker):
-        """Find the 1-based line number of `marker` in tempdir/source_file
-
-        Used to set a source-level (file:line) breakpoint at a stable,
-        self-documenting location instead of hard-coding a line number that
-        would silently go stale whenever the example source changes.
-        """
-        source_path = os.path.join(tempdir, source_file)
-        with open(source_path) as f:
-            for lineno, line in enumerate(f, start=1):
-                if marker in line:
-                    return lineno
-        self.fail("Could not find marker %r in %s" % (marker, source_path))
-
     def _lldb_debug_cpp_example_batch_commands(self, tempdir):
         """Get a list of lldb --batch '-o' commands to debug the cpp-example-lib example
 
Signature · 1 line
2.53.0
#17 lore raw

PATCH v2 16/18 oeqa/selftest: run IDE tasks with configured environment

In reply to Enguerrand de Ribaucourt

Apply each generated shell task's options.env and return its runCmd results. Cargo build tasks intentionally keep only the SDK PATH, and the following rebuild integration test needs both to execute and inspect that task.

AI-Generated: Uses GitHub Copilot

---
 meta/lib/oeqa/selftest/cases/devtool.py | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)
meta/lib/oeqa/selftest/cases/devtool.py +9 -2
index f0bfa5ce27..f23e1a7f95 100644
@@ -3485,6 +3485,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
         self.assertIn(task_label, task_by_label,
                       "Task '%s' not found in tasks.json" % task_label)
 
+        task_results = []
         ran = set()
 
         def run_task(label, execute_task):
@@ -3509,14 +3510,20 @@ class DevtoolIdeSdkTests(DevtoolBase):
                         args = [args]
                     task_cmd = [command] + [str(arg) for arg in args]
 
-                    task_cwd = task.get('options', {}).get('cwd', tempdir)
+                    task_options = task.get('options', {})
+                    task_cwd = task_options.get('cwd', tempdir)
                     if isinstance(task_cwd, str):
                         task_cwd = task_cwd.replace('${workspaceFolder}', tempdir)
-                    runCmd(task_cmd, cwd=task_cwd, output_log=self._cmd_logger)
+                    task_env = os.environ.copy()
+                    task_env.update(task_options.get('env', {}))
+                    task_results.append(runCmd(
+                        task_cmd, cwd=task_cwd, env=task_env,
+                        output_log=self._cmd_logger))
 
             ran.add(label)
 
         run_task(task_label, run_main_task)
+        return task_results
 
     def _ide_sdk_package_opts(self):
         """Extra `devtool ide-sdk --package` options. Override to scope packages."""
Signature · 1 line
2.53.0
#18 lore raw

PATCH v2 17/18 oeqa/selftest: debug Cargo example over QEMU slirp

In reply to Enguerrand de Ribaucourt

Exercise Cargo deploy and CodeLLDB launch against oe-selftest-image over slirp. Verify source breakpoints, Rust and runtime backtrace frames, and that the BitBake RUST_DEBUG_REMAP includes the devtool external source tree so package QA does not reject host paths in DWARF.

AI-Generated: Uses GitHub Copilot

---

Changes in v2:

- Use STAGING_LIBDIR_NATIVE to locate Rust standard-library sources in the SDK.
- Remove the second ide-sdk invocation and repeated-remap assertion while retaining the QEMU debugging and rebuild coverage.

---
 meta/lib/oeqa/selftest/cases/devtool.py | 117 ++++++++++++++++++++++++++++++++
 1 file changed, 117 insertions(+)
meta/lib/oeqa/selftest/cases/devtool.py +117 -0
index f23e1a7f95..0128d37d83 100644
@@ -4456,6 +4456,123 @@ class DevtoolIdeSdkRustTests(DevtoolIdeSdkTests):
             if task['label'].startswith('install && deploy-target '))
         self.assertEqual(install_task['dependsOn'], [build_task['label']])
 
+    @OETestTag("runqemu")
+    def test_code_cargo_qemu_slirp(self):
+        recipe_name = 'cargo-example'
+        testimage = 'oe-selftest-image'
+        self._check_workspace()
+        self._write_bb_config()
+        tempdir = self._devtool_ide_sdk_recipe(
+            recipe_name, 'Cargo.toml', testimage)
+        port_start = self._reserve_slirp_port_block()
+        port_opts = ' -G %d' % port_start
+        runCmd(
+            'devtool ide-sdk %s %s --ide=code --target root@localhost '
+            '--port 2222 --no-host-check%s' % (
+                recipe_name, testimage, port_opts),
+            output_log=self._cmd_logger)
+        source_remap = '--remap-path-prefix=%s=%s' % (
+            os.path.realpath(tempdir),
+            get_bb_var('TARGET_DBGSRC_DIR', recipe_name))
+        rustflags = shlex.split(get_bb_var('RUSTFLAGS', recipe_name))
+        self.assertIn(
+            source_remap, rustflags,
+            'Cargo BitBake builds must remap the external source tree for DWARF')
+        self._verify_slirp_bbappend(testimage)
+
+        with runqemu(
+                testimage, runqemuparams='nographic slirp snapshot') as qemu:
+            self.assertIsNotNone(qemu.port, 'No SSH port for the slirp target')
+            self._verify_rust_lldb_debugging(tempdir, expected_input=4)
+
+    def _verify_rust_lldb_debugging(self, tempdir, expected_input):
+        with open(os.path.join(tempdir, '.vscode', 'launch.json')) as launch_file:
+            launch = json.load(launch_file)['configurations'][0]
+        with open(os.path.join(tempdir, '.vscode', 'tasks.json')) as tasks_file:
+            tasks = json.load(tasks_file)['tasks']
+
+        self.assertEqual(launch['type'], 'lldb')
+        target_dbgsrc_dir = get_bb_var('TARGET_DBGSRC_DIR', 'cargo-example')
+        self.assertEqual(
+            launch['sourceMap'][target_dbgsrc_dir], os.path.realpath(tempdir))
+        self._run_vscode_task_with_dependencies(
+            tempdir, launch['preLaunchTask'], run_main_task=False)
+        server_task = next(
+            task for task in tasks if task['label'] == launch['preLaunchTask'])
+        self.assertEqual(server_task['command'], 'ssh')
+        ssh_args = [server_task['command']] + server_task['args']
+        if ssh_args[-1].startswith('"') and ssh_args[-1].endswith('"'):
+            ssh_args[-1] = ssh_args[-1][1:-1].replace('\\$', '$')
+        runCmd(ssh_args, output_log=self._cmd_logger)
+
+        source_line = self._find_source_break_line(
+            tempdir, 'src/main.rs', '    match n {')
+        println_line = self._find_source_break_line(
+            tempdir, 'src/main.rs', 'println!')
+        lldb_batch = [
+            os.path.join(
+                get_bb_var('RECIPE_SYSROOT_NATIVE', 'lldb-native'),
+                'usr', 'bin', 'lldb'),
+            '--batch',
+        ]
+        for command in launch['initCommands'] + launch['targetCreateCommands']:
+            lldb_batch += ['-o', command]
+        source_map_args = []
+        for source, destination in launch.get('sourceMap', {}).items():
+            source_map_args += [source, destination]
+        if source_map_args:
+            lldb_batch += [
+                '-o', 'settings set target.source-map %s' %
+                ' '.join(source_map_args)]
+        lldb_batch += [
+            '-o', 'breakpoint set --file main.rs --line %d' % source_line,
+            '-o', 'breakpoint set --file main.rs --line %d' % println_line,
+            '-o', 'breakpoint disable 2',
+            '-o', 'breakpoint list',
+            '-o', 'run',
+            '-o', 'frame variable n',
+            '-o', 'thread backtrace',
+            '-o', 'breakpoint disable 1',
+            '-o', 'breakpoint enable 2',
+            '-o', 'continue',
+            '-o', 'frame info',
+            '-o', 'frame select 1',
+            '-o', 'frame info',
+            '-o', 'frame select 3',
+            '-o', "image lookup -v -r -n 'std::rt::lang_start.*'",
+            '-o', 'thread backtrace',
+            '-o', 'exit',
+        ]
+        result = runCmd(lldb_batch, timeout=60, output_log=self._cmd_logger)
+        self.assertNotIn('(pending)', result.output)
+        self.assertIn('cargo_example::fibonacci', result.output)
+        self.assertIn('cargo_example::main', result.output)
+        self.assertIn('std::rt::lang_start', result.output)
+        self.assertIn('__libc_start', result.output)
+        self.assertRegex(result.output, r'n\s*=\s*%d' % expected_input)
+        libstd_rustsrc = get_bb_var('RUSTSRC', 'libstd-rs')
+        libstd_work_shared = os.path.join(
+            get_bb_var('TMPDIR', 'libstd-rs'), 'work-shared')
+        rust_std_target_root = os.path.join(
+            get_bb_var('TARGET_DBGSRC_DIR', 'libstd-rs'),
+            os.path.relpath(libstd_rustsrc, libstd_work_shared),
+            'library')
+        rust_std_sdk_root = os.path.join(
+            get_bb_var('STAGING_LIBDIR_NATIVE', 'cargo-example'),
+            'rustlib', 'src', 'rust', 'library')
+        rust_std_source = os.path.join(
+            rust_std_target_root, 'std', 'src', 'rt.rs')
+        self.assertEqual(
+            launch['sourceMap'][rust_std_target_root], rust_std_sdk_root)
+        self.assertIn(
+            rust_std_source, result.output,
+            'Rust runtime backtrace did not resolve to the SDK debug path:\n%s' %
+            result.output)
+        self.assertNotIn(libstd_rustsrc, result.output)
+        self.assertTrue(
+            os.path.isfile(os.path.join(rust_std_sdk_root, 'std', 'src', 'rt.rs')),
+            'Rust standard-library source is missing from the recipe SDK sysroot')
+
 
 class DevtoolIdeSdkKernelTests(DevtoolIdeSdkTests):
 
Signature · 1 line
2.53.0
#19 lore raw

PATCH v2 18/18 oeqa/selftest: verify Cargo rebuild and redeploy

In reply to Enguerrand de Ribaucourt

After a source edit, run the ordered Cargo build/install/deploy tasks and verify the changed Fibonacci result. Use a helper to shift source lines so the breakpoint location can change without hard-coded line numbers; reuse the shared LLDB runner for both debug sessions.

AI-Generated: Uses GitHub Copilot

---
 meta/lib/oeqa/selftest/cases/devtool.py | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
meta/lib/oeqa/selftest/cases/devtool.py +31 -0
index 0128d37d83..c54f13de5a 100644
@@ -3147,6 +3147,15 @@ class DevtoolIdeSdkTests(DevtoolBase):
                     return lineno
         self.fail("Could not find marker %r in %s" % (marker, source_path))
 
+    def _insert_source_line_shift(self, source_path, marker, line_count):
+        with open(source_path) as source_handle:
+            source = source_handle.read()
+        self.assertEqual(source.count(marker), 1)
+        inserted_lines = ''.join(
+            '    // line shift %d\n' % index for index in range(line_count))
+        with open(source_path, 'w') as source_handle:
+            source_handle.write(source.replace(marker, inserted_lines + marker, 1))
+
     def _find_multi_script_port(self, recipe_name, script_prefix, example_exe):
         """Discover the port assigned to a generated 'multi' mode debug-server
         script (gdbserver_*/lldb_server_*), since each --ide plugin allocates
@@ -4485,6 +4494,28 @@ class DevtoolIdeSdkRustTests(DevtoolIdeSdkTests):
             self.assertIsNotNone(qemu.port, 'No SSH port for the slirp target')
             self._verify_rust_lldb_debugging(tempdir, expected_input=4)
 
+            main_rs = os.path.join(tempdir, 'src', 'main.rs')
+            self._insert_source_line_shift(main_rs, '    match n {', 4)
+            with open(main_rs) as source_file:
+                shifted_source = source_file.read()
+            self.assertEqual(shifted_source.count('fibonacci(4)'), 1)
+            with open(main_rs, 'w') as source_file:
+                source_file.write(
+                    shifted_source.replace('fibonacci(4)', 'fibonacci(5)', 1))
+
+            with open(os.path.join(tempdir, '.vscode', 'tasks.json')) as tasks_file:
+                tasks = json.load(tasks_file)['tasks']
+            install_task = next(
+                task for task in tasks
+                if task['label'].startswith('install && deploy-target '))
+            task_results = self._run_vscode_task_with_dependencies(
+                tempdir, install_task['label'])
+            self.assertTrue(
+                any('Compiling cargo-example' in result.output
+                    for result in task_results),
+                'Cargo build task did not recompile the modified Rust source')
+            self._verify_rust_lldb_debugging(tempdir, expected_input=5)
+
     def _verify_rust_lldb_debugging(self, tempdir, expected_input):
         with open(os.path.join(tempdir, '.vscode', 'launch.json')) as launch_file:
             launch = json.load(launch_file)['configurations'][0]
Signature · 1 line
2.53.0
#20 lore raw
In reply to Enguerrand de Ribaucourt
Quoted 31 lines · Enguerrand de Ribaucourt via openembedded-core

Hello, this series introduces support for Rust ide-sdk VS Code configuration against the Yocto cross-compilation toolchain:

- editor symbol resolution and linting
- build task
- deploy task
- debugging

It introduces a cargo-example recipe following the model used for cmake and meson.

The generated configurations can be portable to standalone SDK installations by manually updating the paths.

C/C++ IntelliSense could provide navigation for native code compiled by Cargo build scripts, using the recipe compiler and sysroot. This first series defers it because no mixed-language fixture is available to validate that workflow.

BitBake generates the Cargo configuration consumed by ide-sdk. This series updates the parsed configuration and writes its supported TOML values without adding a new dependency.

I reproduced the selftest coverage expected from other recipe classes, including configuration analysis up to actual debugging symbol resolution.

I largely used AI generation to write this stack. After many iterations, I am finally happy with the code and commit history quality of this series. I also have a documentation commit ready for the manual if this gets approved.

Quoted 8 lines

Any feedback is appreciated. Hope this will help lots of Rust developers working with Yocto distros :).

Cheers,
Enguerrand de Ribaucourt

---

Hi Enguerrand,

Thanks for the v2.

I can see one of the selftest is failing, but only in 1 of the 3 selftest builds. So it might be something not fully deterministic with lldb-server installation:

2026-11-10 10:42:27,794 - oe-selftest - INFO - devtool.DevtoolIdeSdkRustTests.test_code_cargo_qemu_slirp (subunit.RemotedTestCase)
2026-11-10 10:42:27,795 - oe-selftest - INFO -  ... FAIL
...
2026-11-10 10:42:27,795 - oe-selftest - INFO - testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 4503, in test_code_cargo_qemu_slirp
    self._verify_rust_lldb_debugging(tempdir, expected_input=4)
  File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 4545, in _verify_rust_lldb_debugging
    runCmd(ssh_args, output_log=self._cmd_logger)
  File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/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 '['ssh', '-p', '2222', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', 'root@localhost', "/bin/sh -c 'grep -q :04F2 /proc/net/tcp /proc/net/tcp6 2>/dev/null && exit 0; mkdir -p /tmp/lldb_server_1266_usr-bin-cargo-example_multi; cd /tmp/lldb_server_1266_usr-bin-cargo-example_multi; /usr/bin/lldb-server platform --server --listen *:1266 --gdbserver-port 1267 > /tmp/lldb_server_1266_usr-bin-cargo-example_multi/lldb_server.log 2>&1 & _lldb_server_pid=$!; echo $_lldb_server_pid > /tmp/lldb_server_1266_usr-bin-cargo-example_multi/lldb_server.pid; _w=0; while ! grep -q :04F2 /proc/net/tcp /proc/net/tcp6 2>/dev/null; do kill -0 $_lldb_server_pid 2>/dev/null || { cat /tmp/lldb_server_1266_usr-bin-cargo-example_multi/lldb_server.log >&2; echo lldb-server exited before it started listening on port 1266 >&2; exit 1; }; _w=$((_w+1)); [ $_w -lt 600 ] || { kill $_lldb_server_pid 2>/dev/null; echo lldb-server did not start on port 1266 after $_w retries >&2; cat /tmp/lldb_server_1266_usr-bin-cargo-example_multi/lldb_server.log >&2; exit 1; }; sleep 0.1; done;'"]' returned non-zero exit status 1:
Warning: Permanently added '[localhost]:2222' (RSA) to the list of known hosts.
/bin/sh: line 0: /usr/bin/lldb-server: not found
lldb-server exited before it started listening on port 1266

https://autobuilder.yoctoproject.org/valkyrie/#/builders/48/builds/5043

Can you have a look at the issue?

I launched a rebuild of that build, out of curiosity. But in all cases, we should understand what happened here.

Thanks,
Mathieu

Signature · 3 lines
Mathieu Dubois-Briand, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
#21 lore raw

Am Di., 10. Nov. 2026 um 13:25 Uhr schrieb Mathieu Dubois-Briand via openembedded-core <[email protected]

Quoted 100 lines

:

Quoted 14 lines · Enguerrand de Ribaucourt via openembedded-core

Hello, this series introduces support for Rust ide-sdk VS Code configuration against the Yocto cross-compilation toolchain:

- editor symbol resolution and linting
- build task
- deploy task
- debugging

It introduces a cargo-example recipe following the model used for cmake and meson.

The generated configurations can be portable to standalone SDK installations by manually updating the paths.

Quoted 6 lines

C/C++ IntelliSense could provide navigation for native code compiled by Cargo build scripts, using the recipe compiler and sysroot. This first series defers it because no mixed-language fixture is available to validate that workflow.

BitBake generates the Cargo configuration consumed by ide-sdk. This

series

Quoted 5 lines

updates the parsed configuration and writes its supported TOML values without adding a new dependency.

I reproduced the selftest coverage expected from other recipe classes, including configuration analysis up to actual debugging symbol

resolution.

Quoted 3 lines

I largely used AI generation to write this stack. After many iterations, I am finally happy with the code and commit history quality of this

series.

Quoted 10 lines

I also have a documentation commit ready for the manual if this gets approved.

Any feedback is appreciated. Hope this will help lots of Rust developers working with Yocto distros :).

Cheers,
Enguerrand de Ribaucourt

Signed-off-by Enguerrand de Ribaucourt <

[email protected]>

Quoted 1 line

---

Hi Enguerrand,

Thanks for the v2.

I can see one of the selftest is failing, but only in 1 of the 3 selftest builds. So it might be something not fully deterministic with lldb-server installation:

2026-11-10 10:42:27,794 - oe-selftest - INFO -
devtool.DevtoolIdeSdkRustTests.test_code_cargo_qemu_slirp
(subunit.RemotedTestCase)
2026-11-10 10:42:27,795 - oe-selftest - INFO -  ... FAIL
...
2026-11-10 10:42:27,795 - oe-selftest - INFO -

testtools.testresult.real._StringException: Traceback (most recent call
last):
File
"/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 4503, in test_code_cargo_qemu_slirp
self._verify_rust_lldb_debugging(tempdir, expected_input=4)
File
"/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 4545, in _verify_rust_lldb_debugging
runCmd(ssh_args, output_log=self._cmd_logger)
File
"/srv/pokybuild/yocto-worker/oe-selftest-fedora/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 '['ssh', '-p', '2222', '-o',
'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no',
'root@localhost', "/bin/sh -c 'grep -q :04F2 /proc/net/tcp /proc/net/tcp6
2>/dev/null && exit 0; mkdir -p
/tmp/lldb_server_1266_usr-bin-cargo-example_multi; cd /tmp/lldb_server_1266_usr-bin-cargo-example_multi; /usr/bin/lldb-server
platform --server --listen *:1266 --gdbserver-port 1267 > /tmp/lldb_server_1266_usr-bin-cargo-example_multi/lldb_server.log 2>&1 &
_lldb_server_pid=$!; echo $_lldb_server_pid > /tmp/lldb_server_1266_usr-bin-cargo-example_multi/lldb_server.pid; _w=0;
while ! grep -q :04F2 /proc/net/tcp /proc/net/tcp6 2>/dev/null; do kill -0
$_lldb_server_pid 2>/dev/null || { cat /tmp/lldb_server_1266_usr-bin-cargo-example_multi/lldb_server.log >&2; echo
lldb-server exited before it started listening on port 1266 >&2; exit 1; };
_w=$((_w+1)); [ $_w -lt 600 ] || { kill $_lldb_server_pid 2>/dev/null; echo
lldb-server did not start on port 1266 after $_w retries >&2; cat /tmp/lldb_server_1266_usr-bin-cargo-example_multi/lldb_server.log >&2; exit
1; }; sleep 0.1; done;'"]' returned non-zero exit status 1:
Warning: Permanently added '[localhost]:2222' (RSA) to the list of known
hosts.
/bin/sh: line 0: /usr/bin/lldb-server: not found
lldb-server exited before it started listening on port 1266

Quoted 7 lines

https://autobuilder.yoctoproject.org/valkyrie/#/builders/48/builds/5043

Can you have a look at the issue?

I launched a rebuild of that build, out of curiosity. But in all cases, we should understand what happened here.

This can be tricky. The explanation could be that a test

1. devtool ide-sdk adds the lldb-server to the image and builds it
2. The test
1. starts Qemu
2. starts lldb-server in Qemu which goes wrong because lldb-server is not in the image

That can happen if one test instance tries to connect to the wrong runqemu instance (e.g. one with gdb-server installed) respectively the port exposed on the build machine. There is a patch series which tries to fix that. But v1 was rejected because it used Qemu's human readable QMP API instead of the machine readable JSON API. v2 is still WIP
https://git.openembedded.org/openembedded-core-contrib/log/?h=adrianf/runqemu-use-qmp-for-port-alloc.
It requires adding some APIs to Qemu upstream. Not a trivial quick fix. I was also lowering the priority on that because it seemed like working.

Probably this issue is not directly introduced by this patch series. But the more tests we add, the more likely it becomes that two of them run in parallel and fail in that wy.

Regards,
Adrian

Quoted 20 lines

Thanks,
Mathieu

--
Mathieu Dubois-Briand, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

-=-=-=-=-=-=-=-=-=-=-=-
Links: You receive all messages sent to this group. View/Reply Online (#249920):
https://lists.openembedded.org/g/openembedded-core/message/249920
Mute This Topic: https://lists.openembedded.org/mt/122173943/4454582
Group Owner: [email protected]
Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [ [email protected]]
-=-=-=-=-=-=-=-=-=-=-=-