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