mirror of
https://github.com/Nuitka/Nuitka.git
synced 2025-12-14 20:35:49 +01:00
* Because the Python DLL cannot be used from the outside, it cannot be made to work without copying the Python DLL which is too ugly and an irrelevant configuration.
454 lines
12 KiB
Python
454 lines
12 KiB
Python
# Copyright 2025, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file
|
|
|
|
|
|
""" Python flavors specifics.
|
|
|
|
This abstracts the Python variants from different people. There is not just
|
|
CPython, but Anaconda, Debian, pyenv, Apple, lots of people who make Python
|
|
in a way the requires technical differences, e.g. static linking, LTO, or
|
|
DLL presence, link paths, etc.
|
|
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
from nuitka.utils.FileOperations import (
|
|
areSamePaths,
|
|
isFilenameBelowPath,
|
|
isFilenameSameAsOrBelowPath,
|
|
)
|
|
from nuitka.utils.Utils import (
|
|
isAlpineLinux,
|
|
isAndroidBasedLinux,
|
|
isArchBasedLinux,
|
|
isFedoraBasedLinux,
|
|
isLinux,
|
|
isMacOS,
|
|
isPosixWindows,
|
|
isWin32Windows,
|
|
withNoDeprecationWarning,
|
|
)
|
|
|
|
from .PythonVersions import (
|
|
getInstalledPythonRegistryPaths,
|
|
getRunningPythonDLLPath,
|
|
getSystemPrefixPath,
|
|
isStaticallyLinkedPython,
|
|
python_version,
|
|
python_version_str,
|
|
)
|
|
|
|
|
|
def isNuitkaPython():
|
|
"""Is this our own fork of CPython named Nuitka-Python."""
|
|
|
|
# spell-checker: ignore nuitkapython
|
|
|
|
if python_version >= 0x300:
|
|
return sys.implementation.name == "nuitkapython"
|
|
else:
|
|
return sys.subversion[0] == "nuitkapython"
|
|
|
|
|
|
_is_anaconda = None
|
|
|
|
|
|
def isAnacondaPython():
|
|
"""Detect if Python variant Anaconda"""
|
|
|
|
# singleton, pylint: disable=global-statement
|
|
global _is_anaconda
|
|
|
|
if _is_anaconda is None:
|
|
_is_anaconda = os.path.exists(os.path.join(sys.prefix, "conda-meta"))
|
|
|
|
return _is_anaconda
|
|
|
|
|
|
def isApplePython():
|
|
if not isMacOS():
|
|
return False
|
|
|
|
# Python2 on 10.15 or higher
|
|
if "+internal-os" in sys.version:
|
|
return True
|
|
|
|
# Older macOS had that
|
|
if isFilenameSameAsOrBelowPath(path="/usr/bin/", filename=getSystemPrefixPath()):
|
|
return True
|
|
# Newer macOS has that
|
|
if isFilenameSameAsOrBelowPath(
|
|
path="/Library/Developer/CommandLineTools/", filename=getSystemPrefixPath()
|
|
):
|
|
return True
|
|
|
|
# Xcode has that on macOS, we consider it an Apple Python for now, it might
|
|
# be more usable than Apple Python, we but we delay that.
|
|
if isFilenameSameAsOrBelowPath(
|
|
path="/Applications/Xcode.app/Contents/Developer/",
|
|
filename=getSystemPrefixPath(),
|
|
):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def isHomebrewPython():
|
|
# spell-checker: ignore sitecustomize
|
|
if not isMacOS():
|
|
return False
|
|
|
|
if isGithubActionsPython():
|
|
return True
|
|
|
|
candidate = os.path.join(
|
|
getSystemPrefixPath(), "lib", "python" + python_version_str, "sitecustomize.py"
|
|
)
|
|
|
|
if os.path.exists(candidate):
|
|
with open(candidate, "rb") as site_file:
|
|
line = site_file.readline()
|
|
|
|
if b"Homebrew" in line:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def getHomebrewInstallPath():
|
|
assert isHomebrewPython()
|
|
|
|
candidate = getSystemPrefixPath()
|
|
|
|
while candidate != "/":
|
|
if os.path.isdir(os.path.join(candidate, "Cellar")):
|
|
return candidate
|
|
|
|
candidate = os.path.dirname(candidate)
|
|
|
|
sys.exit("Error, failed to locate homebrew installation path.")
|
|
|
|
|
|
def isRyePython():
|
|
if isMacOS():
|
|
import sysconfig
|
|
|
|
# We didn't find a better one, since they do not leave much of any trace
|
|
# otherwise than an unusable "libpython.a"
|
|
# spell-checker: ignore isysroot,flto,ldflags
|
|
value = sysconfig.get_config_var("_OSX_SUPPORT_INITIAL_PY_CORE_LDFLAGS")
|
|
return value is not None and "-flto=thin" in value and "-isysroot" in value
|
|
|
|
return False
|
|
|
|
|
|
def isPythonBuildStandalonePython():
|
|
try:
|
|
import sysconfig
|
|
|
|
return sysconfig.get_config_var("PYTHON_BUILD_STANDALONE") == 1
|
|
except ImportError:
|
|
return False
|
|
|
|
|
|
def isPyenvPython():
|
|
if isWin32Windows():
|
|
return False
|
|
|
|
return os.getenv("PYENV_ROOT") and isFilenameSameAsOrBelowPath(
|
|
path=os.getenv("PYENV_ROOT"), filename=getSystemPrefixPath()
|
|
)
|
|
|
|
|
|
def isMSYS2MingwPython():
|
|
"""MSYS2 the MinGW64 variant that is more Win32 compatible."""
|
|
if not isWin32Windows() or "GCC" not in sys.version:
|
|
return False
|
|
|
|
import sysconfig
|
|
|
|
if python_version >= 0x3B0:
|
|
return "-mingw_" in sysconfig.get_config_var("EXT_SUFFIX")
|
|
else:
|
|
return "-mingw_" in sysconfig.get_config_var("SO")
|
|
|
|
|
|
def isTermuxPython():
|
|
"""Is this Termux Android Python."""
|
|
# spell-checker: ignore termux
|
|
|
|
if not isAndroidBasedLinux():
|
|
return False
|
|
|
|
return "com.termux" in getSystemPrefixPath().split("/")
|
|
|
|
|
|
def isUninstalledPython():
|
|
"""Decide if this a Python that doesn't have a system wide DLL installation for its use."""
|
|
# return driven, pylint: disable=too-many-return-statements
|
|
|
|
if isDebianPackagePython():
|
|
return False
|
|
|
|
if isSelfCompiledPythonUninstalled():
|
|
return True
|
|
|
|
if isPythonBuildStandalonePython():
|
|
return True
|
|
|
|
if isAnacondaPython() or isWinPython():
|
|
return True
|
|
|
|
if os.name == "nt":
|
|
import ctypes.wintypes
|
|
|
|
GetSystemDirectory = ctypes.windll.kernel32.GetSystemDirectoryW
|
|
GetSystemDirectory.argtypes = (ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD)
|
|
GetSystemDirectory.restype = ctypes.wintypes.DWORD
|
|
|
|
MAX_PATH = 4096
|
|
buf = ctypes.create_unicode_buffer(MAX_PATH)
|
|
|
|
res = GetSystemDirectory(buf, MAX_PATH)
|
|
assert res != 0
|
|
|
|
system_path = buf.value
|
|
return not isFilenameBelowPath(
|
|
path=system_path, filename=getRunningPythonDLLPath()
|
|
)
|
|
|
|
if isStaticallyLinkedPython():
|
|
return False
|
|
|
|
return None
|
|
|
|
|
|
_is_win_python = None
|
|
|
|
|
|
def isWinPython():
|
|
"""Is this Python from WinPython."""
|
|
if "WinPython" in sys.version:
|
|
return True
|
|
|
|
# singleton, pylint: disable=global-statement
|
|
global _is_win_python
|
|
|
|
if _is_win_python is None:
|
|
for element in sys.path:
|
|
if os.path.basename(element) == "site-packages":
|
|
if os.path.exists(os.path.join(element, "WinPython")):
|
|
_is_win_python = True
|
|
break
|
|
else:
|
|
_is_win_python = False
|
|
|
|
return _is_win_python
|
|
|
|
|
|
def isDebianPackagePython():
|
|
"""Is this Python from a debian package."""
|
|
|
|
# spell-checker: ignore multiarch
|
|
|
|
if not isLinux():
|
|
return False
|
|
|
|
if python_version < 0x300:
|
|
return hasattr(sys, "_multiarch")
|
|
elif python_version < 0x3C0:
|
|
with withNoDeprecationWarning():
|
|
try:
|
|
from distutils.dir_util import _multiarch
|
|
except ImportError:
|
|
return False
|
|
else:
|
|
return True
|
|
else:
|
|
import sysconfig
|
|
|
|
# Need to check there for Debian patch, pylint: disable=protected-access
|
|
return "deb_system" in sysconfig._INSTALL_SCHEMES
|
|
|
|
|
|
def isFedoraPackagePython():
|
|
"""Is the Python from a Fedora package."""
|
|
if not isFedoraBasedLinux():
|
|
return False
|
|
|
|
system_prefix_path = getSystemPrefixPath()
|
|
|
|
return system_prefix_path == "/usr"
|
|
|
|
|
|
def isAlpinePackagePython():
|
|
"""Is the Python from a Alpine package."""
|
|
if not isAlpineLinux():
|
|
return False
|
|
|
|
system_prefix_path = getSystemPrefixPath()
|
|
|
|
return system_prefix_path == "/usr"
|
|
|
|
|
|
def isArchPackagePython():
|
|
"""Is the Python from a Fedora package."""
|
|
if not isArchBasedLinux():
|
|
return False
|
|
|
|
system_prefix_path = getSystemPrefixPath()
|
|
|
|
return system_prefix_path == "/usr"
|
|
|
|
|
|
def isCPythonOfficialPackage():
|
|
"""Official CPython download, kind of hard to detect since self-compiled doesn't change much."""
|
|
|
|
sys_prefix = getSystemPrefixPath()
|
|
|
|
# For macOS however, it's very knowable.
|
|
if isMacOS():
|
|
for candidate in (
|
|
"/Library/Frameworks/Python.framework/Versions/",
|
|
"/Library/Frameworks/PythonT.framework/Versions/",
|
|
):
|
|
|
|
if isFilenameBelowPath(path=candidate, filename=sys_prefix):
|
|
return True
|
|
|
|
# For Windows, we check registry.
|
|
if isWin32Windows():
|
|
for registry_python_exe in getInstalledPythonRegistryPaths(python_version_str):
|
|
if areSamePaths(sys_prefix, os.path.dirname(registry_python_exe)):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
_is_self_compiled_python = None
|
|
|
|
|
|
def isSelfCompiledPythonUninstalled():
|
|
# singleton, pylint: disable=global-statement
|
|
global _is_self_compiled_python
|
|
|
|
if _is_self_compiled_python is None:
|
|
sys_prefix = getSystemPrefixPath()
|
|
|
|
_is_self_compiled_python = os.path.isdir(os.path.join(sys_prefix, "PCbuild"))
|
|
|
|
return _is_self_compiled_python
|
|
|
|
|
|
_is_manylinux_python = None
|
|
|
|
|
|
def isManyLinuxPython():
|
|
if not isLinux():
|
|
return False
|
|
|
|
# singleton, pylint: disable=global-statement
|
|
global _is_manylinux_python
|
|
|
|
sys_prefix = getSystemPrefixPath()
|
|
|
|
if _is_manylinux_python is None:
|
|
_is_manylinux_python = os.path.isfile(
|
|
os.path.join(sys_prefix, "..", "static-libs-for-embedding-only.tar.xz")
|
|
)
|
|
|
|
return _is_manylinux_python
|
|
|
|
|
|
def isGithubActionsPython():
|
|
# spell-checker: ignore hostedtoolcache
|
|
|
|
return os.getenv("GITHUB_ACTIONS") == "true" and getSystemPrefixPath().startswith(
|
|
"/opt/hostedtoolcache/Python"
|
|
)
|
|
|
|
|
|
def isWindowsStorePython():
|
|
if not isWin32Windows():
|
|
return False
|
|
|
|
# spell-checker: ignore LOCALAPPDATA
|
|
local_app_data = os.getenv("LOCALAPPDATA")
|
|
if not local_app_data:
|
|
# Not insisting to be any better for those.
|
|
return False
|
|
|
|
return isFilenameBelowPath(
|
|
path=os.path.join(local_app_data, "Microsoft", "WindowsApps"),
|
|
filename=sys.executable,
|
|
)
|
|
|
|
|
|
def getPythonFlavorName():
|
|
"""For output to the user only."""
|
|
# return driven, pylint: disable=too-many-branches,too-many-return-statements
|
|
|
|
if isNuitkaPython():
|
|
return "Nuitka Python"
|
|
elif isAnacondaPython():
|
|
return "Anaconda Python"
|
|
elif isWinPython():
|
|
return "WinPython"
|
|
elif isDebianPackagePython():
|
|
return "Debian Python"
|
|
elif isFedoraPackagePython():
|
|
return "Fedora Python"
|
|
elif isArchPackagePython():
|
|
return "Arch Python"
|
|
elif isAlpinePackagePython():
|
|
return "Alpine Python"
|
|
elif isGithubActionsPython():
|
|
return "GitHub Actions Python"
|
|
elif isHomebrewPython():
|
|
return "Homebrew Python"
|
|
elif isRyePython():
|
|
return "Rye Python"
|
|
elif isPythonBuildStandalonePython():
|
|
return "Python Build Standalone"
|
|
elif isApplePython():
|
|
return "Apple Python"
|
|
elif isPyenvPython():
|
|
return "pyenv"
|
|
elif isPosixWindows():
|
|
return "MSYS2 Posix"
|
|
elif isMSYS2MingwPython():
|
|
return "MSYS2 MinGW"
|
|
elif isTermuxPython():
|
|
return "Android Termux"
|
|
elif isWindowsStorePython():
|
|
return "Windows Store Python"
|
|
elif isCPythonOfficialPackage():
|
|
return "CPython Official"
|
|
elif isSelfCompiledPythonUninstalled():
|
|
return "Self Compiled Uninstalled"
|
|
elif isManyLinuxPython():
|
|
return "Manylinux Python"
|
|
else:
|
|
return "Unknown"
|
|
|
|
|
|
def hasAcceleratedSupportedFlavor():
|
|
return not isWindowsStorePython()
|
|
|
|
|
|
# Part of "Nuitka", an optimizing Python compiler that is compatible and
|
|
# integrates with CPython, but also works on its own.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|