|
@@ -0,0 +1,392 @@
|
|
|
|
|
+import os
|
|
|
|
|
+import subprocess
|
|
|
|
|
+import toml
|
|
|
|
|
+import shutil
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# Configuration to feature mapping table
|
|
|
|
|
+# This table defines which RT-Thread configurations should enable which Rust features
|
|
|
|
|
+# All feature configurations are now defined in feature_config_examples.py
|
|
|
|
|
+CONFIG_FEATURE_MAP = {}
|
|
|
|
|
+
|
|
|
|
|
+# Application directory to Kconfig mapping table
|
|
|
|
|
+# This table defines which Kconfig options control which application directories
|
|
|
|
|
+APP_CONFIG_MAP = {
|
|
|
|
|
+ 'fs': 'RT_RUST_EXAMPLE_FS',
|
|
|
|
|
+ 'loadlib': 'RT_RUST_EXAMPLE_LOADLIB',
|
|
|
|
|
+ 'mutex': 'RT_RUST_EXAMPLE_MUTEX',
|
|
|
|
|
+ 'param': 'RT_RUST_EXAMPLE_PARAM',
|
|
|
|
|
+ 'queue': 'RT_RUST_EXAMPLE_QUEUE',
|
|
|
|
|
+ 'semaphore': 'RT_RUST_EXAMPLE_SEMAPHORE',
|
|
|
|
|
+ 'thread': 'RT_RUST_EXAMPLE_THREAD'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def should_build_app(app_dir, has_func):
|
|
|
|
|
+ """
|
|
|
|
|
+ Check if an application should be built based on Kconfig configuration
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ app_dir: Application directory path
|
|
|
|
|
+ has_func: Function to check if a configuration is enabled
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ bool: True if the application should be built
|
|
|
|
|
+ """
|
|
|
|
|
+ # Get the application name from the directory
|
|
|
|
|
+ app_name = os.path.basename(app_dir)
|
|
|
|
|
+
|
|
|
|
|
+ # Check if there's a specific Kconfig option for this app
|
|
|
|
|
+ if app_name in APP_CONFIG_MAP:
|
|
|
|
|
+ config_option = APP_CONFIG_MAP[app_name]
|
|
|
|
|
+ return has_func(config_option)
|
|
|
|
|
+
|
|
|
|
|
+ # If no specific config found, check if applications are enabled in general
|
|
|
|
|
+ return has_func('RT_RUST_BUILD_APPLICATIONS')
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def check_app_dependencies(app_dir, required_dependencies):
|
|
|
|
|
+ """
|
|
|
|
|
+ Check if an application has the required dependencies
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ app_dir: Application directory path
|
|
|
|
|
+ required_dependencies: List of dependency names to check
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ bool: True if all required dependencies are present
|
|
|
|
|
+ """
|
|
|
|
|
+ if not app_dir or not required_dependencies:
|
|
|
|
|
+ return True
|
|
|
|
|
+
|
|
|
|
|
+ cargo_toml_path = os.path.join(app_dir, 'Cargo.toml')
|
|
|
|
|
+ if not os.path.exists(cargo_toml_path):
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ with open(cargo_toml_path, 'r') as f:
|
|
|
|
|
+ cargo_data = toml.load(f)
|
|
|
|
|
+
|
|
|
|
|
+ dependencies = cargo_data.get('dependencies', {})
|
|
|
|
|
+
|
|
|
|
|
+ # Check if all required dependencies are present
|
|
|
|
|
+ for dep in required_dependencies:
|
|
|
|
|
+ if dep not in dependencies:
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+ return True
|
|
|
|
|
+
|
|
|
|
|
+ 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
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ has_func: Function to check if a configuration is enabled
|
|
|
|
|
+ app_dir: Application directory to check dependencies (optional)
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ list: List of features to enable
|
|
|
|
|
+ """
|
|
|
|
|
+ features = []
|
|
|
|
|
+
|
|
|
|
|
+ # Iterate through all configured mappings
|
|
|
|
|
+ for config_name, config_info in CONFIG_FEATURE_MAP.items():
|
|
|
|
|
+ # Check if this RT-Thread configuration is enabled
|
|
|
|
|
+ if has_func(config_name):
|
|
|
|
|
+ feature_name = config_info['feature']
|
|
|
|
|
+ required_deps = config_info.get('dependencies', [])
|
|
|
|
|
+
|
|
|
|
|
+ # If app_dir is provided, check dependencies
|
|
|
|
|
+ if app_dir:
|
|
|
|
|
+ if check_app_dependencies(app_dir, required_deps):
|
|
|
|
|
+ features.append(feature_name)
|
|
|
|
|
+ print(f"Enabling feature '{feature_name}' for {config_name} in {os.path.basename(app_dir)}")
|
|
|
|
|
+ else:
|
|
|
|
|
+ # If no app_dir provided, enable for all (backward compatibility)
|
|
|
|
|
+ features.append(feature_name)
|
|
|
|
|
+ print(f"Enabling feature '{feature_name}' for {config_name}")
|
|
|
|
|
+
|
|
|
|
|
+ return features
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class UserAppBuildError(Exception):
|
|
|
|
|
+ """User application build error exception"""
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def parse_cargo_toml(cargo_toml_path):
|
|
|
|
|
+ """
|
|
|
|
|
+ Parse Cargo.toml file to extract library name and library type
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ cargo_toml_path: Path to Cargo.toml file
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ tuple: (lib_name, is_staticlib)
|
|
|
|
|
+ """
|
|
|
|
|
+ try:
|
|
|
|
|
+ with open(cargo_toml_path, 'r') as f:
|
|
|
|
|
+ cargo_data = toml.load(f)
|
|
|
|
|
+
|
|
|
|
|
+ package_name = cargo_data.get('package', {}).get('name')
|
|
|
|
|
+ if not package_name:
|
|
|
|
|
+ raise UserAppBuildError(f"No package name found in {cargo_toml_path}")
|
|
|
|
|
+
|
|
|
|
|
+ lib_config = cargo_data.get('lib', {})
|
|
|
|
|
+ crate_type = lib_config.get('crate-type', [])
|
|
|
|
|
+ is_staticlib = 'staticlib' in crate_type
|
|
|
|
|
+
|
|
|
|
|
+ # Use lib name if specified, otherwise use package name
|
|
|
|
|
+ lib_name = lib_config.get('name', package_name)
|
|
|
|
|
+
|
|
|
|
|
+ return lib_name, is_staticlib
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ raise UserAppBuildError(f"Failed to parse {cargo_toml_path}: {e}")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def discover_user_apps(base_dir):
|
|
|
|
|
+ """
|
|
|
|
|
+ Discover all user application directories
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ base_dir: Base directory path
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ list: List of directories containing Cargo.toml
|
|
|
|
|
+ """
|
|
|
|
|
+ user_apps = []
|
|
|
|
|
+
|
|
|
|
|
+ for root, dirs, files in os.walk(base_dir):
|
|
|
|
|
+ if 'Cargo.toml' in files:
|
|
|
|
|
+ if 'target' in root or 'build' in root:
|
|
|
|
|
+ continue
|
|
|
|
|
+ user_apps.append(root)
|
|
|
|
|
+
|
|
|
|
|
+ return user_apps
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def build_user_app(app_dir, target, debug, rustflags, build_root, features=None):
|
|
|
|
|
+ """
|
|
|
|
|
+ Build a single user application
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ app_dir: Application directory
|
|
|
|
|
+ target: Rust target architecture
|
|
|
|
|
+ debug: Whether this is a debug build
|
|
|
|
|
+ rustflags: Rust compilation flags
|
|
|
|
|
+ build_root: Build root directory
|
|
|
|
|
+ features: List of features to enable
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ tuple: (success, lib_name, lib_path)
|
|
|
|
|
+ """
|
|
|
|
|
+ try:
|
|
|
|
|
+ cargo_toml_path = os.path.join(app_dir, 'Cargo.toml')
|
|
|
|
|
+ lib_name, is_staticlib = parse_cargo_toml(cargo_toml_path)
|
|
|
|
|
+
|
|
|
|
|
+ if not is_staticlib:
|
|
|
|
|
+ return False, None, None
|
|
|
|
|
+
|
|
|
|
|
+ env = os.environ.copy()
|
|
|
|
|
+ env['RUSTFLAGS'] = rustflags
|
|
|
|
|
+ env['CARGO_TARGET_DIR'] = build_root
|
|
|
|
|
+
|
|
|
|
|
+ cmd = ['cargo', 'build', '--target', target]
|
|
|
|
|
+ if not debug:
|
|
|
|
|
+ cmd.append('--release')
|
|
|
|
|
+
|
|
|
|
|
+ # Add features if specified
|
|
|
|
|
+ if features:
|
|
|
|
|
+ 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)
|
|
|
|
|
+
|
|
|
|
|
+ if result.returncode != 0:
|
|
|
|
|
+ print(f"Failed to build user app in {app_dir}")
|
|
|
|
|
+ print(f"Command: {' '.join(cmd)}")
|
|
|
|
|
+ print(f"Return code: {result.returncode}")
|
|
|
|
|
+ print(f"STDOUT: {result.stdout}")
|
|
|
|
|
+ print(f"STDERR: {result.stderr}")
|
|
|
|
|
+ return False, None, None
|
|
|
|
|
+
|
|
|
|
|
+ lib_file = find_library_file(build_root, target, lib_name, debug)
|
|
|
|
|
+ if lib_file:
|
|
|
|
|
+ # Return the library name for linking
|
|
|
|
|
+ return True, lib_name, lib_file
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(f"Library file not found for lib {lib_name}")
|
|
|
|
|
+ return False, None, None
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f"Exception occurred while building user app in {app_dir}: {e}")
|
|
|
|
|
+ return False, None, None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def find_library_file(build_root, target, lib_name, debug):
|
|
|
|
|
+ """
|
|
|
|
|
+ Find the generated library file
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ build_root: Build root directory
|
|
|
|
|
+ target: Rust target architecture
|
|
|
|
|
+ lib_name: Library name
|
|
|
|
|
+ debug: Whether this is a debug build
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ str: Library file path, or None if not found
|
|
|
|
|
+ """
|
|
|
|
|
+ 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")
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ for search_path in search_paths:
|
|
|
|
|
+ if not os.path.exists(search_path):
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ for name in possible_names:
|
|
|
|
|
+ lib_path = os.path.join(search_path, name)
|
|
|
|
|
+ if os.path.exists(lib_path):
|
|
|
|
|
+ return lib_path
|
|
|
|
|
+
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def build_all_user_apps(base_dir, target, debug, rustflags, build_root, has_func):
|
|
|
|
|
+ """
|
|
|
|
|
+ Build all user applications
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ base_dir: User applications base directory
|
|
|
|
|
+ target: Rust target architecture
|
|
|
|
|
+ debug: Whether this is a debug build
|
|
|
|
|
+ rustflags: Rust compilation flags
|
|
|
|
|
+ build_root: Build root directory
|
|
|
|
|
+ has_func: Function to check if a configuration is enabled
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ tuple: (LIBS, LIBPATH, success_count, total_count)
|
|
|
|
|
+ """
|
|
|
|
|
+ LIBS = []
|
|
|
|
|
+ LIBPATH = []
|
|
|
|
|
+ success_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
|
|
|
|
|
+ if not should_build_app(app_dir, has_func):
|
|
|
|
|
+ app_name = os.path.basename(app_dir)
|
|
|
|
|
+ print(f"Skipping {app_name} (disabled in Kconfig)")
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ # Collect features for this specific app
|
|
|
|
|
+ features = collect_features(has_func, app_dir)
|
|
|
|
|
+ success, lib_name, lib_path = build_user_app(app_dir, target, debug, rustflags, build_root, features)
|
|
|
|
|
+
|
|
|
|
|
+ if success and lib_path:
|
|
|
|
|
+ app_name = os.path.basename(app_dir)
|
|
|
|
|
+ print(f"Example user app {app_name} built successfully")
|
|
|
|
|
+ LIBS.append(lib_name)
|
|
|
|
|
+ lib_dir = os.path.dirname(lib_path)
|
|
|
|
|
+ if lib_dir not in LIBPATH:
|
|
|
|
|
+ LIBPATH.append(lib_dir)
|
|
|
|
|
+ success_count += 1
|
|
|
|
|
+
|
|
|
|
|
+ return LIBS, LIBPATH, success_count, total_count
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def generate_linkflags(LIBS, LIBPATH):
|
|
|
|
|
+ """
|
|
|
|
|
+ Generate link flags
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ LIBS: List of library names
|
|
|
|
|
+ LIBPATH: List of library paths
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ str: Link flags string
|
|
|
|
|
+ """
|
|
|
|
|
+ if not LIBS or not LIBPATH:
|
|
|
|
|
+ return ""
|
|
|
|
|
+
|
|
|
|
|
+ linkflags = f" -L{LIBPATH[0]} -Wl,--whole-archive"
|
|
|
|
|
+ for lib in LIBS:
|
|
|
|
|
+ linkflags += f" -l{lib}"
|
|
|
|
|
+ linkflags += " -Wl,--no-whole-archive -Wl,--allow-multiple-definition"
|
|
|
|
|
+
|
|
|
|
|
+ return linkflags
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def clean_user_apps_build(build_root):
|
|
|
|
|
+ """
|
|
|
|
|
+ Clean user applications build artifacts
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ build_root: Build root directory
|
|
|
|
|
+ """
|
|
|
|
|
+ if os.path.exists(build_root):
|
|
|
|
|
+ shutil.rmtree(build_root)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def build_example_usrapp(cwd, has_func, rtconfig, build_root=None):
|
|
|
|
|
+ """
|
|
|
|
|
+ Build the example user applications.
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ cwd: Current working directory (usrapp directory)
|
|
|
|
|
+ has_func: Function to check if a configuration is enabled
|
|
|
|
|
+ rtconfig: RT-Thread configuration module
|
|
|
|
|
+ build_root: Optional build root directory
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ tuple: (LIBS, LIBPATH, LINKFLAGS) for SCons
|
|
|
|
|
+ """
|
|
|
|
|
+ LIBS = []
|
|
|
|
|
+ LIBPATH = []
|
|
|
|
|
+ LINKFLAGS = ""
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ # Import build support functions
|
|
|
|
|
+ import sys
|
|
|
|
|
+ sys.path.append(os.path.join(cwd, '../rust/tools'))
|
|
|
|
|
+ import build_support as rust_build_support
|
|
|
|
|
+
|
|
|
|
|
+ target = rust_build_support.detect_rust_target(has_func, rtconfig)
|
|
|
|
|
+ debug = bool(has_func('RUST_DEBUG_BUILD'))
|
|
|
|
|
+ rustflags = rust_build_support.make_rustflags(rtconfig, target)
|
|
|
|
|
+ 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:
|
|
|
|
|
+ LINKFLAGS = generate_linkflags(LIBS, LIBPATH)
|
|
|
|
|
+ print(f'Example user apps linked successfully')
|
|
|
|
|
+
|
|
|
|
|
+ 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
|