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

umu_run: handle Protons without an explicit runtime requirement #410

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
106 changes: 58 additions & 48 deletions umu/umu_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
from umu.umu_log import log
from umu.umu_plugins import set_env_toml
from umu.umu_proton import get_umu_proton
from umu.umu_runtime import setup_umu
from umu.umu_runtime import create_shim, setup_umu
from umu.umu_util import (
get_libc,
get_library_paths,
Expand Down Expand Up @@ -99,9 +99,7 @@ def setup_pfx(path: str) -> None:
wineuser.symlink_to("steamuser")


def check_env(
env: dict[str, str], session_pools: tuple[ThreadPoolExecutor, PoolManager]
) -> dict[str, str] | dict[str, Any]:
def check_env(env: dict[str, str]) -> tuple[dict[str, str] | dict[str, Any], bool]:
"""Before executing a game, check for environment variables and set them.

GAMEID is strictly required and the client is responsible for setting this.
Expand Down Expand Up @@ -133,9 +131,10 @@ def check_env(

env["WINEPREFIX"] = os.environ.get("WINEPREFIX", "")

download_proton = False
# Skip Proton if running a native Linux executable
if os.environ.get("UMU_NO_PROTON") == "1":
return env
return env, download_proton

path: Path = STEAM_COMPAT.joinpath(os.environ.get("PROTONPATH", ""))
if os.environ.get("PROTONPATH") and path.name == "UMU-Latest":
Expand All @@ -147,24 +146,15 @@ def check_env(

# Proton Codename
if os.environ.get("PROTONPATH") in {"GE-Proton", "GE-Latest", "UMU-Latest"}:
get_umu_proton(env, session_pools)
download_proton = True

if "PROTONPATH" not in os.environ:
os.environ["PROTONPATH"] = ""
get_umu_proton(env, session_pools)
download_proton = True

env["PROTONPATH"] = os.environ["PROTONPATH"]

# If download fails/doesn't exist in the system, raise an error
if not os.environ["PROTONPATH"]:
err: str = (
"Environment variable not set or is empty: PROTONPATH\n"
f"Possible reason: GE-Proton or UMU-Proton not found in '{STEAM_COMPAT}'"
" or network error"
)
raise FileNotFoundError(err)

return env
return env, download_proton


def set_env(
Expand Down Expand Up @@ -299,12 +289,17 @@ def enable_steam_game_drive(env: dict[str, str]) -> dict[str, str]:
def build_command(
env: dict[str, str],
local: Path,
opts: list[str] = [],
version: str,
opts: list[str] | None = None,
) -> tuple[Path | str, ...]:
"""Build the command to be executed."""
shim: Path = local.joinpath("umu-shim")
proton: Path = Path(env["PROTONPATH"], "proton")
entry_point: Path = local.joinpath("umu")
entry_point: tuple[Path, str, str, str] | tuple[()] = (
local.joinpath(version, "umu"), "--verb", env["PROTON_VERB"], "--"
) if version != "host" else ()
if opts is None:
opts = []

if env.get("UMU_NO_PROTON") != "1" and not proton.is_file():
err: str = "The following file was not found in PROTONPATH: proton"
Expand All @@ -313,7 +308,7 @@ def build_command(
# Exit if the entry point is missing
# The _v2-entry-point script and container framework tools are included in
# the same image, so this can happen if the image failed to download
if not entry_point.is_file():
if entry_point and not entry_point[0].is_file():
err: str = (
f"_v2-entry-point (umu) cannot be found in '{local}'\n"
"Runtime Platform missing or download incomplete"
Expand All @@ -325,10 +320,7 @@ def build_command(
# The position of arguments matter for winetricks
# Usage: ./winetricks [options] [command|verb|path-to-verb] ...
return (
entry_point,
"--verb",
env["PROTON_VERB"],
"--",
*entry_point,
proton,
env["PROTON_VERB"],
env["EXE"],
Expand All @@ -340,18 +332,15 @@ def build_command(
# Ideally, for reliability, executables should be compiled within
# the Steam Runtime
if env.get("UMU_NO_PROTON") == "1":
return (entry_point, "--verb", env["PROTON_VERB"], "--", env["EXE"], *opts)
return *entry_point, env["EXE"], *opts

# Will run the game outside the Steam Runtime w/ Proton
if env.get("UMU_NO_RUNTIME") == "1":
log.warning("Runtime Platform disabled")
return proton, env["PROTON_VERB"], env["EXE"], *opts

return (
entry_point,
"--verb",
env["PROTON_VERB"],
"--",
*entry_point,
shim,
proton,
env["PROTON_VERB"],
Expand Down Expand Up @@ -757,7 +746,7 @@ def get_umu_version_from_manifest(
break

if not appid:
return None
return "host", "host", "host"

if appid not in appids:
return None
Expand Down Expand Up @@ -874,23 +863,51 @@ def umu_run(args: Namespace | tuple[str, list[str]]) -> int:
# Default to a strict 5 second timeouts throughout
timeout: Timeout = Timeout(connect=NET_TIMEOUT, read=NET_TIMEOUT)

# ensure base directory exists
UMU_LOCAL.mkdir(parents=True, exist_ok=True)

with (
ThreadPoolExecutor() as thread_pool,
PoolManager(timeout=timeout, retries=retries) as http_pool,
):
session_pools: tuple[ThreadPoolExecutor, PoolManager] = (thread_pool, http_pool)
# Setup the launcher and runtime files
future: Future = thread_pool.submit(
setup_umu, root, UMU_LOCAL / version[1], version, session_pools
)

download_proton = False
if isinstance(args, Namespace):
env, opts = set_env_toml(env, args)
else:
opts = args[1] # Reference the executable options
check_env(env, session_pools)
_, download_proton = check_env(env)

UMU_LOCAL.joinpath(version[1]).mkdir(parents=True, exist_ok=True)
if version[1] != "host":
UMU_LOCAL.joinpath(version[1]).mkdir(parents=True, exist_ok=True)

future: Future = thread_pool.submit(
setup_umu, root, UMU_LOCAL / version[1], version, session_pools
)

if download_proton:
get_umu_proton(env, session_pools)

# If download fails/doesn't exist in the system, raise an error
if not os.environ["PROTONPATH"]:
err: str = (
"Environment variable not set or is empty: PROTONPATH\n"
f"Possible reason: GE-Proton or UMU-Proton not found in '{STEAM_COMPAT}'"
" or network error"
)
raise FileNotFoundError(err)

try:
future.result()
except (MaxRetryError, NewConnectionError, TimeoutErrorUrllib3, ValueError) as e:
if not has_umu_setup():
err: str = (
"umu has not been setup for the user\n"
"An internet connection is required to setup umu"
)
raise RuntimeError(err) from e
log.debug("Network is unreachable")

# Prepare the prefix
with unix_flock(f"{UMU_LOCAL}/{FileLock.Prefix.value}"):
Expand All @@ -899,31 +916,24 @@ def umu_run(args: Namespace | tuple[str, list[str]]) -> int:
# Configure the environment
set_env(env, args)

# Restore shim if missing
if not UMU_LOCAL.joinpath("umu-shim").is_file():
create_shim(UMU_LOCAL / "umu-shim")

# Set all environment variables
# NOTE: `env` after this block should be read only
for key, val in env.items():
log.debug("%s=%s", key, val)
os.environ[key] = val

try:
future.result()
except (MaxRetryError, NewConnectionError, TimeoutErrorUrllib3, ValueError):
if not has_umu_setup():
err: str = (
"umu has not been setup for the user\n"
"An internet connection is required to setup umu"
)
raise RuntimeError(err)
log.debug("Network is unreachable")

# Exit if the winetricks verb is already installed to avoid reapplying it
if env["EXE"].endswith("winetricks") and is_installed_verb(
opts, Path(env["WINEPREFIX"])
):
sys.exit(1)

# Build the command
command: tuple[Path | str, ...] = build_command(env, UMU_LOCAL / version[1], opts)
command: tuple[Path | str, ...] = build_command(env, UMU_LOCAL, version[1], opts)
log.debug("%s", command)

# Run the command
Expand Down
6 changes: 0 additions & 6 deletions umu/umu_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,6 @@ def _install_umu(
log.debug("Renaming: _v2-entry-point -> umu")
local.joinpath("_v2-entry-point").rename(local.joinpath("umu"))

create_shim(local / "umu-shim")

# Validate the runtime after moving the files
check_runtime(local, runtime_ver)

Expand Down Expand Up @@ -390,10 +388,6 @@ def _update_umu(
# Update our runtime
_update_umu_platform(local, runtime, runtime_ver, session_pools, resp)

# Restore shim if missing
if not local.joinpath("umu-shim").is_file():
create_shim(local / "umu-shim")

log.info("%s is up to date", variant)


Expand Down
Loading