Source code for varats.tools.research_tools.vara

"""Module for the research tool VaRA that describes the VaRA code base layout
and how to configure and setup VaRA."""
import logging
import os
import shutil
import typing as tp
from pathlib import Path

from benchbuild.utils.cmd import ln, mkdir
from plumbum import local
from PyQt5.QtCore import QProcess

from varats.plot.plot_utils import check_required_args
from varats.tools.research_tools.research_tool import (
    CodeBase,
    ResearchTool,
    SubProject,
)
from varats.tools.research_tools.vara_manager import (
    BuildType,
    ProcessManager,
    run_process_with_output,
    set_vara_cmake_variables,
)
from varats.utils.exceptions import ProcessTerminatedError
from varats.utils.logger_util import log_without_linesep
from varats.utils.settings import save_config, vara_cfg

LOG = logging.getLogger(__name__)


[docs]class VaRACodeBase(CodeBase): """Layout of the VaRA code base: setting up vara-llvm-project fork, VaRA, and optinaly phasar for static analysis.""" def __init__(self, base_dir: Path) -> None: sub_projects = [ SubProject( self, "vara-llvm-project", "https://github.com/llvm/llvm-project.git", "upstream", "vara-llvm-project" ), SubProject( self, "VaRA", "git@github.com:se-passau/VaRA.git", "origin", "vara-llvm-project/vara" ), SubProject( self, "phasar", "https://github.com/secure-software-engineering/phasar.git", "origin", "vara-llvm-project/phasar", auto_clone=False ) ] super().__init__(base_dir, sub_projects)
[docs] def setup_vara_remotes(self) -> None: """Sets up VaRA specific upstream remotes for projects that were forked.""" self.get_sub_project("vara-llvm-project").add_remote( "origin", "git@github.com:se-passau/vara-llvm-project.git" )
[docs] def checkout_vara_version( self, version: int, use_dev_branches: bool ) -> None: """ Checkout out a specific version of VaRA. Args: version: major version number, e.g., 100 or 110 use_dev_branches: true, if one wants the current development version """ dev_suffix = "-dev" if use_dev_branches else "" print(f"Checking out VaRA version {str(version) + dev_suffix}") self.get_sub_project("vara-llvm-project" ).checkout_branch(f"vara-{version}{dev_suffix}") # TODO (sattlerf): make different checkout for older versions self.get_sub_project("VaRA").checkout_branch(f"vara{dev_suffix}")
[docs] def setup_submodules(self) -> None: """Set up the git submodules of all sub projects.""" self.get_sub_project("vara-llvm-project").init_and_update_submodules() self.get_sub_project("phasar").init_and_update_submodules()
[docs] def pull(self) -> None: """Pull and update all ``SubProject`` s.""" self.map_sub_projects(lambda prj: prj.pull()) self.setup_submodules()
[docs]class VaRA(ResearchTool[VaRACodeBase]): """ Research tool implementation for VaRA. Find the main repo online on github: https://github.com/se-passau/VaRA """ def __init__(self, base_dir: Path) -> None: super().__init__("VaRA", [BuildType.DEV], VaRACodeBase(base_dir)) vara_cfg()["vara"]["llvm_source_dir"] = str(base_dir) save_config()
[docs] @staticmethod def source_location() -> Path: """Returns the source location of the research tool.""" return Path(vara_cfg()["vara"]["llvm_source_dir"].value)
[docs] @staticmethod def has_source_location() -> bool: """Checks if a source location of the research tool is configured.""" return vara_cfg()["vara"]["llvm_source_dir"].value is not None
[docs] @staticmethod def install_location() -> Path: """Returns the install location of the research tool.""" return Path(vara_cfg()["vara"]["llvm_install_dir"].value)
[docs] @staticmethod def has_install_location() -> bool: """Checks if a install location of the research tool is configured.""" return vara_cfg()["vara"]["llvm_install_dir"].value is not None
[docs] @check_required_args(["install_prefix", "version"]) def setup(self, source_folder: tp.Optional[Path], **kwargs: tp.Any) -> None: """ Setup the research tool VaRA with it's code base. This method sets up all relevant config variables, downloads repositories via the ``CodeBase``, checkouts the correct branches and prepares the research tool to be built. Args: source_folder: location to store the code base in **kwargs: * version * install_prefix """ cfg = vara_cfg() if source_folder: cfg["vara"]["llvm_source_dir"] = str(source_folder) cfg["vara"]["llvm_install_dir"] = str(kwargs["install_prefix"]) version = kwargs.get("version") if version: version = int(tp.cast(int, version)) cfg["vara"]["version"] = version else: version = cfg["vara"]["version"].value save_config() print(f"Setting up VaRA in {self.source_location()}") use_dev_branches = cfg["vara"]["developer_version"].value self.code_base.clone(self.source_location()) self.code_base.setup_vara_remotes() self.code_base.checkout_vara_version(version, use_dev_branches) self.code_base.setup_submodules() self.code_base.setup_build_link()
[docs] def upgrade(self) -> None: """Upgrade the research tool to a newer version.""" version = 100 # TODO (se-passau/VaRA#640): version upgrade if str(vara_cfg()["vara"]["version"]) != str(version): raise NotImplementedError self.code_base.pull()
[docs] def build(self, build_type: BuildType, install_location: Path) -> None: """ Build/Compile VaRA in the specified ``build_type``. This method leaves VaRA in a finished state, i.e., being ready to be installed. Args: build_type: which type of build should be used, e.g., debug, development or release """ full_path = self.code_base.base_dir / "vara-llvm-project" / "build/" if not self.is_build_type_supported(build_type): LOG.critical( f"BuildType {build_type.name} is not supported by VaRA" ) return full_path /= build_type.build_folder() # Setup configured build folder print(" - Setting up build folder.") if not os.path.exists(full_path): try: os.makedirs(full_path.parent, exist_ok=True) build_script = "./build_cfg/build-{build_type}.sh".format( build_type=str(build_type) ) with ProcessManager.create_process( build_script, workdir=full_path.parent ) as proc: proc.setProcessChannelMode(QProcess.MergedChannels) proc.readyReadStandardOutput.connect( lambda: run_process_with_output( proc, log_without_linesep(print) ) ) except ProcessTerminatedError as error: shutil.rmtree(full_path) raise error print(" - Finished setup of build folder.") # Set install prefix in cmake with local.cwd(full_path): vara_cfg()["vara"]["llvm_install_dir"] = str(install_location) set_vara_cmake_variables( str(install_location), log_without_linesep(print) ) print(" - Finished extra cmake config.") print(" - Now building...") # Compile llvm + VaRA with ProcessManager.create_process( "ninja", ["install"], workdir=full_path ) as proc: proc.setProcessChannelMode(QProcess.MergedChannels) proc.readyReadStandardOutput.connect( lambda: run_process_with_output(proc, log_without_linesep(print)) )
[docs] def verify_install(self, install_location: Path) -> bool: # pylint: disable=no-self-use """ Verify if VaRA was correctly installed. Returns: True, if the tool was correctly installed """ status_ok = True status_ok &= (install_location / "bin/clang++").exists() status_ok &= (install_location / "bin/opt").exists() status_ok &= (install_location / "bin/phasar-llvm").exists() return status_ok