| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- 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
|