mirror of
https://github.com/lutris/lutris
synced 2024-09-15 22:09:55 +00:00
First cut at version compatibility restrictions
This code looks for a '.lutris_compat.json' file at the root of a component's version's directory. This contains, in JSON, the minimum Lutris version required. If Lutris finds this to be too late, it will try earlier versions. The code is messy and this could download a lot of versions, since it tries them one at a time. But it's a start.
This commit is contained in:
parent
567c9a99b5
commit
14ae02536e
|
@ -16,7 +16,7 @@ from lutris.util.display import DISPLAY_MANAGER, get_default_dpi
|
||||||
from lutris.util.graphics import vkquery
|
from lutris.util.graphics import vkquery
|
||||||
from lutris.util.log import logger
|
from lutris.util.log import logger
|
||||||
from lutris.util.steam.config import get_steam_dir
|
from lutris.util.steam.config import get_steam_dir
|
||||||
from lutris.util.strings import parse_version, split_arguments
|
from lutris.util.strings import split_arguments
|
||||||
from lutris.util.wine.d3d_extras import D3DExtrasManager
|
from lutris.util.wine.d3d_extras import D3DExtrasManager
|
||||||
from lutris.util.wine.dgvoodoo2 import dgvoodoo2Manager
|
from lutris.util.wine.dgvoodoo2 import dgvoodoo2Manager
|
||||||
from lutris.util.wine.dxvk import REQUIRED_VULKAN_API_VERSION, DXVKManager
|
from lutris.util.wine.dxvk import REQUIRED_VULKAN_API_VERSION, DXVKManager
|
||||||
|
@ -28,7 +28,7 @@ from lutris.util.wine.wine import (
|
||||||
POL_PATH, WINE_DIR, WINE_PATHS, detect_arch, display_vulkan_error, esync_display_limit_warning,
|
POL_PATH, WINE_DIR, WINE_PATHS, detect_arch, display_vulkan_error, esync_display_limit_warning,
|
||||||
esync_display_version_warning, fsync_display_support_warning, fsync_display_version_warning, get_default_version,
|
esync_display_version_warning, fsync_display_support_warning, fsync_display_version_warning, get_default_version,
|
||||||
get_overrides_env, get_proton_paths, get_real_executable, get_wine_version, get_wine_versions, is_esync_limit_set,
|
get_overrides_env, get_proton_paths, get_real_executable, get_wine_version, get_wine_versions, is_esync_limit_set,
|
||||||
is_fsync_supported, is_gstreamer_build, is_version_esync, is_version_fsync
|
is_fsync_supported, is_gstreamer_build, is_version_esync, is_version_fsync, parse_wine_version
|
||||||
)
|
)
|
||||||
|
|
||||||
DEFAULT_WINE_PREFIX = "~/.wine"
|
DEFAULT_WINE_PREFIX = "~/.wine"
|
||||||
|
@ -729,9 +729,9 @@ class wine(Runner):
|
||||||
|
|
||||||
wine_versions = get_wine_versions()
|
wine_versions = get_wine_versions()
|
||||||
if min_version:
|
if min_version:
|
||||||
min_version_list, _, _ = parse_version(min_version)
|
min_version_list, _, _ = parse_wine_version(min_version)
|
||||||
for wine_version in wine_versions:
|
for wine_version in wine_versions:
|
||||||
version_list, _, _ = parse_version(wine_version)
|
version_list, _, _ = parse_wine_version(wine_version)
|
||||||
if version_list > min_version_list:
|
if version_list > min_version_list:
|
||||||
return True
|
return True
|
||||||
logger.warning("Wine %s or higher not found", min_version)
|
logger.warning("Wine %s or higher not found", min_version)
|
||||||
|
|
|
@ -69,8 +69,6 @@ def parse_version(version):
|
||||||
Returns:
|
Returns:
|
||||||
tuple: (version number as list, prefix, suffix)
|
tuple: (version number as list, prefix, suffix)
|
||||||
"""
|
"""
|
||||||
version = version.replace("Proton7-", "Proton-7.")
|
|
||||||
version = version.replace("Proton8-", "Proton-8.")
|
|
||||||
version_match = re.search(r"(\d[\d\.]+\d)", version)
|
version_match = re.search(r"(\d[\d\.]+\d)", version)
|
||||||
if not version_match:
|
if not version_match:
|
||||||
return [], "", ""
|
return [], "", ""
|
||||||
|
@ -80,19 +78,6 @@ def parse_version(version):
|
||||||
return [int(p) for p in version_number.split(".")], suffix, prefix
|
return [int(p) for p in version_number.split(".")], suffix, prefix
|
||||||
|
|
||||||
|
|
||||||
def version_sort(versions, reverse=False):
|
|
||||||
|
|
||||||
def version_key(version):
|
|
||||||
version_list, prefix, suffix = parse_version(version)
|
|
||||||
# Normalize the length of sub-versions
|
|
||||||
sort_key = version_list + [0] * (10 - len(version_list))
|
|
||||||
sort_key.append(prefix)
|
|
||||||
sort_key.append(suffix)
|
|
||||||
return sort_key
|
|
||||||
|
|
||||||
return sorted(versions, key=version_key, reverse=reverse)
|
|
||||||
|
|
||||||
|
|
||||||
def unpack_dependencies(string):
|
def unpack_dependencies(string):
|
||||||
"""Parse a string to allow for complex dependencies
|
"""Parse a string to allow for complex dependencies
|
||||||
Works in a similar fashion as Debian dependencies, separate dependencies
|
Works in a similar fashion as Debian dependencies, separate dependencies
|
||||||
|
|
|
@ -4,10 +4,12 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
from lutris import settings
|
||||||
from lutris.util import system
|
from lutris.util import system
|
||||||
from lutris.util.extract import extract_archive
|
from lutris.util.extract import extract_archive
|
||||||
from lutris.util.http import download_file
|
from lutris.util.http import download_file
|
||||||
from lutris.util.log import logger
|
from lutris.util.log import logger
|
||||||
|
from lutris.util.strings import parse_version
|
||||||
from lutris.util.wine.prefix import WinePrefixManager
|
from lutris.util.wine.prefix import WinePrefixManager
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,10 +52,35 @@ class DLLManager:
|
||||||
recommended_versions = [v for v in versions if self.is_recommended_version(v)]
|
recommended_versions = [v for v in versions if self.is_recommended_version(v)]
|
||||||
return recommended_versions[0] if recommended_versions else versions[0]
|
return recommended_versions[0] if recommended_versions else versions[0]
|
||||||
|
|
||||||
|
def get_recommended_versions(self):
|
||||||
|
"""Returns a list of version numbers that are recommended, based on the versions JSON file;
|
||||||
|
merely having a directory does not count, but we do return only recommended versions. This
|
||||||
|
means that a version can be recommended until it is downloaded, and then if it has a
|
||||||
|
'.lutris_compat.json' file it may cease to be recommended. The DLL download code retries
|
||||||
|
with an earlier version if this happens.
|
||||||
|
|
||||||
|
This list is in the usual highest-version-first order, and we try the downloads in that order.
|
||||||
|
"""
|
||||||
|
versions = self.load_versions()
|
||||||
|
return [v for v in versions if self.is_recommended_version(v)]
|
||||||
|
|
||||||
def is_recommended_version(self, version):
|
def is_recommended_version(self, version):
|
||||||
"""True if the version given should be usable as the default; false if it
|
"""True if the version given should be usable as the default; false if it
|
||||||
should not be the default, but may be selected by the user. If only
|
should not be the default, but may be selected by the user. If only
|
||||||
non-recommended versions exist, we'll still default to one of them, however."""
|
non-recommended versions exist, we'll still default to one of them, however.
|
||||||
|
|
||||||
|
By default, we check for a '.lutris_compat.json' file; if this Lutris is
|
||||||
|
too old, we'll reject the version."""
|
||||||
|
path = os.path.join(self.base_dir, version, ".lutris_compat.json")
|
||||||
|
if os.path.isfile(path):
|
||||||
|
with open(path, "r", encoding='utf-8') as json_file:
|
||||||
|
js = json.load(json_file)
|
||||||
|
if "min_lutris_version" in js:
|
||||||
|
min_lutris_version = parse_version(js["min_lutris_version"])
|
||||||
|
current_lutris_version = parse_version(settings.VERSION)
|
||||||
|
if current_lutris_version < min_lutris_version:
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -271,8 +298,19 @@ class DLLManager:
|
||||||
def upgrade(self):
|
def upgrade(self):
|
||||||
self.fetch_versions()
|
self.fetch_versions()
|
||||||
if not self.is_available():
|
if not self.is_available():
|
||||||
if self.version:
|
versions = self.get_recommended_versions()
|
||||||
logger.info("Downloading %s %s...", self.component, self.version)
|
|
||||||
|
while versions:
|
||||||
|
# Try to download the latest recommended version.
|
||||||
|
version = versions[0]
|
||||||
|
logger.info("Downloading %s %s...", self.component, version)
|
||||||
self.download()
|
self.download()
|
||||||
else:
|
|
||||||
logger.warning("Unable to download %s because version information was not available.", self.component)
|
# If the version is still recommended, we're done,
|
||||||
|
# if not we'll try again with the next one.
|
||||||
|
new_versions = self.get_recommended_versions()
|
||||||
|
if version in new_versions:
|
||||||
|
return
|
||||||
|
versions = new_versions
|
||||||
|
|
||||||
|
logger.warning("Unable to download %s because version information was not available.", self.component)
|
||||||
|
|
|
@ -10,7 +10,7 @@ from lutris.gui.dialogs import DontShowAgainDialog, ErrorDialog
|
||||||
from lutris.runners.steam import steam
|
from lutris.runners.steam import steam
|
||||||
from lutris.util import linux, system
|
from lutris.util import linux, system
|
||||||
from lutris.util.log import logger
|
from lutris.util.log import logger
|
||||||
from lutris.util.strings import version_sort
|
from lutris.util.strings import parse_version
|
||||||
from lutris.util.wine import fsync
|
from lutris.util.wine import fsync
|
||||||
|
|
||||||
WINE_DIR = os.path.join(settings.RUNNER_DIR, "wine")
|
WINE_DIR = os.path.join(settings.RUNNER_DIR, "wine")
|
||||||
|
@ -236,6 +236,27 @@ def get_wine_version_exe(version):
|
||||||
return os.path.join(WINE_DIR, "{}/bin/wine".format(version))
|
return os.path.join(WINE_DIR, "{}/bin/wine".format(version))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_wine_version(version):
|
||||||
|
"""This is a specialized parse_version() that adjusts some odd
|
||||||
|
Wine versions for correct parsing."""
|
||||||
|
version = version.replace("Proton7-", "Proton-7.")
|
||||||
|
version = version.replace("Proton8-", "Proton-8.")
|
||||||
|
return parse_version(version)
|
||||||
|
|
||||||
|
|
||||||
|
def version_sort(versions, reverse=False):
|
||||||
|
|
||||||
|
def version_key(version):
|
||||||
|
version_list, prefix, suffix = parse_wine_version(version)
|
||||||
|
# Normalize the length of sub-versions
|
||||||
|
sort_key = version_list + [0] * (10 - len(version_list))
|
||||||
|
sort_key.append(prefix)
|
||||||
|
sort_key.append(suffix)
|
||||||
|
return sort_key
|
||||||
|
|
||||||
|
return sorted(versions, key=version_key, reverse=reverse)
|
||||||
|
|
||||||
|
|
||||||
def is_version_installed(version):
|
def is_version_installed(version):
|
||||||
return os.path.isfile(get_wine_version_exe(version))
|
return os.path.isfile(get_wine_version_exe(version))
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import os
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from lutris.util.wine import wine
|
||||||
from lutris.util import fileio, strings, system
|
from lutris.util import fileio, strings, system
|
||||||
from lutris.util.steam import vdfutils
|
from lutris.util.steam import vdfutils
|
||||||
|
|
||||||
|
@ -96,11 +97,11 @@ class TestStringUtils(TestCase):
|
||||||
|
|
||||||
class TestVersionSort(TestCase):
|
class TestVersionSort(TestCase):
|
||||||
def test_parse_version(self):
|
def test_parse_version(self):
|
||||||
self.assertEqual(strings.parse_version("3.6-staging"), ([3, 6], '-staging', ''))
|
self.assertEqual(wine.parse_wine_version("3.6-staging"), ([3, 6], '-staging', ''))
|
||||||
|
|
||||||
def test_versions_are_correctly_sorted(self):
|
def test_versions_are_correctly_sorted(self):
|
||||||
versions = ['1.8', '1.7.4', '1.9.1', '1.9.10', '1.9.4']
|
versions = ['1.8', '1.7.4', '1.9.1', '1.9.10', '1.9.4']
|
||||||
versions = strings.version_sort(versions)
|
versions = wine.version_sort(versions)
|
||||||
self.assertEqual(versions[0], '1.7.4')
|
self.assertEqual(versions[0], '1.7.4')
|
||||||
self.assertEqual(versions[1], '1.8')
|
self.assertEqual(versions[1], '1.8')
|
||||||
self.assertEqual(versions[2], '1.9.1')
|
self.assertEqual(versions[2], '1.9.1')
|
||||||
|
@ -114,7 +115,7 @@ class TestVersionSort(TestCase):
|
||||||
'1.9.10-staging', '1.9.10',
|
'1.9.10-staging', '1.9.10',
|
||||||
'1.9.4', 'staging-1.9.4'
|
'1.9.4', 'staging-1.9.4'
|
||||||
]
|
]
|
||||||
versions = strings.version_sort(versions)
|
versions = wine.version_sort(versions)
|
||||||
self.assertEqual(versions[0], '1.7.4')
|
self.assertEqual(versions[0], '1.7.4')
|
||||||
self.assertEqual(versions[1], '1.8')
|
self.assertEqual(versions[1], '1.8')
|
||||||
self.assertEqual(versions[2], '1.8-staging')
|
self.assertEqual(versions[2], '1.8-staging')
|
||||||
|
@ -126,7 +127,7 @@ class TestVersionSort(TestCase):
|
||||||
|
|
||||||
def test_versions_can_be_reversed(self):
|
def test_versions_can_be_reversed(self):
|
||||||
versions = ['1.9', '1.6', '1.7', '1.8']
|
versions = ['1.9', '1.6', '1.7', '1.8']
|
||||||
versions = strings.version_sort(versions, reverse=True)
|
versions = wine.version_sort(versions, reverse=True)
|
||||||
self.assertEqual(versions[0], '1.9')
|
self.assertEqual(versions[0], '1.9')
|
||||||
self.assertEqual(versions[3], '1.6')
|
self.assertEqual(versions[3], '1.6')
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue