mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2025-10-18 18:34:20 +08:00
339 lines
9.1 KiB
Python
339 lines
9.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Utility functions for RT-Thread build system.
|
|
|
|
This module provides common utility functions used throughout the build system.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import platform
|
|
from typing import List, Tuple, Optional
|
|
|
|
|
|
class PathService:
|
|
"""Service for path manipulation and normalization."""
|
|
|
|
def __init__(self, base_path: str = None):
|
|
self.base_path = base_path or os.getcwd()
|
|
|
|
def normalize_path(self, path: str) -> str:
|
|
"""
|
|
Normalize path for cross-platform compatibility.
|
|
|
|
Args:
|
|
path: Path to normalize
|
|
|
|
Returns:
|
|
Normalized path
|
|
"""
|
|
# Convert to absolute path if relative
|
|
if not os.path.isabs(path):
|
|
path = os.path.abspath(os.path.join(self.base_path, path))
|
|
|
|
# Normalize separators
|
|
path = os.path.normpath(path)
|
|
|
|
# Convert to forward slashes for consistency
|
|
if platform.system() == 'Windows':
|
|
path = path.replace('\\', '/')
|
|
|
|
return path
|
|
|
|
def make_relative(self, path: str, base: str = None) -> str:
|
|
"""
|
|
Make path relative to base.
|
|
|
|
Args:
|
|
path: Path to make relative
|
|
base: Base path (defaults to self.base_path)
|
|
|
|
Returns:
|
|
Relative path
|
|
"""
|
|
if base is None:
|
|
base = self.base_path
|
|
|
|
path = self.normalize_path(path)
|
|
base = self.normalize_path(base)
|
|
|
|
try:
|
|
rel_path = os.path.relpath(path, base)
|
|
# Convert to forward slashes
|
|
if platform.system() == 'Windows':
|
|
rel_path = rel_path.replace('\\', '/')
|
|
return rel_path
|
|
except ValueError:
|
|
# Different drives on Windows
|
|
return path
|
|
|
|
def split_path(self, path: str) -> List[str]:
|
|
"""
|
|
Split path into components.
|
|
|
|
Args:
|
|
path: Path to split
|
|
|
|
Returns:
|
|
List of path components
|
|
"""
|
|
path = self.normalize_path(path)
|
|
parts = []
|
|
|
|
while True:
|
|
head, tail = os.path.split(path)
|
|
if tail:
|
|
parts.insert(0, tail)
|
|
if head == path: # Reached root
|
|
if head:
|
|
parts.insert(0, head)
|
|
break
|
|
path = head
|
|
|
|
return parts
|
|
|
|
def common_prefix(self, paths: List[str]) -> str:
|
|
"""
|
|
Find common prefix of multiple paths.
|
|
|
|
Args:
|
|
paths: List of paths
|
|
|
|
Returns:
|
|
Common prefix path
|
|
"""
|
|
if not paths:
|
|
return ""
|
|
|
|
# Normalize all paths
|
|
normalized = [self.normalize_path(p) for p in paths]
|
|
|
|
# Find common prefix
|
|
prefix = os.path.commonpath(normalized)
|
|
|
|
return self.normalize_path(prefix)
|
|
|
|
|
|
class PlatformInfo:
|
|
"""Platform and system information."""
|
|
|
|
@staticmethod
|
|
def get_platform() -> str:
|
|
"""Get platform name (Windows, Linux, Darwin)."""
|
|
return platform.system()
|
|
|
|
@staticmethod
|
|
def get_architecture() -> str:
|
|
"""Get system architecture."""
|
|
return platform.machine()
|
|
|
|
@staticmethod
|
|
def is_windows() -> bool:
|
|
"""Check if running on Windows."""
|
|
return platform.system() == 'Windows'
|
|
|
|
@staticmethod
|
|
def is_linux() -> bool:
|
|
"""Check if running on Linux."""
|
|
return platform.system() == 'Linux'
|
|
|
|
@staticmethod
|
|
def is_macos() -> bool:
|
|
"""Check if running on macOS."""
|
|
return platform.system() == 'Darwin'
|
|
|
|
@staticmethod
|
|
def get_python_version() -> Tuple[int, int, int]:
|
|
"""Get Python version tuple."""
|
|
return sys.version_info[:3]
|
|
|
|
@staticmethod
|
|
def check_python_version(min_version: Tuple[int, int]) -> bool:
|
|
"""
|
|
Check if Python version meets minimum requirement.
|
|
|
|
Args:
|
|
min_version: Minimum version tuple (major, minor)
|
|
|
|
Returns:
|
|
True if version is sufficient
|
|
"""
|
|
current = sys.version_info[:2]
|
|
return current >= min_version
|
|
|
|
|
|
class FileUtils:
|
|
"""File operation utilities."""
|
|
|
|
@staticmethod
|
|
def read_file(filepath: str, encoding: str = 'utf-8') -> str:
|
|
"""
|
|
Read file content.
|
|
|
|
Args:
|
|
filepath: File path
|
|
encoding: File encoding
|
|
|
|
Returns:
|
|
File content
|
|
"""
|
|
with open(filepath, 'r', encoding=encoding) as f:
|
|
return f.read()
|
|
|
|
@staticmethod
|
|
def write_file(filepath: str, content: str, encoding: str = 'utf-8') -> None:
|
|
"""
|
|
Write content to file.
|
|
|
|
Args:
|
|
filepath: File path
|
|
content: Content to write
|
|
encoding: File encoding
|
|
"""
|
|
# Ensure directory exists
|
|
directory = os.path.dirname(filepath)
|
|
if directory:
|
|
os.makedirs(directory, exist_ok=True)
|
|
|
|
with open(filepath, 'w', encoding=encoding) as f:
|
|
f.write(content)
|
|
|
|
@staticmethod
|
|
def copy_file(src: str, dst: str) -> None:
|
|
"""
|
|
Copy file from src to dst.
|
|
|
|
Args:
|
|
src: Source file path
|
|
dst: Destination file path
|
|
"""
|
|
import shutil
|
|
|
|
# Ensure destination directory exists
|
|
dst_dir = os.path.dirname(dst)
|
|
if dst_dir:
|
|
os.makedirs(dst_dir, exist_ok=True)
|
|
|
|
shutil.copy2(src, dst)
|
|
|
|
@staticmethod
|
|
def find_files(directory: str, pattern: str, recursive: bool = True) -> List[str]:
|
|
"""
|
|
Find files matching pattern.
|
|
|
|
Args:
|
|
directory: Directory to search
|
|
pattern: File pattern (supports wildcards)
|
|
recursive: Search recursively
|
|
|
|
Returns:
|
|
List of matching file paths
|
|
"""
|
|
import fnmatch
|
|
|
|
matches = []
|
|
|
|
if recursive:
|
|
for root, dirnames, filenames in os.walk(directory):
|
|
for filename in filenames:
|
|
if fnmatch.fnmatch(filename, pattern):
|
|
matches.append(os.path.join(root, filename))
|
|
else:
|
|
try:
|
|
filenames = os.listdir(directory)
|
|
for filename in filenames:
|
|
if fnmatch.fnmatch(filename, pattern):
|
|
filepath = os.path.join(directory, filename)
|
|
if os.path.isfile(filepath):
|
|
matches.append(filepath)
|
|
except OSError:
|
|
pass
|
|
|
|
return sorted(matches)
|
|
|
|
|
|
class VersionUtils:
|
|
"""Version comparison utilities."""
|
|
|
|
@staticmethod
|
|
def parse_version(version_str: str) -> Tuple[int, ...]:
|
|
"""
|
|
Parse version string to tuple.
|
|
|
|
Args:
|
|
version_str: Version string (e.g., "1.2.3")
|
|
|
|
Returns:
|
|
Version tuple
|
|
"""
|
|
try:
|
|
parts = version_str.split('.')
|
|
return tuple(int(p) for p in parts if p.isdigit())
|
|
except (ValueError, AttributeError):
|
|
return (0,)
|
|
|
|
@staticmethod
|
|
def compare_versions(v1: str, v2: str) -> int:
|
|
"""
|
|
Compare two version strings.
|
|
|
|
Args:
|
|
v1: First version
|
|
v2: Second version
|
|
|
|
Returns:
|
|
-1 if v1 < v2, 0 if equal, 1 if v1 > v2
|
|
"""
|
|
t1 = VersionUtils.parse_version(v1)
|
|
t2 = VersionUtils.parse_version(v2)
|
|
|
|
# Pad shorter version with zeros
|
|
if len(t1) < len(t2):
|
|
t1 = t1 + (0,) * (len(t2) - len(t1))
|
|
elif len(t2) < len(t1):
|
|
t2 = t2 + (0,) * (len(t1) - len(t2))
|
|
|
|
if t1 < t2:
|
|
return -1
|
|
elif t1 > t2:
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
@staticmethod
|
|
def version_satisfies(version: str, requirement: str) -> bool:
|
|
"""
|
|
Check if version satisfies requirement.
|
|
|
|
Args:
|
|
version: Version string
|
|
requirement: Requirement string (e.g., ">=1.2.0")
|
|
|
|
Returns:
|
|
True if satisfied
|
|
"""
|
|
import re
|
|
|
|
# Parse requirement
|
|
match = re.match(r'([<>=]+)\s*(.+)', requirement)
|
|
if not match:
|
|
# Exact match required
|
|
return version == requirement
|
|
|
|
op, req_version = match.groups()
|
|
cmp = VersionUtils.compare_versions(version, req_version)
|
|
|
|
if op == '>=':
|
|
return cmp >= 0
|
|
elif op == '<=':
|
|
return cmp <= 0
|
|
elif op == '>':
|
|
return cmp > 0
|
|
elif op == '<':
|
|
return cmp < 0
|
|
elif op == '==':
|
|
return cmp == 0
|
|
elif op == '!=':
|
|
return cmp != 0
|
|
else:
|
|
return False |