Coverage for pyTooling/Platform/__init__.py: 81%
262 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-25 22:22 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-25 22:22 +0000
1# ==================================================================================================================== #
2# _____ _ _ ____ _ _ __ #
3# _ __ _ |_ _|__ ___ | (_)_ __ __ _ | _ \| | __ _| |_ / _| ___ _ __ _ __ ___ #
4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` | | |_) | |/ _` | __| |_ / _ \| '__| '_ ` _ \ #
5# | |_) | |_| || | (_) | (_) | | | | | | (_| |_| __/| | (_| | |_| _| (_) | | | | | | | | #
6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)_| |_|\__,_|\__|_| \___/|_| |_| |_| |_| #
7# |_| |___/ |___/ #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2017-2025 Patrick Lehmann - Bötzingen, Germany #
15# #
16# Licensed under the Apache License, Version 2.0 (the "License"); #
17# you may not use this file except in compliance with the License. #
18# You may obtain a copy of the License at #
19# #
20# http://www.apache.org/licenses/LICENSE-2.0 #
21# #
22# Unless required by applicable law or agreed to in writing, software #
23# distributed under the License is distributed on an "AS IS" BASIS, #
24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25# See the License for the specific language governing permissions and #
26# limitations under the License. #
27# #
28# SPDX-License-Identifier: Apache-2.0 #
29# ==================================================================================================================== #
30#
31"""
32Common platform information gathered from various sources.
34.. hint:: See :ref:`high-level help <COMMON/Platform>` for explanations and usage examples.
35"""
36from enum import Flag, auto
38try:
39 from pyTooling.Decorators import export, readonly
40 from pyTooling.MetaClasses import ExtendedType
41 from pyTooling.Versioning import PythonVersion
42except (ImportError, ModuleNotFoundError): # pragma: no cover
43 print("[pyTooling.Platform] Could not import from 'pyTooling.*'!")
45 try:
46 from Decorators import export, readonly
47 from MetaClasses import ExtendedType
48 from Versioning import PythonVersion
49 except (ImportError, ModuleNotFoundError) as ex: # pragma: no cover
50 print("[pyTooling.Platform] Could not import directly!")
51 raise ex
54__all__ = ["CurrentPlatform"]
57@export
58class PythonImplementation(Flag):
59 Unknown = 0
61 CPython = 1
62 PyPy = 2
65@export
66class Platforms(Flag):
67 Unknown = 0
69 OS_FreeBSD = auto() #: Operating System: BSD (Unix).
70 OS_Linux = auto() #: Operating System: Linux.
71 OS_MacOS = auto() #: Operating System: macOS.
72 OS_Windows = auto() #: Operating System: Windows.
74 OperatingSystem = OS_FreeBSD | OS_Linux | OS_MacOS | OS_Windows #: Mask: Any operating system.
76 SEP_WindowsPath = auto() #: Seperator: Path element seperator (e.g. for directories).
77 SEP_WindowsValue = auto() #: Seperator: Value seperator in variables (e.g. for paths in PATH).
79 ENV_Native = auto() #: Environment: :term:`native`.
80 ENV_WSL = auto() #: Environment: :term:`Windows System for Linux <WSL>`.
81 ENV_MSYS2 = auto() #: Environment: :term:`MSYS2`.
82 ENV_Cygwin = auto() #: Environment: :term:`Cygwin`.
84 Environment = ENV_Native | ENV_WSL | ENV_MSYS2 | ENV_Cygwin #: Mask: Any environment.
86 ARCH_x86_32 = auto() #: Architecture: x86-32 (IA32).
87 ARCH_x86_64 = auto() #: Architecture: x86-64 (AMD64).
88 ARCH_AArch64 = auto() #: Architecture: AArch64 (arm64).
90 Arch_x86 = ARCH_x86_32 | ARCH_x86_64 #: Mask: Any x86 architecture.
91 Arch_Arm = ARCH_AArch64 #: Mask: Any Arm architecture.
92 Architecture = Arch_x86 | Arch_Arm #: Mask: Any architecture.
94 FreeBSD = OS_FreeBSD | ENV_Native | ARCH_x86_64 #: Group: native FreeBSD on x86-64.
95 Linux = OS_Linux | ENV_Native | ARCH_x86_64 #: Group: native Linux on x86-64.
96 MacOS = OS_MacOS | ENV_Native #: Group: native macOS.
97 Windows = OS_Windows | ENV_Native | ARCH_x86_64 | SEP_WindowsPath | SEP_WindowsValue #: Group: native Windows on x86-64.
99 MacOS_Intel = MacOS | ARCH_x86_64 #: Group: native macOS on x86-64.
100 MacOS_ARM = MacOS | ARCH_AArch64 #: Group: native macOS on aarch64.
102 MSYS = auto() #: MSYS2 Runtime: MSYS.
103 MinGW32 = auto() #: MSYS2 Runtime: :term:`MinGW32 <MinGW>`.
104 MinGW64 = auto() #: MSYS2 Runtime: :term:`MinGW64 <MinGW>`.
105 UCRT64 = auto() #: MSYS2 Runtime: :term:`UCRT64 <UCRT>`.
106 Clang32 = auto() #: MSYS2 Runtime: Clang32.
107 Clang64 = auto() #: MSYS2 Runtime: Clang64.
109 MSYS2_Runtime = MSYS | MinGW32 | MinGW64 | UCRT64 | Clang32 | Clang64 #: Mask: Any MSYS2 runtime environment.
111 Windows_MSYS2_MSYS = OS_Windows | ENV_MSYS2 | ARCH_x86_64 | MSYS #: Group: MSYS runtime running on Windows x86-64
112 Windows_MSYS2_MinGW32 = OS_Windows | ENV_MSYS2 | ARCH_x86_64 | MinGW32 #: Group: MinGW32 runtime running on Windows x86-64
113 Windows_MSYS2_MinGW64 = OS_Windows | ENV_MSYS2 | ARCH_x86_64 | MinGW64 #: Group: MinGW64 runtime running on Windows x86-64
114 Windows_MSYS2_UCRT64 = OS_Windows | ENV_MSYS2 | ARCH_x86_64 | UCRT64 #: Group: UCRT64 runtime running on Windows x86-64
115 Windows_MSYS2_Clang32 = OS_Windows | ENV_MSYS2 | ARCH_x86_64 | Clang32 #: Group: Clang32 runtime running on Windows x86-64
116 Windows_MSYS2_Clang64 = OS_Windows | ENV_MSYS2 | ARCH_x86_64 | Clang64 #: Group: Clang64 runtime running on Windows x86-64
118 Windows_Cygwin32 = OS_Windows | ENV_Cygwin | ARCH_x86_32 #: Group: 32-bit Cygwin runtime on Windows x86-64
119 Windows_Cygwin64 = OS_Windows | ENV_Cygwin | ARCH_x86_64 #: Group: 64-bit Cygwin runtime on Windows x86-64
122@export
123class Platform(metaclass=ExtendedType, singleton=True, slots=True):
124 """An instance of this class contains all gathered information available from various sources.
126 .. seealso::
128 StackOverflow question: `Python: What OS am I running on? <https://stackoverflow.com/a/54837707/3719459>`__
129 """
131 _platform: Platforms
132 _pythonImplementation: PythonImplementation
133 _pythonVersion: PythonVersion
135 def __init__(self) -> None:
136 import sys
137 import os
138 import platform
139 import sysconfig
141 # Discover the Python implementation
142 pythonImplementation = platform.python_implementation()
143 if pythonImplementation == "CPython":
144 self._pythonImplementation = PythonImplementation.CPython
145 elif pythonImplementation == "PyPy":
146 self._pythonImplementation = PythonImplementation.PyPy
147 else: # pragma: no cover
148 self._pythonImplementation = PythonImplementation.Unknown
150 # Discover the Python version
151 self._pythonVersion = PythonVersion.FromSysVersionInfo()
153 # Discover the platform
154 self._platform = Platforms.Unknown
156 # system = platform.system()
157 machine = platform.machine()
158 # architecture = platform.architecture()
159 sys_platform = sys.platform
160 sysconfig_platform = sysconfig.get_platform()
162 # print()
163 # print(os.name)
164 # print(system)
165 # print(machine)
166 # print(architecture)
167 # print(sys_platform)
168 # print(sysconfig_platform)
170 if os.name == "nt":
171 self._platform |= Platforms.OS_Windows
173 if sysconfig_platform == "win32": 173 ↛ 174line 173 didn't jump to line 174 because the condition on line 173 was never true
174 self._platform |= Platforms.ENV_Native | Platforms.ARCH_x86_32 | Platforms.SEP_WindowsPath | Platforms.SEP_WindowsValue
175 elif sysconfig_platform == "win-amd64":
176 self._platform |= Platforms.ENV_Native | Platforms.ARCH_x86_64 | Platforms.SEP_WindowsPath | Platforms.SEP_WindowsValue
177 elif sysconfig_platform.startswith("mingw"):
178 if machine == "AMD64":
179 self._platform |= Platforms.ARCH_x86_64
180 else: # pragma: no cover
181 raise Exception(f"Unknown architecture '{machine}' for Windows.")
183 if sysconfig_platform == "mingw_i686_msvcrt_gnu": 183 ↛ 184line 183 didn't jump to line 184 because the condition on line 183 was never true
184 self._platform |= Platforms.ENV_MSYS2 | Platforms.MinGW32
185 elif sysconfig_platform == "mingw_x86_64_msvcrt_gnu":
186 self._platform |= Platforms.ENV_MSYS2 | Platforms.MinGW64
187 elif sysconfig_platform == "mingw_x86_64_ucrt_gnu":
188 self._platform |= Platforms.ENV_MSYS2 | Platforms.UCRT64
189 elif sysconfig_platform == "mingw_x86_64_ucrt_llvm":
190 self._platform |= Platforms.ENV_MSYS2 | Platforms.Clang64
191 elif sysconfig_platform == "mingw_i686": # pragma: no cover
192 self._platform |= Platforms.ENV_MSYS2 | Platforms.MinGW32
193 elif sysconfig_platform == "mingw_x86_64": # pragma: no cover
194 self._platform |= Platforms.ENV_MSYS2 | Platforms.MinGW64
195 elif sysconfig_platform == "mingw_x86_64_ucrt": # pragma: no cover
196 self._platform |= Platforms.ENV_MSYS2 | Platforms.UCRT64
197 elif sysconfig_platform == "mingw_x86_64_clang": # pragma: no cover
198 self._platform |= Platforms.ENV_MSYS2 | Platforms.Clang64
199 else: # pragma: no cover
200 raise Exception(f"Unknown MSYS2 architecture '{sysconfig_platform}'.")
201 else: # pragma: no cover
202 raise Exception(f"Unknown platform '{sysconfig_platform}' running on Windows.")
204 elif os.name == "posix":
205 if sys_platform == "linux":
206 self._platform |= Platforms.OS_Linux | Platforms.ENV_Native
208 if sysconfig_platform == "linux-x86_64": # native Linux x86_64; Windows 64 + WSL 208 ↛ 210line 208 didn't jump to line 210 because the condition on line 208 was always true
209 self._platform |= Platforms.ARCH_x86_64
210 elif sysconfig_platform == "linux-aarch64": # native Linux Aarch64
211 self._platform |= Platforms.ARCH_AArch64
212 else: # pragma: no cover
213 raise Exception(f"Unknown architecture '{sysconfig_platform}' for a native Linux.")
215 elif sys_platform == "darwin": 215 ↛ 232line 215 didn't jump to line 232 because the condition on line 215 was always true
216 self._platform |= Platforms.OS_MacOS | Platforms.ENV_Native
218 if machine == "x86_64":
219 self._platform |= Platforms.ARCH_x86_64
220 elif machine == "arm64":
221 self._platform |= Platforms.ARCH_AArch64
222 else: # pragma: no cover
223 raise Exception(f"Unknown architecture '{machine}' for a native macOS.")
225 # print()
226 # print(os.name)
227 # print(system)
228 # print(machine)
229 # print(architecture)
230 # print(sys_platform)
231 # print(sysconfig_platform)
232 elif sys_platform == "msys":
233 self._platform |= Platforms.OS_Windows | Platforms.ENV_MSYS2 | Platforms.MSYS
235 if machine == "i686":
236 self._platform |= Platforms.ARCH_x86_32
237 elif machine == "x86_64":
238 self._platform |= Platforms.ARCH_x86_64
239 else: # pragma: no cover
240 raise Exception(f"Unknown architecture '{machine}' for MSYS2-MSYS on Windows.")
242 elif sys_platform == "cygwin":
243 self._platform |= Platforms.OS_Windows
245 if machine == "i686":
246 self._platform |= Platforms.ARCH_x86_32
247 elif machine == "x86_64":
248 self._platform |= Platforms.ARCH_x86_64
249 else: # pragma: no cover
250 raise Exception(f"Unknown architecture '{machine}' for Cygwin on Windows.")
252 elif sys_platform.startswith("freebsd"):
253 if machine == "amd64":
254 self._platform = Platforms.FreeBSD
255 else: # pragma: no cover
256 raise Exception(f"Unknown architecture '{machine}' for FreeBSD.")
257 else: # pragma: no cover
258 raise Exception(f"Unknown POSIX platform '{sys_platform}'.")
259 else: # pragma: no cover
260 raise Exception(f"Unknown operating system '{os.name}'.")
262 # print(self._platform)
264 @readonly
265 def PythonImplementation(self) -> PythonImplementation:
266 return self._pythonImplementation
268 @readonly
269 def IsCPython(self) -> bool:
270 """Returns true, if the Python implementation is a :term:`CPython`.
272 :returns: ``True``, if the Python implementation is CPython.
273 """
274 return self._pythonImplementation is PythonImplementation.CPython
276 @readonly
277 def IsPyPy(self) -> bool:
278 """Returns true, if the Python implementation is a :term:`PyPy`.
280 :returns: ``True``, if the Python implementation is PyPY.
281 """
282 return self._pythonImplementation is PythonImplementation.PyPy
284 @readonly
285 def PythonVersion(self) -> PythonVersion:
286 return self._pythonVersion
288 @readonly
289 def HostOperatingSystem(self) -> Platforms:
290 return self._platform & Platforms.OperatingSystem
292 @readonly
293 def IsNativePlatform(self) -> bool:
294 """Returns true, if the platform is a :term:`native` platform.
296 :returns: ``True``, if the platform is a native platform.
297 """
298 return Platforms.ENV_Native in self._platform
300 @readonly
301 def IsNativeFreeBSD(self) -> bool:
302 """Returns true, if the platform is a :term:`native` FreeBSD x86-64 platform.
304 :returns: ``True``, if the platform is a native FreeBSD x86-64 platform.
305 """
306 return Platforms.FreeBSD in self._platform
308 @readonly
309 def IsNativeMacOS(self) -> bool:
310 """Returns true, if the platform is a :term:`native` macOS x86-64 platform.
312 :returns: ``True``, if the platform is a native macOS x86-64 platform.
313 """
314 return Platforms.MacOS in self._platform
316 @readonly
317 def IsNativeLinux(self) -> bool:
318 """Returns true, if the platform is a :term:`native` Linux x86-64 platform.
320 :returns: ``True``, if the platform is a native Linux x86-64 platform.
321 """
322 return Platforms.Linux in self._platform
324 @readonly
325 def IsNativeWindows(self) -> bool:
326 """Returns true, if the platform is a :term:`native` Windows x86-64 platform.
328 :returns: ``True``, if the platform is a native Windows x86-64 platform.
329 """
330 return Platforms.Windows in self._platform
332 @readonly
333 def IsMSYS2Environment(self) -> bool:
334 """Returns true, if the platform is a :term:`MSYS2` environment on Windows.
336 :returns: ``True``, if the platform is a MSYS2 environment on Windows.
337 """
338 return Platforms.ENV_MSYS2 in self._platform
340 @readonly
341 def IsMSYSOnWindows(self) -> bool:
342 """Returns true, if the platform is a MSYS runtime on Windows.
344 :returns: ``True``, if the platform is a MSYS runtime on Windows.
345 """
346 return Platforms.Windows_MSYS2_MSYS in self._platform
348 @readonly
349 def IsMinGW32OnWindows(self) -> bool:
350 """Returns true, if the platform is a :term:`MinGW32 <MinGW>` runtime on Windows.
352 :returns: ``True``, if the platform is a MINGW32 runtime on Windows.
353 """
354 return Platforms.Windows_MSYS2_MinGW32 in self._platform
356 @readonly
357 def IsMinGW64OnWindows(self) -> bool:
358 """Returns true, if the platform is a :term:`MinGW64 <MinGW>` runtime on Windows.
360 :returns: ``True``, if the platform is a MINGW64 runtime on Windows.
361 """
362 return Platforms.Windows_MSYS2_MinGW64 in self._platform
364 @readonly
365 def IsUCRT64OnWindows(self) -> bool:
366 """Returns true, if the platform is a :term:`UCRT64 <UCRT>` runtime on Windows.
368 :returns: ``True``, if the platform is a UCRT64 runtime on Windows.
369 """
370 return Platforms.Windows_MSYS2_UCRT64 in self._platform
372 @readonly
373 def IsClang32OnWindows(self) -> bool:
374 """Returns true, if the platform is a Clang32 runtime on Windows.
376 :returns: ``True``, if the platform is a Clang32 runtime on Windows.
377 """
378 return Platforms.Windows_MSYS2_Clang32 in self._platform
380 @readonly
381 def IsClang64OnWindows(self) -> bool:
382 """Returns true, if the platform is a Clang64 runtime on Windows.
384 :returns: ``True``, if the platform is a Clang64 runtime on Windows.
385 """
386 return Platforms.Windows_MSYS2_Clang64 in self._platform
388 @readonly
389 def IsCygwin32OnWindows(self) -> bool:
390 """Returns true, if the platform is a 32-bit Cygwin runtime on Windows.
392 :returns: ``True``, if the platform is a 32-bit Cygwin runtime on Windows.
393 """
394 return Platforms.Windows_Cygwin32 in self._platform
396 @readonly
397 def IsCygwin64OnWindows(self) -> bool:
398 """Returns true, if the platform is a 64-bit Cygwin runtime on Windows.
400 :returns: ``True``, if the platform is a 64-bit Cygwin runtime on Windows.
401 """
402 return Platforms.Windows_Cygwin64 in self._platform
404 @readonly
405 def IsPOSIX(self) -> bool:
406 """
407 Returns true, if the platform is POSIX or POSIX-like.
409 :returns: ``True``, if POSIX or POSIX-like.
410 """
411 return Platforms.SEP_WindowsPath not in self._platform
413 @readonly
414 def PathSeperator(self) -> str:
415 """
416 Returns the path element separation character (e.g. for directories).
418 * POSIX-like: ``/``
419 * Windows: ``\\``
421 :returns: Path separation character.
422 """
423 if Platforms.SEP_WindowsPath in self._platform:
424 return "\\"
425 else:
426 return "/"
428 @readonly
429 def ValueSeperator(self) -> str:
430 """
431 Returns the value separation character (e.g. for paths in PATH).
433 * POSIX-like: ``:``
434 * Windows: ``;``
436 :returns: Value separation character.
437 """
438 if Platforms.SEP_WindowsValue in self._platform:
439 return ";"
440 else:
441 return ":"
443 @readonly
444 def ExecutableExtension(self) -> str:
445 """
446 Returns the file extension for an executable.
448 * FreeBSD: ``""`` (empty string)
449 * Linux: ``""`` (empty string)
450 * macOS: ``""`` (empty string)
451 * Windows: ``"exe"``
452 """
454 if Platforms.OS_FreeBSD in self._platform: 454 ↛ 455line 454 didn't jump to line 455 because the condition on line 454 was never true
455 return ""
456 elif Platforms.OS_Linux in self._platform:
457 return ""
458 elif Platforms.OS_MacOS in self._platform:
459 return ""
460 elif Platforms.OS_Windows in self._platform:
461 return "exe"
462 else: # pragma: no cover
463 raise Exception(f"Unknown operating system.")
465 @readonly
466 def StaticLibraryExtension(self) -> str:
467 """
468 Returns the file extension for a static library.
470 * FreeBSD: ``"a"``
471 * Linux: ``"a"``
472 * macOS: ``"lib"``
473 * Windows: ``"lib"``
474 """
475 if Platforms.OS_FreeBSD in self._platform: 475 ↛ 476line 475 didn't jump to line 476 because the condition on line 475 was never true
476 return "a"
477 elif Platforms.OS_Linux in self._platform:
478 return "a"
479 elif Platforms.OS_MacOS in self._platform:
480 return "a"
481 elif Platforms.OS_Windows in self._platform:
482 return "lib"
483 else: # pragma: no cover
484 raise Exception(f"Unknown operating system.")
486 @readonly
487 def DynamicLibraryExtension(self) -> str:
488 """
489 Returns the file extension for a dynamic/shared library.
491 * FreeBSD: ``"so"``
492 * Linux: ``"so"``
493 * macOS: ``"dylib"``
494 * Windows: ``"dll"``
495 """
496 if Platforms.OS_FreeBSD in self._platform: 496 ↛ 497line 496 didn't jump to line 497 because the condition on line 496 was never true
497 return "so"
498 elif Platforms.OS_Linux in self._platform:
499 return "so"
500 elif Platforms.OS_MacOS in self._platform:
501 return "dylib"
502 elif Platforms.OS_Windows in self._platform:
503 return "dll"
504 else: # pragma: no cover
505 raise Exception(f"Unknown operating system.")
507 def __repr__(self) -> str:
508 return str(self._platform)
510 def __str__(self) -> str:
511 runtime = ""
513 if Platforms.OS_FreeBSD in self._platform: 513 ↛ 514line 513 didn't jump to line 514 because the condition on line 513 was never true
514 platform = "FreeBSD"
515 elif Platforms.OS_MacOS in self._platform:
516 platform = "macOS"
517 elif Platforms.OS_Linux in self._platform:
518 platform = "Linux"
519 elif Platforms.OS_Windows in self._platform: 519 ↛ 522line 519 didn't jump to line 522 because the condition on line 519 was always true
520 platform = "Windows"
521 else:
522 platform = "plat:dec-err"
524 if Platforms.ENV_Native in self._platform:
525 environment = ""
526 elif Platforms.ENV_WSL in self._platform: 526 ↛ 527line 526 didn't jump to line 527 because the condition on line 526 was never true
527 environment = "+WSL"
528 elif Platforms.ENV_MSYS2 in self._platform: 528 ↛ 546line 528 didn't jump to line 546 because the condition on line 528 was always true
529 environment = "+MSYS2"
531 if Platforms.MSYS in self._platform: 531 ↛ 532line 531 didn't jump to line 532 because the condition on line 531 was never true
532 runtime = " - MSYS"
533 elif Platforms.MinGW32 in self._platform: 533 ↛ 534line 533 didn't jump to line 534 because the condition on line 533 was never true
534 runtime = " - MinGW32"
535 elif Platforms.MinGW64 in self._platform:
536 runtime = " - MinGW64"
537 elif Platforms.UCRT64 in self._platform:
538 runtime = " - UCRT64"
539 elif Platforms.Clang32 in self._platform: 539 ↛ 540line 539 didn't jump to line 540 because the condition on line 539 was never true
540 runtime = " - Clang32"
541 elif Platforms.Clang64 in self._platform: 541 ↛ 544line 541 didn't jump to line 544 because the condition on line 541 was always true
542 runtime = " - Clang64"
543 else:
544 runtime = "rt:dec-err"
546 elif Platforms.ENV_Cygwin in self._platform:
547 environment = "+Cygwin"
548 else:
549 environment = "env:dec-err"
551 if Platforms.ARCH_x86_32 in self._platform: 551 ↛ 552line 551 didn't jump to line 552 because the condition on line 551 was never true
552 architecture = "x86-32"
553 elif Platforms.ARCH_x86_64 in self._platform:
554 architecture = "x86-64"
555 elif Platforms.ARCH_AArch64 in self._platform: 555 ↛ 558line 555 didn't jump to line 558 because the condition on line 555 was always true
556 architecture = "aarch64"
557 else:
558 architecture = "arch:dec-err"
560 return f"{platform}{environment} ({architecture}){runtime}"
563CurrentPlatform = Platform() #: Gathered information for the current platform.