2 # Copyright 2009-2012 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
5 from __future__ import print_function
9 # This block ensures that ^C interrupts are handled quietly.
12 def exithandler(signum,frame):
13 signal.signal(signal.SIGINT, signal.SIG_IGN)
14 signal.signal(signal.SIGTERM, signal.SIG_IGN)
15 sys.exit(128 + signum)
17 signal.signal(signal.SIGINT, exithandler)
18 signal.signal(signal.SIGTERM, exithandler)
20 except KeyboardInterrupt:
21 sys.exit(128 + signal.SIGINT)
31 from os import path as osp
32 pym_path = osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")
33 sys.path.insert(0, pym_path)
35 from portage import os, _encodings, _unicode_encode, _unicode_decode
36 from _emerge.MetadataRegen import MetadataRegen
37 from portage.cache.cache_errors import CacheError, StatCollision
38 from portage.cache.flat_hash import md5_database
39 from portage.manifest import guessManifestFileType
40 from portage.util import cmp_sort_key, writemsg_level
41 from portage import cpv_getkey
42 from portage.dep import Atom, isjustname
43 from portage.versions import pkgsplit, vercmp
46 from xml.etree import ElementTree
51 from xml.parsers.expat import ExpatError
55 from repoman.utilities import parse_metadata_use
57 from repoman.utilities import FindVCS
59 if sys.hexversion >= 0x3000000:
63 usage = "egencache [options] <action> ... [atom] ..."
64 parser = optparse.OptionParser(usage=usage)
66 actions = optparse.OptionGroup(parser, 'Actions')
67 actions.add_option("--update",
69 help="update metadata/cache/ (generate as necessary)")
70 actions.add_option("--update-use-local-desc",
72 help="update the use.local.desc file from metadata.xml")
73 actions.add_option("--update-changelogs",
75 help="update the ChangeLog files from SCM logs")
76 parser.add_option_group(actions)
78 common = optparse.OptionGroup(parser, 'Common options')
79 common.add_option("--repo",
81 help="name of repo to operate on (default repo is located at $PORTDIR)")
82 common.add_option("--config-root",
83 help="location of portage config files",
84 dest="portage_configroot")
85 common.add_option("--portdir",
86 help="override the portage tree location",
88 common.add_option("--portdir-overlay",
89 help="override the PORTDIR_OVERLAY variable (requires that --repo is also specified)",
90 dest="portdir_overlay")
91 common.add_option("--tolerant",
93 help="exit successfully if only minor errors occurred")
94 common.add_option("--ignore-default-opts",
96 help="do not use the EGENCACHE_DEFAULT_OPTS environment variable")
97 parser.add_option_group(common)
99 update = optparse.OptionGroup(parser, '--update options')
100 update.add_option("--cache-dir",
101 help="location of the metadata cache",
103 update.add_option("-j", "--jobs",
105 help="max ebuild processes to spawn")
106 update.add_option("--load-average",
108 help="max load allowed when spawning multiple jobs",
110 update.add_option("--rsync",
112 help="enable rsync stat collision workaround " + \
113 "for bug 139134 (use with --update)")
114 parser.add_option_group(update)
116 uld = optparse.OptionGroup(parser, '--update-use-local-desc options')
117 uld.add_option("--preserve-comments",
119 help="preserve the comments from the existing use.local.desc file")
120 uld.add_option("--use-local-desc-output",
121 help="output file for use.local.desc data (or '-' for stdout)",
123 parser.add_option_group(uld)
125 options, args = parser.parse_args(args)
130 jobs = int(options.jobs)
135 parser.error("Invalid: --jobs='%s'" % \
143 if options.load_average:
145 load_average = float(options.load_average)
149 if load_average <= 0.0:
150 parser.error("Invalid: --load-average='%s'" % \
151 (options.load_average,))
153 options.load_average = load_average
156 options.load_average = None
158 options.config_root = options.portage_configroot
159 if options.config_root is not None and \
160 not os.path.isdir(options.config_root):
161 parser.error("Not a directory: --config-root='%s'" % \
162 (options.config_root,))
164 if options.cache_dir is not None:
165 if not os.path.isdir(options.cache_dir):
166 parser.error("Not a directory: --cache-dir='%s'" % \
167 (options.cache_dir,))
168 if not os.access(options.cache_dir, os.W_OK):
169 parser.error("Write access denied: --cache-dir='%s'" % \
170 (options.cache_dir,))
172 if options.portdir_overlay is not None and \
173 options.repo is None:
174 parser.error("--portdir-overlay option requires --repo option")
178 atom = portage.dep.Atom(atom)
179 except portage.exception.InvalidAtom:
180 parser.error('Invalid atom: %s' % (atom,))
182 if not isjustname(atom):
183 parser.error('Atom is too specific: %s' % (atom,))
185 if options.update_use_local_desc:
190 parser.error('--update-use-local-desc requires python with USE=xml!')
192 if options.uld_output == '-' and options.preserve_comments:
193 parser.error('--preserve-comments can not be used when outputting to stdout')
195 return parser, options, args
197 class GenCache(object):
198 def __init__(self, portdb, cp_iter=None, max_jobs=None, max_load=None,
200 # The caller must set portdb.porttrees in order to constrain
201 # findname, cp_list, and cpv_list to the desired tree.
202 tree = portdb.porttrees[0]
203 self._portdb = portdb
204 self._eclass_db = portdb.repositories.get_repo_for_location(tree).eclass_db
205 self._auxdbkeys = portdb._known_keys
206 # We can globally cleanse stale cache only if we
207 # iterate over every single cp.
208 self._global_cleanse = cp_iter is None
209 if cp_iter is not None:
210 self._cp_set = set(cp_iter)
211 cp_iter = iter(self._cp_set)
212 self._cp_missing = self._cp_set.copy()
215 self._cp_missing = set()
216 self._regen = MetadataRegen(portdb, cp_iter=cp_iter,
217 consumer=self._metadata_callback,
218 max_jobs=max_jobs, max_load=max_load)
219 self.returncode = os.EX_OK
220 conf = portdb.repositories.get_repo_for_location(tree)
221 self._trg_caches = tuple(conf.iter_pregenerated_caches(
222 self._auxdbkeys, force=True, readonly=False))
223 if not self._trg_caches:
224 raise Exception("cache formats '%s' aren't supported" %
225 (" ".join(conf.cache_formats),))
228 for trg_cache in self._trg_caches:
229 if hasattr(trg_cache, 'raise_stat_collision'):
230 trg_cache.raise_stat_collision = True
231 # Make _metadata_callback write this cache first, in case
232 # it raises a StatCollision and triggers mtime
234 self._trg_caches = tuple([trg_cache] +
235 [x for x in self._trg_caches if x is not trg_cache])
237 self._existing_nodes = set()
239 def _metadata_callback(self, cpv, repo_path, metadata,
240 ebuild_hash, eapi_supported):
241 self._existing_nodes.add(cpv)
242 self._cp_missing.discard(cpv_getkey(cpv))
244 # Since we're supposed to be able to efficiently obtain the
245 # EAPI from _parse_eapi_ebuild_head, we don't write cache
246 # entries for unsupported EAPIs.
247 if metadata is not None and eapi_supported:
248 if metadata.get('EAPI') == '0':
250 for trg_cache in self._trg_caches:
251 self._write_cache(trg_cache,
252 cpv, repo_path, metadata, ebuild_hash)
254 def _write_cache(self, trg_cache, cpv, repo_path, metadata, ebuild_hash):
256 if not hasattr(trg_cache, 'raise_stat_collision'):
257 # This cache does not avoid redundant writes automatically,
258 # so check for an identical existing entry before writing.
259 # This prevents unnecessary disk writes and can also prevent
260 # unnecessary rsync transfers.
262 dest = trg_cache[cpv]
263 except (KeyError, CacheError):
266 if trg_cache.validate_entry(dest,
267 ebuild_hash, self._eclass_db):
269 for k in self._auxdbkeys:
270 if dest.get(k, '') != metadata.get(k, ''):
277 chf = trg_cache.validation_chf
278 metadata['_%s_' % chf] = getattr(ebuild_hash, chf)
280 trg_cache[cpv] = metadata
281 except StatCollision as sc:
282 # If the content of a cache entry changes and neither the
283 # file mtime nor size changes, it will prevent rsync from
284 # detecting changes. Cache backends may raise this
285 # exception from _setitem() if they detect this type of stat
286 # collision. These exceptions are handled by bumping the
287 # mtime on the ebuild (and the corresponding cache entry).
288 # See bug #139134. It is convenient to include checks for
289 # redundant writes along with the internal StatCollision
290 # detection code, so for caches with the
291 # raise_stat_collision attribute, we do not need to
292 # explicitly check for redundant writes like we do for the
293 # other cache types above.
295 for ec, ec_hash in metadata['_eclasses_'].items():
296 if max_mtime < ec_hash.mtime:
297 max_mtime = ec_hash.mtime
298 if max_mtime == sc.mtime:
300 max_mtime = long(max_mtime)
302 os.utime(ebuild_hash.location, (max_mtime, max_mtime))
306 "%s writing target: %s\n" % (cpv, e),
307 level=logging.ERROR, noiselevel=-1)
309 ebuild_hash.mtime = max_mtime
310 metadata['_mtime_'] = max_mtime
311 trg_cache[cpv] = metadata
312 self._portdb.auxdb[repo_path][cpv] = metadata
314 except CacheError as ce:
317 "%s writing target: %s\n" % (cpv, ce),
318 level=logging.ERROR, noiselevel=-1)
324 def sighandler(signum, frame):
325 signal.signal(signal.SIGINT, signal.SIG_IGN)
326 signal.signal(signal.SIGTERM, signal.SIG_IGN)
327 self._regen.terminate()
328 received_signal.append(128 + signum)
330 earlier_sigint_handler = signal.signal(signal.SIGINT, sighandler)
331 earlier_sigterm_handler = signal.signal(signal.SIGTERM, sighandler)
336 # Restore previous handlers
337 if earlier_sigint_handler is not None:
338 signal.signal(signal.SIGINT, earlier_sigint_handler)
340 signal.signal(signal.SIGINT, signal.SIG_DFL)
341 if earlier_sigterm_handler is not None:
342 signal.signal(signal.SIGTERM, earlier_sigterm_handler)
344 signal.signal(signal.SIGTERM, signal.SIG_DFL)
347 sys.exit(received_signal[0])
349 self.returncode |= self._regen.returncode
351 for trg_cache in self._trg_caches:
352 self._cleanse_cache(trg_cache)
354 def _cleanse_cache(self, trg_cache):
355 cp_missing = self._cp_missing
357 if self._global_cleanse:
359 for cpv in trg_cache:
364 "Unable to parse cp for '%s'\n" % (cpv,),
365 level=logging.ERROR, noiselevel=-1)
368 except CacheError as ce:
371 "Error listing cache entries for " + \
372 "'%s/metadata/cache': %s, continuing...\n" % \
373 (self._portdb.porttree_root, ce),
374 level=logging.ERROR, noiselevel=-1)
377 cp_set = self._cp_set
379 for cpv in trg_cache:
384 "Unable to parse cp for '%s'\n" % (cpv,),
385 level=logging.ERROR, noiselevel=-1)
387 cp_missing.discard(cp)
390 except CacheError as ce:
393 "Error listing cache entries for " + \
394 "'%s/metadata/cache': %s, continuing...\n" % \
395 (self._portdb.porttree_root, ce),
396 level=logging.ERROR, noiselevel=-1)
400 for cp in sorted(cp_missing):
402 "No ebuilds or cache entries found for '%s'\n" % (cp,),
403 level=logging.ERROR, noiselevel=-1)
406 dead_nodes.difference_update(self._existing_nodes)
412 except CacheError as ce:
415 "%s deleting stale cache: %s\n" % (k, ce),
416 level=logging.ERROR, noiselevel=-1)
418 if not trg_cache.autocommits:
421 except CacheError as ce:
424 "committing target: %s\n" % (ce,),
425 level=logging.ERROR, noiselevel=-1)
427 if hasattr(trg_cache, '_prune_empty_dirs'):
428 trg_cache._prune_empty_dirs()
430 class GenUseLocalDesc(object):
431 def __init__(self, portdb, output=None,
432 preserve_comments=False):
433 self.returncode = os.EX_OK
434 self._portdb = portdb
435 self._output = output
436 self._preserve_comments = preserve_comments
439 repo_path = self._portdb.porttrees[0]
440 ops = {'<':0, '<=':1, '=':2, '>=':3, '>':4}
442 if self._output is None or self._output != '-':
443 if self._output is None:
444 prof_path = os.path.join(repo_path, 'profiles')
445 desc_path = os.path.join(prof_path, 'use.local.desc')
451 desc_path = self._output
454 if self._preserve_comments:
455 # Probe in binary mode, in order to avoid
456 # potential character encoding issues.
457 output = open(_unicode_encode(desc_path,
458 encoding=_encodings['fs'], errors='strict'), 'r+b')
460 output = io.open(_unicode_encode(desc_path,
461 encoding=_encodings['fs'], errors='strict'),
462 mode='w', encoding=_encodings['repo.content'],
463 errors='backslashreplace')
465 if not self._preserve_comments or \
466 os.path.isfile(desc_path):
468 "ERROR: failed to open output file %s: %s\n" \
469 % (desc_path, e), level=logging.ERROR, noiselevel=-1)
473 # Open in r+b mode failed because the file doesn't
474 # exist yet. We can probably recover if we disable
475 # preserve_comments mode now.
477 "WARNING: --preserve-comments enabled, but " + \
478 "output file not found: %s\n" % (desc_path,),
479 level=logging.WARNING, noiselevel=-1)
480 self._preserve_comments = False
482 output = io.open(_unicode_encode(desc_path,
483 encoding=_encodings['fs'], errors='strict'),
484 mode='w', encoding=_encodings['repo.content'],
485 errors='backslashreplace')
488 "ERROR: failed to open output file %s: %s\n" \
489 % (desc_path, e), level=logging.ERROR, noiselevel=-1)
495 if self._preserve_comments:
498 if not output.readline().startswith(b'#'):
504 # Finished probing comments in binary mode, now append
506 output = io.open(_unicode_encode(desc_path,
507 encoding=_encodings['fs'], errors='strict'),
508 mode='a', encoding=_encodings['repo.content'],
509 errors='backslashreplace')
510 output.write(_unicode_decode('\n'))
512 output.write(textwrap.dedent(_unicode_decode('''\
513 # This file is deprecated as per GLEP 56 in favor of metadata.xml. Please add
514 # your descriptions to your package's metadata.xml ONLY.
515 # * generated automatically using egencache *
519 # The cmp function no longer exists in python3, so we'll
520 # implement our own here under a slightly different name
521 # since we don't want any confusion given that we never
522 # want to rely on the builtin cmp function.
524 if a is None or b is None:
525 # None can't be compared with other types in python3.
526 if a is None and b is None:
532 return (a > b) - (a < b)
534 class _MetadataTreeBuilder(ElementTree.TreeBuilder):
536 Implements doctype() as required to avoid deprecation warnings
539 def doctype(self, name, pubid, system):
542 for cp in self._portdb.cp_all():
543 metadata_path = os.path.join(repo_path, cp, 'metadata.xml')
545 metadata = ElementTree.parse(metadata_path,
546 parser=ElementTree.XMLParser(
547 target=_MetadataTreeBuilder()))
550 except (ExpatError, EnvironmentError) as e:
552 "ERROR: failed parsing %s/metadata.xml: %s\n" % (cp, e),
553 level=logging.ERROR, noiselevel=-1)
557 usedict = parse_metadata_use(metadata)
558 except portage.exception.ParseError as e:
560 "ERROR: failed parsing %s/metadata.xml: %s\n" % (cp, e),
561 level=logging.ERROR, noiselevel=-1)
564 for flag in sorted(usedict):
565 def atomcmp(atoma, atomb):
566 # None is better than an atom, that's why we reverse the args
567 if atoma is None or atomb is None:
568 return cmp_func(atomb, atoma)
569 # Same for plain PNs (.operator is None then)
570 elif atoma.operator is None or atomb.operator is None:
571 return cmp_func(atomb.operator, atoma.operator)
573 elif atoma.cpv != atomb.cpv:
574 return vercmp(atoma.version, atomb.version)
575 # Versions match, let's fallback to operator matching
577 return cmp_func(ops.get(atoma.operator, -1),
578 ops.get(atomb.operator, -1))
585 resdict = usedict[flag]
586 if len(resdict) == 1:
587 resdesc = next(iter(resdict.items()))[1]
590 reskeys = dict((_Atom(k), k) for k in resdict)
591 except portage.exception.InvalidAtom as e:
593 "ERROR: failed parsing %s/metadata.xml: %s\n" % (cp, e),
594 level=logging.ERROR, noiselevel=-1)
596 resdesc = next(iter(resdict.items()))[1]
598 resatoms = sorted(reskeys, key=cmp_sort_key(atomcmp))
599 resdesc = resdict[reskeys[resatoms[-1]]]
601 output.write(_unicode_decode(
602 '%s:%s - %s\n' % (cp, flag, resdesc)))
606 if sys.hexversion < 0x3000000:
607 _filename_base = unicode
611 class _special_filename(_filename_base):
613 Helps to sort file names by file type and other criteria.
615 def __new__(cls, status_change, file_name):
616 return _filename_base.__new__(cls, status_change + file_name)
618 def __init__(self, status_change, file_name):
619 _filename_base.__init__(status_change + file_name)
620 self.status_change = status_change
621 self.file_name = file_name
622 self.file_type = guessManifestFileType(file_name)
624 def file_type_lt(self, a, b):
626 Defines an ordering between file types.
633 if first == "EBUILD":
635 elif first == "MISC":
636 return second in ("EBUILD",)
638 return second in ("EBUILD", "MISC")
639 elif first == "DIST":
640 return second in ("EBUILD", "MISC", "AUX")
644 raise ValueError("Unknown file type '%s'" % first)
646 def __lt__(self, other):
648 Compare different file names, first by file type and then
649 for ebuilds by version and lexicographically for others.
650 EBUILD < MISC < AUX < DIST < None
652 if self.__class__ != other.__class__:
653 raise NotImplementedError
655 # Sort by file type as defined by file_type_lt().
656 if self.file_type_lt(self, other):
658 elif self.file_type_lt(other, self):
661 # Files have the same type.
662 if self.file_type == "EBUILD":
663 # Sort by version. Lowest first.
664 ver = "-".join(pkgsplit(self.file_name[:-7])[1:3])
665 other_ver = "-".join(pkgsplit(other.file_name[:-7])[1:3])
666 return vercmp(ver, other_ver) < 0
668 # Sort lexicographically.
669 return self.file_name < other.file_name
671 class GenChangeLogs(object):
672 def __init__(self, portdb):
673 self.returncode = os.EX_OK
674 self._portdb = portdb
675 self._wrapper = textwrap.TextWrapper(
677 initial_indent = ' ',
678 subsequent_indent = ' '
683 p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
684 return _unicode_decode(p.communicate()[0],
685 encoding=_encodings['stdio'], errors='strict')
687 def generate_changelog(self, cp):
689 output = io.open('ChangeLog',
690 mode='w', encoding=_encodings['repo.content'],
691 errors='backslashreplace')
694 "ERROR: failed to open ChangeLog for %s: %s\n" % (cp,e,),
695 level=logging.ERROR, noiselevel=-1)
699 output.write(textwrap.dedent(_unicode_decode('''\
701 # Copyright 1999-%s Gentoo Foundation; Distributed under the GPL v2
704 ''' % (cp, time.strftime('%Y')))))
706 # now grab all the commits
707 commits = self.grab(['git', 'rev-list', 'HEAD', '--', '.']).split()
710 # Explaining the arguments:
711 # --name-status to get a list of added/removed files
712 # --no-renames to avoid getting more complex records on the list
713 # --format to get the timestamp, author and commit description
714 # --root to make it work fine even with the initial commit
715 # --relative to get paths relative to ebuilddir
716 # -r (recursive) to get per-file changes
717 # then the commit-id and path.
719 cinfo = self.grab(['git', 'diff-tree', '--name-status', '--no-renames',
720 '--format=%ct %cN <%cE>%n%B', '--root', '--relative', '-r',
721 c, '--', '.']).rstrip('\n').split('\n')
724 # timestamp Author Name <author@email>
734 for n, l in enumerate(reversed(cinfo)):
740 if f[1] == 'Manifest':
741 pass # XXX: remanifest commits?
742 elif f[1] == 'ChangeLog':
744 elif f[0].startswith('A'):
745 changed.append(_special_filename("+", f[1]))
746 elif f[0].startswith('D'):
747 changed.append(_special_filename("-", f[1]))
748 elif f[0].startswith('M'):
749 changed.append(_special_filename("", f[1]))
752 "ERROR: unexpected git file status for %s: %s\n" % (cp,f,),
753 level=logging.ERROR, noiselevel=-1)
759 (ts, author) = cinfo[0].split(' ', 1)
760 date = time.strftime('%d %b %Y', time.gmtime(float(ts)))
762 changed = [str(x) for x in sorted(changed)]
765 # Reverse the sort order for headers.
766 for c in reversed(changed):
767 if c.startswith('+') and c.endswith('.ebuild'):
768 output.write(_unicode_decode(
769 '*%s (%s)\n' % (c[1:-7], date)))
772 output.write(_unicode_decode('\n'))
774 # strip '<cp>: ', '[<cp>] ', and similar
775 body[0] = re.sub(r'^\W*' + re.escape(cp) + r'\W+', '', body[0])
776 # strip trailing newline
780 if body[-1].startswith('git-svn-id:') and not body[-2]:
782 # strip the repoman version/manifest note
783 if body[-1] == ' (Signed Manifest commit)' or body[-1] == ' (Unsigned Manifest commit)':
785 if body[-1].startswith('(Portage version:') and body[-1].endswith(')'):
790 # don't break filenames on hyphens
791 self._wrapper.break_on_hyphens = False
792 output.write(_unicode_decode(
794 '%s; %s %s:' % (date, author, ', '.join(changed)))))
795 # but feel free to break commit messages there
796 self._wrapper.break_on_hyphens = True
797 output.write(_unicode_decode(
798 '\n%s\n\n' % '\n'.join(self._wrapper.fill(x) for x in body)))
803 repo_path = self._portdb.porttrees[0]
806 if 'git' not in FindVCS():
808 "ERROR: --update-changelogs supported only in git repos\n",
809 level=logging.ERROR, noiselevel=-1)
810 self.returncode = 127
813 for cp in self._portdb.cp_all():
814 os.chdir(os.path.join(repo_path, cp))
815 # Determine whether ChangeLog is up-to-date by comparing
816 # the newest commit timestamp with the ChangeLog timestamp.
817 lmod = self.grab(['git', 'log', '--format=%ct', '-1', '.'])
819 # This cp has not been added to the repo.
823 cmod = os.stat('ChangeLog').st_mtime
827 if float(cmod) < float(lmod):
828 self.generate_changelog(cp)
830 def egencache_main(args):
831 parser, options, atoms = parse_args(args)
833 config_root = options.config_root
835 # The calling environment is ignored, so the program is
836 # completely controlled by commandline arguments.
839 if options.repo is None:
840 env['PORTDIR_OVERLAY'] = ''
841 elif options.portdir_overlay:
842 env['PORTDIR_OVERLAY'] = options.portdir_overlay
844 if options.cache_dir is not None:
845 env['PORTAGE_DEPCACHEDIR'] = options.cache_dir
847 if options.portdir is not None:
848 env['PORTDIR'] = options.portdir
850 settings = portage.config(config_root=config_root,
851 local_config=False, env=env)
854 if not options.ignore_default_opts:
855 default_opts = settings.get('EGENCACHE_DEFAULT_OPTS', '').split()
858 parser, options, args = parse_args(default_opts + args)
860 if options.cache_dir is not None:
861 env['PORTAGE_DEPCACHEDIR'] = options.cache_dir
863 settings = portage.config(config_root=config_root,
864 local_config=False, env=env)
866 if not options.update and not options.update_use_local_desc \
867 and not options.update_changelogs:
868 parser.error('No action specified')
872 if options.repo is not None:
873 repo_path = settings.repositories.treemap.get(options.repo)
874 if repo_path is None:
875 parser.error("Unable to locate repository named '%s'" % \
879 repo_path = settings.repositories.mainRepoLocation()
881 parser.error("PORTDIR is undefined")
884 if options.update and 'metadata-transfer' not in settings.features:
885 metadata_transfer = True
886 repo_config = settings.repositories.get_repo_for_location(repo_path)
887 cache = repo_config.get_pregenerated_cache(
888 portage.dbapi.dbapi._known_keys, readonly=True)
889 if cache is not None:
890 if isinstance(cache, md5_database) and \
891 os.path.isdir(cache.location):
892 metadata_transfer = False
895 if metadata_transfer:
896 settings.features.add('metadata-transfer')
900 portdb = portage.portdbapi(mysettings=settings)
902 # Limit ebuilds to the specified repo.
903 portdb.porttrees = [repo_path]
906 if options.cache_dir is not None:
907 # already validated earlier
910 # We check write access after the portdbapi constructor
911 # has had an opportunity to create it. This ensures that
912 # we don't use the cache in the "volatile" mode which is
913 # undesirable for egencache.
914 if not os.access(settings["PORTAGE_DEPCACHEDIR"], os.W_OK):
915 writemsg_level("ecachegen: error: " + \
916 "write access denied: %s\n" % (settings["PORTAGE_DEPCACHEDIR"],),
917 level=logging.ERROR, noiselevel=-1)
925 cp_iter = iter(atoms)
927 gen_cache = GenCache(portdb, cp_iter=cp_iter,
928 max_jobs=options.jobs,
929 max_load=options.load_average,
935 ret.append(gen_cache.returncode)
937 if options.update_use_local_desc:
938 gen_desc = GenUseLocalDesc(portdb,
939 output=options.uld_output,
940 preserve_comments=options.preserve_comments)
942 ret.append(gen_desc.returncode)
944 if options.update_changelogs:
945 gen_clogs = GenChangeLogs(portdb)
947 ret.append(gen_clogs.returncode)
951 if __name__ == "__main__":
952 portage._disable_legacy_globals()
953 portage.util.noiselimit = -1
954 sys.exit(egencache_main(sys.argv[1:]))