project.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. # -*- coding: utf-8 -*-
  2. """
  3. Project and group management for RT-Thread build system.
  4. This module provides classes for managing project groups and their compilation.
  5. """
  6. import os
  7. from typing import List, Dict, Any, Optional
  8. from dataclasses import dataclass, field
  9. from SCons.Script import *
  10. @dataclass
  11. class ProjectGroup:
  12. """
  13. Represents a project group (component).
  14. This class encapsulates the information from DefineGroup calls.
  15. """
  16. name: str
  17. sources: List[str]
  18. dependencies: List[str] = field(default_factory=list)
  19. environment: Any = None # SCons Environment
  20. # Paths and defines
  21. include_paths: List[str] = field(default_factory=list)
  22. defines: Dict[str, str] = field(default_factory=dict)
  23. # Compiler flags
  24. cflags: str = ""
  25. cxxflags: str = ""
  26. asflags: str = ""
  27. ldflags: str = ""
  28. # Local options (only for this group)
  29. local_cflags: str = ""
  30. local_cxxflags: str = ""
  31. local_include_paths: List[str] = field(default_factory=list)
  32. local_defines: Dict[str, str] = field(default_factory=dict)
  33. # Libraries
  34. libs: List[str] = field(default_factory=list)
  35. lib_paths: List[str] = field(default_factory=list)
  36. # Build objects
  37. objects: List[Any] = field(default_factory=list)
  38. def build(self, env) -> List:
  39. """
  40. Build the group and return objects.
  41. Args:
  42. env: SCons Environment
  43. Returns:
  44. List of build objects
  45. """
  46. if not self.sources:
  47. return []
  48. # Clone environment if we have local options
  49. build_env = env
  50. if self._has_local_options():
  51. build_env = env.Clone()
  52. self._apply_local_options(build_env)
  53. # Apply global options
  54. self._apply_global_options(build_env)
  55. # Build objects
  56. self.objects = []
  57. for src in self.sources:
  58. if isinstance(src, str):
  59. # Build single file
  60. obj = build_env.Object(src)
  61. self.objects.extend(obj if isinstance(obj, list) else [obj])
  62. else:
  63. # Already a Node
  64. self.objects.append(src)
  65. return self.objects
  66. def _has_local_options(self) -> bool:
  67. """Check if group has local options."""
  68. return bool(
  69. self.local_cflags or
  70. self.local_cxxflags or
  71. self.local_include_paths or
  72. self.local_defines
  73. )
  74. def _apply_local_options(self, env) -> None:
  75. """Apply local options to environment."""
  76. if self.local_cflags:
  77. env.AppendUnique(CFLAGS=self.local_cflags.split())
  78. if self.local_cxxflags:
  79. env.AppendUnique(CXXFLAGS=self.local_cxxflags.split())
  80. if self.local_include_paths:
  81. paths = [os.path.abspath(p) for p in self.local_include_paths]
  82. env.AppendUnique(CPPPATH=paths)
  83. if self.local_defines:
  84. env.AppendUnique(CPPDEFINES=self.local_defines)
  85. def _apply_global_options(self, env) -> None:
  86. """Apply global options to environment."""
  87. # These options affect dependent groups too
  88. if self.include_paths:
  89. paths = [os.path.abspath(p) for p in self.include_paths]
  90. env.AppendUnique(CPPPATH=paths)
  91. if self.defines:
  92. env.AppendUnique(CPPDEFINES=self.defines)
  93. if self.cflags and 'CFLAGS' not in env:
  94. env['CFLAGS'] = self.cflags
  95. if self.cxxflags and 'CXXFLAGS' not in env:
  96. env['CXXFLAGS'] = self.cxxflags
  97. if self.libs:
  98. env.AppendUnique(LIBS=self.libs)
  99. if self.lib_paths:
  100. paths = [os.path.abspath(p) for p in self.lib_paths]
  101. env.AppendUnique(LIBPATH=paths)
  102. def get_info(self) -> Dict[str, Any]:
  103. """
  104. Get group information for project generators.
  105. Returns:
  106. Dictionary with group information
  107. """
  108. return {
  109. 'name': self.name,
  110. 'sources': self.sources,
  111. 'include_paths': self.include_paths + self.local_include_paths,
  112. 'defines': {**self.defines, **self.local_defines},
  113. 'cflags': f"{self.cflags} {self.local_cflags}".strip(),
  114. 'cxxflags': f"{self.cxxflags} {self.local_cxxflags}".strip(),
  115. 'libs': self.libs,
  116. 'lib_paths': self.lib_paths
  117. }
  118. class ProjectRegistry:
  119. """
  120. Registry for all project groups.
  121. This class manages all registered project groups and provides
  122. methods for querying and merging them.
  123. """
  124. def __init__(self):
  125. self.groups: List[ProjectGroup] = []
  126. self._group_index: Dict[str, ProjectGroup] = {}
  127. def register_group(self, group: ProjectGroup) -> None:
  128. """
  129. Register a project group.
  130. Args:
  131. group: ProjectGroup instance
  132. """
  133. self.groups.append(group)
  134. self._group_index[group.name] = group
  135. def get_group(self, name: str) -> Optional[ProjectGroup]:
  136. """
  137. Get group by name.
  138. Args:
  139. name: Group name
  140. Returns:
  141. ProjectGroup or None
  142. """
  143. return self._group_index.get(name)
  144. def get_all_groups(self) -> List[ProjectGroup]:
  145. """Get all registered groups."""
  146. return self.groups.copy()
  147. def get_groups_by_dependency(self, dependency: str) -> List[ProjectGroup]:
  148. """
  149. Get groups that depend on a specific macro.
  150. Args:
  151. dependency: Dependency name
  152. Returns:
  153. List of matching groups
  154. """
  155. return [g for g in self.groups if dependency in g.dependencies]
  156. def merge_groups(self, env) -> List:
  157. """
  158. Merge all groups into a single list of objects.
  159. Args:
  160. env: SCons Environment
  161. Returns:
  162. List of all build objects
  163. """
  164. all_objects = []
  165. for group in self.groups:
  166. if group.objects:
  167. all_objects.extend(group.objects)
  168. return all_objects
  169. def get_project_info(self) -> Dict[str, Any]:
  170. """
  171. Get complete project information for generators.
  172. Returns:
  173. Dictionary with project information
  174. """
  175. # Collect all unique values
  176. all_sources = []
  177. all_includes = set()
  178. all_defines = {}
  179. all_libs = []
  180. all_lib_paths = set()
  181. for group in self.groups:
  182. info = group.get_info()
  183. # Sources
  184. all_sources.extend(info['sources'])
  185. # Include paths
  186. all_includes.update(info['include_paths'])
  187. # Defines
  188. all_defines.update(info['defines'])
  189. # Libraries
  190. all_libs.extend(info['libs'])
  191. all_lib_paths.update(info['lib_paths'])
  192. return {
  193. 'groups': [g.get_info() for g in self.groups],
  194. 'all_sources': all_sources,
  195. 'all_includes': sorted(list(all_includes)),
  196. 'all_defines': all_defines,
  197. 'all_libs': all_libs,
  198. 'all_lib_paths': sorted(list(all_lib_paths))
  199. }
  200. def clear(self) -> None:
  201. """Clear all registered groups."""
  202. self.groups.clear()
  203. self._group_index.clear()