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 CHANGED
@@ -8,4 +8,4 @@
8
8
  from .config import Config # noqa: F401
9
9
  from .image_mgr import ImageMgr # noqa: F401
10
10
 
11
- __version__ = "1.4.6"
11
+ __version__ = "1.4.8"
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
- class DataModel(Enum):
24
- """
25
- Enum representing the data model of the tool.
26
- """
23
+ if not TYPE_CHECKING:
27
24
 
28
- LP64 = "LP64"
29
- ILP32 = "ILP32"
25
+ class DataModel(Enum):
26
+ """
27
+ Enum representing the data model of the tool.
28
+ """
30
29
 
31
- def __str__(self):
32
- return self.value
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 setup_fm_tool
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
- setup_fm_tool(
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.fmdata import FmData
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
- class FmData:
23
- def __init__(self, data, version):
24
- raise ImportError("fm_tools is not imported.")
29
+ class FmToolVersion:
30
+ def __init__(self, data: FmTool, version: str):
31
+ raise ImportError("fm_tools is not imported.")
25
32
 
26
- def get_actor_name(self):
27
- raise ImportError("fm_tools is not imported.")
33
+ def get_actor_name(self):
34
+ raise ImportError("fm_tools is not imported.")
28
35
 
29
- def get_version(self):
30
- raise ImportError("fm_tools is not imported.")
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[[Any], Path]) -> Callable[[Any], Path]:
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: FmData) -> Path:
171
+ def get_shelve_space_for(self, fm_data: FmToolVersion) -> Path:
163
172
  shelve = self.cache_location
164
- tool_name = fm_data.get_actor_name() # safe to use in filesystem
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
- shutil.copy(source_path, target)
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
- shutil.copy(source_path, target)
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: Optional[str]) -> FmData:
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 FmData(data, version)
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.fmdata import FmData, FmImageConfig
24
+ from fm_tools.fmtoolversion import (
25
+ FmImageConfig, # type: ignore
26
+ FmToolVersion, # type: ignore
27
+ )
24
28
  except ImportError:
25
- # Mock the FmData and FmImageConfig class for type checking
26
- class FmData:
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
- class FmImageConfig:
31
- pass
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()}:{CWD_MOUNT_LOCATION}",
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: tuple[str, ...]) -> list[str]:
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: tuple[str, ...]) -> tuple[str, ...]:
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: FmData, config: Config):
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: list[str]):
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, io.StringIO())
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.fmdata import FmImageConfig
13
+ from fm_tools.fmtoolversion import FmImageConfig
14
14
  except ImportError:
15
+ if not TYPE_CHECKING:
15
16
 
16
- class FmImageConfig:
17
- def __init__(self, full_images, base_images, required_packages):
18
- raise ImportError("fm_tools is not imported.")
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
@@ -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
- resource_dir = Path(__file__).parent
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
- FM_DATA_LOCATION = resource_dir / "fm_tools"
17
- PROPERTY_LOCATION = resource_dir / "properties"
15
+
18
16
  RUN_WITH_OVERLAY = "run_with_overlay.sh"
19
- BENCHEXEC_WHL = resource_dir / "BenchExec-3.27-py3-none-any.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 FM_DATA_LOCATION.iterdir():
25
- if fm_data.is_file() and (fm_data.name.endswith(".yml") or fm_data.name.endswith(".yaml")):
26
- yield fm_data
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 PROPERTY_LOCATION.iterdir():
31
- if prop.is_file():
32
- yield prop
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
@@ -17,7 +17,7 @@ fmtools_entry_maintainers:
17
17
  - dbeyer
18
18
 
19
19
  maintainers:
20
- - orcid: 0000-0000-0000-0000
20
+ - orcid: 0009-0000-5304-8081
21
21
  name: Hubert Garavel
22
22
  institution: INRIA
23
23
  country: France
@@ -2,7 +2,7 @@ id: hybridtiger
2
2
  name: HybridTiger
3
3
  input_languages:
4
4
  - C
5
- project_url: https://www.es.tu-darmstadt.de/es/team/sebastian-ruland/testcomp20
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: sv-sanitizers
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 FmData
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: FmData) -> str:
37
- tool = get_tool_info(tool)
38
- return self.determine_result(tool)
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
- shutil.copyfile(BENCHEXEC_WHL, benchexec_package)
46
- engine.env["PYTHONPATH"] = f"{CACHE_MOUNT_LOCATION}/.lib/benchexec.whl"
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, FmData],
66
+ fm_tool: Union[Path, FmToolVersion],
64
67
  version: Optional[str],
65
68
  configuration: Config,
66
69
  offline_mode: bool = False,
67
- ) -> Tuple[FmData, Path]:
68
- # Don't explicitly disallow non-FmData here; Pythonic Users might want to exchange the FmData object
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.archive_location.checksum()
79
- skip_download = check_cache_entry(shelve_space, checksum, configuration)
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
- fm_data.download_and_install_into(shelve_space)
83
- checksum = fm_data.archive_location.checksum()
84
- update_checksum(shelve_space, checksum, configuration)
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, FmData],
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
- shutil.copyfile(source_property_path, property_path)
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, None
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, FmData],
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.6
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.3.3
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=DV5C4Bm7M37AnsKAzMOPeyxn9kt_BMFQM69KKWhiOos,351
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=3XfzaMqh2Bl_Es3n_7Kv80LScJmQdI6et0tcEI_Z2W4,17069
5
- fm_weck/config.py,sha256=AAp5qPZfx2Gh-43jySzfHzHOEmbduHJalA7guV9SIzI,7340
6
- fm_weck/engine.py,sha256=PfcjtN-szlpinSFtv1uE0zs_w__GvR8BSlqbz0u6lII,16742
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/image_mgr.py,sha256=f1RlmQPBiMrsqPyH1Rtfb8WMp2YBq-7z-Rto_kCfS7U,1861
9
- fm_weck/run_result.py,sha256=b7t_g6qN2GFGPW6cCoLX_BWMSQ_Q-1cca3Fw3lmIB9M,1207
10
- fm_weck/runexec_mode.py,sha256=vV90CfxwvoR1ApwL4BXq2q_7f8wjSFsS5XQyugDFWMQ,1909
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=JaaOi2ipJbPiATHVcpjXOoE8XyuoWXSFlrKtdfkqjU8,7342
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=XRQiBOncbVvTqZkJ6vSuvu5SUYd3zEV-dGHZTmmLsEM,1023
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=nJsmaBl4qwLsP_ZKujCY7Gbjc0qWV6FbmErpPPw1f2c,3352
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=Zh9z-CQwB0tjF94_s4Yw9wnTblYAvumiwPk30-qfe_I,1833
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=tisvbuYRXutJuk-rsqzRJ-3NSMBL29hHjym3eqE4MAI,1431
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.6.dist-info/METADATA,sha256=lZPOGSSc4OeZvJ0m5VGGbo25DdX0twG-9rCEfYGE2A0,2822
151
- fm_weck-1.4.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
152
- fm_weck-1.4.6.dist-info/entry_points.txt,sha256=toWpKCSY1u593MPnI_xW5gnwlnkerP4AvmPQ1s2nPgY,50
153
- fm_weck-1.4.6.dist-info/RECORD,,
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,,