Skip to content
Snippets Groups Projects
mpsd-software-environment.py 17.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • #!/usr/bin/env python3
    
    
    """mpsd-software-environment: tool help installation of toolchains."""
    
    
    import argparse
    import datetime
    
    import os
    import subprocess
    
    from pathlib import Path
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    from typing import List, Tuple
    
    about_tool = """
    
    Build toolchains using Spack.\n
    
    This function builds toolchains for MPSD-HPC at the appropriate directory, \n
    for given system architecture and MPSD software stack version.\n
    
    The toolchains
    are built using the bash script spack_setup.sh, and the results are logged. """
    
        "cmd_log_file": "install.log",
    
        "build_log_file": (
            "logs/mpsd_spack_ver_toolchains_"
            f"{datetime.datetime.now().replace(microsecond=0).isoformat()}.log"
        ),
        # TODO: modify toolchains,mpsd_spack_ver when the variable is available
    
        "spack_environments_repo": "https://gitlab.gwdg.de/mpsd-cs/spack-environments.git",
    }
    
    # Helper class to change directory via context manager
    class os_chdir:
    
        """The os_chdir class is a context manager.
    
    
        It changes the current directory to a specified directory
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        and returns to the original directory after execution.
        """
    
        def __init__(self, new_dir):
    
            """Initialize, save original directory."""
    
            self.new_dir = new_dir
            self.saved_dir = os.getcwd()
    
        def __enter__(self):
    
            """Go to target directory (main action for context)."""
    
            os.chdir(self.new_dir)
    
        def __exit__(self, exc_type, exc_val, exc_tb):
    
            """On exist we return to original directory."""
    
            os.chdir(self.saved_dir)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    def setup_log_cmd(
        mpsd_release: str, script_dir: str, msg: str = None, *args, **kwargs
    ) -> None:
    
        Log the command used to build the toolchains.
    
    
        It also logs information about the software environment installer branch,
    
        the Spack environments branch, and the commit hashes of each.
        It also logs steps taken
    
        in the install process using the optional message argument.
    
        Parameters
        ----------
        - mpsd_release : str
            The name of the release to install toolchains for.
        - script_dir : str
            The path to the directory where the scripts are located.
        - msg : str, optional
            An optional message to log in the command log file.
    
        - *args : tuple
            undocumented XXX
        - **kwargs : dict
    
            A dictionary with values for 
            - spe_branch : str 
                The name of the Spack environments branch.
            - spe_commit_hash : str
                The commit hash of the Spack environments branch.
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        """
    
        release_base_dir = script_dir / mpsd_release
    
        # Write to the log file with the following format
        # --------------------------------------------------
        # 2023-02-29T23:32:01, install-software-environment.py --release 23b --install ALL
        # Software environment installer branch: script_branch (commit hash: \
        #    script_commit_hash)
        # Spack environments branch: dev-23a (commit hash: spe_commit_hash)
        # MSGs
    
        with os_chdir(release_base_dir):
            with open(config_vars["cmd_log_file"], "a") as f:
                if msg:
                    # Write the message to the log file
                    f.write(msg + "\n")
                else:
                    # Write the header
                    f.write("-" * 50 + "\n")
    
                    # Gather data to log
                    # call statement:
                    cmd_line = " ".join(sys.argv)
                    # script branch and commit hash
    
                    with os_chdir(script_dir):
                        script_branch = (
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                            subprocess.run(
                                ["git", "rev-parse", "--abbrev-ref", "HEAD"],
                                stdout=subprocess.PIPE,
                            )
    
                            .stdout.decode()
                            .strip()
                        )
                        script_commit_hash = (
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                            subprocess.run(
                                ["git", "rev-parse", "--short", "HEAD"],
                                stdout=subprocess.PIPE,
                            )
    
                            .stdout.decode()
                            .strip()
    
                    # spack-environments branch and commit hash from kwargs
                    spe_branch = kwargs.get("spe_branch", None)
                    spe_commit_hash = kwargs.get("spe_commit_hash", None)
    
                    # Write to log file
                    f.write(f"{datetime.datetime.now().isoformat()}, {cmd_line}\n")
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                    f.write(
    
                        f"Software environment installer branch: {script_branch} "
                        f"(commit hash: {script_commit_hash})\n"
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                    )
                    f.write(
    
                        f"Spack environments branch: {spe_branch} "
                        f"(commit hash: {spe_commit_hash})\n"
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                    )
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    def create_dir_structure(mpsd_release: str, script_dir: Path) -> None:
    
        """
        Create the directory structure and clone spack environments repo.
    
        The create_dir_structure function creates the directory structure for
    
        the specified release and clones the Spack environments repository if it
        doesn't exist.
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        Parameters
        ----------
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        - mpsd_release: A string representing the MPSD release version.
        - script_dir: A Path object representing the path to the scripts directory.
    
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        - None
        """
    
        # Create the directory structure for the release
    
        release_base_dir = script_dir / mpsd_release
        release_base_dir.mkdir(parents=True, exist_ok=True)
    
    
        with os_chdir(release_base_dir):
            # Clone the spack-environments repo if it doesn't exist
            if not os.path.exists("spack-environments"):
    
                subprocess.run(
                    [
                        "git",
    
                        config_vars["spack_environments_repo"],
    
            with os_chdir("spack-environments"):
    
                # Git fetch and checkout the release branch and git pull
                # to be sure that the resulting repo is up to date
    
                subprocess.run(["git", "fetch", "--all"])
    
                checkout_result = subprocess.run(["git", "checkout", mpsd_release])
                if checkout_result.returncode != 0:
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                    raise Exception(
    
                        "Release branch does not exist in spack-environment repo \n."
                        "Check for typos."
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                    )
    
                subprocess.run(["git", "pull"])
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    def get_release_info(mpsd_release: str, script_dir: Path) -> Tuple[str, str, List[str]]:
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        """
    
        Get information about the specified release.
    
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        Get information about the specified release, such as the branch and commit hash
        of the Spack environments repository and the available toolchains.
    
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        mpsd_release : str
    
            The name of the release to get information for.
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        script_dir : pathlib.Path
    
            The base directory where releases are stored.
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        spe_branch : str
    
            The name of the branch for the Spack environments repository.
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        spe_commit_hash : str
    
            The commit hash for the Spack environments repository.
    
        available_toolchains : list
    
            A list of strings representing the available toolchains for the release.
    
        FileNotFoundError
            If the release directory does not exist. Run `create_dir_structure()` first.
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        """
    
        # Get the info for release
    
        release_base_dir = script_dir / mpsd_release
    
        if not os.path.exists(release_base_dir):
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                "Release directory does not exist. Run create_dir_structure() first."
            )
    
        with os_chdir(release_base_dir):
            with os_chdir("spack-environments"):
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                # Get the branch and commit hash of the spack-environments repo
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                    subprocess.run(["git", "rev-parse", "HEAD"], stdout=subprocess.PIPE)
                    .stdout.decode()
                    .strip()
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                    subprocess.run(
                        ["git", "rev-parse", "--abbrev-ref", "HEAD"], stdout=subprocess.PIPE
                    )
    
                    .stdout.decode()
                    .strip()
                )
    
                available_toolchains = os.listdir("toolchains")
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        return spe_branch, spe_commit_hash, available_toolchains
    
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    def prepare_environment(mpsd_release: str, script_dir: Path) -> List[str]:
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        """
    
        Create the directory structure for the given MPSD release.
    
    
        It does the following steps:
        Clones the spack-environments repository.
    
        Determines the branch and commit hash of the spack-environments repository
        and the available toolchains.
    
        Logs the command usage.
    
        Parameters
        ----------
        mpsd_release : str
            The name of the MPSD release to prepare the environment for.
        script_dir : pathlib.Path
    
            The base directory to create the release folder and
            clone the spack-environments repository into.
    
        Returns
        -------
        available_toolchains : list
            A list of available toolchains for the given MPSD release.
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        """
    
        create_dir_structure(mpsd_release, script_dir)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        spe_branch, spe_commit_hash, available_toolchains = get_release_info(
            mpsd_release, script_dir
        )
        setup_log_cmd(
            mpsd_release, script_dir, spe_branch=spe_branch, spe_commit_hash=spe_commit_hash
        )
    
        return available_toolchains
    
    def install_environment(
        mpsd_release: str,
        toolchains: List[str],
        script_dir: Path,
        force_reinstall: bool = False,
        enable_build_cache: bool = False,
    ) -> None:
    
        Install the specified MPSD release and toolchains.
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        The function installs the toolchain to the specified directory, using Spack.
    
        Parameters
        ----------
        mpsd_release : str
            A string representing the MPSD release version.
        toolchains : list of str
            A list of strings representing the toolchains to install
            (e.g., "foss2021a-mpi", "global_generic", "ALL").
        script_dir : pathlib.Path
            A Path object representing the path to the directory where
            the release and toolchains will be installed.
        force_reinstall : bool, optional
            A boolean indicating whether to force a reinstallation
            even if the release and toolchains already exist. Defaults to False.
        enable_build_cache : bool, optional
    
            A boolean indicating whether to build the build cache
            when installing toolchains. Defaults to False.
    
        Raises
        ------
        ValueError
            If a requested toolchain is not available in the specified release.
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        """
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        print(
    
            f"Installing release {mpsd_release} with toolchains {toolchains} "
            f"to {script_dir}"
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        )
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # Set required variables
    
        release_base_dir = script_dir / mpsd_release
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        os.environ.get("MPSD_OS", "UNKNOWN_OS")
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        mpsd_microarch = os.environ.get("MPSD_MICROARCH", "UNKNOWN_MICROARCH")
        toolchain_dir = release_base_dir / mpsd_microarch
    
        toolchain_dir.mkdir(parents=True, exist_ok=True)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        spack_setup_script = release_base_dir / "spack-environments" / "spack_setup.sh"
        install_flags = []
    
        if not enable_build_cache:
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            install_flags.append("-b")
    
        # run the prepare_environment function
        available_toolchains = prepare_environment(mpsd_release, script_dir)
        # Ensure that the requested toolchains are available in the release
    
        if toolchains == "ALL":
            toolchains = available_toolchains
    
            # No toolchains requested, so we only create the env and print the
            # list of available toolchains
    
                "No toolchains requested. Available toolchains for release "
                f"{mpsd_release} are: \n {available_toolchains}"
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        for toolchain in toolchains:
            if toolchain not in available_toolchains:
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                    f"Toolchain '{toolchain}' is not available in release {mpsd_release}."
                )
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # Install the toolchains
        with os_chdir(toolchain_dir):
            # run spack_setup_script with the toolchains as arguments
    
            # if the log folder doesn't exist, create it
            if not os.path.exists("logs"):
                os.mkdir("logs")
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            for toolchain in toolchains:
                # Set the install log file name to config_vars["install_log_file"]
    
                # and replace _toolchains_ with the toolchain name and
                # _mpsd_spack_ver_ with mpsd_release
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    
    
                print(f"Installing toolchain {toolchain} to {toolchain_dir}")
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                install_log_file = (
    
                    config_vars["build_log_file"]
    
                    .replace("mpsd_spack_ver_", f"{mpsd_release}_")
    
                    .replace("_toolchains_", f"_{toolchain}_")
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                )
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                # log the command
                setup_log_cmd(
                    mpsd_release,
                    script_dir,
                    msg=f"installing {toolchain} and logging at {install_log_file}",
                )
                setup_log_cmd(
                    mpsd_release,
                    script_dir,
    
                    msg=(
                        f"CMD: bash {spack_setup_script} {' '.join(install_flags)}"
                        "{toolchain}"
                    ),
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                )
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                subprocess.run(
    
                    f"bash {spack_setup_script} {' '.join(install_flags)} {toolchain} 2>&1 "
                    f"| tee -a {install_log_file} ",
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    
    
    def remove_environment(release, toolchains, target_dir):
    
        """Remove release from installation."""
    
        msg = f"Removing release {release} with toolchains {toolchains} from {target_dir}"
        print(msg)
        raise NotImplementedError(msg)
    
    def start_new_environment(release, from_release, target_dir):
    
        """Start new MPSD software environment version."""
    
        msg = f"Starting new release {release} from {from_release} to {target_dir}"
        print(msg)
        raise NotImplementedError(msg)
    
    def main():
    
        """Execute main entry point."""
    
        parser = argparse.ArgumentParser(description=about_tool)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        subparsers = parser.add_subparsers(
            dest="action", title="actions", description="valid actions", required=True
        )
    
        subparsers.required = True
        list_of_cmds = [
    
            ("prepare", "Prepare the environment for installation on the disk"),
            ("install", "Install a software environment"),
            ("reinstall", "Reinstall a software environment"),
            ("remove", "Remove a software environment or toolchains from an environment"),
            ("start-new", "Start a new software environment version"),
    
        ]
        for cmd, help_text in list_of_cmds:
            subp = subparsers.add_parser(cmd, help=help_text)
    
    
            if cmd == "start-new":
                subp.add_argument(
                    "--from-release",
                    dest="from_release",
                    type=str,
                    required=True,
                    help="Release version to start from",
                )
                subp.add_argument(
                    "--to-release",
                    dest="to_release",
                    type=str,
                    required=True,
                    help="Release version to create",
                )
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                subp.add_argument(
    
                    "release",
                    type=str,
                    help="Release version to prepare, install, reinstall or remove",
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                )
    
                if cmd in ["install", "reinstall", "remove"]:
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                    # "install" command needs additional documentation
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                        f"Pass a list of toolchains to command {cmd}. "
                        "Use '--toolchains ALL' to "
                        f"{cmd} all toolchains. If '--toolchain' is not "
                        "specified, list available toolchains for the release "
                        "(after environment has been prepared if not done yet)."
                    )
    
    
                    subp.add_argument(
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                        "--toolchains",  # first option defines attribute
                        # name `args.toolchains` in `args = parser_args()`
                        "--toolchain",  # allow singular as alternative
                        # (-> creates attribute `args.toolchains` if used)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                        nargs="+",
    
                    )
                    subp.add_argument(
                        "--enable-build-cache",
                        action="store_true",
    
                            "Enable Spack build cache. Useful for reinstallation but "
                            "consumes time and disk space."
    
        # Carry out the action
        args = parser.parse_args()
    
        # target dir is the place where this script exists. the
        # release `dev` in script_dir/dev-23a
    
        script_dir = Path(os.path.dirname(os.path.realpath(__file__)))
    
    
        # Check the command and run related function
    
        if args.action == "remove":
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            remove_environment(args.release, args.toolchains, script_dir)
    
        elif args.action == "start-new":
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            start_new_environment(args.from_release, args.to_release, script_dir)
    
        elif args.action == "install":
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            install_environment(
                args.release, args.toolchains, script_dir, False, args.enable_build_cache
            )
    
        elif args.action == "prepare":
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            prepare_environment(args.release, script_dir)
    
    if __name__ == "__main__":