123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- # -*- coding: utf-8 -*-
- """
- Toolchain management for RT-Thread build system.
- This module provides abstraction for different toolchains (GCC, Keil, IAR, etc.).
- """
- import os
- import shutil
- import subprocess
- from abc import ABC, abstractmethod
- from typing import Dict, List, Optional, Tuple
- from dataclasses import dataclass
- @dataclass
- class ToolchainInfo:
- """Toolchain information."""
- name: str
- version: str
- path: str
- prefix: str = ""
- suffix: str = ""
-
-
- class Toolchain(ABC):
- """Abstract base class for toolchains."""
-
- def __init__(self):
- self.info = None
-
- @abstractmethod
- def get_name(self) -> str:
- """Get toolchain name."""
- pass
-
- @abstractmethod
- def detect(self) -> bool:
- """Detect if toolchain is available."""
- pass
-
- @abstractmethod
- def configure_environment(self, env) -> None:
- """Configure SCons environment for this toolchain."""
- pass
-
- @abstractmethod
- def get_compile_flags(self, cpu: str, fpu: str = None, float_abi: str = None) -> Dict[str, str]:
- """Get compilation flags for target CPU."""
- pass
-
- def get_version(self) -> Optional[str]:
- """Get toolchain version."""
- return self.info.version if self.info else None
-
- def _run_command(self, cmd: List[str]) -> Tuple[int, str, str]:
- """Run command and return (returncode, stdout, stderr)."""
- try:
- result = subprocess.run(cmd, capture_output=True, text=True)
- return result.returncode, result.stdout, result.stderr
- except Exception as e:
- return -1, "", str(e)
- class GccToolchain(Toolchain):
- """GCC toolchain implementation."""
-
- def __init__(self, prefix: str = ""):
- super().__init__()
- self.prefix = prefix or "arm-none-eabi-"
-
- def get_name(self) -> str:
- return "gcc"
-
- def detect(self) -> bool:
- """Detect GCC toolchain."""
- gcc_path = shutil.which(self.prefix + "gcc")
- if not gcc_path:
- return False
-
- # Get version
- ret, stdout, _ = self._run_command([gcc_path, "--version"])
- if ret == 0:
- lines = stdout.split('\n')
- if lines:
- version = lines[0].split()[-1]
- self.info = ToolchainInfo(
- name="gcc",
- version=version,
- path=os.path.dirname(gcc_path),
- prefix=self.prefix
- )
- return True
-
- return False
-
- def configure_environment(self, env) -> None:
- """Configure environment for GCC."""
- env['CC'] = self.prefix + 'gcc'
- env['CXX'] = self.prefix + 'g++'
- env['AS'] = self.prefix + 'gcc'
- env['AR'] = self.prefix + 'ar'
- env['LINK'] = self.prefix + 'gcc'
- env['SIZE'] = self.prefix + 'size'
- env['OBJDUMP'] = self.prefix + 'objdump'
- env['OBJCPY'] = self.prefix + 'objcopy'
-
- # Set default flags
- env['ARFLAGS'] = '-rc'
- env['ASFLAGS'] = '-x assembler-with-cpp'
-
- # Path
- if self.info and self.info.path:
- env.PrependENVPath('PATH', self.info.path)
-
- def get_compile_flags(self, cpu: str, fpu: str = None, float_abi: str = None) -> Dict[str, str]:
- """Get GCC compilation flags."""
- flags = {
- 'CFLAGS': [],
- 'CXXFLAGS': [],
- 'ASFLAGS': [],
- 'LDFLAGS': []
- }
-
- # CPU flags
- cpu_flags = {
- 'cortex-m0': '-mcpu=cortex-m0 -mthumb',
- 'cortex-m0+': '-mcpu=cortex-m0plus -mthumb',
- 'cortex-m3': '-mcpu=cortex-m3 -mthumb',
- 'cortex-m4': '-mcpu=cortex-m4 -mthumb',
- 'cortex-m7': '-mcpu=cortex-m7 -mthumb',
- 'cortex-m23': '-mcpu=cortex-m23 -mthumb',
- 'cortex-m33': '-mcpu=cortex-m33 -mthumb',
- 'cortex-a7': '-mcpu=cortex-a7',
- 'cortex-a9': '-mcpu=cortex-a9'
- }
-
- if cpu in cpu_flags:
- base_flags = cpu_flags[cpu]
- for key in ['CFLAGS', 'CXXFLAGS', 'ASFLAGS']:
- flags[key].append(base_flags)
-
- # FPU flags
- if fpu:
- fpu_flag = f'-mfpu={fpu}'
- for key in ['CFLAGS', 'CXXFLAGS']:
- flags[key].append(fpu_flag)
-
- # Float ABI
- if float_abi:
- abi_flag = f'-mfloat-abi={float_abi}'
- for key in ['CFLAGS', 'CXXFLAGS']:
- flags[key].append(abi_flag)
-
- # Common flags
- common_flags = ['-ffunction-sections', '-fdata-sections']
- flags['CFLAGS'].extend(common_flags)
- flags['CXXFLAGS'].extend(common_flags)
-
- # Linker flags
- flags['LDFLAGS'].extend(['-Wl,--gc-sections'])
-
- # Convert lists to strings
- return {k: ' '.join(v) for k, v in flags.items()}
- class ArmccToolchain(Toolchain):
- """ARM Compiler (Keil) toolchain implementation."""
-
- def get_name(self) -> str:
- return "armcc"
-
- def detect(self) -> bool:
- """Detect ARM Compiler toolchain."""
- armcc_path = shutil.which("armcc")
- if not armcc_path:
- # Try common Keil installation paths
- keil_paths = [
- r"C:\Keil_v5\ARM\ARMCC\bin",
- r"C:\Keil\ARM\ARMCC\bin",
- "/opt/arm/bin"
- ]
- for path in keil_paths:
- test_path = os.path.join(path, "armcc")
- if os.path.exists(test_path):
- armcc_path = test_path
- break
-
- if not armcc_path:
- return False
-
- # Get version
- ret, stdout, _ = self._run_command([armcc_path, "--version"])
- if ret == 0:
- lines = stdout.split('\n')
- for line in lines:
- if "ARM Compiler" in line:
- version = line.split()[-1]
- self.info = ToolchainInfo(
- name="armcc",
- version=version,
- path=os.path.dirname(armcc_path)
- )
- return True
-
- return False
-
- def configure_environment(self, env) -> None:
- """Configure environment for ARM Compiler."""
- env['CC'] = 'armcc'
- env['CXX'] = 'armcc'
- env['AS'] = 'armasm'
- env['AR'] = 'armar'
- env['LINK'] = 'armlink'
-
- # ARM Compiler specific settings
- env['ARCOM'] = '$AR --create $TARGET $SOURCES'
- env['LIBPREFIX'] = ''
- env['LIBSUFFIX'] = '.lib'
- env['LIBLINKPREFIX'] = ''
- env['LIBLINKSUFFIX'] = '.lib'
- env['LIBDIRPREFIX'] = '--userlibpath '
-
- # Path
- if self.info and self.info.path:
- env.PrependENVPath('PATH', self.info.path)
-
- def get_compile_flags(self, cpu: str, fpu: str = None, float_abi: str = None) -> Dict[str, str]:
- """Get ARM Compiler flags."""
- flags = {
- 'CFLAGS': [],
- 'CXXFLAGS': [],
- 'ASFLAGS': [],
- 'LDFLAGS': []
- }
-
- # CPU selection
- cpu_map = {
- 'cortex-m0': '--cpu Cortex-M0',
- 'cortex-m0+': '--cpu Cortex-M0+',
- 'cortex-m3': '--cpu Cortex-M3',
- 'cortex-m4': '--cpu Cortex-M4',
- 'cortex-m7': '--cpu Cortex-M7'
- }
-
- if cpu in cpu_map:
- cpu_flag = cpu_map[cpu]
- for key in flags:
- flags[key].append(cpu_flag)
-
- # Common flags
- flags['CFLAGS'].extend(['--c99', '--gnu'])
- flags['CXXFLAGS'].extend(['--cpp', '--gnu'])
-
- return {k: ' '.join(v) for k, v in flags.items()}
- class IarToolchain(Toolchain):
- """IAR toolchain implementation."""
-
- def get_name(self) -> str:
- return "iar"
-
- def detect(self) -> bool:
- """Detect IAR toolchain."""
- iccarm_path = shutil.which("iccarm")
- if not iccarm_path:
- # Try common IAR installation paths
- iar_paths = [
- r"C:\Program Files (x86)\IAR Systems\Embedded Workbench 8.0\arm\bin",
- r"C:\Program Files\IAR Systems\Embedded Workbench 8.0\arm\bin",
- "/opt/iar/bin"
- ]
- for path in iar_paths:
- test_path = os.path.join(path, "iccarm.exe" if os.name == 'nt' else "iccarm")
- if os.path.exists(test_path):
- iccarm_path = test_path
- break
-
- if not iccarm_path:
- return False
-
- self.info = ToolchainInfo(
- name="iar",
- version="8.x", # IAR version detection is complex
- path=os.path.dirname(iccarm_path)
- )
- return True
-
- def configure_environment(self, env) -> None:
- """Configure environment for IAR."""
- env['CC'] = 'iccarm'
- env['CXX'] = 'iccarm'
- env['AS'] = 'iasmarm'
- env['AR'] = 'iarchive'
- env['LINK'] = 'ilinkarm'
-
- # IAR specific settings
- env['LIBPREFIX'] = ''
- env['LIBSUFFIX'] = '.a'
- env['LIBLINKPREFIX'] = ''
- env['LIBLINKSUFFIX'] = '.a'
-
- # Path
- if self.info and self.info.path:
- env.PrependENVPath('PATH', self.info.path)
-
- def get_compile_flags(self, cpu: str, fpu: str = None, float_abi: str = None) -> Dict[str, str]:
- """Get IAR flags."""
- flags = {
- 'CFLAGS': [],
- 'CXXFLAGS': [],
- 'ASFLAGS': [],
- 'LDFLAGS': []
- }
-
- # CPU selection
- cpu_map = {
- 'cortex-m0': '--cpu=Cortex-M0',
- 'cortex-m0+': '--cpu=Cortex-M0+',
- 'cortex-m3': '--cpu=Cortex-M3',
- 'cortex-m4': '--cpu=Cortex-M4',
- 'cortex-m7': '--cpu=Cortex-M7'
- }
-
- if cpu in cpu_map:
- cpu_flag = cpu_map[cpu]
- flags['CFLAGS'].append(cpu_flag)
- flags['CXXFLAGS'].append(cpu_flag)
-
- # Common flags
- flags['CFLAGS'].extend(['-e', '--dlib_config', 'DLib_Config_Normal.h'])
-
- return {k: ' '.join(v) for k, v in flags.items()}
- class ToolchainManager:
- """Manager for toolchain selection and configuration."""
-
- def __init__(self):
- self.toolchains: Dict[str, Toolchain] = {}
- self.current_toolchain: Optional[Toolchain] = None
- self._register_default_toolchains()
-
- def _register_default_toolchains(self) -> None:
- """Register default toolchains."""
- # Try to detect available toolchains
- toolchain_classes = [
- (GccToolchain, ['arm-none-eabi-', 'riscv32-unknown-elf-', 'riscv64-unknown-elf-']),
- (ArmccToolchain, ['']),
- (IarToolchain, [''])
- ]
-
- for toolchain_class, prefixes in toolchain_classes:
- for prefix in prefixes:
- if toolchain_class == GccToolchain:
- tc = toolchain_class(prefix)
- else:
- tc = toolchain_class()
-
- if tc.detect():
- name = f"{tc.get_name()}-{prefix}" if prefix else tc.get_name()
- self.register_toolchain(name, tc)
-
- def register_toolchain(self, name: str, toolchain: Toolchain) -> None:
- """Register a toolchain."""
- self.toolchains[name] = toolchain
-
- def select_toolchain(self, name: str) -> Toolchain:
- """Select a toolchain by name."""
- if name not in self.toolchains:
- # Try to create it
- if name == 'gcc':
- tc = GccToolchain()
- elif name == 'armcc' or name == 'keil':
- tc = ArmccToolchain()
- elif name == 'iar':
- tc = IarToolchain()
- else:
- raise ValueError(f"Unknown toolchain: {name}")
-
- if tc.detect():
- self.register_toolchain(name, tc)
- else:
- raise RuntimeError(f"Toolchain '{name}' not found")
-
- self.current_toolchain = self.toolchains[name]
- return self.current_toolchain
-
- def get_current(self) -> Optional[Toolchain]:
- """Get current toolchain."""
- return self.current_toolchain
-
- def list_toolchains(self) -> List[str]:
- """List available toolchains."""
- return list(self.toolchains.keys())
|