utils.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. # -*- coding: utf-8 -*-
  2. """
  3. Utility functions for RT-Thread build system.
  4. This module provides common utility functions used throughout the build system.
  5. """
  6. import os
  7. import sys
  8. import platform
  9. from typing import List, Tuple, Optional
  10. class PathService:
  11. """Service for path manipulation and normalization."""
  12. def __init__(self, base_path: str = None):
  13. self.base_path = base_path or os.getcwd()
  14. def normalize_path(self, path: str) -> str:
  15. """
  16. Normalize path for cross-platform compatibility.
  17. Args:
  18. path: Path to normalize
  19. Returns:
  20. Normalized path
  21. """
  22. # Convert to absolute path if relative
  23. if not os.path.isabs(path):
  24. path = os.path.abspath(os.path.join(self.base_path, path))
  25. # Normalize separators
  26. path = os.path.normpath(path)
  27. # Convert to forward slashes for consistency
  28. if platform.system() == 'Windows':
  29. path = path.replace('\\', '/')
  30. return path
  31. def make_relative(self, path: str, base: str = None) -> str:
  32. """
  33. Make path relative to base.
  34. Args:
  35. path: Path to make relative
  36. base: Base path (defaults to self.base_path)
  37. Returns:
  38. Relative path
  39. """
  40. if base is None:
  41. base = self.base_path
  42. path = self.normalize_path(path)
  43. base = self.normalize_path(base)
  44. try:
  45. rel_path = os.path.relpath(path, base)
  46. # Convert to forward slashes
  47. if platform.system() == 'Windows':
  48. rel_path = rel_path.replace('\\', '/')
  49. return rel_path
  50. except ValueError:
  51. # Different drives on Windows
  52. return path
  53. def split_path(self, path: str) -> List[str]:
  54. """
  55. Split path into components.
  56. Args:
  57. path: Path to split
  58. Returns:
  59. List of path components
  60. """
  61. path = self.normalize_path(path)
  62. parts = []
  63. while True:
  64. head, tail = os.path.split(path)
  65. if tail:
  66. parts.insert(0, tail)
  67. if head == path: # Reached root
  68. if head:
  69. parts.insert(0, head)
  70. break
  71. path = head
  72. return parts
  73. def common_prefix(self, paths: List[str]) -> str:
  74. """
  75. Find common prefix of multiple paths.
  76. Args:
  77. paths: List of paths
  78. Returns:
  79. Common prefix path
  80. """
  81. if not paths:
  82. return ""
  83. # Normalize all paths
  84. normalized = [self.normalize_path(p) for p in paths]
  85. # Find common prefix
  86. prefix = os.path.commonpath(normalized)
  87. return self.normalize_path(prefix)
  88. class PlatformInfo:
  89. """Platform and system information."""
  90. @staticmethod
  91. def get_platform() -> str:
  92. """Get platform name (Windows, Linux, Darwin)."""
  93. return platform.system()
  94. @staticmethod
  95. def get_architecture() -> str:
  96. """Get system architecture."""
  97. return platform.machine()
  98. @staticmethod
  99. def is_windows() -> bool:
  100. """Check if running on Windows."""
  101. return platform.system() == 'Windows'
  102. @staticmethod
  103. def is_linux() -> bool:
  104. """Check if running on Linux."""
  105. return platform.system() == 'Linux'
  106. @staticmethod
  107. def is_macos() -> bool:
  108. """Check if running on macOS."""
  109. return platform.system() == 'Darwin'
  110. @staticmethod
  111. def get_python_version() -> Tuple[int, int, int]:
  112. """Get Python version tuple."""
  113. return sys.version_info[:3]
  114. @staticmethod
  115. def check_python_version(min_version: Tuple[int, int]) -> bool:
  116. """
  117. Check if Python version meets minimum requirement.
  118. Args:
  119. min_version: Minimum version tuple (major, minor)
  120. Returns:
  121. True if version is sufficient
  122. """
  123. current = sys.version_info[:2]
  124. return current >= min_version
  125. class FileUtils:
  126. """File operation utilities."""
  127. @staticmethod
  128. def read_file(filepath: str, encoding: str = 'utf-8') -> str:
  129. """
  130. Read file content.
  131. Args:
  132. filepath: File path
  133. encoding: File encoding
  134. Returns:
  135. File content
  136. """
  137. with open(filepath, 'r', encoding=encoding) as f:
  138. return f.read()
  139. @staticmethod
  140. def write_file(filepath: str, content: str, encoding: str = 'utf-8') -> None:
  141. """
  142. Write content to file.
  143. Args:
  144. filepath: File path
  145. content: Content to write
  146. encoding: File encoding
  147. """
  148. # Ensure directory exists
  149. directory = os.path.dirname(filepath)
  150. if directory:
  151. os.makedirs(directory, exist_ok=True)
  152. with open(filepath, 'w', encoding=encoding) as f:
  153. f.write(content)
  154. @staticmethod
  155. def copy_file(src: str, dst: str) -> None:
  156. """
  157. Copy file from src to dst.
  158. Args:
  159. src: Source file path
  160. dst: Destination file path
  161. """
  162. import shutil
  163. # Ensure destination directory exists
  164. dst_dir = os.path.dirname(dst)
  165. if dst_dir:
  166. os.makedirs(dst_dir, exist_ok=True)
  167. shutil.copy2(src, dst)
  168. @staticmethod
  169. def find_files(directory: str, pattern: str, recursive: bool = True) -> List[str]:
  170. """
  171. Find files matching pattern.
  172. Args:
  173. directory: Directory to search
  174. pattern: File pattern (supports wildcards)
  175. recursive: Search recursively
  176. Returns:
  177. List of matching file paths
  178. """
  179. import fnmatch
  180. matches = []
  181. if recursive:
  182. for root, dirnames, filenames in os.walk(directory):
  183. for filename in filenames:
  184. if fnmatch.fnmatch(filename, pattern):
  185. matches.append(os.path.join(root, filename))
  186. else:
  187. try:
  188. filenames = os.listdir(directory)
  189. for filename in filenames:
  190. if fnmatch.fnmatch(filename, pattern):
  191. filepath = os.path.join(directory, filename)
  192. if os.path.isfile(filepath):
  193. matches.append(filepath)
  194. except OSError:
  195. pass
  196. return sorted(matches)
  197. class VersionUtils:
  198. """Version comparison utilities."""
  199. @staticmethod
  200. def parse_version(version_str: str) -> Tuple[int, ...]:
  201. """
  202. Parse version string to tuple.
  203. Args:
  204. version_str: Version string (e.g., "1.2.3")
  205. Returns:
  206. Version tuple
  207. """
  208. try:
  209. parts = version_str.split('.')
  210. return tuple(int(p) for p in parts if p.isdigit())
  211. except (ValueError, AttributeError):
  212. return (0,)
  213. @staticmethod
  214. def compare_versions(v1: str, v2: str) -> int:
  215. """
  216. Compare two version strings.
  217. Args:
  218. v1: First version
  219. v2: Second version
  220. Returns:
  221. -1 if v1 < v2, 0 if equal, 1 if v1 > v2
  222. """
  223. t1 = VersionUtils.parse_version(v1)
  224. t2 = VersionUtils.parse_version(v2)
  225. # Pad shorter version with zeros
  226. if len(t1) < len(t2):
  227. t1 = t1 + (0,) * (len(t2) - len(t1))
  228. elif len(t2) < len(t1):
  229. t2 = t2 + (0,) * (len(t1) - len(t2))
  230. if t1 < t2:
  231. return -1
  232. elif t1 > t2:
  233. return 1
  234. else:
  235. return 0
  236. @staticmethod
  237. def version_satisfies(version: str, requirement: str) -> bool:
  238. """
  239. Check if version satisfies requirement.
  240. Args:
  241. version: Version string
  242. requirement: Requirement string (e.g., ">=1.2.0")
  243. Returns:
  244. True if satisfied
  245. """
  246. import re
  247. # Parse requirement
  248. match = re.match(r'([<>=]+)\s*(.+)', requirement)
  249. if not match:
  250. # Exact match required
  251. return version == requirement
  252. op, req_version = match.groups()
  253. cmp = VersionUtils.compare_versions(version, req_version)
  254. if op == '>=':
  255. return cmp >= 0
  256. elif op == '<=':
  257. return cmp <= 0
  258. elif op == '>':
  259. return cmp > 0
  260. elif op == '<':
  261. return cmp < 0
  262. elif op == '==':
  263. return cmp == 0
  264. elif op == '!=':
  265. return cmp != 0
  266. else:
  267. return False