Show an error message when deletion of stale cache fails.
[portage.git] / bin / egencache
1 #!/usr/bin/python
2 # Copyright 2009 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
4 # $Id$
5
6 import sys
7 # This block ensures that ^C interrupts are handled quietly.
8 try:
9         import signal
10
11         def exithandler(signum,frame):
12                 signal.signal(signal.SIGINT, signal.SIG_IGN)
13                 signal.signal(signal.SIGTERM, signal.SIG_IGN)
14                 sys.exit(1)
15
16         signal.signal(signal.SIGINT, exithandler)
17         signal.signal(signal.SIGTERM, exithandler)
18
19 except KeyboardInterrupt:
20         sys.exit(1)
21
22 import logging
23 import optparse
24 import os
25 import portage
26 import _emerge
27 from portage.cache.cache_errors import CacheError, StatCollision
28 from portage.util import writemsg_level
29 from portage import cpv_getkey
30
31 def parse_args(args):
32         usage = "egencache [options] --update [atom] ..."
33         parser = optparse.OptionParser(usage=usage)
34         parser.add_option("--update",
35                 action="store_true",
36                 help="update metadata/cache/ (generate as necessary)")
37         parser.add_option("--repo",
38                 action="store",
39                 help="name of repo to operate on (default repo is located at $PORTDIR)")
40         parser.add_option("--cache-dir",
41                 help="location of the metadata cache",
42                 dest="cache_dir")
43         parser.add_option("--config-root",
44                 help="location of portage config files",
45                 dest="config_root")
46         parser.add_option("--jobs",
47                 action="store",
48                 help="max ebuild processes to spawn")
49         parser.add_option("--load-average",
50                 action="store",
51                 help="max load allowed when spawning multiple jobs",
52                 dest="load_average")
53         parser.add_option("--rsync",
54                 action="store_true",
55                 help="enable rsync stat collision workaround " + \
56                         "for bug 139134 (use with --update)")
57         parser.add_option("--ignore-default-opts",
58                 action="store_true",
59                 help="do not use the EGENCACHE_DEFAULT_OPTS environment variable")
60         options, args = parser.parse_args(args)
61
62         if options.jobs:
63                 jobs = None
64                 try:
65                         jobs = int(options.jobs)
66                 except ValueError:
67                         jobs = -1
68
69                 if jobs < 1:
70                         parser.error("Invalid: --jobs='%s'" % \
71                                 (options.jobs,))
72
73                 options.jobs = jobs
74
75         else:
76                 options.jobs = None
77
78         if options.load_average:
79                 try:
80                         load_average = float(options.load_average)
81                 except ValueError:
82                         load_average = 0.0
83
84                 if load_average <= 0.0:
85                         parser.error("Invalid: --load-average='%s'" % \
86                                 (options.load_average,))
87
88                 options.load_average = load_average
89
90         else:
91                 options.load_average = None
92
93         if options.config_root is not None and \
94                 not os.path.isdir(options.config_root):
95                 parser.error("Not a directory: --config-root='%s'" % \
96                         (options.config_root,))
97
98         if options.cache_dir is not None and not os.path.isdir(options.cache_dir):
99                 parser.error("Not a directory: --cache-dir='%s'" % \
100                         (options.cache_dir,))
101
102         for atom in args:
103                 try:
104                         atom = portage.dep.Atom(atom)
105                 except portage.exception.InvalidAtom:
106                         parser.error('Invalid atom: %s' % (atom,))
107
108                 if str(atom) != atom.cp:
109                         parser.error('Atom is too specific: %s' % (atom,))
110
111         return parser, options, args
112
113 class GenCache(object):
114         def __init__(self, portdb, cp_iter=None, max_jobs=None, max_load=None,
115                 rsync=False):
116                 self._portdb = portdb
117                 # We can globally cleanse stale cache only if we
118                 # iterate over every single cp.
119                 self._global_cleanse = cp_iter is None
120                 if cp_iter is not None:
121                         self._cp_set = set(cp_iter)
122                         cp_iter = iter(self._cp_set)
123                         self._cp_missing = self._cp_set.copy()
124                 else:
125                         self._cp_set = None
126                         self._cp_missing = set()
127                 self._regen = _emerge.MetadataRegen(portdb, cp_iter=cp_iter,
128                         consumer=self._metadata_callback,
129                         max_jobs=max_jobs, max_load=max_load)
130                 self.returncode = os.EX_OK
131                 metadbmodule = portdb.mysettings.load_best_module("portdbapi.metadbmodule")
132                 self._trg_cache = metadbmodule(portdb.porttrees[0],
133                         "metadata/cache", portage.auxdbkeys[:])
134                 if rsync:
135                         self._trg_cache.raise_stat_collision = True
136                 try:
137                         self._trg_cache.ec = \
138                                 portdb._repo_info[portdb.porttrees[0]].eclass_db
139                 except AttributeError:
140                         pass
141                 self._existing_nodes = set()
142
143         def _metadata_callback(self, cpv, ebuild_path, repo_path, metadata):
144                 self._existing_nodes.add(cpv)
145                 self._cp_missing.discard(cpv_getkey(cpv))
146                 if metadata is not None:
147                         if metadata.get('EAPI') == '0':
148                                 del metadata['EAPI']
149                         try:
150                                 try:
151                                         self._trg_cache[cpv] = metadata
152                                 except StatCollision, sc:
153                                         # If the content of a cache entry changes and neither the
154                                         # file mtime nor size changes, it will prevent rsync from
155                                         # detecting changes. Cache backends may raise this
156                                         # exception from _setitem() if they detect this type of stat
157                                         # collision. These exceptions are handled by bumping the
158                                         # mtime on the ebuild (and the corresponding cache entry).
159                                         # See bug #139134.
160                                         max_mtime = sc.mtime
161                                         for ec, (loc, ec_mtime) in metadata['_eclasses_'].iteritems():
162                                                 if max_mtime < ec_mtime:
163                                                         max_mtime = ec_mtime
164                                         if max_mtime == sc.mtime:
165                                                 max_mtime += 1
166                                         max_mtime = long(max_mtime)
167                                         try:
168                                                 os.utime(ebuild_path, (max_mtime, max_mtime))
169                                         except OSError, e:
170                                                 self.returncode |= 1
171                                                 writemsg_level(
172                                                         "%s writing target: %s\n" % (cpv, e),
173                                                         level=logging.ERROR, noiselevel=-1)
174                                         else:
175                                                 metadata['_mtime_'] = max_mtime
176                                                 self._trg_cache[cpv] = metadata
177                                                 self._portdb.auxdb[repo_path][cpv] = metadata
178
179                         except CacheError, ce:
180                                 self.returncode |= 1
181                                 writemsg_level(
182                                         "%s writing target: %s\n" % (cpv, ce),
183                                         level=logging.ERROR, noiselevel=-1)
184
185         def run(self):
186                 self._regen.run()
187                 self.returncode |= self._regen.returncode
188                 cp_missing = self._cp_missing
189
190                 trg_cache = self._trg_cache
191                 dead_nodes = set()
192                 if self._global_cleanse:
193                         try:
194                                 for cpv in trg_cache.iterkeys():
195                                         cp = cpv_getkey(cpv)
196                                         if cp is None:
197                                                 self.returncode |= 1
198                                                 writemsg_level(
199                                                         "Unable to parse cp for '%s'\n"  % (cpv,),
200                                                         level=logging.ERROR, noiselevel=-1)
201                                         else:
202                                                 dead_nodes.add(cpv)
203                         except CacheError, ce:
204                                 self.returncode |= 1
205                                 writemsg_level(
206                                         "Error listing cache entries for " + \
207                                         "'%s/metadata/cache': %s, continuing...\n" % \
208                                         (self._portdb.porttree_root, ce),
209                                         level=logging.ERROR, noiselevel=-1)
210
211                 else:
212                         cp_set = self._cp_set
213                         try:
214                                 for cpv in trg_cache.iterkeys():
215                                         cp = cpv_getkey(cpv)
216                                         if cp is None:
217                                                 self.returncode |= 1
218                                                 writemsg_level(
219                                                         "Unable to parse cp for '%s'\n"  % (cpv,),
220                                                         level=logging.ERROR, noiselevel=-1)
221                                         else:
222                                                 cp_missing.discard(cp)
223                                                 if cp in cp_set:
224                                                         dead_nodes.add(cpv)
225                         except CacheError, ce:
226                                 self.returncode |= 1
227                                 writemsg_level(
228                                         "Error listing cache entries for " + \
229                                         "'%s/metadata/cache': %s, continuing...\n" % \
230                                         (self._portdb.porttree_root, ce),
231                                         level=logging.ERROR, noiselevel=-1)
232
233                 if cp_missing:
234                         self.returncode |= 1
235                         for cp in sorted(cp_missing):
236                                 writemsg_level(
237                                         "No ebuilds or cache entries found for '%s'\n"  % (cp,),
238                                         level=logging.ERROR, noiselevel=-1)
239
240                 if dead_nodes:
241                         dead_nodes.difference_update(self._existing_nodes)
242                         for k in dead_nodes:
243                                 try:
244                                         del trg_cache[k]
245                                 except KeyError:
246                                         pass
247                                 except CacheError, ce:
248                                         self.returncode |= 1
249                                         writemsg_level(
250                                                 "%s deleting stale cache: %s\n" % (k, ce),
251                                                 level=logging.ERROR, noiselevel=-1)
252
253                 if not trg_cache.autocommits:
254                         try:
255                                 trg_cache.commit()
256                         except CacheError, ce:
257                                 self.returncode |= 1
258                                 writemsg_level(
259                                         "committing target: %s\n" % (ce,),
260                                         level=logging.ERROR, noiselevel=-1)
261
262 def egencache_main(args):
263         parser, options, atoms = parse_args(args)
264
265         config_root = options.config_root
266         if config_root is None:
267                 config_root = '/'
268
269         # The calling environment is ignored, so the program is
270         # completely controlled by commandline arguments.
271         env = {}
272
273         if options.repo is None:
274                 env['PORTDIR_OVERLAY'] = ''
275
276         if options.cache_dir is not None:
277                 env['PORTAGE_DEPCACHEDIR'] = options.cache_dir
278
279         settings = portage.config(config_root=config_root,
280                 target_root='/', env=env)
281
282         default_opts = None
283         if not options.ignore_default_opts:
284                 default_opts = settings.get('EGENCACHE_DEFAULT_OPTS', '').split()
285
286         if default_opts:
287                 parser, options, args = parse_args(default_opts + args)
288
289                 if options.config_root is not None:
290                         config_root = options.config_root
291
292                 if options.cache_dir is not None:
293                         env['PORTAGE_DEPCACHEDIR'] = options.cache_dir
294
295                 settings = portage.config(config_root=config_root,
296                         target_root='/', env=env)
297
298         if not options.update:
299                 parser.error('No action specified (--update ' + \
300                         'is the only available action)')
301                 return 1
302
303         if 'metadata-transfer' not in settings.features:
304                 writemsg_level("ecachegen: warning: " + \
305                         "automatically enabling FEATURES=metadata-transfer\n",
306                         level=logging.WARNING, noiselevel=-1)
307                 settings.features.add('metadata-transfer')
308                 settings['FEATURES'] = ' '.join(sorted(settings.features))
309                 settings.backup_changes('FEATURES')
310
311         settings.lock()
312
313         portdb = portage.portdbapi(settings["PORTDIR"], mysettings=settings)
314         if options.repo is not None:
315                 repo_path = portdb.getRepositoryPath(options.repo)
316                 if repo_path is None:
317                         parser.error("Unable to locate repository named '%s'" % \
318                                 (options.repo,))
319                         return 1
320
321                 # Limit ebuilds to the specified repo.
322                 portdb.porttrees = [repo_path]
323
324         cp_iter = None
325         if atoms:
326                 cp_iter = iter(atoms)
327
328         gen_cache = GenCache(portdb, cp_iter=cp_iter,
329                 max_jobs=options.jobs,
330                 max_load=options.load_average,
331                 rsync=options.rsync)
332         gen_cache.run()
333         return gen_cache.returncode
334
335 if __name__ == "__main__":
336         portage._disable_legacy_globals()
337         portage.util.noiselimit = -1
338         sys.exit(egencache_main(sys.argv[1:]))