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