de3e8e45e8ef788937cffcadcde8398be9bd52f8
[portage.git] / pym / portage / _sets / __init__.py
1 # Copyright 2007-2012 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 from __future__ import print_function
5
6 __all__ = ["SETPREFIX", "get_boolean", "SetConfigError",
7         "SetConfig", "load_default_config"]
8
9 import io
10 import logging
11 import sys
12 try:
13         from configparser import NoOptionError, ParsingError
14         if sys.hexversion >= 0x3020000:
15                 from configparser import ConfigParser as SafeConfigParser
16         else:
17                 from configparser import SafeConfigParser
18 except ImportError:
19         from ConfigParser import SafeConfigParser, NoOptionError, ParsingError
20 from portage import os
21 from portage import load_mod
22 from portage import _unicode_decode
23 from portage import _unicode_encode
24 from portage import _encodings
25 from portage.const import USER_CONFIG_PATH, GLOBAL_CONFIG_PATH
26 from portage.const import _ENABLE_SET_CONFIG
27 from portage.exception import PackageSetNotFound
28 from portage.localization import _
29 from portage.util import writemsg_level
30
31 SETPREFIX = "@"
32
33 def get_boolean(options, name, default):
34         if not name in options:
35                 return default
36         elif options[name].lower() in ("1", "yes", "on", "true"):
37                 return True
38         elif options[name].lower() in ("0", "no", "off", "false"):
39                 return False
40         else:
41                 raise SetConfigError(_("invalid value '%(value)s' for option '%(option)s'") % {"value": options[name], "option": name})
42
43 class SetConfigError(Exception):
44         pass
45
46 class SetConfig(object):
47         def __init__(self, paths, settings, trees):
48                 self._parser = SafeConfigParser(
49                         defaults={
50                                 "EPREFIX" : settings["EPREFIX"],
51                                 "EROOT" : settings["EROOT"],
52                                 "PORTAGE_CONFIGROOT" : settings["PORTAGE_CONFIGROOT"],
53                                 "ROOT" : settings["ROOT"],
54                         })
55
56                 if _ENABLE_SET_CONFIG:
57                         # use read_file/readfp in order to control decoding of unicode
58                         try:
59                                 # Python >=3.2
60                                 read_file = self._parser.read_file
61                         except AttributeError:
62                                 read_file = self._parser.readfp
63
64                         for p in paths:
65                                 f = None
66                                 try:
67                                         f = io.open(_unicode_encode(p,
68                                                 encoding=_encodings['fs'], errors='strict'),
69                                                 mode='r', encoding=_encodings['repo.content'],
70                                                 errors='replace')
71                                 except EnvironmentError:
72                                         pass
73                                 else:
74                                         try:
75                                                 read_file(f)
76                                         except ParsingError as e:
77                                                 writemsg_level(_unicode_decode(
78                                                         _("!!! Error while reading sets config file: %s\n")
79                                                         ) % e, level=logging.ERROR, noiselevel=-1)
80                                 finally:
81                                         if f is not None:
82                                                 f.close()
83                 else:
84                         self._create_default_config()
85
86                 self.errors = []
87                 self.psets = {}
88                 self.trees = trees
89                 self.settings = settings
90                 self._parsed = False
91                 self.active = []
92
93         def _create_default_config(self):
94                 """
95                 Create a default hardcoded set configuration for a portage version
96                 that does not support set configuration files. This is only used
97                 in the current branch of portage if _ENABLE_SET_CONFIG is False.
98                 Even if it's not used in this branch, keep it here in order to
99                 minimize the diff between branches.
100
101                         [world]
102                         class = portage.sets.base.DummyPackageSet
103                         packages = @selected @system
104
105                         [selected]
106                         class = portage.sets.files.WorldSelectedSet
107
108                         [system]
109                         class = portage.sets.profiles.PackagesSystemSet
110
111                 """
112                 parser = self._parser
113
114                 parser.remove_section("world")
115                 parser.add_section("world")
116                 parser.set("world", "class", "portage.sets.base.DummyPackageSet")
117                 parser.set("world", "packages", "@selected @system")
118
119                 parser.remove_section("selected")
120                 parser.add_section("selected")
121                 parser.set("selected", "class", "portage.sets.files.WorldSelectedSet")
122
123                 parser.remove_section("system")
124                 parser.add_section("system")
125                 parser.set("system", "class", "portage.sets.profiles.PackagesSystemSet")
126
127                 parser.remove_section("usersets")
128                 parser.add_section("usersets")
129                 parser.set("usersets", "class", "portage.sets.files.StaticFileSet")
130                 parser.set("usersets", "multiset", "true")
131                 parser.set("usersets", "directory", "%(PORTAGE_CONFIGROOT)setc/portage/sets")
132                 parser.set("usersets", "world-candidate", "true")
133
134                 parser.remove_section("live-rebuild")
135                 parser.add_section("live-rebuild")
136                 parser.set("live-rebuild", "class", "portage.sets.dbapi.VariableSet")
137                 parser.set("live-rebuild", "variable", "INHERITED")
138                 parser.set("live-rebuild", "includes", "bzr cvs darcs git git-2 mercurial subversion tla")
139
140                 parser.remove_section("module-rebuild")
141                 parser.add_section("module-rebuild")
142                 parser.set("module-rebuild", "class", "portage.sets.dbapi.OwnerSet")
143                 parser.set("module-rebuild", "files", "/lib/modules")
144
145                 parser.remove_section("preserved-rebuild")
146                 parser.add_section("preserved-rebuild")
147                 parser.set("preserved-rebuild", "class", "portage.sets.libs.PreservedLibraryConsumerSet")
148
149                 parser.remove_section("x11-module-rebuild")
150                 parser.add_section("x11-module-rebuild")
151                 parser.set("x11-module-rebuild", "class", "portage.sets.dbapi.OwnerSet")
152                 parser.set("x11-module-rebuild", "files", "/usr/lib/xorg/modules")
153                 parser.set("x11-module-rebuild", "exclude-files", "/usr/bin/Xorg")
154
155         def update(self, setname, options):
156                 parser = self._parser
157                 self.errors = []
158                 if not setname in self.psets:
159                         options["name"] = setname
160                         options["world-candidate"] = "False"
161                         
162                         # for the unlikely case that there is already a section with the requested setname
163                         import random
164                         while setname in parser.sections():
165                                 setname = "%08d" % random.randint(0, 10**10)
166                         
167                         parser.add_section(setname)
168                         for k, v in options.items():
169                                 parser.set(setname, k, v)
170                 else:
171                         section = self.psets[setname].creator
172                         if parser.has_option(section, "multiset") and \
173                                 parser.getboolean(section, "multiset"):
174                                 self.errors.append(_("Invalid request to reconfigure set '%(set)s' generated "
175                                         "by multiset section '%(section)s'") % {"set": setname, "section": section})
176                                 return
177                         for k, v in options.items():
178                                 parser.set(section, k, v)
179                 self._parse(update=True)
180
181         def _parse(self, update=False):
182                 if self._parsed and not update:
183                         return
184                 parser = self._parser
185                 for sname in parser.sections():
186                         # find classname for current section, default to file based sets
187                         if not parser.has_option(sname, "class"):
188                                 classname = "portage._sets.files.StaticFileSet"
189                         else:
190                                 classname = parser.get(sname, "class")
191
192                         if classname.startswith('portage.sets.'):
193                                 # The module has been made private, but we still support
194                                 # the previous namespace for sets.conf entries.
195                                 classname = classname.replace('sets', '_sets', 1)
196
197                         # try to import the specified class
198                         try:
199                                 setclass = load_mod(classname)
200                         except (ImportError, AttributeError):
201                                 try:
202                                         setclass = load_mod("portage._sets." + classname)
203                                 except (ImportError, AttributeError):
204                                         self.errors.append(_("Could not import '%(class)s' for section "
205                                                 "'%(section)s'") % {"class": classname, "section": sname})
206                                         continue
207                         # prepare option dict for the current section
208                         optdict = {}
209                         for oname in parser.options(sname):
210                                 optdict[oname] = parser.get(sname, oname)
211                         
212                         # create single or multiple instances of the given class depending on configuration
213                         if parser.has_option(sname, "multiset") and \
214                                 parser.getboolean(sname, "multiset"):
215                                 if hasattr(setclass, "multiBuilder"):
216                                         newsets = {}
217                                         try:
218                                                 newsets = setclass.multiBuilder(optdict, self.settings, self.trees)
219                                         except SetConfigError as e:
220                                                 self.errors.append(_("Configuration error in section '%s': %s") % (sname, str(e)))
221                                                 continue
222                                         for x in newsets:
223                                                 if x in self.psets and not update:
224                                                         self.errors.append(_("Redefinition of set '%s' (sections: '%s', '%s')") % (x, self.psets[x].creator, sname))
225                                                 newsets[x].creator = sname
226                                                 if parser.has_option(sname, "world-candidate") and \
227                                                         parser.getboolean(sname, "world-candidate"):
228                                                         newsets[x].world_candidate = True
229                                         self.psets.update(newsets)
230                                 else:
231                                         self.errors.append(_("Section '%(section)s' is configured as multiset, but '%(class)s' "
232                                                 "doesn't support that configuration") % {"section": sname, "class": classname})
233                                         continue
234                         else:
235                                 try:
236                                         setname = parser.get(sname, "name")
237                                 except NoOptionError:
238                                         setname = sname
239                                 if setname in self.psets and not update:
240                                         self.errors.append(_("Redefinition of set '%s' (sections: '%s', '%s')") % (setname, self.psets[setname].creator, sname))
241                                 if hasattr(setclass, "singleBuilder"):
242                                         try:
243                                                 self.psets[setname] = setclass.singleBuilder(optdict, self.settings, self.trees)
244                                                 self.psets[setname].creator = sname
245                                                 if parser.has_option(sname, "world-candidate") and \
246                                                         parser.getboolean(sname, "world-candidate"):
247                                                         self.psets[setname].world_candidate = True
248                                         except SetConfigError as e:
249                                                 self.errors.append(_("Configuration error in section '%s': %s") % (sname, str(e)))
250                                                 continue
251                                 else:
252                                         self.errors.append(_("'%(class)s' does not support individual set creation, section '%(section)s' "
253                                                 "must be configured as multiset") % {"class": classname, "section": sname})
254                                         continue
255                 self._parsed = True
256         
257         def getSets(self):
258                 self._parse()
259                 return self.psets.copy()
260
261         def getSetAtoms(self, setname, ignorelist=None):
262                 """
263                 This raises PackageSetNotFound if the give setname does not exist.
264                 """
265                 self._parse()
266                 try:
267                         myset = self.psets[setname]
268                 except KeyError:
269                         raise PackageSetNotFound(setname)
270                 myatoms = myset.getAtoms()
271
272                 if ignorelist is None:
273                         ignorelist = set()
274
275                 ignorelist.add(setname)
276                 for n in myset.getNonAtoms():
277                         if n.startswith(SETPREFIX):
278                                 s = n[len(SETPREFIX):]
279                                 if s in self.psets:
280                                         if s not in ignorelist:
281                                                 myatoms.update(self.getSetAtoms(s,
282                                                         ignorelist=ignorelist))
283                                 else:
284                                         raise PackageSetNotFound(s)
285
286                 return myatoms
287
288 def load_default_config(settings, trees):
289
290         if not _ENABLE_SET_CONFIG:
291                 return SetConfig(None, settings, trees)
292
293         global_config_path = GLOBAL_CONFIG_PATH
294         if settings['EPREFIX']:
295                 global_config_path = os.path.join(settings['EPREFIX'],
296                         GLOBAL_CONFIG_PATH.lstrip(os.sep))
297         def _getfiles():
298                 for path, dirs, files in os.walk(os.path.join(global_config_path, "sets")):
299                         for f in files:
300                                 if not f.startswith(b'.'):
301                                         yield os.path.join(path, f)
302
303                 dbapi = trees["porttree"].dbapi
304                 for repo in dbapi.getRepositories():
305                         path = dbapi.getRepositoryPath(repo)
306                         yield os.path.join(path, "sets.conf")
307
308                 yield os.path.join(settings["PORTAGE_CONFIGROOT"],
309                         USER_CONFIG_PATH, "sets.conf")
310
311         return SetConfig(_getfiles(), settings, trees)