Skip to content

Commit

Permalink
feat: support running native Linux executables within SLR (#175)
Browse files Browse the repository at this point in the history
* umu_run: move runtime disable option after winetricks

* umu_run: support running native linux executables within runtime

- Up until now, we were running every executable through Proton (wine) as if it were a Windows executable. If the user or the client knows that Proton is not necessary and is safe to reliably run, we can skip the overhead of wine.

* docs: update docs

* umu_test: update tests

* umu_run: use UMU_NO_PROTON for slr native usage

* docs: update docs

* umu_run: deprecate pressure-vessel value

* umu_test: update tests

* umu_run: skip downloading proton if running native

* umu_run: skip proton file check
  • Loading branch information
R1kaB3rN authored Oct 26, 2024
1 parent 785bcce commit 72499f3
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 35 deletions.
6 changes: 6 additions & 0 deletions docs/umu.1.scd
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ _WINEPREFIX_
_UMU_LOG_
Optional. Enables debug logs for the launcher.


Set _1_ to log environment variables, _warning_ for warning messages, or _debug_ to enable all logs.

_STORE_
Expand All @@ -168,6 +169,11 @@ _UMU_RUNTIME_UPDATE_

Set _0_ to disable updates.

_UMU_NO_PROTON_
Optional. Runs the executable natively within the Steam Linux Runtime. Intended for native Linux games.

Set _1_ to run the executable natively within the SLR.

# SEE ALSO

_umu_(5), _winetricks_(1), _zenity_(1)
Expand Down
50 changes: 37 additions & 13 deletions umu/umu_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ def check_env(

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

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

# Proton Version
if (
os.environ.get("PROTONPATH")
Expand Down Expand Up @@ -296,6 +300,7 @@ def set_env(
# Runtime
env["UMU_NO_RUNTIME"] = os.environ.get("UMU_NO_RUNTIME") or ""
env["UMU_RUNTIME_UPDATE"] = os.environ.get("UMU_RUNTIME_UPDATE") or ""
env["UMU_NO_PROTON"] = os.environ.get("UMU_NO_PROTON") or ""

return env

Expand Down Expand Up @@ -341,20 +346,10 @@ def build_command(
proton: Path = Path(env["PROTONPATH"], "proton")
entry_point: Path = local.joinpath("umu")

# Will run the game w/o Proton, effectively running the game as is. This
# option is intended for debugging purposes, and is otherwise useless
if env.get("UMU_NO_RUNTIME") == "1":
log.warning("Runtime Platform disabled")
return env["EXE"], *opts

if not proton.is_file():
if env.get("UMU_NO_PROTON") != "1" and not proton.is_file():
err: str = "The following file was not found in PROTONPATH: proton"
raise FileNotFoundError(err)

if env.get("UMU_NO_RUNTIME") == "pressure-vessel":
log.warning("Using Proton without Runtime Platform")
return proton, env["PROTON_VERB"], env["EXE"], *opts

# 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
Expand All @@ -365,11 +360,39 @@ def build_command(
)
raise FileNotFoundError(err)

# Configure winetricks to not be prompted for any windows
# Winetricks
if env.get("EXE", "").endswith("winetricks") and opts:
# The position of arguments matter for winetricks
# Usage: ./winetricks [options] [command|verb|path-to-verb] ...
opts = ["-q", *opts]
return (
entry_point,
"--verb",
env["PROTON_VERB"],
"--",
proton,
env["PROTON_VERB"],
env["EXE"],
"-q",
*opts,
)

# Will run the game within the Steam Runtime w/o Proton
# 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,
)

# 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,
Expand Down Expand Up @@ -760,6 +783,7 @@ def main() -> int: # noqa: D103
"UMU_ZENITY": "",
"UMU_NO_RUNTIME": "",
"UMU_RUNTIME_UPDATE": "",
"UMU_NO_PROTON": "",
}
opts: list[str] = []
prereq: bool = False
Expand Down
97 changes: 75 additions & 22 deletions umu/umu_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1200,13 +1200,11 @@ def test_game_drive_empty(self):
self.env["EXE"], "Expected EXE to be empty on empty string"
)

def test_build_command_noruntime(self):
"""Test build_command when disabling the Steam Runtime.
def test_build_command_linux_exe(self):
"""Test build_command when running a Linux executable.
UMU_NO_RUNTIME=1 disables the Steam Runtime, which is no different than
running the executable directly.
Expects the list to contain one string element.
UMU_NO_PROTON=1 disables Proton, running the executable directly in the
Steam Linux Runtime.
"""
result_args = None
test_command = []
Expand All @@ -1222,9 +1220,7 @@ def test_build_command_noruntime(self):
os.environ["PROTONPATH"] = self.test_file
os.environ["GAMEID"] = self.test_file
os.environ["STORE"] = self.test_file
# Setting this mocks a Flatpak environment and UMU_NO_RUNTIME is
# only valid for Flatpak apps
os.environ["UMU_NO_RUNTIME"] = "1"
os.environ["UMU_NO_PROTON"] = "1"
# Args
result_args = umu_run.parse_args()
# Config
Expand All @@ -1233,31 +1229,65 @@ def test_build_command_noruntime(self):
umu_run.setup_pfx(self.env["WINEPREFIX"])
# Env
umu_run.set_env(self.env, result_args)
# Mock setting UMU_NO_RUNTIME. This will not be set in the function
# because the FLATPAK_PATH constant will evaluate to None
self.env["UMU_NO_RUNTIME"] = os.environ["UMU_NO_RUNTIME"]
# Game drive
umu_run.enable_steam_game_drive(self.env)

os.environ |= self.env

# Mock setting up the runtime
with (
patch.object(umu_runtime, "_install_umu", return_value=None),
):
umu_runtime.setup_umu(
self.test_user_share, self.test_local_share, None
)
copytree(
Path(self.test_user_share, "sniper_platform_0.20240125.75305"),
Path(
self.test_local_share, "sniper_platform_0.20240125.75305"
),
dirs_exist_ok=True,
symlinks=True,
)
copy(
Path(self.test_user_share, "run"),
Path(self.test_local_share, "run"),
)
copy(
Path(self.test_user_share, "run-in-sniper"),
Path(self.test_local_share, "run-in-sniper"),
)
copy(
Path(self.test_user_share, "umu"),
Path(self.test_local_share, "umu"),
)

# Build
test_command = umu_run.build_command(self.env, self.test_local_share)
self.assertIsInstance(
test_command, tuple, "Expected a tuple from build_command"
)
self.assertEqual(
len(test_command),
1,
f"Expected 1 element, received {len(test_command)}",
5,
f"Expected 5 element, received {len(test_command)}",
)

entry_point, opt, verb, sep, exe = [*test_command]
self.assertEqual(
entry_point,
self.test_local_share / "umu",
"Expected an entry point",
)
exe, *_ = [*test_command]
self.assertEqual(opt, "--verb", "Expected --verb")
self.assertEqual(verb, "waitforexitandrun", "Expected PROTON_VERB")
self.assertEqual(sep, "--", "Expected --")
self.assertEqual(exe, self.env["EXE"], "Expected the EXE")

def test_build_command_nopv(self):
"""Test build_command when disabling Pressure Vessel.
UMU_NO_RUNTIME=pressure-vessel disables Pressure Vessel, allowing
UMU_NO_RUNTIME=1 disables Pressure Vessel, allowing
the launcher to run Proton on the host -- Flatpak environment.
Expects the list to contain 3 string elements.
Expand All @@ -1276,9 +1306,7 @@ def test_build_command_nopv(self):
os.environ["PROTONPATH"] = self.test_file
os.environ["GAMEID"] = self.test_file
os.environ["STORE"] = self.test_file
# Setting this mocks a Flatpak environment and UMU_NO_RUNTIME is
# only valid for Flatpak apps
os.environ["UMU_NO_RUNTIME"] = "pressure-vessel"
os.environ["UMU_NO_RUNTIME"] = "1"
# Args
result_args = umu_run.parse_args()
# Config
Expand All @@ -1287,12 +1315,37 @@ def test_build_command_nopv(self):
umu_run.setup_pfx(self.env["WINEPREFIX"])
# Env
umu_run.set_env(self.env, result_args)
# Mock setting UMU_NO_RUNTIME. This will not be set in the function
# because the FLATPAK_PATH constant will evaluate to None
self.env["UMU_NO_RUNTIME"] = os.environ["UMU_NO_RUNTIME"]
# Game drive
umu_run.enable_steam_game_drive(self.env)

# Mock setting up the runtime
with (
patch.object(umu_runtime, "_install_umu", return_value=None),
):
umu_runtime.setup_umu(
self.test_user_share, self.test_local_share, None
)
copytree(
Path(self.test_user_share, "sniper_platform_0.20240125.75305"),
Path(
self.test_local_share, "sniper_platform_0.20240125.75305"
),
dirs_exist_ok=True,
symlinks=True,
)
copy(
Path(self.test_user_share, "run"),
Path(self.test_local_share, "run"),
)
copy(
Path(self.test_user_share, "run-in-sniper"),
Path(self.test_local_share, "run-in-sniper"),
)
copy(
Path(self.test_user_share, "umu"),
Path(self.test_local_share, "umu"),
)

os.environ |= self.env

# Build
Expand Down

0 comments on commit 72499f3

Please sign in to comment.