Skip to content

Commit a42de3b

Browse files
committed
singularity/apptainer: simplify version checks & strengthen tests
Previously they were at risk of leaking the changes to other tests
1 parent 28f4bf5 commit a42de3b

File tree

3 files changed

+150
-146
lines changed

3 files changed

+150
-146
lines changed

cwltool/singularity.py

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from subprocess import check_call, check_output # nosec
1414
from typing import cast
1515

16+
from packaging.version import Version
1617
from schema_salad.sourceline import SourceLine
1718
from schema_salad.utils import json_dumps
1819
from spython.main import Client
@@ -33,7 +34,7 @@
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 = ""
@@ -43,14 +44,14 @@
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

8687
def 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

9495
def 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

106107
def 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

114117
def 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

122127
def 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

130137
def 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

138147
def 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

144155
def _normalize_image_id(string: str) -> str:

tests/test_environment.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,6 @@ def HOME(v: str, env: Env) -> bool:
124124
class Singularity(CheckHolder):
125125
"""Run in a Singularity container."""
126126

127-
@staticmethod
128-
def get_sing_version() -> Version:
129-
return Version(".".join(map(str, get_version()[0])))
130-
131127
@staticmethod
132128
def checks(tmp_prefix: str) -> EnvChecks:
133129
"""Create checks."""
@@ -145,7 +141,7 @@ def PWD(v: str, env: Env) -> bool:
145141
}
146142

147143
# Singularity variables appear to be in flux somewhat.
148-
version = Singularity.get_sing_version()
144+
version, _ = get_version()
149145
assert version >= Version("3"), "Tests only work for Singularity 3+"
150146
sing_vars: EnvChecks = {
151147
"SINGULARITY_CONTAINER": None,
@@ -323,10 +319,7 @@ def test_preserve_all(
323319
except KeyError as kerr:
324320
if vname not in os.environ:
325321
if crt_params_ == Singularity:
326-
print(
327-
f"Raw Singularity version: {get_version()}, "
328-
"processed version: {Singularity.get_sing_version()}."
329-
)
322+
print(f"Singularity version: {get_version()}.")
330323
raise kerr
331324
assert val == os.environ[vname]
332325
except AssertionError:

0 commit comments

Comments
 (0)