diff --git a/installer/lib/installer.py b/installer/lib/installer.py index 62e4b4a504..be6c26399b 100644 --- a/installer/lib/installer.py +++ b/installer/lib/installer.py @@ -5,6 +5,7 @@ InvokeAI installer script import os import platform +import re import shutil import subprocess import sys @@ -22,6 +23,15 @@ ARCH = platform.uname().machine VERSION = "latest" +def get_version_from_wheel_filename(wheel_filename: str) -> str: + match = re.search(r"-(\d+\.\d+\.\d+)", wheel_filename) + if match: + version = match.group(1) + return version + else: + raise ValueError(f"Could not extract version from wheel filename: {wheel_filename}") + + class Installer: """ Deploys an InvokeAI installation into a given path @@ -111,6 +121,7 @@ class Installer: yes_to_all: bool = False, version: Optional[str] = None, find_links: Optional[str] = None, + wheel: Optional[Path] = None, ) -> None: """ Install the InvokeAI application into the given runtime path @@ -127,9 +138,12 @@ class Installer: import messages - messages.welcome(self.available_releases) - - version = messages.choose_version(self.available_releases) + if wheel: + messages.installing_from_wheel(wheel.name) + version = get_version_from_wheel_filename(wheel.name) + else: + messages.welcome(self.available_releases) + version = messages.choose_version(self.available_releases) auto_dest = Path(os.environ.get("INVOKEAI_ROOT", root)).expanduser().resolve() destination = auto_dest if yes_to_all else messages.dest_path(root) @@ -144,11 +158,7 @@ class Installer: # install dependencies and the InvokeAI application (extra_index_url, optional_modules) = get_torch_source() if not yes_to_all else (None, None) - self.instance.install( - extra_index_url, - optional_modules, - find_links, - ) + self.instance.install(extra_index_url, optional_modules, find_links, wheel) # install the launch/update scripts into the runtime directory self.instance.install_user_scripts() @@ -187,6 +197,7 @@ class InvokeAiInstance: extra_index_url: Optional[str] = None, optional_modules: Optional[str] = None, find_links: Optional[str] = None, + wheel: Optional[Path] = None, ): """ Install the package from PyPi. @@ -231,12 +242,12 @@ class InvokeAiInstance: "--require-virtualenv", "--force-reinstall", "--use-pep517", - str(src), + str(src) if not wheel else str(wheel), "--find-links" if find_links is not None else None, find_links, "--extra-index-url" if extra_index_url is not None else None, extra_index_url, - pre_flag, + pre_flag if not wheel else None, ] try: diff --git a/installer/lib/main.py b/installer/lib/main.py index b3582f2fdc..08149bd107 100644 --- a/installer/lib/main.py +++ b/installer/lib/main.py @@ -44,6 +44,14 @@ if __name__ == "__main__": default=None, ) + parser.add_argument( + "--wheel", + dest="wheel", + help="Specifies a wheel for the InvokeAI package. Used for troubleshooting or testing prereleases.", + type=Path, + default=None, + ) + args = parser.parse_args() inst = Installer() diff --git a/installer/lib/messages.py b/installer/lib/messages.py index f7734de5ef..5a08303b1c 100644 --- a/installer/lib/messages.py +++ b/installer/lib/messages.py @@ -73,6 +73,33 @@ def welcome(available_releases: tuple[list[str], list[str]] | None = None) -> No console.line() +def installing_from_wheel(wheel_filename: str) -> None: + """Display a message about installing from a wheel""" + + @group() + def text(): + yield Text.from_markup(f"You are installing from a wheel file: [bold]{wheel_filename}\n") + yield Text.from_markup( + "[bold orange3]If you are not sure why you are doing this, you should cancel and install InvokeAI normally." + ) + + console.print( + Panel( + title="Installing from Wheel", + renderable=text(), + box=box.DOUBLE, + expand=True, + padding=(1, 2), + ) + ) + + should_proceed = Confirm.ask("Do you want to proceed?") + + if not should_proceed: + console.print("Installation cancelled.") + exit() + + def choose_version(available_releases: tuple[list[str], list[str]] | None = None) -> str: """ Prompt the user to choose an Invoke version to install