fm-weck 1.1.1__py3-none-any.whl → 1.1.2__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 +12 -9
- fm_weck/config.py +36 -4
- fm_weck/engine.py +204 -128
- fm_weck/image_mgr.py +11 -3
- fm_weck/resources/Containerfile +3 -2
- fm_weck/resources/fm_tools/bubaak-split.yml +8 -1
- fm_weck/resources/fm_tools/cbmc.yml +12 -1
- fm_weck/resources/fm_tools/coastal.yml +8 -3
- fm_weck/resources/fm_tools/concurrentwitness2test.yml +8 -1
- fm_weck/resources/fm_tools/coveriteam-verifier-algo-selection.yml +11 -4
- fm_weck/resources/fm_tools/coveriteam-verifier-parallel-portfolio.yml +11 -4
- fm_weck/resources/fm_tools/cpa-witness2test.yml +4 -4
- fm_weck/resources/fm_tools/cpachecker.yml +3 -8
- fm_weck/resources/fm_tools/cpv.yml +18 -7
- fm_weck/resources/fm_tools/key.yml +69 -0
- fm_weck/resources/fm_tools/predatorhp.yml +17 -1
- fm_weck/resources/fm_tools/proton.yml +8 -1
- fm_weck/resources/fm_tools/schema.yml +60 -9
- fm_weck/resources/fm_tools/symbiotic-witch.yml +12 -1
- fm_weck/resources/fm_tools/symbiotic.yml +41 -1
- fm_weck/resources/fm_tools/testcov.yml +1 -1
- fm_weck/resources/fm_tools/witch.yml +8 -1
- fm_weck/resources/run_with_overlay.sh +5 -1
- fm_weck/serve.py +22 -21
- {fm_weck-1.1.1.dist-info → fm_weck-1.1.2.dist-info}/METADATA +3 -2
- {fm_weck-1.1.1.dist-info → fm_weck-1.1.2.dist-info}/RECORD +29 -28
- {fm_weck-1.1.1.dist-info → fm_weck-1.1.2.dist-info}/WHEEL +0 -0
- {fm_weck-1.1.1.dist-info → fm_weck-1.1.2.dist-info}/entry_points.txt +0 -0
fm_weck/__init__.py
CHANGED
fm_weck/cli.py
CHANGED
|
@@ -24,6 +24,8 @@ from . import __version__
|
|
|
24
24
|
from .engine import Engine, NoImageError
|
|
25
25
|
from .serve import run_guided, run_manual, setup_fm_tool
|
|
26
26
|
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
27
29
|
|
|
28
30
|
@dataclass
|
|
29
31
|
class ToolQualifier:
|
|
@@ -232,22 +234,23 @@ def set_log_level(loglevel: Optional[str], config: dict[str, Any]):
|
|
|
232
234
|
level = "WARNING"
|
|
233
235
|
level = loglevel.upper() if loglevel else config.get("logging", {}).get("level", level)
|
|
234
236
|
logging.basicConfig(level=level)
|
|
237
|
+
logging.getLogger("httpcore").setLevel("WARNING")
|
|
235
238
|
|
|
236
239
|
|
|
237
240
|
def main_run(args: argparse.Namespace):
|
|
238
241
|
if not args.TOOL:
|
|
239
|
-
|
|
242
|
+
logger.error("No fm-tool given. Aborting...")
|
|
240
243
|
return 1
|
|
241
244
|
try:
|
|
242
245
|
fm_data = resolve_tool(args.TOOL)
|
|
243
246
|
except KeyError:
|
|
244
|
-
|
|
247
|
+
logger.error("Unknown tool %s", args.TOOL)
|
|
245
248
|
return 1
|
|
246
249
|
|
|
247
250
|
try:
|
|
248
251
|
property_path = resolve_property(args.property) if args.property else None
|
|
249
252
|
except KeyError:
|
|
250
|
-
|
|
253
|
+
logger.error("Unknown property %s", args.property)
|
|
251
254
|
return 1
|
|
252
255
|
|
|
253
256
|
run_guided(
|
|
@@ -264,12 +267,12 @@ def main_run(args: argparse.Namespace):
|
|
|
264
267
|
|
|
265
268
|
def main_manual(args: argparse.Namespace):
|
|
266
269
|
if not args.TOOL:
|
|
267
|
-
|
|
270
|
+
logger.error("No fm-tool given. Aborting...")
|
|
268
271
|
return 1
|
|
269
272
|
try:
|
|
270
273
|
fm_data = resolve_tool(args.TOOL)
|
|
271
274
|
except KeyError:
|
|
272
|
-
|
|
275
|
+
logger.error("Unknown tool %s", args.TOOL)
|
|
273
276
|
return 1
|
|
274
277
|
|
|
275
278
|
run_manual(
|
|
@@ -286,7 +289,7 @@ def main_install(args: argparse.Namespace):
|
|
|
286
289
|
try:
|
|
287
290
|
fm_data = resolve_tool(tool)
|
|
288
291
|
except KeyError:
|
|
289
|
-
|
|
292
|
+
logger.error("Unknown tool %s. Skipping installation...", tool)
|
|
290
293
|
continue
|
|
291
294
|
|
|
292
295
|
setup_fm_tool(
|
|
@@ -303,7 +306,7 @@ def main_shell(args: argparse.Namespace):
|
|
|
303
306
|
try:
|
|
304
307
|
fm_data = resolve_tool(args.TOOL)
|
|
305
308
|
except KeyError:
|
|
306
|
-
|
|
309
|
+
logger.error("Unknown tool %s", args.fm_data)
|
|
307
310
|
return 1
|
|
308
311
|
engine = Engine.from_config(fm_data, args.TOOL.version, Config())
|
|
309
312
|
engine.interactive = True
|
|
@@ -336,7 +339,7 @@ printf '[defaults]\\nimage = "<your_image>"' > .weck
|
|
|
336
339
|
|
|
337
340
|
Replace <your_image> with the image you want to use.
|
|
338
341
|
"""
|
|
339
|
-
|
|
342
|
+
logger.error(text)
|
|
340
343
|
return
|
|
341
344
|
|
|
342
345
|
text = """
|
|
@@ -350,7 +353,7 @@ image = "your_image"
|
|
|
350
353
|
to your .weck file.
|
|
351
354
|
"""
|
|
352
355
|
|
|
353
|
-
|
|
356
|
+
logger.error(text, tool, config)
|
|
354
357
|
|
|
355
358
|
|
|
356
359
|
def cli(raw_args: list[str]):
|
fm_weck/config.py
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
# SPDX-FileCopyrightText: 2024 Dirk Beyer <https://www.sosy-lab.org>
|
|
5
5
|
#
|
|
6
6
|
# SPDX-License-Identifier: Apache-2.0
|
|
7
|
+
|
|
7
8
|
import os
|
|
8
9
|
import shutil
|
|
9
10
|
from functools import cache
|
|
10
11
|
from pathlib import Path
|
|
11
|
-
from typing import Any, Optional
|
|
12
|
+
from typing import Any, Callable, Iterable, Optional, Tuple, TypeVar
|
|
12
13
|
|
|
13
14
|
import yaml
|
|
14
15
|
from fm_tools.fmdata import FmData
|
|
@@ -31,8 +32,11 @@ BASE_CONFIG = """
|
|
|
31
32
|
level = "INFO"
|
|
32
33
|
|
|
33
34
|
[defaults]
|
|
35
|
+
engine = "podman"
|
|
34
36
|
"""
|
|
35
37
|
|
|
38
|
+
_T = TypeVar("_T")
|
|
39
|
+
|
|
36
40
|
|
|
37
41
|
class Config(object):
|
|
38
42
|
"""
|
|
@@ -41,6 +45,7 @@ class Config(object):
|
|
|
41
45
|
|
|
42
46
|
_instance = None
|
|
43
47
|
_config_source = None
|
|
48
|
+
_source_path = None
|
|
44
49
|
|
|
45
50
|
def __new__(cls):
|
|
46
51
|
if cls._instance is None:
|
|
@@ -58,6 +63,7 @@ class Config(object):
|
|
|
58
63
|
|
|
59
64
|
with config.open("rb") as f:
|
|
60
65
|
self._config = toml.load(f)
|
|
66
|
+
self._config_source = config.resolve()
|
|
61
67
|
return self._config
|
|
62
68
|
|
|
63
69
|
for path in _SEARCH_ORDER:
|
|
@@ -76,7 +82,7 @@ class Config(object):
|
|
|
76
82
|
def __getitem__(self, key: str) -> Any:
|
|
77
83
|
return self._config[key]
|
|
78
84
|
|
|
79
|
-
def get(self, key: str, default:
|
|
85
|
+
def get(self, key: str, default: _T = None) -> _T:
|
|
80
86
|
if self._config is not None:
|
|
81
87
|
return self._config.get(key, default)
|
|
82
88
|
|
|
@@ -88,7 +94,25 @@ class Config(object):
|
|
|
88
94
|
def from_defaults_or_none(self, key: str) -> Any:
|
|
89
95
|
return self.defaults().get(key, None)
|
|
90
96
|
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _handle_relative_paths(fn: Callable[[Any], Path]) -> Callable[[Any], Path]:
|
|
99
|
+
def wrapper(self, *args, **kwargs) -> Path:
|
|
100
|
+
"""Makes sure relative Paths in the config are relative to the config file."""
|
|
101
|
+
|
|
102
|
+
path = fn(self, *args, **kwargs)
|
|
103
|
+
|
|
104
|
+
if not self._config_source:
|
|
105
|
+
return path
|
|
106
|
+
|
|
107
|
+
if path.is_absolute():
|
|
108
|
+
return path
|
|
109
|
+
|
|
110
|
+
return (self._config_source.parent / path).resolve()
|
|
111
|
+
|
|
112
|
+
return wrapper
|
|
113
|
+
|
|
91
114
|
@property
|
|
115
|
+
@_handle_relative_paths
|
|
92
116
|
def cache_location(self) -> Path:
|
|
93
117
|
cache = Path.home() / ".cache" / "weck_cache"
|
|
94
118
|
xdg_cache_home = os.environ.get("XDG_CACHE_HOME")
|
|
@@ -96,8 +120,16 @@ class Config(object):
|
|
|
96
120
|
if xdg_cache_home:
|
|
97
121
|
cache = Path(xdg_cache_home) / "weck_cache"
|
|
98
122
|
|
|
99
|
-
return Path(self.defaults().get("cache_location", cache))
|
|
100
|
-
|
|
123
|
+
return Path(self.defaults().get("cache_location", cache.resolve()))
|
|
124
|
+
|
|
125
|
+
@_handle_relative_paths
|
|
126
|
+
def as_absolute_path(self, path: Path) -> Path:
|
|
127
|
+
return path
|
|
128
|
+
|
|
129
|
+
def mounts(self) -> Iterable[Tuple[Path, Path]]:
|
|
130
|
+
for local, container in self.get("mount", {}).items():
|
|
131
|
+
yield self.as_absolute_path(Path(local)), Path(container)
|
|
132
|
+
|
|
101
133
|
def get_checksum_db(self) -> Path:
|
|
102
134
|
return self.cache_location / ".checksums.dbm"
|
|
103
135
|
|
fm_weck/engine.py
CHANGED
|
@@ -7,90 +7,217 @@
|
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
9
|
import subprocess
|
|
10
|
-
import uuid
|
|
11
10
|
from abc import ABC, abstractmethod
|
|
12
|
-
from functools import singledispatchmethod
|
|
11
|
+
from functools import cached_property, singledispatchmethod
|
|
13
12
|
from pathlib import Path
|
|
14
|
-
from typing import Optional, Union
|
|
13
|
+
from typing import List, Optional, Union
|
|
15
14
|
|
|
16
15
|
from fm_tools.fmdata import FmData, FmImageConfig
|
|
17
16
|
|
|
18
17
|
from fm_weck.config import Config, parse_fm_data
|
|
19
18
|
from fm_weck.image_mgr import ImageMgr
|
|
20
19
|
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
21
22
|
|
|
22
23
|
class NoImageError(Exception):
|
|
23
24
|
pass
|
|
24
25
|
|
|
26
|
+
|
|
25
27
|
class Engine(ABC):
|
|
26
28
|
interactive: bool = False
|
|
27
29
|
add_benchexec_capabilities: bool = False
|
|
28
30
|
image: Optional[str] = None
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
def __init__(self, image: Union[str, FmImageConfig]):
|
|
33
|
+
self.image = self._initialize_image(image)
|
|
34
|
+
self.extra_args = {}
|
|
35
|
+
self._engine = None
|
|
36
|
+
|
|
37
|
+
def get_workdir(self):
|
|
38
|
+
return Path("/home/cwd")
|
|
39
|
+
|
|
40
|
+
def mount(self, source: str, target: str):
|
|
41
|
+
self.extra_args["mounts"] = self.extra_args.get("mounts", []) + [
|
|
42
|
+
"-v",
|
|
43
|
+
f"{source}:{target}",
|
|
44
|
+
]
|
|
33
45
|
|
|
34
46
|
@abstractmethod
|
|
35
|
-
def
|
|
47
|
+
def benchexec_capabilities(self):
|
|
36
48
|
raise NotImplementedError
|
|
37
49
|
|
|
50
|
+
def base_command(self):
|
|
51
|
+
return [self._engine, "run"]
|
|
52
|
+
|
|
53
|
+
def interactive_command(self):
|
|
54
|
+
return ["-it"]
|
|
55
|
+
|
|
56
|
+
def setup_command(self):
|
|
57
|
+
return [
|
|
58
|
+
"--entrypoint",
|
|
59
|
+
'[""]',
|
|
60
|
+
"--cap-add",
|
|
61
|
+
"SYS_ADMIN",
|
|
62
|
+
"-v",
|
|
63
|
+
f"{Path.cwd().absolute()}:/home/cwd",
|
|
64
|
+
"-v",
|
|
65
|
+
f"{Config().cache_location}:/home/weck_cache",
|
|
66
|
+
"--workdir",
|
|
67
|
+
str(self.get_workdir()),
|
|
68
|
+
"--rm",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
def assemble_command(self, command: tuple[str, ...]) -> list[str]:
|
|
72
|
+
base = self.base_command()
|
|
73
|
+
if self.add_benchexec_capabilities:
|
|
74
|
+
base += self.benchexec_capabilities()
|
|
75
|
+
|
|
76
|
+
base += self.setup_command()
|
|
77
|
+
|
|
78
|
+
if self.interactive:
|
|
79
|
+
base += self.interactive_command()
|
|
80
|
+
|
|
81
|
+
for value in self.extra_args.values():
|
|
82
|
+
if isinstance(value, list) and not isinstance(value, str):
|
|
83
|
+
base += value
|
|
84
|
+
else:
|
|
85
|
+
base.append(value)
|
|
86
|
+
|
|
87
|
+
_command = self._prep_command(command)
|
|
88
|
+
return base + [self.image, *_command]
|
|
89
|
+
|
|
90
|
+
def _prep_command(self, command: tuple[str, ...]) -> tuple[str, ...]:
|
|
91
|
+
"""We want to map absolute paths of the current working directory to the
|
|
92
|
+
working directory of the container."""
|
|
93
|
+
|
|
94
|
+
def _map_path(p: Union[str, Path]) -> Union[str, Path]:
|
|
95
|
+
if isinstance(p, Path):
|
|
96
|
+
if not p.is_absolute():
|
|
97
|
+
return p
|
|
98
|
+
if p.is_relative_to(Path.cwd()):
|
|
99
|
+
relative = p.relative_to(Path.cwd())
|
|
100
|
+
return self.get_workdir() / relative
|
|
101
|
+
elif p.is_relative_to(Config().cache_location):
|
|
102
|
+
relative = p.relative_to(Config().cache_location)
|
|
103
|
+
return Path("/home/weck_cache") / relative
|
|
104
|
+
else:
|
|
105
|
+
return p
|
|
106
|
+
mapped = _map_path(Path(p))
|
|
107
|
+
if Path(p) == mapped:
|
|
108
|
+
return p
|
|
109
|
+
else:
|
|
110
|
+
return mapped
|
|
111
|
+
|
|
112
|
+
return tuple(map(_map_path, command))
|
|
113
|
+
|
|
114
|
+
@singledispatchmethod
|
|
115
|
+
def _initialize_image(self, image: str) -> str:
|
|
116
|
+
logger.debug("Initializing image from string %s", image)
|
|
117
|
+
return image
|
|
118
|
+
|
|
119
|
+
@_initialize_image.register
|
|
120
|
+
def _from_fm_config(self, fm_config: FmImageConfig) -> str:
|
|
121
|
+
logger.debug("Initializing image from FmImageConfig: %s", fm_config)
|
|
122
|
+
return ImageMgr().prepare_image(self, fm_config)
|
|
123
|
+
|
|
38
124
|
@staticmethod
|
|
39
125
|
def extract_image(fm: Union[str, Path], version: str, config: dict) -> str:
|
|
40
126
|
image = config.get("defaults", {}).get("image", None)
|
|
41
127
|
|
|
42
128
|
return parse_fm_data(fm, version).get_images().with_fallback(image)
|
|
43
129
|
|
|
130
|
+
@staticmethod
|
|
131
|
+
def _base_engine_class(config: Config):
|
|
132
|
+
engine = config.defaults().get("engine", "").lower()
|
|
133
|
+
|
|
134
|
+
if engine == "docker":
|
|
135
|
+
return Docker
|
|
136
|
+
if engine == "podman":
|
|
137
|
+
return Podman
|
|
138
|
+
|
|
139
|
+
raise ValueError(f"Unknown engine {engine}")
|
|
140
|
+
|
|
44
141
|
@singledispatchmethod
|
|
45
142
|
@staticmethod
|
|
46
143
|
def from_config(config: Config) -> "Engine":
|
|
47
|
-
|
|
144
|
+
Base = Engine._base_engine_class(config)
|
|
145
|
+
engine = Base(config.from_defaults_or_none("image"))
|
|
48
146
|
return Engine._prepare_engine(engine, config)
|
|
49
147
|
|
|
50
148
|
@from_config.register
|
|
51
149
|
@staticmethod
|
|
52
150
|
def _(fm: Path, version: str, config: Config):
|
|
53
151
|
image = Engine.extract_image(fm, version, config)
|
|
54
|
-
|
|
152
|
+
Base = Engine._base_engine_class(config)
|
|
153
|
+
engine = Base(image)
|
|
55
154
|
return Engine._prepare_engine(engine, config)
|
|
56
155
|
|
|
57
156
|
@from_config.register
|
|
58
157
|
@staticmethod
|
|
59
158
|
def _(fm: str, version: str, config: Config):
|
|
60
159
|
image = Engine.extract_image(fm, version, config)
|
|
61
|
-
|
|
160
|
+
Base = Engine._base_engine_class(config)
|
|
161
|
+
engine = Base(image)
|
|
62
162
|
return Engine._prepare_engine(engine, config)
|
|
63
163
|
|
|
64
164
|
@from_config.register
|
|
65
165
|
@staticmethod
|
|
66
166
|
def _(fm: FmData, config: Config):
|
|
67
167
|
image = fm.get_images().with_fallback(config.from_defaults_or_none("image"))
|
|
68
|
-
|
|
168
|
+
Base = Engine._base_engine_class(config)
|
|
169
|
+
engine = Base(image)
|
|
69
170
|
return Engine._prepare_engine(engine, config)
|
|
70
171
|
|
|
71
172
|
@staticmethod
|
|
72
|
-
def _prepare_engine(engine, config:
|
|
73
|
-
for src, target in config.
|
|
173
|
+
def _prepare_engine(engine, config: Config) -> "Engine":
|
|
174
|
+
for src, target in config.mounts():
|
|
74
175
|
if not Path(src).exists():
|
|
75
|
-
|
|
176
|
+
logger.warning("Mount source %s does not exist. Ignoring it...", src)
|
|
76
177
|
continue
|
|
77
178
|
engine.mount(src, target)
|
|
78
179
|
|
|
79
180
|
return engine
|
|
80
181
|
|
|
81
182
|
@abstractmethod
|
|
82
|
-
def image_from(self, containerfile: Path) -> "BuildCommand":
|
|
83
|
-
...
|
|
183
|
+
def image_from(self, containerfile: Path) -> "BuildCommand": ...
|
|
84
184
|
|
|
85
185
|
class BuildCommand(ABC):
|
|
86
|
-
|
|
87
|
-
def base_image(self, image: str): ...
|
|
186
|
+
build_args: List[str] = []
|
|
88
187
|
|
|
89
188
|
@abstractmethod
|
|
90
|
-
def
|
|
189
|
+
def __init__(self, containerfile: Path, **kwargs):
|
|
190
|
+
pass
|
|
91
191
|
|
|
92
|
-
|
|
93
|
-
|
|
192
|
+
def base_image(self, image: str):
|
|
193
|
+
self.build_args += ["--build-arg", f"BASE_IMAGE={image}"]
|
|
194
|
+
return self
|
|
195
|
+
|
|
196
|
+
def packages(self, packages: list[str]):
|
|
197
|
+
self.build_args += ["--build-arg", f"REQUIRED_PACKAGES={' '.join(packages)}"]
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
def engine(self):
|
|
201
|
+
return [self._engine]
|
|
202
|
+
|
|
203
|
+
def build(self):
|
|
204
|
+
cmd = self.engine() + [
|
|
205
|
+
"build",
|
|
206
|
+
"-f",
|
|
207
|
+
self.containerfile,
|
|
208
|
+
*self.build_args,
|
|
209
|
+
".",
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
logging.debug("Running command: %s", cmd)
|
|
213
|
+
|
|
214
|
+
ret = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
215
|
+
|
|
216
|
+
tag = ret.stdout.decode().splitlines()[-1].strip()
|
|
217
|
+
logger.info("Built image %s", tag)
|
|
218
|
+
logger.debug("Output of build image was:\n%s", ret.stdout.decode())
|
|
219
|
+
|
|
220
|
+
return tag
|
|
94
221
|
|
|
95
222
|
@staticmethod
|
|
96
223
|
def _run_process(command: tuple[str, ...] | list[str]):
|
|
@@ -102,84 +229,25 @@ class Engine(ABC):
|
|
|
102
229
|
raise NoImageError("No image set for engine.")
|
|
103
230
|
|
|
104
231
|
command = self.assemble_command(command)
|
|
105
|
-
|
|
232
|
+
logger.info("Running: %s", command)
|
|
106
233
|
self._run_process(command)
|
|
107
234
|
|
|
108
|
-
def get_workdir(self):
|
|
109
|
-
return Path.cwd().absolute()
|
|
110
|
-
|
|
111
235
|
|
|
112
236
|
class Podman(Engine):
|
|
113
237
|
def __init__(self, image: Union[str, FmImageConfig]):
|
|
238
|
+
super().__init__(image)
|
|
114
239
|
self._engine = "podman"
|
|
115
|
-
self.image = self._initialize_image(image)
|
|
116
|
-
self.extra_args = {}
|
|
117
|
-
|
|
118
|
-
@singledispatchmethod
|
|
119
|
-
def _initialize_image(self, image: str) -> str:
|
|
120
|
-
return image
|
|
121
|
-
|
|
122
|
-
@_initialize_image.register
|
|
123
|
-
def _from_fm_config(self, fm_config: FmImageConfig) -> str:
|
|
124
|
-
return ImageMgr().prepare_image(self, fm_config)
|
|
125
|
-
|
|
126
|
-
def mount_benchexec(self, benchexec_dir: str):
|
|
127
|
-
self.extra_args["benchexec"] = [
|
|
128
|
-
"-v",
|
|
129
|
-
f"{benchexec_dir.absolute()}:/benchexec",
|
|
130
|
-
]
|
|
131
|
-
|
|
132
|
-
def mount(self, source: str, target: str):
|
|
133
|
-
self.extra_args["mounts"] = self.extra_args.get("mounts", []) + [
|
|
134
|
-
"-v",
|
|
135
|
-
f"{source}:{target}",
|
|
136
|
-
]
|
|
137
240
|
|
|
138
241
|
class PodmanBuildCommand(Engine.BuildCommand):
|
|
139
242
|
def __init__(self, containerfile: Path):
|
|
140
243
|
self.containerfile = containerfile
|
|
141
|
-
self.
|
|
142
|
-
|
|
143
|
-
def base_image(self, image: str):
|
|
144
|
-
self.build_args["--build-arg"] = f"BASE_IMAGE={image}"
|
|
145
|
-
return self
|
|
146
|
-
|
|
147
|
-
def packages(self, packages: list[str]):
|
|
148
|
-
self.build_args["--build-arg"] = f"REQUIRED_PACKAGES=\"{' '.join(packages)}\""
|
|
149
|
-
return self
|
|
150
|
-
|
|
151
|
-
def build(self):
|
|
152
|
-
tag_id = uuid.uuid4().hex
|
|
153
|
-
tag = f"fmweck/{tag_id}"
|
|
154
|
-
|
|
155
|
-
ret = subprocess.run(
|
|
156
|
-
[
|
|
157
|
-
"podman",
|
|
158
|
-
"build",
|
|
159
|
-
"-t",
|
|
160
|
-
tag,
|
|
161
|
-
"-f",
|
|
162
|
-
self.containerfile,
|
|
163
|
-
*self.build_args,
|
|
164
|
-
".",
|
|
165
|
-
],
|
|
166
|
-
check=True,
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
logging.debug("Build output: %s", ret.stdout)
|
|
170
|
-
logging.info("Built image %s", tag)
|
|
171
|
-
|
|
172
|
-
return tag
|
|
244
|
+
self._engine = "podman"
|
|
173
245
|
|
|
174
246
|
def image_from(self, containerfile: Path):
|
|
175
247
|
return self.PodmanBuildCommand(containerfile)
|
|
176
248
|
|
|
177
|
-
def
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
def assemble_command(self, command: tuple[str, ...]) -> list[str]:
|
|
181
|
-
|
|
182
|
-
benchexec_cap = [
|
|
249
|
+
def benchexec_capabilities(self):
|
|
250
|
+
return [
|
|
183
251
|
"--annotation",
|
|
184
252
|
"run.oci.keep_original_groups=1",
|
|
185
253
|
"--security-opt",
|
|
@@ -190,17 +258,49 @@ class Podman(Engine):
|
|
|
190
258
|
"/sys/fs/cgroup:/sys/fs/cgroup",
|
|
191
259
|
]
|
|
192
260
|
|
|
193
|
-
base = [
|
|
194
|
-
"podman",
|
|
195
|
-
"run",
|
|
196
|
-
]
|
|
197
261
|
|
|
198
|
-
|
|
199
|
-
|
|
262
|
+
class Docker(Engine):
|
|
263
|
+
def __init__(self, image: Union[str, FmImageConfig]):
|
|
264
|
+
super().__init__(image)
|
|
265
|
+
logger.debug("Image: %s", self.image)
|
|
266
|
+
self._engine = "docker"
|
|
267
|
+
|
|
268
|
+
class DockerBuildCommand(Engine.BuildCommand):
|
|
269
|
+
def __init__(self, containerfile: Path, needs_sudo: bool = False):
|
|
270
|
+
self.containerfile = containerfile
|
|
271
|
+
if needs_sudo:
|
|
272
|
+
self._engine = "sudo docker"
|
|
273
|
+
else:
|
|
274
|
+
self._engine = "docker"
|
|
275
|
+
|
|
276
|
+
def engine(self):
|
|
277
|
+
return self._engine.split(" ")
|
|
200
278
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
279
|
+
def image_from(self, containerfile: Path):
|
|
280
|
+
return self.DockerBuildCommand(containerfile, needs_sudo=self._requires_sudo)
|
|
281
|
+
|
|
282
|
+
@cached_property
|
|
283
|
+
def _requires_sudo(self):
|
|
284
|
+
"""Test if docker works without sudo."""
|
|
285
|
+
try:
|
|
286
|
+
subprocess.run(["docker", "info"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
287
|
+
logger.debug("Docker does not require sudo.")
|
|
288
|
+
return False
|
|
289
|
+
except subprocess.CalledProcessError:
|
|
290
|
+
logger.debug("Docker requires sudo.")
|
|
291
|
+
return True
|
|
292
|
+
|
|
293
|
+
def base_command(self):
|
|
294
|
+
if self._requires_sudo:
|
|
295
|
+
return ["sudo", "docker", "run"]
|
|
296
|
+
return ["docker", "run"]
|
|
297
|
+
|
|
298
|
+
def setup_command(self):
|
|
299
|
+
return [
|
|
300
|
+
"--entrypoint",
|
|
301
|
+
"/bin/sh",
|
|
302
|
+
"--cap-add",
|
|
303
|
+
"SYS_ADMIN",
|
|
204
304
|
"-v",
|
|
205
305
|
f"{Path.cwd().absolute()}:/home/cwd",
|
|
206
306
|
"-v",
|
|
@@ -210,37 +310,13 @@ class Podman(Engine):
|
|
|
210
310
|
"--rm",
|
|
211
311
|
]
|
|
212
312
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def _prep_command(self, command: tuple[str, ...]) -> tuple[str, ...]:
|
|
225
|
-
"""We want to map absolute paths of the current working directory to the
|
|
226
|
-
working directory of the container."""
|
|
227
|
-
|
|
228
|
-
def _map_path(p: Union[str, Path]) -> Union[str, Path]:
|
|
229
|
-
if isinstance(p, Path):
|
|
230
|
-
if not p.is_absolute():
|
|
231
|
-
return p
|
|
232
|
-
if p.is_relative_to(Path.cwd()):
|
|
233
|
-
relative = p.relative_to(Path.cwd())
|
|
234
|
-
return self.get_workdir() / relative
|
|
235
|
-
elif p.is_relative_to(Config().cache_location):
|
|
236
|
-
relative = p.relative_to(Config().cache_location)
|
|
237
|
-
return Path("/home/weck_cache") / relative
|
|
238
|
-
else:
|
|
239
|
-
return p
|
|
240
|
-
mapped = _map_path(Path(p))
|
|
241
|
-
if Path(p) == mapped:
|
|
242
|
-
return p
|
|
243
|
-
else:
|
|
244
|
-
return mapped
|
|
245
|
-
|
|
246
|
-
return tuple(map(_map_path, command))
|
|
313
|
+
def benchexec_capabilities(self):
|
|
314
|
+
return [
|
|
315
|
+
"--security-opt",
|
|
316
|
+
"seccomp=unconfined",
|
|
317
|
+
"--security-opt",
|
|
318
|
+
"apparmor=unconfined",
|
|
319
|
+
"--security-opt",
|
|
320
|
+
"label=disable",
|
|
321
|
+
"-v /sys/fs/cgroup:/sys/fs/cgroup",
|
|
322
|
+
]
|
fm_weck/image_mgr.py
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
#
|
|
6
6
|
# SPDX-License-Identifier: Apache-2.0
|
|
7
7
|
|
|
8
|
+
import logging
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import TYPE_CHECKING
|
|
10
11
|
|
|
11
12
|
from fm_tools.fmdata import FmImageConfig
|
|
13
|
+
from yaspin import yaspin
|
|
12
14
|
|
|
13
15
|
from fm_weck import Config
|
|
14
16
|
|
|
@@ -38,9 +40,15 @@ class ImageMgr(object):
|
|
|
38
40
|
if image.base_images and not image.required_packages:
|
|
39
41
|
return image.base_images[0]
|
|
40
42
|
|
|
43
|
+
logging.info(
|
|
44
|
+
"Building image from from base image %s with packages %s", image.base_images[0], image.required_packages
|
|
45
|
+
)
|
|
41
46
|
image_cmd = engine.image_from(CONTAINERFILE)
|
|
42
|
-
image_cmd.packages(image.required_packages)
|
|
43
47
|
image_cmd.base_image(image.base_images[0])
|
|
48
|
+
image_cmd.packages(image.required_packages)
|
|
49
|
+
|
|
50
|
+
with yaspin(text="Building image", color="cyan") as spinner:
|
|
51
|
+
tag = image_cmd.build()
|
|
52
|
+
spinner.ok("✅ ")
|
|
44
53
|
|
|
45
|
-
tag
|
|
46
|
-
return "localhost/" + tag
|
|
54
|
+
return tag
|
fm_weck/resources/Containerfile
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
#
|
|
6
6
|
# SPDX-License-Identifier: Apache-2.0
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
ARG BASE_IMAGE
|
|
10
10
|
FROM ${BASE_IMAGE}
|
|
11
11
|
RUN apt-get update
|
|
12
|
-
|
|
12
|
+
ARG REQUIRED_PACKAGES
|
|
13
|
+
RUN apt-get install -y ${REQUIRED_PACKAGES} && rm -rf /var/lib/apt/lists/*
|
|
@@ -11,7 +11,8 @@ fmtools_entry_maintainers:
|
|
|
11
11
|
- cedricrupb
|
|
12
12
|
|
|
13
13
|
maintainers:
|
|
14
|
-
-
|
|
14
|
+
- orcid: 0000-0003-1132-5516
|
|
15
|
+
name: Marek Chalupa
|
|
15
16
|
institution: ISTA
|
|
16
17
|
country: Austria
|
|
17
18
|
url: null
|
|
@@ -30,6 +31,7 @@ competition_participations:
|
|
|
30
31
|
track: "Verification"
|
|
31
32
|
tool_version: "svcomp24"
|
|
32
33
|
jury_member:
|
|
34
|
+
orcid: 0000-0003-1132-5516
|
|
33
35
|
name: Marek Chalupa
|
|
34
36
|
institution: ISTA
|
|
35
37
|
country: Austria
|
|
@@ -46,3 +48,8 @@ techniques:
|
|
|
46
48
|
- Portfolio
|
|
47
49
|
|
|
48
50
|
frameworks_solvers: []
|
|
51
|
+
|
|
52
|
+
literature:
|
|
53
|
+
- doi: 10.1007/978-3-031-57256-2_20
|
|
54
|
+
title: "Bubaak-SpLit: Split What You Cannot Verify (Competition Contribution)"
|
|
55
|
+
year: 2024
|