Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(installer): remove updater & support installing from wheel #6054

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 52 additions & 40 deletions installer/lib/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import os
import platform
import re
import shutil
import subprocess
import sys
Expand All @@ -22,6 +23,15 @@
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
Expand All @@ -36,7 +46,7 @@ def __init__(self) -> None:
self.bootstrap()
self.available_releases = get_github_releases()

def mktemp_venv(self) -> TemporaryDirectory:
def mktemp_venv(self) -> TemporaryDirectory[str]:
"""
Creates a temporary virtual environment for the installer itself

Expand All @@ -58,7 +68,7 @@ def mktemp_venv(self) -> TemporaryDirectory:

return venv_dir

def bootstrap(self, verbose: bool = False) -> TemporaryDirectory | None:
def bootstrap(self, verbose: bool = False) -> TemporaryDirectory[str] | None:
"""
Bootstrap the installer venv with packages required at install time
"""
Expand Down Expand Up @@ -87,7 +97,7 @@ def bootstrap(self, verbose: bool = False) -> TemporaryDirectory | None:
except subprocess.CalledProcessError as e:
print(e)

def app_venv(self, venv_parent) -> Path:
def app_venv(self, venv_parent: Path) -> Path:
"""
Create a virtualenv for the InvokeAI installation
"""
Expand All @@ -106,26 +116,29 @@ def app_venv(self, venv_parent) -> Path:
return venv_dir

def install(
self, version=None, root: str = "~/invokeai", yes_to_all=False, find_links: Optional[Path] = None
self,
root: str = "~/invokeai",
yes_to_all: bool = False,
find_links: Optional[str] = None,
wheel: Optional[Path] = None,
) -> None:
"""
Install the InvokeAI application into the given runtime path

:param root: Destination path for the installation
:type root: str
:param version: InvokeAI version to install
:type version: str
:param yes: Accept defaults to all questions
:type yes: bool
:param find_links: A local directory to search for requirement wheels before going to remote indexes
:type find_links: Path
"""Install the InvokeAI application into the given runtime path

Args:
root: Destination path for the installation
yes_to_all: Accept defaults to all questions
find_links: A local directory to search for requirement wheels before going to remote indexes
wheel: A wheel file to install
"""

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)
Expand All @@ -140,11 +153,7 @@ def install(

# 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()
Expand Down Expand Up @@ -178,18 +187,20 @@ def get(self) -> tuple[Path, Path]:

return (self.runtime, self.venv)

def install(self, extra_index_url=None, optional_modules=None, find_links=None):
"""
Install the package from PyPi.

:param extra_index_url: the "--extra-index-url ..." line for pip to look in extra indexes.
:type extra_index_url: str

:param optional_modules: optional modules to install using "[module1,module2]" format.
:type optional_modules: str

:param find_links: path to a directory containing wheels to be searched prior to going to the internet
:type find_links: Path
def install(
self,
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 or a wheel, if provided.

Args:
extra_index_url: the "--extra-index-url ..." line for pip to look in extra indexes.
optional_modules: optional modules to install using "[module1,module2]" format.
find_links: path to a directory containing wheels to be searched prior to going to the internet
wheel: a wheel file to install
"""

import messages
Expand All @@ -213,7 +224,7 @@ def install(self, extra_index_url=None, optional_modules=None, find_links=None):

messages.simple_banner("Installing the InvokeAI Application :art:")

from plumbum import FG, ProcessExecutionError, local # type: ignore
from plumbum import FG, ProcessExecutionError, local

pip = local[self.pip]

Expand All @@ -222,12 +233,12 @@ def install(self, extra_index_url=None, optional_modules=None, find_links=None):
"--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, # Ignore the flag if we are installing a wheel
]

try:
Expand Down Expand Up @@ -320,7 +331,7 @@ def set_sys_path(venv_path: Path) -> None:
sys.path.append(str(Path(venv_path, lib, "site-packages").expanduser().resolve()))


def get_github_releases() -> tuple[list, list] | None:
def get_github_releases() -> tuple[list[str], list[str]] | None:
"""
Query Github for published (pre-)release versions.
Return a tuple where the first element is a list of stable releases and the second element is a list of pre-releases.
Expand All @@ -331,7 +342,8 @@ def get_github_releases() -> tuple[list, list] | None:

## get latest releases using github api
url = "https://api.github.com/repos/invoke-ai/InvokeAI/releases"
releases, pre_releases = [], []
releases: list[str] = []
pre_releases: list[str] = []
try:
res = requests.get(url)
res.raise_for_status()
Expand Down
13 changes: 7 additions & 6 deletions installer/lib/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,17 @@
)

parser.add_argument(
"--version",
dest="version",
help="Version of InvokeAI to install. Default to the latest stable release. A special 'pre' value will install the latest published pre-release version.",
"--find-links",
dest="find_links",
help="Specifies a directory of local wheel files to be searched prior to searching the online repositories.",
type=Path,
default=None,
)

parser.add_argument(
"--find-links",
dest="find_links",
help="Specifies a directory of local wheel files to be searched prior to searching the online repositories.",
"--wheel",
dest="wheel",
help="Specifies a wheel for the InvokeAI package. Used for troubleshooting or testing prereleases.",
type=Path,
default=None,
)
Expand Down
34 changes: 31 additions & 3 deletions installer/lib/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import platform
from enum import Enum
from pathlib import Path
from typing import Optional

from prompt_toolkit import prompt
from prompt_toolkit.completion import FuzzyWordCompleter, PathCompleter
Expand Down Expand Up @@ -36,7 +37,7 @@
console = Console(style=Style(color="grey74", bgcolor="grey19"))


def welcome(available_releases: tuple | None = None) -> None:
def welcome(available_releases: tuple[list[str], list[str]] | None = None) -> None:
@group()
def text():
if (platform_specific := _platform_specific_help()) is not None:
Expand Down Expand Up @@ -72,7 +73,34 @@ def text():
console.line()


def choose_version(available_releases: tuple | None = None) -> str:
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
"""
Expand Down Expand Up @@ -114,7 +142,7 @@ def confirm_install(dest: Path) -> bool:
return dest_confirmed


def dest_path(dest=None) -> Path | None:
def dest_path(dest: Optional[str | Path] = None) -> Path | None:
"""
Prompt the user for the destination path and create the path

Expand Down
17 changes: 7 additions & 10 deletions installer/templates/invoke.bat.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ set INVOKEAI_ROOT=.
echo Desired action:
echo 1. Generate images with the browser-based interface
echo 2. Open the developer console
echo 3. Update InvokeAI (DEPRECATED - please use the installer)
echo 4. Run the InvokeAI image database maintenance script
echo 5. Command-line help
echo 3. Run the InvokeAI image database maintenance script
echo 4. Command-line help
echo Q - Quit
set /P choice="Please enter 1-10, Q: [1] "
echo.
echo To update, download and run the installer from https://github.com/invoke-ai/InvokeAI/releases/latest.
echo.
set /P choice="Please enter 1-4, Q: [1] "
if not defined choice set choice=1
IF /I "%choice%" == "1" (
echo Starting the InvokeAI browser-based UI..
Expand All @@ -32,14 +34,9 @@ IF /I "%choice%" == "1" (
echo *** Type `exit` to quit this shell and deactivate the Python virtual environment ***
call cmd /k
) ELSE IF /I "%choice%" == "3" (
echo UPDATING FROM WITHIN THE APP IS BEING DEPRECATED.
echo Please download the installer from https://github.com/invoke-ai/InvokeAI/releases/latest and run it to update your installation.
timeout 4
python -m invokeai.frontend.install.invokeai_update
) ELSE IF /I "%choice%" == "4" (
echo Running the db maintenance script...
python .venv\Scripts\invokeai-db-maintenance.exe
) ELSE IF /I "%choice%" == "5" (
) ELSE IF /I "%choice%" == "4" (
echo Displaying command line help...
python .venv\Scripts\invokeai-web.exe --help %*
pause
Expand Down
17 changes: 5 additions & 12 deletions installer/templates/invoke.sh.in
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,11 @@ do_choice() {
bash --init-file "$file_name"
;;
3)
clear
printf "UPDATING FROM WITHIN THE APP IS BEING DEPRECATED\n"
printf "Please download the installer from https://github.com/invoke-ai/InvokeAI/releases/latest and run it to update your installation.\n"
sleep 4
python -m invokeai.frontend.install.invokeai_update
;;
4)
clear
printf "Running the db maintenance script\n"
invokeai-db-maintenance --root ${INVOKEAI_ROOT}
;;
5)
4)
clear
printf "Command-line help\n"
invokeai-web --help
Expand All @@ -79,11 +72,11 @@ do_line_input() {
printf "What would you like to do?\n"
printf "1: Generate images using the browser-based interface\n"
printf "2: Open the developer console\n"
printf "3: Update InvokeAI\n"
printf "4: Run the InvokeAI image database maintenance script\n"
printf "5: Command-line help\n"
printf "3: Run the InvokeAI image database maintenance script\n"
printf "4: Command-line help\n"
printf "Q: Quit\n\n"
read -p "Please enter 1-5, Q: [1] " yn
printf "To update, download and run the installer from https://github.com/invoke-ai/InvokeAI/releases/latest.\n\n"
read -p "Please enter 1-4, Q: [1] " yn
choice=${yn:='1'}
do_choice $choice
clear
Expand Down
Loading
Loading