# -*- coding: utf-8 -*- ############################################################################### # LAYMAN OVERLAY SOURCE BASE CLASS ############################################################################### # File: source.py # # Base class for the different overlay types. # # Copyright: # (c) 2010 Sebastian Pipping # Distributed under the terms of the GNU General Public License v2 # # Author(s): # Sebastian Pipping import os import copy import sys import shutil import subprocess from layman.utils import path supported_cache = {} def _supported(key, check_supported=None): """internal caching function that checks tracks any un-supported/supported repo types.""" if key is None: return False if key not in supported_cache: supported_cache[key] = check_supported() return supported_cache[key] def _resolve_command(command, _output): if os.path.isabs(command): if not os.path.exists(command): _output('Program "%s" not found' % command) return ('File', None) return ('File', command) else: kind = 'Command' env_path = os.environ['PATH'] for d in env_path.split(os.pathsep): f = os.path.join(d, command) if os.path.exists(f): return ('Command', f) _output('Cound not resolve command ' +\ '"%s" based on PATH "%s"' % (command, env_path)) return ('Command', None) def require_supported(binaries, _output): for command, mtype, package in binaries: kind, path = _resolve_command(command, _output) if not path: if _output: _output(kind + ' ' + command + ' seems to be missing!' ' Overlay type "' + mtype + '" not support' 'ed. Did you emerge ' + package + '?') return False return True class OverlaySource(object): type_key = None def __init__(self, parent, config, _location, ignore = 0): self.parent = parent self.src = _location self.config = config self.ignore = ignore self.output = config['output'] def __eq__(self, other): return self.src == other.src def __ne__(self, other): return not self.__eq__(other) def add(self, base): '''Add the overlay.''' mdir = path([base, self.parent.name]) if os.path.exists(mdir): self.output.error('Directory ' + mdir + ' already exists. Will not overwrite its contents!') return False os.makedirs(mdir) return True def sync(self, base): '''Sync the overlay.''' pass def delete(self, base): '''Delete the overlay.''' mdir = path([base, self.parent.name]) if not os.path.exists(mdir): self.output.warn('Directory ' + mdir + \ ' did not exist, no files deleted.') return False self.output.info('Deleting directory "%s"' % mdir, 2) shutil.rmtree(mdir) return True def supported(self): '''Is the overlay type supported?''' return True def is_supported(self): '''Is the overlay type supported?''' return _supported(self.get_type_key(), self.supported) def get_type_key(self): return '%s' % self.__class__.type_key def command(self): return self.config['%s_command' % self.__class__.type_key] def run_command(self, command, args, **kwargs): self.output.debug("OverlaySource.run_command(): " + command, 6) file_to_run = _resolve_command(command, self.output.error)[1] args = [file_to_run] + args assert('pwd' not in kwargs) # Bug detector self.output.debug("OverlaySource.run_command(): cleared 'assert'", 7) cwd = kwargs.get('cwd', None) env = None env_updates = None if 'env' in kwargs: # Build actual env from surrounding plus updates env_updates = kwargs['env'] env = copy.copy(os.environ) env.update(env_updates) command_repr = ' '.join(args) if env_updates is not None: command_repr = '%s %s' % (' '.join('%s=%s' % (k, v) for (k, v) in sorted(env_updates.items())), command_repr) if cwd is not None: command_repr = '( cd %s && %s )' % (cwd, command_repr) cmd = kwargs.get('cmd', '') self.output.info('Running %s... # %s' % (cmd, command_repr), 2) if self.config['quiet']: input_source = subprocess.PIPE output_target = open('/dev/null', 'w') else: # Re-use parent file descriptors input_source = None output_target = None proc = subprocess.Popen(args, stdin=input_source, stdout=output_target, stderr=self.config['stderr'], cwd=cwd, env=env) if self.config['quiet']: # Make child non-interactive proc.stdin.close() try: result = proc.wait() except KeyboardInterrupt: self.output.info('Interrupted manually', 2) self.output.warn("Checking for cleanup actions to perform", 4) self.cleanup() result = 1 except Exception, err: self.output.error( 'Unknown exception running command: %s' % command_repr) self.output.error('Original error was: %s' % str(err)) result = 1 if self.config['quiet']: output_target.close() if result: self.output.info('Failure result returned from %s' % cmd , 2) return result def postsync(self, failed_sync, **kwargs): """Runs any repo specific postsync operations """ # check if the add/sync operation succeeded if failed_sync: return failed_sync # good to continue postsync_opt = self.config['%s_postsync' % self.__class__.type_key] if len(postsync_opt): # repalce "%cwd=" while it's still a string' _opt = postsync_opt.replace('%cwd=', kwargs.get('cwd', '')).split() command = _opt[0] args = _opt[1:] return self.run_command(command, args, cmd='%s_postsync' % self.__class__.type_key) return failed_sync def to_xml_hook(self, repo_elem): pass def cleanup(self): '''cleanup a failed/interrupted process overridden in subclass if it is needed.''' pass