fm-weck 1.4.5__py3-none-any.whl → 1.4.7__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.
Files changed (127) hide show
  1. fm_weck/__init__.py +1 -1
  2. fm_weck/__main__.py +1 -0
  3. fm_weck/cache_mgr.py +47 -0
  4. fm_weck/cli.py +136 -27
  5. fm_weck/config.py +31 -17
  6. fm_weck/engine.py +110 -32
  7. fm_weck/exceptions.py +1 -0
  8. fm_weck/image_mgr.py +5 -4
  9. fm_weck/resources/{BenchExec-3.25-py3-none-any.whl → BenchExec-3.27-py3-none-any.whl} +0 -0
  10. fm_weck/resources/__init__.py +14 -12
  11. fm_weck/resources/fm_tools/2ls.yml +32 -7
  12. fm_weck/resources/fm_tools/aise.yml +6 -1
  13. fm_weck/resources/fm_tools/aprove.yml +73 -72
  14. fm_weck/resources/fm_tools/blast.yml +58 -0
  15. fm_weck/resources/fm_tools/brick.yml +4 -4
  16. fm_weck/resources/fm_tools/bubaak-split.yml +3 -3
  17. fm_weck/resources/fm_tools/bubaak.yml +4 -4
  18. fm_weck/resources/fm_tools/cadp.yml +95 -0
  19. fm_weck/resources/fm_tools/cbmc.yml +8 -4
  20. fm_weck/resources/fm_tools/cetfuzz.yml +4 -3
  21. fm_weck/resources/fm_tools/coastal.yml +13 -8
  22. fm_weck/resources/fm_tools/concurrentwitness2test.yml +4 -1
  23. fm_weck/resources/fm_tools/cooperace.yml +7 -6
  24. fm_weck/resources/fm_tools/coveriteam-verifier-algo-selection.yml +9 -20
  25. fm_weck/resources/fm_tools/coveriteam-verifier-parallel-portfolio.yml +9 -20
  26. fm_weck/resources/fm_tools/coveritest.yml +27 -6
  27. fm_weck/resources/fm_tools/cpa-bam-bnb.yml +13 -9
  28. fm_weck/resources/fm_tools/cpa-bam-smg.yml +12 -7
  29. fm_weck/resources/fm_tools/cpa-lockator.yml +13 -9
  30. fm_weck/resources/fm_tools/cpa-witness2test.yml +13 -3
  31. fm_weck/resources/fm_tools/cpachecker.yml +3 -3
  32. fm_weck/resources/fm_tools/cpv.yml +2 -0
  33. fm_weck/resources/fm_tools/crux.yml +20 -8
  34. fm_weck/resources/fm_tools/cseq.yml +9 -4
  35. fm_weck/resources/fm_tools/dartagnan.yml +12 -1
  36. fm_weck/resources/fm_tools/deagle.yml +5 -0
  37. fm_weck/resources/fm_tools/divine.yml +20 -11
  38. fm_weck/resources/fm_tools/ebf.yml +8 -5
  39. fm_weck/resources/fm_tools/emergentheta.yml +9 -2
  40. fm_weck/resources/fm_tools/esbmc-incr.yml +13 -10
  41. fm_weck/resources/fm_tools/esbmc-kind.yml +12 -11
  42. fm_weck/resources/fm_tools/fdse.yml +1 -1
  43. fm_weck/resources/fm_tools/fizzer.yml +15 -24
  44. fm_weck/resources/fm_tools/frama-c-sv.yml +3 -2
  45. fm_weck/resources/fm_tools/fshell-witness2test.yml +9 -6
  46. fm_weck/resources/fm_tools/fusebmc-ia.yml +6 -3
  47. fm_weck/resources/fm_tools/fusebmc.yml +26 -6
  48. fm_weck/resources/fm_tools/gazer-theta.yml +10 -7
  49. fm_weck/resources/fm_tools/gdart-llvm.yml +8 -4
  50. fm_weck/resources/fm_tools/gdart.yml +3 -0
  51. fm_weck/resources/fm_tools/goblint.yml +7 -2
  52. fm_weck/resources/fm_tools/graves-par.yml +9 -14
  53. fm_weck/resources/fm_tools/graves.yml +10 -4
  54. fm_weck/resources/fm_tools/gwit.yml +10 -4
  55. fm_weck/resources/fm_tools/hornix.yml +12 -7
  56. fm_weck/resources/fm_tools/hybridtiger.yml +13 -8
  57. fm_weck/resources/fm_tools/infer.yml +12 -7
  58. fm_weck/resources/fm_tools/java-ranger.yml +6 -3
  59. fm_weck/resources/fm_tools/jayhorn.yml +6 -4
  60. fm_weck/resources/fm_tools/jbmc.yml +11 -2
  61. fm_weck/resources/fm_tools/jcwit.yml +9 -5
  62. fm_weck/resources/fm_tools/jdart.yml +12 -7
  63. fm_weck/resources/fm_tools/klee.yml +13 -8
  64. fm_weck/resources/fm_tools/kleef.yml +3 -3
  65. fm_weck/resources/fm_tools/korn.yml +11 -1
  66. fm_weck/resources/fm_tools/lazycseq.yml +10 -7
  67. fm_weck/resources/fm_tools/legion-symcc.yml +6 -12
  68. fm_weck/resources/fm_tools/legion.yml +9 -14
  69. fm_weck/resources/fm_tools/lf-checker.yml +10 -6
  70. fm_weck/resources/fm_tools/liv.yml +19 -4
  71. fm_weck/resources/fm_tools/locksmith.yml +10 -6
  72. fm_weck/resources/fm_tools/ltsmin.yml +40 -0
  73. fm_weck/resources/fm_tools/metaval++.yml +19 -3
  74. fm_weck/resources/fm_tools/metaval.yml +21 -3
  75. fm_weck/resources/fm_tools/mlb.yml +5 -5
  76. fm_weck/resources/fm_tools/mopsa.yml +9 -7
  77. fm_weck/resources/fm_tools/nacpa.yml +7 -1
  78. fm_weck/resources/fm_tools/nitwit.yml +5 -3
  79. fm_weck/resources/fm_tools/owic.yml +4 -3
  80. fm_weck/resources/fm_tools/pesco.yml +10 -4
  81. fm_weck/resources/fm_tools/pichecker.yml +10 -6
  82. fm_weck/resources/fm_tools/pinaka.yml +13 -8
  83. fm_weck/resources/fm_tools/predatorhp.yml +6 -4
  84. fm_weck/resources/fm_tools/prism.yml +67 -0
  85. fm_weck/resources/fm_tools/proton.yml +6 -6
  86. fm_weck/resources/fm_tools/prtest.yml +22 -6
  87. fm_weck/resources/fm_tools/racerf.yml +14 -5
  88. fm_weck/resources/fm_tools/relay-sv.yml +8 -13
  89. fm_weck/resources/fm_tools/rizzer.yml +8 -12
  90. fm_weck/resources/fm_tools/schema.yml +56 -3
  91. fm_weck/resources/fm_tools/sikraken.yml +3 -3
  92. fm_weck/resources/fm_tools/spf.yml +13 -8
  93. fm_weck/resources/fm_tools/sv-sanitizers.yml +3 -1
  94. fm_weck/resources/fm_tools/svf-svc.yml +15 -6
  95. fm_weck/resources/fm_tools/symbiotic-witch.yml +9 -5
  96. fm_weck/resources/fm_tools/symbiotic.yml +6 -2
  97. fm_weck/resources/fm_tools/testcov.yml +8 -8
  98. fm_weck/resources/fm_tools/theta.yml +8 -4
  99. fm_weck/resources/fm_tools/thorn.yml +3 -1
  100. fm_weck/resources/fm_tools/tracerx-wp.yml +11 -2
  101. fm_weck/resources/fm_tools/tracerx.yml +5 -1
  102. fm_weck/resources/fm_tools/uautomizer.yml +7 -12
  103. fm_weck/resources/fm_tools/ugemcutter.yml +8 -2
  104. fm_weck/resources/fm_tools/ukojak.yml +5 -2
  105. fm_weck/resources/fm_tools/ureferee.yml +3 -4
  106. fm_weck/resources/fm_tools/utaipan.yml +8 -2
  107. fm_weck/resources/fm_tools/utestgen.yml +11 -3
  108. fm_weck/resources/fm_tools/vercors.yml +94 -0
  109. fm_weck/resources/fm_tools/veriabs.yml +11 -9
  110. fm_weck/resources/fm_tools/veriabsl.yml +10 -8
  111. fm_weck/resources/fm_tools/verifuzz.yml +16 -266
  112. fm_weck/resources/fm_tools/verioover.yml +10 -6
  113. fm_weck/resources/fm_tools/wasp-c.yml +6 -4
  114. fm_weck/resources/fm_tools/wit4java.yml +20 -13
  115. fm_weck/resources/fm_tools/witch.yml +8 -4
  116. fm_weck/resources/fm_tools/witnesslint.yml +37 -9
  117. fm_weck/resources/fm_tools/witnessmap.yml +16 -1
  118. fm_weck/run_result.py +38 -0
  119. fm_weck/runexec_mode.py +15 -4
  120. fm_weck/serve.py +119 -22
  121. fm_weck/version_listing.py +25 -0
  122. {fm_weck-1.4.5.dist-info → fm_weck-1.4.7.dist-info}/METADATA +5 -3
  123. fm_weck-1.4.7.dist-info/RECORD +154 -0
  124. {fm_weck-1.4.5.dist-info → fm_weck-1.4.7.dist-info}/WHEEL +1 -1
  125. fm_weck-1.4.5.dist-info/RECORD +0 -146
  126. /fm_weck/resources/{BenchExec-3.25-py3-none-any.whl.license → BenchExec-3.27-py3-none-any.whl.license} +0 -0
  127. {fm_weck-1.4.5.dist-info → fm_weck-1.4.7.dist-info}/entry_points.txt +0 -0
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.5"
11
+ __version__ = "1.4.7"
fm_weck/__main__.py CHANGED
@@ -4,6 +4,7 @@
4
4
  # SPDX-FileCopyrightText: 2024 Dirk Beyer <https://www.sosy-lab.org>
5
5
  #
6
6
  # SPDX-License-Identifier: Apache-2.0
7
+ # PYTHON_ARGCOMPLETE_OK
7
8
 
8
9
  import sys
9
10
 
fm_weck/cache_mgr.py ADDED
@@ -0,0 +1,47 @@
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 logging
9
+ import os
10
+ import shutil
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def ask_and_clear(cache_location: str):
16
+ response = (
17
+ input(f"The following cache location will be deleted: {cache_location}\nDo you want to proceed? (Y/n): ")
18
+ .strip()
19
+ .lower()
20
+ )
21
+
22
+ if response == "y":
23
+ clear_cache(cache_location)
24
+ elif response == "n":
25
+ return
26
+ else:
27
+ logger.error(f"Unknown command '{response}'\n")
28
+ ask_and_clear(cache_location)
29
+
30
+
31
+ def clear_cache(cache_location: str):
32
+ if not cache_location:
33
+ logger.error("Cache location is unknown.")
34
+ return
35
+
36
+ if os.path.isdir(cache_location):
37
+ for item in os.listdir(cache_location):
38
+ item_path = os.path.join(cache_location, item)
39
+ try:
40
+ if os.path.isdir(item_path):
41
+ shutil.rmtree(item_path)
42
+ else:
43
+ os.remove(item_path)
44
+ except Exception as e:
45
+ logger.error(f"Error removing {item_path}: {e}")
46
+ else:
47
+ logger.error(f"The path {cache_location} is not a valid directory.")
fm_weck/cli.py CHANGED
@@ -5,6 +5,7 @@
5
5
  #
6
6
  # SPDX-License-Identifier: Apache-2.0
7
7
 
8
+
8
9
  import argparse
9
10
  import logging
10
11
  import os
@@ -12,26 +13,31 @@ from argparse import Namespace
12
13
  from dataclasses import dataclass
13
14
  from functools import cache
14
15
  from pathlib import Path
15
- from typing import Any, Callable, Optional, Tuple, Union
16
+ from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple, Union
16
17
 
17
18
  try:
18
19
  from fm_tools.benchexec_helper import DataModel
19
20
  except ImportError:
20
21
  from enum import Enum
21
22
 
22
- class DataModel(Enum):
23
- """
24
- Enum representing the data model of the tool.
25
- """
23
+ if not TYPE_CHECKING:
26
24
 
27
- LP64 = "LP64"
28
- ILP32 = "ILP32"
25
+ class DataModel(Enum):
26
+ """
27
+ Enum representing the data model of the tool.
28
+ """
29
29
 
30
- def __str__(self):
31
- return self.value
30
+ LP64 = "LP64"
31
+ ILP32 = "ILP32"
32
32
 
33
+ def __str__(self):
34
+ return self.value
35
+
36
+
37
+ import contextlib
33
38
 
34
39
  from fm_weck import Config
40
+ from fm_weck.cache_mgr import ask_and_clear, clear_cache
35
41
  from fm_weck.config import _SEARCH_ORDER
36
42
  from fm_weck.resources import iter_fm_data, iter_properties
37
43
 
@@ -61,7 +67,49 @@ class ToolQualifier:
61
67
  return
62
68
 
63
69
 
64
- def add_tool_arg(parser, nargs="?"):
70
+ class ShellCompletion:
71
+ @staticmethod
72
+ def properties_completer(prefix, parsed_args, **kwargs):
73
+ return list_known_properties()
74
+
75
+ @staticmethod
76
+ def versions_completer(prefix, parsed_args, **kwargs):
77
+ return list_known_tools()
78
+
79
+ @staticmethod
80
+ def tool_completer(prefix, parsed_args, **kwargs):
81
+ try:
82
+ import yaml
83
+ except ImportError:
84
+ logger.error("PyYAML is not installed. Cannot complete tool names.")
85
+ return []
86
+
87
+ tools_and_versions: dict[str, list] = {}
88
+
89
+ for tool_path in iter_fm_data():
90
+ with open(tool_path) as stream:
91
+ tool_data = yaml.safe_load(stream)
92
+
93
+ try:
94
+ versions = [version_data["version"] for version_data in tool_data["versions"]]
95
+ except KeyError:
96
+ continue
97
+ tools_and_versions[tool_data["name"]] = versions
98
+
99
+ tools_and_versions_list = [
100
+ f"{tool.lower()}:{version}" for tool, versions in tools_and_versions.items() for version in versions
101
+ ]
102
+
103
+ selected_tools = set([tool.split(":")[0] for tool in tools_and_versions_list if tool.startswith(prefix)])
104
+ if len(selected_tools) == 1:
105
+ # If exactly one tool has been selected, suggest versions
106
+ return [tool for tool in tools_and_versions_list if tool.startswith(prefix)]
107
+
108
+ # If no tool has been selected, suggest tool names
109
+ return selected_tools
110
+
111
+
112
+ def add_tool_arg(parser, nargs: str | None = "?"):
65
113
  parser.add_argument(
66
114
  "TOOL",
67
115
  help="The tool to obtain the container from. Can be the form <tool>:<version>. "
@@ -69,14 +117,15 @@ def add_tool_arg(parser, nargs="?"):
69
117
  "the path to a fm-tools yaml file.",
70
118
  type=ToolQualifier,
71
119
  nargs=nargs,
72
- )
120
+ ).completer = ShellCompletion.tool_completer
73
121
 
74
122
 
75
123
  def add_shared_args_for_run_modes(parser):
76
124
  parser.add_argument(
77
- "--skip-download",
125
+ "--offline",
78
126
  action="store_true",
79
- help="Do not download the fm-tool, even if it is not available in the cache.",
127
+ help="Run the tools offline. The offline mode assumes that both the tool and its info-module are located "
128
+ "at the location specified by the config's 'cache_location' field.",
80
129
  )
81
130
 
82
131
  add_tool_arg(parser, nargs=None)
@@ -149,7 +198,7 @@ def parse(raw_args: list[str]) -> Tuple[Callable[[], None], Namespace]:
149
198
  ),
150
199
  required=False,
151
200
  default=None,
152
- )
201
+ ).completer = ShellCompletion.properties_completer # type: ignore[assignment]
153
202
 
154
203
  run.add_argument(
155
204
  "-d",
@@ -178,7 +227,7 @@ def parse(raw_args: list[str]) -> Tuple[Callable[[], None], Namespace]:
178
227
  "argument_list",
179
228
  metavar="args",
180
229
  nargs="*",
181
- help="Additional arguments for the fm-tool." " To add them, separate them with '--' from the files.",
230
+ help="Additional arguments for the fm-tool. To add them, separate them with '--' from the files.",
182
231
  )
183
232
  run.set_defaults(main=main_run)
184
233
 
@@ -248,6 +297,32 @@ def parse(raw_args: list[str]) -> Tuple[Callable[[], None], Namespace]:
248
297
  runexec.add_argument("argument_list", metavar="args", nargs="*", help="Arguments for runexec.")
249
298
  runexec.set_defaults(main=main_runexec)
250
299
 
300
+ clear_cache = subparsers.add_parser("clear-cache", help="Clear the cache directory.")
301
+ clear_cache.add_argument(
302
+ "--yes",
303
+ "-y",
304
+ "-Y",
305
+ action="store_true",
306
+ help="Add automatic approval for clearing the cache.",
307
+ required=False,
308
+ default=False,
309
+ )
310
+ clear_cache.set_defaults(main=main_clear_cache)
311
+
312
+ versions = subparsers.add_parser("versions", help="Show the versions of the chosen tool(s).")
313
+ versions.add_argument(
314
+ "TOOL",
315
+ help="The tool(s) for which to print the versions.",
316
+ type=ToolQualifier,
317
+ nargs="+",
318
+ ).completer = ShellCompletion.versions_completer # type: ignore[assignment]
319
+ versions.set_defaults(main=main_versions)
320
+
321
+ with contextlib.suppress(ImportError):
322
+ import argcomplete
323
+
324
+ argcomplete.autocomplete(parser)
325
+
251
326
  def help_callback():
252
327
  parser.print_help()
253
328
 
@@ -295,7 +370,7 @@ def resolve_tool(tool: ToolQualifier) -> Path:
295
370
  if (as_path := Path(tool_name)).exists() and as_path.is_file():
296
371
  return as_path
297
372
 
298
- return fm_tools_choice_map()[tool_name]
373
+ return fm_tools_choice_map()[str(tool_name)]
299
374
 
300
375
 
301
376
  def resolve_property(prop_name: str) -> Path:
@@ -333,22 +408,25 @@ def main_run(args: argparse.Namespace):
333
408
  logger.error("Unknown property %s", args.property)
334
409
  return 1
335
410
 
336
- return run_guided(
411
+ result = run_guided(
337
412
  fm_tool=fm_data,
338
413
  version=args.TOOL.version,
339
414
  configuration=Config(),
340
415
  prop=property_path,
416
+ witness=Path(args.witness) if args.witness else None,
341
417
  program_files=args.files,
342
418
  additional_args=args.argument_list,
343
419
  data_model=args.data_model,
344
- skip_download=args.skip_download,
420
+ offline_mode=args.offline,
345
421
  )
346
422
 
423
+ return result.exit_code
424
+
347
425
 
348
426
  def main_runexec(args: argparse.Namespace):
349
427
  from .runexec_mode import run_runexec
350
428
 
351
- return run_runexec(
429
+ result = run_runexec(
352
430
  benchexec_package=args.benchexec_package,
353
431
  use_image=args.use_image,
354
432
  configuration=Config(),
@@ -356,6 +434,8 @@ def main_runexec(args: argparse.Namespace):
356
434
  command=args.argument_list,
357
435
  )
358
436
 
437
+ return result.exit_code
438
+
359
439
 
360
440
  def main_manual(args: argparse.Namespace):
361
441
  from .serve import run_manual
@@ -369,20 +449,19 @@ def main_manual(args: argparse.Namespace):
369
449
  logger.error("Unknown tool %s", args.TOOL)
370
450
  return 1
371
451
 
372
- return run_manual(
452
+ result = run_manual(
373
453
  fm_tool=fm_data,
374
454
  version=args.TOOL.version,
375
455
  configuration=Config(),
376
456
  command=args.argument_list,
377
- skip_download=args.skip_download,
457
+ offline_mode=args.offline,
378
458
  )
379
459
 
460
+ return result.exit_code
380
461
 
381
- def main_install(args: argparse.Namespace):
382
- from .serve import setup_fm_tool
383
462
 
384
- if args.destination:
385
- Config()._config["defaults"]["cache_location"] = args.destination.resolve()
463
+ def main_install(args: argparse.Namespace):
464
+ from .serve import install_fm_tool
386
465
 
387
466
  for tool in args.TOOL:
388
467
  try:
@@ -391,10 +470,11 @@ def main_install(args: argparse.Namespace):
391
470
  logger.error("Unknown tool %s. Skipping installation...", tool)
392
471
  continue
393
472
 
394
- setup_fm_tool(
473
+ install_fm_tool(
395
474
  fm_tool=fm_data,
396
475
  version=tool.version,
397
476
  configuration=Config(),
477
+ install_path=args.destination
398
478
  )
399
479
 
400
480
  return 0
@@ -413,7 +493,35 @@ def main_shell(args: argparse.Namespace):
413
493
  return 1
414
494
  engine = Engine.from_config(fm_data, args.TOOL.version, Config())
415
495
  engine.interactive = True
416
- return engine.run(args.entry)
496
+ result = engine.run(args.entry)
497
+ return result.exit_code
498
+
499
+
500
+ def main_clear_cache(args: argparse.Namespace):
501
+ if args.yes:
502
+ clear_cache(Config().get("defaults", {}).get("cache_location")) # type: ignore
503
+ else:
504
+ ask_and_clear(Config().get("defaults", {}).get("cache_location")) # type: ignore
505
+ return
506
+
507
+
508
+ def main_versions(args: argparse.Namespace):
509
+ from .version_listing import VersionListing
510
+
511
+ tools = args.TOOL
512
+ tool_paths = []
513
+ if not args.TOOL:
514
+ logger.error("No fm-tool given. Aborting...")
515
+ return 1
516
+
517
+ for tool in tools:
518
+ try:
519
+ tool_paths.append(resolve_tool(tool))
520
+ except KeyError:
521
+ logger.error("Unknown tool %s", tool)
522
+ return 1
523
+
524
+ VersionListing(tool_paths).print_versions()
417
525
 
418
526
 
419
527
  def log_no_image_error(tool, config):
@@ -462,6 +570,7 @@ to your .fm-weck file.
462
570
  def cli(raw_args: list[str]):
463
571
  help_callback, args = parse(raw_args)
464
572
  configuration = Config().load(args.config)
573
+
465
574
  set_log_options(args.loglevel, args.logfile, configuration)
466
575
  if args.dry_run:
467
576
  Config().set_dry_run(True)
fm_weck/config.py CHANGED
@@ -13,21 +13,29 @@ import stat
13
13
  import sys
14
14
  from functools import cache
15
15
  from pathlib import Path
16
- from typing import Any, Callable, Iterable, Optional, Tuple, TypeVar
16
+ from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, Tuple, TypeVar
17
+
18
+ from werkzeug.utils import secure_filename
17
19
 
18
20
  try:
19
- from fm_tools.fmdata import FmData
21
+ from fm_tools.fmtool import FmTool
22
+ from fm_tools.fmtoolversion import FmToolVersion
20
23
  except ImportError:
24
+ if not TYPE_CHECKING:
25
+
26
+ class FmTool:
27
+ def __init__(self, data):
28
+ raise ImportError("fm_tools is not imported.")
21
29
 
22
- class FmData:
23
- def __init__(self, data, version):
24
- raise ImportError("fm_tools is not imported.")
30
+ class FmToolVersion:
31
+ def __init__(self, data: FmTool, version: str):
32
+ raise ImportError("fm_tools is not imported.")
25
33
 
26
- def get_actor_name(self):
27
- raise ImportError("fm_tools is not imported.")
34
+ def get_actor_name(self):
35
+ raise ImportError("fm_tools is not imported.")
28
36
 
29
- def get_version(self):
30
- raise ImportError("fm_tools is not imported.")
37
+ def get_version(self):
38
+ raise ImportError("fm_tools is not imported.")
31
39
 
32
40
 
33
41
  from fm_weck.resources import RUN_WITH_OVERLAY, RUNEXEC_SCRIPT
@@ -35,7 +43,7 @@ from fm_weck.resources import RUN_WITH_OVERLAY, RUNEXEC_SCRIPT
35
43
  try:
36
44
  import tomllib as toml
37
45
  except ImportError:
38
- import tomli as toml
46
+ import tomli as toml # type: ignore
39
47
 
40
48
  _SEARCH_ORDER: tuple[Path, ...] = (
41
49
  Path.cwd() / ".fm-weck",
@@ -121,7 +129,7 @@ class Config(object):
121
129
  return self.defaults().get(key, None)
122
130
 
123
131
  @staticmethod
124
- def _handle_relative_paths(fn: Callable[[Any], Path]) -> Callable[[Any], Path]:
132
+ def _handle_relative_paths(fn: Callable[..., Path]) -> Callable[..., Path]:
125
133
  def wrapper(self, *args, **kwargs) -> Path:
126
134
  """Makes sure relative Paths in the config are relative to the config file."""
127
135
 
@@ -159,9 +167,15 @@ class Config(object):
159
167
  def get_checksum_db(self) -> Path:
160
168
  return self.cache_location / ".checksums.dbm"
161
169
 
162
- def get_shelve_space_for(self, fm_data: FmData) -> Path:
170
+ def get_shelve_space_for(self, fm_data: FmToolVersion) -> Path:
163
171
  shelve = self.cache_location
164
- tool_name = fm_data.get_actor_name() # safe to use in filesystem
172
+ # Remove leading http:// or https:// from the raw archive location
173
+ raw_location = fm_data.get_archive_location().raw
174
+ if raw_location.startswith("http://"):
175
+ raw_location = raw_location[len("http://") :]
176
+ elif raw_location.startswith("https://"):
177
+ raw_location = raw_location[len("https://") :]
178
+ tool_name = secure_filename(raw_location)
165
179
  return shelve / tool_name
166
180
 
167
181
  def get_shelve_path_for_property(self, path: Path) -> Path:
@@ -179,10 +193,10 @@ class Config(object):
179
193
  def _system_is_not_posix():
180
194
  return not (sys.platform.startswith("linux") or sys.platform == "darwin")
181
195
 
182
- def make_runexec_script_available(self) -> Path:
196
+ def make_runexec_script_available(self) -> Path | None:
183
197
  return self.make_script_available(RUNEXEC_SCRIPT)
184
198
 
185
- def make_script_available(self, target_name: str = RUN_WITH_OVERLAY) -> Path:
199
+ def make_script_available(self, target_name: str = RUN_WITH_OVERLAY) -> Path | None:
186
200
  script_dir = self.cache_location / ".scripts"
187
201
  target = script_dir / target_name
188
202
 
@@ -224,7 +238,7 @@ class Config(object):
224
238
 
225
239
 
226
240
  @cache
227
- def parse_fm_data(fm_data: Path, version: Optional[str]) -> FmData:
241
+ def parse_fm_data(fm_data: Path, version: str | None) -> FmToolVersion:
228
242
  import yaml
229
243
 
230
244
  if not fm_data.exists() or not fm_data.is_file():
@@ -233,4 +247,4 @@ def parse_fm_data(fm_data: Path, version: Optional[str]) -> FmData:
233
247
  with fm_data.open("rb") as f:
234
248
  data = yaml.safe_load(f)
235
249
 
236
- return FmData(data, version)
250
+ return FmToolVersion(data, version)