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

Eliminate use of distutils.version.StrictVersion, use LooseVersion instead #4476

Merged
merged 6 commits into from
Mar 13, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 0 additions & 10 deletions easybuild/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,4 @@
__path__ = __import__('pkgutil').extend_path(__path__, __name__)


import distutils.version
import warnings
from easybuild.tools.loose_version import LooseVersion # noqa(F401)


class StrictVersion(distutils.version.StrictVersion):
"""Temporary wrapper over distuitls StrictVersion that silences the deprecation warning"""
def __init__(self, *args, **kwargs):
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=DeprecationWarning)
distutils.version.StrictVersion.__init__(self, *args, **kwargs)
16 changes: 16 additions & 0 deletions easybuild/tools/loose_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ def version(self):
"""Readonly access to the parsed version (list or None)"""
return self._version

def is_prerelease(self, other, markers):
"""Check if this is a prerelease of other

Markers is a list of strings that denote a prerelease
"""
if isinstance(other, str):
vstring = other
else:
vstring = other._vstring
if self._vstring.startswith(vstring):
prerelease = self._vstring[len(vstring):]
for marker in markers:
if prerelease.startswith(marker):
return True
return False

def __str__(self):
return self._vstring

Expand Down
27 changes: 10 additions & 17 deletions easybuild/tools/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
import shlex

from easybuild.base import fancylogger
from easybuild.tools import StrictVersion
from easybuild.tools import LooseVersion
from easybuild.tools.build_log import EasyBuildError, print_warning
from easybuild.tools.config import ERROR, IGNORE, PURGE, UNLOAD, UNSET
from easybuild.tools.config import EBROOT_ENV_VAR_ACTIONS, LOADED_MODULES_ACTIONS
Expand Down Expand Up @@ -144,11 +144,11 @@ class ModulesTool(object):
COMMAND_SHELL = None
# option to determine the version
VERSION_OPTION = '--version'
# minimal required version (StrictVersion; suffix rc replaced with b (and treated as beta by StrictVersion))
# minimal required version (cannot include -beta or rc)
REQ_VERSION = None
# deprecated version limit (support for versions below this version is deprecated)
DEPR_VERSION = None
# maximum version allowed (StrictVersion; suffix rc replaced with b (and treated as beta by StrictVersion))
# maximum version allowed (cannot include -beta or rc)
MAX_VERSION = None
# the regexp, should have a "version" group (multiline search)
VERSION_REGEXP = None
Expand Down Expand Up @@ -239,14 +239,6 @@ def set_and_check_version(self):
if res:
self.version = res.group('version')
self.log.info("Found %s version %s", self.NAME, self.version)

# make sure version is a valid StrictVersion (e.g., 5.7.3.1 is invalid),
# and replace 'rc' by 'b', to make StrictVersion treat it as a beta-release
self.version = self.version.replace('rc', 'b').replace('-beta', 'b1')
if len(self.version.split('.')) > 3:
self.version = '.'.join(self.version.split('.')[:3])

self.log.info("Converted actual version to '%s'" % self.version)
else:
raise EasyBuildError("Failed to determine %s version from option '%s' output: %s",
self.NAME, self.VERSION_OPTION, txt)
Expand All @@ -259,24 +251,25 @@ def set_and_check_version(self):
elif build_option('modules_tool_version_check'):
self.log.debug("Checking whether %s version %s meets requirements", self.NAME, self.version)

version = LooseVersion(self.version)
if self.REQ_VERSION is not None:
self.log.debug("Required minimum %s version defined: %s", self.NAME, self.REQ_VERSION)
if StrictVersion(self.version) < StrictVersion(self.REQ_VERSION):
if version < self.REQ_VERSION or version.is_prerelease(self.REQ_VERSION, ['rc', '-beta']):
raise EasyBuildError("EasyBuild requires %s >= v%s, found v%s",
self.NAME, self.REQ_VERSION, self.version)
else:
self.log.debug('%s version %s matches requirement >= %s', self.NAME, self.version, self.REQ_VERSION)

if self.DEPR_VERSION is not None:
self.log.debug("Deprecated %s version limit defined: %s", self.NAME, self.DEPR_VERSION)
if StrictVersion(self.version) < StrictVersion(self.DEPR_VERSION):
if version < self.DEPR_VERSION or version.is_prerelease(self.DEPR_VERSION, ['rc', '-beta']):
depr_msg = "Support for %s version < %s is deprecated, " % (self.NAME, self.DEPR_VERSION)
depr_msg += "found version %s" % self.version
self.log.deprecated(depr_msg, '6.0')

if self.MAX_VERSION is not None:
self.log.debug("Maximum allowed %s version defined: %s", self.NAME, self.MAX_VERSION)
if StrictVersion(self.version) > StrictVersion(self.MAX_VERSION):
if self.version > self.MAX_VERSION and not version.is_prerelease(self.MAX_VERSION, ['rc', '-beta']):
raise EasyBuildError("EasyBuild requires %s <= v%s, found v%s",
self.NAME, self.MAX_VERSION, self.version)
else:
Expand Down Expand Up @@ -1390,7 +1383,7 @@ def available(self, mod_name=None, extra_args=None):
if extra_args is None:
extra_args = []
# make hidden modules visible (requires Environment Modules 4.6.0)
if StrictVersion(self.version) >= StrictVersion('4.6.0'):
if LooseVersion(self.version) >= LooseVersion('4.6.0'):
extra_args.append(self.SHOW_HIDDEN_OPTION)

return super(EnvironmentModules, self).available(mod_name=mod_name, extra_args=extra_args)
Expand Down Expand Up @@ -1440,11 +1433,11 @@ def __init__(self, *args, **kwargs):
setvar('LMOD_EXTENDED_DEFAULT', 'no', verbose=False)

super(Lmod, self).__init__(*args, **kwargs)
version = StrictVersion(self.version)
version = LooseVersion(self.version)

self.supports_depends_on = True
# See https://lmod.readthedocs.io/en/latest/125_personal_spider_cache.html
if version >= '8.7.12':
if version >= LooseVersion('8.7.12'):
self.USER_CACHE_DIR = os.path.join(os.path.expanduser('~'), '.cache', 'lmod')
else:
self.USER_CACHE_DIR = os.path.join(os.path.expanduser('~'), '.lmod.d', '.cache')
Expand Down
14 changes: 8 additions & 6 deletions test/framework/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
import easybuild.tools.modules as mod
from easybuild.framework.easyblock import EasyBlock
from easybuild.framework.easyconfig.easyconfig import EasyConfig
from easybuild.tools import StrictVersion
from easybuild.tools import LooseVersion
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.environment import modify_env
from easybuild.tools.filetools import adjust_permissions, copy_file, copy_dir, mkdir
Expand Down Expand Up @@ -226,15 +226,16 @@ def test_avail(self):

# all test modules are accounted for
ms = self.modtool.available()
version = LooseVersion(self.modtool.version)

if isinstance(self.modtool, Lmod) and StrictVersion(self.modtool.version) >= StrictVersion('5.7.5'):
if isinstance(self.modtool, Lmod) and version >= '5.7.5' and not version.is_prerelease('5.7.5', ['rc']):
# with recent versions of Lmod, also the hidden modules are included in the output of 'avail'
self.assertEqual(len(ms), TEST_MODULES_COUNT + 3)
self.assertIn('bzip2/.1.0.6', ms)
self.assertIn('toy/.0.0-deps', ms)
self.assertIn('OpenMPI/.2.1.2-GCC-6.4.0-2.28', ms)
elif (isinstance(self.modtool, EnvironmentModules)
and StrictVersion(self.modtool.version) >= StrictVersion('4.6.0')):
and version >= '4.6.0' and not version.is_prerelease('4.6.0', ['-beta'])):
# bzip2/.1.0.6 is not there, since that's a module file in Lua syntax
self.assertEqual(len(ms), TEST_MODULES_COUNT + 2)
self.assertIn('toy/.0.0-deps', ms)
Expand Down Expand Up @@ -314,7 +315,8 @@ def test_exist(self):

avail_mods = self.modtool.available()
self.assertIn('Java/1.8.0_181', avail_mods)
if isinstance(self.modtool, Lmod) and StrictVersion(self.modtool.version) >= StrictVersion('7.0'):
version = LooseVersion(self.modtool.version)
if isinstance(self.modtool, Lmod) and version >= '7.0' and not version.is_prerelease('7.0', ['rc']):
self.assertIn('Java/1.8', avail_mods)
self.assertIn('Java/site_default', avail_mods)
self.assertIn('JavaAlias', avail_mods)
Expand Down Expand Up @@ -374,7 +376,7 @@ def test_exist(self):
self.assertEqual(self.modtool.exist(['Core/Java/1.8', 'Core/Java/site_default']), [True, True])

# also check with .modulerc.lua for Lmod 7.8 or newer
if isinstance(self.modtool, Lmod) and StrictVersion(self.modtool.version) >= StrictVersion('7.8'):
if isinstance(self.modtool, Lmod) and version >= '7.8' and not version.is_prerelease('7.8', ['rc']):
shutil.move(os.path.join(self.test_prefix, 'Core', 'Java'), java_mod_dir)
reset_module_caches()

Expand Down Expand Up @@ -406,7 +408,7 @@ def test_exist(self):
self.assertEqual(self.modtool.exist(['Core/Java/site_default']), [True])

# Test alias in home directory .modulerc
if isinstance(self.modtool, Lmod) and StrictVersion(self.modtool.version) >= StrictVersion('7.0'):
if isinstance(self.modtool, Lmod) and version >= '7.0' and not version.is_prerelease('7.0', ['rc']):
# Required or temporary HOME would be in MODULEPATH already
self.init_testmods()
# Sanity check: Module aliases don't exist yet
Expand Down
6 changes: 3 additions & 3 deletions test/framework/modulestool.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from unittest import TextTestRunner

from easybuild.base import fancylogger
from easybuild.tools import modules, StrictVersion
from easybuild.tools import modules, LooseVersion
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.filetools import read_file, which, write_file
from easybuild.tools.modules import EnvironmentModules, Lmod
Expand Down Expand Up @@ -76,7 +76,7 @@ def test_mock(self):
mmt = MockModulesTool(mod_paths=[], testing=True)

# the version of the MMT is the commandline option
self.assertEqual(mmt.version, StrictVersion(MockModulesTool.VERSION_OPTION))
self.assertEqual(mmt.version, LooseVersion(MockModulesTool.VERSION_OPTION))

cmd_abspath = which(MockModulesTool.COMMAND)

Expand All @@ -100,7 +100,7 @@ def test_environment_command(self):
bmmt = BrokenMockModulesTool(mod_paths=[], testing=True)
cmd_abspath = which(MockModulesTool.COMMAND)

self.assertEqual(bmmt.version, StrictVersion(MockModulesTool.VERSION_OPTION))
self.assertEqual(bmmt.version, LooseVersion(MockModulesTool.VERSION_OPTION))
self.assertEqual(bmmt.cmd, cmd_abspath)

# clean it up
Expand Down
4 changes: 4 additions & 0 deletions test/framework/utilities_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ def test_LooseVersion(self):
# Careful here: 1.0 > 1 !!!
self.assertGreater(LooseVersion('1.0'), LooseVersion('1'))
self.assertLess(LooseVersion('1'), LooseVersion('1.0'))
# checking prereleases
self.assertGreater(LooseVersion('4.0.0-beta'), LooseVersion('4.0.0'))
self.assertEqual(LooseVersion('4.0.0-beta').is_prerelease('4.0.0', ['-beta']), True)
self.assertEqual(LooseVersion('4.0.0-beta').is_prerelease('4.0.0', ['rc']), False)

# The following test is taken from Python distutils tests
# licensed under the Python Software Foundation License Version 2
Expand Down