8ed458dbb8fe9bde1419d4a74d01c973b1179f50
[portage.git] / pym / _emerge / FakeVartree.py
1 # Copyright 1999-2011 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 import sys
5
6 import portage
7 from portage import os
8 from _emerge.Package import Package
9 from _emerge.PackageVirtualDbapi import PackageVirtualDbapi
10 from portage.const import VDB_PATH
11 from portage.dbapi.vartree import vartree
12 from portage.repository.config import _gen_valid_repo
13 from portage.update import grab_updates, parse_updates, update_dbentries
14
15 if sys.hexversion >= 0x3000000:
16         long = int
17
18 class FakeVardbapi(PackageVirtualDbapi):
19         """
20         Implements the vardbapi.getpath() method which is used in error handling
21         code for the Package class and vartree.get_provide().
22         """
23         def getpath(self, cpv, filename=None):
24                 path = os.path.join(self.settings['EROOT'], VDB_PATH, cpv)
25                 if filename is not None:
26                         path =os.path.join(path, filename)
27                 return path
28
29 class FakeVartree(vartree):
30         """This is implements an in-memory copy of a vartree instance that provides
31         all the interfaces required for use by the depgraph.  The vardb is locked
32         during the constructor call just long enough to read a copy of the
33         installed package information.  This allows the depgraph to do it's
34         dependency calculations without holding a lock on the vardb.  It also
35         allows things like vardb global updates to be done in memory so that the
36         user doesn't necessarily need write access to the vardb in cases where
37         global updates are necessary (updates are performed when necessary if there
38         is not a matching ebuild in the tree). Instances of this class are not
39         populated until the sync() method is called."""
40         def __init__(self, root_config, pkg_cache=None, pkg_root_config=None):
41                 self._root_config = root_config
42                 if pkg_root_config is None:
43                         pkg_root_config = self._root_config
44                 self._pkg_root_config = pkg_root_config
45                 if pkg_cache is None:
46                         pkg_cache = {}
47                 real_vartree = root_config.trees["vartree"]
48                 self._real_vardb = real_vartree.dbapi
49                 portdb = root_config.trees["porttree"].dbapi
50                 self.root = real_vartree.root
51                 self.settings = real_vartree.settings
52                 mykeys = list(real_vartree.dbapi._aux_cache_keys)
53                 if "_mtime_" not in mykeys:
54                         mykeys.append("_mtime_")
55                 self._db_keys = mykeys
56                 self._pkg_cache = pkg_cache
57                 self.dbapi = FakeVardbapi(real_vartree.settings)
58
59                 # Initialize variables needed for lazy cache pulls of the live ebuild
60                 # metadata.  This ensures that the vardb lock is released ASAP, without
61                 # being delayed in case cache generation is triggered.
62                 self._aux_get = self.dbapi.aux_get
63                 self.dbapi.aux_get = self._aux_get_wrapper
64                 self._match = self.dbapi.match
65                 self.dbapi.match = self._match_wrapper
66                 self._aux_get_history = set()
67                 self._portdb_keys = ["EAPI", "DEPEND", "RDEPEND", "PDEPEND"]
68                 self._portdb = portdb
69                 self._global_updates = None
70
71         def _match_wrapper(self, cpv, use_cache=1):
72                 """
73                 Make sure the metadata in Package instances gets updated for any
74                 cpv that is returned from a match() call, since the metadata can
75                 be accessed directly from the Package instance instead of via
76                 aux_get().
77                 """
78                 matches = self._match(cpv, use_cache=use_cache)
79                 for cpv in matches:
80                         if cpv in self._aux_get_history:
81                                 continue
82                         self._aux_get_wrapper(cpv, [])
83                 return matches
84
85         def _aux_get_wrapper(self, pkg, wants, myrepo=None):
86                 if pkg in self._aux_get_history:
87                         return self._aux_get(pkg, wants)
88                 self._aux_get_history.add(pkg)
89                 # We need to check the EAPI, and this also raises
90                 # a KeyError to the caller if appropriate.
91                 installed_eapi, repo = self._aux_get(pkg, ["EAPI", "repository"])
92                 try:
93                         # Use the live ebuild metadata if possible.
94                         repo = _gen_valid_repo(repo)
95                         live_metadata = dict(zip(self._portdb_keys,
96                                 self._portdb.aux_get(pkg, self._portdb_keys, myrepo=repo)))
97                         if not portage.eapi_is_supported(live_metadata["EAPI"]) or \
98                                 installed_eapi != live_metadata["EAPI"]:
99                                 raise KeyError(pkg)
100                         self.dbapi.aux_update(pkg, live_metadata)
101                 except (KeyError, portage.exception.PortageException):
102                         if self._global_updates is None:
103                                 self._global_updates = \
104                                         grab_global_updates(self._portdb)
105                         perform_global_updates(
106                                 pkg, self.dbapi, self._global_updates)
107                 return self._aux_get(pkg, wants)
108
109         def cpv_discard(self, pkg):
110                 """
111                 Discard a package from the fake vardb if it exists.
112                 """
113                 old_pkg = self.dbapi.get(pkg)
114                 if old_pkg is not None:
115                         self.dbapi.cpv_remove(old_pkg)
116                         self._pkg_cache.pop(old_pkg, None)
117                         self._aux_get_history.discard(old_pkg.cpv)
118
119         def sync(self, acquire_lock=1):
120                 """
121                 Call this method to synchronize state with the real vardb
122                 after one or more packages may have been installed or
123                 uninstalled.
124                 """
125                 locked = False
126                 try:
127                         if acquire_lock and os.access(self._real_vardb._dbroot, os.W_OK):
128                                 self._real_vardb.lock()
129                                 locked = True
130                         self._sync()
131                 finally:
132                         if locked:
133                                 self._real_vardb.unlock()
134
135                 # Populate the old-style virtuals using the cached values.
136                 # Skip the aux_get wrapper here, to avoid unwanted
137                 # cache generation.
138                 try:
139                         self.dbapi.aux_get = self._aux_get
140                         self.settings._populate_treeVirtuals_if_needed(self)
141                 finally:
142                         self.dbapi.aux_get = self._aux_get_wrapper
143
144         def _sync(self):
145
146                 real_vardb = self._root_config.trees["vartree"].dbapi
147                 current_cpv_set = frozenset(real_vardb.cpv_all())
148                 pkg_vardb = self.dbapi
149                 pkg_cache = self._pkg_cache
150                 aux_get_history = self._aux_get_history
151
152                 # Remove any packages that have been uninstalled.
153                 for pkg in list(pkg_vardb):
154                         if pkg.cpv not in current_cpv_set:
155                                 self.cpv_discard(pkg)
156
157                 # Validate counters and timestamps.
158                 slot_counters = {}
159                 root_config = self._pkg_root_config
160                 validation_keys = ["COUNTER", "_mtime_"]
161                 for cpv in current_cpv_set:
162
163                         pkg_hash_key = Package._gen_hash_key(cpv=cpv,
164                                 installed=True, root_config=root_config,
165                                 type_name="installed")
166                         pkg = pkg_vardb.get(pkg_hash_key)
167                         if pkg is not None:
168                                 counter, mtime = real_vardb.aux_get(cpv, validation_keys)
169                                 try:
170                                         counter = long(counter)
171                                 except ValueError:
172                                         counter = 0
173
174                                 if counter != pkg.counter or \
175                                         mtime != pkg.mtime:
176                                         self.cpv_discard(pkg)
177                                         pkg = None
178
179                         if pkg is None:
180                                 pkg = self._pkg(cpv)
181
182                         other_counter = slot_counters.get(pkg.slot_atom)
183                         if other_counter is not None:
184                                 if other_counter > pkg.counter:
185                                         continue
186
187                         slot_counters[pkg.slot_atom] = pkg.counter
188                         pkg_vardb.cpv_inject(pkg)
189
190                 real_vardb.flush_cache()
191
192         def _pkg(self, cpv):
193                 """
194                 The RootConfig instance that will become the Package.root_config
195                 attribute can be overridden by the FakeVartree pkg_root_config
196                 constructory argument, since we want to be consistent with the
197                 depgraph._pkg() method which uses a specially optimized
198                 RootConfig that has a FakeVartree instead of a real vartree.
199                 """
200                 pkg = Package(cpv=cpv, built=True, installed=True,
201                         metadata=zip(self._db_keys,
202                         self._real_vardb.aux_get(cpv, self._db_keys)),
203                         root_config=self._pkg_root_config,
204                         type_name="installed")
205
206                 try:
207                         mycounter = long(pkg.metadata["COUNTER"])
208                 except ValueError:
209                         mycounter = 0
210                         pkg.metadata["COUNTER"] = str(mycounter)
211
212                 self._pkg_cache[pkg] = pkg
213                 return pkg
214
215 def grab_global_updates(portdb):
216         retupdates = {}
217
218         for repo_name in portdb.getRepositories():
219                 repo = portdb.getRepositoryPath(repo_name)
220                 updpath = os.path.join(repo, "profiles", "updates")
221                 if not os.path.isdir(updpath):
222                         continue
223
224                 try:
225                         rawupdates = grab_updates(updpath)
226                 except portage.exception.DirectoryNotFound:
227                         rawupdates = []
228                 upd_commands = []
229                 for mykey, mystat, mycontent in rawupdates:
230                         commands, errors = parse_updates(mycontent)
231                         upd_commands.extend(commands)
232                 retupdates[repo_name] = upd_commands
233
234         master_repo = portdb.getRepositoryName(portdb.porttree_root)
235         if master_repo in retupdates:
236                 retupdates['DEFAULT'] = retupdates[master_repo]
237
238         return retupdates
239
240 def perform_global_updates(mycpv, mydb, myupdates):
241         aux_keys = ["DEPEND", "RDEPEND", "PDEPEND", 'repository']
242         aux_dict = dict(zip(aux_keys, mydb.aux_get(mycpv, aux_keys)))
243         repository = aux_dict.pop('repository')
244         try:
245                 mycommands = myupdates[repository]
246         except KeyError:
247                 try:
248                         mycommands = myupdates['DEFAULT']
249                 except KeyError:
250                         return
251
252         if not mycommands:
253                 return
254
255         updates = update_dbentries(mycommands, aux_dict)
256         if updates:
257                 mydb.aux_update(mycpv, updates)