fm-weck 1.4.6__py3-none-any.whl → 1.4.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fm_weck/__init__.py +1 -1
- fm_weck/cli.py +19 -24
- fm_weck/config.py +35 -20
- fm_weck/engine.py +36 -26
- fm_weck/file_util.py +23 -0
- fm_weck/image_mgr.py +5 -4
- fm_weck/resources/__init__.py +14 -12
- fm_weck/resources/fm_tools/cadp.yml +1 -1
- fm_weck/resources/fm_tools/hybridtiger.yml +1 -1
- fm_weck/resources/fm_tools/ltsmin.yml +58 -0
- fm_weck/resources/fm_tools/sv-sanitizers.yml +3 -1
- fm_weck/run_result.py +5 -5
- fm_weck/runexec_mode.py +8 -2
- fm_weck/serve.py +99 -17
- fm_weck/tmp_file.py +61 -0
- {fm_weck-1.4.6.dist-info → fm_weck-1.4.8.dist-info}/METADATA +12 -6
- {fm_weck-1.4.6.dist-info → fm_weck-1.4.8.dist-info}/RECORD +19 -16
- {fm_weck-1.4.6.dist-info → fm_weck-1.4.8.dist-info}/WHEEL +0 -0
- {fm_weck-1.4.6.dist-info → fm_weck-1.4.8.dist-info}/entry_points.txt +0 -0
fm_weck/__init__.py
CHANGED
fm_weck/cli.py
CHANGED
|
@@ -13,23 +13,25 @@ from argparse import Namespace
|
|
|
13
13
|
from dataclasses import dataclass
|
|
14
14
|
from functools import cache
|
|
15
15
|
from pathlib import Path
|
|
16
|
-
from typing import Any, Callable, Optional, Tuple, Union
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple, Union
|
|
17
17
|
|
|
18
18
|
try:
|
|
19
19
|
from fm_tools.benchexec_helper import DataModel
|
|
20
20
|
except ImportError:
|
|
21
21
|
from enum import Enum
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
"""
|
|
25
|
-
Enum representing the data model of the tool.
|
|
26
|
-
"""
|
|
23
|
+
if not TYPE_CHECKING:
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
class DataModel(Enum):
|
|
26
|
+
"""
|
|
27
|
+
Enum representing the data model of the tool.
|
|
28
|
+
"""
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
LP64 = "LP64"
|
|
31
|
+
ILP32 = "ILP32"
|
|
32
|
+
|
|
33
|
+
def __str__(self):
|
|
34
|
+
return self.value
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
import contextlib
|
|
@@ -107,7 +109,7 @@ class ShellCompletion:
|
|
|
107
109
|
return selected_tools
|
|
108
110
|
|
|
109
111
|
|
|
110
|
-
def add_tool_arg(parser, nargs="?"):
|
|
112
|
+
def add_tool_arg(parser, nargs: str | None = "?"):
|
|
111
113
|
parser.add_argument(
|
|
112
114
|
"TOOL",
|
|
113
115
|
help="The tool to obtain the container from. Can be the form <tool>:<version>. "
|
|
@@ -196,7 +198,7 @@ def parse(raw_args: list[str]) -> Tuple[Callable[[], None], Namespace]:
|
|
|
196
198
|
),
|
|
197
199
|
required=False,
|
|
198
200
|
default=None,
|
|
199
|
-
).completer = ShellCompletion.properties_completer
|
|
201
|
+
).completer = ShellCompletion.properties_completer # type: ignore[assignment]
|
|
200
202
|
|
|
201
203
|
run.add_argument(
|
|
202
204
|
"-d",
|
|
@@ -313,7 +315,7 @@ def parse(raw_args: list[str]) -> Tuple[Callable[[], None], Namespace]:
|
|
|
313
315
|
help="The tool(s) for which to print the versions.",
|
|
314
316
|
type=ToolQualifier,
|
|
315
317
|
nargs="+",
|
|
316
|
-
).completer = ShellCompletion.versions_completer
|
|
318
|
+
).completer = ShellCompletion.versions_completer # type: ignore[assignment]
|
|
317
319
|
versions.set_defaults(main=main_versions)
|
|
318
320
|
|
|
319
321
|
with contextlib.suppress(ImportError):
|
|
@@ -368,7 +370,7 @@ def resolve_tool(tool: ToolQualifier) -> Path:
|
|
|
368
370
|
if (as_path := Path(tool_name)).exists() and as_path.is_file():
|
|
369
371
|
return as_path
|
|
370
372
|
|
|
371
|
-
return fm_tools_choice_map()[tool_name]
|
|
373
|
+
return fm_tools_choice_map()[str(tool_name)]
|
|
372
374
|
|
|
373
375
|
|
|
374
376
|
def resolve_property(prop_name: str) -> Path:
|
|
@@ -459,10 +461,7 @@ def main_manual(args: argparse.Namespace):
|
|
|
459
461
|
|
|
460
462
|
|
|
461
463
|
def main_install(args: argparse.Namespace):
|
|
462
|
-
from .serve import
|
|
463
|
-
|
|
464
|
-
if args.destination:
|
|
465
|
-
Config()._config["defaults"]["cache_location"] = args.destination.resolve()
|
|
464
|
+
from .serve import install_fm_tool
|
|
466
465
|
|
|
467
466
|
for tool in args.TOOL:
|
|
468
467
|
try:
|
|
@@ -471,11 +470,7 @@ def main_install(args: argparse.Namespace):
|
|
|
471
470
|
logger.error("Unknown tool %s. Skipping installation...", tool)
|
|
472
471
|
continue
|
|
473
472
|
|
|
474
|
-
|
|
475
|
-
fm_tool=fm_data,
|
|
476
|
-
version=tool.version,
|
|
477
|
-
configuration=Config(),
|
|
478
|
-
)
|
|
473
|
+
install_fm_tool(fm_tool=fm_data, version=tool.version, configuration=Config(), install_path=args.destination)
|
|
479
474
|
|
|
480
475
|
return 0
|
|
481
476
|
|
|
@@ -499,9 +494,9 @@ def main_shell(args: argparse.Namespace):
|
|
|
499
494
|
|
|
500
495
|
def main_clear_cache(args: argparse.Namespace):
|
|
501
496
|
if args.yes:
|
|
502
|
-
clear_cache(Config().get("defaults").get("cache_location"))
|
|
497
|
+
clear_cache(Config().get("defaults", {}).get("cache_location")) # type: ignore
|
|
503
498
|
else:
|
|
504
|
-
ask_and_clear(Config().get("defaults").get("cache_location"))
|
|
499
|
+
ask_and_clear(Config().get("defaults", {}).get("cache_location")) # type: ignore
|
|
505
500
|
return
|
|
506
501
|
|
|
507
502
|
|
fm_weck/config.py
CHANGED
|
@@ -8,34 +8,43 @@
|
|
|
8
8
|
import importlib.resources as pkg_resources
|
|
9
9
|
import logging
|
|
10
10
|
import os
|
|
11
|
-
import shutil
|
|
12
11
|
import stat
|
|
13
12
|
import sys
|
|
14
13
|
from functools import cache
|
|
15
14
|
from pathlib import Path
|
|
16
|
-
from typing import Any, Callable, Iterable, Optional, Tuple, TypeVar
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, Tuple, TypeVar
|
|
16
|
+
|
|
17
|
+
from werkzeug.utils import secure_filename
|
|
17
18
|
|
|
18
19
|
try:
|
|
19
|
-
from fm_tools.
|
|
20
|
+
from fm_tools.fmtool import FmTool
|
|
21
|
+
from fm_tools.fmtoolversion import FmToolVersion
|
|
20
22
|
except ImportError:
|
|
23
|
+
if not TYPE_CHECKING:
|
|
24
|
+
|
|
25
|
+
class FmTool:
|
|
26
|
+
def __init__(self, data):
|
|
27
|
+
raise ImportError("fm_tools is not imported.")
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
class FmToolVersion:
|
|
30
|
+
def __init__(self, data: FmTool, version: str):
|
|
31
|
+
raise ImportError("fm_tools is not imported.")
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
def get_actor_name(self):
|
|
34
|
+
raise ImportError("fm_tools is not imported.")
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
def get_version(self):
|
|
37
|
+
raise ImportError("fm_tools is not imported.")
|
|
31
38
|
|
|
32
39
|
|
|
33
40
|
from fm_weck.resources import RUN_WITH_OVERLAY, RUNEXEC_SCRIPT
|
|
34
41
|
|
|
42
|
+
from .file_util import copy_ensuring_unix_line_endings
|
|
43
|
+
|
|
35
44
|
try:
|
|
36
45
|
import tomllib as toml
|
|
37
46
|
except ImportError:
|
|
38
|
-
import tomli as toml
|
|
47
|
+
import tomli as toml # type: ignore
|
|
39
48
|
|
|
40
49
|
_SEARCH_ORDER: tuple[Path, ...] = (
|
|
41
50
|
Path.cwd() / ".fm-weck",
|
|
@@ -121,7 +130,7 @@ class Config(object):
|
|
|
121
130
|
return self.defaults().get(key, None)
|
|
122
131
|
|
|
123
132
|
@staticmethod
|
|
124
|
-
def _handle_relative_paths(fn: Callable[
|
|
133
|
+
def _handle_relative_paths(fn: Callable[..., Path]) -> Callable[..., Path]:
|
|
125
134
|
def wrapper(self, *args, **kwargs) -> Path:
|
|
126
135
|
"""Makes sure relative Paths in the config are relative to the config file."""
|
|
127
136
|
|
|
@@ -159,9 +168,15 @@ class Config(object):
|
|
|
159
168
|
def get_checksum_db(self) -> Path:
|
|
160
169
|
return self.cache_location / ".checksums.dbm"
|
|
161
170
|
|
|
162
|
-
def get_shelve_space_for(self, fm_data:
|
|
171
|
+
def get_shelve_space_for(self, fm_data: FmToolVersion) -> Path:
|
|
163
172
|
shelve = self.cache_location
|
|
164
|
-
|
|
173
|
+
# Remove leading http:// or https:// from the raw archive location
|
|
174
|
+
raw_location = fm_data.get_archive_location().raw
|
|
175
|
+
if raw_location.startswith("http://"):
|
|
176
|
+
raw_location = raw_location[len("http://") :]
|
|
177
|
+
elif raw_location.startswith("https://"):
|
|
178
|
+
raw_location = raw_location[len("https://") :]
|
|
179
|
+
tool_name = secure_filename(raw_location)
|
|
165
180
|
return shelve / tool_name
|
|
166
181
|
|
|
167
182
|
def get_shelve_path_for_property(self, path: Path) -> Path:
|
|
@@ -179,10 +194,10 @@ class Config(object):
|
|
|
179
194
|
def _system_is_not_posix():
|
|
180
195
|
return not (sys.platform.startswith("linux") or sys.platform == "darwin")
|
|
181
196
|
|
|
182
|
-
def make_runexec_script_available(self) -> Path:
|
|
197
|
+
def make_runexec_script_available(self) -> Path | None:
|
|
183
198
|
return self.make_script_available(RUNEXEC_SCRIPT)
|
|
184
199
|
|
|
185
|
-
def make_script_available(self, target_name: str = RUN_WITH_OVERLAY) -> Path:
|
|
200
|
+
def make_script_available(self, target_name: str = RUN_WITH_OVERLAY) -> Path | None:
|
|
186
201
|
script_dir = self.cache_location / ".scripts"
|
|
187
202
|
target = script_dir / target_name
|
|
188
203
|
|
|
@@ -192,7 +207,7 @@ class Config(object):
|
|
|
192
207
|
# Try to copy from package resources
|
|
193
208
|
try:
|
|
194
209
|
with pkg_resources.path("fm_weck.resources", target_name) as source_path:
|
|
195
|
-
|
|
210
|
+
copy_ensuring_unix_line_endings(source_path, target)
|
|
196
211
|
except FileNotFoundError:
|
|
197
212
|
logging.error(f"Resource {target_name} not found in package.")
|
|
198
213
|
return None
|
|
@@ -200,7 +215,7 @@ class Config(object):
|
|
|
200
215
|
# Compare modification time if the file exists
|
|
201
216
|
with pkg_resources.path("fm_weck.resources", target_name) as source_path:
|
|
202
217
|
if source_path.stat().st_mtime > target.stat().st_mtime:
|
|
203
|
-
|
|
218
|
+
copy_ensuring_unix_line_endings(source_path, target)
|
|
204
219
|
else:
|
|
205
220
|
logging.debug(f"Using existing {target_name} script")
|
|
206
221
|
return target
|
|
@@ -224,7 +239,7 @@ class Config(object):
|
|
|
224
239
|
|
|
225
240
|
|
|
226
241
|
@cache
|
|
227
|
-
def parse_fm_data(fm_data: Path, version:
|
|
242
|
+
def parse_fm_data(fm_data: Path, version: str | None) -> FmToolVersion:
|
|
228
243
|
import yaml
|
|
229
244
|
|
|
230
245
|
if not fm_data.exists() or not fm_data.is_file():
|
|
@@ -233,4 +248,4 @@ def parse_fm_data(fm_data: Path, version: Optional[str]) -> FmData:
|
|
|
233
248
|
with fm_data.open("rb") as f:
|
|
234
249
|
data = yaml.safe_load(f)
|
|
235
250
|
|
|
236
|
-
return
|
|
251
|
+
return FmToolVersion(data, version)
|
fm_weck/engine.py
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import io
|
|
9
9
|
import logging
|
|
10
|
+
import platform
|
|
10
11
|
import shutil
|
|
11
12
|
import signal
|
|
12
13
|
import subprocess
|
|
@@ -15,20 +16,25 @@ from abc import ABC, abstractmethod
|
|
|
15
16
|
from functools import cached_property, singledispatchmethod
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
from tempfile import mkdtemp
|
|
18
|
-
from typing import Callable, List, Optional, Union
|
|
19
|
+
from typing import TYPE_CHECKING, Callable, Iterable, List, Optional, Union
|
|
19
20
|
|
|
20
21
|
from fm_weck.run_result import RunResult
|
|
21
22
|
|
|
22
23
|
try:
|
|
23
|
-
from fm_tools.
|
|
24
|
+
from fm_tools.fmtoolversion import (
|
|
25
|
+
FmImageConfig, # type: ignore
|
|
26
|
+
FmToolVersion, # type: ignore
|
|
27
|
+
)
|
|
24
28
|
except ImportError:
|
|
25
|
-
# Mock the
|
|
26
|
-
|
|
27
|
-
def get_images(self):
|
|
28
|
-
pass
|
|
29
|
+
# Mock the FmToolVersion and FmImageConfig class for type checking
|
|
30
|
+
if not TYPE_CHECKING:
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
class FmImageConfig:
|
|
33
|
+
with_fallback: Callable[[str], "FmImageConfig"] # type: ignore
|
|
34
|
+
|
|
35
|
+
class FmToolVersion:
|
|
36
|
+
def get_images(self) -> "FmImageConfig": # type: ignore
|
|
37
|
+
pass
|
|
32
38
|
|
|
33
39
|
|
|
34
40
|
from fm_weck.config import Config, parse_fm_data
|
|
@@ -68,8 +74,8 @@ class Engine(ABC):
|
|
|
68
74
|
if self._tmp_output_dir.exists():
|
|
69
75
|
shutil.rmtree(self._tmp_output_dir)
|
|
70
76
|
|
|
71
|
-
def get_workdir(self):
|
|
72
|
-
return Path(CWD_MOUNT_LOCATION)
|
|
77
|
+
def get_workdir(self) -> str:
|
|
78
|
+
return Path(CWD_MOUNT_LOCATION).as_posix()
|
|
73
79
|
|
|
74
80
|
def set_output_log(self, output_log: Path):
|
|
75
81
|
self.log_file = output_log
|
|
@@ -121,7 +127,7 @@ class Engine(ABC):
|
|
|
121
127
|
"--entrypoint",
|
|
122
128
|
'[""]',
|
|
123
129
|
"-v",
|
|
124
|
-
f"{Path.cwd().absolute()}:{
|
|
130
|
+
f"{Path.cwd().absolute()}:{self.get_workdir()}",
|
|
125
131
|
"-v",
|
|
126
132
|
f"{Config().cache_location}:{CACHE_MOUNT_LOCATION}",
|
|
127
133
|
"-v",
|
|
@@ -157,7 +163,7 @@ class Engine(ABC):
|
|
|
157
163
|
except shutil.Error as e:
|
|
158
164
|
logger.warning(f"Error while copying the output {file} directory: {e}")
|
|
159
165
|
|
|
160
|
-
def assemble_command(self, command:
|
|
166
|
+
def assemble_command(self, command: Iterable[str]) -> list[str]:
|
|
161
167
|
base = self.base_command()
|
|
162
168
|
if self.add_benchexec_capabilities:
|
|
163
169
|
base += self.benchexec_capabilities()
|
|
@@ -180,7 +186,7 @@ class Engine(ABC):
|
|
|
180
186
|
_command = self._prep_command(command)
|
|
181
187
|
return base + [self.image, *_command]
|
|
182
188
|
|
|
183
|
-
def _prep_command(self, command:
|
|
189
|
+
def _prep_command(self, command: Iterable[str]) -> tuple[str | Path, ...]:
|
|
184
190
|
"""We want to map absolute paths of the current working directory to the
|
|
185
191
|
working directory of the container."""
|
|
186
192
|
|
|
@@ -198,9 +204,9 @@ class Engine(ABC):
|
|
|
198
204
|
return p
|
|
199
205
|
mapped = _map_path(Path(p))
|
|
200
206
|
if Path(p) == mapped:
|
|
201
|
-
return p
|
|
207
|
+
return Path(p).as_posix()
|
|
202
208
|
else:
|
|
203
|
-
return mapped
|
|
209
|
+
return Path(mapped).as_posix()
|
|
204
210
|
|
|
205
211
|
return tuple(map(_map_path, command))
|
|
206
212
|
|
|
@@ -218,11 +224,11 @@ class Engine(ABC):
|
|
|
218
224
|
def extract_image(fm: Union[str, Path], version: str, config: dict) -> str:
|
|
219
225
|
image = config.get("defaults", {}).get("image", None)
|
|
220
226
|
|
|
221
|
-
return parse_fm_data(fm, version).get_images().with_fallback(image)
|
|
227
|
+
return parse_fm_data(fm, version).get_images().with_fallback(image) # type: ignore
|
|
222
228
|
|
|
223
229
|
@staticmethod
|
|
224
230
|
def _base_engine_class(config: Config):
|
|
225
|
-
engine = config.defaults().get("engine", "podman").lower()
|
|
231
|
+
engine = "docker" if platform.system() != "Linux" else config.defaults().get("engine", "podman").lower()
|
|
226
232
|
|
|
227
233
|
if engine == "docker":
|
|
228
234
|
return Docker
|
|
@@ -241,7 +247,7 @@ class Engine(ABC):
|
|
|
241
247
|
@from_config.register
|
|
242
248
|
@staticmethod
|
|
243
249
|
def _(fm: Path, version: str, config: Config):
|
|
244
|
-
image = Engine.extract_image(fm, version, config)
|
|
250
|
+
image = Engine.extract_image(fm, version, config) # type: ignore
|
|
245
251
|
Base = Engine._base_engine_class(config)
|
|
246
252
|
engine = Base(image)
|
|
247
253
|
return Engine._prepare_engine(engine, config)
|
|
@@ -249,14 +255,14 @@ class Engine(ABC):
|
|
|
249
255
|
@from_config.register
|
|
250
256
|
@staticmethod
|
|
251
257
|
def _(fm: str, version: str, config: Config):
|
|
252
|
-
image = Engine.extract_image(fm, version, config)
|
|
258
|
+
image = Engine.extract_image(fm, version, config) # type: ignore
|
|
253
259
|
Base = Engine._base_engine_class(config)
|
|
254
260
|
engine = Base(image)
|
|
255
261
|
return Engine._prepare_engine(engine, config)
|
|
256
262
|
|
|
257
263
|
@from_config.register
|
|
258
264
|
@staticmethod
|
|
259
|
-
def _(fm:
|
|
265
|
+
def _(fm: FmToolVersion, config: Config):
|
|
260
266
|
image = fm.get_images().with_fallback(config.from_defaults_or_none("image"))
|
|
261
267
|
Base = Engine._base_engine_class(config)
|
|
262
268
|
engine = Base(image)
|
|
@@ -281,6 +287,8 @@ class Engine(ABC):
|
|
|
281
287
|
|
|
282
288
|
class BuildCommand(ABC):
|
|
283
289
|
build_args: List[str] = []
|
|
290
|
+
containerfile: Path
|
|
291
|
+
_engine: str
|
|
284
292
|
|
|
285
293
|
@abstractmethod
|
|
286
294
|
def __init__(self, containerfile: Path, **kwargs):
|
|
@@ -290,7 +298,7 @@ class Engine(ABC):
|
|
|
290
298
|
self.build_args += ["--build-arg", f"BASE_IMAGE={image}"]
|
|
291
299
|
return self
|
|
292
300
|
|
|
293
|
-
def packages(self, packages:
|
|
301
|
+
def packages(self, packages: Iterable[str]):
|
|
294
302
|
self.build_args += ["--build-arg", f"REQUIRED_PACKAGES={' '.join(packages)}"]
|
|
295
303
|
return self
|
|
296
304
|
|
|
@@ -342,7 +350,7 @@ class Engine(ABC):
|
|
|
342
350
|
process.terminate()
|
|
343
351
|
process.wait()
|
|
344
352
|
|
|
345
|
-
return RunResult(command, process.returncode,
|
|
353
|
+
return RunResult(command, process.returncode, "")
|
|
346
354
|
|
|
347
355
|
def _run_process(self, command: tuple[str, ...] | list[str], timeout_sec: Optional[float] = None) -> RunResult:
|
|
348
356
|
process = None # To make sure process is defined if a signal is caught early
|
|
@@ -363,16 +371,18 @@ class Engine(ABC):
|
|
|
363
371
|
|
|
364
372
|
def run_and_poll(writer: Callable[[str], None]):
|
|
365
373
|
while process.poll() is None:
|
|
374
|
+
if process.stdout is None:
|
|
375
|
+
continue
|
|
366
376
|
line = process.stdout.readline().decode("utf-8")
|
|
367
377
|
writer(line)
|
|
368
378
|
if self.print_output_to_stdout:
|
|
369
379
|
sys.stdout.write(line)
|
|
370
380
|
|
|
371
381
|
if self.log_file is None:
|
|
372
|
-
run_and_poll(full_stdout.write)
|
|
382
|
+
run_and_poll(full_stdout.write) # type: ignore
|
|
373
383
|
else:
|
|
374
384
|
with self.log_file.open("w") as log:
|
|
375
|
-
run_and_poll(log.write)
|
|
385
|
+
run_and_poll(log.write) # type: ignore
|
|
376
386
|
|
|
377
387
|
assert process is not None, "Process should be defined at this point."
|
|
378
388
|
try:
|
|
@@ -391,12 +401,12 @@ class Engine(ABC):
|
|
|
391
401
|
if self.image is None:
|
|
392
402
|
raise NoImageError("No image set for engine.")
|
|
393
403
|
|
|
394
|
-
command = self.assemble_command(command)
|
|
404
|
+
command = self.assemble_command(command) # type: ignore
|
|
395
405
|
logger.debug("Running: %s", command)
|
|
396
406
|
if self.dry_run:
|
|
397
407
|
print("Command to be executed:")
|
|
398
408
|
print(" ".join(map(str, command)))
|
|
399
|
-
return 0, ""
|
|
409
|
+
return RunResult(command, 0, "Dry run: no command executed.")
|
|
400
410
|
|
|
401
411
|
if self.interactive or not self.handle_io:
|
|
402
412
|
return self._run_process_without_attaching_io(command, timeout_sec=timeout_sec)
|
fm_weck/file_util.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# This file is part of fm-weck: executing fm-tools in containerized environments.
|
|
2
|
+
# https://gitlab.com/sosy-lab/software/fm-weck
|
|
3
|
+
#
|
|
4
|
+
# SPDX-FileCopyrightText: 2024 Dirk Beyer <https://www.sosy-lab.org>
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# replacement strings
|
|
11
|
+
WINDOWS_LINE_ENDING = b"\r\n"
|
|
12
|
+
UNIX_LINE_ENDING = b"\n"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def copy_ensuring_unix_line_endings(src: Path, dst: Path) -> None:
|
|
16
|
+
with open(src, "rb") as src_file:
|
|
17
|
+
content = src_file.read()
|
|
18
|
+
|
|
19
|
+
# Windows ➡ Unix
|
|
20
|
+
content = content.replace(WINDOWS_LINE_ENDING, UNIX_LINE_ENDING)
|
|
21
|
+
|
|
22
|
+
with open(dst, "wb") as dst_file:
|
|
23
|
+
dst_file.write(content)
|
fm_weck/image_mgr.py
CHANGED
|
@@ -10,12 +10,13 @@ from pathlib import Path
|
|
|
10
10
|
from typing import TYPE_CHECKING
|
|
11
11
|
|
|
12
12
|
try:
|
|
13
|
-
from fm_tools.
|
|
13
|
+
from fm_tools.fmtoolversion import FmImageConfig
|
|
14
14
|
except ImportError:
|
|
15
|
+
if not TYPE_CHECKING:
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
class FmImageConfig:
|
|
18
|
+
def __init__(self, full_images, base_images, required_packages):
|
|
19
|
+
raise ImportError("fm_tools is not imported.")
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
from fm_weck.exceptions import NoImageError
|
fm_weck/resources/__init__.py
CHANGED
|
@@ -5,28 +5,30 @@
|
|
|
5
5
|
#
|
|
6
6
|
# SPDX-License-Identifier: Apache-2.0
|
|
7
7
|
|
|
8
|
+
import importlib.resources as pkg_resources
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
CONTAINERFILE = resource_dir / "Containerfile"
|
|
11
|
+
from . import fm_tools, properties
|
|
13
12
|
|
|
14
13
|
# During the build of the wheel file, the fm-tools/data directory is copied
|
|
15
14
|
# to the wheel file under fm_weck/resources/fm_tools
|
|
16
|
-
|
|
17
|
-
PROPERTY_LOCATION = resource_dir / "properties"
|
|
15
|
+
|
|
18
16
|
RUN_WITH_OVERLAY = "run_with_overlay.sh"
|
|
19
|
-
BENCHEXEC_WHL =
|
|
17
|
+
BENCHEXEC_WHL = "BenchExec-3.25-py3-none-any.whl"
|
|
20
18
|
RUNEXEC_SCRIPT = "runexec"
|
|
21
19
|
|
|
22
20
|
|
|
23
21
|
def iter_fm_data():
|
|
24
|
-
for fm_data in
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
for fm_data in pkg_resources.contents(fm_tools):
|
|
23
|
+
with pkg_resources.path(fm_tools, fm_data) as fake_context_path:
|
|
24
|
+
fm_data_path = Path(fake_context_path)
|
|
25
|
+
if fm_data_path.is_file() and (fm_data_path.name.endswith(".yml") or fm_data_path.name.endswith(".yaml")):
|
|
26
|
+
yield fm_data_path
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def iter_properties():
|
|
30
|
-
for prop in
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
for prop in pkg_resources.contents(properties):
|
|
31
|
+
with pkg_resources.path(properties, prop) as fake_context_path:
|
|
32
|
+
prop_path = Path(fake_context_path)
|
|
33
|
+
if prop_path.is_file():
|
|
34
|
+
yield prop_path
|
|
@@ -2,7 +2,7 @@ id: hybridtiger
|
|
|
2
2
|
name: HybridTiger
|
|
3
3
|
input_languages:
|
|
4
4
|
- C
|
|
5
|
-
project_url: https://
|
|
5
|
+
project_url: https://gitlab.com/sosy-lab/software/cpachecker
|
|
6
6
|
repository_url: https://gitlab.com/sosy-lab/software/cpachecker
|
|
7
7
|
spdx_license_identifier: Apache-2.0
|
|
8
8
|
benchexec_toolinfo_module: "https://gitlab.com/sosy-lab/software/benchexec/-/raw/main/benchexec/tools/cpachecker.py"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
id: ltsmin
|
|
2
|
+
name: LTSmin
|
|
3
|
+
description: |
|
|
4
|
+
LTSmin is a language-independent model-checking toolset supporting
|
|
5
|
+
multiple input languages and advanced state-space generation techniques via a unified PINS interface.
|
|
6
|
+
It features modular architecture, symbolic/distributed/multi-core reachability,
|
|
7
|
+
and easy extensibility for new languages.
|
|
8
|
+
input_languages:
|
|
9
|
+
- B
|
|
10
|
+
- DVE
|
|
11
|
+
- ETF
|
|
12
|
+
- Event-B
|
|
13
|
+
- MCRL
|
|
14
|
+
- MCRL2
|
|
15
|
+
- PNML
|
|
16
|
+
- Promela
|
|
17
|
+
- TLA+
|
|
18
|
+
- Z
|
|
19
|
+
project_url: https://ltsmin.utwente.nl/
|
|
20
|
+
repository_url: https://github.com/utwente-fmt/ltsmin
|
|
21
|
+
spdx_license_identifier: BSD-3-Clause
|
|
22
|
+
benchexec_toolinfo_module: "https://www.cip.ifi.lmu.de/~wachowitz/ltsmin.py"
|
|
23
|
+
fmtools_format_version: "2.0"
|
|
24
|
+
fmtools_entry_maintainers:
|
|
25
|
+
- dbeyer
|
|
26
|
+
- ricffb
|
|
27
|
+
|
|
28
|
+
maintainers:
|
|
29
|
+
- orcid: 0000-0002-2433-4174
|
|
30
|
+
name: Alfons Laarman
|
|
31
|
+
institution: Leiden Institute for Advanced Computer Science
|
|
32
|
+
country: Netherlands
|
|
33
|
+
url: https://alfons.laarman.com/
|
|
34
|
+
|
|
35
|
+
versions:
|
|
36
|
+
- version: "pnml2lts-sym-3.0.2"
|
|
37
|
+
url: "https://github.com/utwente-fmt/ltsmin/releases/download/v3.0.2/ltsmin-v3.0.2-linux.tgz"
|
|
38
|
+
benchexec_toolinfo_options: ["pnml2lts-sym"]
|
|
39
|
+
required_ubuntu_packages: []
|
|
40
|
+
base_container_images: ["ubuntu:24.04"]
|
|
41
|
+
|
|
42
|
+
competition_participations: []
|
|
43
|
+
|
|
44
|
+
techniques: []
|
|
45
|
+
|
|
46
|
+
frameworks_solvers: []
|
|
47
|
+
|
|
48
|
+
literature:
|
|
49
|
+
- doi: 10.1007/978-3-662-46681-0_61
|
|
50
|
+
title: "LTSmin: High-Performance Language-Independent Model Checking"
|
|
51
|
+
year: 2015
|
|
52
|
+
- doi: 10.1007/978-3-642-20398-5_40
|
|
53
|
+
title: "Multi-Core LTSmin: Marrying Modularity and Scalability"
|
|
54
|
+
year: 2011
|
|
55
|
+
- doi: 10.1007/978-3-642-14295-6_31
|
|
56
|
+
title: "LTSmin: Distributed and Symbolic Reachability"
|
|
57
|
+
year: 2010
|
|
58
|
+
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
id: sv-sanitizers
|
|
2
|
-
name:
|
|
2
|
+
name: SV-sanitizers
|
|
3
|
+
description: |
|
|
4
|
+
SV-COMP wrapper for sanitizers (ThreadSanitizer, AddressSanitizer, LeakSanitizer, UndefinedBehaviorSanitizer).
|
|
3
5
|
input_languages:
|
|
4
6
|
- C
|
|
5
7
|
project_url: https://github.com/sim642/sv-sanitizers
|
fm_weck/run_result.py
CHANGED
|
@@ -10,13 +10,13 @@ from functools import singledispatchmethod
|
|
|
10
10
|
|
|
11
11
|
from benchexec.tools.template import BaseTool2
|
|
12
12
|
from benchexec.util import ProcessExitCode
|
|
13
|
-
from fm_tools import
|
|
13
|
+
from fm_tools.fmtoolversion import FmToolVersion
|
|
14
14
|
from fm_tools.run import get_tool_info
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@dataclass(frozen=True)
|
|
18
18
|
class RunResult:
|
|
19
|
-
command: tuple[str, ...]
|
|
19
|
+
command: tuple[str, ...] | list[str]
|
|
20
20
|
exit_code: int
|
|
21
21
|
raw_output: str
|
|
22
22
|
|
|
@@ -33,6 +33,6 @@ class RunResult:
|
|
|
33
33
|
return tool.determine_result(self.as_benchexec_run())
|
|
34
34
|
|
|
35
35
|
@determine_result.register
|
|
36
|
-
def _(self, tool:
|
|
37
|
-
|
|
38
|
-
return self.determine_result(
|
|
36
|
+
def _(self, tool: FmToolVersion) -> str:
|
|
37
|
+
tool_ = get_tool_info(tool)
|
|
38
|
+
return self.determine_result(tool_)
|
fm_weck/runexec_mode.py
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#
|
|
6
6
|
# SPDX-License-Identifier: Apache-2.0
|
|
7
7
|
|
|
8
|
+
import importlib.resources as pkg_resources
|
|
8
9
|
import logging
|
|
9
10
|
import shutil
|
|
10
11
|
from pathlib import Path
|
|
@@ -42,8 +43,13 @@ def run_runexec(
|
|
|
42
43
|
else:
|
|
43
44
|
# Default to the bundled benchexec package
|
|
44
45
|
benchexec_package = configuration.get_shelve_path_for_benchexec()
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
try:
|
|
47
|
+
with pkg_resources.path("fm_weck.resources", BENCHEXEC_WHL) as source_path:
|
|
48
|
+
shutil.copy(source_path, benchexec_package)
|
|
49
|
+
engine.env["PYTHONPATH"] = f"{CACHE_MOUNT_LOCATION}/.lib/benchexec.whl"
|
|
50
|
+
except FileNotFoundError:
|
|
51
|
+
logging.error(f"Resource {BENCHEXEC_WHL} not found in package.")
|
|
52
|
+
return None
|
|
47
53
|
|
|
48
54
|
for path in mountable_absolute_paths_of_command(Path.cwd().absolute(), command):
|
|
49
55
|
engine.mount(path, str(path) + ":ro")
|
fm_weck/serve.py
CHANGED
|
@@ -6,20 +6,23 @@
|
|
|
6
6
|
# SPDX-License-Identifier: Apache-2.0
|
|
7
7
|
|
|
8
8
|
import dbm
|
|
9
|
+
import json
|
|
9
10
|
import logging
|
|
10
11
|
import os
|
|
11
|
-
import shutil
|
|
12
12
|
import sys
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import Optional, Tuple, Union
|
|
14
|
+
from typing import Optional, Tuple, Union, cast
|
|
15
15
|
|
|
16
|
-
from fm_tools import FmData
|
|
17
16
|
from fm_tools.benchexec_helper import DataModel
|
|
17
|
+
from fm_tools.files import unzip
|
|
18
|
+
from fm_tools.fmtoolversion import FmToolVersion
|
|
18
19
|
|
|
19
20
|
from fm_weck.run_result import RunResult
|
|
21
|
+
from fm_weck.tmp_file import NTempFile
|
|
20
22
|
|
|
21
23
|
from .config import Config, parse_fm_data
|
|
22
24
|
from .engine import CACHE_MOUNT_LOCATION, Engine
|
|
25
|
+
from .file_util import copy_ensuring_unix_line_endings
|
|
23
26
|
|
|
24
27
|
logger = logging.getLogger(__name__)
|
|
25
28
|
|
|
@@ -60,14 +63,15 @@ def update_checksum(shelve_space: Path, checksum: str, config: Config):
|
|
|
60
63
|
|
|
61
64
|
|
|
62
65
|
def setup_fm_tool(
|
|
63
|
-
fm_tool: Union[Path,
|
|
66
|
+
fm_tool: Union[Path, FmToolVersion],
|
|
64
67
|
version: Optional[str],
|
|
65
68
|
configuration: Config,
|
|
66
69
|
offline_mode: bool = False,
|
|
67
|
-
) -> Tuple[
|
|
68
|
-
# Don't explicitly disallow non-
|
|
70
|
+
) -> Tuple[FmToolVersion, Path]:
|
|
71
|
+
# Don't explicitly disallow non-FmToolVersion here; Pythonic Users might want to exchange the FmToolVersion object
|
|
69
72
|
# by a class with the same interface
|
|
70
73
|
fm_data = parse_fm_data(fm_tool, version) if isinstance(fm_tool, (Path, str)) else fm_tool
|
|
74
|
+
fm_data = cast(FmToolVersion, fm_data)
|
|
71
75
|
|
|
72
76
|
shelve_space = configuration.get_shelve_space_for(fm_data)
|
|
73
77
|
logger.debug("Using shelve space %s", shelve_space)
|
|
@@ -75,13 +79,58 @@ def setup_fm_tool(
|
|
|
75
79
|
|
|
76
80
|
if (not offline_mode) and shelve_space.exists() and shelve_space.is_dir():
|
|
77
81
|
logger.debug("Shelve space already exists, testing checksum")
|
|
78
|
-
checksum = fm_data.
|
|
79
|
-
|
|
82
|
+
checksum = fm_data.get_archive_location().resolve().checksum
|
|
83
|
+
if checksum is None:
|
|
84
|
+
logger.warning("No checksum available for %s, skipping cache check", fm_data.get_tool_name_with_version())
|
|
85
|
+
skip_download = False
|
|
86
|
+
else:
|
|
87
|
+
skip_download = check_cache_entry(shelve_space, checksum, configuration)
|
|
80
88
|
|
|
81
89
|
if not skip_download:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
if sys.platform != "win32":
|
|
91
|
+
fm_data.download_and_install_into(shelve_space)
|
|
92
|
+
checksum = fm_data.get_archive_location().resolve().checksum
|
|
93
|
+
if checksum is None:
|
|
94
|
+
logger.warning(
|
|
95
|
+
"No checksum available for %s, skipping checksum update", fm_data.get_tool_name_with_version()
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
update_checksum(shelve_space, checksum, configuration)
|
|
99
|
+
|
|
100
|
+
# On Windows, we need to download the tool first and then unzip it
|
|
101
|
+
# This is because the unzip operation might fail due to a permission error
|
|
102
|
+
else:
|
|
103
|
+
dl_loc = NTempFile(f"{fm_data.get_tool_name_with_version()}-dl.zip")
|
|
104
|
+
fm_data.download_into(dl_loc.name)
|
|
105
|
+
|
|
106
|
+
success = False
|
|
107
|
+
last_error = None
|
|
108
|
+
|
|
109
|
+
logging.info("Unzipping downloaded archive...")
|
|
110
|
+
|
|
111
|
+
# On Windows, the unzip operation might fail due to a permission error.
|
|
112
|
+
# Retrying the operation a few times mitigates this issue.
|
|
113
|
+
for _ in range(100):
|
|
114
|
+
try:
|
|
115
|
+
unzip(dl_loc.name, shelve_space)
|
|
116
|
+
success = True
|
|
117
|
+
break
|
|
118
|
+
except PermissionError as e:
|
|
119
|
+
last_error = e
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
if not success:
|
|
123
|
+
logger.error("Failed to unzip downloaded archive")
|
|
124
|
+
raise last_error
|
|
125
|
+
|
|
126
|
+
checksum = fm_data.get_archive_location().resolve().checksum
|
|
127
|
+
if checksum is None:
|
|
128
|
+
logger.warning(
|
|
129
|
+
"No checksum available for %s, skipping checksum update", fm_data.get_tool_name_with_version()
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
update_checksum(shelve_space, checksum, configuration)
|
|
133
|
+
map_doi(fm_data, shelve_space)
|
|
85
134
|
|
|
86
135
|
tool_info_module = fm_data.get_toolinfo_module()
|
|
87
136
|
|
|
@@ -98,8 +147,18 @@ def setup_fm_tool(
|
|
|
98
147
|
return fm_data, shelve_space
|
|
99
148
|
|
|
100
149
|
|
|
150
|
+
def install_fm_tool(
|
|
151
|
+
fm_tool: Union[Path, FmToolVersion], version: Optional[str], configuration: Config, install_path: Path
|
|
152
|
+
) -> None:
|
|
153
|
+
fm_data = parse_fm_data(fm_tool, version) if isinstance(fm_tool, (Path, str)) else fm_tool
|
|
154
|
+
shelve_space = install_path.resolve() if install_path else configuration.get_shelve_space_for(fm_data)
|
|
155
|
+
|
|
156
|
+
logger.debug("Installing tool into %s", shelve_space)
|
|
157
|
+
fm_data.download_and_install_into(shelve_space)
|
|
158
|
+
|
|
159
|
+
|
|
101
160
|
def run_guided(
|
|
102
|
-
fm_tool: Union[Path,
|
|
161
|
+
fm_tool: Union[Path, FmToolVersion],
|
|
103
162
|
version: Optional[str],
|
|
104
163
|
configuration: Config,
|
|
105
164
|
prop: Optional[Path],
|
|
@@ -120,10 +179,10 @@ def run_guided(
|
|
|
120
179
|
# copy the property to the weck_cache which should be mounted
|
|
121
180
|
source_property_path = prop
|
|
122
181
|
property_path = configuration.get_shelve_path_for_property(source_property_path)
|
|
123
|
-
|
|
182
|
+
copy_ensuring_unix_line_endings(source_property_path, property_path)
|
|
124
183
|
except KeyError:
|
|
125
184
|
logger.error("Unknown property %s", prop)
|
|
126
|
-
return 1,
|
|
185
|
+
return RunResult(command=[], exit_code=1, raw_output="Unknown property")
|
|
127
186
|
|
|
128
187
|
configuration.make_script_available()
|
|
129
188
|
|
|
@@ -157,7 +216,7 @@ def run_guided(
|
|
|
157
216
|
# We are using the second approach here, in order to avoid modifying FM-Data
|
|
158
217
|
if witness is not None:
|
|
159
218
|
assert any(c == "${witness}" for c in command), "The version given does not support witness files"
|
|
160
|
-
command = [witness if c == "${witness}" else c for c in command]
|
|
219
|
+
command = [str(witness) if c == "${witness}" else c for c in command]
|
|
161
220
|
|
|
162
221
|
logger.debug("Assembled command from fm-tools: %s", command)
|
|
163
222
|
|
|
@@ -169,7 +228,7 @@ def run_guided(
|
|
|
169
228
|
|
|
170
229
|
|
|
171
230
|
def run_manual(
|
|
172
|
-
fm_tool: Union[Path,
|
|
231
|
+
fm_tool: Union[Path, FmToolVersion],
|
|
173
232
|
version: Optional[str],
|
|
174
233
|
configuration: Config,
|
|
175
234
|
command: list[str],
|
|
@@ -200,6 +259,29 @@ def run_manual(
|
|
|
200
259
|
|
|
201
260
|
engine.print_output_to_stdout = print_tool_output_to_console
|
|
202
261
|
|
|
203
|
-
execution_result = engine.run(executable, *command, timeout_sec=timeout_sec)
|
|
262
|
+
execution_result = engine.run(str(executable), *command, timeout_sec=timeout_sec)
|
|
204
263
|
|
|
205
264
|
return execution_result
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
### Move to cache_mgr.py after merge to main ###
|
|
268
|
+
def map_doi(fm_data: FmToolVersion, tool_path: Path):
|
|
269
|
+
doi_map = tool_path.parent / "doi_map.json"
|
|
270
|
+
|
|
271
|
+
if os.path.exists(doi_map):
|
|
272
|
+
with open(doi_map, "r") as file:
|
|
273
|
+
data = json.load(file)
|
|
274
|
+
else:
|
|
275
|
+
data = {}
|
|
276
|
+
|
|
277
|
+
doi = str(tool_path).split("/")[-1]
|
|
278
|
+
if doi not in data:
|
|
279
|
+
data[doi] = []
|
|
280
|
+
if fm_data.get_tool_name_with_version() not in data[doi]:
|
|
281
|
+
data[doi].append(fm_data.get_tool_name_with_version())
|
|
282
|
+
|
|
283
|
+
with open(doi_map, "w") as file:
|
|
284
|
+
json.dump(data, file, indent=2)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
### Move to cache_mgr.py after merge to main ###
|
fm_weck/tmp_file.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# This file is part of fm-weck: executing fm-tools in containerized environments.
|
|
2
|
+
# https://gitlab.com/sosy-lab/software/fm-weck
|
|
3
|
+
#
|
|
4
|
+
# SPDX-FileCopyrightText: 2024 Dirk Beyer <https://www.sosy-lab.org>
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
7
|
+
|
|
8
|
+
import contextlib
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from tempfile import mkdtemp
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NTempFile:
|
|
15
|
+
"""
|
|
16
|
+
Custom temporary file context manager.
|
|
17
|
+
It creates a temporary file and deletes it after the context manager is closed.
|
|
18
|
+
The file is not kept open in order to achieve compatibility with Windows.
|
|
19
|
+
|
|
20
|
+
Inspired by https://stackoverflow.com/a/63173312
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, name, mode="wb"):
|
|
24
|
+
self.__tmp_dir = mkdtemp()
|
|
25
|
+
self.name = Path(self.__tmp_dir) / name
|
|
26
|
+
self._mode = mode
|
|
27
|
+
self._file = None
|
|
28
|
+
|
|
29
|
+
def __enter__(self):
|
|
30
|
+
"""Enter the context manager and return the file object."""
|
|
31
|
+
self._file = open(self.name, self._mode)
|
|
32
|
+
return self
|
|
33
|
+
|
|
34
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
35
|
+
"""Exit the context manager and clean up."""
|
|
36
|
+
if self._file is not None:
|
|
37
|
+
self._file.close()
|
|
38
|
+
self._cleanup()
|
|
39
|
+
|
|
40
|
+
def write(self, data):
|
|
41
|
+
"""Write data to the temporary file."""
|
|
42
|
+
if self._file is None:
|
|
43
|
+
# If not in context manager, open the file temporarily
|
|
44
|
+
with open(self.name, self._mode) as f:
|
|
45
|
+
f.write(data)
|
|
46
|
+
else:
|
|
47
|
+
self._file.write(data)
|
|
48
|
+
self._file.flush() # Ensure data is written immediately
|
|
49
|
+
|
|
50
|
+
def _cleanup(self):
|
|
51
|
+
"""Clean up the temporary file and directory."""
|
|
52
|
+
with contextlib.suppress(OSError):
|
|
53
|
+
if self.name.exists():
|
|
54
|
+
os.unlink(self.name)
|
|
55
|
+
with contextlib.suppress(OSError):
|
|
56
|
+
if Path(self.__tmp_dir).exists():
|
|
57
|
+
os.rmdir(self.__tmp_dir)
|
|
58
|
+
|
|
59
|
+
def __del__(self):
|
|
60
|
+
"""Cleanup when object is garbage collected."""
|
|
61
|
+
self._cleanup()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fm-weck
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.8
|
|
4
4
|
Author-email: Henrik Wachowitz <henrik.wachowitz@ifi.lmu.de>
|
|
5
5
|
Maintainer-email: Henrik Wachowitz <henrik.wachowitz@ifi.lmu.de>
|
|
6
6
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Requires-Python: >=3.10
|
|
13
13
|
Requires-Dist: argcomplete
|
|
14
|
-
Requires-Dist: fm-tools>=0.
|
|
14
|
+
Requires-Dist: fm-tools>=1.0.0
|
|
15
15
|
Requires-Dist: pyyaml>=6.0
|
|
16
16
|
Requires-Dist: tabulate
|
|
17
17
|
Requires-Dist: tomli>=2.0; python_version <= '3.10'
|
|
@@ -36,7 +36,7 @@ We provide a tutorial on how to use fm-weck [here](doc/Tutorial.md).
|
|
|
36
36
|
|
|
37
37
|
## Install dependencies
|
|
38
38
|
|
|
39
|
-
`fm-weck` requires Python 3.10 or higher. `fm-weck` relies on an available Podman installation. To install Podman on Ubuntu, run the following command:
|
|
39
|
+
`fm-weck` requires Python 3.10 or higher. `fm-weck` relies on an available Podman or Docker installation. To install Podman on Ubuntu, run the following command:
|
|
40
40
|
|
|
41
41
|
```
|
|
42
42
|
sudo apt install podman
|
|
@@ -46,11 +46,11 @@ sudo apt install podman
|
|
|
46
46
|
|
|
47
47
|
There are three modes of operation: `run`, `shell` and `expert`.
|
|
48
48
|
|
|
49
|
+
- `run`: (**Recommended**) enables plug-and-play execution of formal methods tools: it
|
|
50
|
+
downloads and unpacks a tool from the fm-tools metadata file into a user-specified cache directory on the host system and then runs the tool in the containerized environment
|
|
49
51
|
- `expert`: executes a tool in it's containerized environment specified through the
|
|
50
52
|
corresponding fm-tools YAML file: All arguments are passed verbatim to the tool.
|
|
51
53
|
- `shell`: enters an interactive shell inside of the container specified by the given tool
|
|
52
|
-
- `run`: enables plug-and-play execution of formal methods tools: it
|
|
53
|
-
downloads and unpacks a tool from the fm-tools metadata file into a user-specified cache directory on the host system and then runs the tool in the containerized environment
|
|
54
54
|
|
|
55
55
|
## Development and Testing
|
|
56
56
|
|
|
@@ -63,4 +63,10 @@ In Linux this can be done by running the following command in the root directory
|
|
|
63
63
|
|
|
64
64
|
```
|
|
65
65
|
ln -s $(pwd)/fm_tools/data $(pwd)/src/fm_weck/resources/fm_tools
|
|
66
|
-
```
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Publications
|
|
69
|
+
|
|
70
|
+
A paper about fm-weck has been accepted at the FM '24 conference.
|
|
71
|
+
|
|
72
|
+
- [<img src="/doc/images/pdf.png" alt="PDF icon" width="32"/> FM-Weck: Containerized Execution of Formal-Methods Tools](https://link.springer.com/content/pdf/10.1007/978-3-031-71177-0_3.pdf), by Dirk Beyer and Henrik Wachowitz. Proc. FM. Springer (2024). [doi:10.1007/978-3-031-71177-0_3](https://doi.org/10.1007/978-3-031-71177-0_3)
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
fm_weck/__init__.py,sha256=
|
|
1
|
+
fm_weck/__init__.py,sha256=hwPTOu20emETJm7zBfj7trYoLdZD-A_ZoL0mtmZcyLw,351
|
|
2
2
|
fm_weck/__main__.py,sha256=IfNDAqM6MK6P7KsQoW3wOHPOscB8evdVlS9C7R4wd_0,391
|
|
3
3
|
fm_weck/cache_mgr.py,sha256=3-OQFmCeswazXmX08ND4oEHFOR07ZDCwWzjmFTDkOSE,1373
|
|
4
|
-
fm_weck/cli.py,sha256=
|
|
5
|
-
fm_weck/config.py,sha256=
|
|
6
|
-
fm_weck/engine.py,sha256=
|
|
4
|
+
fm_weck/cli.py,sha256=gWiFsbkCOWS6oXTr6srWza-qFrfpU_XR1P-MxHrCQuM,17136
|
|
5
|
+
fm_weck/config.py,sha256=8XXlHbb9cW1N1jatNFY5AnaRdxsSz-ohCrqq5t90RAc,8099
|
|
6
|
+
fm_weck/engine.py,sha256=zway0nC4piPohK3hoBYr9Sv3xAa32dLeuPSuOw1Wers,17376
|
|
7
7
|
fm_weck/exceptions.py,sha256=xXxbYK-FZW6YksBtaN79KaA4fGnFCBO-wc0QMqtq3Og,282
|
|
8
|
-
fm_weck/
|
|
9
|
-
fm_weck/
|
|
10
|
-
fm_weck/
|
|
8
|
+
fm_weck/file_util.py,sha256=R9y6LUZCDNJR4j25_Q24lN_zhACnOjYP5BvcpYaQPiA,649
|
|
9
|
+
fm_weck/image_mgr.py,sha256=mDqP-YD6AEOW2xldYJ4D-wxYKF0Ta5SgoJiX2CrhyQg,1906
|
|
10
|
+
fm_weck/run_result.py,sha256=0d8G3py1VCPP4jLxPCVzL8Vljvrwt9IHlmvCkW0pwx8,1249
|
|
11
|
+
fm_weck/runexec_mode.py,sha256=UamxVvYm0XErPjR2sRJaLMX8uHBzRcgCTWbQIZjdju0,2195
|
|
11
12
|
fm_weck/runexec_util.py,sha256=YBvVIPpmEousZVxbZ5NS8jzpKPLyws31kIFE2z3Ki2E,1370
|
|
12
|
-
fm_weck/serve.py,sha256=
|
|
13
|
+
fm_weck/serve.py,sha256=mDaZ1BUKwUh_fPgGMR4LXrJ1c5GOmwwXbHbMllmJzi8,10608
|
|
14
|
+
fm_weck/tmp_file.py,sha256=oJiE8VGTPxhl-bXdtbM8eNqQ4e9ECPG1jDmiboVDo_k,1956
|
|
13
15
|
fm_weck/version_listing.py,sha256=caaoC3n9R-Ao2sEQ_ngOVO3bnKr7cNVeH6EiA8jO5Sc,864
|
|
14
16
|
fm_weck/resources/BenchExec-3.27-py3-none-any.whl,sha256=g-db8LM8HfqLhbnl7n5lvUbMnF2tZ4MHAVOxTGxqO8w,732849
|
|
15
17
|
fm_weck/resources/BenchExec-3.27-py3-none-any.whl.license,sha256=Nq2Mwgn_pyr6ZZrTT095QPtFP3hr15ZeIRIaY0B7eC8,201
|
|
16
18
|
fm_weck/resources/Containerfile,sha256=MltxP1of9klsQFNR8WyngRTJrPwxQTF4C9ennRxVqSo,391
|
|
17
|
-
fm_weck/resources/__init__.py,sha256
|
|
19
|
+
fm_weck/resources/__init__.py,sha256=-YLqFTF2Gu4RA9ye3SdvBk1rjhlYxk_f1TgPsix3SAk,1213
|
|
18
20
|
fm_weck/resources/run_with_overlay.sh,sha256=v1gV_6kMQ0v9BQ3chgDqI1MAOLHbPWeeTC52aCqVpEM,1162
|
|
19
21
|
fm_weck/resources/runexec,sha256=ogIBO38HLu9C9kDTTANBgAqVnH-UIF1bSJ9d3DSjyF4,462
|
|
20
22
|
fm_weck/resources/properties/coverage-branches.prp,sha256=Gl2r1cgBFoh4M2laa8dVGhteHkL04oiBRLzxz_hbkEU,56
|
|
@@ -46,7 +48,7 @@ fm_weck/resources/fm_tools/blast.yml,sha256=zwPhJN0PfNJo_ZM4a5DNTxKCqc_b0CDaADRf
|
|
|
46
48
|
fm_weck/resources/fm_tools/brick.yml,sha256=7mWXlRdc-XiFR2c2wohhuoWHCznmEcnr1i1aLCtcZTk,2007
|
|
47
49
|
fm_weck/resources/fm_tools/bubaak-split.yml,sha256=7hmY5CYgIdQJAXYrpcGwR-3tqDFnQNTemFUforrDojQ,1886
|
|
48
50
|
fm_weck/resources/fm_tools/bubaak.yml,sha256=pcJCljIHYmgEMOdeMp8YQxAURO0WlLQ-FpYlYktQHLw,2171
|
|
49
|
-
fm_weck/resources/fm_tools/cadp.yml,sha256=
|
|
51
|
+
fm_weck/resources/fm_tools/cadp.yml,sha256=xkXVGEn4PYMK7ItnaleVLOvbmAU8_9VDlqKjaln5C-k,3352
|
|
50
52
|
fm_weck/resources/fm_tools/cbmc.yml,sha256=QfgJxq7aBhRcuQpR1_26wKvumM0H4JH7u3SQ_oXYyBM,2095
|
|
51
53
|
fm_weck/resources/fm_tools/cetfuzz.yml,sha256=4JULcR2E0rM_RhgaFv9x8J34loMwB1uiluIKKcQninw,1808
|
|
52
54
|
fm_weck/resources/fm_tools/coastal.yml,sha256=ebKhmUoTdlCTw6W4jIllpAJ-CSoFfGvaB004fDg17nY,1650
|
|
@@ -84,7 +86,7 @@ fm_weck/resources/fm_tools/graves-par.yml,sha256=GLoBUwv16gQBzrtg5dota5-wbls8Dd_
|
|
|
84
86
|
fm_weck/resources/fm_tools/graves.yml,sha256=adyp-k_7moQ9pgSeCtEauPOGFIufWoRlQydIRjn0yi0,2269
|
|
85
87
|
fm_weck/resources/fm_tools/gwit.yml,sha256=31k4Js4d3nCqW2uJDQnnfuqJcqfK77C0PuGBhdKMC98,1848
|
|
86
88
|
fm_weck/resources/fm_tools/hornix.yml,sha256=zfJZg09qiueTtyDcZLpWEQDK8GhwfvjfbLI1G_tzUUM,1532
|
|
87
|
-
fm_weck/resources/fm_tools/hybridtiger.yml,sha256=
|
|
89
|
+
fm_weck/resources/fm_tools/hybridtiger.yml,sha256=4ieXZSbm5qdmW3hK5vSfIwOPW6ZI7rSmecE5Ql0xPpI,1814
|
|
88
90
|
fm_weck/resources/fm_tools/infer.yml,sha256=sRIzRShUOhY_Y624Oiwdk5MR-c1ligXJdAgLdt-20qk,1884
|
|
89
91
|
fm_weck/resources/fm_tools/java-ranger.yml,sha256=3T1Vq3v_7dBZl3oIgjZjoZVkPK-OGeFzoaxzpn8h4sE,1989
|
|
90
92
|
fm_weck/resources/fm_tools/jayhorn.yml,sha256=4-R_2eBD_RvbY2f6Wsrp0ObssHdEsJT29aYCngYnozY,1994
|
|
@@ -101,6 +103,7 @@ fm_weck/resources/fm_tools/legion.yml,sha256=ClD8_UEJq8wHIEp9_ifhTNKJuNmzvekaQx1
|
|
|
101
103
|
fm_weck/resources/fm_tools/lf-checker.yml,sha256=lV4-GzzNvzWW505FFtYAUHlSG4gxOn-LL4xT8Tpyvq0,1386
|
|
102
104
|
fm_weck/resources/fm_tools/liv.yml,sha256=t0-Rfs0Ot3VPn7DD_pt3VAbyfpUOP8kdYV3E7uN3qGI,3260
|
|
103
105
|
fm_weck/resources/fm_tools/locksmith.yml,sha256=c3wNvIzQ_PZaYv6Mm15QH-Rm4oTuNzILNJs1fssESYQ,1397
|
|
106
|
+
fm_weck/resources/fm_tools/ltsmin.yml,sha256=WkQ5aP-p1yFP6XfRXJeb5ftcd7o_buvNjEKZRVr6mpM,1644
|
|
104
107
|
fm_weck/resources/fm_tools/metaval++.yml,sha256=KAOcvftDNLvuHzIKaUoebekeOxBE6DVS2Acq2VbL1Dg,2006
|
|
105
108
|
fm_weck/resources/fm_tools/metaval.yml,sha256=aclr5cfGDuCduSrlk9kLOcK8GaUQAzPtDAxIJHgHs44,4668
|
|
106
109
|
fm_weck/resources/fm_tools/mlb.yml,sha256=S_ykc9ThLHBbMSXOgYIvk81r96cEwXbAXI5lPRDxJ4s,1907
|
|
@@ -121,7 +124,7 @@ fm_weck/resources/fm_tools/rizzer.yml,sha256=ZmVL72-mBTzmMY29I3x5HmSCtQTMbJ0vw2U
|
|
|
121
124
|
fm_weck/resources/fm_tools/schema.yml,sha256=_SqJzwFGxM0EuuLgcMF-S_hUtoGxaFKraGTfQDUyILY,22945
|
|
122
125
|
fm_weck/resources/fm_tools/sikraken.yml,sha256=WnLxKyl-4zZ8rvXWqLuuqaC8b9GGQDg-jB3798SeK6M,1067
|
|
123
126
|
fm_weck/resources/fm_tools/spf.yml,sha256=6-7306izjPHKcLZ1veQRmGWOVQcsFJbNyL_55JYVQXA,1692
|
|
124
|
-
fm_weck/resources/fm_tools/sv-sanitizers.yml,sha256=
|
|
127
|
+
fm_weck/resources/fm_tools/sv-sanitizers.yml,sha256=bLG5MoLK95KruF5WCH0Yz2pJPAikPfkNgZRfbP5a2j0,1559
|
|
125
128
|
fm_weck/resources/fm_tools/svf-svc.yml,sha256=gQ12F7_7uis7fVgsTiNdleKBgydi-BOgS4ne-dRyHvk,1591
|
|
126
129
|
fm_weck/resources/fm_tools/swat.yml,sha256=gsvsFBeItbspzdXVVQ8TFWulbdYHzIHSmcKlWkWxGk0,1684
|
|
127
130
|
fm_weck/resources/fm_tools/symbiotic-witch.yml,sha256=6ND-WISFvJEeyEB3Q-Xu71dZrc_ayr1kAnEzfV3TA84,2421
|
|
@@ -147,7 +150,7 @@ fm_weck/resources/fm_tools/wit4java.yml,sha256=ylfze2XbV4zKkVUH57Veqn7G49gW0Byxd
|
|
|
147
150
|
fm_weck/resources/fm_tools/witch.yml,sha256=wwe6lrI2sxGKVZbLeipa38rPhB2pcSUFi9uVngtXGUQ,1795
|
|
148
151
|
fm_weck/resources/fm_tools/witnesslint.yml,sha256=EvMBcm5fx6lgSLRmHSKXSxXIJKZ-BrxLwTXI4GQ6FMs,6812
|
|
149
152
|
fm_weck/resources/fm_tools/witnessmap.yml,sha256=FyZtEloxpWBBjLn9kyqoen2kPjOkH2r4fxAj5gfV8Bg,1692
|
|
150
|
-
fm_weck-1.4.
|
|
151
|
-
fm_weck-1.4.
|
|
152
|
-
fm_weck-1.4.
|
|
153
|
-
fm_weck-1.4.
|
|
153
|
+
fm_weck-1.4.8.dist-info/METADATA,sha256=gGPe1x8nywUCLB85Ldg2MTFg4j7gMU8LRPFarOjIQKo,3269
|
|
154
|
+
fm_weck-1.4.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
155
|
+
fm_weck-1.4.8.dist-info/entry_points.txt,sha256=toWpKCSY1u593MPnI_xW5gnwlnkerP4AvmPQ1s2nPgY,50
|
|
156
|
+
fm_weck-1.4.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|