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

Fix configuring Steam game drive and misc #104

Merged
merged 8 commits into from
May 17, 2024
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
5 changes: 4 additions & 1 deletion umu/ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ select = [
# Ensure explicit check= for subprocess.run to avoid silent failures
"PLW1510",
"UP",
"FURB"
"FURB",
# Enforce the encoding argument when opening files
# Encoding for text should be utf-8
"PLW1514"
]
ignore = [
# Format
Expand Down
37 changes: 0 additions & 37 deletions umu/umu_plugins.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from subprocess import Popen, TimeoutExpired, PIPE, STDOUT
from os import environ
from pathlib import Path
from typing import Any
from argparse import Namespace
Expand Down Expand Up @@ -105,42 +104,6 @@ def _check_env_toml(toml: dict[str, Any]) -> dict[str, Any]:
return toml


def enable_steam_game_drive(env: dict[str, str]) -> dict[str, str]:
"""Enable Steam Game Drive functionality.

Expects STEAM_COMPAT_INSTALL_PATH to be set
STEAM_RUNTIME_LIBRARY_PATH will not be set if the exe directory does not exist
"""
paths: set[str] = set()
root: Path = Path("/")

# Check for mount points going up toward the root
# NOTE: Subvolumes can be mount points
for path in Path(env["STEAM_COMPAT_INSTALL_PATH"]).parents:
if path.is_mount() and path != root:
if env["STEAM_COMPAT_LIBRARY_PATHS"]:
env["STEAM_COMPAT_LIBRARY_PATHS"] = (
env["STEAM_COMPAT_LIBRARY_PATHS"] + ":" + path.as_posix()
)
else:
env["STEAM_COMPAT_LIBRARY_PATHS"] = path.as_posix()
break

if environ.get("LD_LIBRARY_PATH"):
paths = {path for path in environ["LD_LIBRARY_PATH"].split(":")}

if env["STEAM_COMPAT_INSTALL_PATH"]:
paths.add(env["STEAM_COMPAT_INSTALL_PATH"])

# Hard code for now because these paths seem to be pretty standard
# This way we avoid shelling to ldconfig
paths.add("/usr/lib")
paths.add("/usr/lib32")
env["STEAM_RUNTIME_LIBRARY_PATH"] = ":".join(list(paths))

return env


def enable_zenity(command: str, opts: list[str], msg: str) -> int:
"""Execute the command and pipe the output to Zenity.

Expand Down
51 changes: 47 additions & 4 deletions umu/umu_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from concurrent.futures import ThreadPoolExecutor, Future
from socket import AF_INET, SOCK_DGRAM, socket
from pwd import getpwuid
from umu_plugins import set_env_toml
from ctypes.util import find_library
from umu_consts import (
PROTON_VERBS,
DEBUG_FORMAT,
Expand All @@ -23,10 +25,6 @@
FLATPAK_PATH,
FLATPAK_ID,
)
from umu_plugins import (
enable_steam_game_drive,
set_env_toml,
)


def parse_args() -> Namespace | tuple[str, list[str]]: # noqa: D103
Expand Down Expand Up @@ -233,6 +231,51 @@ def set_env(
return env


def enable_steam_game_drive(env: dict[str, str]) -> dict[str, str]:
"""Enable Steam Game Drive functionality.

Expects STEAM_COMPAT_INSTALL_PATH to be set
STEAM_RUNTIME_LIBRARY_PATH will not be set if the exe directory does not exist
"""
paths: set[str] = set()
root: Path = Path("/")
libc: str = find_library("c")

# Check for mount points going up toward the root
# NOTE: Subvolumes can be mount points
for path in Path(env["STEAM_COMPAT_INSTALL_PATH"]).parents:
if path.is_mount() and path != root:
if os.environ.get("STEAM_COMPAT_LIBRARY_PATHS"):
env["STEAM_COMPAT_LIBRARY_PATHS"] = (
os.environ["STEAM_COMPAT_LIBRARY_PATHS"] + ":" + path.as_posix()
)
else:
env["STEAM_COMPAT_LIBRARY_PATHS"] = path.as_posix()
break

if os.environ.get("LD_LIBRARY_PATH"):
paths = {path for path in os.environ["LD_LIBRARY_PATH"].split(":")}

if env["STEAM_COMPAT_INSTALL_PATH"]:
paths.add(env["STEAM_COMPAT_INSTALL_PATH"])

# Include all paths that are currently supported by the container
# runtime framework
# See https://gitlab.steamos.cloud/steamrt/steam-runtime-tools/-/blob/main/docs/distro-assumptions.md
for path in [
"/usr/lib64",
"/usr/lib32",
"/usr/lib",
"/usr/lib/x86_64-linux-gnu",
"/usr/lib/i386-linux-gnu",
]:
if not Path(path).is_symlink() and Path(path, libc).is_file():
paths.add(path)
env["STEAM_RUNTIME_LIBRARY_PATH"] = ":".join(list(paths))

return env


def build_command(
env: dict[str, str],
local: Path,
Expand Down
40 changes: 26 additions & 14 deletions umu/umu_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import os
import argparse
import re
import umu_plugins
import umu_dl_util
import tarfile
import umu_util
Expand Down Expand Up @@ -92,7 +91,9 @@ def setUp(self):
# Mock a valid configuration file at /usr/share/umu:
# tmp.BXk2NnvW2m/umu_version.json
Path(self.test_user_share, "umu_version.json").touch()
with Path(self.test_user_share, "umu_version.json").open(mode="w") as file:
with Path(self.test_user_share, "umu_version.json").open(
mode="w", encoding="utf-8"
) as file:
file.write(self.test_config)

# Mock the launcher files
Expand Down Expand Up @@ -667,7 +668,9 @@ def test_get_json_err(self):
Path(self.test_user_share, "umu_version.json").unlink(missing_ok=True)

Path(self.test_user_share, "umu_version.json").touch()
with Path(self.test_user_share, "umu_version.json").open(mode="w") as file:
with Path(self.test_user_share, "umu_version.json").open(
mode="w", encoding="utf-8"
) as file:
file.write(test_config)

# Test when "umu" doesn't exist
Expand All @@ -678,7 +681,9 @@ def test_get_json_err(self):
Path(self.test_user_share, "umu_version.json").unlink(missing_ok=True)

Path(self.test_user_share, "umu_version.json").touch()
with Path(self.test_user_share, "umu_version.json").open(mode="w") as file:
with Path(self.test_user_share, "umu_version.json").open(
mode="w", encoding="utf-8"
) as file:
file.write(test_config2)

with self.assertRaisesRegex(ValueError, "load"):
Expand Down Expand Up @@ -1141,7 +1146,7 @@ def test_game_drive_libpath_empty(self):
os.environ["LD_LIBRARY_PATH"] = paths

# Game drive
result_gamedrive = umu_plugins.enable_steam_game_drive(self.env)
result_gamedrive = umu_run.enable_steam_game_drive(self.env)

for key, val in self.env.items():
os.environ[key] = val
Expand Down Expand Up @@ -1226,7 +1231,7 @@ def test_game_drive_libpath(self):
os.environ["LD_LIBRARY_PATH"] = paths

# Game drive
result_gamedrive = umu_plugins.enable_steam_game_drive(self.env)
result_gamedrive = umu_run.enable_steam_game_drive(self.env)

for key, val in self.env.items():
os.environ[key] = val
Expand Down Expand Up @@ -1281,6 +1286,14 @@ def test_game_drive_empty(self):
"""
args = None
result_gamedrive = None
# Expected library paths for the container runtime framework
libpaths = {
"/usr/lib64",
"/usr/lib32",
"/usr/lib",
"/usr/lib/x86_64-linux-gnu",
"/usr/lib/i386-linux-gnu",
}
Path(self.test_file + "/proton").touch()

# Replicate main's execution and test up until enable_steam_game_drive
Expand All @@ -1305,7 +1318,7 @@ def test_game_drive_empty(self):
os.environ.pop("LD_LIBRARY_PATH")

# Game drive
result_gamedrive = umu_plugins.enable_steam_game_drive(self.env)
result_gamedrive = umu_run.enable_steam_game_drive(self.env)

# Ubuntu sources this variable and will be added once Game Drive is enabled
# Just test the case without it
Expand All @@ -1329,12 +1342,11 @@ def test_game_drive_empty(self):
"Expected two values in STEAM_RUNTIME_LIBRARY_PATH",
)

# We need to sort the elements because the values were originally in a set
str1, str2 = [*sorted(self.env["STEAM_RUNTIME_LIBRARY_PATH"].split(":"))]

# Check that there are no trailing colons or unexpected characters
self.assertEqual(str1, "/usr/lib", "Expected /usr/lib")
self.assertEqual(str2, "/usr/lib32", "Expected /usr/lib32")
# Check that there are no trailing colons, unexpected characters
# and is officially supported
str1, str2 = self.env["STEAM_RUNTIME_LIBRARY_PATH"].split(":")
self.assertTrue(str1 in libpaths, f"Expected a path in: {libpaths}")
self.assertTrue(str2 in libpaths, f"Expected a path in: {libpaths}")

# Both of these values should be empty still after calling
# enable_steam_game_drive
Expand Down Expand Up @@ -1373,7 +1385,7 @@ def test_build_command(self):
# Env
umu_run.set_env(self.env, result_args)
# Game drive
umu_plugins.enable_steam_game_drive(self.env)
umu_run.enable_steam_game_drive(self.env)

for key, val in self.env.items():
os.environ[key] = val
Expand Down
28 changes: 15 additions & 13 deletions umu/umu_test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ def setUp(self):
# Mock a valid configuration file at /usr/share/umu:
# tmp.BXk2NnvW2m/umu_version.json
Path(self.test_user_share, "umu_version.json").touch()
with Path(self.test_user_share, "umu_version.json").open(mode="w") as file:
with Path(self.test_user_share, "umu_version.json").open(
mode="w", encoding="utf-8"
) as file:
file.write(self.test_config)

# Mock the launcher files
Expand Down Expand Up @@ -192,7 +194,7 @@ def test_build_command_entry(self):
# Mock the proton file
Path(self.test_file, "proton").touch()

with Path(toml_path).open(mode="w") as file:
with Path(toml_path).open(mode="w", encoding="utf-8") as file:
file.write(toml_str)

with patch.object(
Expand All @@ -209,7 +211,7 @@ def test_build_command_entry(self):
# Env
umu_run.set_env(self.env, result)
# Game drive
umu_plugins.enable_steam_game_drive(self.env)
umu_run.enable_steam_game_drive(self.env)

# Mock setting up the runtime
# Don't copy _v2-entry-point
Expand Down Expand Up @@ -260,7 +262,7 @@ def test_build_command_proton(self):
test_command = []
Path(toml_path).touch()

with Path(toml_path).open(mode="w") as file:
with Path(toml_path).open(mode="w", encoding="utf-8") as file:
file.write(toml_str)

with patch.object(
Expand All @@ -277,7 +279,7 @@ def test_build_command_proton(self):
# Env
umu_run.set_env(self.env, result)
# Game drive
umu_plugins.enable_steam_game_drive(self.env)
umu_run.enable_steam_game_drive(self.env)

# Mock setting up the runtime
with (
Expand Down Expand Up @@ -331,7 +333,7 @@ def test_build_command_toml(self):
Path(self.test_file + "/proton").touch()
Path(toml_path).touch()

with Path(toml_path).open(mode="w") as file:
with Path(toml_path).open(mode="w", encoding="utf-8") as file:
file.write(toml_str)

with patch.object(
Expand All @@ -348,7 +350,7 @@ def test_build_command_toml(self):
# Env
umu_run.set_env(self.env, result)
# Game drive
umu_plugins.enable_steam_game_drive(self.env)
umu_run.enable_steam_game_drive(self.env)

# Mock setting up the runtime
with (
Expand Down Expand Up @@ -421,7 +423,7 @@ def test_set_env_toml_nofile(self):

Path(toml_path).touch()

with Path(toml_path).open(mode="w") as file:
with Path(toml_path).open(mode="w", encoding="utf-8") as file:
file.write(toml_str)

with patch.object(
Expand Down Expand Up @@ -457,7 +459,7 @@ def test_set_env_toml_err(self):

Path(toml_path).touch()

with Path(toml_path).open(mode="w") as file:
with Path(toml_path).open(mode="w", encoding="utf-8") as file:
file.write(toml_str)

with patch.object(
Expand Down Expand Up @@ -493,7 +495,7 @@ def test_set_env_toml_nodir(self):

Path(toml_path).touch()

with Path(toml_path).open(mode="w") as file:
with Path(toml_path).open(mode="w", encoding="utf-8") as file:
file.write(toml_str)

with patch.object(
Expand Down Expand Up @@ -529,7 +531,7 @@ def test_set_env_toml_tables(self):

Path(toml_path).touch()

with Path(toml_path).open(mode="w") as file:
with Path(toml_path).open(mode="w", encoding="utf-8") as file:
file.write(toml_str)

with patch.object(
Expand Down Expand Up @@ -648,7 +650,7 @@ def test_set_env_toml_opts(self):

Path(toml_path).touch()

with Path(toml_path).open(mode="w") as file:
with Path(toml_path).open(mode="w", encoding="utf-8") as file:
file.write(toml_str)

with patch.object(
Expand Down Expand Up @@ -718,7 +720,7 @@ def test_set_env_toml(self):

Path(toml_path).touch()

with Path(toml_path).open(mode="w") as file:
with Path(toml_path).open(mode="w", encoding="utf-8") as file:
file.write(toml_str)

with patch.object(
Expand Down