build_usrapp.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. import os
  2. import subprocess
  3. import toml
  4. import shutil
  5. # Configuration to feature mapping table
  6. # This table defines which RT-Thread configurations should enable which Rust features
  7. # All feature configurations are now defined in feature_config_examples.py
  8. CONFIG_FEATURE_MAP = {}
  9. # Application directory to Kconfig mapping table
  10. # This table defines which Kconfig options control which application directories
  11. APP_CONFIG_MAP = {
  12. 'fs': 'RT_RUST_EXAMPLE_FS',
  13. 'loadlib': 'RT_RUST_EXAMPLE_LOADLIB',
  14. 'mutex': 'RT_RUST_EXAMPLE_MUTEX',
  15. 'param': 'RT_RUST_EXAMPLE_PARAM',
  16. 'queue': 'RT_RUST_EXAMPLE_QUEUE',
  17. 'semaphore': 'RT_RUST_EXAMPLE_SEMAPHORE',
  18. 'thread': 'RT_RUST_EXAMPLE_THREAD'
  19. }
  20. def should_build_app(app_dir, has_func):
  21. """
  22. Check if an application should be built based on Kconfig configuration
  23. Args:
  24. app_dir: Application directory path
  25. has_func: Function to check if a configuration is enabled
  26. Returns:
  27. bool: True if the application should be built
  28. """
  29. # Get the application name from the directory
  30. app_name = os.path.basename(app_dir)
  31. # Check if there's a specific Kconfig option for this app
  32. if app_name in APP_CONFIG_MAP:
  33. config_option = APP_CONFIG_MAP[app_name]
  34. return has_func(config_option)
  35. # If no specific config found, check if applications are enabled in general
  36. return has_func('RT_RUST_BUILD_APPLICATIONS')
  37. def check_app_dependencies(app_dir, required_dependencies):
  38. """
  39. Check if an application has the required dependencies
  40. Args:
  41. app_dir: Application directory path
  42. required_dependencies: List of dependency names to check
  43. Returns:
  44. bool: True if all required dependencies are present
  45. """
  46. if not app_dir or not required_dependencies:
  47. return True
  48. cargo_toml_path = os.path.join(app_dir, 'Cargo.toml')
  49. if not os.path.exists(cargo_toml_path):
  50. return False
  51. try:
  52. with open(cargo_toml_path, 'r') as f:
  53. cargo_data = toml.load(f)
  54. dependencies = cargo_data.get('dependencies', {})
  55. # Check if all required dependencies are present
  56. for dep in required_dependencies:
  57. if dep not in dependencies:
  58. return False
  59. return True
  60. except Exception as e:
  61. print(f"Warning: Failed to parse {cargo_toml_path}: {e}")
  62. return False
  63. def collect_features(has_func, app_dir=None):
  64. """
  65. Collect Rust features based on RT-Thread configuration using extensible mapping table
  66. Args:
  67. has_func: Function to check if a configuration is enabled
  68. app_dir: Application directory to check dependencies (optional)
  69. Returns:
  70. list: List of features to enable
  71. """
  72. features = []
  73. # Iterate through all configured mappings
  74. for config_name, config_info in CONFIG_FEATURE_MAP.items():
  75. # Check if this RT-Thread configuration is enabled
  76. if has_func(config_name):
  77. feature_name = config_info['feature']
  78. required_deps = config_info.get('dependencies', [])
  79. # If app_dir is provided, check dependencies
  80. if app_dir:
  81. if check_app_dependencies(app_dir, required_deps):
  82. features.append(feature_name)
  83. print(f"Enabling feature '{feature_name}' for {config_name} in {os.path.basename(app_dir)}")
  84. else:
  85. # If no app_dir provided, enable for all (backward compatibility)
  86. features.append(feature_name)
  87. print(f"Enabling feature '{feature_name}' for {config_name}")
  88. return features
  89. class UserAppBuildError(Exception):
  90. """User application build error exception"""
  91. pass
  92. def parse_cargo_toml(cargo_toml_path):
  93. """
  94. Parse Cargo.toml file to extract library name and library type
  95. Args:
  96. cargo_toml_path: Path to Cargo.toml file
  97. Returns:
  98. tuple: (lib_name, is_staticlib)
  99. """
  100. try:
  101. with open(cargo_toml_path, 'r') as f:
  102. cargo_data = toml.load(f)
  103. package_name = cargo_data.get('package', {}).get('name')
  104. if not package_name:
  105. raise UserAppBuildError(f"No package name found in {cargo_toml_path}")
  106. lib_config = cargo_data.get('lib', {})
  107. crate_type = lib_config.get('crate-type', [])
  108. is_staticlib = 'staticlib' in crate_type
  109. # Use lib name if specified, otherwise use package name
  110. lib_name = lib_config.get('name', package_name)
  111. return lib_name, is_staticlib
  112. except Exception as e:
  113. raise UserAppBuildError(f"Failed to parse {cargo_toml_path}: {e}")
  114. def discover_user_apps(base_dir):
  115. """
  116. Discover all user application directories
  117. Args:
  118. base_dir: Base directory path
  119. Returns:
  120. list: List of directories containing Cargo.toml
  121. """
  122. user_apps = []
  123. for root, dirs, files in os.walk(base_dir):
  124. if 'Cargo.toml' in files:
  125. if 'target' in root or 'build' in root:
  126. continue
  127. user_apps.append(root)
  128. return user_apps
  129. def build_user_app(app_dir, target, debug, rustflags, build_root, features=None):
  130. """
  131. Build a single user application
  132. Args:
  133. app_dir: Application directory
  134. target: Rust target architecture
  135. debug: Whether this is a debug build
  136. rustflags: Rust compilation flags
  137. build_root: Build root directory
  138. features: List of features to enable
  139. Returns:
  140. tuple: (success, lib_name, lib_path)
  141. """
  142. try:
  143. cargo_toml_path = os.path.join(app_dir, 'Cargo.toml')
  144. lib_name, is_staticlib = parse_cargo_toml(cargo_toml_path)
  145. if not is_staticlib:
  146. return False, None, None
  147. env = os.environ.copy()
  148. env['RUSTFLAGS'] = rustflags
  149. env['CARGO_TARGET_DIR'] = build_root
  150. cmd = ['cargo', 'build', '--target', target]
  151. if not debug:
  152. cmd.append('--release')
  153. # Add features if specified
  154. if features:
  155. cmd.extend(['--features', ','.join(features)])
  156. print(f"Building example user app {lib_name} (cargo)…")
  157. result = subprocess.run(cmd, cwd=app_dir, env=env,
  158. capture_output=True, text=True)
  159. if result.returncode != 0:
  160. print(f"Failed to build user app in {app_dir}")
  161. print(f"Command: {' '.join(cmd)}")
  162. print(f"Return code: {result.returncode}")
  163. print(f"STDOUT: {result.stdout}")
  164. print(f"STDERR: {result.stderr}")
  165. return False, None, None
  166. lib_file = find_library_file(build_root, target, lib_name, debug)
  167. if lib_file:
  168. # Return the library name for linking
  169. return True, lib_name, lib_file
  170. else:
  171. print(f"Library file not found for lib {lib_name}")
  172. return False, None, None
  173. except Exception as e:
  174. print(f"Exception occurred while building user app in {app_dir}: {e}")
  175. return False, None, None
  176. def find_library_file(build_root, target, lib_name, debug):
  177. """
  178. Find the generated library file
  179. Args:
  180. build_root: Build root directory
  181. target: Rust target architecture
  182. lib_name: Library name
  183. debug: Whether this is a debug build
  184. Returns:
  185. str: Library file path, or None if not found
  186. """
  187. profile = "debug" if debug else "release"
  188. possible_names = [
  189. f"lib{lib_name}.a",
  190. f"lib{lib_name.replace('-', '_')}.a"
  191. ]
  192. search_paths = [
  193. os.path.join(build_root, target, profile),
  194. os.path.join(build_root, target, profile, "deps")
  195. ]
  196. for search_path in search_paths:
  197. if not os.path.exists(search_path):
  198. continue
  199. for name in possible_names:
  200. lib_path = os.path.join(search_path, name)
  201. if os.path.exists(lib_path):
  202. return lib_path
  203. return None
  204. def build_all_user_apps(base_dir, target, debug, rustflags, build_root, has_func):
  205. """
  206. Build all user applications
  207. Args:
  208. base_dir: User applications base directory
  209. target: Rust target architecture
  210. debug: Whether this is a debug build
  211. rustflags: Rust compilation flags
  212. build_root: Build root directory
  213. has_func: Function to check if a configuration is enabled
  214. Returns:
  215. tuple: (LIBS, LIBPATH, success_count, total_count)
  216. """
  217. LIBS = []
  218. LIBPATH = []
  219. success_count = 0
  220. user_apps = discover_user_apps(base_dir)
  221. total_count = len(user_apps)
  222. for app_dir in user_apps:
  223. # Check if this application should be built based on Kconfig
  224. if not should_build_app(app_dir, has_func):
  225. app_name = os.path.basename(app_dir)
  226. print(f"Skipping {app_name} (disabled in Kconfig)")
  227. continue
  228. # Collect features for this specific app
  229. features = collect_features(has_func, app_dir)
  230. success, lib_name, lib_path = build_user_app(app_dir, target, debug, rustflags, build_root, features)
  231. if success and lib_path:
  232. app_name = os.path.basename(app_dir)
  233. print(f"Example user app {app_name} built successfully")
  234. LIBS.append(lib_name)
  235. lib_dir = os.path.dirname(lib_path)
  236. if lib_dir not in LIBPATH:
  237. LIBPATH.append(lib_dir)
  238. success_count += 1
  239. return LIBS, LIBPATH, success_count, total_count
  240. def generate_linkflags(LIBS, LIBPATH):
  241. """
  242. Generate link flags
  243. Args:
  244. LIBS: List of library names
  245. LIBPATH: List of library paths
  246. Returns:
  247. str: Link flags string
  248. """
  249. if not LIBS or not LIBPATH:
  250. return ""
  251. linkflags = f" -L{LIBPATH[0]} -Wl,--whole-archive"
  252. for lib in LIBS:
  253. linkflags += f" -l{lib}"
  254. linkflags += " -Wl,--no-whole-archive -Wl,--allow-multiple-definition"
  255. return linkflags
  256. def clean_user_apps_build(build_root):
  257. """
  258. Clean user applications build artifacts
  259. Args:
  260. build_root: Build root directory
  261. """
  262. if os.path.exists(build_root):
  263. shutil.rmtree(build_root)
  264. def build_example_usrapp(cwd, has_func, rtconfig, build_root=None):
  265. """
  266. Build the example user applications.
  267. Args:
  268. cwd: Current working directory (usrapp directory)
  269. has_func: Function to check if a configuration is enabled
  270. rtconfig: RT-Thread configuration module
  271. build_root: Optional build root directory
  272. Returns:
  273. tuple: (LIBS, LIBPATH, LINKFLAGS) for SCons
  274. """
  275. LIBS = []
  276. LIBPATH = []
  277. LINKFLAGS = ""
  278. try:
  279. # Import build support functions
  280. import sys
  281. sys.path.append(os.path.join(cwd, '../rust/tools'))
  282. import build_support as rust_build_support
  283. target = rust_build_support.detect_rust_target(has_func, rtconfig)
  284. debug = bool(has_func('RUST_DEBUG_BUILD'))
  285. rustflags = rust_build_support.make_rustflags(rtconfig, target)
  286. LIBS, LIBPATH, success_count, total_count = build_all_user_apps(
  287. cwd, target, debug, rustflags, build_root, has_func
  288. )
  289. if success_count == 0 and total_count > 0:
  290. print(f'Warning: Failed to build all {total_count} user applications')
  291. elif success_count > 0:
  292. LINKFLAGS = generate_linkflags(LIBS, LIBPATH)
  293. print(f'Example user apps linked successfully')
  294. except UserAppBuildError as e:
  295. print(f'Error: {e}')
  296. except Exception as e:
  297. print(f'Unexpected error during user apps build: {e}')
  298. return LIBS, LIBPATH, LINKFLAGS