Source code for e3_build_tools.builder

"""Core objects for building e3 modules."""

import logging
import pathlib
from typing import Dict, List, Optional, Tuple

from git import GitCommandError, InvalidGitRepositoryError, NoSuchPathError, Repo

from e3_build_tools import utils
from e3_build_tools.exceptions import ModuleBuildFailException, NotInitialisedError
from e3_build_tools.fs.environment import InstalledEnvironment
from e3_build_tools.module import EPICSBaseSource, ModuleSource

[docs] logger = logging.getLogger(__name__)
[docs] class Builder: """Class for building e3 modules.""" def __init__( self, build_dir: pathlib.Path, use_ssh: bool = False, jobs: int = 1 ) -> None: """Initialize object."""
[docs] self.build_dir = build_dir
[docs] self.use_ssh = use_ssh
[docs] self.jobs = jobs
[docs] def build( self, module: ModuleSource, target_environment: InstalledEnvironment, with_substitutions: Optional[Dict[str, Dict[str, str]]] = None, ) -> Tuple[List[Tuple[str, str]], List[Tuple[str, str]]]: """Build all of the versions of a Module. Raises: NotInitialisedError: If module was not initialised. GitCommandError: If clone failed. InvalidGitRepositoryError: If directory exists but is not a git repository. """ successes: List[Tuple[str, str]] = [] failures: List[Tuple[str, str]] = [] self.build_dir.mkdir(parents=True, exist_ok=True) if module.versions: try: module.repo = self._clone_repo(module) except (NotInitialisedError, InvalidGitRepositoryError, GitCommandError): failures.extend([(module.name, ref) for ref in module.versions]) # FIXME: This is so that we "fail fast" return successes, failures for ref in module.versions: try: self._build_version(module, ref, target_environment, with_substitutions) except ModuleBuildFailException: failures.append((module.name, ref)) # FIXME: This is so that we "fail fast" break else: successes.append((module.name, ref)) return successes, failures
def _build_version( self, module: ModuleSource, ref: str, target_environment: InstalledEnvironment, with_substitutions: Optional[Dict[str, Dict[str, str]]] = None, ) -> None: """Build a fixed version of a module.""" logger.debug(f"Attempting to build {ref!r} of {module.name!r}") try: module.repo.git.checkout(ref) except GitCommandError as e: raise ModuleBuildFailException( f"Skipping {module.name} {ref} - failure when checking out due to gitlab error: " + str(e) ) if with_substitutions is not None: Builder.modify_makefiles_in_directory( pathlib.Path(str(module.repo.working_tree_dir)), with_substitutions, ) Builder.modify_install_path( module, repo_path=pathlib.Path(str(module.repo.working_tree_dir)), install_path=target_environment.root if isinstance(module, EPICSBaseSource) else target_environment.base_directory, ) logger.info( f"Building {module.name}, {ref} ({module.versions[ref]['version_string']})..." ) for target in module.targets: result = utils.run_make( pathlib.Path(str(module.repo.working_tree_dir)), f"-j{self.jobs}", target, ) if result.returncode: raise ModuleBuildFailException( f"Failure for {module.name} version {ref} on 'make {target}'" ) module.repo.git.reset("--hard") def _clone_repo(self, module: ModuleSource) -> Repo: """Clone a module repository to the build directory. If the module has already been cloned, fetches new changes. Raises: NotInitialisedError: If module was not initialised GitCommandError: If clone failed InvalidGitRepositoryError: If directory exists but is not a git repository """ module_path = self.build_dir / f"e3-{module.name}" logger.debug(f"Cloning {module.name!r} to {module_path.resolve()!s}") try: remote_url = ( module.project.ssh_url_to_repo if self.use_ssh else module.project.http_url_to_repo ) except NotInitialisedError as e: raise NotInitialisedError( f"Wrapper {module.name!r} is not initialised (no URL to the repository)" ) from e try: repo = Repo(module_path) logger.debug( f"Directory exists - trying to update remotes for {module.name!r} ({remote_url!r})" ) for remote in repo.remotes: remote.fetch() return repo except NoSuchPathError: try: return Repo.clone_from(remote_url, module_path) except GitCommandError as e: raise GitCommandError(f"Failure when cloning {module.name!r}") from e except InvalidGitRepositoryError as e: raise InvalidGitRepositoryError( f"Directory {module_path!r} exists, but it is not a git repository" ) from e @staticmethod
[docs] def modify_makefiles_in_directory( directory: pathlib.Path, substitutions: Dict[str, Dict[str, str]], ) -> None: """Apply substitutions.""" for file, change in substitutions.items(): path = directory / file old_fcontent = path.read_text() new_fcontent = utils.modify_makefile_definitions(old_fcontent, change) if new_fcontent and new_fcontent != old_fcontent: logger.debug(f"Substituting {file!r} with {change!r}") path.write_text(new_fcontent) else: logger.debug(f"No substitutions to be made in {file!r} ({change!r})")
@staticmethod
[docs] def modify_install_path( module: ModuleSource, *, repo_path: pathlib.Path, install_path: pathlib.Path, ) -> None: """Modify the install path for a module.""" utils.modify_config_file( repo_path / module.install_config_file, module.install_config_var, str(install_path), )