from __future__ import print_function import io import os import platform import socket import sys from pathlib import Path import requests from PyInstaller import __version__ as pyinstaller_version_string from . import __version__ class ForwardToFunctionStream(io.TextIOBase): def __init__(self, output_function=print): self.output_function = output_function def write(self, string): self.output_function(string) return len(string) def open_output_in_explorer(output_directory, input_filename, is_one_file): """Open the output in the local file explorer""" folder_directory = os.path.abspath(output_directory) if platform.system() == "Windows": # For windows, we can highlight the file in the explorer window target_base_to_open = Path(input_filename).stem + (".exe" if is_one_file else "") target_path_to_open = Path(folder_directory) / target_base_to_open if target_path_to_open.exists(): os.popen(f'explorer /select,"{target_path_to_open}"') else: # If the file doesn't exist, just open the folder) os.startfile(folder_directory, "explore") elif platform.system() == "Linux": os.system('xdg-open "' + folder_directory + '"') elif platform.system() == "Darwin": os.system('open "' + folder_directory + '"') else: return False return True class PackageVersion: def __init__(self, version_string, url) -> None: self.version_string = version_string self.version = parse_version_tuple(version_string) self.url = url def __get_latest_version_for_library(library_repo): try: response = requests.get(f"https://api.github.com/repos/{library_repo}/releases/latest") response.raise_for_status() response_data = response.json() latest_release_tag_name = response_data["tag_name"].strip("v") return PackageVersion(latest_release_tag_name.strip("v"), response_data["html_url"]) except requests.exceptions.RequestException: return None def __get_latest_auto_py_to_exe_version(): """Get the latest version of auto-py-to-exe""" return __get_latest_version_for_library("brentvollebregt/auto-py-to-exe") def __get_latest_pyinstaller_version(): """Get the latest version of PyInstaller""" return __get_latest_version_for_library("pyinstaller/pyinstaller") def get_warnings(): warnings = [] # Check auto-py-to-exe version is it latest try: current_auto_py_to_exe_version = parse_version_tuple(__version__) latest_auto_py_to_exe_version = __get_latest_auto_py_to_exe_version() if latest_auto_py_to_exe_version is None: raise Exception("Unable to check for the latest version of auto-py-to-exe.") elif latest_auto_py_to_exe_version.version > current_auto_py_to_exe_version: message = f'A new version of auto-py-to-exe has been released: {__version__} → {latest_auto_py_to_exe_version.version_string}' message += "\nUpgrade using: python -m pip install auto-py-to-exe --upgrade" warnings.append(message) except Exception as e: message = f"\nWarning: {e}" warnings.append(message) # Check PyInstaller version is it latest try: current_pyinstaller_version = parse_version_tuple(pyinstaller_version_string) latest_pyinstaller_version = __get_latest_pyinstaller_version() if latest_pyinstaller_version is None: raise Exception("Unable to check for the latest version of PyInstaller.") elif latest_pyinstaller_version.version > current_pyinstaller_version: message = f'A new version of PyInstaller has been released: {pyinstaller_version_string} → {latest_pyinstaller_version.version_string}' message += "\nUpgrade using: python -m pip install pyinstaller --upgrade" warnings.append(message) except VersionParseError: pass # Don't warn about a new version when using a non-official release except Exception as e: message = f"\nWarning: {e}" warnings.append(message) try: pyinstaller_version = parse_version_tuple(pyinstaller_version_string) except ValueError: message = "Unable to parse PyInstaller version - this may be because you aren't using an official release." message += "\nYou are currently using PyInstaller {pyinstaller_version}.".format( pyinstaller_version=pyinstaller_version_string ) message += "\nIf this is an official release, please report this issue on GitHub." warnings.append(message) return warnings # Make sure PyInstaller 3.4 or above is being used with Python 3.7 try: if sys.version_info >= (3, 7) and pyinstaller_version < (3, 4): message = "You will need PyInstaller 3.4 or above to use this tool with Python 3.7." message += "\nYou are currently using PyInstaller {pyinstaller_version}.".format( pyinstaller_version=pyinstaller_version_string ) message += "\nPlease upgrade PyInstaller: python -m pip install pyinstaller --upgrade" warnings.append(message) except ValueError: pass # Dev branches will have pyinstaller_version as a string in the form X.Y.devZ+HASH. Ignore it if this is the case. # Make sure PyInstaller 4.0 or above is being used with Python 3.8 and 3.9 try: if ( sys.version_info.major == 3 and (sys.version_info.minor == 8 or sys.version_info.minor == 9) and pyinstaller_version < (4, 1) ): message = "PyInstaller 4.0 and below does not officially support Python 3.8 and 3.9." message += "\nYou are currently using PyInstaller {pyinstaller_version}.".format( pyinstaller_version=pyinstaller_version_string ) message += "\nIt is highly recommended to update your version of PyInstaller using: python -m pip install pyinstaller --upgrade" warnings.append(message) except ValueError: pass # Dev branches will have pyinstaller_version as a string in the form X.Y.devZ+HASH. Ignore it if this is the case. # Make sure PyInstaller 4.6 or above is being used with Python 3.10 try: if sys.version_info.major == 3 and sys.version_info.minor == 10 and pyinstaller_version < (4, 6): message = "You will need PyInstaller 4.6 or above to use this tool with Python 3.10." message += "\nYou are currently using PyInstaller {pyinstaller_version}.".format( pyinstaller_version=pyinstaller_version_string ) message += "\nPlease upgrade PyInstaller: python -m pip install pyinstaller --upgrade" warnings.append(message) except ValueError: pass # Dev branches will have pyinstaller_version as a string in the form X.Y.devZ+HASH. Ignore it if this is the case. # If Python 3.10.0 is being used, we are probably going to see `IndexError: tuple index out of range`. if sys.version_info.major == 3 and sys.version_info.minor == 10 and sys.version_info.micro == 0: message = 'You are using Python 3.10.0. This version of Python has a bug that causes PyInstaller to fail.' message += "\nPlease upgrade to Python 3.10.1 or above." warnings.append(message) # Make sure we are not using Python from the Windows Store if r"Packages\PythonSoftwareFoundation.Python." in sys.executable: message = "It looks like you may be using Python from the Windows Store, the Python binary you are currently using is at: " message += '"' + sys.executable + '"' message += "\n\nPython from the Windows Store is not supported by PyInstaller so you may get errors referencing \"win32ctypes.pywin32.pywintypes.error: (1920, 'LoadLibraryEx', 'The file cannot be accessed by the system'\"." message += '\nTo fix this, use a distribution of Python from python.org.' warnings.append(message) return warnings def get_port(): """Get an available port by starting a new server, stopping and and returning the port""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(("localhost", 0)) port = sock.getsockname()[1] sock.close() return port class VersionParseError(Exception): pass def parse_version_tuple(version_string): """Turn a version string into a tuple of integers e.g. "1.2.3" -> (1, 2, 3)""" try: return tuple(map(int, (version_string.split(".")))) except ValueError: raise VersionParseError()