#!/usr/bin/env python # # __init__.py - Miscellaneous functions used throughout fsl_ci. # # Author: Paul McCarthy # import os.path as op import os import io import sys import errno import shlex import string import time import tempfile import contextlib as ctxlib import subprocess as sp __version__ = '0.6.2' """Current version of the fsl-ci-rules.""" USERNAME = 'fsl-ci-rules' """Username to be used for all git interactions which require one. """ EMAIL = 'fsl-ci-rules@git.fmrib.ox.ac.uk' """Password to be used for all git interactions which require one. """ def fprint(*args, **kwargs): """Print with flush=True. """ print(*args, **kwargs, flush=True) @ctxlib.contextmanager def tempdir(): """Context manager to create, and change into, a temporary directory, and then afterwards delete it and change back to the original working directory. """ with tempfile.TemporaryDirectory() as td: prevdir = os.getcwd() os.chdir(td) try: yield td finally: os.chdir(prevdir) @ctxlib.contextmanager def indir(dirname): """Context manager to change into a directory, and then afterwards change back to the original working directory. """ prevdir = os.getcwd() os.chdir(dirname) try: yield finally: os.chdir(prevdir) def sprun(cmd, **kwargs): """Runs the given command with subprocess.run. """ fprint(f'Running {cmd}') if not kwargs.get('shell', False): cmd = shlex.split(cmd) if 'check' not in kwargs: kwargs['check'] = True return sp.run(cmd, **kwargs) def is_valid_project_version(version): """Return True if the given version/tag is "valid" - it must be a sequence of integers, separated by periods, with an optional leading 'v'. """ if version.lower().startswith('v'): version = version[1:] for part in version.split('.'): if not all([c in string.digits for c in part]): return False return True @ctxlib.contextmanager def lockdir(dirname): """Primitive mechanism by which concurrent access to a directory can be prevented. Attempts to create a semaphore file in the directory, but waits if that file already exists. Removes the file when finished. """ delay = 10 lockfile = op.join(dirname, '.fsl_ci.lockdir') while True: try: fprint(f'Attempting to lock {dirname} for exclusive access.') fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR) break except OSError as e: if e.errno != errno.EEXIST: raise e fprint(f'{dirname} is already locked - ' f'trying again in {delay} seconds ...') time.sleep(10) fprint(f'Exclusive access acquired for {dirname} ...') try: yield finally: fprint(f'Relinquishing lock on {dirname}') os.close( fd) os.unlink(lockfile) class CaptureStdout: """Context manager which captures stdout and stderr. """ def __init__(self): self.reset() def reset(self): self.__mock_stdout = io.StringIO('') self.__mock_stderr = io.StringIO('') self.__mock_stdout.mode = 'w' self.__mock_stderr.mode = 'w' return self def __enter__(self): self.__real_stdout = sys.stdout self.__real_stderr = sys.stderr sys.stdout = self.__mock_stdout sys.stderr = self.__mock_stderr def __exit__(self, *args, **kwargs): sys.stdout = self.__real_stdout sys.stderr = self.__real_stderr @property def stdout(self): self.__mock_stdout.seek(0) return self.__mock_stdout.read() @property def stderr(self): self.__mock_stderr.seek(0) return self.__mock_stderr.read() def health_check(): """Rudimentary health check of the fsl-ci-rules codebase. """ print(f'fsl-ci-rules version {__version__}') import fsl_ci.conda import fsl_ci.gitlab import fsl_ci.scripts.build_conda_package import fsl_ci.scripts.deploy_conda_package import fsl_ci.scripts.trigger_package_build import fsl_ci.scripts.update_conda_recipe import fsl_ci.scripts.run_unit_tests import fsl_ci.utils.configure_repositories import fsl_ci.utils.create_conda_recipe import fsl_ci.utils.fsl_project_dependencies import fsl_ci.utils.package_status import fsl_ci.utils.set_gitlab_variables import fsl_ci.utils.trigger_build import fsl_ci.utils.trigger_pipeline