Source code for fm_weck.runexec_mode
# This file is part of fm-weck: executing fm-tools in containerized environments.
# https://gitlab.com/sosy-lab/software/fm-weck
#
# SPDX-FileCopyrightText: 2024 Dirk Beyer <https://www.sosy-lab.org>
#
# SPDX-License-Identifier: Apache-2.0
import importlib.resources as pkg_resources
import logging
import shutil
import stat
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Optional
from fm_weck.resources import BENCHEXEC_WHL, RUNEXEC_SCRIPT
from fm_weck.runexec_util import mountable_absolute_paths_of_command
from .config import Config
from .engine import CACHE_MOUNT_LOCATION, Engine
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from tempfile import _TemporaryFileWrapper
from fm_weck.run_result import RunResult
def _setup_script_in_cache(target: "_TemporaryFileWrapper[bytes]") -> Path:
# When running multiple instances in parallel it can happen, that two processes interfere
# when copying the script causing a "Text file is busy" error.
# Using a temp file avoids this problem.
# The cleaner solution would be to use a direct bind mount from the package resource, but this does not
# work on the benchcloud right now.
with pkg_resources.path("fm_weck.resources", RUNEXEC_SCRIPT) as source_path:
shutil.copy(source_path, target.name)
target_path = Path(target.name)
mode = target_path.stat().st_mode
# make the temp file executable
target_path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
target.flush()
target.close()
return target_path
[docs]
def run_runexec(
benchexec_package: Optional[Path],
use_image: Optional[str],
configuration: Config,
extra_container_args: list[list[str]],
command: list[str],
mount_rw: Optional[list[Path]] = None,
) -> "RunResult| None":
if use_image is not None:
configuration.set_default_image(use_image)
engine = Engine.from_config(configuration)
engine.add_benchexec_capabilities = True
engine.add_mounting_capabilities = False
if benchexec_package is not None:
engine.mount(str(benchexec_package.parent.absolute()), "/home/__fm_weck_benchexec")
engine.env["PYTHONPATH"] = f"/home/__fm_weck_benchexec/{benchexec_package.name}"
else:
# Default to the bundled benchexec package
benchexec_package = configuration.get_shelve_path_for_benchexec()
try:
with pkg_resources.path("fm_weck.resources", BENCHEXEC_WHL) as source_path:
shutil.copy(source_path, benchexec_package)
engine.env["PYTHONPATH"] = f"{CACHE_MOUNT_LOCATION}/.lib/benchexec.whl"
except FileNotFoundError:
logging.error(f"Resource {BENCHEXEC_WHL} not found in package.")
return None
rw_paths: set[Path] = set()
for p in mount_rw or []:
abs_p = p.absolute()
rw_paths.add(abs_p)
engine.mount(str(abs_p), str(abs_p))
for path in mountable_absolute_paths_of_command(Path.cwd().absolute(), command):
if path in rw_paths:
continue
engine.mount(str(path), str(path) + ":ro")
for arg in extra_container_args:
engine.add_container_long_opt(arg)
engine.handle_io = False
scripts_dir = configuration.cache_location / ".scripts"
scripts_dir.mkdir(parents=True, exist_ok=True)
with (
NamedTemporaryFile(prefix="runexec_", dir=str(scripts_dir), delete=True, delete_on_close=False) as tmp_file, # ty:ignore[no-matching-overload]
):
script_path = _setup_script_in_cache(tmp_file)
rel_path = script_path.relative_to(configuration.cache_location)
return engine.run(f"{CACHE_MOUNT_LOCATION}/{rel_path}", *command)