mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2026-05-19 13:54:49 -04:00
Add redbot-update command for updating Red (#6734)
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Final, Optional, Tuple
|
||||
|
||||
import click
|
||||
from packaging.version import Version
|
||||
from rich.text import Text
|
||||
|
||||
from redbot._update import cog_compatibility_checker, common
|
||||
from redbot._update.cog_compatibility_checker import CompatibilitySummary
|
||||
from redbot.core import _drivers
|
||||
from redbot.core._cli import asyncio_run
|
||||
from redbot.core.utils._internal_utils import fetch_latest_red_version
|
||||
|
||||
from . import arg_names
|
||||
|
||||
|
||||
EXIT_INSTANCE_SITE_PREFIX_MISMATCH: Final = 4
|
||||
EXIT_INSTANCE_BACKEND_UNSUPPORTED: Final = 5
|
||||
CMD_NAME: Final = "check-cog-compatibility"
|
||||
_COMPATIBILITY_RESULTS_ENV_VAR = "_RED_UPDATE_COMPATIBILITY_RESULTS_FILE"
|
||||
|
||||
|
||||
@click.command(CMD_NAME)
|
||||
@click.argument(
|
||||
"instances",
|
||||
nargs=-1,
|
||||
type=click.Choice(common.INSTANCE_LIST),
|
||||
default=None,
|
||||
metavar="[INSTANCE_NAME]",
|
||||
)
|
||||
@click.option(
|
||||
arg_names.RED_VERSION,
|
||||
type=common.VersionParamType(),
|
||||
default=None,
|
||||
help="The Red version to check cog compatibility for."
|
||||
" If not provided, the information about latest available version will be fetched"
|
||||
" and the command will check whether installed cogs support that version.\n"
|
||||
"If this option is provided, --python-version also has to be provided.",
|
||||
)
|
||||
@click.option(
|
||||
arg_names.PYTHON_VERSION,
|
||||
type=common.VersionParamType(),
|
||||
default=None,
|
||||
help="The Python version to check cog compatibility for."
|
||||
" If not provided, the command will either use the current interpreter's version or,"
|
||||
" if that version is not compatible with the latest Red version, it will try to"
|
||||
" find the latest available CPython interpreter on the system and will check whether"
|
||||
" installed cogs support it.\n"
|
||||
"If this option is provided, --red-version also has to be provided.",
|
||||
)
|
||||
@click.pass_context
|
||||
def check_cog_compatibility(
|
||||
ctx: click.Context,
|
||||
instances: Tuple[str, ...],
|
||||
red_version: Optional[Version],
|
||||
python_version: Optional[Version],
|
||||
) -> None:
|
||||
"""
|
||||
Check if the installed cogs are compatible with the given version.
|
||||
"""
|
||||
if (red_version, python_version).count(None) == 1:
|
||||
raise click.BadParameter(
|
||||
"Either both --red-version and --python-version options"
|
||||
" have to be specified or neither.",
|
||||
param_hint=[arg_names.RED_VERSION, arg_names.PYTHON_VERSION],
|
||||
)
|
||||
|
||||
asyncio_run(
|
||||
_check_cog_compatibility_command_impl(
|
||||
red_version=red_version,
|
||||
python_version=python_version,
|
||||
instances=instances,
|
||||
ignore_prefix=ctx.obj["IGNORE_PREFIX"],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def _check_cog_compatibility_command_impl(
|
||||
*,
|
||||
red_version: Optional[Version],
|
||||
python_version: Optional[Version],
|
||||
instances: Tuple[str, ...] = (),
|
||||
ignore_prefix: bool = False,
|
||||
) -> None:
|
||||
console = common.get_console()
|
||||
if red_version is None or python_version is None:
|
||||
with console.status("Checking latest version..."):
|
||||
latest = await fetch_latest_red_version()
|
||||
red_version = latest.version
|
||||
|
||||
python_version = Version(".".join(map(str, sys.version_info[:3])))
|
||||
if python_version not in latest.requires_python:
|
||||
interpreters = common.search_for_interpreters(latest.requires_python)
|
||||
_, python_version, _ = interpreters[0]
|
||||
|
||||
if len(instances) == 1:
|
||||
results_file = os.getenv(_COMPATIBILITY_RESULTS_ENV_VAR, "")
|
||||
try:
|
||||
results = await cog_compatibility_checker.check_instance(
|
||||
instances[0],
|
||||
latest_version=red_version,
|
||||
interpreter_version=python_version,
|
||||
ignore_prefix=ignore_prefix,
|
||||
)
|
||||
except _drivers.MissingExtraRequirements:
|
||||
if not results_file:
|
||||
common.print_with_prefix_column(
|
||||
common.ICON_ERROR,
|
||||
Text(instances[0], style="bold"),
|
||||
" instance could not be checked as it uses a storage backend"
|
||||
" that is not supported by the current Red installation"
|
||||
" (some requirements are missing).",
|
||||
)
|
||||
raise SystemExit(EXIT_INSTANCE_BACKEND_UNSUPPORTED)
|
||||
except cog_compatibility_checker.InstanceSitePrefixMismatchError as exc:
|
||||
if not results_file:
|
||||
common.print_with_prefix_column(
|
||||
common.ICON_ERROR,
|
||||
Text(exc.instance_name, style="bold"),
|
||||
" instance could not be checked as it is a part of"
|
||||
" a different Python installation and/or virtual environment.",
|
||||
)
|
||||
raise SystemExit(EXIT_INSTANCE_SITE_PREFIX_MISMATCH)
|
||||
if results_file:
|
||||
with open(results_file, "w", encoding="utf-8") as fp:
|
||||
json.dump(results.to_json_dict(), fp)
|
||||
return
|
||||
|
||||
if not instances:
|
||||
instances = tuple(common.INSTANCE_LIST)
|
||||
checked_instances = []
|
||||
for instance_name in instances:
|
||||
exit_code, _, _ = await call(
|
||||
instance_name,
|
||||
red_version=red_version,
|
||||
python_version=python_version,
|
||||
ignore_prefix=ignore_prefix,
|
||||
)
|
||||
if exit_code != EXIT_INSTANCE_SITE_PREFIX_MISMATCH:
|
||||
if exit_code:
|
||||
raise SystemExit(exit_code)
|
||||
checked_instances.append(instance_name)
|
||||
|
||||
if not checked_instances:
|
||||
common.print_with_prefix_column(
|
||||
common.ICON_ERROR, "There were no instances to check cog compatibility for."
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
async def call(
|
||||
instance_name: str,
|
||||
*,
|
||||
red_version: Version,
|
||||
python_version: Version,
|
||||
ignore_prefix: bool = False,
|
||||
return_results: bool = False,
|
||||
stdout: Optional[int] = None,
|
||||
) -> Tuple[int, Optional[str], Optional[CompatibilitySummary]]:
|
||||
debug_args = (arg_names.DEBUG,) * common.get_log_cli_level()
|
||||
args = [
|
||||
"-m",
|
||||
"redbot._update",
|
||||
*debug_args,
|
||||
CMD_NAME,
|
||||
instance_name,
|
||||
arg_names.RED_VERSION,
|
||||
str(red_version),
|
||||
arg_names.PYTHON_VERSION,
|
||||
str(python_version),
|
||||
]
|
||||
if ignore_prefix:
|
||||
args.append(arg_names.CHECK_OTHER_PYTHON_INSTALLS)
|
||||
env = os.environ.copy()
|
||||
|
||||
# terminal woes
|
||||
console = common.get_console()
|
||||
if console.is_terminal:
|
||||
env["TTY_COMPATIBLE"] = "1"
|
||||
# Rich only checks stdout for Windows console features:
|
||||
# https://github.com/Textualize/rich/blob/fc41075a3206d2a5fd846c6f41c4d2becab814fa/rich/_windows.py#L46
|
||||
env[common.INTERNAL_LEGACY_WINDOWS_ENV_VAR] = "1" if console.legacy_windows else "0"
|
||||
else:
|
||||
# Rich does not set legacy_windows correctly when is_terminal is False
|
||||
# https://github.com/Textualize/rich/issues/3647
|
||||
env[common.INTERNAL_LEGACY_WINDOWS_ENV_VAR] = "0"
|
||||
env["PYTHONIOENCODING"] = sys.stdout.encoding
|
||||
|
||||
results = None
|
||||
results_file = None
|
||||
if return_results:
|
||||
results_file = tempfile.NamedTemporaryFile(delete=False)
|
||||
try:
|
||||
if results_file is not None:
|
||||
results_file.close()
|
||||
env[_COMPATIBILITY_RESULTS_ENV_VAR] = str(results_file.name)
|
||||
|
||||
proc = await asyncio.create_subprocess_exec(sys.executable, *args, env=env, stdout=stdout)
|
||||
stdout_data, _ = await proc.communicate()
|
||||
decoded_stdout = None
|
||||
if stdout_data is not None:
|
||||
decoded_stdout = stdout_data.decode()
|
||||
exit_code = await proc.wait()
|
||||
if not exit_code and results_file is not None:
|
||||
with open(results_file.name, encoding="utf-8") as fp:
|
||||
results = CompatibilitySummary.from_json_dict(json.load(fp))
|
||||
finally:
|
||||
if results_file is not None:
|
||||
os.remove(results_file.name)
|
||||
|
||||
return exit_code, decoded_stdout, results
|
||||
Reference in New Issue
Block a user