mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2026-02-06 05:52:12 +00:00
392 lines
12 KiB
Python
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 |