diff --git a/libraries/entropy/client/interfaces/client.py b/libraries/entropy/client/interfaces/client.py index 1df7743d4..06178151f 100644 --- a/libraries/entropy/client/interfaces/client.py +++ b/libraries/entropy/client/interfaces/client.py @@ -453,7 +453,7 @@ class ClientSystemSettingsPlugin(SystemSettingsPlugin): 'filesbackup': etpConst['filesbackup'], 'forcedupdates': etpConst['forcedupdates'], 'packagehashes': etpConst['packagehashes'], - 'ignore_spm_downgrades': etpConst['spm']['ignore-spm-downgrades'], + 'ignore_spm_downgrades': False, 'collisionprotect': etpConst['collisionprotect'], 'configprotect': etpConst['configprotect'][:], 'configprotectmask': etpConst['configprotectmask'][:], diff --git a/libraries/entropy/client/interfaces/dep.py b/libraries/entropy/client/interfaces/dep.py index 517a8ed94..1c4adaea4 100644 --- a/libraries/entropy/client/interfaces/dep.py +++ b/libraries/entropy/client/interfaces/dep.py @@ -24,7 +24,7 @@ class CalculatorsMixin: # get all the installed packages installed_packages = dbconn.listAllIdpackages() - pdepend_id = etpConst['spm']['pdepend_id'] + pdepend_id = etpConst['dependency_type_ids']['pdepend_id'] deps_not_matched = set() # now look length = len(installed_packages) @@ -1553,7 +1553,7 @@ class CalculatorsMixin: monotree = set(idpackages) # monodimensional tree # post-dependencies won't be pulled in - pdepend_id = etpConst['spm']['pdepend_id'] + pdepend_id = etpConst['dependency_type_ids']['pdepend_id'] # check if dependstable is sane before beginning self.clientDbconn.retrieveReverseDependencies(idpackages[0]) count = 0 diff --git a/libraries/entropy/client/interfaces/repository.py b/libraries/entropy/client/interfaces/repository.py index 543b537b7..77ecb9db5 100644 --- a/libraries/entropy/client/interfaces/repository.py +++ b/libraries/entropy/client/interfaces/repository.py @@ -1146,18 +1146,19 @@ class Repository: self.Entropy.update_repository_revision(repo) if self.Entropy.indexing: self.do_database_indexing(repo) - def_repoid = my_repos['default_repository'] - if repo == def_repoid: - try: - self.run_config_files_updates(repo) - except Exception as e: - entropy.tools.print_traceback() - mytxt = "%s: %s" % ( - blue(_("Configuration files update error, not critical, continuing")), - darkred(repr(e)), - ) - self.Entropy.updateProgress(mytxt, importance = 0, - type = "info", header = blue(" # "),) + + try: + spm_class = self.Entropy.Spm_class() + spm_class.entropy_post_repository_update_hook(self.Entropy, + repo) + except Exception as e: + entropy.tools.print_traceback() + mytxt = "%s: %s" % ( + blue(_("Configuration files update error, not critical, continuing")), + darkred(repr(e)), + ) + self.Entropy.updateProgress(mytxt, importance = 0, + type = "info", header = blue(" # "),) # execute post update repo hook self._run_post_update_repository_hook(repo) @@ -1207,197 +1208,6 @@ class Repository: return 0 - def run_config_files_updates(self, repo): - - # are we root? - if etpConst['uid'] != 0: - self.Entropy.updateProgress( - brown(_("Skipping configuration files update, you are not root.")), - importance = 1, - type = "info", - header = blue(" @@ ") - ) - return - - # make.conf - self._config_updates_make_conf(repo) - self._config_updates_make_profile(repo) - - - def _config_updates_make_conf(self, repo): - - ## WARNING: it doesn't handle multi-line variables, yet. remember this. - system_make_conf = etpConst['spm']['global_make_conf'] - - avail_data = self.Entropy.SystemSettings['repositories']['available'] - repo_dbpath = avail_data[repo]['dbpath'] - repo_make_conf = os.path.join(repo_dbpath, - os.path.basename(system_make_conf)) - - if not (os.path.isfile(repo_make_conf) and \ - os.access(repo_make_conf, os.R_OK)): - return - - make_conf_variables_check = ["CHOST"] - - if not os.path.isfile(system_make_conf): - self.Entropy.updateProgress( - "%s %s. %s." % ( - red(system_make_conf), - blue(_("does not exist")), blue(_("Overwriting")), - ), - importance = 1, - type = "info", - header = blue(" @@ ") - ) - if os.path.lexists(system_make_conf): - shutil.move( - system_make_conf, - "%s.backup_%s" % (system_make_conf, - entropy.tools.get_random_number(),) - ) - shutil.copy2(repo_make_conf, system_make_conf) - - elif os.access(system_make_conf, os.W_OK): - - repo_f = open(repo_make_conf, "r") - sys_f = open(system_make_conf, "r") - repo_make_c = [x.strip() for x in repo_f.readlines()] - sys_make_c = [x.strip() for x in sys_f.readlines()] - repo_f.close() - sys_f.close() - - # read repository settings - repo_data = {} - for setting in make_conf_variables_check: - for line in repo_make_c: - if line.startswith(setting+"="): - # there can't be bash vars with a space - # after its name on declaration - repo_data[setting] = line - # I don't break, because there might be - # other overlapping settings - - differences = {} - # update make.conf data in memory - for setting in repo_data: - for idx in range(len(sys_make_c)): - line = sys_make_c[idx] - - if line.startswith(setting+"=") and \ - (line != repo_data[setting]): - - # there can't be bash vars with a - # space after its name on declaration - self.Entropy.updateProgress( - "%s: %s %s. %s." % ( - red(system_make_conf), bold(repr(setting)), - blue(_("variable differs")), red(_("Updating")), - ), - importance = 1, - type = "info", - header = blue(" @@ ") - ) - differences[setting] = repo_data[setting] - line = repo_data[setting] - sys_make_c[idx] = line - - if differences: - - self.Entropy.updateProgress( - "%s: %s." % ( - red(system_make_conf), - blue(_("updating critical variables")), - ), - importance = 1, - type = "info", - header = blue(" @@ ") - ) - # backup user make.conf - shutil.copy2(system_make_conf, - "%s.entropy_backup" % (system_make_conf,)) - - self.Entropy.updateProgress( - "%s: %s." % ( - red(system_make_conf), - darkgreen("writing changes to disk"), - ), - importance = 1, - type = "info", - header = blue(" @@ ") - ) - # write to disk, safely - tmp_make_conf = "%s.entropy_write" % (system_make_conf,) - f = open(tmp_make_conf, "w") - for line in sys_make_c: f.write(line+"\n") - f.flush() - f.close() - shutil.move(tmp_make_conf, system_make_conf) - - # update environment - for var in differences: - try: - myval = '='.join(differences[var].strip().split("=")[1:]) - if myval: - if myval[0] in ("'", '"',): myval = myval[1:] - if myval[-1] in ("'", '"',): myval = myval[:-1] - except IndexError: - myval = '' - os.environ[var] = myval - - def _config_updates_make_profile(self, repo): - - avail_data = self.Entropy.SystemSettings['repositories']['available'] - repo_dbpath = avail_data[repo]['dbpath'] - profile_link_name = etpConst['spm']['global_make_profile_link_name'] - - repo_make_profile = os.path.join(repo_dbpath, profile_link_name) - - if not (os.path.isfile(repo_make_profile) and \ - os.access(repo_make_profile, os.R_OK)): - return - - system_make_profile = etpConst['spm']['global_make_profile'] - - f = open(repo_make_profile, "r") - repo_profile_link_data = f.readline().strip() - f.close() - current_profile_link = '' - if os.path.islink(system_make_profile) and \ - os.access(system_make_profile, os.R_OK): - - current_profile_link = os.readlink(system_make_profile) - - if (repo_profile_link_data != current_profile_link) and \ - repo_profile_link_data: - - self.Entropy.updateProgress( - "%s: %s %s. %s." % ( - red(system_make_profile), blue("link"), - blue(_("differs")), red(_("Updating")), - ), - importance = 1, - type = "info", - header = blue(" @@ ") - ) - merge_sfx = ".entropy_merge" - os.symlink(repo_profile_link_data, system_make_profile+merge_sfx) - if entropy.tools.is_valid_path(system_make_profile+merge_sfx): - os.rename(system_make_profile+merge_sfx, system_make_profile) - else: - # revert change, link does not exist yet - self.Entropy.updateProgress( - "%s: %s %s. %s." % ( - red(system_make_profile), blue("new link"), - blue(_("does not exist")), red(_("Reverting")), - ), - importance = 1, - type = "info", - header = blue(" @@ ") - ) - os.remove(system_make_profile+merge_sfx) - - def check_entropy_updates(self): rc = False if not self.noEquoCheck: diff --git a/libraries/entropy/client/interfaces/trigger.py b/libraries/entropy/client/interfaces/trigger.py index d12a9c9f7..5aae29c14 100644 --- a/libraries/entropy/client/interfaces/trigger.py +++ b/libraries/entropy/client/interfaces/trigger.py @@ -17,18 +17,11 @@ from entropy.const import * from entropy.exceptions import * from entropy.output import * from entropy.i18n import _ +import entropy.tools class Trigger: VALID_PHASES = ("preinstall", "postinstall", "preremove", "postremove",) - ENV_VARS_DIR = etpConst['spm']['env_dir_reference'] - ENV_UPDATE_HOOK = etpConst['spm']['env_update_cmd'] - PHASES = { - 'preinstall': "preinstall", - 'postinstall': "postinstall", - 'preremove': "preremove", - 'postremove': "postremove", - } import entropy.tools as entropyTools def __init__(self, entropy_client, phase, pkgdata, package_action = None): @@ -89,22 +82,20 @@ class Trigger: functions = [] if self.spm_support: + spm_class = self.Entropy.Spm_class() + phases_map = spm_class.package_phases_map() while True: - if self.pkgdata['spm_phases'] != None: - if etpConst['spm']['postinst_phase'] not \ + if self.pkgdata['spm_phases'] is not None: + if phases_map.get('postinstall') not \ in self.pkgdata['spm_phases']: break functions.append(self.trigger_spm_postinstall) break cont_dirs = set((os.path.dirname(x) for x in self.pkgdata['content'])) - - if Trigger.ENV_VARS_DIR in cont_dirs: + ldpaths = entropy.tools.collect_linker_paths() + if len(cont_dirs) != len(cont_dirs - set(ldpaths)): functions.append(self.trigger_env_update) - else: - ldpaths = self.Entropy.entropyTools.collect_linker_paths() - if len(cont_dirs) != len(cont_dirs - set(ldpaths)): - functions.append(self.trigger_env_update) if self.pkgdata['trigger']: functions.append(self.trigger_call_ext_postinstall) @@ -117,9 +108,11 @@ class Trigger: # Portage phases if self.spm_support: + spm_class = self.Entropy.Spm_class() + phases_map = spm_class.package_phases_map() while True: if self.pkgdata['spm_phases'] != None: - if etpConst['spm']['preinst_phase'] not \ + if phases_map.get('preinstall') not \ in self.pkgdata['spm_phases']: break functions.append(self.trigger_spm_preinstall) @@ -137,12 +130,9 @@ class Trigger: cont_dirs = set((os.path.dirname(x) for x in \ self.pkgdata['removecontent'])) - if Trigger.ENV_VARS_DIR in cont_dirs: + ldpaths = entropy.tools.collect_linker_paths() + if len(cont_dirs) != len(cont_dirs - set(ldpaths)): functions.append(self.trigger_env_update) - else: - ldpaths = self.Entropy.entropyTools.collect_linker_paths() - if len(cont_dirs) != len(cont_dirs - set(ldpaths)): - functions.append(self.trigger_env_update) if self.pkgdata['trigger']: functions.append(self.trigger_call_ext_postremove) @@ -156,10 +146,12 @@ class Trigger: # Portage hook if self.spm_support: + spm_class = self.Entropy.Spm_class() + phases_map = spm_class.package_phases_map() while True: if self.pkgdata['spm_phases'] != None: - if etpConst['spm']['prerm_phase'] not \ + if phases_map.get('preremove') not \ in self.pkgdata['spm_phases']: break functions.append(self.trigger_spm_preremove) @@ -169,7 +161,7 @@ class Trigger: # in place and also because doesn't make any difference while True: if self.pkgdata['spm_phases'] != None: - if etpConst['spm']['postrm_phase'] not \ + if phases_map.get('postremove') not \ in self.pkgdata['spm_phases']: break functions.append(self.trigger_spm_postremove) @@ -386,7 +378,7 @@ class Trigger: tg_pfx = "%s/trigger-" % (etpConst['entropyunpackdir'],) while True: - triggerfile = "%s%s" % (tg_pfx, self.Entropy.entropyTools.get_random_number(),) + triggerfile = "%s%s" % (tg_pfx, entropy.tools.get_random_number(),) if not os.path.isfile(triggerfile): break triggerdir = os.path.dirname(triggerfile) @@ -422,20 +414,13 @@ class Trigger: return my.run(self.phase, self.pkgdata, triggerfile) def trigger_env_update(self): - self.Entropy.clientLog.log( ETP_LOGPRI_INFO, ETP_LOGLEVEL_NORMAL, - "[POST] Running env-update" + "[POST] Running env_update" ) - kwargs = {} - if etpUi['mute']: - kwargs['stdout'] = self.Entropy.clientLog - kwargs['stderr'] = self.Entropy.clientLog - - args = (Trigger.ENV_UPDATE_HOOK,) - proc = subprocess.Popen(args, **kwargs) - proc.wait() + self.Spm.environment_update(stdin = self.Entropy.clientLog, + stdout = self.Entropy.clientLog) def trigger_spm_postinstall(self): @@ -444,8 +429,7 @@ class Trigger: importance = 0, header = red(" ## ") ) - return self.Spm.execute_package_phase(self.pkgdata, - Trigger.PHASES['postinstall']) + return self.Spm.execute_package_phase(self.pkgdata, 'postinstall') def trigger_spm_preinstall(self): @@ -454,8 +438,7 @@ class Trigger: importance = 0, header = red(" ## ") ) - return self.Spm.execute_package_phase(self.pkgdata, - Trigger.PHASES['preinstall']) + return self.Spm.execute_package_phase(self.pkgdata, 'preinstall') def trigger_spm_preremove(self): @@ -464,8 +447,7 @@ class Trigger: importance = 0, header = red(" ## ") ) - return self.Spm.execute_package_phase(self.pkgdata, - Trigger.PHASES['preremove']) + return self.Spm.execute_package_phase(self.pkgdata, 'preremove') def trigger_spm_postremove(self): @@ -474,5 +456,4 @@ class Trigger: importance = 0, header = red(" ## ") ) - return self.Spm.execute_package_phase(self.pkgdata, - Trigger.PHASES['postremove']) + return self.Spm.execute_package_phase(self.pkgdata, 'postremove') diff --git a/libraries/entropy/const.py b/libraries/entropy/const.py index 95d4b14c5..31918476c 100644 --- a/libraries/entropy/const.py +++ b/libraries/entropy/const.py @@ -507,69 +507,11 @@ def const_default_settings(rootdir): 'default_nice': 0, # Default download socket timeout for Entropy Client transceivers 'default_download_timeout': 20, - 'spm': { + # Entropy package dependencies type identifiers + 'dependency_type_ids': { '(r)depend_id': 0, 'pdepend_id': 1, 'mdepend_id': 2, # actually, this is entropy-only - 'ebuild_file_extension': "ebuild", - 'preinst_phase': "preinst", - 'postinst_phase': "postinst", - 'prerm_phase': "prerm", - 'postrm_phase': "postrm", - 'setup_phase': "setup", - 'compile_phase': "compile", - 'install_phase': "install", - 'unpack_phase': "unpack", - 'ebuild_pkg_tag_var': "ENTROPY_PROJECT_TAG", - 'global_make_conf': rootdir+"/etc/make.conf", - 'global_package_keywords': rootdir+"/etc/portage/package.keywords", - 'global_package_use': rootdir+"/etc/portage/package.use", - 'global_package_mask': rootdir+"/etc/portage/package.mask", - 'global_package_unmask': rootdir+"/etc/portage/package.unmask", - 'global_make_profile': rootdir+"/etc/make.profile", - 'global_make_profile_link_name': "profile.link", - # source package manager executable - 'exec': rootdir+"/usr/bin/emerge", - 'env_dir_reference': "/etc/env.d", - 'env_update_cmd': "/usr/sbin/env-update", - 'source_profile': ["source", "/etc/profile"], - 'ask_cmd': "--ask", - 'info_cmd': "--info", - 'remove_cmd': "-C", - 'nodeps_cmd': "--nodeps", - 'fetchonly_cmd': "--fetchonly", - 'buildonly_cmd': "--buildonly", - 'oneshot_cmd': "--oneshot", - 'pretend_cmd': "--pretend", - 'verbose_cmd': "--verbose", - 'nocolor_cmd': "--color=n", - 'backend': "portage", # default one - 'xpak_entries': { - 'description': "DESCRIPTION", - 'homepage': "HOMEPAGE", - 'chost': "CHOST", - 'category': "CATEGORY", - 'cflags': "CFLAGS", - 'cxxflags': "CXXFLAGS", - 'license': "LICENSE", - 'src_uri': "SRC_URI", - 'use': "USE", - 'iuse': "IUSE", - 'slot': "SLOT", - 'provide': "PROVIDE", - 'depend': "DEPEND", - 'rdepend': "RDEPEND", - 'pdepend': "PDEPEND", - 'needed': "NEEDED", - 'inherited': "INHERITED", - 'keywords': "KEYWORDS", - 'contents': "CONTENTS", - 'counter': "COUNTER", - 'defined_phases': "DEFINED_PHASES", - 'pf': "PF", - }, - 'system_packages': [], - 'ignore-spm-downgrades': False, }, # entropy client packages download speed limit (in kb/sec) diff --git a/libraries/entropy/core/settings/base.py b/libraries/entropy/core/settings/base.py index 262679dff..9294d9a3b 100644 --- a/libraries/entropy/core/settings/base.py +++ b/libraries/entropy/core/settings/base.py @@ -860,7 +860,7 @@ class SystemSettings(Singleton, EntropyPluginStore): 'proxy': etpConst['proxy'].copy(), 'name': etpConst['systemname'], 'log_level': etpConst['entropyloglevel'], - 'spm_backend': etpConst['spm']['backend'], + 'spm_backend': None, } etp_conf = self.__setting_files['system'] diff --git a/libraries/entropy/db/__init__.py b/libraries/entropy/db/__init__.py index 9c3939216..9b3842e90 100644 --- a/libraries/entropy/db/__init__.py +++ b/libraries/entropy/db/__init__.py @@ -1043,7 +1043,7 @@ class EntropyRepository(EntropyRepositoryPluginStore, TextInterface): if manual_dep in pkgdata['dependencies']: continue pkgdata['dependencies'][manual_dep] = \ - etpConst['spm']['mdepend_id'] + etpConst['dependency_type_ids']['mdepend_id'] # FIXME: this is Entropy Client related but also part of the # currently implemented metaphor, so let's wait to have a Rule @@ -1261,7 +1261,8 @@ class EntropyRepository(EntropyRepositoryPluginStore, TextInterface): for manual_dep in manual_deps: if manual_dep in dep_dict: continue - dep_dict[manual_dep] = etpConst['spm']['mdepend_id'] + dep_dict[manual_dep] = \ + etpConst['dependency_type_ids']['mdepend_id'] else: # force to None @@ -1937,7 +1938,7 @@ class EntropyRepository(EntropyRepositoryPluginStore, TextInterface): """ mydict = {} for manual_dep in manual_deps: - mydict[manual_dep] = etpConst['spm']['mdepend_id'] + mydict[manual_dep] = etpConst['dependency_type_ids']['mdepend_id'] return self.insertDependencies(idpackage, mydict) def removeContent(self, idpackage): @@ -4127,7 +4128,7 @@ class EntropyRepository(EntropyRepositoryPluginStore, TextInterface): @type extended: bool """ return self.retrieveDependencies(idpackage, extended = extended, - deptype = etpConst['spm']['pdepend_id']) + deptype = etpConst['dependency_type_ids']['pdepend_id']) def retrieveManualDependencies(self, idpackage, extended = False): """ @@ -4141,7 +4142,7 @@ class EntropyRepository(EntropyRepositoryPluginStore, TextInterface): @type extended: bool """ return self.retrieveDependencies(idpackage, extended = extended, - deptype = etpConst['spm']['mdepend_id']) + deptype = etpConst['dependency_type_ids']['mdepend_id']) def retrieveDependencies(self, idpackage, extended = False, deptype = None, exclude_deptypes = None): @@ -4154,7 +4155,7 @@ class EntropyRepository(EntropyRepositoryPluginStore, TextInterface): composed by dependency name and dependency type) @type extended: bool @keyword deptype: return only given type of dependencies - see etpConst['spm']['*depend_id'] for dependency type + see etpConst['dependency_type_ids']['*depend_id'] for dependency type identifiers @type deptype: bool @keyword exclude_deptypes: list of dependency types to exclude diff --git a/libraries/entropy/server/interfaces/mirrors.py b/libraries/entropy/server/interfaces/mirrors.py index 1b5dfe673..c1d8c5af8 100644 --- a/libraries/entropy/server/interfaces/mirrors.py +++ b/libraries/entropy/server/interfaces/mirrors.py @@ -22,6 +22,7 @@ from entropy.i18n import _ from entropy.misc import RSS from entropy.transceivers import EntropyTransceiver from entropy.db import dbapi2 +import entropy.tools class Server: @@ -1252,33 +1253,32 @@ class Server: critical.append(ssl_server_cert) # Some information regarding how packages are built - spm_files = [ - (etpConst['spm']['global_make_conf'], "global_make_conf"), - (etpConst['spm']['global_package_keywords'], - "global_package_keywords"), - (etpConst['spm']['global_package_use'], "global_package_use"), - (etpConst['spm']['global_package_mask'], "global_package_mask"), - (etpConst['spm']['global_package_unmask'], "global_package_unmask"), - ] - for myfile, myname in spm_files: + spm_files_map = self.Entropy.Spm_class().config_files_map() + spm_syms = {} + for myname, myfile in spm_files_map.items(): + if os.path.islink(myfile): + spm_syms[myname] = myfile + continue # we don't want symlinks if os.path.isfile(myfile) and os.access(myfile, os.R_OK): data[myname] = myfile extra_text_files.append(myfile) - make_profile = etpConst['spm']['global_make_profile'] - rnd_tmp_file = self.Entropy.entropyTools.get_random_temp_file() - mytmpdir = os.path.dirname(rnd_tmp_file) - mytmpfile = os.path.join(mytmpdir, - etpConst['spm']['global_make_profile_link_name']) - extra_text_files.append(mytmpfile) + # XXX/FIXME: for symlinks, we read their link and send a file with that + # content. This is the default behaviour for now and allows to send + # /etc/make.profile link pointer correctly. + for symname, symfile in spm_syms.items(): - if os.path.islink(make_profile): - mylink = os.readlink(make_profile) + tmp_file = entropy.tools.get_random_temp_file() + mytmpdir = os.path.dirname(tmp_file) + mytmpfile = os.path.join(mytmpdir, os.path.basename(symfile)) + extra_text_files.append(mytmpfile) + + mylink = os.readlink(symfile) f_mkp = open(mytmpfile, "w") f_mkp.write(mylink) f_mkp.flush() f_mkp.close() - data['global_make_profile'] = mytmpfile + data[symname] = mytmpfile return data, critical, extra_text_files @@ -3033,7 +3033,7 @@ class Server: header = darkred(" !!! ") ) - exc_txt = self.Entropy.entropyTools.print_exception( + exc_txt = entropy.tools.print_exception( returndata = True) for line in exc_txt: self.Entropy.updateProgress( diff --git a/libraries/entropy/services/system/executors.py b/libraries/entropy/services/system/executors.py index eb071b903..98ef37b50 100644 --- a/libraries/entropy/services/system/executors.py +++ b/libraries/entropy/services/system/executors.py @@ -155,87 +155,85 @@ class Base: stdout_err.close() return True, rc - def compile_atoms( self, - queue_id, atoms, - pretend = False, oneshot = False, - verbose = True, nocolor = True, - fetchonly = False, buildonly = False, - nodeps = False, custom_use = '', ldflags = '', cflags = '' - ): + def compile_atoms( + self, + queue_id, atoms, + pretend = False, oneshot = False, + verbose = True, nocolor = True, + fetchonly = False, buildonly = False, + nodeps = False, custom_use = '', ldflags = '', cflags = ''): - queue_data, key = self.SystemManagerExecutor.SystemInterface.get_item_by_queue_id(queue_id, copy = True) + sys_intf = self.SystemManagerExecutor.SystemInterface + queue_data, key = sys_intf.get_item_by_queue_id(queue_id, copy = True) if queue_data == None: return False, 'no item in queue' + def set_proc_pid(pid): + self._set_processing_pid(queue_id, pid) + stdout_err = open(queue_data['stdout'], "a+") - - cmd = [etpConst['spm']['env_update_cmd'], "&&"] - cmd += etpConst['spm']['source_profile']+["&&"] - if custom_use: - cmd += ['export USE="']+custom_use.strip().split()+['"', '&&'] - if ldflags: - cmd += ['export LDFLAGS="']+custom_use.strip().split()+['"', '&&'] - if cflags: - cmd += ['export CFLAGS="']+custom_use.strip().split()+['"', '&&'] - cmd += [etpConst['spm']['exec']]+atoms - if pretend: - cmd.append(etpConst['spm']['pretend_cmd']) - if verbose: - cmd.append(etpConst['spm']['verbose_cmd']) - if oneshot: - cmd.append(etpConst['spm']['oneshot_cmd']) - if nocolor: - cmd.append(etpConst['spm']['nocolor_cmd']) - if fetchonly: - cmd.append(etpConst['spm']['fetchonly_cmd']) - if buildonly: - cmd.append(etpConst['spm']['buildonly_cmd']) - if nodeps: - cmd.append(etpConst['spm']['nodeps_cmd']) - - stdout_err.write("Preparing to spawn parameter: '%s'. Good luck mate!\n" % (' '.join(cmd),)) + stdout_err.write("Preparing to spawn compilation of: '%s'" + ". Good luck mate!\n" % (' '.join(atoms),)) stdout_err.flush() + env = {} + if ldflags: + env['LDFLAGS'] = ldflags + if cflags: + env['CFLAGS'] = cflags + try: - p = subprocess.Popen(' '.join(cmd), stdout = stdout_err, stderr = stdout_err, stdin = self._get_stdin(queue_id), shell = True) - self._set_processing_pid(queue_id, p.pid) - rc = p.wait() + + spm = sys_intf.Entropy.Spm() + status = spm.compile_packages(atoms, + stdin = self._get_stdin(queue_id), + stdout = stdour_err, stderr = stdout_err, environ = env, + pid_write_func = set_proc_pid, pretend = pretend, + verbose = verbose, fetch_only = fetchonly, + build_only = buildonly, no_dependencies = nodeps, + coloured_output = not nocolor, oneshot = oneshot) + finally: + stdout_err.write("\n### Done ###\n") stdout_err.flush() stdout_err.close() - return True, rc + + return True, status def spm_remove_atoms(self, queue_id, atoms, pretend = True, verbose = True, nocolor = True): - queue_data, key = self.SystemManagerExecutor.SystemInterface.get_item_by_queue_id(queue_id, copy = True) + sys_intf = self.SystemManagerExecutor.SystemInterface + queue_data, key = sys_intf.get_item_by_queue_id(queue_id, copy = True) if queue_data == None: return False, 'no item in queue' + def set_proc_pid(pid): + self._set_processing_pid(queue_id, pid) + stdout_err = open(queue_data['stdout'], "a+") - - cmd = [etpConst['spm']['env_update_cmd'], "&&"] - cmd += etpConst['spm']['source_profile']+["&&"] - cmd += [etpConst['spm']['exec'], etpConst['spm']['remove_cmd']]+atoms - if pretend: - cmd.append(etpConst['spm']['pretend_cmd']) - if verbose: - cmd.append(etpConst['spm']['verbose_cmd']) - if nocolor: - cmd.append(etpConst['spm']['nocolor_cmd']) - - stdout_err.write("Preparing to spawn parameter: '%s'. Good luck mate!\n" % (' '.join(cmd),)) + stdout_err.write("Preparing to spawn removal of: '%s'" + ". Good luck mate!\n" % (' '.join(atoms),)) stdout_err.flush() try: - p = subprocess.Popen(' '.join(cmd), stdout = stdout_err, stderr = stdout_err, stdin = self._get_stdin(queue_id), shell = True) - self._set_processing_pid(queue_id, p.pid) - rc = p.wait() + + spm = sys_intf.Entropy.Spm() + status = spm.remove_packages(atoms, + stdin = self._get_stdin(queue_id), + stdout = stdour_err, stderr = stdout_err, + pid_write_func = set_proc_pid, pretend = pretend, + verbose = verbose, fetch_only = fetchonly, + build_only = buildonly, no_dependencies = nodeps, + coloured_output = not nocolor, oneshot = oneshot) + finally: + stdout_err.write("\n### Done ###\n") stdout_err.flush() stdout_err.close() - return True, rc + + return True, status def enable_uses_for_atoms(self, queue_id, atoms, useflags): @@ -350,26 +348,33 @@ class Base: def run_spm_info(self, queue_id): - queue_data, key = self.SystemManagerExecutor.SystemInterface.get_item_by_queue_id(queue_id, copy = True) + sys_intf = self.SystemManagerExecutor.SystemInterface + queue_data, key = sys_intf.get_item_by_queue_id(queue_id, copy = True) if queue_data == None: return False, 'no item in queue' + def set_proc_pid(pid): + self._set_processing_pid(queue_id, pid) + stdout_err = open(queue_data['stdout'], "a+") - - cmd = [etpConst['spm']['exec'], etpConst['spm']['info_cmd']] - - stdout_err.write("Preparing to spawn parameter: '%s'. Good luck mate!\n" % (' '.join(cmd),)) + stdout_err.write("Preparing to spawn SPM info. Good luck mate!\n") stdout_err.flush() try: - p = subprocess.Popen(cmd, stdout = stdout_err, stderr = stdout_err, stdin = self._get_stdin(queue_id)) - self._set_processing_pid(queue_id, p.pid) - rc = p.wait() + + spm = sys_intf.Entropy.Spm() + status = spm.print_build_environment_info( + stdin = self._get_stdin(queue_id), + stdout = stdour_err, stderr = stdout_err, + pid_write_func = set_proc_pid, coloured_output = False) + finally: + stdout_err.write("\n### Done ###\n") stdout_err.flush() stdout_err.close() - return True, rc + + return True, status def run_custom_shell_command(self, queue_id, command): @@ -379,16 +384,14 @@ class Base: stdout_err = open(queue_data['stdout'], "a+") - cmd = [etpConst['spm']['env_update_cmd'], "&&"] - cmd += etpConst['spm']['source_profile']+[";"] - cmd += command.split() - + cmd = command.split() cmd = ' '.join(cmd) stdout_err.write("Preparing to spawn parameter: '%s'. Good luck mate!\n" % (cmd,)) stdout_err.flush() try: - p = subprocess.Popen(cmd, stdout = stdout_err, stderr = stdout_err, stdin = self._get_stdin(queue_id), shell = True) + p = subprocess.Popen(cmd, stdout = stdout_err, stderr = stdout_err, + stdin = self._get_stdin(queue_id), shell = True) self._set_processing_pid(queue_id, p.pid) rc = p.wait() finally: diff --git a/libraries/entropy/spm/plugins/factory.py b/libraries/entropy/spm/plugins/factory.py index e921f7c1d..65a1c6925 100644 --- a/libraries/entropy/spm/plugins/factory.py +++ b/libraries/entropy/spm/plugins/factory.py @@ -16,16 +16,41 @@ from entropy.i18n import _ from entropy.spm.plugins.skel import SpmPlugin import entropy.spm.plugins.interfaces as plugs -settings = SystemSettings() -default_plugin = settings['system'].get('spm_backend', - etpConst['spm']['backend']) +_settings = SystemSettings() +_USER_PLUG = _settings['system'].get('spm_backend') FACTORY = EntropyPluginFactory(SpmPlugin, plugs, - default_plugin_name = default_plugin, - fallback_plugin_name = etpConst['spm']['backend']) + default_plugin_name = _USER_PLUG) get_available_plugins = FACTORY.get_available_plugins -get_default_class = FACTORY.get_default_plugin + +def get_default_class(): + """ + Return default Source Package Manager plugin class. + + @return: default Source Package Manager plugin class + @raise SystemError: if no default plugin class has been specified. + This usually means a programming error. + """ + fallback_used = False + myplugs = get_available_plugins() + if _USER_PLUG is not None: + user_plugin = myplugs.get(_USER_PLUG) + if user_plugin is not None: + return user_plugin + fallback_used = True + + for plug_id in sorted(myplugs): + plug_class = myplugs[plug_id] + if plug_class.IS_DEFAULT: + if fallback_used: + import warnings + warnings.warn("%s: %s" % ( + "User configured Source Package Manager Plugin not available, " + "using fallback", plug_id,)) + return plug_class + + raise SystemError("no SPM default plugin configured") def get_default_instance(output_interface): """ diff --git a/libraries/entropy/spm/plugins/interfaces/portage_plugin/__init__.py b/libraries/entropy/spm/plugins/interfaces/portage_plugin/__init__.py index fab527234..d9c704e58 100644 --- a/libraries/entropy/spm/plugins/interfaces/portage_plugin/__init__.py +++ b/libraries/entropy/spm/plugins/interfaces/portage_plugin/__init__.py @@ -14,6 +14,7 @@ import bz2 import sys import shutil import tempfile +import subprocess from entropy.const import etpConst, etpUi, const_get_stringtype, \ const_convert_to_unicode, const_convert_to_rawstring from entropy.exceptions import FileNotFound, SPMError, InvalidDependString, \ @@ -155,7 +156,52 @@ class PortagePlugin(SpmPlugin): "downgrade", "unavailable" ] - package_phases_map = { + xpak_entries = { + 'description': "DESCRIPTION", + 'homepage': "HOMEPAGE", + 'chost': "CHOST", + 'category': "CATEGORY", + 'cflags': "CFLAGS", + 'cxxflags': "CXXFLAGS", + 'license': "LICENSE", + 'src_uri': "SRC_URI", + 'use': "USE", + 'iuse': "IUSE", + 'slot': "SLOT", + 'provide': "PROVIDE", + 'depend': "DEPEND", + 'rdepend': "RDEPEND", + 'pdepend': "PDEPEND", + 'needed': "NEEDED", + 'inherited': "INHERITED", + 'keywords': "KEYWORDS", + 'contents': "CONTENTS", + 'counter': "COUNTER", + 'defined_phases': "DEFINED_PHASES", + 'pf': "PF", + } + + _ebuild_entries = { + 'ebuild_pkg_tag_var': "ENTROPY_PROJECT_TAG", + } + + _cmd_map = { + 'env_update_cmd': "/usr/sbin/env-update", + 'ask_cmd': "--ask", + 'info_cmd': "--info", + 'remove_cmd': "-C", + 'nodeps_cmd': "--nodeps", + 'fetchonly_cmd': "--fetchonly", + 'buildonly_cmd': "--buildonly", + 'oneshot_cmd': "--oneshot", + 'pretend_cmd': "--pretend", + 'verbose_cmd': "--verbose", + 'nocolor_cmd': "--color=n", + 'source_profile_cmd': ["source", "/etc/profile"], + 'exec_cmd': "/usr/bin/emerge", + } + + _package_phases_map = { 'setup': 'setup', 'preinstall': 'preinst', 'postinstall': 'postinst', @@ -164,7 +210,16 @@ class PortagePlugin(SpmPlugin): 'configure': 'config', } - PLUGIN_API_VERSION = 2 + _config_files_map = { + 'global_make_conf': "/etc/make.conf", + 'global_package_keywords': "/etc/portage/package.keywords", + 'global_package_use': "/etc/portage/package.use", + 'global_package_mask': "/etc/portage/package.mask", + 'global_package_unmask': "/etc/portage/package.unmask", + 'global_make_profile': "/etc/make.profile", + } + + PLUGIN_API_VERSION = 3 SUPPORTED_MATCH_TYPES = [ "bestmatch-visible", "cp-list", "list-visible", "match-all", @@ -178,6 +233,7 @@ class PortagePlugin(SpmPlugin): 'portagetree': {}, } + IS_DEFAULT = True PLUGIN_NAME = 'portage' ENV_FILE_COMP = "environment.bz2" EBUILD_EXT = ".ebuild" @@ -349,7 +405,6 @@ class PortagePlugin(SpmPlugin): for package in self.portage.settings.packages: pkgs = self.match_installed_package(package, match_all = True) system.extend(pkgs) - system.extend(etpConst['spm']['system_packages']) return system def get_package_categories(self): @@ -768,12 +823,12 @@ class PortagePlugin(SpmPlugin): data['keywords'] = set(data['keywords']) needed_file = os.path.join(meta_dir, - etpConst['spm']['xpak_entries']['needed']) + PortagePlugin.xpak_entries['needed']) data['needed'] = self._extract_pkg_metadata_needed(needed_file) content_file = os.path.join(meta_dir, - etpConst['spm']['xpak_entries']['contents']) + PortagePlugin.xpak_entries['contents']) data['content'] = self._extract_pkg_metadata_content(content_file, package_file) data['disksize'] = entropy.tools.sum_file_sizes(data['content']) @@ -788,9 +843,9 @@ class PortagePlugin(SpmPlugin): if data['category'] != PortagePlugin.KERNEL_CATEGORY: kern_dep_key = self._add_kernel_dependency_to_pkg(data) - file_ext = etpConst['spm']['ebuild_file_extension'] + file_ext = PortagePlugin.EBUILD_EXT ebuilds_in_path = [x for x in os.listdir(meta_dir) if \ - x.endswith(".%s" % (file_ext,))] + x.endswith(file_ext)] if not data['versiontag'] and ebuilds_in_path: # has the user specified a custom package tag inside the ebuild @@ -853,12 +908,14 @@ class PortagePlugin(SpmPlugin): for x in portage_metadata['RDEPEND'].split(): if x.startswith("!") or (x in ("(", "||", ")", "")): continue - data['dependencies'][x] = etpConst['spm']['(r)depend_id'] + data['dependencies'][x] = \ + etpConst['dependency_type_ids']['(r)depend_id'] for x in portage_metadata['PDEPEND'].split(): if x.startswith("!") or (x in ("(", "||", ")", "")): continue - data['dependencies'][x] = etpConst['spm']['pdepend_id'] + data['dependencies'][x] = \ + etpConst['dependency_type_ids']['pdepend_id'] data['conflicts'] = [x.replace("!", "") for x in \ portage_metadata['RDEPEND'].split() + \ @@ -866,7 +923,8 @@ class PortagePlugin(SpmPlugin): x.startswith("!") and not x in ("(", "||", ")", "")] if kern_dep_key is not None: - data['dependencies'][kern_dep_key] = etpConst['spm']['(r)depend_id'] + data['dependencies'][kern_dep_key] = \ + etpConst['dependency_type_ids']['(r)depend_id'] # Conflicting tagged packages support # Needs Entropy Client System Settings Plugin, @@ -1172,6 +1230,119 @@ class PortagePlugin(SpmPlugin): return available + def compile_packages(self, packages, stdin = None, stdout = None, + stderr = None, environ = None, pid_write_func = None, + pretend = False, verbose = False, fetch_only = False, + build_only = False, no_dependencies = False, + ask = False, coloured_output = False, oneshot = False): + + cmd = [PortagePlugin._cmd_map['exec_cmd']] + if pretend: + cmd.append(PortagePlugin._cmd_map['pretend_cmd']) + if verbose: + cmd.append(PortagePlugin._cmd_map['verbose_cmd']) + if ask: + cmd.append(PortagePlugin._cmd_map['ask_cmd']) + if oneshot: + cmd.append(PortagePlugin._cmd_map['oneshot_cmd']) + if not coloured_output: + cmd.append(PortagePlugin._cmd_map['nocolor_cmd']) + if fetch_only: + cmd.append(PortagePlugin._cmd_map['fetchonly_cmd']) + if build_only: + cmd.append(PortagePlugin._cmd_map['buildonly_cmd']) + if no_dependencies: + cmd.append(PortagePlugin._cmd_map['nodeps_cmd']) + + cmd.extend(packages) + cmd_string = """\ + %s && %s && %s + """ % (PortagePlugin._cmd_map['env_update_cmd'], + PortagePlugin._cmd_map['source_profile_cmd'], + ' '.join(cmd) + ) + + env = os.environ.copy() + if environ is not None: + env.update(environ) + + proc = subprocess.Popen(cmd_string, stdout = stdout, stderr = stderr, + stdin = stdin, env = env, shell = True) + if pid_write_func is not None: + pid_write_func(proc.pid) + return proc.wait() + + def remove_packages(self, packages, stdin = None, stdout = None, + stderr = None, environ = None, pid_write_func = None, + pretend = False, verbose = False, no_dependencies = False, ask = False, + coloured_output = False): + + cmd = [PortagePlugin._cmd_map['exec_cmd'], + PortagePlugin._cmd_map['remove_cmd']] + if pretend: + cmd.append(PortagePlugin._cmd_map['pretend_cmd']) + if verbose: + cmd.append(PortagePlugin._cmd_map['verbose_cmd']) + if ask: + cmd.append(PortagePlugin._cmd_map['ask_cmd']) + if not coloured_output: + cmd.append(PortagePlugin._cmd_map['nocolor_cmd']) + if no_dependencies: + cmd.append(PortagePlugin._cmd_map['nodeps_cmd']) + + cmd.extend(packages) + cmd_string = """\ + %s && %s && %s + """ % (PortagePlugin._cmd_map['env_update_cmd'], + PortagePlugin._cmd_map['source_profile_cmd'], + ' '.join(cmd) + ) + + env = os.environ.copy() + if environ is not None: + env.update(environ) + + proc = subprocess.Popen(cmd_string, stdout = stdout, stderr = stderr, + stdin = stdin, env = env, shell = True) + if pid_write_func is not None: + pid_write_func(proc.pid) + return proc.wait() + + def environment_update(self, stdout = None, stderr = None): + kwargs = {} + if etpUi['mute']: + kwargs['stdout'] = stdout + kwargs['stderr'] = stderr + args = (PortagePlugin._cmd_map['env_update_cmd'],) + proc = subprocess.Popen(args, **kwargs) + proc.wait() + + def print_build_environment_info(self, stdin = None, stdout = None, + stderr = None, environ = None, pid_write_func = None, + coloured_output = False): + + cmd = [PortagePlugin._cmd_map['exec_cmd'], + PortagePlugin._cmd_map['info_cmd']] + if not coloured_output: + cmd.append(PortagePlugin._cmd_map['nocolor_cmd']) + + cmd_string = """\ + %s && %s && %s + """ % (PortagePlugin._cmd_map['env_update_cmd'], + PortagePlugin._cmd_map['source_profile_cmd'], + ' '.join(cmd) + ) + + env = os.environ.copy() + if environ is not None: + env.update(environ) + + proc = subprocess.Popen(cmd_string, stdout = stdout, stderr = stderr, + stdin = stdin, env = env, shell = True) + if pid_write_func is not None: + pid_write_func(proc.pid) + return proc.wait() + def get_installed_packages(self, categories = None, root = None): """ Reimplemented from SpmPlugin class. @@ -1225,7 +1396,7 @@ class PortagePlugin(SpmPlugin): root = root) counter_dir = os.path.dirname(dbbuild) - counter_name = etpConst['spm']['xpak_entries']['counter'] + counter_name = PortagePlugin.xpak_entries['counter'] counter_path = os.path.join(counter_dir, counter_name) if not os.access(counter_dir, os.W_OK): @@ -1243,7 +1414,7 @@ class PortagePlugin(SpmPlugin): """ Reimplemented from SpmPlugin class. """ - counter_path = etpConst['spm']['xpak_entries']['counter'] + counter_path = PortagePlugin.xpak_entries['counter'] entropy_atom = entropy_repository.retrieveAtom( entropy_repository_package_id) @@ -2079,11 +2250,25 @@ class PortagePlugin(SpmPlugin): entropy_repository_id, portage_dirs_digest) entropy_repository.commitChanges() + @staticmethod + def package_phases_map(): + """ + Reimplemented from SpmPlugin class. + """ + return PortagePlugin._package_phases_map.copy() + + @staticmethod + def config_files_map(): + """ + Reimplemented from SpmPlugin class. + """ + return PortagePlugin._config_files_map.copy() + def execute_package_phase(self, package_metadata, phase_name): """ Reimplemented from SpmPlugin class. """ - portage_phase = PortagePlugin.package_phases_map[phase_name] + portage_phase = PortagePlugin._package_phases_map[phase_name] phase_calls = { 'setup': self._pkg_setup, 'preinst': self._pkg_preinst, @@ -2396,6 +2581,207 @@ class PortagePlugin(SpmPlugin): return env_rc, msg + @staticmethod + def _config_updates_make_conf(entropy_client, repo): + + ## WARNING: it doesn't handle multi-line variables, yet. remember this. + system_make_conf = PortagePlugin._config_files_map['global_make_conf'] + + avail_data = entropy_client.SystemSettings['repositories']['available'] + repo_dbpath = avail_data[repo]['dbpath'] + repo_make_conf = os.path.join(repo_dbpath, + os.path.basename(system_make_conf)) + + if not (os.path.isfile(repo_make_conf) and \ + os.access(repo_make_conf, os.R_OK)): + return + + make_conf_variables_check = ["CHOST"] + + if not os.path.isfile(system_make_conf): + entropy_client.updateProgress( + "%s %s. %s." % ( + red(system_make_conf), + blue(_("does not exist")), blue(_("Overwriting")), + ), + importance = 1, + type = "info", + header = blue(" @@ ") + ) + if os.path.lexists(system_make_conf): + shutil.move( + system_make_conf, + "%s.backup_%s" % (system_make_conf, + entropy.tools.get_random_number(),) + ) + shutil.copy2(repo_make_conf, system_make_conf) + + elif os.access(system_make_conf, os.W_OK): + + repo_f = open(repo_make_conf, "r") + sys_f = open(system_make_conf, "r") + repo_make_c = [x.strip() for x in repo_f.readlines()] + sys_make_c = [x.strip() for x in sys_f.readlines()] + repo_f.close() + sys_f.close() + + # read repository settings + repo_data = {} + for setting in make_conf_variables_check: + for line in repo_make_c: + if line.startswith(setting+"="): + # there can't be bash vars with a space + # after its name on declaration + repo_data[setting] = line + # I don't break, because there might be + # other overlapping settings + + differences = {} + # update make.conf data in memory + for setting in repo_data: + for idx in range(len(sys_make_c)): + line = sys_make_c[idx] + + if line.startswith(setting+"=") and \ + (line != repo_data[setting]): + + # there can't be bash vars with a + # space after its name on declaration + entropy_client.updateProgress( + "%s: %s %s. %s." % ( + red(system_make_conf), bold(repr(setting)), + blue(_("variable differs")), red(_("Updating")), + ), + importance = 1, + type = "info", + header = blue(" @@ ") + ) + differences[setting] = repo_data[setting] + line = repo_data[setting] + sys_make_c[idx] = line + + if differences: + + entropy_client.updateProgress( + "%s: %s." % ( + red(system_make_conf), + blue(_("updating critical variables")), + ), + importance = 1, + type = "info", + header = blue(" @@ ") + ) + # backup user make.conf + shutil.copy2(system_make_conf, + "%s.entropy_backup" % (system_make_conf,)) + + entropy_client.updateProgress( + "%s: %s." % ( + red(system_make_conf), + darkgreen("writing changes to disk"), + ), + importance = 1, + type = "info", + header = blue(" @@ ") + ) + # write to disk, safely + tmp_make_conf = "%s.entropy_write" % (system_make_conf,) + f = open(tmp_make_conf, "w") + for line in sys_make_c: + f.write(line+"\n") + f.flush() + f.close() + shutil.move(tmp_make_conf, system_make_conf) + + # update environment + for var in differences: + try: + myval = '='.join(differences[var].strip().split("=")[1:]) + if myval: + if myval[0] in ("'", '"',): myval = myval[1:] + if myval[-1] in ("'", '"',): myval = myval[:-1] + except IndexError: + myval = '' + os.environ[var] = myval + + @staticmethod + def _config_updates_make_profile(entropy_client, repo): + + avail_data = entropy_client.SystemSettings['repositories']['available'] + repo_dbpath = avail_data[repo]['dbpath'] + profile_link = PortagePlugin._config_files_map['global_make_profile'] + profile_link_name = os.path.basename(profile_link) + + repo_make_profile = os.path.join(repo_dbpath, profile_link_name) + + if not (os.path.isfile(repo_make_profile) and \ + os.access(repo_make_profile, os.R_OK)): + return + + system_make_profile = \ + PortagePlugin._config_files_map['global_make_profile'] + + f = open(repo_make_profile, "r") + repo_profile_link_data = f.readline().strip() + f.close() + current_profile_link = '' + if os.path.islink(system_make_profile) and \ + os.access(system_make_profile, os.R_OK): + + current_profile_link = os.readlink(system_make_profile) + + if (repo_profile_link_data != current_profile_link) and \ + repo_profile_link_data: + + entropy_client.updateProgress( + "%s: %s %s. %s." % ( + red(system_make_profile), blue("link"), + blue(_("differs")), red(_("Updating")), + ), + importance = 1, + type = "info", + header = blue(" @@ ") + ) + merge_sfx = ".entropy_merge" + os.symlink(repo_profile_link_data, system_make_profile+merge_sfx) + if entropy.tools.is_valid_path(system_make_profile+merge_sfx): + os.rename(system_make_profile+merge_sfx, system_make_profile) + else: + # revert change, link does not exist yet + entropy_client.updateProgress( + "%s: %s %s. %s." % ( + red(system_make_profile), blue("new link"), + blue(_("does not exist")), red(_("Reverting")), + ), + importance = 1, + type = "info", + header = blue(" @@ ") + ) + os.remove(system_make_profile+merge_sfx) + + @staticmethod + def entropy_client_post_repository_update_hook(entropy_client, + entropy_repository_id): + + # are we root? + if etpConst['uid'] != 0: + entropy_client.updateProgress( + brown(_("Skipping configuration files update, you are not root.")), + importance = 1, + type = "info", + header = blue(" @@ ") + ) + return 0 + + default_repo = \ + entropy_client.SystemSettings['repositories']['default_repository'] + + if default_repo == entropy_repository_id: + PortagePlugin._config_updates_make_conf(entropy_repository_id) + PortagePlugin._config_updates_make_profile(entropy_repository_id) + + return 0 + @staticmethod def entropy_install_setup_hook(entropy_client, package_metadata): """ @@ -3280,83 +3666,83 @@ class PortagePlugin(SpmPlugin): def _extract_pkg_metadata_generate_extraction_dict(self): data = { 'pf': { - 'path': etpConst['spm']['xpak_entries']['pf'], + 'path': PortagePlugin.xpak_entries['pf'], 'critical': True, }, 'chost': { - 'path': etpConst['spm']['xpak_entries']['chost'], + 'path': PortagePlugin.xpak_entries['chost'], 'critical': True, }, 'description': { - 'path': etpConst['spm']['xpak_entries']['description'], + 'path': PortagePlugin.xpak_entries['description'], 'critical': False, }, 'homepage': { - 'path': etpConst['spm']['xpak_entries']['homepage'], + 'path': PortagePlugin.xpak_entries['homepage'], 'critical': False, }, 'slot': { - 'path': etpConst['spm']['xpak_entries']['slot'], + 'path': PortagePlugin.xpak_entries['slot'], 'critical': False, }, 'cflags': { - 'path': etpConst['spm']['xpak_entries']['cflags'], + 'path': PortagePlugin.xpak_entries['cflags'], 'critical': False, }, 'cxxflags': { - 'path': etpConst['spm']['xpak_entries']['cxxflags'], + 'path': PortagePlugin.xpak_entries['cxxflags'], 'critical': False, }, 'category': { - 'path': etpConst['spm']['xpak_entries']['category'], + 'path': PortagePlugin.xpak_entries['category'], 'critical': True, }, 'rdepend': { - 'path': etpConst['spm']['xpak_entries']['rdepend'], + 'path': PortagePlugin.xpak_entries['rdepend'], 'critical': False, }, 'pdepend': { - 'path': etpConst['spm']['xpak_entries']['pdepend'], + 'path': PortagePlugin.xpak_entries['pdepend'], 'critical': False, }, 'depend': { - 'path': etpConst['spm']['xpak_entries']['depend'], + 'path': PortagePlugin.xpak_entries['depend'], 'critical': False, }, 'use': { - 'path': etpConst['spm']['xpak_entries']['use'], + 'path': PortagePlugin.xpak_entries['use'], 'critical': False, }, 'iuse': { - 'path': etpConst['spm']['xpak_entries']['iuse'], + 'path': PortagePlugin.xpak_entries['iuse'], 'critical': False, }, 'license': { - 'path': etpConst['spm']['xpak_entries']['license'], + 'path': PortagePlugin.xpak_entries['license'], 'critical': False, }, 'provide': { - 'path': etpConst['spm']['xpak_entries']['provide'], + 'path': PortagePlugin.xpak_entries['provide'], 'critical': False, }, 'sources': { - 'path': etpConst['spm']['xpak_entries']['src_uri'], + 'path': PortagePlugin.xpak_entries['src_uri'], 'critical': False, }, 'eclasses': { - 'path': etpConst['spm']['xpak_entries']['inherited'], + 'path': PortagePlugin.xpak_entries['inherited'], 'critical': False, }, 'counter': { - 'path': etpConst['spm']['xpak_entries']['counter'], + 'path': PortagePlugin.xpak_entries['counter'], 'critical': False, }, 'keywords': { - 'path': etpConst['spm']['xpak_entries']['keywords'], + 'path': PortagePlugin.xpak_entries['keywords'], 'critical': False, }, 'spm_phases': { - 'path': etpConst['spm']['xpak_entries']['defined_phases'], + 'path': PortagePlugin.xpak_entries['defined_phases'], 'critical': False, }, } @@ -3567,7 +3953,7 @@ class PortagePlugin(SpmPlugin): return pkg_links def _extract_pkg_metadata_ebuild_entropy_tag(self, ebuild): - search_tag = etpConst['spm']['ebuild_pkg_tag_var'] + search_tag = PortagePlugin._ebuild_entries['ebuild_pkg_tag_var'] ebuild_tag = '' # open in unicode fmt f = open(ebuild, "r") diff --git a/libraries/entropy/spm/plugins/skel.py b/libraries/entropy/spm/plugins/skel.py index 9b489dc12..b0ddeb9de 100644 --- a/libraries/entropy/spm/plugins/skel.py +++ b/libraries/entropy/spm/plugins/skel.py @@ -18,7 +18,7 @@ from entropy.misc import LogFile class SpmPlugin(Singleton): """Base class for Source Package Manager plugins""" - BASE_PLUGIN_API_VERSION = 2 + BASE_PLUGIN_API_VERSION = 3 # this must be reimplemented by subclasses and value # must match BASE_PLUGIN_API_VERSION @@ -31,12 +31,16 @@ class SpmPlugin(Singleton): # Name of your Spm Plugin PLUGIN_NAME = None + # At least one of your SpmPlugin classes must be set as default + # by setting IS_DEFAULT = True + # There can't be more than _one_ default SPM plugin, in that case + # the first (alphabetically) one will be automatically selected + IS_DEFAULT = False + def init_singleton(self, output_interface): """ Source Package Manager Plugin singleton method. This method must be reimplemented by subclasses. - At this stage, you should also consider to tweak etpConst['spm'] - content (importing etpConst from entropy.const). @param output_interface: Entropy output interface @type output_interface: entropy.output.TextInterface based instances @@ -61,7 +65,8 @@ class SpmPlugin(Singleton): """ raise NotImplementedError() - def package_phases(self): + @staticmethod + def package_phases(): """ Return a list of available and valid package build phases. Default value is ["setup", "preinstall", "postinstall", "preremove", @@ -73,6 +78,29 @@ class SpmPlugin(Singleton): return ["setup", "preinstall", "postinstall", "preremove", "postremove", "configure"] + @staticmethod + def package_phases_map(): + """ + Return a map of phases names between Entropy (as keys) and + Source Package Manager. + + @return: map of package phases + @rtype: dict + """ + raise NotImplementedError() + + @staticmethod + def config_files_map(): + """ + Return a map composed by configuration file identifiers and their + path on disk. These configuration files are related to Source Package + Manager. + + @return: configuration files map + @rtype: dict + """ + raise NotImplementedError() + def get_cache_directory(self, root = None): """ Return Source Package Manager cache directory path. @@ -441,6 +469,133 @@ class SpmPlugin(Singleton): """ raise NotImplementedError() + def compile_packages(self, packages, stdin = None, stdout = None, + stderr = None, environ = None, pid_write_func = None, + pretend = False, verbose = False, fetch_only = False, + build_only = False, no_dependencies = False, + ask = False, coloured_output = False, oneshot = False): + """ + Compile given packages using given compile options. Extra compiler + options can be set via environmental variables (CFLAGS, LDFLAGS, etc). + By default, this function writes to stdout and can potentially interact + with user via stdin/stdout/stderr. + By default, when build_only=False, compiled packages are installed onto + the running system. + + @param packages: list of Source Package Manager package names + @type packages: list + @keyword stdin: custom standard input + @type stdin: file object or valid file descriptor number + @keyword stdout: custom standard output + @type stdout: file object or valid file descriptor number + @keyword stderr: custom standard error + @type stderr: file object or valid file descriptor number + @keyword environ: dict + @type environ: map of environmental variables + @keyword pid_write_func: function to call with execution pid number + @type pid_write_func: callable function, signature func(int_pid_number) + @keyword pretend: just show what would be done + @type pretend: bool + @keyword verbose: execute compilation in verbose mode + @type verbose: bool + @keyword fetch_only: fetch source code only + @type fetch_only: bool + @keyword build_only: do not actually touch live system (don't install + compiled + @type build_only: bool + @keyword no_dependencies: ignore possible build time dependencies + @type no_dependencies: bool + @keyword ask: ask user via stdin before executing the required tasks + @type ask: bool + @keyword coloured_output: allow coloured output + @type coloured_output: bool + @keyword oneshot: when compiled packages are intended to not get + recorded into personal user compile preferences (if you are not + using a Portage-based SPM, just ignore this) + @type oneshot: bool + @return: execution status + @rtype: int + """ + raise NotImplementedError() + + def remove_packages(self, packages, stdin = None, stdout = None, + stderr = None, environ = None, pid_write_func = None, + pretend = False, verbose = False, no_dependencies = False, ask = False, + coloured_output = False): + """ + Compile given packages using given compile options. Extra compiler + options can be set via environmental variables (CFLAGS, LDFLAGS, etc). + By default, this function writes to stdout and can potentially interact + with user via stdin/stdout/stderr. + By default, when build_only=False, compiled packages are installed onto + the running system. + + @param packages: list of Source Package Manager package names + @type packages: list + @keyword stdin: custom standard input + @type stdin: file object or valid file descriptor number + @keyword stdout: custom standard output + @type stdout: file object or valid file descriptor number + @keyword stderr: custom standard error + @type stderr: file object or valid file descriptor number + @keyword environ: dict + @type environ: map of environmental variables + @keyword pid_write_func: function to call with execution pid number + @type pid_write_func: callable function, signature func(int_pid_number) + @keyword pretend: just show what would be done + @type pretend: bool + @keyword verbose: execute compilation in verbose mode + @type verbose: bool + @keyword no_dependencies: ignore possible build time dependencies + @type no_dependencies: bool + @keyword ask: ask user via stdin before executing the required tasks + @type ask: bool + @keyword coloured_output: allow coloured output + @type coloured_output: bool + @return: execution status + @rtype: int + """ + raise NotImplementedError() + + def print_build_environment_info(self, stdin = None, stdout = None, + stderr = None, environ = None, pid_write_func = None, + coloured_output = False): + """ + Print build environment info to stdout. + + @keyword stdin: custom standard input + @type stdin: file object or valid file descriptor number + @keyword stdout: custom standard output + @type stdout: file object or valid file descriptor number + @keyword stderr: custom standard error + @type stderr: file object or valid file descriptor number + @keyword environ: dict + @type environ: map of environmental variables + @keyword pid_write_func: function to call with execution pid number + @type pid_write_func: callable function, signature func(int_pid_number) + @keyword coloured_output: allow coloured output + @type coloured_output: bool + @return: execution status + @rtype: int + """ + raise NotImplementedError() + + def environment_update(self, stdout = None, stderr = None): + """ + Hook used by Entropy Client and Entropy Server to ask Source Package + Manager to update /etc/profile* and other environment settings around. + Since this is part of the Source Package Manager metaphor it must stay + in this class. + + @keyword stdout: custom standard output + @type stdout: file object or valid file descriptor number + @keyword stderr: custom standard error + @type stderr: file object or valid file descriptor number + @return: execution status + @rtype: int + """ + raise NotImplementedError() + def get_installed_packages(self, categories = None, root = None): """ Return list of packages found in installed packages repository. @@ -645,6 +800,23 @@ class SpmPlugin(Singleton): """ raise NotImplementedError() + @staticmethod + def entropy_client_post_repository_update_hook(entropy_client, + entropy_repository_id): + """ + This function is called by Entropy Client when updating Entropy + repositories. Place here all your Source Package Manager bullshit and, + remember to return an int form execution status. + + @param entropy_client: Entropy Client interface instance + @type entropy_client: entropy.client.interfaces.Client.Client + @param entropy_repository_id: Entropy Repository unique identifier + @type: string + @return: execution status + @rtype: int + """ + raise NotImplementedError() + @staticmethod def entropy_install_setup_hook(entropy_client, package_metadata): """ diff --git a/libraries/entropy/tools.py b/libraries/entropy/tools.py index c355f6fdb..fe396624a 100644 --- a/libraries/entropy/tools.py +++ b/libraries/entropy/tools.py @@ -20,6 +20,7 @@ import os import time import shutil import tarfile +import tempfile import subprocess import grp import pwd @@ -2710,14 +2711,9 @@ def get_random_temp_file(): @return: @rtype: """ - if not os.path.isdir(etpConst['packagestmpdir']): - os.makedirs(etpConst['packagestmpdir']) - path = os.path.join(etpConst['packagestmpdir'], - "temp_"+str(get_random_number())) - while os.path.lexists(path): - path = os.path.join(etpConst['packagestmpdir'], - "temp_"+str(get_random_number())) - return path + fd, tmp_path = tempfile.mkstemp() + os.close(fd) + return tmp_path def get_file_timestamp(path): """ diff --git a/server/server_reagent.py b/server/server_reagent.py index a32e72747..4c3247ad4 100644 --- a/server/server_reagent.py +++ b/server/server_reagent.py @@ -170,7 +170,8 @@ def repositories(options): for idpackage in idpackages: atom = dbconn.retrieveAtom(idpackage) orig_deps = dbconn.retrieveDependencies(idpackage, extended = True) - atom_deps = [x for x in orig_deps if x[1] != etpConst['spm']['mdepend_id']] + atom_deps = [x for x in orig_deps if x[1] != \ + etpConst['dependency_type_ids']['mdepend_id']] atom_manual_deps = [x for x in orig_deps if x not in atom_deps] print_info(brown(" @@ ")+"%s: %s:" % (blue(atom), darkgreen(_("package dependencies")),)) for dep_str, dep_id in atom_deps: @@ -196,7 +197,8 @@ def repositories(options): continue w_dbconn = Entropy.open_server_repository(repo = repo, read_only = False) - atom_deps += [(x, etpConst['spm']['mdepend_id'],) for x in new_mdeps] + atom_deps += [(x, etpConst['dependency_type_ids']['mdepend_id'],) \ + for x in new_mdeps] deps_dict = {} for atom_dep, dep_id in atom_deps: deps_dict[atom_dep] = dep_id @@ -762,15 +764,16 @@ def spm_compile_categories(options, do_list = False): except ValueError: break + spm = Entropy.Spm() categories = sorted(set(options)) - packages = Entropy.Spm().get_packages(categories) + packages = spm.get_packages(categories) packages = sorted(packages) # remove older packages from list (through slot) if not oldslots: oldslots_meta = {} for package in packages: - pkg_slot = Entropy.Spm().get_package_metadata(package, "SLOT") + pkg_slot = spm.get_package_metadata(package, "SLOT") pkg_key = entropy.tools.dep_getkey(package) obj = oldslots_meta.setdefault(pkg_key, set()) obj.add((pkg_slot, package,)) @@ -782,10 +785,8 @@ def spm_compile_categories(options, do_list = False): if do_list: print_generic(' '.join(["="+x for x in packages])) else: - args = [etpConst['spm']['exec'], etpConst['spm']['ask_cmd'], - etpConst['spm']['verbose_cmd']] - args.extend(["="+x for x in packages]) - return subprocess.call(args) + return spm.compile_atoms(["="+x for x in packages], + ask = True, verbose = True) return 0 def spm_compile_pkgset(pkgsets, do_rebuild = False, do_dbupdate = False, @@ -804,33 +805,26 @@ def spm_compile_pkgset(pkgsets, do_rebuild = False, do_dbupdate = False, _("package set not found"), pkgset,) )) return 1 - extra_args = [] - if etpUi['ask']: - extra_args.append(etpConst['spm']['ask_cmd']) - if etpUi['verbose']: - extra_args.append(etpConst['spm']['verbose_cmd']) - if etpUi['pretend']: - extra_args.append(etpConst['spm']['pretend_cmd']) + spm = Entropy.Spm() + done_atoms = set() # expand package sets for pkgset in pkgsets: - set_atoms = [Entropy.Spm().match_package(x) for x in avail_sets[pkgset]] + set_atoms = [spm.match_package(x) for x in avail_sets[pkgset]] set_atoms = [x for x in set_atoms if x] if not do_rebuild: set_atoms = [x for x in set_atoms if not \ - Entropy.Spm().match_installed_package(x)] + spm.match_installed_package(x)] set_atoms = ["="+x for x in set_atoms] if not set_atoms: continue - args = [etpConst['spm']['exec']] - args.extend(extra_args) - args.extend(set_atoms) - rc = subprocess.call(args) + rc = spm.compile_packages(set_atoms, verbose = etpUi['verbose'], + ask = etpUi['ask'], pretend = etpUi['pretend']) if rc != 0: return rc done_atoms.update(set_atoms)