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