194 lines
8.8 KiB
Python
194 lines
8.8 KiB
Python
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 href="{latest_auto_py_to_exe_version.url}" target="_blank">A new version of auto-py-to-exe has been released</a>: {__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 href="{latest_pyinstaller_version.url}" target="_blank">A new version of PyInstaller has been released</a>: {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. <a href="https://github.com/brentvollebregt/auto-py-to-exe/issues/215" target="_blank">This version of Python has a bug that causes PyInstaller to fail.</a>'
|
|
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 += '\n<a href="https://github.com/brentvollebregt/auto-py-to-exe/issues/166#issuecomment-827492005" target="_blank">To fix this, use a distribution of Python from python.org.</a>'
|
|
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()
|