From 8f32c4e04c6f514886448564b6d2a3b1bc1f8434 Mon Sep 17 00:00:00 2001 From: yan hu Date: Tue, 7 Jul 2026 18:54:07 +0800 Subject: [PATCH 1/8] Fix Rust build example configuration handling --- components/rust/core/SConscript | 70 +++++-- .../rust/examples/application/SConscript | 4 +- components/rust/examples/component/SConscript | 5 +- components/rust/examples/modules/SConscript | 92 +++++++-- .../examples/modules/example_lib/Cargo.toml | 2 +- components/rust/tools/build_component.py | 185 ++++++++++++++++-- components/rust/tools/build_support.py | 137 +++++++++---- components/rust/tools/build_usrapp.py | 185 +++++++++++++++--- 8 files changed, 552 insertions(+), 128 deletions(-) diff --git a/components/rust/core/SConscript b/components/rust/core/SConscript index 880f6bce92c..5856b9a3162 100644 --- a/components/rust/core/SConscript +++ b/components/rust/core/SConscript @@ -11,6 +11,7 @@ from build_support import ( verify_rust_toolchain, ensure_rust_target_installed, cargo_build_staticlib, + get_staticlib_link_name, clean_rust_build, ) def _has(sym: str) -> bool: @@ -20,10 +21,27 @@ def _has(sym: str) -> bool: return bool(GetDepend(sym)) -# Source files – MSH command glue -src = ['rust_cmd.c'] +group = [] +if not _has('RT_RUST_CORE'): + Return('group') + + +def get_staticlib_link_name_from_artifact(lib_path): + """Derive the link name from the actual staticlib artifact file name.""" + artifact_name = os.path.basename(os.fspath(lib_path)) + if artifact_name.startswith("lib") and artifact_name.endswith(".a"): + return artifact_name[3:-2] + return None + + +# Source files – MSH command glue. +# rust_cmd.c references rust_init(), which is only provided by the Rust core +# static library. It must only enter the build when that library is actually +# produced; otherwise the C link stage fails with 'undefined reference to +# rust_init' instead of failing/skipping cleanly at the Rust build step. LIBS = [] LIBPATH = [] +include_rust_cmd = False if GetOption('clean'): # Register Rust artifacts for cleaning @@ -33,9 +51,12 @@ if GetOption('clean'): Clean('.', rust_build_dir) else: print('No rust build artifacts to clean') + # Keep rust_cmd.c in the group during clean so its object is cleaned too. + include_rust_cmd = True else: if verify_rust_toolchain(): import rtconfig + rust_build_dir = clean_rust_build(Dir('#').abspath) target = detect_rust_target(_has, rtconfig) if not target: @@ -44,28 +65,39 @@ else: print(f'Detected Rust target: {target}') # Optional hint if target missing - ensure_rust_target_installed(target) - - # Build mode and features - debug = bool(_has('RUST_DEBUG_BUILD')) - features = collect_features(_has) + if not ensure_rust_target_installed(target): + print('Warning: Rust target is not installed; skipping Rust library build') + else: + # Build mode and features + debug = bool(_has('RUST_DEBUG_BUILD')) + features = collect_features(_has) - rustflags = make_rustflags(rtconfig, target) - rust_lib = cargo_build_staticlib( - rust_dir=cwd, target=target, features=features, debug=debug, rustflags=rustflags - ) + rustflags = make_rustflags(rtconfig, target) + rust_lib = cargo_build_staticlib( + rust_dir=cwd, target=target, features=features, debug=debug, rustflags=rustflags, build_root=rust_build_dir + ) - if rust_lib: - LIBS = ['rt_rust'] - LIBPATH = [os.path.dirname(rust_lib)] - print('Rust library linked successfully') - else: - print('Warning: Failed to build Rust library') + if rust_lib: + # Derive the link name from the actual artifact so it always + # matches the file that was built. Only fall back to + # re-parsing Cargo.toml when the artifact name does not + # follow the lib.a convention. + link_lib_name = get_staticlib_link_name_from_artifact(rust_lib) + if not link_lib_name: + link_lib_name = get_staticlib_link_name(cwd) + LIBS = [link_lib_name] + LIBPATH = [os.path.dirname(rust_lib)] + include_rust_cmd = True + print('Rust library linked successfully') + else: + print('Warning: Failed to build Rust library') else: print('Warning: Rust toolchain not found') print('Please install Rust from https://rustup.rs') -# Define component group for SCons -group = DefineGroup('rust', src, depend=['RT_USING_RUST'], LIBS=LIBS, LIBPATH=LIBPATH) +# Only define the component group (with rust_cmd.c) when the Rust core static +# library was actually produced, so its rust_init() reference always resolves. +if include_rust_cmd: + group = DefineGroup('rust', ['rust_cmd.c'], depend=['RT_USING_RUST', 'RT_RUST_CORE'], LIBS=LIBS, LIBPATH=LIBPATH) Return('group') diff --git a/components/rust/examples/application/SConscript b/components/rust/examples/application/SConscript index c6c4de3db83..930d0f21cd1 100644 --- a/components/rust/examples/application/SConscript +++ b/components/rust/examples/application/SConscript @@ -31,7 +31,7 @@ def load_extended_feature_configs(): group = [] -if not _has('RT_RUST_BUILD_APPLICATIONS'): +if not (_has('RT_RUST_BUILD_APPLICATIONS') or _has('RT_RUST_BUILD_ALL_EXAMPLES')): Return('group') # Load extended feature configurations @@ -67,4 +67,4 @@ group = DefineGroup( LINKFLAGS=LINKFLAGS ) -Return('group') \ No newline at end of file +Return('group') diff --git a/components/rust/examples/component/SConscript b/components/rust/examples/component/SConscript index 40d11c04469..056173ce505 100644 --- a/components/rust/examples/component/SConscript +++ b/components/rust/examples/component/SConscript @@ -25,12 +25,11 @@ def _has(sym: str) -> bool: return bool(GetDepend(sym)) -# Early return if Rust or log component is not enabled if not _has('RT_USING_RUST'): group = [] Return('group') -if not _has('RUST_LOG_COMPONENT'): +if not (_has('RT_RUST_BUILD_COMPONENTS') or _has('RT_RUST_BUILD_ALL_EXAMPLES')): group = [] Return('group') @@ -68,4 +67,4 @@ group = DefineGroup( LINKFLAGS=LINKFLAGS ) -Return('group') \ No newline at end of file +Return('group') diff --git a/components/rust/examples/modules/SConscript b/components/rust/examples/modules/SConscript index 98e57d1fd9c..7256daa3ff8 100644 --- a/components/rust/examples/modules/SConscript +++ b/components/rust/examples/modules/SConscript @@ -1,7 +1,6 @@ import os import sys import subprocess -import toml from building import * cwd = GetCurrentDir() @@ -25,6 +24,10 @@ sys.path.insert(0, tools_dir) from build_support import detect_rust_target, ensure_rust_target_installed, clean_rust_build +MODULE_CONFIG_MAP = { + 'example_lib': 'RT_RUST_MODULE_SIMPLE_MODULE', +} + def _has(sym: str) -> bool: """Helper function to check if a configuration symbol is enabled""" try: @@ -32,6 +35,16 @@ def _has(sym: str) -> bool: except Exception: return bool(GetDepend(sym)) +def should_build_module(module_dir): + if _has('RT_RUST_BUILD_ALL_EXAMPLES'): + return True + + config = MODULE_CONFIG_MAP.get(os.path.basename(module_dir)) + if config: + return _has(config) + + return True + def detect_target_for_dynamic_modules(): """ Detect the appropriate Rust target for dynamic modules. @@ -58,19 +71,43 @@ def detect_target_for_dynamic_modules(): print("Error: Unable to detect appropriate Rust target for dynamic modules") raise RuntimeError("Target detection failed - no valid target found") +def _decode_cargo_output(output): + """Decode cargo stdout/stderr which may be bytes or str.""" + if output is None: + return "" + if isinstance(output, bytes): + return output.decode(errors="replace") + return str(output) + def build_rust_module(module_dir, build_root): """Build a Rust dynamic module with automatic target detection""" cargo_toml_path = os.path.join(module_dir, 'Cargo.toml') if not os.path.exists(cargo_toml_path): return [], [], "" + + try: + import toml + except ImportError: + print(f"Warning: Failed to parse {cargo_toml_path}: missing toml module") + return [], [], "" - with open(cargo_toml_path, 'r') as f: - cargo_config = toml.load(f) - - module_name = cargo_config['package']['name'] + try: + with open(cargo_toml_path, 'r') as f: + cargo_config = toml.load(f) + module_name = cargo_config['package']['name'] + lib_name = cargo_config.get('lib', {}).get('name') or module_name + link_lib_name = lib_name.replace('-', '_') + except Exception as e: + print(f"Warning: Failed to parse module metadata from {cargo_toml_path}: {e}") + return [], [], "" # Detect target automatically based on the current configuration - target = detect_target_for_dynamic_modules() + try: + target = detect_target_for_dynamic_modules() + except RuntimeError as e: + print(f"Warning: Failed to detect Rust target for module '{module_name}': {e}") + return [], [], "" + print(f"Building Rust module '{module_name}' for target: {target}") # Detect debug mode from rtconfig (same as main rust/SConscript) @@ -92,7 +129,14 @@ def build_rust_module(module_dir, build_root): # Set up build environment env = os.environ.copy() - env['RUSTFLAGS'] = rustflags + previous_rustflags = env.get('RUSTFLAGS', '').strip() + new_rustflags = rustflags.strip() if rustflags else '' + if previous_rustflags and new_rustflags: + env['RUSTFLAGS'] = f'{previous_rustflags} {new_rustflags}' + elif new_rustflags: + env['RUSTFLAGS'] = new_rustflags + elif previous_rustflags: + env['RUSTFLAGS'] = previous_rustflags env['CARGO_TARGET_DIR'] = build_root # Build the module with configurable parameters using dictionary configuration @@ -116,12 +160,30 @@ def build_rust_module(module_dir, build_root): try: subprocess.run(build_cmd, cwd=module_dir, env=env, check=True, capture_output=True) lib_dir = os.path.join(build_root, target, build_mode) - return [module_name], [lib_dir], "" - except subprocess.CalledProcessError: + artifact_name = f"lib{link_lib_name}.so" + artifact_path = os.path.join(lib_dir, artifact_name) + if not os.path.isfile(artifact_path) or os.path.getsize(artifact_path) == 0: + print(f"Warning: Rust module artifact not found, is not a file, or is empty: {artifact_path}") + return [], [], "" + return [link_lib_name], [lib_dir], "" + except FileNotFoundError: + print("Error: cargo executable not found. Please install Rust/Cargo and ensure it is in PATH.") + return [], [], "" + except subprocess.CalledProcessError as e: + print(f"Error: Failed to build Rust module: {e}") + stdout = _decode_cargo_output(getattr(e, "stdout", None) or getattr(e, "output", None)).strip() + stderr = _decode_cargo_output(getattr(e, "stderr", None)).strip() + if stdout: + print(stdout) + if stderr: + print(stderr) return [], [], "" # Check dependencies -if not _has('RT_RUST_BUILD_MODULES'): +if not _has('RT_USING_MODULE'): + Return([]) + +if not (_has('RT_RUST_BUILD_MODULES') or _has('RT_RUST_BUILD_ALL_EXAMPLES')): Return([]) build_root = os.path.join(Dir('#').abspath, "build", "rust_modules") @@ -142,7 +204,7 @@ else: modules_built = [] for item in os.listdir(cwd): item_path = os.path.join(cwd, item) - if os.path.isdir(item_path) and os.path.exists(os.path.join(item_path, 'Cargo.toml')): + if os.path.isdir(item_path) and os.path.exists(os.path.join(item_path, 'Cargo.toml')) and should_build_module(item_path): result = build_rust_module(item_path, build_root) if result[0]: modules_built.extend(result[0]) @@ -151,9 +213,9 @@ else: print(f"Successfully built {len(modules_built)} Rust dynamic module(s): {', '.join(modules_built)}") group = DefineGroup( - 'rust_modules', - [], - depend=['RT_RUST_BUILD_MODULES'] + 'rust_modules', + [], + depend=['RT_USING_MODULE'] ) -Return('group') \ No newline at end of file +Return('group') diff --git a/components/rust/examples/modules/example_lib/Cargo.toml b/components/rust/examples/modules/example_lib/Cargo.toml index e44aae28488..aa047064117 100644 --- a/components/rust/examples/modules/example_lib/Cargo.toml +++ b/components/rust/examples/modules/example_lib/Cargo.toml @@ -9,4 +9,4 @@ edition = "2024" crate-type = ["cdylib"] [dependencies] -rt-rust = { path = "../../.." } \ No newline at end of file +rt-rust = { path = "../../../core" } diff --git a/components/rust/tools/build_component.py b/components/rust/tools/build_component.py index 8b604f68b83..94b0e097552 100644 --- a/components/rust/tools/build_component.py +++ b/components/rust/tools/build_component.py @@ -5,13 +5,104 @@ # Configuration to feature mapping table for components # This table defines which RT-Thread configurations should enable which component features # All feature configurations are now defined in feature_config_component.py -CONFIG_COMPONENT_FEATURE_MAP = {} +CONFIG_COMPONENT_FEATURE_MAP = { + 'RUST_LOG_COMPONENT': { + 'feature': 'enable-log', + 'dependencies': ['em_component_log'], + 'description': 'Enable Rust logging component integration' + }, +} class ComponentBuildError(Exception): pass +def normalize_component_build_root(build_root, cwd): + """ + Normalize the component build root directory. + + Args: + build_root: Optional build root directory + cwd: Current working directory (component directory) + + Returns: + str: Absolute build root path + """ + if build_root is None: + if not cwd: + raise ComponentBuildError("Invalid build_root: cwd is required when build_root is None") + build_root = os.path.join(cwd, "build", "rust", "component") + + try: + build_root = os.fspath(build_root) + except TypeError: + raise ComponentBuildError("Invalid build_root: expected a non-empty path") + + if not isinstance(build_root, str) or not build_root: + raise ComponentBuildError("Invalid build_root: expected a non-empty path") + + return os.path.abspath(build_root) + + +def get_component_staticlib_artifact_name(rust_dir): + cargo_toml_path = os.path.join(rust_dir, "Cargo.toml") + lib_name = "em_component_registry" + + try: + import toml + with open(cargo_toml_path, "r") as f: + cargo_data = toml.load(f) + + package_name = cargo_data.get("package", {}).get("name") + lib_name = cargo_data.get("lib", {}).get("name") or package_name or lib_name + except Exception as e: + print(f"Warning: Failed to parse Rust component static library metadata from {cargo_toml_path}: {e}") + + if not isinstance(lib_name, str) or not lib_name: + lib_name = "em_component_registry" + + lib_name = lib_name.replace("-", "_") + return f"lib{lib_name}.a" + + +def get_component_staticlib_link_name(rust_dir): + cargo_toml_path = os.path.join(rust_dir, "Cargo.toml") + lib_name = "em_component_registry" + + try: + import toml + with open(cargo_toml_path, "r") as f: + cargo_data = toml.load(f) + + package_name = cargo_data.get("package", {}).get("name") + lib_name = cargo_data.get("lib", {}).get("name") or package_name or lib_name + except Exception as e: + print(f"Warning: Failed to parse Rust component static library metadata from {cargo_toml_path}: {e}") + + if not isinstance(lib_name, str) or not lib_name: + lib_name = "em_component_registry" + + return lib_name.replace("-", "_") + + +def get_staticlib_link_name_from_artifact(lib_path): + """ + Derive the linker library name from the actual staticlib artifact file name. + + Args: + lib_path: Path to the built staticlib artifact + + Returns: + str: Link name (artifact name without the leading 'lib' and trailing + '.a'), or None if the artifact does not follow that convention. + """ + artifact_name = os.path.basename(os.fspath(lib_path)) + if artifact_name.startswith("lib") and artifact_name.endswith(".a"): + return artifact_name[3:-2] + return None + + def check_component_dependencies(component_dir, required_dependencies): """ Check if a component has the required dependencies @@ -65,7 +156,7 @@ def collect_component_features(has_func, component_dir=None): # Iterate through all configured mappings for config_name, config_info in CONFIG_COMPONENT_FEATURE_MAP.items(): # Check if this RT-Thread configuration is enabled - if has_func(config_name): + if has_func(config_name) or has_func('RT_RUST_BUILD_ALL_EXAMPLES'): feature_name = config_info['feature'] required_deps = config_info.get('dependencies', []) @@ -81,6 +172,29 @@ def collect_component_features(has_func, component_dir=None): return features + +def declared_component_features(component_dir): + cargo_toml_path = os.path.join(component_dir, 'Cargo.toml') + if not os.path.exists(cargo_toml_path): + return None + + try: + import toml + with open(cargo_toml_path, 'r') as f: + cargo_data = toml.load(f) + return set(cargo_data.get('features', {}).keys()) + except Exception as e: + print(f"Warning: Failed to parse component features from {cargo_toml_path}: {e}") + return None + + +def filter_declared_component_features(component_dir, features): + declared_features = declared_component_features(component_dir) + if declared_features is None: + return list(features) + return [feature for feature in features if feature in declared_features] + + def cargo_build_component_staticlib(rust_dir, target, features, debug, rustflags=None, build_root=None): """ Build a Rust component as a static library using Cargo. @@ -91,15 +205,12 @@ def cargo_build_component_staticlib(rust_dir, target, features, debug, rustflags features: List of features to enable debug: Whether this is a debug build rustflags: Additional Rust compilation flags - build_root: Build root directory (if not provided, will raise error) + build_root: Build root directory Returns: str: Path to the built library file, or None if build failed """ - if not build_root: - raise ComponentBuildError("build_root parameter is required") - - build_root = os.path.abspath(build_root) + build_root = normalize_component_build_root(build_root, None) os.makedirs(build_root, exist_ok=True) env = os.environ.copy() @@ -121,7 +232,11 @@ def cargo_build_component_staticlib(rust_dir, target, features, debug, rustflags cmd += ["--no-default-features", "--features", ",".join(features)] print("Building example component log (cargo)…") - res = subprocess.run(cmd, cwd=rust_dir, env=env, capture_output=True, text=True) + try: + res = subprocess.run(cmd, cwd=rust_dir, env=env, capture_output=True, text=True) + except FileNotFoundError: + print("Error: cargo executable not found. Please install Rust/Cargo and ensure it is in PATH.") + return None if res.returncode != 0: print("Warning: Example component build failed") @@ -132,12 +247,13 @@ def cargo_build_component_staticlib(rust_dir, target, features, debug, rustflags mode = "debug" if debug else "release" # Try target-specific path first, then fallback to direct path - lib_path = os.path.join(build_root, target, mode, "libem_component_registry.a") - if os.path.exists(lib_path): + artifact_name = get_component_staticlib_artifact_name(rust_dir) + lib_path = os.path.join(build_root, target, mode, artifact_name) + if os.path.isfile(lib_path) and os.path.getsize(lib_path) > 0: print("Example component log built successfully") return lib_path - print("Warning: Library not found at expected location") + print("Warning: Rust component static library artifact not found, is not a file, or is empty") print(f"Expected: {lib_path}") return None @@ -157,26 +273,39 @@ def build_example_component(cwd, has_func, rtconfig, build_root=None): """ LIBS = [] LIBPATH = [] - LINKFLAGS = "" - + LINKFLAGS = [] + # Import build support functions import sys - sys.path.append(os.path.join(cwd, '../rust/tools')) + tools_dir = os.path.abspath(os.path.join(cwd, '..', '..', 'tools')) + if tools_dir not in sys.path: + sys.path.append(tools_dir) from build_support import ( detect_rust_target, + ensure_rust_target_installed, + collect_features, make_rustflags, ) target = detect_rust_target(has_func, rtconfig) + if not target: + print('Warning: Could not detect Rust target for example component build') + return LIBS, LIBPATH, LINKFLAGS # Build mode and features debug = bool(has_func('RUST_DEBUG_BUILD')) + features = collect_features(has_func) # Build the component registry registry_dir = os.path.join(cwd, 'component_registry') - features = collect_component_features(has_func, registry_dir) + features += collect_component_features(has_func, registry_dir) + features = filter_declared_component_features(registry_dir, features) rustflags = make_rustflags(rtconfig, target) + build_root = normalize_component_build_root(build_root, cwd) + if not ensure_rust_target_installed(target): + print('Warning: Rust target is not installed; skipping example component build') + return LIBS, LIBPATH, LINKFLAGS rust_lib = cargo_build_component_staticlib( rust_dir=registry_dir, @@ -188,13 +317,27 @@ def build_example_component(cwd, has_func, rtconfig, build_root=None): ) if rust_lib: - LIBS = ['em_component_registry'] - LIBPATH = [os.path.dirname(rust_lib)] - # Add LINKFLAGS to ensure component is linked into final binary - LINKFLAGS += " -Wl,--whole-archive -lem_component_registry -Wl,--no-whole-archive" - LINKFLAGS += " -Wl,--allow-multiple-definition" + lib_dir = os.path.dirname(rust_lib) + # Derive the link name from the actual artifact so it always matches + # the file that was built. Only fall back to re-parsing Cargo.toml when + # the artifact name does not follow the lib.a convention. + link_lib_name = get_staticlib_link_name_from_artifact(rust_lib) + if not link_lib_name: + link_lib_name = get_component_staticlib_link_name(registry_dir) + LIBS = [link_lib_name] + LIBPATH = [lib_dir] + # Add LINKFLAGS to ensure component is linked into final binary. + # Use list tokens so each flag stays a single argument; -L + # must precede -l and remain intact even when lib_dir has spaces. + LINKFLAGS = [ + f"-L{os.fspath(lib_dir)}", + "-Wl,--whole-archive", + f"-l{link_lib_name}", + "-Wl,--no-whole-archive", + "-Wl,--allow-multiple-definition", + ] print('Example component registry library linked successfully') else: print('Warning: Failed to build example component registry library') - return LIBS, LIBPATH, LINKFLAGS \ No newline at end of file + return LIBS, LIBPATH, LINKFLAGS diff --git a/components/rust/tools/build_support.py b/components/rust/tools/build_support.py index eb05fc1f299..481911fcb74 100644 --- a/components/rust/tools/build_support.py +++ b/components/rust/tools/build_support.py @@ -48,13 +48,19 @@ def detect_rust_target(has, rtconfig): cflags = getattr(rtconfig, "CFLAGS", "") hard_float = "-mfloat-abi=hard" in cflags or has("ARCH_ARM_FPU") or has("ARCH_FPU_VFP") + if has("ARCH_ARM_CORTEX_M0") or has("ARCH_ARM_CORTEX_M0PLUS"): + return "thumbv6m-none-eabi" if has("ARCH_ARM_CORTEX_M3"): return "thumbv7m-none-eabi" if has("ARCH_ARM_CORTEX_M4") or has("ARCH_ARM_CORTEX_M7"): return "thumbv7em-none-eabihf" if hard_float else "thumbv7em-none-eabi" + if has("ARCH_ARM_CORTEX_M23"): + return "thumbv8m.base-none-eabi" if has("ARCH_ARM_CORTEX_M33"): # v8m.main return "thumbv8m.main-none-eabi" + if has("ARCH_ARM_CORTEX_M85"): + return "thumbv8m.main-none-eabi" if has("ARCH_ARM_CORTEX_A"): return "armv7a-none-eabi" @@ -93,8 +99,17 @@ def detect_rust_target(has, rtconfig): return "armv7a-none-eabi" if "riscv32" in arch_l: return "riscv32imac-unknown-none-elf" - if "riscv64" in arch_l or "risc-v" in arch_l: - # Many BSPs use "risc-v" token; assume 64-bit for virt64 + if "riscv64" in arch_l: + return "riscv64imac-unknown-none-elf" + if "risc-v" in arch_l: + info = _parse_cflags(getattr(rtconfig, "CFLAGS", "")) + abi = info["mabi"] or "" + abi_has_fp = abi.endswith("f") or abi.endswith("d") + if info["rv_bits"] == 32: + return "riscv32imafc-unknown-none-elf" if abi_has_fp else "riscv32imac-unknown-none-elf" + if info["rv_bits"] == 64: + return "riscv64gc-unknown-none-elf" if abi_has_fp else "riscv64imac-unknown-none-elf" + # Many BSPs use "risc-v" token; assume 64-bit for virt64 when CFLAGS do not specify width return "riscv64imac-unknown-none-elf" # Parse CFLAGS for hints @@ -106,35 +121,15 @@ def detect_rust_target(has, rtconfig): return "thumbv7em-none-eabihf" return "thumbv7em-none-eabi" if "-march=rv32" in cflags: - march_val = None - mabi_val = None - for flag in cflags.split(): - if flag.startswith("-march="): - march_val = flag[len("-march="):] - elif flag.startswith("-mabi="): - mabi_val = flag[len("-mabi="):] - has_f_or_d = False - if march_val and any(x in march_val for x in ("f", "d")): - has_f_or_d = True - if mabi_val and any(x in mabi_val for x in ("f", "d")): - has_f_or_d = True - return "riscv32imafc-unknown-none-elf" if has_f_or_d else "riscv32imac-unknown-none-elf" + info = _parse_cflags(cflags) + abi = info["mabi"] or "" + abi_has_fp = abi.endswith("f") or abi.endswith("d") + return "riscv32imafc-unknown-none-elf" if abi_has_fp else "riscv32imac-unknown-none-elf" if "-march=rv64" in cflags: - march_val = None - mabi_val = None - for flag in cflags.split(): - if flag.startswith("-march="): - march_val = flag[len("-march="):] - elif flag.startswith("-mabi="): - mabi_val = flag[len("-mabi="):] - has_f_or_d = False - if mabi_val and (("lp64d" in mabi_val) or ("lp64f" in mabi_val)): - has_f_or_d = True - if march_val and any(x in march_val for x in ("f", "d")): - has_f_or_d = True - if mabi_val and any(x in mabi_val for x in ("f", "d")): - has_f_or_d = True - if has_f_or_d: + info = _parse_cflags(cflags) + abi = info["mabi"] or "" + abi_has_fp = abi.endswith("f") or abi.endswith("d") + if abi_has_fp: return "riscv64gc-unknown-none-elf" return "riscv64imac-unknown-none-elf" @@ -142,6 +137,14 @@ def detect_rust_target(has, rtconfig): def make_rustflags(rtconfig, target: str): + if not isinstance(target, str) or not target: + arch = getattr(rtconfig, "ARCH", None) + cflags = getattr(rtconfig, "CFLAGS", "") + raise ValueError( + "Unsupported Rust target: unable to detect a Rust target " + f"for ARCH={arch!r}, CFLAGS={cflags!r}" + ) + rustflags = [ "-C", "opt-level=z", "-C", "panic=abort", @@ -181,10 +184,60 @@ def verify_rust_toolchain(): return False +def parse_installed_rust_targets(output): + return {line.strip() for line in output.splitlines() if line.strip()} + + +def get_staticlib_artifact_name(rust_dir): + cargo_toml_path = os.path.join(rust_dir, "Cargo.toml") + lib_name = "rt_rust" + + try: + import toml + with open(cargo_toml_path, "r") as f: + cargo_data = toml.load(f) + + package_name = cargo_data.get("package", {}).get("name") + lib_name = cargo_data.get("lib", {}).get("name") or package_name or lib_name + except Exception as e: + print(f"Warning: Failed to parse Rust static library metadata from {cargo_toml_path}: {e}") + + if not isinstance(lib_name, str) or not lib_name: + lib_name = "rt_rust" + + lib_name = lib_name.replace("-", "_") + return f"lib{lib_name}.a" + + +def get_staticlib_link_name(rust_dir): + cargo_toml_path = os.path.join(rust_dir, "Cargo.toml") + lib_name = "rt_rust" + + try: + import toml + with open(cargo_toml_path, "r") as f: + cargo_data = toml.load(f) + + package_name = cargo_data.get("package", {}).get("name") + lib_name = cargo_data.get("lib", {}).get("name") or package_name or lib_name + except Exception as e: + print(f"Warning: Failed to parse Rust static library metadata from {cargo_toml_path}: {e}") + + if not isinstance(lib_name, str) or not lib_name: + lib_name = "rt_rust" + + return lib_name.replace("-", "_") + + def ensure_rust_target_installed(target: str): + if not isinstance(target, str) or not target: + print("Invalid Rust target: expected a non-empty string") + return False + try: result = subprocess.run(["rustup", "target", "list", "--installed"], capture_output=True, text=True) - if result.returncode == 0 and target in result.stdout: + installed_targets = parse_installed_rust_targets(result.stdout) + if result.returncode == 0 and target in installed_targets: return True print(f"Rust target '{target}' is not installed.") print(f"Please install it with: rustup target add {target}") @@ -193,8 +246,11 @@ def ensure_rust_target_installed(target: str): return False -def cargo_build_staticlib(rust_dir: str, target: str, features, debug: bool, rustflags: str = None): - build_root = os.path.join((os.path.abspath(os.path.join(rust_dir, os.pardir, os.pardir))), "build", "rust") +def cargo_build_staticlib(rust_dir: str, target: str, features, debug: bool, rustflags: str = None, build_root: str = None): + if build_root is None: + build_root = os.path.join((os.path.abspath(os.path.join(rust_dir, os.pardir, os.pardir))), "build", "rust") + else: + build_root = os.path.abspath(os.fspath(build_root)) target_dir = os.path.join(build_root, "target") os.makedirs(build_root, exist_ok=True) @@ -211,7 +267,11 @@ def cargo_build_staticlib(rust_dir: str, target: str, features, debug: bool, rus cmd += ["--no-default-features", "--features", ",".join(features)] print("Building Rust component (cargo)…") - res = subprocess.run(cmd, cwd=rust_dir, env=env, capture_output=True, text=True) + try: + res = subprocess.run(cmd, cwd=rust_dir, env=env, capture_output=True, text=True) + except FileNotFoundError: + print("Error: cargo executable not found. Please install Rust/Cargo and ensure it is in PATH.") + return None if res.returncode != 0: print("Warning: Rust build failed") if res.stderr: @@ -219,15 +279,16 @@ def cargo_build_staticlib(rust_dir: str, target: str, features, debug: bool, rus return None mode = "debug" if debug else "release" - lib_path = os.path.join(target_dir, target, mode, "librt_rust.a") - if os.path.exists(lib_path): + artifact_name = get_staticlib_artifact_name(rust_dir) + lib_path = os.path.join(target_dir, target, mode, artifact_name) + if os.path.isfile(lib_path) and os.path.getsize(lib_path) > 0: print("Rust component built successfully") return lib_path - print("Warning: Library not found at expected location") + print(f"Warning: Rust static library artifact not found, is not a file, or is empty: {lib_path}") return None def clean_rust_build(bsp_root: str, artifact_type: str = "rust"): """Return the build directory path for SCons Clean operation""" build_dir = os.path.join(bsp_root, "build", artifact_type) - return build_dir \ No newline at end of file + return build_dir diff --git a/components/rust/tools/build_usrapp.py b/components/rust/tools/build_usrapp.py index 9d97a60ffeb..6e0637a581b 100644 --- a/components/rust/tools/build_usrapp.py +++ b/components/rust/tools/build_usrapp.py @@ -1,6 +1,5 @@ import os import subprocess -import toml import shutil @@ -21,6 +20,25 @@ 'thread': 'RT_RUST_EXAMPLE_THREAD' } +APP_DEPENDENCY_MAP = { + 'loadlib': ['RT_USING_MODULE'], +} + + +def app_dependencies_satisfied(app_name, has_func): + if app_name == 'fs': + if has_func('RT_USING_POSIX_FS'): + return True + if not has_func('RT_USING_DFS'): + return False + return has_func('DFS_USING_POSIX') or has_func('RT_USING_DFS_V2') + + for dep in APP_DEPENDENCY_MAP.get(app_name, []): + if not has_func(dep): + return False + + return True + def should_build_app(app_dir, has_func): """ @@ -35,6 +53,12 @@ def should_build_app(app_dir, has_func): """ # Get the application name from the directory app_name = os.path.basename(app_dir) + + if not app_dependencies_satisfied(app_name, has_func): + return False + + if has_func('RT_RUST_BUILD_ALL_EXAMPLES'): + return True # Check if there's a specific Kconfig option for this app if app_name in APP_CONFIG_MAP: @@ -64,6 +88,7 @@ def check_app_dependencies(app_dir, required_dependencies): return False try: + toml = load_toml_module() with open(cargo_toml_path, 'r') as f: cargo_data = toml.load(f) @@ -81,6 +106,25 @@ def check_app_dependencies(app_dir, required_dependencies): return False +def app_has_feature_dependency(app_dir, feature_name, dependency_name): + cargo_toml_path = os.path.join(app_dir, 'Cargo.toml') + if not os.path.exists(cargo_toml_path): + return False + + try: + toml = load_toml_module() + with open(cargo_toml_path, 'r') as f: + cargo_data = toml.load(f) + + features = cargo_data.get('features', {}) + dependencies = cargo_data.get('dependencies', {}) + return feature_name in features and dependency_name in dependencies + + except Exception as e: + print(f"Warning: Failed to parse {cargo_toml_path}: {e}") + return False + + def collect_features(has_func, app_dir=None): """ Collect Rust features based on RT-Thread configuration using extensible mapping table @@ -110,6 +154,10 @@ def collect_features(has_func, app_dir=None): # If no app_dir provided, enable for all (backward compatibility) features.append(feature_name) print(f"Enabling feature '{feature_name}' for {config_name}") + + if app_dir and os.path.basename(app_dir) == 'fs': + if app_has_feature_dependency(app_dir, 'enable-log', 'em_component_log') and 'enable-log' not in features: + features.append('enable-log') return features @@ -122,6 +170,41 @@ class UserAppBuildError(Exception): pass +def load_toml_module(): + try: + import toml + return toml + except ImportError as e: + raise UserAppBuildError("Missing toml module required to parse Cargo.toml") from e + + +def normalize_build_root(build_root, cwd): + """ + Normalize the user application build root directory. + + Args: + build_root: Optional build root directory + cwd: Current working directory (usrapp directory) + + Returns: + str: Absolute build root path + """ + if build_root is None: + if not cwd: + raise UserAppBuildError("Invalid build_root: cwd is required when build_root is None") + build_root = os.path.join(cwd, "build", "rust", "usrapp") + + try: + build_root = os.fspath(build_root) + except TypeError: + raise UserAppBuildError("Invalid build_root: expected a non-empty path") + + if not isinstance(build_root, str) or not build_root: + raise UserAppBuildError("Invalid build_root: expected a non-empty path") + + return os.path.abspath(build_root) + + def parse_cargo_toml(cargo_toml_path): """ Parse Cargo.toml file to extract library name and library type @@ -133,6 +216,7 @@ def parse_cargo_toml(cargo_toml_path): tuple: (lib_name, is_staticlib) """ try: + toml = load_toml_module() with open(cargo_toml_path, 'r') as f: cargo_data = toml.load(f) @@ -166,14 +250,23 @@ def discover_user_apps(base_dir): user_apps = [] for root, dirs, files in os.walk(base_dir): + dirs[:] = [d for d in dirs if d not in ("build", "target")] if 'Cargo.toml' in files: - if 'target' in root or 'build' in root: - continue user_apps.append(root) return user_apps +def staticlib_candidates(lib_name): + normalized_name = lib_name.replace('-', '_') + candidates = [(f"lib{normalized_name}.a", normalized_name)] + + if normalized_name != lib_name: + candidates.append((f"lib{lib_name}.a", lib_name)) + + return candidates + + def build_user_app(app_dir, target, debug, rustflags, build_root, features=None): """ Build a single user application @@ -189,6 +282,8 @@ def build_user_app(app_dir, target, debug, rustflags, build_root, features=None) Returns: tuple: (success, lib_name, lib_path) """ + build_root = normalize_build_root(build_root, None) + try: cargo_toml_path = os.path.join(app_dir, 'Cargo.toml') lib_name, is_staticlib = parse_cargo_toml(cargo_toml_path) @@ -197,7 +292,14 @@ def build_user_app(app_dir, target, debug, rustflags, build_root, features=None) return False, None, None env = os.environ.copy() - env['RUSTFLAGS'] = rustflags + previous_rustflags = env.get('RUSTFLAGS', '').strip() + new_rustflags = rustflags.strip() if rustflags else '' + if previous_rustflags and new_rustflags: + env['RUSTFLAGS'] = f'{previous_rustflags} {new_rustflags}' + elif new_rustflags: + env['RUSTFLAGS'] = new_rustflags + elif previous_rustflags: + env['RUSTFLAGS'] = previous_rustflags env['CARGO_TARGET_DIR'] = build_root cmd = ['cargo', 'build', '--target', target] @@ -209,8 +311,12 @@ def build_user_app(app_dir, target, debug, rustflags, build_root, features=None) cmd.extend(['--features', ','.join(features)]) print(f"Building example user app {lib_name} (cargo)…") - result = subprocess.run(cmd, cwd=app_dir, env=env, - capture_output=True, text=True) + try: + result = subprocess.run(cmd, cwd=app_dir, env=env, + capture_output=True, text=True) + except FileNotFoundError: + print("Error: cargo executable not found. Please install Rust/Cargo and ensure it is in PATH.") + return False, None, None if result.returncode != 0: print(f"Failed to build user app in {app_dir}") @@ -220,10 +326,10 @@ def build_user_app(app_dir, target, debug, rustflags, build_root, features=None) print(f"STDERR: {result.stderr}") return False, None, None - lib_file = find_library_file(build_root, target, lib_name, debug) + lib_file, link_lib_name = find_library_file(build_root, target, lib_name, debug) if lib_file: # Return the library name for linking - return True, lib_name, lib_file + return True, link_lib_name, lib_file else: print(f"Library file not found for lib {lib_name}") return False, None, None @@ -244,15 +350,11 @@ def find_library_file(build_root, target, lib_name, debug): debug: Whether this is a debug build Returns: - str: Library file path, or None if not found + tuple: (library file path, link library name), or (None, None) if not found """ + build_root = normalize_build_root(build_root, None) profile = "debug" if debug else "release" - - possible_names = [ - f"lib{lib_name}.a", - f"lib{lib_name.replace('-', '_')}.a" - ] - + search_paths = [ os.path.join(build_root, target, profile), os.path.join(build_root, target, profile, "deps") @@ -262,12 +364,14 @@ def find_library_file(build_root, target, lib_name, debug): if not os.path.exists(search_path): continue - for name in possible_names: + for name, link_lib_name in staticlib_candidates(lib_name): lib_path = os.path.join(search_path, name) if os.path.exists(lib_path): - return lib_path + if os.path.isfile(lib_path) and os.path.getsize(lib_path) > 0: + return lib_path, link_lib_name + print(f"Warning: Rust user app static library artifact not found, is not a file, or is empty: {lib_path}") - return None + return None, None def build_all_user_apps(base_dir, target, debug, rustflags, build_root, has_func): @@ -288,9 +392,9 @@ def build_all_user_apps(base_dir, target, debug, rustflags, build_root, has_func LIBS = [] LIBPATH = [] success_count = 0 + total_count = 0 user_apps = discover_user_apps(base_dir) - total_count = len(user_apps) for app_dir in user_apps: # Check if this application should be built based on Kconfig @@ -298,6 +402,8 @@ def build_all_user_apps(base_dir, target, debug, rustflags, build_root, has_func app_name = os.path.basename(app_dir) print(f"Skipping {app_name} (disabled in Kconfig)") continue + + total_count += 1 # Collect features for this specific app features = collect_features(has_func, app_dir) @@ -329,10 +435,16 @@ def generate_linkflags(LIBS, LIBPATH): if not LIBS or not LIBPATH: return "" - linkflags = f" -L{LIBPATH[0]} -Wl,--whole-archive" + linkflags = [] + for path in LIBPATH: + linkflags.append(f"-L{os.fspath(path)}") + linkflags.append("-Wl,--whole-archive") for lib in LIBS: - linkflags += f" -l{lib}" - linkflags += " -Wl,--no-whole-archive -Wl,--allow-multiple-definition" + linkflags.append(f"-l{lib}") + linkflags.extend([ + "-Wl,--no-whole-archive", + "-Wl,--allow-multiple-definition", + ]) return linkflags @@ -368,25 +480,40 @@ def build_example_usrapp(cwd, has_func, rtconfig, build_root=None): try: # Import build support functions import sys - sys.path.append(os.path.join(cwd, '../rust/tools')) + tools_dir = os.path.abspath(os.path.join(cwd, '..', '..', 'tools')) + if tools_dir not in sys.path: + sys.path.append(tools_dir) import build_support as rust_build_support - + + build_root = normalize_build_root(build_root, cwd) target = rust_build_support.detect_rust_target(has_func, rtconfig) + if not target: + print('Warning: Could not detect Rust target for user application build') + return LIBS, LIBPATH, LINKFLAGS debug = bool(has_func('RUST_DEBUG_BUILD')) rustflags = rust_build_support.make_rustflags(rtconfig, target) + if not rust_build_support.ensure_rust_target_installed(target): + print('Warning: Rust target is not installed; skipping user application build') + return LIBS, LIBPATH, LINKFLAGS + LIBS, LIBPATH, success_count, total_count = build_all_user_apps( cwd, target, debug, rustflags, build_root, has_func ) - if success_count == 0 and total_count > 0: - print(f'Warning: Failed to build all {total_count} user applications') - elif success_count > 0: + if success_count > 0: LINKFLAGS = generate_linkflags(LIBS, LIBPATH) - print(f'Example user apps linked successfully') + if success_count == total_count: + print('Example user apps linked successfully') + else: + print(f'Warning: Built {success_count}/{total_count} user applications; linking only successfully built apps') + elif total_count > 0: + print(f'Warning: Failed to build all {total_count} user applications') + else: + print('No user applications enabled for Rust build') except UserAppBuildError as e: print(f'Error: {e}') except Exception as e: print(f'Unexpected error during user apps build: {e}') - return LIBS, LIBPATH, LINKFLAGS \ No newline at end of file + return LIBS, LIBPATH, LINKFLAGS From f89e400483a5d7d44c50b3d74cbdc5e7740051e4 Mon Sep 17 00:00:00 2001 From: yan hu Date: Thu, 9 Jul 2026 11:56:17 +0800 Subject: [PATCH 2/8] Add Rust build CI attach config Add a rust attach config to the stm32f407-rt-spark BSP so CI enables the Rust core and component-log example on a Cortex-M4 target. The Rust toolchain setup is scoped to this attach configuration so unrelated BSP build jobs are not affected. --- .../.ci/attachconfig/ci.attachconfig.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bsp/stm32/stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml b/bsp/stm32/stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml index 6b62ab697a4..70279c94217 100644 --- a/bsp/stm32/stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml +++ b/bsp/stm32/stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml @@ -230,3 +230,20 @@ component.cherryusb_cdc: - CONFIG_RT_CHERRYUSB_DEVICE_DWC2_ST=y - CONFIG_RT_CHERRYUSB_DEVICE_CDC_ACM=y - CONFIG_RT_CHERRYUSB_DEVICE_TEMPLATE_CDC_ACM=y +# ------ rust CI ------ +rust: + <<: *scons + pre_build: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal + sudo ln -sf "$HOME/.cargo/bin/cargo" /usr/local/bin/cargo + sudo ln -sf "$HOME/.cargo/bin/rustc" /usr/local/bin/rustc + sudo ln -sf "$HOME/.cargo/bin/rustup" /usr/local/bin/rustup + rustup target add thumbv7em-none-eabihf + rustc --version + cargo --version + kconfig: + - CONFIG_RT_USING_RUST=y + - CONFIG_RT_RUST_CORE=y + - CONFIG_RT_USING_RUST_EXAMPLES=y + - CONFIG_RT_RUST_BUILD_COMPONENTS=y + - CONFIG_RUST_LOG_COMPONENT=y From a05282c1a8bd66877ee128a7a364657d11419543 Mon Sep 17 00:00:00 2001 From: yan hu Date: Thu, 9 Jul 2026 22:48:40 +0800 Subject: [PATCH 3/8] Fix Rust attach build failures --- .../stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml | 3 ++- components/rust/core/src/api/queue.rs | 6 +++--- components/rust/core/src/bindings/mod.rs | 3 +-- components/rust/core/src/lib.rs | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/bsp/stm32/stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml b/bsp/stm32/stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml index 70279c94217..f48686fb689 100644 --- a/bsp/stm32/stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml +++ b/bsp/stm32/stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml @@ -234,7 +234,8 @@ component.cherryusb_cdc: rust: <<: *scons pre_build: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal + python3 -m pip install --user toml + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly --profile minimal sudo ln -sf "$HOME/.cargo/bin/cargo" /usr/local/bin/cargo sudo ln -sf "$HOME/.cargo/bin/rustc" /usr/local/bin/rustc sudo ln -sf "$HOME/.cargo/bin/rustup" /usr/local/bin/rustup diff --git a/components/rust/core/src/api/queue.rs b/components/rust/core/src/api/queue.rs index 96665e47f23..9eb2bd9e37d 100644 --- a/components/rust/core/src/api/queue.rs +++ b/components/rust/core/src/api/queue.rs @@ -20,7 +20,7 @@ pub type APIRawQueue = rt_mq_t; pub(crate) fn queue_create(name: &str, len: u64, message_size: u64) -> Option { let s = CString::new(name).unwrap(); let raw; - unsafe { raw = rt_mq_create(s.as_ptr(), message_size, len, 0) } + unsafe { raw = rt_mq_create(s.as_ptr(), message_size as rt_size_t, len as rt_size_t, 0) } if raw == ptr::null_mut() { None } else { @@ -35,7 +35,7 @@ pub(crate) fn queue_send_wait( msg_size: u64, tick: i32, ) -> RttCResult { - unsafe { rt_mq_send_wait(handle, msg, msg_size, tick).into() } + unsafe { rt_mq_send_wait(handle, msg, msg_size as rt_size_t, tick).into() } } #[inline] @@ -45,7 +45,7 @@ pub(crate) fn queue_receive_wait( msg_size: u64, tick: i32, ) -> RttCResult { - unsafe { rt_mq_recv(handle, msg, msg_size, tick).into() } + unsafe { rt_mq_recv(handle, msg, msg_size as rt_size_t, tick).into() } } #[inline] diff --git a/components/rust/core/src/bindings/mod.rs b/components/rust/core/src/bindings/mod.rs index ae2610e9c8d..cad651a1dd2 100644 --- a/components/rust/core/src/bindings/mod.rs +++ b/components/rust/core/src/bindings/mod.rs @@ -46,8 +46,7 @@ pub use librt::{ /* Memory management functions */ pub use librt::{ - rt_malloc, rt_free, rt_realloc, rt_calloc, rt_malloc_align, rt_free_align, - rt_safe_malloc, rt_safe_free + rt_malloc, rt_free, rt_realloc, rt_calloc, rt_malloc_align, rt_free_align }; /* Device management functions */ diff --git a/components/rust/core/src/lib.rs b/components/rust/core/src/lib.rs index 01e4b34102b..ebfe0487511 100644 --- a/components/rust/core/src/lib.rs +++ b/components/rust/core/src/lib.rs @@ -17,7 +17,6 @@ and device interfaces. Designed for embedded devices running RT-Thread. */ #![no_std] -#![feature(alloc_error_handler)] #![feature(linkage)] #![allow(dead_code)] From 5d382458d55b9dc08139eae95a12e841740cd69e Mon Sep 17 00:00:00 2001 From: yan hu Date: Fri, 10 Jul 2026 13:04:15 +0800 Subject: [PATCH 4/8] Fix Rust build failure propagation --- components/rust/core/SConscript | 11 +++-- components/rust/core/src/fs.rs | 2 +- .../rust/examples/application/SConscript | 18 ++++---- components/rust/examples/component/SConscript | 18 ++++---- components/rust/examples/modules/SConscript | 27 ++++++------ components/rust/tools/build_component.py | 15 ++++--- components/rust/tools/build_usrapp.py | 42 ++++++++++--------- 7 files changed, 78 insertions(+), 55 deletions(-) diff --git a/components/rust/core/SConscript b/components/rust/core/SConscript index 5856b9a3162..cf6767061f7 100644 --- a/components/rust/core/SConscript +++ b/components/rust/core/SConscript @@ -1,4 +1,5 @@ import os +import sys from building import * cwd = GetCurrentDir() @@ -61,12 +62,14 @@ else: target = detect_rust_target(_has, rtconfig) if not target: print('Error: Unable to detect Rust target; please check configuration') + sys.exit(1) else: print(f'Detected Rust target: {target}') # Optional hint if target missing if not ensure_rust_target_installed(target): - print('Warning: Rust target is not installed; skipping Rust library build') + print('Error: Rust target is not installed; Rust library build failed') + sys.exit(1) else: # Build mode and features debug = bool(_has('RUST_DEBUG_BUILD')) @@ -90,10 +93,12 @@ else: include_rust_cmd = True print('Rust library linked successfully') else: - print('Warning: Failed to build Rust library') + print('Error: Failed to build Rust library') + sys.exit(1) else: - print('Warning: Rust toolchain not found') + print('Error: Rust toolchain not found') print('Please install Rust from https://rustup.rs') + sys.exit(1) # Only define the component group (with rust_cmd.c) when the Rust core static # library was actually produced, so its rust_init() reference always resolves. diff --git a/components/rust/core/src/fs.rs b/components/rust/core/src/fs.rs index 6ad02037abd..9a8d1afa8df 100644 --- a/components/rust/core/src/fs.rs +++ b/components/rust/core/src/fs.rs @@ -98,7 +98,7 @@ impl File { pub fn seek(&self, offset: i64) -> RTResult { let n = unsafe { libc::lseek(self.fd, offset as libc::off_t, libc::SEEK_SET) }; - if n < 0 { Err(FileSeekErr) } else { Ok(n) } + if n < 0 { Err(FileSeekErr) } else { Ok(n.into()) } } pub fn flush(&self) -> RTResult<()> { diff --git a/components/rust/examples/application/SConscript b/components/rust/examples/application/SConscript index 930d0f21cd1..6c42257bf5f 100644 --- a/components/rust/examples/application/SConscript +++ b/components/rust/examples/application/SConscript @@ -6,7 +6,7 @@ cwd = GetCurrentDir() # Import usrapp build module and build support sys.path.append(os.path.join(cwd, '../../tools')) -from build_usrapp import build_example_usrapp +from build_usrapp import UserAppBuildError, build_example_usrapp from build_support import clean_rust_build @@ -51,12 +51,16 @@ if GetOption('clean'): print('No example_usrapp build artifacts to clean') else: import rtconfig - LIBS, LIBPATH, LINKFLAGS = build_example_usrapp( - cwd=cwd, - has_func=_has, - rtconfig=rtconfig, - build_root=os.path.join(Dir('#').abspath, "build", "example_usrapp") - ) + try: + LIBS, LIBPATH, LINKFLAGS = build_example_usrapp( + cwd=cwd, + has_func=_has, + rtconfig=rtconfig, + build_root=os.path.join(Dir('#').abspath, "build", "example_usrapp") + ) + except UserAppBuildError as e: + print(f'Error: {e}') + sys.exit(1) group = DefineGroup( 'example_usrapp', diff --git a/components/rust/examples/component/SConscript b/components/rust/examples/component/SConscript index 056173ce505..3e89189b7cc 100644 --- a/components/rust/examples/component/SConscript +++ b/components/rust/examples/component/SConscript @@ -6,7 +6,7 @@ cwd = GetCurrentDir() # Import component build module and build support sys.path.append(os.path.join(cwd, '../../tools')) -from build_component import build_example_component +from build_component import ComponentBuildError, build_example_component from build_support import clean_rust_build # Load feature configurations @@ -50,12 +50,16 @@ if GetOption('clean'): else: # Build the component using the extracted build module import rtconfig - LIBS, LIBPATH, LINKFLAGS = build_example_component( - cwd=cwd, - has_func=_has, - rtconfig=rtconfig, - build_root=os.path.join(Dir('#').abspath, "build", "example_component") - ) + try: + LIBS, LIBPATH, LINKFLAGS = build_example_component( + cwd=cwd, + has_func=_has, + rtconfig=rtconfig, + build_root=os.path.join(Dir('#').abspath, "build", "example_component") + ) + except ComponentBuildError as e: + print(f'Error: {e}') + sys.exit(1) # Define component group for SCons group = DefineGroup( diff --git a/components/rust/examples/modules/SConscript b/components/rust/examples/modules/SConscript index 7256daa3ff8..cd45e77fb3d 100644 --- a/components/rust/examples/modules/SConscript +++ b/components/rust/examples/modules/SConscript @@ -28,6 +28,9 @@ MODULE_CONFIG_MAP = { 'example_lib': 'RT_RUST_MODULE_SIMPLE_MODULE', } +class ModuleBuildError(Exception): + pass + def _has(sym: str) -> bool: """Helper function to check if a configuration symbol is enabled""" try: @@ -88,8 +91,7 @@ def build_rust_module(module_dir, build_root): try: import toml except ImportError: - print(f"Warning: Failed to parse {cargo_toml_path}: missing toml module") - return [], [], "" + raise ModuleBuildError(f"Failed to parse {cargo_toml_path}: missing toml module") try: with open(cargo_toml_path, 'r') as f: @@ -98,15 +100,13 @@ def build_rust_module(module_dir, build_root): lib_name = cargo_config.get('lib', {}).get('name') or module_name link_lib_name = lib_name.replace('-', '_') except Exception as e: - print(f"Warning: Failed to parse module metadata from {cargo_toml_path}: {e}") - return [], [], "" + raise ModuleBuildError(f"Failed to parse module metadata from {cargo_toml_path}: {e}") # Detect target automatically based on the current configuration try: target = detect_target_for_dynamic_modules() except RuntimeError as e: - print(f"Warning: Failed to detect Rust target for module '{module_name}': {e}") - return [], [], "" + raise ModuleBuildError(f"Failed to detect Rust target for module '{module_name}': {e}") print(f"Building Rust module '{module_name}' for target: {target}") @@ -123,7 +123,7 @@ def build_rust_module(module_dir, build_root): if not ensure_rust_target_installed(target): print(f"Error: Rust target '{target}' is not installed") print(f"Please install it with: rustup target add {target}") - return [], [], "" + raise ModuleBuildError(f"Rust target '{target}' is not installed") else: print(f"Warning: Cannot verify if target '{target}' is installed") @@ -163,12 +163,11 @@ def build_rust_module(module_dir, build_root): artifact_name = f"lib{link_lib_name}.so" artifact_path = os.path.join(lib_dir, artifact_name) if not os.path.isfile(artifact_path) or os.path.getsize(artifact_path) == 0: - print(f"Warning: Rust module artifact not found, is not a file, or is empty: {artifact_path}") - return [], [], "" + raise ModuleBuildError(f"Rust module artifact not found, is not a file, or is empty: {artifact_path}") return [link_lib_name], [lib_dir], "" except FileNotFoundError: print("Error: cargo executable not found. Please install Rust/Cargo and ensure it is in PATH.") - return [], [], "" + raise ModuleBuildError(f"Cargo executable not found while building module '{module_name}' for target {target}") except subprocess.CalledProcessError as e: print(f"Error: Failed to build Rust module: {e}") stdout = _decode_cargo_output(getattr(e, "stdout", None) or getattr(e, "output", None)).strip() @@ -177,7 +176,7 @@ def build_rust_module(module_dir, build_root): print(stdout) if stderr: print(stderr) - return [], [], "" + raise ModuleBuildError(f"Failed to build Rust module '{module_name}' in {module_dir} for target {target}") # Check dependencies if not _has('RT_USING_MODULE'): @@ -205,7 +204,11 @@ else: for item in os.listdir(cwd): item_path = os.path.join(cwd, item) if os.path.isdir(item_path) and os.path.exists(os.path.join(item_path, 'Cargo.toml')) and should_build_module(item_path): - result = build_rust_module(item_path, build_root) + try: + result = build_rust_module(item_path, build_root) + except ModuleBuildError as e: + print(f"Error: {e}") + sys.exit(1) if result[0]: modules_built.extend(result[0]) diff --git a/components/rust/tools/build_component.py b/components/rust/tools/build_component.py index 94b0e097552..371c8503be7 100644 --- a/components/rust/tools/build_component.py +++ b/components/rust/tools/build_component.py @@ -239,7 +239,12 @@ def cargo_build_component_staticlib(rust_dir, target, features, debug, rustflags return None if res.returncode != 0: - print("Warning: Example component build failed") + print(f"Warning: Example component build failed for {rust_dir}") + print(f"Target: {target}") + print(f"Command: {' '.join(cmd)}") + print(f"Return code: {res.returncode}") + if res.stdout: + print(res.stdout) if res.stderr: print(res.stderr) return None @@ -289,8 +294,7 @@ def build_example_component(cwd, has_func, rtconfig, build_root=None): target = detect_rust_target(has_func, rtconfig) if not target: - print('Warning: Could not detect Rust target for example component build') - return LIBS, LIBPATH, LINKFLAGS + raise ComponentBuildError(f'Could not detect Rust target for example component build in {cwd}') # Build mode and features debug = bool(has_func('RUST_DEBUG_BUILD')) @@ -304,8 +308,7 @@ def build_example_component(cwd, has_func, rtconfig, build_root=None): rustflags = make_rustflags(rtconfig, target) build_root = normalize_component_build_root(build_root, cwd) if not ensure_rust_target_installed(target): - print('Warning: Rust target is not installed; skipping example component build') - return LIBS, LIBPATH, LINKFLAGS + raise ComponentBuildError(f"Rust target '{target}' is not installed; example component build failed") rust_lib = cargo_build_component_staticlib( rust_dir=registry_dir, @@ -338,6 +341,6 @@ def build_example_component(cwd, has_func, rtconfig, build_root=None): ] print('Example component registry library linked successfully') else: - print('Warning: Failed to build example component registry library') + raise ComponentBuildError(f"Failed to build example component registry library in {registry_dir} for target {target}") return LIBS, LIBPATH, LINKFLAGS diff --git a/components/rust/tools/build_usrapp.py b/components/rust/tools/build_usrapp.py index 6e0637a581b..18b27a34a6e 100644 --- a/components/rust/tools/build_usrapp.py +++ b/components/rust/tools/build_usrapp.py @@ -289,7 +289,7 @@ def build_user_app(app_dir, target, debug, rustflags, build_root, features=None) lib_name, is_staticlib = parse_cargo_toml(cargo_toml_path) if not is_staticlib: - return False, None, None + raise UserAppBuildError(f"User app in {app_dir} is not configured as a staticlib") env = os.environ.copy() previous_rustflags = env.get('RUSTFLAGS', '').strip() @@ -316,7 +316,7 @@ def build_user_app(app_dir, target, debug, rustflags, build_root, features=None) capture_output=True, text=True) except FileNotFoundError: print("Error: cargo executable not found. Please install Rust/Cargo and ensure it is in PATH.") - return False, None, None + raise UserAppBuildError(f"Cargo executable not found while building user app in {app_dir}") if result.returncode != 0: print(f"Failed to build user app in {app_dir}") @@ -324,7 +324,7 @@ def build_user_app(app_dir, target, debug, rustflags, build_root, features=None) print(f"Return code: {result.returncode}") print(f"STDOUT: {result.stdout}") print(f"STDERR: {result.stderr}") - return False, None, None + raise UserAppBuildError(f"Failed to build user app in {app_dir}") lib_file, link_lib_name = find_library_file(build_root, target, lib_name, debug) if lib_file: @@ -332,11 +332,12 @@ def build_user_app(app_dir, target, debug, rustflags, build_root, features=None) return True, link_lib_name, lib_file else: print(f"Library file not found for lib {lib_name}") - return False, None, None + raise UserAppBuildError(f"Library file not found for user app {lib_name}") + except UserAppBuildError: + raise except Exception as e: - print(f"Exception occurred while building user app in {app_dir}: {e}") - return False, None, None + raise UserAppBuildError(f"Exception occurred while building user app in {app_dir}: {e}") from e def find_library_file(build_root, target, lib_name, debug): @@ -417,6 +418,8 @@ def build_all_user_apps(base_dir, target, debug, rustflags, build_root, has_func if lib_dir not in LIBPATH: LIBPATH.append(lib_dir) success_count += 1 + else: + raise UserAppBuildError(f"Failed to build enabled user app: {app_dir}") return LIBS, LIBPATH, success_count, total_count @@ -486,15 +489,21 @@ def build_example_usrapp(cwd, has_func, rtconfig, build_root=None): import build_support as rust_build_support build_root = normalize_build_root(build_root, cwd) + enabled_apps = [ + app_dir for app_dir in discover_user_apps(cwd) + if should_build_app(app_dir, has_func) + ] + if not enabled_apps: + print('No user applications enabled for Rust build') + return LIBS, LIBPATH, LINKFLAGS + target = rust_build_support.detect_rust_target(has_func, rtconfig) if not target: - print('Warning: Could not detect Rust target for user application build') - return LIBS, LIBPATH, LINKFLAGS + raise UserAppBuildError('Could not detect Rust target for user application build') debug = bool(has_func('RUST_DEBUG_BUILD')) rustflags = rust_build_support.make_rustflags(rtconfig, target) if not rust_build_support.ensure_rust_target_installed(target): - print('Warning: Rust target is not installed; skipping user application build') - return LIBS, LIBPATH, LINKFLAGS + raise UserAppBuildError('Rust target is not installed; user application build failed') LIBS, LIBPATH, success_count, total_count = build_all_user_apps( cwd, target, debug, rustflags, build_root, has_func @@ -502,18 +511,13 @@ def build_example_usrapp(cwd, has_func, rtconfig, build_root=None): if success_count > 0: LINKFLAGS = generate_linkflags(LIBS, LIBPATH) - if success_count == total_count: - print('Example user apps linked successfully') - else: - print(f'Warning: Built {success_count}/{total_count} user applications; linking only successfully built apps') - elif total_count > 0: - print(f'Warning: Failed to build all {total_count} user applications') + print('Example user apps linked successfully') else: print('No user applications enabled for Rust build') - except UserAppBuildError as e: - print(f'Error: {e}') + except UserAppBuildError: + raise except Exception as e: - print(f'Unexpected error during user apps build: {e}') + raise UserAppBuildError(f'Unexpected error during user apps build: {e}') from e return LIBS, LIBPATH, LINKFLAGS From fb9252bccaec2261351a8dfe10791105d4e6e292 Mon Sep 17 00:00:00 2001 From: yan hu Date: Fri, 10 Jul 2026 17:13:29 +0800 Subject: [PATCH 5/8] Fix Rust link flags for GCC and ArmClang --- components/rust/tools/build_component.py | 47 +++++++++++++---- components/rust/tools/build_usrapp.py | 66 ++++++++++++++++++++---- 2 files changed, 94 insertions(+), 19 deletions(-) diff --git a/components/rust/tools/build_component.py b/components/rust/tools/build_component.py index 371c8503be7..f31251dcda3 100644 --- a/components/rust/tools/build_component.py +++ b/components/rust/tools/build_component.py @@ -1,5 +1,6 @@ import os import subprocess +from SCons.Subst import quote_spaces # Configuration to feature mapping table for components @@ -9,6 +10,7 @@ 'RUST_LOG_COMPONENT': { 'feature': 'enable-log', 'dependencies': ['em_component_log'], + 'export_symbols': ['__rust_component_registry_component_seg'], 'description': 'Enable Rust logging component integration' }, } @@ -103,6 +105,20 @@ def get_staticlib_link_name_from_artifact(lib_path): return None +def get_component_export_symbols(features): + enabled_features = set(features or []) + symbols = [] + for config_info in CONFIG_COMPONENT_FEATURE_MAP.values(): + if config_info['feature'] in enabled_features: + export_symbols = config_info.get('export_symbols', []) + if not export_symbols: + raise ComponentBuildError( + f"No link anchor metadata registered for enabled component feature '{config_info['feature']}'" + ) + symbols.extend(export_symbols) + return symbols + + def check_component_dependencies(component_dir, required_dependencies): """ Check if a component has the required dependencies @@ -329,16 +345,27 @@ def build_example_component(cwd, has_func, rtconfig, build_root=None): link_lib_name = get_component_staticlib_link_name(registry_dir) LIBS = [link_lib_name] LIBPATH = [lib_dir] - # Add LINKFLAGS to ensure component is linked into final binary. - # Use list tokens so each flag stays a single argument; -L - # must precede -l and remain intact even when lib_dir has spaces. - LINKFLAGS = [ - f"-L{os.fspath(lib_dir)}", - "-Wl,--whole-archive", - f"-l{link_lib_name}", - "-Wl,--no-whole-archive", - "-Wl,--allow-multiple-definition", - ] + platform = getattr(rtconfig, 'PLATFORM', None) + if platform == 'armclang': + if not (os.path.isfile(rust_lib) and os.path.getsize(rust_lib) > 0): + raise ComponentBuildError( + f"ArmClang Rust component link requires a non-empty archive, but got: {rust_lib}" + ) + anchors = get_component_export_symbols(features) + LINKFLAGS = [f"--undefined={symbol}" for symbol in anchors] + LINKFLAGS.append(quote_spaces(os.fspath(rust_lib))) + LINKFLAGS = " " + " ".join(LINKFLAGS) + LIBS = [] + LIBPATH = [] + else: + LINKFLAGS = [ + quote_spaces(f"-L{os.fspath(lib_dir)}"), + "-Wl,--whole-archive", + f"-l{link_lib_name}", + "-Wl,--no-whole-archive", + "-Wl,--allow-multiple-definition", + ] + LINKFLAGS = " " + " ".join(LINKFLAGS) print('Example component registry library linked successfully') else: raise ComponentBuildError(f"Failed to build example component registry library in {registry_dir} for target {target}") diff --git a/components/rust/tools/build_usrapp.py b/components/rust/tools/build_usrapp.py index 18b27a34a6e..0d7d4dc5bfd 100644 --- a/components/rust/tools/build_usrapp.py +++ b/components/rust/tools/build_usrapp.py @@ -1,6 +1,7 @@ import os import subprocess import shutil +from SCons.Subst import quote_spaces # Configuration to feature mapping table @@ -24,6 +25,16 @@ 'loadlib': ['RT_USING_MODULE'], } +APP_EXPORT_SYMBOL_MAP = { + 'fs': ['__rust_file_demo_cmd_seg'], + 'loadlib': ['__rust_dl_demo_cmd_seg'], + 'mutex': ['__rust_mutex_demo_cmd_seg'], + 'param': ['__rust_param_demo_cmd_seg'], + 'queue': ['__rust_queue_demo_cmd_seg'], + 'semaphore': ['__rust_sem_demo_cmd_seg'], + 'thread': ['__rust_thread_demo_cmd_seg'], +} + def app_dependencies_satisfied(app_name, has_func): if app_name == 'fs': @@ -69,6 +80,16 @@ def should_build_app(app_dir, has_func): return has_func('RT_RUST_BUILD_APPLICATIONS') +def get_app_export_symbols(app_dir): + app_name = os.path.basename(app_dir) + if app_name not in APP_EXPORT_SYMBOL_MAP: + raise UserAppBuildError( + f"No link anchor metadata registered for enabled user app '{app_name}'. " + f"Path: {app_dir}. Add its export anchor to APP_EXPORT_SYMBOL_MAP." + ) + return list(APP_EXPORT_SYMBOL_MAP[app_name]) + + def check_app_dependencies(app_dir, required_dependencies): """ Check if an application has the required dependencies @@ -375,7 +396,7 @@ def find_library_file(build_root, target, lib_name, debug): return None, None -def build_all_user_apps(base_dir, target, debug, rustflags, build_root, has_func): +def build_all_user_apps(base_dir, target, debug, rustflags, build_root, has_func, require_export_symbols=False): """ Build all user applications @@ -392,6 +413,8 @@ def build_all_user_apps(base_dir, target, debug, rustflags, build_root, has_func """ LIBS = [] LIBPATH = [] + LIBFILES = [] + UNDEFINED_SYMBOLS = [] success_count = 0 total_count = 0 @@ -414,6 +437,9 @@ def build_all_user_apps(base_dir, target, debug, rustflags, build_root, has_func app_name = os.path.basename(app_dir) print(f"Example user app {app_name} built successfully") LIBS.append(lib_name) + LIBFILES.append(lib_path) + if require_export_symbols: + UNDEFINED_SYMBOLS.extend(get_app_export_symbols(app_dir)) lib_dir = os.path.dirname(lib_path) if lib_dir not in LIBPATH: LIBPATH.append(lib_dir) @@ -421,10 +447,10 @@ def build_all_user_apps(base_dir, target, debug, rustflags, build_root, has_func else: raise UserAppBuildError(f"Failed to build enabled user app: {app_dir}") - return LIBS, LIBPATH, success_count, total_count + return LIBS, LIBPATH, LIBFILES, UNDEFINED_SYMBOLS, success_count, total_count -def generate_linkflags(LIBS, LIBPATH): +def generate_linkflags(LIBS, LIBPATH, platform, LIBFILES=None, UNDEFINED_SYMBOLS=None): """ Generate link flags @@ -435,12 +461,30 @@ def generate_linkflags(LIBS, LIBPATH): Returns: str: Link flags string """ + if platform == 'armclang': + if not LIBFILES: + raise UserAppBuildError( + "ArmClang Rust link requires built static library archives, but none were produced" + ) + for lib in LIBFILES: + if not (os.path.isfile(lib) and os.path.getsize(lib) > 0): + raise UserAppBuildError( + f"ArmClang Rust link requires a non-empty archive, but got: {lib}" + ) + if not UNDEFINED_SYMBOLS: + raise UserAppBuildError( + "ArmClang Rust link requires at least one --undefined anchor symbol, but none were resolved" + ) + linkflags = [f"--undefined={symbol}" for symbol in UNDEFINED_SYMBOLS] + linkflags.extend(quote_spaces(os.fspath(lib)) for lib in LIBFILES) + return " " + " ".join(linkflags) + if not LIBS or not LIBPATH: return "" - + linkflags = [] for path in LIBPATH: - linkflags.append(f"-L{os.fspath(path)}") + linkflags.append(quote_spaces(f"-L{os.fspath(path)}")) linkflags.append("-Wl,--whole-archive") for lib in LIBS: linkflags.append(f"-l{lib}") @@ -449,7 +493,7 @@ def generate_linkflags(LIBS, LIBPATH): "-Wl,--allow-multiple-definition", ]) - return linkflags + return " " + " ".join(linkflags) def clean_user_apps_build(build_root): @@ -505,12 +549,16 @@ def build_example_usrapp(cwd, has_func, rtconfig, build_root=None): if not rust_build_support.ensure_rust_target_installed(target): raise UserAppBuildError('Rust target is not installed; user application build failed') - LIBS, LIBPATH, success_count, total_count = build_all_user_apps( - cwd, target, debug, rustflags, build_root, has_func + platform = getattr(rtconfig, 'PLATFORM', None) + LIBS, LIBPATH, LIBFILES, UNDEFINED_SYMBOLS, success_count, total_count = build_all_user_apps( + cwd, target, debug, rustflags, build_root, has_func, platform == 'armclang' ) if success_count > 0: - LINKFLAGS = generate_linkflags(LIBS, LIBPATH) + LINKFLAGS = generate_linkflags(LIBS, LIBPATH, platform, LIBFILES, UNDEFINED_SYMBOLS) + if platform == 'armclang': + LIBS = [] + LIBPATH = [] print('Example user apps linked successfully') else: print('No user applications enabled for Rust build') From 75310a8220665f44289dbfef8ab74c533715df14 Mon Sep 17 00:00:00 2001 From: yan hu Date: Fri, 10 Jul 2026 18:59:24 +0800 Subject: [PATCH 6/8] Fix Rust component anchor configuration --- components/rust/tools/build_component.py | 6 ------ components/rust/tools/feature_config_component.py | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/components/rust/tools/build_component.py b/components/rust/tools/build_component.py index f31251dcda3..7a45b63c9ae 100644 --- a/components/rust/tools/build_component.py +++ b/components/rust/tools/build_component.py @@ -7,12 +7,6 @@ # This table defines which RT-Thread configurations should enable which component features # All feature configurations are now defined in feature_config_component.py CONFIG_COMPONENT_FEATURE_MAP = { - 'RUST_LOG_COMPONENT': { - 'feature': 'enable-log', - 'dependencies': ['em_component_log'], - 'export_symbols': ['__rust_component_registry_component_seg'], - 'description': 'Enable Rust logging component integration' - }, } diff --git a/components/rust/tools/feature_config_component.py b/components/rust/tools/feature_config_component.py index 221d529abe0..e0c7e2f510b 100644 --- a/components/rust/tools/feature_config_component.py +++ b/components/rust/tools/feature_config_component.py @@ -15,6 +15,7 @@ def setup_all_component_features(): 'RUST_LOG_COMPONENT': { 'feature': 'enable-log', 'dependencies': ['em_component_log'], + 'export_symbols': ['__rust_component_registry_component_seg'], 'description': 'Enable Rust logging component integration' }, }) From fc87fc7193e3bd70870291625e0556a2369f3c17 Mon Sep 17 00:00:00 2001 From: yan hu Date: Fri, 10 Jul 2026 21:30:22 +0800 Subject: [PATCH 7/8] Fix ArmClang Rust core archive linking --- components/rust/core/SConscript | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/components/rust/core/SConscript b/components/rust/core/SConscript index cf6767061f7..fe6fa96d023 100644 --- a/components/rust/core/SConscript +++ b/components/rust/core/SConscript @@ -1,6 +1,7 @@ import os import sys from building import * +from SCons.Subst import quote_spaces cwd = GetCurrentDir() @@ -42,6 +43,7 @@ def get_staticlib_link_name_from_artifact(lib_path): # rust_init' instead of failing/skipping cleanly at the Rust build step. LIBS = [] LIBPATH = [] +LINKFLAGS = "" include_rust_cmd = False if GetOption('clean'): @@ -90,6 +92,13 @@ else: link_lib_name = get_staticlib_link_name(cwd) LIBS = [link_lib_name] LIBPATH = [os.path.dirname(rust_lib)] + if rtconfig.PLATFORM == 'armclang': + if not (os.path.isfile(rust_lib) and os.path.getsize(rust_lib) > 0): + print(f'Error: ArmClang Rust core link requires a non-empty archive, but got: {rust_lib}') + sys.exit(1) + LINKFLAGS = " " + quote_spaces(os.fspath(rust_lib)) + LIBS = [] + LIBPATH = [] include_rust_cmd = True print('Rust library linked successfully') else: @@ -103,6 +112,6 @@ else: # Only define the component group (with rust_cmd.c) when the Rust core static # library was actually produced, so its rust_init() reference always resolves. if include_rust_cmd: - group = DefineGroup('rust', ['rust_cmd.c'], depend=['RT_USING_RUST', 'RT_RUST_CORE'], LIBS=LIBS, LIBPATH=LIBPATH) + group = DefineGroup('rust', ['rust_cmd.c'], depend=['RT_USING_RUST', 'RT_RUST_CORE'], LIBS=LIBS, LIBPATH=LIBPATH, LINKFLAGS=LINKFLAGS) Return('group') From 269212f4243952106747088641eff79734825b3e Mon Sep 17 00:00:00 2001 From: yan hu Date: Fri, 10 Jul 2026 21:56:45 +0800 Subject: [PATCH 8/8] Fail closed on component feature parsing --- components/rust/tools/build_component.py | 19 ++++++++++++------- tools/requirements.txt | 3 ++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/components/rust/tools/build_component.py b/components/rust/tools/build_component.py index 7a45b63c9ae..276958117b1 100644 --- a/components/rust/tools/build_component.py +++ b/components/rust/tools/build_component.py @@ -14,6 +14,14 @@ class ComponentBuildError(Exception): pass +def load_toml_module(): + try: + import toml + return toml + except ImportError as e: + raise ComponentBuildError("Missing toml module required to parse component Cargo.toml") from e + + def normalize_component_build_root(build_root, cwd): """ Normalize the component build root directory. @@ -185,23 +193,20 @@ def collect_component_features(has_func, component_dir=None): def declared_component_features(component_dir): cargo_toml_path = os.path.join(component_dir, 'Cargo.toml') - if not os.path.exists(cargo_toml_path): - return None + if not os.path.isfile(cargo_toml_path): + raise ComponentBuildError(f"Component Cargo.toml not found: {cargo_toml_path}") + toml = load_toml_module() try: - import toml with open(cargo_toml_path, 'r') as f: cargo_data = toml.load(f) return set(cargo_data.get('features', {}).keys()) except Exception as e: - print(f"Warning: Failed to parse component features from {cargo_toml_path}: {e}") - return None + raise ComponentBuildError(f"Failed to parse component features from {cargo_toml_path}: {e}") from e def filter_declared_component_features(component_dir, features): declared_features = declared_component_features(component_dir) - if declared_features is None: - return list(features) return [feature for feature in features if feature in declared_features] diff --git a/tools/requirements.txt b/tools/requirements.txt index 33b60c4c741..bfc09635a3c 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -2,4 +2,5 @@ scons>=4.0.1 requests>=2.27.1 tqdm>=4.67.1 kconfiglib>=13.7.1 -PyYAML>=6.0 \ No newline at end of file +PyYAML>=6.0 +toml>=0.10.2