| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- # -*- coding: utf-8 -*-
- """
- Project generator framework for RT-Thread build system.
- This module provides the base classes for project generators (MDK, IAR, VS Code, etc.).
- """
- import os
- import shutil
- import json
- import xml.etree.ElementTree as ET
- from abc import ABC, abstractmethod
- from typing import Dict, List, Any, Optional
- from dataclasses import dataclass
- from .utils import PathService
- @dataclass
- class GeneratorConfig:
- """Configuration for project generators."""
- output_dir: str
- project_name: str = "rtthread"
- target_name: str = "rtthread.elf"
-
-
- class ProjectGenerator(ABC):
- """Abstract base class for project generators."""
-
- def __init__(self, config: GeneratorConfig):
- self.config = config
- self.path_service = PathService(os.getcwd())
-
- @abstractmethod
- def get_name(self) -> str:
- """Get generator name."""
- pass
-
- @abstractmethod
- def generate(self, context, project_info: Dict[str, Any]) -> bool:
- """
- Generate project files.
-
- Args:
- context: BuildContext instance
- project_info: Project information from registry
-
- Returns:
- True if successful
- """
- pass
-
- @abstractmethod
- def clean(self) -> bool:
- """
- Clean generated files.
-
- Returns:
- True if successful
- """
- pass
-
- def _ensure_output_dir(self) -> None:
- """Ensure output directory exists."""
- os.makedirs(self.config.output_dir, exist_ok=True)
-
- def _copy_template(self, template_name: str, output_name: str = None) -> str:
- """
- Copy template file to output directory.
-
- Args:
- template_name: Template file name
- output_name: Output file name (defaults to template_name)
-
- Returns:
- Output file path
- """
- if output_name is None:
- output_name = template_name
-
- template_dir = os.path.join(os.path.dirname(__file__), '..', 'targets')
- template_path = os.path.join(template_dir, template_name)
- output_path = os.path.join(self.config.output_dir, output_name)
-
- if os.path.exists(template_path):
- shutil.copy2(template_path, output_path)
- return output_path
- else:
- raise FileNotFoundError(f"Template not found: {template_path}")
- class VscodeGenerator(ProjectGenerator):
- """Visual Studio Code project generator."""
-
- def get_name(self) -> str:
- return "vscode"
-
- def generate(self, context, project_info: Dict[str, Any]) -> bool:
- """Generate VS Code project files."""
- self._ensure_output_dir()
-
- # Create .vscode directory
- vscode_dir = os.path.join(self.config.output_dir, '.vscode')
- os.makedirs(vscode_dir, exist_ok=True)
-
- # Generate c_cpp_properties.json
- self._generate_cpp_properties(vscode_dir, context, project_info)
-
- # Generate tasks.json
- self._generate_tasks(vscode_dir, context)
-
- # Generate launch.json
- self._generate_launch(vscode_dir, context)
-
- # Generate settings.json
- self._generate_settings(vscode_dir)
-
- return True
-
- def clean(self) -> bool:
- """Clean VS Code files."""
- vscode_dir = os.path.join(self.config.output_dir, '.vscode')
- if os.path.exists(vscode_dir):
- shutil.rmtree(vscode_dir)
- return True
-
- def _generate_cpp_properties(self, vscode_dir: str, context, project_info: Dict) -> None:
- """Generate c_cpp_properties.json."""
- # Get toolchain info
- toolchain = context.toolchain_manager.get_current()
- compiler_path = ""
- if toolchain and toolchain.info:
- if toolchain.get_name() == "gcc":
- compiler_path = os.path.join(toolchain.info.path, toolchain.info.prefix + "gcc")
-
- config = {
- "configurations": [
- {
- "name": "RT-Thread",
- "includePath": [
- "${workspaceFolder}/**"
- ] + project_info.get('all_includes', []),
- "defines": [f"{k}={v}" if v != '1' else k
- for k, v in project_info.get('all_defines', {}).items()],
- "compilerPath": compiler_path,
- "cStandard": "c99",
- "cppStandard": "c++11",
- "intelliSenseMode": "gcc-arm" if "arm" in compiler_path else "gcc-x64"
- }
- ],
- "version": 4
- }
-
- output_path = os.path.join(vscode_dir, 'c_cpp_properties.json')
- with open(output_path, 'w') as f:
- json.dump(config, f, indent=4)
-
- def _generate_tasks(self, vscode_dir: str, context) -> None:
- """Generate tasks.json."""
- tasks = {
- "version": "2.0.0",
- "tasks": [
- {
- "label": "build",
- "type": "shell",
- "command": "scons",
- "problemMatcher": "$gcc",
- "group": {
- "kind": "build",
- "isDefault": True
- }
- },
- {
- "label": "clean",
- "type": "shell",
- "command": "scons -c",
- "problemMatcher": "$gcc"
- },
- {
- "label": "rebuild",
- "type": "shell",
- "command": "scons -c && scons",
- "problemMatcher": "$gcc"
- }
- ]
- }
-
- output_path = os.path.join(vscode_dir, 'tasks.json')
- with open(output_path, 'w') as f:
- json.dump(tasks, f, indent=4)
-
- def _generate_launch(self, vscode_dir: str, context) -> None:
- """Generate launch.json."""
- launch = {
- "version": "0.2.0",
- "configurations": [
- {
- "name": "Cortex Debug",
- "type": "cortex-debug",
- "request": "launch",
- "servertype": "openocd",
- "cwd": "${workspaceRoot}",
- "executable": "${workspaceRoot}/" + self.config.target_name,
- "device": "STM32F103C8",
- "configFiles": [
- "interface/stlink-v2.cfg",
- "target/stm32f1x.cfg"
- ]
- }
- ]
- }
-
- output_path = os.path.join(vscode_dir, 'launch.json')
- with open(output_path, 'w') as f:
- json.dump(launch, f, indent=4)
-
- def _generate_settings(self, vscode_dir: str) -> None:
- """Generate settings.json."""
- settings = {
- "files.associations": {
- "*.h": "c",
- "*.c": "c",
- "*.cpp": "cpp",
- "*.cc": "cpp",
- "*.cxx": "cpp"
- },
- "C_Cpp.errorSquiggles": "Enabled"
- }
-
- output_path = os.path.join(vscode_dir, 'settings.json')
- with open(output_path, 'w') as f:
- json.dump(settings, f, indent=4)
- class CMakeGenerator(ProjectGenerator):
- """CMake project generator."""
-
- def get_name(self) -> str:
- return "cmake"
-
- def generate(self, context, project_info: Dict[str, Any]) -> bool:
- """Generate CMakeLists.txt."""
- self._ensure_output_dir()
-
- # Get toolchain info
- toolchain = context.toolchain_manager.get_current()
-
- lines = [
- "cmake_minimum_required(VERSION 3.10)",
- "",
- "# RT-Thread CMake Project",
- f"project({self.config.project_name} C CXX ASM)",
- "",
- "# C Standard",
- "set(CMAKE_C_STANDARD 99)",
- "set(CMAKE_CXX_STANDARD 11)",
- ""
- ]
-
- # Toolchain configuration
- if toolchain and toolchain.get_name() == "gcc":
- lines.extend([
- "# Toolchain",
- f"set(CMAKE_C_COMPILER {toolchain.info.prefix}gcc)",
- f"set(CMAKE_CXX_COMPILER {toolchain.info.prefix}g++)",
- f"set(CMAKE_ASM_COMPILER {toolchain.info.prefix}gcc)",
- ""
- ])
-
- # Include directories
- lines.extend([
- "# Include directories",
- "include_directories("
- ])
- for inc in project_info.get('all_includes', []):
- lines.append(f" {inc}")
- lines.extend([")", ""])
-
- # Definitions
- lines.extend([
- "# Definitions",
- "add_definitions("
- ])
- for k, v in project_info.get('all_defines', {}).items():
- if v == '1':
- lines.append(f" -D{k}")
- else:
- lines.append(f" -D{k}={v}")
- lines.extend([")", ""])
-
- # Source files
- lines.extend([
- "# Source files",
- "set(SOURCES"
- ])
- for src in project_info.get('all_sources', []):
- lines.append(f" {src}")
- lines.extend([")", ""])
-
- # Executable
- lines.extend([
- "# Executable",
- f"add_executable(${{PROJECT_NAME}} ${{SOURCES}})",
- ""
- ])
-
- # Libraries
- if project_info.get('all_libs'):
- lines.extend([
- "# Libraries",
- f"target_link_libraries(${{PROJECT_NAME}}"
- ])
- for lib in project_info['all_libs']:
- lines.append(f" {lib}")
- lines.extend([")", ""])
-
- # Write file
- output_path = os.path.join(self.config.output_dir, 'CMakeLists.txt')
- with open(output_path, 'w') as f:
- f.write('\n'.join(lines))
-
- return True
-
- def clean(self) -> bool:
- """Clean CMake files."""
- files_to_remove = ['CMakeLists.txt', 'CMakeCache.txt']
- dirs_to_remove = ['CMakeFiles']
-
- for file in files_to_remove:
- file_path = os.path.join(self.config.output_dir, file)
- if os.path.exists(file_path):
- os.remove(file_path)
-
- for dir in dirs_to_remove:
- dir_path = os.path.join(self.config.output_dir, dir)
- if os.path.exists(dir_path):
- shutil.rmtree(dir_path)
-
- return True
- class GeneratorRegistry:
- """Registry for project generators."""
-
- def __init__(self):
- self.generators: Dict[str, type] = {}
- self._register_default_generators()
-
- def _register_default_generators(self) -> None:
- """Register default generators."""
- self.register("vscode", VscodeGenerator)
- self.register("vsc", VscodeGenerator) # Alias
- self.register("cmake", CMakeGenerator)
-
- def register(self, name: str, generator_class: type) -> None:
- """Register a generator class."""
- self.generators[name] = generator_class
-
- def create_generator(self, name: str, config: GeneratorConfig) -> ProjectGenerator:
- """Create a generator instance."""
- if name not in self.generators:
- raise ValueError(f"Unknown generator: {name}")
-
- return self.generators[name](config)
-
- def list_generators(self) -> List[str]:
- """List available generators."""
- return list(self.generators.keys())
|