Files
rt-thread/tools/ng/generator.py

368 lines
12 KiB
Python

# -*- 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())