1313from subprocess import check_call , check_output # nosec
1414from typing import cast
1515
16+ from packaging .version import Version
1617from schema_salad .sourceline import SourceLine
1718from schema_salad .utils import json_dumps
1819from spython .main import Client
3334# This is a list containing major and minor versions as integer.
3435# (The number of minor version digits can vary among different distributions,
3536# therefore we need a list here.)
36- _SINGULARITY_VERSION : list [ int ] | None = None
37+ _SINGULARITY_VERSION : Version | None = None
3738# Cached flavor / distribution of singularity
3839# Can be singularity, singularity-ce or apptainer
3940_SINGULARITY_FLAVOR : str = ""
4344_IMAGES_LOCK = threading .Lock ()
4445
4546
46- def get_version () -> tuple [list [ int ] , str ]:
47+ def get_version () -> tuple [Version , str ]:
4748 """
4849 Parse the output of 'singularity --version' to determine the flavor and version.
4950
5051 Both pieces of information will be cached.
5152
5253 :returns: A tuple containing:
53- - A tuple with major and minor version numbers as integer .
54+ - A parsed Version object .
5455 - A string with the name of the singularity flavor.
5556 """
5657 global _SINGULARITY_VERSION # pylint: disable=global-statement
@@ -63,7 +64,7 @@ def get_version() -> tuple[list[int], str]:
6364 raise RuntimeError ("Output of 'singularity --version' not recognized." )
6465
6566 version_string = version_match .group (2 )
66- _SINGULARITY_VERSION = [ int ( i ) for i in version_string . split ( "." )]
67+ _SINGULARITY_VERSION = Version ( version_string )
6768 _SINGULARITY_FLAVOR = version_match .group (1 )
6869
6970 _logger .debug (f"Singularity version: { version_string } " " ({_SINGULARITY_FLAVOR}." )
@@ -77,18 +78,18 @@ def is_apptainer_1_or_newer() -> bool:
7778 Apptainer v1.0.0 is compatible with SingularityCE 3.9.5.
7879 See: https://github.com/apptainer/apptainer/releases
7980 """
80- v = get_version ()
81- if v [ 1 ] != "apptainer" :
81+ version , flavor = get_version ()
82+ if flavor != "apptainer" :
8283 return False
83- return v [ 0 ][ 0 ] >= 1
84+ return version >= Version ( "1" )
8485
8586
8687def is_apptainer_1_1_or_newer () -> bool :
8788 """Check if apptainer singularity distribution is version 1.1 or higher."""
88- v = get_version ()
89- if v [ 1 ] != "apptainer" :
89+ version , flavor = get_version ()
90+ if flavor != "apptainer" :
9091 return False
91- return v [ 0 ][ 0 ] >= 2 or ( v [ 0 ][ 0 ] >= 1 and v [ 0 ][ 1 ] >= 1 )
92+ return version >= Version ( "1.1" )
9293
9394
9495def is_version_2_6 () -> bool :
@@ -97,48 +98,58 @@ def is_version_2_6() -> bool:
9798
9899 Also returns False if the flavor is not singularity or singularity-ce.
99100 """
100- v = get_version ()
101- if v [ 1 ] != "singularity" and v [ 1 ] != "singularity-ce" :
101+ version , flavor = get_version ()
102+ if flavor not in ( "singularity" , "singularity-ce" ) :
102103 return False
103- return v [ 0 ][ 0 ] == 2 and v [ 0 ][ 1 ] == 6
104+ return version >= Version ( "2.6" ) and version < Version ( "2.7" )
104105
105106
106107def is_version_3_or_newer () -> bool :
107108 """Check if this version is singularity version 3 or newer or equivalent."""
108109 if is_apptainer_1_or_newer ():
109110 return True # this is equivalent to singularity-ce > 3.9.5
110- v = get_version ()
111- return v [0 ][0 ] >= 3
111+ version , flavor = get_version ()
112+ if flavor == "apptainer" :
113+ return False
114+ return version >= Version ("3" )
112115
113116
114117def is_version_3_1_or_newer () -> bool :
115118 """Check if this version is singularity version 3.1 or newer or equivalent."""
116119 if is_apptainer_1_or_newer ():
117120 return True # this is equivalent to singularity-ce > 3.9.5
118- v = get_version ()
119- return v [0 ][0 ] >= 4 or (v [0 ][0 ] == 3 and v [0 ][1 ] >= 1 )
121+ version , flavor = get_version ()
122+ if flavor == "apptainer" :
123+ return False
124+ return version >= Version ("3.1" )
120125
121126
122127def is_version_3_4_or_newer () -> bool :
123128 """Detect if Singularity v3.4+ is available."""
124129 if is_apptainer_1_or_newer ():
125130 return True # this is equivalent to singularity-ce > 3.9.5
126- v = get_version ()
127- return v [0 ][0 ] >= 4 or (v [0 ][0 ] == 3 and v [0 ][1 ] >= 4 )
131+ version , flavor = get_version ()
132+ if flavor == "apptainer" :
133+ return False
134+ return version >= Version ("3.4" )
128135
129136
130137def is_version_3_9_or_newer () -> bool :
131138 """Detect if Singularity v3.9+ is available."""
132139 if is_apptainer_1_or_newer ():
133140 return True # this is equivalent to singularity-ce > 3.9.5
134- v = get_version ()
135- return v [0 ][0 ] >= 4 or (v [0 ][0 ] == 3 and v [0 ][1 ] >= 9 )
141+ version , flavor = get_version ()
142+ if flavor == "apptainer" :
143+ return False
144+ return version >= Version ("3.9" )
136145
137146
138147def is_version_3_10_or_newer () -> bool :
139148 """Detect if Singularity v3.10+ is available."""
140- v = get_version ()
141- return v [0 ][0 ] >= 4 or (v [0 ][0 ] == 3 and v [0 ][1 ] >= 10 )
149+ version , flavor = get_version ()
150+ if flavor not in ("singularity" , "singularity-ce" ):
151+ return False
152+ return version >= Version ("3.10" )
142153
143154
144155def _normalize_image_id (string : str ) -> str :
0 commit comments