generator.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. # -*- coding: utf-8 -*-
  2. """
  3. Project generator framework for RT-Thread build system.
  4. This module provides the base classes for project generators (MDK, IAR, VS Code, etc.).
  5. """
  6. import os
  7. import shutil
  8. import json
  9. import xml.etree.ElementTree as ET
  10. from abc import ABC, abstractmethod
  11. from typing import Dict, List, Any, Optional
  12. from dataclasses import dataclass
  13. from .utils import PathService
  14. @dataclass
  15. class GeneratorConfig:
  16. """Configuration for project generators."""
  17. output_dir: str
  18. project_name: str = "rtthread"
  19. target_name: str = "rtthread.elf"
  20. class ProjectGenerator(ABC):
  21. """Abstract base class for project generators."""
  22. def __init__(self, config: GeneratorConfig):
  23. self.config = config
  24. self.path_service = PathService(os.getcwd())
  25. @abstractmethod
  26. def get_name(self) -> str:
  27. """Get generator name."""
  28. pass
  29. @abstractmethod
  30. def generate(self, context, project_info: Dict[str, Any]) -> bool:
  31. """
  32. Generate project files.
  33. Args:
  34. context: BuildContext instance
  35. project_info: Project information from registry
  36. Returns:
  37. True if successful
  38. """
  39. pass
  40. @abstractmethod
  41. def clean(self) -> bool:
  42. """
  43. Clean generated files.
  44. Returns:
  45. True if successful
  46. """
  47. pass
  48. def _ensure_output_dir(self) -> None:
  49. """Ensure output directory exists."""
  50. os.makedirs(self.config.output_dir, exist_ok=True)
  51. def _copy_template(self, template_name: str, output_name: str = None) -> str:
  52. """
  53. Copy template file to output directory.
  54. Args:
  55. template_name: Template file name
  56. output_name: Output file name (defaults to template_name)
  57. Returns:
  58. Output file path
  59. """
  60. if output_name is None:
  61. output_name = template_name
  62. template_dir = os.path.join(os.path.dirname(__file__), '..', 'targets')
  63. template_path = os.path.join(template_dir, template_name)
  64. output_path = os.path.join(self.config.output_dir, output_name)
  65. if os.path.exists(template_path):
  66. shutil.copy2(template_path, output_path)
  67. return output_path
  68. else:
  69. raise FileNotFoundError(f"Template not found: {template_path}")
  70. class VscodeGenerator(ProjectGenerator):
  71. """Visual Studio Code project generator."""
  72. def get_name(self) -> str:
  73. return "vscode"
  74. def generate(self, context, project_info: Dict[str, Any]) -> bool:
  75. """Generate VS Code project files."""
  76. self._ensure_output_dir()
  77. # Create .vscode directory
  78. vscode_dir = os.path.join(self.config.output_dir, '.vscode')
  79. os.makedirs(vscode_dir, exist_ok=True)
  80. # Generate c_cpp_properties.json
  81. self._generate_cpp_properties(vscode_dir, context, project_info)
  82. # Generate tasks.json
  83. self._generate_tasks(vscode_dir, context)
  84. # Generate launch.json
  85. self._generate_launch(vscode_dir, context)
  86. # Generate settings.json
  87. self._generate_settings(vscode_dir)
  88. return True
  89. def clean(self) -> bool:
  90. """Clean VS Code files."""
  91. vscode_dir = os.path.join(self.config.output_dir, '.vscode')
  92. if os.path.exists(vscode_dir):
  93. shutil.rmtree(vscode_dir)
  94. return True
  95. def _generate_cpp_properties(self, vscode_dir: str, context, project_info: Dict) -> None:
  96. """Generate c_cpp_properties.json."""
  97. # Get toolchain info
  98. toolchain = context.toolchain_manager.get_current()
  99. compiler_path = ""
  100. if toolchain and toolchain.info:
  101. if toolchain.get_name() == "gcc":
  102. compiler_path = os.path.join(toolchain.info.path, toolchain.info.prefix + "gcc")
  103. config = {
  104. "configurations": [
  105. {
  106. "name": "RT-Thread",
  107. "includePath": [
  108. "${workspaceFolder}/**"
  109. ] + project_info.get('all_includes', []),
  110. "defines": [f"{k}={v}" if v != '1' else k
  111. for k, v in project_info.get('all_defines', {}).items()],
  112. "compilerPath": compiler_path,
  113. "cStandard": "c99",
  114. "cppStandard": "c++11",
  115. "intelliSenseMode": "gcc-arm" if "arm" in compiler_path else "gcc-x64"
  116. }
  117. ],
  118. "version": 4
  119. }
  120. output_path = os.path.join(vscode_dir, 'c_cpp_properties.json')
  121. with open(output_path, 'w') as f:
  122. json.dump(config, f, indent=4)
  123. def _generate_tasks(self, vscode_dir: str, context) -> None:
  124. """Generate tasks.json."""
  125. tasks = {
  126. "version": "2.0.0",
  127. "tasks": [
  128. {
  129. "label": "build",
  130. "type": "shell",
  131. "command": "scons",
  132. "problemMatcher": "$gcc",
  133. "group": {
  134. "kind": "build",
  135. "isDefault": True
  136. }
  137. },
  138. {
  139. "label": "clean",
  140. "type": "shell",
  141. "command": "scons -c",
  142. "problemMatcher": "$gcc"
  143. },
  144. {
  145. "label": "rebuild",
  146. "type": "shell",
  147. "command": "scons -c && scons",
  148. "problemMatcher": "$gcc"
  149. }
  150. ]
  151. }
  152. output_path = os.path.join(vscode_dir, 'tasks.json')
  153. with open(output_path, 'w') as f:
  154. json.dump(tasks, f, indent=4)
  155. def _generate_launch(self, vscode_dir: str, context) -> None:
  156. """Generate launch.json."""
  157. launch = {
  158. "version": "0.2.0",
  159. "configurations": [
  160. {
  161. "name": "Cortex Debug",
  162. "type": "cortex-debug",
  163. "request": "launch",
  164. "servertype": "openocd",
  165. "cwd": "${workspaceRoot}",
  166. "executable": "${workspaceRoot}/" + self.config.target_name,
  167. "device": "STM32F103C8",
  168. "configFiles": [
  169. "interface/stlink-v2.cfg",
  170. "target/stm32f1x.cfg"
  171. ]
  172. }
  173. ]
  174. }
  175. output_path = os.path.join(vscode_dir, 'launch.json')
  176. with open(output_path, 'w') as f:
  177. json.dump(launch, f, indent=4)
  178. def _generate_settings(self, vscode_dir: str) -> None:
  179. """Generate settings.json."""
  180. settings = {
  181. "files.associations": {
  182. "*.h": "c",
  183. "*.c": "c",
  184. "*.cpp": "cpp",
  185. "*.cc": "cpp",
  186. "*.cxx": "cpp"
  187. },
  188. "C_Cpp.errorSquiggles": "Enabled"
  189. }
  190. output_path = os.path.join(vscode_dir, 'settings.json')
  191. with open(output_path, 'w') as f:
  192. json.dump(settings, f, indent=4)
  193. class CMakeGenerator(ProjectGenerator):
  194. """CMake project generator."""
  195. def get_name(self) -> str:
  196. return "cmake"
  197. def generate(self, context, project_info: Dict[str, Any]) -> bool:
  198. """Generate CMakeLists.txt."""
  199. self._ensure_output_dir()
  200. # Get toolchain info
  201. toolchain = context.toolchain_manager.get_current()
  202. lines = [
  203. "cmake_minimum_required(VERSION 3.10)",
  204. "",
  205. "# RT-Thread CMake Project",
  206. f"project({self.config.project_name} C CXX ASM)",
  207. "",
  208. "# C Standard",
  209. "set(CMAKE_C_STANDARD 99)",
  210. "set(CMAKE_CXX_STANDARD 11)",
  211. ""
  212. ]
  213. # Toolchain configuration
  214. if toolchain and toolchain.get_name() == "gcc":
  215. lines.extend([
  216. "# Toolchain",
  217. f"set(CMAKE_C_COMPILER {toolchain.info.prefix}gcc)",
  218. f"set(CMAKE_CXX_COMPILER {toolchain.info.prefix}g++)",
  219. f"set(CMAKE_ASM_COMPILER {toolchain.info.prefix}gcc)",
  220. ""
  221. ])
  222. # Include directories
  223. lines.extend([
  224. "# Include directories",
  225. "include_directories("
  226. ])
  227. for inc in project_info.get('all_includes', []):
  228. lines.append(f" {inc}")
  229. lines.extend([")", ""])
  230. # Definitions
  231. lines.extend([
  232. "# Definitions",
  233. "add_definitions("
  234. ])
  235. for k, v in project_info.get('all_defines', {}).items():
  236. if v == '1':
  237. lines.append(f" -D{k}")
  238. else:
  239. lines.append(f" -D{k}={v}")
  240. lines.extend([")", ""])
  241. # Source files
  242. lines.extend([
  243. "# Source files",
  244. "set(SOURCES"
  245. ])
  246. for src in project_info.get('all_sources', []):
  247. lines.append(f" {src}")
  248. lines.extend([")", ""])
  249. # Executable
  250. lines.extend([
  251. "# Executable",
  252. f"add_executable(${{PROJECT_NAME}} ${{SOURCES}})",
  253. ""
  254. ])
  255. # Libraries
  256. if project_info.get('all_libs'):
  257. lines.extend([
  258. "# Libraries",
  259. f"target_link_libraries(${{PROJECT_NAME}}"
  260. ])
  261. for lib in project_info['all_libs']:
  262. lines.append(f" {lib}")
  263. lines.extend([")", ""])
  264. # Write file
  265. output_path = os.path.join(self.config.output_dir, 'CMakeLists.txt')
  266. with open(output_path, 'w') as f:
  267. f.write('\n'.join(lines))
  268. return True
  269. def clean(self) -> bool:
  270. """Clean CMake files."""
  271. files_to_remove = ['CMakeLists.txt', 'CMakeCache.txt']
  272. dirs_to_remove = ['CMakeFiles']
  273. for file in files_to_remove:
  274. file_path = os.path.join(self.config.output_dir, file)
  275. if os.path.exists(file_path):
  276. os.remove(file_path)
  277. for dir in dirs_to_remove:
  278. dir_path = os.path.join(self.config.output_dir, dir)
  279. if os.path.exists(dir_path):
  280. shutil.rmtree(dir_path)
  281. return True
  282. class GeneratorRegistry:
  283. """Registry for project generators."""
  284. def __init__(self):
  285. self.generators: Dict[str, type] = {}
  286. self._register_default_generators()
  287. def _register_default_generators(self) -> None:
  288. """Register default generators."""
  289. self.register("vscode", VscodeGenerator)
  290. self.register("vsc", VscodeGenerator) # Alias
  291. self.register("cmake", CMakeGenerator)
  292. def register(self, name: str, generator_class: type) -> None:
  293. """Register a generator class."""
  294. self.generators[name] = generator_class
  295. def create_generator(self, name: str, config: GeneratorConfig) -> ProjectGenerator:
  296. """Create a generator instance."""
  297. if name not in self.generators:
  298. raise ValueError(f"Unknown generator: {name}")
  299. return self.generators[name](config)
  300. def list_generators(self) -> List[str]:
  301. """List available generators."""
  302. return list(self.generators.keys())