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