1# -*- coding: utf-8 -*-
2"""
3Environment extensions for RT-Thread build system.
4
5This module provides methods that are injected into the SCons Environment object
6to provide RT-Thread-specific functionality.
7"""
8
9import os
10from typing import List, Union, Dict, Any, Optional
11from SCons.Script import *
12
13from .core import BuildContext
14from .project import ProjectGroup
15
16
17class RTEnv:
18    """
19    RT-Thread environment extensions (RTEnv).
20
21    This class provides methods that are added to the SCons Environment object.
22    """
23
24    @staticmethod
25    def inject_methods(env):
26        """
27        Inject RT-Thread methods into SCons Environment.
28
29        Args:
30            env: SCons Environment object
31        """
32        # Core build methods
33        env.AddMethod(RTEnv.DefineGroup, 'DefineGroup')
34        env.AddMethod(RTEnv.GetDepend, 'GetDepend')
35        env.AddMethod(RTEnv.SrcRemove, 'SrcRemove')
36        env.AddMethod(RTEnv.GetCurrentDir, 'GetCurrentDir')
37        env.AddMethod(RTEnv.BuildPackage, 'BuildPackage')
38
39        # Utility methods
40        env.AddMethod(RTEnv.Glob, 'GlobFiles')
41        env.AddMethod(RTEnv.GetBuildOptions, 'GetBuildOptions')
42        env.AddMethod(RTEnv.GetContext, 'GetContext')
43
44        # Path utilities
45        env.AddMethod(RTEnv.GetRTTRoot, 'GetRTTRoot')
46        env.AddMethod(RTEnv.GetBSPRoot, 'GetBSPRoot')
47
48    @staticmethod
49    def DefineGroup(env, name: str, src: List[str], depend: Any = None, **kwargs) -> List:
50        """
51        Define a component group.
52
53        This method maintains compatibility with the original DefineGroup function
54        while using the new object-oriented implementation.
55
56        Args:
57            env: SCons Environment
58            name: Group name
59            src: Source file list
60            depend: Dependency conditions
61            **kwargs: Additional parameters (CPPPATH, CPPDEFINES, etc.)
62
63        Returns:
64            List of build objects
65        """
66        context = BuildContext.get_current()
67        if not context:
68            raise RuntimeError("BuildContext not initialized")
69
70        # Check dependencies
71        if depend and not env.GetDepend(depend):
72            return []
73
74        # Process source files
75        if isinstance(src, str):
76            src = [src]
77
78        # Create project group
79        group = ProjectGroup(
80            name=name,
81            sources=src,
82            dependencies=depend if isinstance(depend, list) else [depend] if depend else [],
83            environment=env
84        )
85
86        # Process parameters
87        group.include_paths = kwargs.get('CPPPATH', [])
88        group.defines = kwargs.get('CPPDEFINES', {})
89        group.cflags = kwargs.get('CFLAGS', '')
90        group.cxxflags = kwargs.get('CXXFLAGS', '')
91        group.local_cflags = kwargs.get('LOCAL_CFLAGS', '')
92        group.local_cxxflags = kwargs.get('LOCAL_CXXFLAGS', '')
93        group.local_include_paths = kwargs.get('LOCAL_CPPPATH', [])
94        group.local_defines = kwargs.get('LOCAL_CPPDEFINES', {})
95        group.libs = kwargs.get('LIBS', [])
96        group.lib_paths = kwargs.get('LIBPATH', [])
97
98        # Build objects
99        objects = group.build(env)
100
101        # Register group
102        context.register_project_group(group)
103
104        return objects
105
106    @staticmethod
107    def GetDepend(env, depend: Any) -> bool:
108        """
109        Check if dependency is satisfied.
110
111        Args:
112            env: SCons Environment
113            depend: Dependency name or list of names
114
115        Returns:
116            True if dependency is satisfied
117        """
118        context = BuildContext.get_current()
119        if not context:
120            # Fallback to checking environment variables
121            if isinstance(depend, str):
122                return env.get(depend, False)
123            elif isinstance(depend, list):
124                return all(env.get(d, False) for d in depend)
125            return False
126
127        return context.get_dependency(depend)
128
129    @staticmethod
130    def SrcRemove(env, src: List[str], remove: List[str]) -> None:
131        """
132        Remove files from source list.
133
134        Args:
135            env: SCons Environment
136            src: Source file list (modified in place)
137            remove: Files to remove
138        """
139        if not isinstance(remove, list):
140            remove = [remove]
141
142        for item in remove:
143            # Handle both exact matches and pattern matches
144            if item in src:
145                src.remove(item)
146            else:
147                # Try pattern matching
148                import fnmatch
149                to_remove = [f for f in src if fnmatch.fnmatch(f, item)]
150                for f in to_remove:
151                    src.remove(f)
152
153    @staticmethod
154    def GetCurrentDir(env) -> str:
155        """
156        Get current directory.
157
158        Args:
159            env: SCons Environment
160
161        Returns:
162            Current directory path
163        """
164        return Dir('.').abspath
165
166    @staticmethod
167    def BuildPackage(env, package_path: str = None) -> List:
168        """
169        Build package from package.json.
170
171        Args:
172            env: SCons Environment
173            package_path: Path to package.json. If None, looks for package.json in current directory.
174
175        Returns:
176            List of build objects
177        """
178        # Import the existing package module
179        import sys
180        import os
181
182        # Get the building module path
183        building_path = os.path.dirname(os.path.abspath(__file__))
184        tools_path = os.path.dirname(building_path)
185
186        # Add to path if not already there
187        if tools_path not in sys.path:
188            sys.path.insert(0, tools_path)
189
190        # Import and use the existing BuildPackage
191        try:
192            from package import BuildPackage as build_package_func
193
194            # BuildPackage uses global functions, so we need to set up the context
195            # Save current directory
196            current_dir = os.getcwd()
197
198            # Change to the directory where we want to build
199            if package_path is None:
200                work_dir = env.GetCurrentDir()
201            elif os.path.isdir(package_path):
202                work_dir = package_path
203            else:
204                work_dir = os.path.dirname(package_path)
205
206            os.chdir(work_dir)
207
208            try:
209                # Call the original BuildPackage
210                result = build_package_func(package_path)
211            finally:
212                # Restore directory
213                os.chdir(current_dir)
214
215            return result
216
217        except ImportError:
218            # Fallback if import fails
219            context = BuildContext.get_current()
220            if context:
221                context.logger.error("Failed to import package module")
222            return []
223
224    @staticmethod
225    def Glob(env, pattern: str) -> List[str]:
226        """
227        Enhanced glob with better error handling.
228
229        Args:
230            env: SCons Environment
231            pattern: File pattern
232
233        Returns:
234            List of matching files
235        """
236        try:
237            files = Glob(pattern, strings=True)
238            return sorted(files)  # Sort for consistent ordering
239        except Exception as e:
240            context = BuildContext.get_current()
241            if context:
242                context.logger.warning(f"Glob pattern '{pattern}' failed: {e}")
243            return []
244
245    @staticmethod
246    def GetBuildOptions(env) -> Dict[str, Any]:
247        """
248        Get build options.
249
250        Args:
251            env: SCons Environment
252
253        Returns:
254            Dictionary of build options
255        """
256        context = BuildContext.get_current()
257        if context:
258            return context.build_options
259        return {}
260
261    @staticmethod
262    def GetContext(env) -> Optional[BuildContext]:
263        """
264        Get current build context.
265
266        Args:
267            env: SCons Environment
268
269        Returns:
270            BuildContext instance or None
271        """
272        return BuildContext.get_current()
273
274    @staticmethod
275    def GetRTTRoot(env) -> str:
276        """
277        Get RT-Thread root directory.
278
279        Args:
280            env: SCons Environment
281
282        Returns:
283            RT-Thread root path
284        """
285        return env.get('RTT_ROOT', '')
286
287    @staticmethod
288    def GetBSPRoot(env) -> str:
289        """
290        Get BSP root directory.
291
292        Args:
293            env: SCons Environment
294
295        Returns:
296            BSP root path
297        """
298        return env.get('BSP_ROOT', '')