summaryrefslogtreecommitdiff
path: root/tools/ng/project.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/ng/project.py')
-rw-r--r--tools/ng/project.py260
1 files changed, 260 insertions, 0 deletions
diff --git a/tools/ng/project.py b/tools/ng/project.py
new file mode 100644
index 0000000000..4bd3bfab50
--- /dev/null
+++ b/tools/ng/project.py
@@ -0,0 +1,260 @@
+# -*- coding: utf-8 -*-
+"""
+Project and group management for RT-Thread build system.
+
+This module provides classes for managing project groups and their compilation.
+"""
+
+import os
+from typing import List, Dict, Any, Optional
+from dataclasses import dataclass, field
+from SCons.Script import *
+
+
+@dataclass
+class ProjectGroup:
+ """
+ Represents a project group (component).
+
+ This class encapsulates the information from DefineGroup calls.
+ """
+ name: str
+ sources: List[str]
+ dependencies: List[str] = field(default_factory=list)
+ environment: Any = None # SCons Environment
+
+ # Paths and defines
+ include_paths: List[str] = field(default_factory=list)
+ defines: Dict[str, str] = field(default_factory=dict)
+
+ # Compiler flags
+ cflags: str = ""
+ cxxflags: str = ""
+ asflags: str = ""
+ ldflags: str = ""
+
+ # Local options (only for this group)
+ local_cflags: str = ""
+ local_cxxflags: str = ""
+ local_include_paths: List[str] = field(default_factory=list)
+ local_defines: Dict[str, str] = field(default_factory=dict)
+
+ # Libraries
+ libs: List[str] = field(default_factory=list)
+ lib_paths: List[str] = field(default_factory=list)
+
+ # Build objects
+ objects: List[Any] = field(default_factory=list)
+
+ def build(self, env) -> List:
+ """
+ Build the group and return objects.
+
+ Args:
+ env: SCons Environment
+
+ Returns:
+ List of build objects
+ """
+ if not self.sources:
+ return []
+
+ # Clone environment if we have local options
+ build_env = env
+ if self._has_local_options():
+ build_env = env.Clone()
+ self._apply_local_options(build_env)
+
+ # Apply global options
+ self._apply_global_options(build_env)
+
+ # Build objects
+ self.objects = []
+ for src in self.sources:
+ if isinstance(src, str):
+ # Build single file
+ obj = build_env.Object(src)
+ self.objects.extend(obj if isinstance(obj, list) else [obj])
+ else:
+ # Already a Node
+ self.objects.append(src)
+
+ return self.objects
+
+ def _has_local_options(self) -> bool:
+ """Check if group has local options."""
+ return bool(
+ self.local_cflags or
+ self.local_cxxflags or
+ self.local_include_paths or
+ self.local_defines
+ )
+
+ def _apply_local_options(self, env) -> None:
+ """Apply local options to environment."""
+ if self.local_cflags:
+ env.AppendUnique(CFLAGS=self.local_cflags.split())
+
+ if self.local_cxxflags:
+ env.AppendUnique(CXXFLAGS=self.local_cxxflags.split())
+
+ if self.local_include_paths:
+ paths = [os.path.abspath(p) for p in self.local_include_paths]
+ env.AppendUnique(CPPPATH=paths)
+
+ if self.local_defines:
+ env.AppendUnique(CPPDEFINES=self.local_defines)
+
+ def _apply_global_options(self, env) -> None:
+ """Apply global options to environment."""
+ # These options affect dependent groups too
+ if self.include_paths:
+ paths = [os.path.abspath(p) for p in self.include_paths]
+ env.AppendUnique(CPPPATH=paths)
+
+ if self.defines:
+ env.AppendUnique(CPPDEFINES=self.defines)
+
+ if self.cflags and 'CFLAGS' not in env:
+ env['CFLAGS'] = self.cflags
+
+ if self.cxxflags and 'CXXFLAGS' not in env:
+ env['CXXFLAGS'] = self.cxxflags
+
+ if self.libs:
+ env.AppendUnique(LIBS=self.libs)
+
+ if self.lib_paths:
+ paths = [os.path.abspath(p) for p in self.lib_paths]
+ env.AppendUnique(LIBPATH=paths)
+
+ def get_info(self) -> Dict[str, Any]:
+ """
+ Get group information for project generators.
+
+ Returns:
+ Dictionary with group information
+ """
+ return {
+ 'name': self.name,
+ 'sources': self.sources,
+ 'include_paths': self.include_paths + self.local_include_paths,
+ 'defines': {**self.defines, **self.local_defines},
+ 'cflags': f"{self.cflags} {self.local_cflags}".strip(),
+ 'cxxflags': f"{self.cxxflags} {self.local_cxxflags}".strip(),
+ 'libs': self.libs,
+ 'lib_paths': self.lib_paths
+ }
+
+
+class ProjectRegistry:
+ """
+ Registry for all project groups.
+
+ This class manages all registered project groups and provides
+ methods for querying and merging them.
+ """
+
+ def __init__(self):
+ self.groups: List[ProjectGroup] = []
+ self._group_index: Dict[str, ProjectGroup] = {}
+
+ def register_group(self, group: ProjectGroup) -> None:
+ """
+ Register a project group.
+
+ Args:
+ group: ProjectGroup instance
+ """
+ self.groups.append(group)
+ self._group_index[group.name] = group
+
+ def get_group(self, name: str) -> Optional[ProjectGroup]:
+ """
+ Get group by name.
+
+ Args:
+ name: Group name
+
+ Returns:
+ ProjectGroup or None
+ """
+ return self._group_index.get(name)
+
+ def get_all_groups(self) -> List[ProjectGroup]:
+ """Get all registered groups."""
+ return self.groups.copy()
+
+ def get_groups_by_dependency(self, dependency: str) -> List[ProjectGroup]:
+ """
+ Get groups that depend on a specific macro.
+
+ Args:
+ dependency: Dependency name
+
+ Returns:
+ List of matching groups
+ """
+ return [g for g in self.groups if dependency in g.dependencies]
+
+ def merge_groups(self, env) -> List:
+ """
+ Merge all groups into a single list of objects.
+
+ Args:
+ env: SCons Environment
+
+ Returns:
+ List of all build objects
+ """
+ all_objects = []
+
+ for group in self.groups:
+ if group.objects:
+ all_objects.extend(group.objects)
+
+ return all_objects
+
+ def get_project_info(self) -> Dict[str, Any]:
+ """
+ Get complete project information for generators.
+
+ Returns:
+ Dictionary with project information
+ """
+ # Collect all unique values
+ all_sources = []
+ all_includes = set()
+ all_defines = {}
+ all_libs = []
+ all_lib_paths = set()
+
+ for group in self.groups:
+ info = group.get_info()
+
+ # Sources
+ all_sources.extend(info['sources'])
+
+ # Include paths
+ all_includes.update(info['include_paths'])
+
+ # Defines
+ all_defines.update(info['defines'])
+
+ # Libraries
+ all_libs.extend(info['libs'])
+ all_lib_paths.update(info['lib_paths'])
+
+ return {
+ 'groups': [g.get_info() for g in self.groups],
+ 'all_sources': all_sources,
+ 'all_includes': sorted(list(all_includes)),
+ 'all_defines': all_defines,
+ 'all_libs': all_libs,
+ 'all_lib_paths': sorted(list(all_lib_paths))
+ }
+
+ def clear(self) -> None:
+ """Clear all registered groups."""
+ self.groups.clear()
+ self._group_index.clear() \ No newline at end of file