Files
rt-thread/components/rust/tools/build_usrapp.py
2025-12-08 18:34:25 +08:00

392 lines
12 KiB
Python

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