Support repository-wide updates.
[portage.git] / pym / portage / dbapi / __init__.py
1 # Copyright 1998-2009 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 __all__ = ["dbapi"]
5
6 import re
7
8 import portage
9 portage.proxy.lazyimport.lazyimport(globals(),
10         'portage.dbapi.dep_expand:dep_expand@_dep_expand',
11         'portage.dep:match_from_list',
12         'portage.locks:unlockfile',
13         'portage.output:colorize',
14         'portage.util:cmp_sort_key,writemsg',
15         'portage.versions:catsplit,catpkgsplit,vercmp',
16 )
17
18 from portage import os
19 from portage import auxdbkeys
20 from portage.localization import _
21
22 class dbapi(object):
23         _category_re = re.compile(r'^\w[-.+\w]*$')
24         _pkg_dir_name_re = re.compile(r'^\w[-+\w]*$')
25         _categories = None
26         _use_mutable = False
27         _known_keys = frozenset(x for x in auxdbkeys
28                 if not x.startswith("UNUSED_0"))
29         def __init__(self):
30                 pass
31
32         @property
33         def categories(self):
34                 """
35                 Use self.cp_all() to generate a category list. Mutable instances
36                 can delete the self._categories attribute in cases when the cached
37                 categories become invalid and need to be regenerated.
38                 """
39                 if self._categories is not None:
40                         return self._categories
41                 self._categories = tuple(sorted(set(catsplit(x)[0] \
42                         for x in self.cp_all())))
43                 return self._categories
44
45         def close_caches(self):
46                 pass
47
48         def cp_list(self, cp, use_cache=1):
49                 raise NotImplementedError(self)
50
51         def _cpv_sort_ascending(self, cpv_list):
52                 """
53                 Use this to sort self.cp_list() results in ascending
54                 order. It sorts in place and returns None.
55                 """
56                 if len(cpv_list) > 1:
57                         # If the cpv includes explicit -r0, it has to be preserved
58                         # for consistency in findname and aux_get calls, so use a
59                         # dict to map strings back to their original values.
60                         ver_map = {}
61                         for cpv in cpv_list:
62                                 ver_map[cpv] = '-'.join(catpkgsplit(cpv)[2:])
63                         def cmp_cpv(cpv1, cpv2):
64                                 return vercmp(ver_map[cpv1], ver_map[cpv2])
65                         cpv_list.sort(key=cmp_sort_key(cmp_cpv))
66
67         def cpv_all(self):
68                 """Return all CPVs in the db
69                 Args:
70                         None
71                 Returns:
72                         A list of Strings, 1 per CPV
73
74                 This function relies on a subclass implementing cp_all, this is why the hasattr is there
75                 """
76
77                 if not hasattr(self, "cp_all"):
78                         raise NotImplementedError
79                 cpv_list = []
80                 for cp in self.cp_all():
81                         cpv_list.extend(self.cp_list(cp))
82                 return cpv_list
83
84         def cp_all(self):
85                 """ Implement this in a child class
86                 Args
87                         None
88                 Returns:
89                         A list of strings 1 per CP in the datastore
90                 """
91                 return NotImplementedError
92
93         def aux_get(self, mycpv, mylist):
94                 """Return the metadata keys in mylist for mycpv
95                 Args:
96                         mycpv - "sys-apps/foo-1.0"
97                         mylist - ["SLOT","DEPEND","HOMEPAGE"]
98                 Returns: 
99                         a list of results, in order of keys in mylist, such as:
100                         ["0",">=sys-libs/bar-1.0","http://www.foo.com"] or [] if mycpv not found'
101                 """
102                 raise NotImplementedError
103         
104         def aux_update(self, cpv, metadata_updates):
105                 """
106                 Args:
107                   cpv - "sys-apps/foo-1.0"
108                         metadata_updates = { key : newvalue }
109                 Returns:
110                         None
111                 """
112                 raise NotImplementedError
113
114         def match(self, origdep, use_cache=1):
115                 """Given a dependency, try to find packages that match
116                 Args:
117                         origdep - Depend atom
118                         use_cache - Boolean indicating if we should use the cache or not
119                         NOTE: Do we ever not want the cache?
120                 Returns:
121                         a list of packages that match origdep
122                 """
123                 mydep = _dep_expand(origdep, mydb=self, settings=self.settings)
124                 return list(self._iter_match(mydep,
125                         self.cp_list(mydep.cp, use_cache=use_cache)))
126
127         def _iter_match(self, atom, cpv_iter):
128                 cpv_iter = iter(match_from_list(atom, cpv_iter))
129                 if atom.slot:
130                         cpv_iter = self._iter_match_slot(atom, cpv_iter)
131                 if atom.use:
132                         cpv_iter = self._iter_match_use(atom, cpv_iter)
133                 return cpv_iter
134
135         def _iter_match_slot(self, atom, cpv_iter):
136                 for cpv in cpv_iter:
137                         try:
138                                 if self.aux_get(cpv, ["SLOT"])[0] == atom.slot:
139                                         yield cpv
140                         except KeyError:
141                                 continue
142
143         def _iter_match_use(self, atom, cpv_iter):
144                 """
145                 1) Check for required IUSE intersection (need implicit IUSE here).
146                 2) Check enabled/disabled flag states.
147                 """
148
149                 iuse_implicit_re = self.settings._iuse_implicit_re
150                 for cpv in cpv_iter:
151                         try:
152                                 iuse, slot, use = self.aux_get(cpv, ["IUSE", "SLOT", "USE"])
153                         except KeyError:
154                                 continue
155                         use = use.split()
156                         iuse = frozenset(x.lstrip('+-') for x in iuse.split())
157                         missing_iuse = False
158                         for x in atom.use.required:
159                                 if x not in iuse and iuse_implicit_re.match(x) is None:
160                                         missing_iuse = True
161                                         break
162                         if missing_iuse:
163                                 continue
164                         if not self._use_mutable:
165                                 if atom.use.enabled.difference(use):
166                                         continue
167                                 if atom.use.disabled.intersection(use):
168                                         continue
169                         else:
170                                 # Check masked and forced flags for repoman.
171                                 mysettings = getattr(self, 'settings', None)
172                                 if mysettings is not None and not mysettings.local_config:
173
174                                         pkg = "%s:%s" % (cpv, slot)
175                                         usemask = mysettings._getUseMask(pkg)
176                                         if usemask.intersection(atom.use.enabled):
177                                                 continue
178
179                                         useforce = mysettings._getUseForce(pkg).difference(usemask)
180                                         if useforce.intersection(atom.use.disabled):
181                                                 continue
182
183                         yield cpv
184
185         def invalidentry(self, mypath):
186                 if mypath.endswith('portage_lockfile'):
187                         if "PORTAGE_MASTER_PID" not in os.environ:
188                                 writemsg(_("Lockfile removed: %s\n") % mypath, 1)
189                                 unlockfile((mypath, None, None))
190                         else:
191                                 # Nothing we can do about it. We're probably sandboxed.
192                                 pass
193                 elif '/-MERGING-' in mypath:
194                         if os.path.exists(mypath):
195                                 writemsg(colorize("BAD", _("INCOMPLETE MERGE:"))+" %s\n" % mypath,
196                                         noiselevel=-1)
197                 else:
198                         writemsg("!!! Invalid db entry: %s\n" % mypath, noiselevel=-1)
199
200         def update_ents(self, updates, onProgress=None, onUpdate=None):
201                 """
202                 Update metadata of all packages for package moves.
203                 @param updates: A list of move commands
204                 @type updates: List
205                 @param onProgress: A progress callback function
206                 @type onProgress: a callable that takes 2 integer arguments: maxval and curval
207                 @param onUpdate: A progress callback function called only
208                         for packages that are modified by updates.
209                 @type onUpdate: a callable that takes 2 integer arguments:
210                         maxval and curval
211                 """
212                 cpv_all = self.cpv_all()
213                 cpv_all.sort()
214                 maxval = len(cpv_all)
215                 aux_get = self.aux_get
216                 aux_update = self.aux_update
217                 update_keys = ["DEPEND", "RDEPEND", "PDEPEND", "PROVIDE"]
218                 from portage.update import update_dbentries
219                 if onUpdate:
220                         onUpdate(maxval, 0)
221                 if onProgress:
222                         onProgress(maxval, 0)
223                 for i, cpv in enumerate(cpv_all):
224                         metadata = dict(zip(update_keys, aux_get(cpv, update_keys)))
225                         metadata_updates = update_dbentries(updates, metadata)
226                         if metadata_updates:
227                                 aux_update(cpv, metadata_updates)
228                                 if onUpdate:
229                                         onUpdate(maxval, i+1)
230                         if onProgress:
231                                 onProgress(maxval, i+1)
232
233         def move_slot_ent(self, mylist, repo_name = None):
234                 """This function takes a sequence:
235                 Args:
236                         mylist: a sequence of (package, originalslot, newslot)
237                         repo_name: repository from which update is originated
238                 Returns:
239                         The number of slotmoves this function did
240                 """
241                 pkg = mylist[1]
242                 origslot = mylist[2]
243                 newslot = mylist[3]
244                 origmatches = self.match(pkg)
245                 moves = 0
246                 if not origmatches:
247                         return moves
248                 for mycpv in origmatches:
249                         slot = self.aux_get(mycpv, ["SLOT"])[0]
250                         if slot != origslot:
251                                 continue
252                         if repo_name and self.aux_get(mycpv, ['repository'])[0] != repo_name:
253                                 continue
254                         moves += 1
255                         mydata = {"SLOT": newslot+"\n"}
256                         self.aux_update(mycpv, mydata)
257                 return moves