Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH 3/7] binpkg-multi-instance 3 of 7
Date: Wed, 18 Feb 2015 03:06:52
Message-Id: 1424228745-7794-4-git-send-email-zmedico@gentoo.org
In Reply to: [gentoo-portage-dev] [PATCH 0/7] Add FEATURES=binpkg-multi-instance (bug 150031) by Zac Medico
1 FEATURES=binpkg-multi-instance causes an integer build-id to be
2 associated with each binary package instance. Inclusion of the build-id
3 in the file name of the binary package file makes it possible to store
4 an arbitrary number of binary packages built from the same ebuild.
5
6 Having multiple instances is useful for a number of purposes, such as
7 retaining builds that were built with different USE flags or linked
8 against different versions of libraries. The location of any particular
9 package within PKGDIR can be expressed as follows:
10
11 ${PKGDIR}/${CATEGORY}/${PN}/${PF}-${BUILD_ID}.xpak
12
13 The build-id starts at 1 for the first build of a particular ebuild,
14 and is incremented by 1 for each new build. It is possible to share a
15 writable PKGDIR over NFS, and locking ensures that each package added
16 to PKGDIR will have a unique build-id. It is not necessary to migrate
17 an existing PKGDIR to the new layout, since portage is capable of
18 working with a mixed PKGDIR layout, where packages using the old layout
19 are allowed to remain in place.
20
21 The new PKGDIR layout is backward-compatible with binhost clients
22 running older portage, since the file format is identical, the
23 per-package PATH attribute in the 'Packages' index directs them to
24 download the file from the correct URI, and they automatically use
25 BUILD_TIME metadata to select the latest builds.
26
27 There is currently no automated way to prune old builds from PKGDIR,
28 although it is possible to remove packages manually, and then run
29 'emaint --fix binhost' to update the ${PKGDIR}/Packages index. Support
30 for FEATURES=binpkg-multi-instance is planned for eclean-pkg.
31
32 X-Gentoo-Bug: 150031
33 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=150031
34 ---
35 bin/quickpkg | 1 -
36 man/make.conf.5 | 27 +
37 pym/_emerge/Binpkg.py | 33 +-
38 pym/_emerge/BinpkgFetcher.py | 13 +-
39 pym/_emerge/BinpkgVerifier.py | 6 +-
40 pym/_emerge/EbuildBinpkg.py | 9 +-
41 pym/_emerge/EbuildBuild.py | 36 +-
42 pym/_emerge/Package.py | 16 +-
43 pym/_emerge/Scheduler.py | 6 +-
44 pym/_emerge/clear_caches.py | 1 -
45 pym/portage/const.py | 2 +
46 pym/portage/dbapi/bintree.py | 684 +++++++++++++++++---------
47 pym/portage/emaint/modules/binhost/binhost.py | 47 +-
48 13 files changed, 601 insertions(+), 280 deletions(-)
49
50 diff --git a/bin/quickpkg b/bin/quickpkg
51 index 2c69a69..8b71c3e 100755
52 --- a/bin/quickpkg
53 +++ b/bin/quickpkg
54 @@ -63,7 +63,6 @@ def quickpkg_atom(options, infos, arg, eout):
55 pkgs_for_arg = 0
56 for cpv in matches:
57 excluded_config_files = []
58 - bintree.prevent_collision(cpv)
59 dblnk = vardb._dblink(cpv)
60 have_lock = False
61
62 diff --git a/man/make.conf.5 b/man/make.conf.5
63 index 84b7191..6ead61b 100644
64 --- a/man/make.conf.5
65 +++ b/man/make.conf.5
66 @@ -256,6 +256,33 @@ has a \fB\-\-force\fR option that can be used to force regeneration of digests.
67 Keep logs from successful binary package merges. This is relevant only when
68 \fBPORT_LOGDIR\fR is set.
69 .TP
70 +.B binpkg\-multi\-instance
71 +Enable support for multiple binary package instances per ebuild.
72 +Having multiple instances is useful for a number of purposes, such as
73 +retaining builds that were built with different USE flags or linked
74 +against different versions of libraries. The location of any particular
75 +package within PKGDIR can be expressed as follows:
76 +
77 + ${PKGDIR}/${CATEGORY}/${PN}/${PF}\-${BUILD_ID}.xpak
78 +
79 +The build\-id starts at 1 for the first build of a particular ebuild,
80 +and is incremented by 1 for each new build. It is possible to share a
81 +writable PKGDIR over NFS, and locking ensures that each package added
82 +to PKGDIR will have a unique build\-id. It is not necessary to migrate
83 +an existing PKGDIR to the new layout, since portage is capable of
84 +working with a mixed PKGDIR layout, where packages using the old layout
85 +are allowed to remain in place.
86 +
87 +The new PKGDIR layout is backward\-compatible with binhost clients
88 +running older portage, since the file format is identical, the
89 +per\-package PATH attribute in the 'Packages' index directs them to
90 +download the file from the correct URI, and they automatically use
91 +BUILD_TIME metadata to select the latest builds.
92 +
93 +There is currently no automated way to prune old builds from PKGDIR,
94 +although it is possible to remove packages manually, and then run
95 +\(aqemaint \-\-fix binhost' to update the ${PKGDIR}/Packages index.
96 +.TP
97 .B buildpkg
98 Binary packages will be created for all packages that are merged. Also see
99 \fBquickpkg\fR(1) and \fBemerge\fR(1) \fB\-\-buildpkg\fR and
100 diff --git a/pym/_emerge/Binpkg.py b/pym/_emerge/Binpkg.py
101 index ded6dfd..7b7ae17 100644
102 --- a/pym/_emerge/Binpkg.py
103 +++ b/pym/_emerge/Binpkg.py
104 @@ -121,16 +121,11 @@ class Binpkg(CompositeTask):
105 fetcher = BinpkgFetcher(background=self.background,
106 logfile=self.settings.get("PORTAGE_LOG_FILE"), pkg=self.pkg,
107 pretend=self.opts.pretend, scheduler=self.scheduler)
108 - pkg_path = fetcher.pkg_path
109 - self._pkg_path = pkg_path
110 - # This gives bashrc users an opportunity to do various things
111 - # such as remove binary packages after they're installed.
112 - self.settings["PORTAGE_BINPKG_FILE"] = pkg_path
113
114 if self.opts.getbinpkg and self._bintree.isremote(pkg.cpv):
115 -
116 msg = " --- (%s of %s) Fetching Binary (%s::%s)" %\
117 - (pkg_count.curval, pkg_count.maxval, pkg.cpv, pkg_path)
118 + (pkg_count.curval, pkg_count.maxval, pkg.cpv,
119 + fetcher.pkg_path)
120 short_msg = "emerge: (%s of %s) %s Fetch" % \
121 (pkg_count.curval, pkg_count.maxval, pkg.cpv)
122 self.logger.log(msg, short_msg=short_msg)
123 @@ -149,7 +144,7 @@ class Binpkg(CompositeTask):
124 # The fetcher only has a returncode when
125 # --getbinpkg is enabled.
126 if fetcher.returncode is not None:
127 - self._fetched_pkg = True
128 + self._fetched_pkg = fetcher.pkg_path
129 if self._default_exit(fetcher) != os.EX_OK:
130 self._unlock_builddir()
131 self.wait()
132 @@ -163,9 +158,15 @@ class Binpkg(CompositeTask):
133
134 verifier = None
135 if self._verify:
136 + if self._fetched_pkg:
137 + path = self._fetched_pkg
138 + else:
139 + path = self.pkg.root_config.trees["bintree"].getname(
140 + self.pkg.cpv)
141 logfile = self.settings.get("PORTAGE_LOG_FILE")
142 verifier = BinpkgVerifier(background=self.background,
143 - logfile=logfile, pkg=self.pkg, scheduler=self.scheduler)
144 + logfile=logfile, pkg=self.pkg, scheduler=self.scheduler,
145 + _pkg_path=path)
146 self._start_task(verifier, self._verifier_exit)
147 return
148
149 @@ -181,10 +182,20 @@ class Binpkg(CompositeTask):
150 logger = self.logger
151 pkg = self.pkg
152 pkg_count = self.pkg_count
153 - pkg_path = self._pkg_path
154
155 if self._fetched_pkg:
156 - self._bintree.inject(pkg.cpv, filename=pkg_path)
157 + pkg_path = self._bintree.getname(
158 + self._bintree.inject(pkg.cpv,
159 + filename=self._fetched_pkg),
160 + allocate_new=False)
161 + else:
162 + pkg_path = self.pkg.root_config.trees["bintree"].getname(
163 + self.pkg.cpv)
164 +
165 + # This gives bashrc users an opportunity to do various things
166 + # such as remove binary packages after they're installed.
167 + self.settings["PORTAGE_BINPKG_FILE"] = pkg_path
168 + self._pkg_path = pkg_path
169
170 logfile = self.settings.get("PORTAGE_LOG_FILE")
171 if logfile is not None and os.path.isfile(logfile):
172 diff --git a/pym/_emerge/BinpkgFetcher.py b/pym/_emerge/BinpkgFetcher.py
173 index 543881e..a7f2d44 100644
174 --- a/pym/_emerge/BinpkgFetcher.py
175 +++ b/pym/_emerge/BinpkgFetcher.py
176 @@ -24,7 +24,8 @@ class BinpkgFetcher(SpawnProcess):
177 def __init__(self, **kwargs):
178 SpawnProcess.__init__(self, **kwargs)
179 pkg = self.pkg
180 - self.pkg_path = pkg.root_config.trees["bintree"].getname(pkg.cpv)
181 + self.pkg_path = pkg.root_config.trees["bintree"].getname(
182 + pkg.cpv) + ".partial"
183
184 def _start(self):
185
186 @@ -51,10 +52,12 @@ class BinpkgFetcher(SpawnProcess):
187 # urljoin doesn't work correctly with
188 # unrecognized protocols like sftp
189 if bintree._remote_has_index:
190 - rel_uri = bintree._remotepkgs[pkg.cpv].get("PATH")
191 + instance_key = bintree.dbapi._instance_key(pkg.cpv)
192 + rel_uri = bintree._remotepkgs[instance_key].get("PATH")
193 if not rel_uri:
194 rel_uri = pkg.cpv + ".tbz2"
195 - remote_base_uri = bintree._remotepkgs[pkg.cpv]["BASE_URI"]
196 + remote_base_uri = bintree._remotepkgs[
197 + instance_key]["BASE_URI"]
198 uri = remote_base_uri.rstrip("/") + "/" + rel_uri.lstrip("/")
199 else:
200 uri = settings["PORTAGE_BINHOST"].rstrip("/") + \
201 @@ -128,7 +131,9 @@ class BinpkgFetcher(SpawnProcess):
202 # the fetcher didn't already do it automatically.
203 bintree = self.pkg.root_config.trees["bintree"]
204 if bintree._remote_has_index:
205 - remote_mtime = bintree._remotepkgs[self.pkg.cpv].get("MTIME")
206 + remote_mtime = bintree._remotepkgs[
207 + bintree.dbapi._instance_key(
208 + self.pkg.cpv)].get("MTIME")
209 if remote_mtime is not None:
210 try:
211 remote_mtime = long(remote_mtime)
212 diff --git a/pym/_emerge/BinpkgVerifier.py b/pym/_emerge/BinpkgVerifier.py
213 index 2c69792..7a6d15e 100644
214 --- a/pym/_emerge/BinpkgVerifier.py
215 +++ b/pym/_emerge/BinpkgVerifier.py
216 @@ -33,7 +33,6 @@ class BinpkgVerifier(CompositeTask):
217 digests = _apply_hash_filter(digests, hash_filter)
218
219 self._digests = digests
220 - self._pkg_path = bintree.getname(self.pkg.cpv)
221
222 try:
223 size = os.stat(self._pkg_path).st_size
224 @@ -90,8 +89,11 @@ class BinpkgVerifier(CompositeTask):
225 if portage.output.havecolor:
226 portage.output.havecolor = not self.background
227
228 + path = self._pkg_path
229 + if path.endswith(".partial"):
230 + path = path[:-len(".partial")]
231 eout = EOutput()
232 - eout.ebegin("%s %s ;-)" % (os.path.basename(self._pkg_path),
233 + eout.ebegin("%s %s ;-)" % (os.path.basename(path),
234 " ".join(sorted(self._digests))))
235 eout.eend(0)
236
237 diff --git a/pym/_emerge/EbuildBinpkg.py b/pym/_emerge/EbuildBinpkg.py
238 index 34a6aef..6e098eb 100644
239 --- a/pym/_emerge/EbuildBinpkg.py
240 +++ b/pym/_emerge/EbuildBinpkg.py
241 @@ -10,13 +10,12 @@ class EbuildBinpkg(CompositeTask):
242 This assumes that src_install() has successfully completed.
243 """
244 __slots__ = ('pkg', 'settings') + \
245 - ('_binpkg_tmpfile',)
246 + ('_binpkg_tmpfile', '_binpkg_info')
247
248 def _start(self):
249 pkg = self.pkg
250 root_config = pkg.root_config
251 bintree = root_config.trees["bintree"]
252 - bintree.prevent_collision(pkg.cpv)
253 binpkg_tmpfile = os.path.join(bintree.pkgdir,
254 pkg.cpv + ".tbz2." + str(os.getpid()))
255 bintree._ensure_dir(os.path.dirname(binpkg_tmpfile))
256 @@ -43,8 +42,12 @@ class EbuildBinpkg(CompositeTask):
257
258 pkg = self.pkg
259 bintree = pkg.root_config.trees["bintree"]
260 - bintree.inject(pkg.cpv, filename=self._binpkg_tmpfile)
261 + self._binpkg_info = bintree.inject(pkg.cpv,
262 + filename=self._binpkg_tmpfile)
263
264 self._current_task = None
265 self.returncode = os.EX_OK
266 self.wait()
267 +
268 + def get_binpkg_info(self):
269 + return self._binpkg_info
270 diff --git a/pym/_emerge/EbuildBuild.py b/pym/_emerge/EbuildBuild.py
271 index b5b1e87..0e98602 100644
272 --- a/pym/_emerge/EbuildBuild.py
273 +++ b/pym/_emerge/EbuildBuild.py
274 @@ -1,6 +1,10 @@
275 # Copyright 1999-2014 Gentoo Foundation
276 # Distributed under the terms of the GNU General Public License v2
277
278 +from __future__ import unicode_literals
279 +
280 +import io
281 +
282 import _emerge.emergelog
283 from _emerge.EbuildExecuter import EbuildExecuter
284 from _emerge.EbuildPhase import EbuildPhase
285 @@ -15,7 +19,7 @@ from _emerge.TaskSequence import TaskSequence
286
287 from portage.util import writemsg
288 import portage
289 -from portage import os
290 +from portage import _encodings, _unicode_decode, _unicode_encode, os
291 from portage.output import colorize
292 from portage.package.ebuild.digestcheck import digestcheck
293 from portage.package.ebuild.digestgen import digestgen
294 @@ -317,9 +321,13 @@ class EbuildBuild(CompositeTask):
295 phase="rpm", scheduler=self.scheduler,
296 settings=self.settings))
297 else:
298 - binpkg_tasks.add(EbuildBinpkg(background=self.background,
299 + task = EbuildBinpkg(
300 + background=self.background,
301 pkg=self.pkg, scheduler=self.scheduler,
302 - settings=self.settings))
303 + settings=self.settings)
304 + binpkg_tasks.add(task)
305 + task.addExitListener(
306 + self._record_binpkg_info)
307
308 if binpkg_tasks:
309 self._start_task(binpkg_tasks, self._buildpkg_exit)
310 @@ -356,6 +364,28 @@ class EbuildBuild(CompositeTask):
311 self.returncode = packager.returncode
312 self.wait()
313
314 + def _record_binpkg_info(self, task):
315 + if task.returncode != os.EX_OK:
316 + return
317 +
318 + # Save info about the created binary package, so that
319 + # identifying information can be passed to the install
320 + # task, to be recorded in the installed package database.
321 + pkg = task.get_binpkg_info()
322 + infoloc = os.path.join(self.settings["PORTAGE_BUILDDIR"],
323 + "build-info")
324 + info = {
325 + "BINPKGMD5": "%s\n" % pkg._metadata["MD5"],
326 + }
327 + if pkg.build_id is not None:
328 + info["BUILD_ID"] = "%s\n" % pkg.build_id
329 + for k, v in info.items():
330 + with io.open(_unicode_encode(os.path.join(infoloc, k),
331 + encoding=_encodings['fs'], errors='strict'),
332 + mode='w', encoding=_encodings['repo.content'],
333 + errors='strict') as f:
334 + f.write(v)
335 +
336 def _buildpkgonly_success_hook_exit(self, success_hooks):
337 self._default_exit(success_hooks)
338 self.returncode = None
339 diff --git a/pym/_emerge/Package.py b/pym/_emerge/Package.py
340 index 975335d..2c1a116 100644
341 --- a/pym/_emerge/Package.py
342 +++ b/pym/_emerge/Package.py
343 @@ -219,6 +219,8 @@ class Package(Task):
344 else:
345 raise TypeError("root_config argument is required")
346
347 + elements = [type_name, root, _unicode(cpv), operation]
348 +
349 # For installed (and binary) packages we don't care for the repo
350 # when it comes to hashing, because there can only be one cpv.
351 # So overwrite the repo_key with type_name.
352 @@ -229,14 +231,22 @@ class Package(Task):
353 raise AssertionError(
354 "Package._gen_hash_key() " + \
355 "called without 'repo_name' argument")
356 - repo_key = repo_name
357 + elements.append(repo_name)
358 + elif type_name == "binary":
359 + # Including a variety of fingerprints in the hash makes
360 + # it possible to simultaneously consider multiple similar
361 + # packages. Note that digests are not included here, since
362 + # they are relatively expensive to compute, and they may
363 + # not necessarily be available.
364 + elements.extend([cpv.build_id, cpv.file_size,
365 + cpv.build_time, cpv.mtime])
366 else:
367 # For installed (and binary) packages we don't care for the repo
368 # when it comes to hashing, because there can only be one cpv.
369 # So overwrite the repo_key with type_name.
370 - repo_key = type_name
371 + elements.append(type_name)
372
373 - return (type_name, root, _unicode(cpv), operation, repo_key)
374 + return tuple(elements)
375
376 def _validate_deps(self):
377 """
378 diff --git a/pym/_emerge/Scheduler.py b/pym/_emerge/Scheduler.py
379 index d6db311..dae7944 100644
380 --- a/pym/_emerge/Scheduler.py
381 +++ b/pym/_emerge/Scheduler.py
382 @@ -862,8 +862,12 @@ class Scheduler(PollScheduler):
383 continue
384 fetched = fetcher.pkg_path
385
386 + if fetched is False:
387 + filename = bintree.getname(x.cpv)
388 + else:
389 + filename = fetched
390 verifier = BinpkgVerifier(pkg=x,
391 - scheduler=sched_iface)
392 + scheduler=sched_iface, _pkg_path=filename)
393 current_task = verifier
394 verifier.start()
395 if verifier.wait() != os.EX_OK:
396 diff --git a/pym/_emerge/clear_caches.py b/pym/_emerge/clear_caches.py
397 index 513df62..cb0db10 100644
398 --- a/pym/_emerge/clear_caches.py
399 +++ b/pym/_emerge/clear_caches.py
400 @@ -7,7 +7,6 @@ def clear_caches(trees):
401 for d in trees.values():
402 d["porttree"].dbapi.melt()
403 d["porttree"].dbapi._aux_cache.clear()
404 - d["bintree"].dbapi._aux_cache.clear()
405 d["bintree"].dbapi._clear_cache()
406 if d["vartree"].dbapi._linkmap is None:
407 # preserve-libs is entirely disabled
408 diff --git a/pym/portage/const.py b/pym/portage/const.py
409 index febdb4a..c7ecda2 100644
410 --- a/pym/portage/const.py
411 +++ b/pym/portage/const.py
412 @@ -122,6 +122,7 @@ EBUILD_PHASES = (
413 SUPPORTED_FEATURES = frozenset([
414 "assume-digests",
415 "binpkg-logs",
416 + "binpkg-multi-instance",
417 "buildpkg",
418 "buildsyspkg",
419 "candy",
420 @@ -268,6 +269,7 @@ LIVE_ECLASSES = frozenset([
421 ])
422
423 SUPPORTED_BINPKG_FORMATS = ("tar", "rpm")
424 +SUPPORTED_XPAK_EXTENSIONS = (".tbz2", ".xpak")
425
426 # Time formats used in various places like metadata.chk.
427 TIMESTAMP_FORMAT = "%a, %d %b %Y %H:%M:%S +0000" # to be used with time.gmtime()
428 diff --git a/pym/portage/dbapi/bintree.py b/pym/portage/dbapi/bintree.py
429 index cd30b67..460b9f7 100644
430 --- a/pym/portage/dbapi/bintree.py
431 +++ b/pym/portage/dbapi/bintree.py
432 @@ -17,14 +17,13 @@ portage.proxy.lazyimport.lazyimport(globals(),
433 'portage.update:update_dbentries',
434 'portage.util:atomic_ofstream,ensure_dirs,normalize_path,' + \
435 'writemsg,writemsg_stdout',
436 - 'portage.util.listdir:listdir',
437 'portage.util.path:first_existing',
438 'portage.util._urlopen:urlopen@_urlopen',
439 'portage.versions:best,catpkgsplit,catsplit,_pkg_str',
440 )
441
442 from portage.cache.mappings import slot_dict_class
443 -from portage.const import CACHE_PATH
444 +from portage.const import CACHE_PATH, SUPPORTED_XPAK_EXTENSIONS
445 from portage.dbapi.virtual import fakedbapi
446 from portage.dep import Atom, use_reduce, paren_enclose
447 from portage.exception import AlarmSignal, InvalidData, InvalidPackageName, \
448 @@ -71,18 +70,26 @@ class bindbapi(fakedbapi):
449 _known_keys = frozenset(list(fakedbapi._known_keys) + \
450 ["CHOST", "repository", "USE"])
451 def __init__(self, mybintree=None, **kwargs):
452 - fakedbapi.__init__(self, **kwargs)
453 + # Always enable multi_instance mode for bindbapi indexing. This
454 + # does not affect the local PKGDIR file layout, since that is
455 + # controlled independently by FEATURES=binpkg-multi-instance.
456 + # The multi_instance mode is useful for the following reasons:
457 + # * binary packages with the same cpv from multiple binhosts
458 + # can be considered simultaneously
459 + # * if binpkg-multi-instance is disabled, it's still possible
460 + # to properly access a PKGDIR which has binpkg-multi-instance
461 + # layout (or mixed layout)
462 + fakedbapi.__init__(self, exclusive_slots=False,
463 + multi_instance=True, **kwargs)
464 self.bintree = mybintree
465 self.move_ent = mybintree.move_ent
466 - self.cpvdict={}
467 - self.cpdict={}
468 # Selectively cache metadata in order to optimize dep matching.
469 self._aux_cache_keys = set(
470 - ["BUILD_TIME", "CHOST", "DEPEND", "EAPI",
471 - "HDEPEND", "IUSE", "KEYWORDS",
472 - "LICENSE", "PDEPEND", "PROPERTIES", "PROVIDE",
473 - "RDEPEND", "repository", "RESTRICT", "SLOT", "USE",
474 - "DEFINED_PHASES", "PROVIDES", "REQUIRES"
475 + ["BUILD_ID", "BUILD_TIME", "CHOST", "DEFINED_PHASES",
476 + "DEPEND", "EAPI", "HDEPEND", "IUSE", "KEYWORDS",
477 + "LICENSE", "MD5", "PDEPEND", "PROPERTIES", "PROVIDE",
478 + "PROVIDES", "RDEPEND", "repository", "REQUIRES", "RESTRICT",
479 + "SIZE", "SLOT", "USE", "_mtime_"
480 ])
481 self._aux_cache_slot_dict = slot_dict_class(self._aux_cache_keys)
482 self._aux_cache = {}
483 @@ -109,33 +116,49 @@ class bindbapi(fakedbapi):
484 return fakedbapi.cpv_exists(self, cpv)
485
486 def cpv_inject(self, cpv, **kwargs):
487 - self._aux_cache.pop(cpv, None)
488 - fakedbapi.cpv_inject(self, cpv, **kwargs)
489 + if not self.bintree.populated:
490 + self.bintree.populate()
491 + fakedbapi.cpv_inject(self, cpv,
492 + metadata=cpv._metadata, **kwargs)
493
494 def cpv_remove(self, cpv):
495 - self._aux_cache.pop(cpv, None)
496 + if not self.bintree.populated:
497 + self.bintree.populate()
498 fakedbapi.cpv_remove(self, cpv)
499
500 def aux_get(self, mycpv, wants, myrepo=None):
501 if self.bintree and not self.bintree.populated:
502 self.bintree.populate()
503 - cache_me = False
504 + # Support plain string for backward compatibility with API
505 + # consumers (including portageq, which passes in a cpv from
506 + # a command-line argument).
507 + instance_key = self._instance_key(mycpv,
508 + support_string=True)
509 if not self._known_keys.intersection(
510 wants).difference(self._aux_cache_keys):
511 - aux_cache = self._aux_cache.get(mycpv)
512 + aux_cache = self.cpvdict[instance_key]
513 if aux_cache is not None:
514 return [aux_cache.get(x, "") for x in wants]
515 - cache_me = True
516 mysplit = mycpv.split("/")
517 mylist = []
518 tbz2name = mysplit[1]+".tbz2"
519 if not self.bintree._remotepkgs or \
520 not self.bintree.isremote(mycpv):
521 - tbz2_path = self.bintree.getname(mycpv)
522 - if not os.path.exists(tbz2_path):
523 + try:
524 + tbz2_path = self.bintree._pkg_paths[instance_key]
525 + except KeyError:
526 + raise KeyError(mycpv)
527 + tbz2_path = os.path.join(self.bintree.pkgdir, tbz2_path)
528 + try:
529 + st = os.lstat(tbz2_path)
530 + except OSError:
531 raise KeyError(mycpv)
532 metadata_bytes = portage.xpak.tbz2(tbz2_path).get_data()
533 def getitem(k):
534 + if k == "_mtime_":
535 + return _unicode(st[stat.ST_MTIME])
536 + elif k == "SIZE":
537 + return _unicode(st.st_size)
538 v = metadata_bytes.get(_unicode_encode(k,
539 encoding=_encodings['repo.content'],
540 errors='backslashreplace'))
541 @@ -144,11 +167,9 @@ class bindbapi(fakedbapi):
542 encoding=_encodings['repo.content'], errors='replace')
543 return v
544 else:
545 - getitem = self.bintree._remotepkgs[mycpv].get
546 + getitem = self.cpvdict[instance_key].get
547 mydata = {}
548 mykeys = wants
549 - if cache_me:
550 - mykeys = self._aux_cache_keys.union(wants)
551 for x in mykeys:
552 myval = getitem(x)
553 # myval is None if the key doesn't exist
554 @@ -159,16 +180,24 @@ class bindbapi(fakedbapi):
555 if not mydata.setdefault('EAPI', '0'):
556 mydata['EAPI'] = '0'
557
558 - if cache_me:
559 - aux_cache = self._aux_cache_slot_dict()
560 - for x in self._aux_cache_keys:
561 - aux_cache[x] = mydata.get(x, '')
562 - self._aux_cache[mycpv] = aux_cache
563 return [mydata.get(x, '') for x in wants]
564
565 def aux_update(self, cpv, values):
566 if not self.bintree.populated:
567 self.bintree.populate()
568 + build_id = None
569 + try:
570 + build_id = cpv.build_id
571 + except AttributeError:
572 + if self.bintree._multi_instance:
573 + # The cpv.build_id attribute is required if we are in
574 + # multi-instance mode, since otherwise we won't know
575 + # which instance to update.
576 + raise
577 + else:
578 + cpv = self._instance_key(cpv, support_string=True)[0]
579 + build_id = cpv.build_id
580 +
581 tbz2path = self.bintree.getname(cpv)
582 if not os.path.exists(tbz2path):
583 raise KeyError(cpv)
584 @@ -187,7 +216,7 @@ class bindbapi(fakedbapi):
585 del mydata[k]
586 mytbz2.recompose_mem(portage.xpak.xpak_mem(mydata))
587 # inject will clear stale caches via cpv_inject.
588 - self.bintree.inject(cpv)
589 + self.bintree.inject(cpv, filename=tbz2path)
590
591 def cp_list(self, *pargs, **kwargs):
592 if not self.bintree.populated:
593 @@ -219,7 +248,7 @@ class bindbapi(fakedbapi):
594 if not self.bintree.isremote(pkg):
595 pass
596 else:
597 - metadata = self.bintree._remotepkgs[pkg]
598 + metadata = self.bintree._remotepkgs[self._instance_key(pkg)]
599 try:
600 size = int(metadata["SIZE"])
601 except KeyError:
602 @@ -300,6 +329,13 @@ class binarytree(object):
603
604 if True:
605 self.pkgdir = normalize_path(pkgdir)
606 + # NOTE: Event if binpkg-multi-instance is disabled, it's
607 + # still possible to access a PKGDIR which uses the
608 + # binpkg-multi-instance layout (or mixed layout).
609 + self._multi_instance = ("binpkg-multi-instance" in
610 + settings.features)
611 + if self._multi_instance:
612 + self._allocate_filename = self._allocate_filename_multi
613 self.dbapi = bindbapi(self, settings=settings)
614 self.update_ents = self.dbapi.update_ents
615 self.move_slot_ent = self.dbapi.move_slot_ent
616 @@ -310,7 +346,6 @@ class binarytree(object):
617 self.invalids = []
618 self.settings = settings
619 self._pkg_paths = {}
620 - self._pkgindex_uri = {}
621 self._populating = False
622 self._all_directory = os.path.isdir(
623 os.path.join(self.pkgdir, "All"))
624 @@ -318,12 +353,14 @@ class binarytree(object):
625 self._pkgindex_hashes = ["MD5","SHA1"]
626 self._pkgindex_file = os.path.join(self.pkgdir, "Packages")
627 self._pkgindex_keys = self.dbapi._aux_cache_keys.copy()
628 - self._pkgindex_keys.update(["CPV", "MTIME", "SIZE"])
629 + self._pkgindex_keys.update(["CPV", "SIZE"])
630 self._pkgindex_aux_keys = \
631 - ["BUILD_TIME", "CHOST", "DEPEND", "DESCRIPTION", "EAPI",
632 - "HDEPEND", "IUSE", "KEYWORDS", "LICENSE", "PDEPEND", "PROPERTIES",
633 - "PROVIDE", "RESTRICT", "RDEPEND", "repository", "SLOT", "USE", "DEFINED_PHASES",
634 - "BASE_URI", "PROVIDES", "REQUIRES"]
635 + ["BASE_URI", "BUILD_ID", "BUILD_TIME", "CHOST",
636 + "DEFINED_PHASES", "DEPEND", "DESCRIPTION", "EAPI",
637 + "HDEPEND", "IUSE", "KEYWORDS", "LICENSE", "PDEPEND",
638 + "PKGINDEX_URI", "PROPERTIES", "PROVIDE", "PROVIDES",
639 + "RDEPEND", "repository", "REQUIRES", "RESTRICT",
640 + "SIZE", "SLOT", "USE"]
641 self._pkgindex_aux_keys = list(self._pkgindex_aux_keys)
642 self._pkgindex_use_evaluated_keys = \
643 ("DEPEND", "HDEPEND", "LICENSE", "RDEPEND",
644 @@ -336,6 +373,7 @@ class binarytree(object):
645 "USE_EXPAND", "USE_EXPAND_HIDDEN", "USE_EXPAND_IMPLICIT",
646 "USE_EXPAND_UNPREFIXED"])
647 self._pkgindex_default_pkg_data = {
648 + "BUILD_ID" : "",
649 "BUILD_TIME" : "",
650 "DEFINED_PHASES" : "",
651 "DEPEND" : "",
652 @@ -365,6 +403,7 @@ class binarytree(object):
653
654 self._pkgindex_translated_keys = (
655 ("DESCRIPTION" , "DESC"),
656 + ("_mtime_" , "MTIME"),
657 ("repository" , "REPO"),
658 )
659
660 @@ -455,16 +494,21 @@ class binarytree(object):
661 mytbz2.recompose_mem(portage.xpak.xpak_mem(mydata))
662
663 self.dbapi.cpv_remove(mycpv)
664 - del self._pkg_paths[mycpv]
665 + del self._pkg_paths[self.dbapi._instance_key(mycpv)]
666 + metadata = self.dbapi._aux_cache_slot_dict()
667 + for k in self.dbapi._aux_cache_keys:
668 + v = mydata.get(_unicode_encode(k))
669 + if v is not None:
670 + v = _unicode_decode(v)
671 + metadata[k] = " ".join(v.split())
672 + mynewcpv = _pkg_str(mynewcpv, metadata=metadata)
673 new_path = self.getname(mynewcpv)
674 - self._pkg_paths[mynewcpv] = os.path.join(
675 + self._pkg_paths[
676 + self.dbapi._instance_key(mynewcpv)] = os.path.join(
677 *new_path.split(os.path.sep)[-2:])
678 if new_path != mytbz2:
679 self._ensure_dir(os.path.dirname(new_path))
680 _movefile(tbz2path, new_path, mysettings=self.settings)
681 - self._remove_symlink(mycpv)
682 - if new_path.split(os.path.sep)[-2] == "All":
683 - self._create_symlink(mynewcpv)
684 self.inject(mynewcpv)
685
686 return moves
687 @@ -645,55 +689,63 @@ class binarytree(object):
688 # prior to performing package moves since it only wants to
689 # operate on local packages (getbinpkgs=0).
690 self._remotepkgs = None
691 - self.dbapi._clear_cache()
692 - self.dbapi._aux_cache.clear()
693 + self.dbapi.clear()
694 + _instance_key = self.dbapi._instance_key
695 if True:
696 pkg_paths = {}
697 self._pkg_paths = pkg_paths
698 - dirs = listdir(self.pkgdir, dirsonly=True, EmptyOnError=True)
699 - if "All" in dirs:
700 - dirs.remove("All")
701 - dirs.sort()
702 - dirs.insert(0, "All")
703 + dir_files = {}
704 + for parent, dir_names, file_names in os.walk(self.pkgdir):
705 + relative_parent = parent[len(self.pkgdir)+1:]
706 + dir_files[relative_parent] = file_names
707 +
708 pkgindex = self._load_pkgindex()
709 - pf_index = None
710 if not self._pkgindex_version_supported(pkgindex):
711 pkgindex = self._new_pkgindex()
712 header = pkgindex.header
713 metadata = {}
714 + basename_index = {}
715 for d in pkgindex.packages:
716 - metadata[d["CPV"]] = d
717 + cpv = _pkg_str(d["CPV"], metadata=d,
718 + settings=self.settings)
719 + d["CPV"] = cpv
720 + metadata[_instance_key(cpv)] = d
721 + path = d.get("PATH")
722 + if not path:
723 + path = cpv + ".tbz2"
724 + basename = os.path.basename(path)
725 + basename_index.setdefault(basename, []).append(d)
726 +
727 update_pkgindex = False
728 - for mydir in dirs:
729 - for myfile in listdir(os.path.join(self.pkgdir, mydir)):
730 - if not myfile.endswith(".tbz2"):
731 + for mydir, file_names in dir_files.items():
732 + try:
733 + mydir = _unicode_decode(mydir,
734 + encoding=_encodings["fs"], errors="strict")
735 + except UnicodeDecodeError:
736 + continue
737 + for myfile in file_names:
738 + try:
739 + myfile = _unicode_decode(myfile,
740 + encoding=_encodings["fs"], errors="strict")
741 + except UnicodeDecodeError:
742 + continue
743 + if not myfile.endswith(SUPPORTED_XPAK_EXTENSIONS):
744 continue
745 mypath = os.path.join(mydir, myfile)
746 full_path = os.path.join(self.pkgdir, mypath)
747 s = os.lstat(full_path)
748 - if stat.S_ISLNK(s.st_mode):
749 +
750 + if not stat.S_ISREG(s.st_mode):
751 continue
752
753 # Validate data from the package index and try to avoid
754 # reading the xpak if possible.
755 - if mydir != "All":
756 - possibilities = None
757 - d = metadata.get(mydir+"/"+myfile[:-5])
758 - if d:
759 - possibilities = [d]
760 - else:
761 - if pf_index is None:
762 - pf_index = {}
763 - for mycpv in metadata:
764 - mycat, mypf = catsplit(mycpv)
765 - pf_index.setdefault(
766 - mypf, []).append(metadata[mycpv])
767 - possibilities = pf_index.get(myfile[:-5])
768 + possibilities = basename_index.get(myfile)
769 if possibilities:
770 match = None
771 for d in possibilities:
772 try:
773 - if long(d["MTIME"]) != s[stat.ST_MTIME]:
774 + if long(d["_mtime_"]) != s[stat.ST_MTIME]:
775 continue
776 except (KeyError, ValueError):
777 continue
778 @@ -707,15 +759,14 @@ class binarytree(object):
779 break
780 if match:
781 mycpv = match["CPV"]
782 - if mycpv in pkg_paths:
783 - # discard duplicates (All/ is preferred)
784 - continue
785 - mycpv = _pkg_str(mycpv)
786 - pkg_paths[mycpv] = mypath
787 + instance_key = _instance_key(mycpv)
788 + pkg_paths[instance_key] = mypath
789 # update the path if the package has been moved
790 oldpath = d.get("PATH")
791 if oldpath and oldpath != mypath:
792 update_pkgindex = True
793 + # Omit PATH if it is the default path for
794 + # the current Packages format version.
795 if mypath != mycpv + ".tbz2":
796 d["PATH"] = mypath
797 if not oldpath:
798 @@ -725,11 +776,6 @@ class binarytree(object):
799 if oldpath:
800 update_pkgindex = True
801 self.dbapi.cpv_inject(mycpv)
802 - if not self.dbapi._aux_cache_keys.difference(d):
803 - aux_cache = self.dbapi._aux_cache_slot_dict()
804 - for k in self.dbapi._aux_cache_keys:
805 - aux_cache[k] = d[k]
806 - self.dbapi._aux_cache[mycpv] = aux_cache
807 continue
808 if not os.access(full_path, os.R_OK):
809 writemsg(_("!!! Permission denied to read " \
810 @@ -737,13 +783,12 @@ class binarytree(object):
811 noiselevel=-1)
812 self.invalids.append(myfile[:-5])
813 continue
814 - metadata_bytes = portage.xpak.tbz2(full_path).get_data()
815 - mycat = _unicode_decode(metadata_bytes.get(b"CATEGORY", ""),
816 - encoding=_encodings['repo.content'], errors='replace')
817 - mypf = _unicode_decode(metadata_bytes.get(b"PF", ""),
818 - encoding=_encodings['repo.content'], errors='replace')
819 - slot = _unicode_decode(metadata_bytes.get(b"SLOT", ""),
820 - encoding=_encodings['repo.content'], errors='replace')
821 + pkg_metadata = self._read_metadata(full_path, s,
822 + keys=chain(self.dbapi._aux_cache_keys,
823 + ("PF", "CATEGORY")))
824 + mycat = pkg_metadata.get("CATEGORY", "")
825 + mypf = pkg_metadata.get("PF", "")
826 + slot = pkg_metadata.get("SLOT", "")
827 mypkg = myfile[:-5]
828 if not mycat or not mypf or not slot:
829 #old-style or corrupt package
830 @@ -767,16 +812,51 @@ class binarytree(object):
831 writemsg("!!! %s\n" % line, noiselevel=-1)
832 self.invalids.append(mypkg)
833 continue
834 - mycat = mycat.strip()
835 - slot = slot.strip()
836 - if mycat != mydir and mydir != "All":
837 +
838 + multi_instance = False
839 + invalid_name = False
840 + build_id = None
841 + if myfile.endswith(".xpak"):
842 + multi_instance = True
843 + build_id = self._parse_build_id(myfile)
844 + if build_id < 1:
845 + invalid_name = True
846 + elif myfile != "%s-%s.xpak" % (
847 + mypf, build_id):
848 + invalid_name = True
849 + else:
850 + mypkg = mypkg[:-len(str(build_id))-1]
851 + elif myfile != mypf + ".tbz2":
852 + invalid_name = True
853 +
854 + if invalid_name:
855 + writemsg(_("\n!!! Binary package name is "
856 + "invalid: '%s'\n") % full_path,
857 + noiselevel=-1)
858 + continue
859 +
860 + if pkg_metadata.get("BUILD_ID"):
861 + try:
862 + build_id = long(pkg_metadata["BUILD_ID"])
863 + except ValueError:
864 + writemsg(_("!!! Binary package has "
865 + "invalid BUILD_ID: '%s'\n") %
866 + full_path, noiselevel=-1)
867 + continue
868 + else:
869 + build_id = None
870 +
871 + if multi_instance:
872 + name_split = catpkgsplit("%s/%s" %
873 + (mycat, mypf))
874 + if (name_split is None or
875 + tuple(catsplit(mydir)) != name_split[:2]):
876 + continue
877 + elif mycat != mydir and mydir != "All":
878 continue
879 if mypkg != mypf.strip():
880 continue
881 mycpv = mycat + "/" + mypkg
882 - if mycpv in pkg_paths:
883 - # All is first, so it's preferred.
884 - continue
885 if not self.dbapi._category_re.match(mycat):
886 writemsg(_("!!! Binary package has an " \
887 "unrecognized category: '%s'\n") % full_path,
888 @@ -786,14 +866,23 @@ class binarytree(object):
889 (mycpv, self.settings["PORTAGE_CONFIGROOT"]),
890 noiselevel=-1)
891 continue
892 - mycpv = _pkg_str(mycpv)
893 - pkg_paths[mycpv] = mypath
894 + if build_id is not None:
895 + pkg_metadata["BUILD_ID"] = _unicode(build_id)
896 + pkg_metadata["SIZE"] = _unicode(s.st_size)
897 + # Discard items used only for validation above.
898 + pkg_metadata.pop("CATEGORY")
899 + pkg_metadata.pop("PF")
900 + mycpv = _pkg_str(mycpv,
901 + metadata=self.dbapi._aux_cache_slot_dict(
902 + pkg_metadata))
903 + pkg_paths[_instance_key(mycpv)] = mypath
904 self.dbapi.cpv_inject(mycpv)
905 update_pkgindex = True
906 - d = metadata.get(mycpv, {})
907 + d = metadata.get(_instance_key(mycpv),
908 + pkgindex._pkg_slot_dict())
909 if d:
910 try:
911 - if long(d["MTIME"]) != s[stat.ST_MTIME]:
912 + if long(d["_mtime_"]) != s[stat.ST_MTIME]:
913 d.clear()
914 except (KeyError, ValueError):
915 d.clear()
916 @@ -804,36 +893,30 @@ class binarytree(object):
917 except (KeyError, ValueError):
918 d.clear()
919
920 + for k in self._pkgindex_allowed_pkg_keys:
921 + v = pkg_metadata.get(k)
922 + if v is not None:
923 + d[k] = v
924 d["CPV"] = mycpv
925 - d["SLOT"] = slot
926 - d["MTIME"] = _unicode(s[stat.ST_MTIME])
927 - d["SIZE"] = _unicode(s.st_size)
928
929 - d.update(zip(self._pkgindex_aux_keys,
930 - self.dbapi.aux_get(mycpv, self._pkgindex_aux_keys)))
931 try:
932 self._eval_use_flags(mycpv, d)
933 except portage.exception.InvalidDependString:
934 writemsg(_("!!! Invalid binary package: '%s'\n") % \
935 self.getname(mycpv), noiselevel=-1)
936 self.dbapi.cpv_remove(mycpv)
937 - del pkg_paths[mycpv]
938 + del pkg_paths[_instance_key(mycpv)]
939
940 # record location if it's non-default
941 if mypath != mycpv + ".tbz2":
942 d["PATH"] = mypath
943 else:
944 d.pop("PATH", None)
945 - metadata[mycpv] = d
946 - if not self.dbapi._aux_cache_keys.difference(d):
947 - aux_cache = self.dbapi._aux_cache_slot_dict()
948 - for k in self.dbapi._aux_cache_keys:
949 - aux_cache[k] = d[k]
950 - self.dbapi._aux_cache[mycpv] = aux_cache
951 + metadata[_instance_key(mycpv)] = d
952
953 - for cpv in list(metadata):
954 - if cpv not in pkg_paths:
955 - del metadata[cpv]
956 + for instance_key in list(metadata):
957 + if instance_key not in pkg_paths:
958 + del metadata[instance_key]
959
960 # Do not bother to write the Packages index if $PKGDIR/All/ exists
961 # since it will provide no benefit due to the need to read CATEGORY
962 @@ -1058,45 +1141,24 @@ class binarytree(object):
963 # The current user doesn't have permission to cache the
964 # file, but that's alright.
965 if pkgindex:
966 - # Organize remote package list as a cpv -> metadata map.
967 - remotepkgs = _pkgindex_cpv_map_latest_build(pkgindex)
968 remote_base_uri = pkgindex.header.get("URI", base_url)
969 - for cpv, remote_metadata in remotepkgs.items():
970 - remote_metadata["BASE_URI"] = remote_base_uri
971 - self._pkgindex_uri[cpv] = url
972 - self._remotepkgs.update(remotepkgs)
973 - self._remote_has_index = True
974 - for cpv in remotepkgs:
975 + for d in pkgindex.packages:
976 + cpv = _pkg_str(d["CPV"], metadata=d,
977 + settings=self.settings)
978 + instance_key = _instance_key(cpv)
979 + # Local package instances override remote instances
980 + # with the same instance_key.
981 + if instance_key in metadata:
982 + continue
983 +
984 + d["CPV"] = cpv
985 + d["BASE_URI"] = remote_base_uri
986 + d["PKGINDEX_URI"] = url
987 + self._remotepkgs[instance_key] = d
988 + metadata[instance_key] = d
989 self.dbapi.cpv_inject(cpv)
990 - if True:
991 - # Remote package instances override local package
992 - # if they are not identical.
993 - hash_names = ["SIZE"] + self._pkgindex_hashes
994 - for cpv, local_metadata in metadata.items():
995 - remote_metadata = self._remotepkgs.get(cpv)
996 - if remote_metadata is None:
997 - continue
998 - # Use digests to compare identity.
999 - identical = True
1000 - for hash_name in hash_names:
1001 - local_value = local_metadata.get(hash_name)
1002 - if local_value is None:
1003 - continue
1004 - remote_value = remote_metadata.get(hash_name)
1005 - if remote_value is None:
1006 - continue
1007 - if local_value != remote_value:
1008 - identical = False
1009 - break
1010 - if identical:
1011 - del self._remotepkgs[cpv]
1012 - else:
1013 - # Override the local package in the aux_get cache.
1014 - self.dbapi._aux_cache[cpv] = remote_metadata
1015 - else:
1016 - # Local package instances override remote instances.
1017 - for cpv in metadata:
1018 - self._remotepkgs.pop(cpv, None)
1019 +
1020 + self._remote_has_index = True
1021
1022 self.populated=1
1023
1024 @@ -1108,7 +1170,8 @@ class binarytree(object):
1025 @param filename: File path of the package to inject, or None if it's
1026 already in the location returned by getname()
1027 @type filename: string
1028 - @rtype: None
1029 + @rtype: _pkg_str or None
1030 + @return: A _pkg_str instance on success, or None on failure.
1031 """
1032 mycat, mypkg = catsplit(cpv)
1033 if not self.populated:
1034 @@ -1126,24 +1189,45 @@ class binarytree(object):
1035 writemsg(_("!!! Binary package does not exist: '%s'\n") % full_path,
1036 noiselevel=-1)
1037 return
1038 - mytbz2 = portage.xpak.tbz2(full_path)
1039 - slot = mytbz2.getfile("SLOT")
1040 + metadata = self._read_metadata(full_path, s)
1041 + slot = metadata.get("SLOT")
1042 + try:
1043 + self._eval_use_flags(cpv, metadata)
1044 + except portage.exception.InvalidDependString:
1045 + slot = None
1046 if slot is None:
1047 writemsg(_("!!! Invalid binary package: '%s'\n") % full_path,
1048 noiselevel=-1)
1049 return
1050 - slot = slot.strip()
1051 - self.dbapi.cpv_inject(cpv)
1052 +
1053 + fetched = False
1054 + try:
1055 + build_id = cpv.build_id
1056 + except AttributeError:
1057 + build_id = None
1058 + else:
1059 + instance_key = self.dbapi._instance_key(cpv)
1060 + if instance_key in self.dbapi.cpvdict:
1061 + # This means we've been called by aux_update (or
1062 + # similar). The instance key typically changes (due to
1063 + # file modification), so we need to discard existing
1064 + # instance key references.
1065 + self.dbapi.cpv_remove(cpv)
1066 + self._pkg_paths.pop(instance_key, None)
1067 + if self._remotepkgs is not None:
1068 + fetched = self._remotepkgs.pop(instance_key, None)
1069 +
1070 + cpv = _pkg_str(cpv, metadata=metadata, settings=self.settings)
1071
1072 # Reread the Packages index (in case it's been changed by another
1073 # process) and then updated it, all while holding a lock.
1074 pkgindex_lock = None
1075 - created_symlink = False
1076 try:
1077 pkgindex_lock = lockfile(self._pkgindex_file,
1078 wantnewlockfile=1)
1079 if filename is not None:
1080 - new_filename = self.getname(cpv)
1081 + new_filename = self.getname(cpv,
1082 + allocate_new=(build_id is None))
1083 try:
1084 samefile = os.path.samefile(filename, new_filename)
1085 except OSError:
1086 @@ -1153,54 +1237,31 @@ class binarytree(object):
1087 _movefile(filename, new_filename, mysettings=self.settings)
1088 full_path = new_filename
1089
1090 - self._file_permissions(full_path)
1091 + basename = os.path.basename(full_path)
1092 + pf = catsplit(cpv)[1]
1093 + if (build_id is None and not fetched and
1094 + basename.endswith(".xpak")):
1095 + # Apply the newly assigned BUILD_ID. This is intended
1096 + # to occur only for locally built packages. If the
1097 + # package was fetched, we want to preserve its
1098 + # attributes, so that we can later distinguish that it
1099 + # is identical to its remote counterpart.
1100 + build_id = self._parse_build_id(basename)
1101 + metadata["BUILD_ID"] = _unicode(build_id)
1102 + cpv = _pkg_str(cpv, metadata=metadata,
1103 + settings=self.settings)
1104 + binpkg = portage.xpak.tbz2(full_path)
1105 + binary_data = binpkg.get_data()
1106 + binary_data[b"BUILD_ID"] = _unicode_encode(
1107 + metadata["BUILD_ID"])
1108 + binpkg.recompose_mem(portage.xpak.xpak_mem(binary_data))
1109
1110 - if self._all_directory and \
1111 - self.getname(cpv).split(os.path.sep)[-2] == "All":
1112 - self._create_symlink(cpv)
1113 - created_symlink = True
1114 + self._file_permissions(full_path)
1115 pkgindex = self._load_pkgindex()
1116 -
1117 if not self._pkgindex_version_supported(pkgindex):
1118 pkgindex = self._new_pkgindex()
1119
1120 - # Discard remote metadata to ensure that _pkgindex_entry
1121 - # gets the local metadata. This also updates state for future
1122 - # isremote calls.
1123 - if self._remotepkgs is not None:
1124 - self._remotepkgs.pop(cpv, None)
1125 -
1126 - # Discard cached metadata to ensure that _pkgindex_entry
1127 - # doesn't return stale metadata.
1128 - self.dbapi._aux_cache.pop(cpv, None)
1129 -
1130 - try:
1131 - d = self._pkgindex_entry(cpv)
1132 - except portage.exception.InvalidDependString:
1133 - writemsg(_("!!! Invalid binary package: '%s'\n") % \
1134 - self.getname(cpv), noiselevel=-1)
1135 - self.dbapi.cpv_remove(cpv)
1136 - del self._pkg_paths[cpv]
1137 - return
1138 -
1139 - # If found, remove package(s) with duplicate path.
1140 - path = d.get("PATH", "")
1141 - for i in range(len(pkgindex.packages) - 1, -1, -1):
1142 - d2 = pkgindex.packages[i]
1143 - if path and path == d2.get("PATH"):
1144 - # Handle path collisions in $PKGDIR/All
1145 - # when CPV is not identical.
1146 - del pkgindex.packages[i]
1147 - elif cpv == d2.get("CPV"):
1148 - if path == d2.get("PATH", ""):
1149 - del pkgindex.packages[i]
1150 - elif created_symlink and not d2.get("PATH", ""):
1151 - # Delete entry for the package that was just
1152 - # overwritten by a symlink to this package.
1153 - del pkgindex.packages[i]
1154 -
1155 - pkgindex.packages.append(d)
1156 -
1157 + d = self._inject_file(pkgindex, cpv, full_path)
1158 self._update_pkgindex_header(pkgindex.header)
1159 self._pkgindex_write(pkgindex)
1160
1161 @@ -1208,6 +1269,73 @@ class binarytree(object):
1162 if pkgindex_lock:
1163 unlockfile(pkgindex_lock)
1164
1165 + # This is used to record BINPKGMD5 in the installed package
1166 + # database, for a package that has just been built.
1167 + cpv._metadata["MD5"] = d["MD5"]
1168 +
1169 + return cpv
1170 +
1171 + def _read_metadata(self, filename, st, keys=None):
1172 + if keys is None:
1173 + keys = self.dbapi._aux_cache_keys
1174 + metadata = self.dbapi._aux_cache_slot_dict()
1175 + else:
1176 + metadata = {}
1177 + binary_metadata = portage.xpak.tbz2(filename).get_data()
1178 + for k in keys:
1179 + if k == "_mtime_":
1180 + metadata[k] = _unicode(st[stat.ST_MTIME])
1181 + elif k == "SIZE":
1182 + metadata[k] = _unicode(st.st_size)
1183 + else:
1184 + v = binary_metadata.get(_unicode_encode(k))
1185 + if v is not None:
1186 + v = _unicode_decode(v)
1187 + metadata[k] = " ".join(v.split())
1188 + metadata.setdefault("EAPI", "0")
1189 + return metadata
1190 +
1191 + def _inject_file(self, pkgindex, cpv, filename):
1192 + """
1193 + Add a package to internal data structures, and add an
1194 + entry to the given pkgindex.
1195 + @param pkgindex: The PackageIndex instance to which an entry
1196 + will be added.
1197 + @type pkgindex: PackageIndex
1198 + @param cpv: A _pkg_str instance corresponding to the package
1199 + being injected.
1200 + @type cpv: _pkg_str
1201 + @param filename: Absolute file path of the package to inject.
1202 + @type filename: string
1203 + @rtype: dict
1204 + @return: A dict corresponding to the new entry which has been
1205 + added to pkgindex. This may be used to access the checksums
1206 + which have just been generated.
1207 + """
1208 + # Update state for future isremote calls.
1209 + instance_key = self.dbapi._instance_key(cpv)
1210 + if self._remotepkgs is not None:
1211 + self._remotepkgs.pop(instance_key, None)
1212 +
1213 + self.dbapi.cpv_inject(cpv)
1214 + self._pkg_paths[instance_key] = filename[len(self.pkgdir)+1:]
1215 + d = self._pkgindex_entry(cpv)
1216 +
1217 + # If found, remove package(s) with duplicate path.
1218 + path = d.get("PATH", "")
1219 + for i in range(len(pkgindex.packages) - 1, -1, -1):
1220 + d2 = pkgindex.packages[i]
1221 + if path and path == d2.get("PATH"):
1222 + # Handle path collisions in $PKGDIR/All
1223 + # when CPV is not identical.
1224 + del pkgindex.packages[i]
1225 + elif cpv == d2.get("CPV"):
1226 + if path == d2.get("PATH", ""):
1227 + del pkgindex.packages[i]
1228 +
1229 + pkgindex.packages.append(d)
1230 + return d
1231 +
1232 def _pkgindex_write(self, pkgindex):
1233 contents = codecs.getwriter(_encodings['repo.content'])(io.BytesIO())
1234 pkgindex.write(contents)
1235 @@ -1233,7 +1361,7 @@ class binarytree(object):
1236
1237 def _pkgindex_entry(self, cpv):
1238 """
1239 - Performs checksums and evaluates USE flag conditionals.
1240 + Performs checksums, and gets size and mtime via lstat.
1241 Raises InvalidDependString if necessary.
1242 @rtype: dict
1243 @return: a dict containing entry for the give cpv.
1244 @@ -1241,23 +1369,20 @@ class binarytree(object):
1245
1246 pkg_path = self.getname(cpv)
1247
1248 - d = dict(zip(self._pkgindex_aux_keys,
1249 - self.dbapi.aux_get(cpv, self._pkgindex_aux_keys)))
1250 -
1251 + d = dict(cpv._metadata.items())
1252 d.update(perform_multiple_checksums(
1253 pkg_path, hashes=self._pkgindex_hashes))
1254
1255 d["CPV"] = cpv
1256 - st = os.stat(pkg_path)
1257 - d["MTIME"] = _unicode(st[stat.ST_MTIME])
1258 + st = os.lstat(pkg_path)
1259 + d["_mtime_"] = _unicode(st[stat.ST_MTIME])
1260 d["SIZE"] = _unicode(st.st_size)
1261
1262 - rel_path = self._pkg_paths[cpv]
1263 + rel_path = pkg_path[len(self.pkgdir)+1:]
1264 # record location if it's non-default
1265 if rel_path != cpv + ".tbz2":
1266 d["PATH"] = rel_path
1267
1268 - self._eval_use_flags(cpv, d)
1269 return d
1270
1271 def _new_pkgindex(self):
1272 @@ -1311,15 +1436,17 @@ class binarytree(object):
1273 return False
1274
1275 def _eval_use_flags(self, cpv, metadata):
1276 - use = frozenset(metadata["USE"].split())
1277 + use = frozenset(metadata.get("USE", "").split())
1278 for k in self._pkgindex_use_evaluated_keys:
1279 if k.endswith('DEPEND'):
1280 token_class = Atom
1281 else:
1282 token_class = None
1283
1284 + deps = metadata.get(k)
1285 + if deps is None:
1286 + continue
1287 try:
1288 - deps = metadata[k]
1289 deps = use_reduce(deps, uselist=use, token_class=token_class)
1290 deps = paren_enclose(deps)
1291 except portage.exception.InvalidDependString as e:
1292 @@ -1349,46 +1476,129 @@ class binarytree(object):
1293 return ""
1294 return mymatch
1295
1296 - def getname(self, pkgname):
1297 - """Returns a file location for this package. The default location is
1298 - ${PKGDIR}/All/${PF}.tbz2, but will be ${PKGDIR}/${CATEGORY}/${PF}.tbz2
1299 - in the rare event of a collision. The prevent_collision() method can
1300 - be called to ensure that ${PKGDIR}/All/${PF}.tbz2 is available for a
1301 - specific cpv."""
1302 + def getname(self, cpv, allocate_new=None):
1303 + """Returns a file location for this package.
1304 + If cpv has both build_time and build_id attributes, then the
1305 + path to the specific corresponding instance is returned.
1306 + Otherwise, allocate a new path and return that. When allocating
1307 + a new path, behavior depends on the binpkg-multi-instance
1308 + FEATURES setting.
1309 + """
1310 if not self.populated:
1311 self.populate()
1312 - mycpv = pkgname
1313 - mypath = self._pkg_paths.get(mycpv, None)
1314 - if mypath:
1315 - return os.path.join(self.pkgdir, mypath)
1316 - mycat, mypkg = catsplit(mycpv)
1317 - if self._all_directory:
1318 - mypath = os.path.join("All", mypkg + ".tbz2")
1319 - if mypath in self._pkg_paths.values():
1320 - mypath = os.path.join(mycat, mypkg + ".tbz2")
1321 +
1322 + try:
1323 + cpv.cp
1324 + except AttributeError:
1325 + cpv = _pkg_str(cpv)
1326 +
1327 + filename = None
1328 + if allocate_new:
1329 + filename = self._allocate_filename(cpv)
1330 + elif self._is_specific_instance(cpv):
1331 + instance_key = self.dbapi._instance_key(cpv)
1332 + path = self._pkg_paths.get(instance_key)
1333 + if path is not None:
1334 + filename = os.path.join(self.pkgdir, path)
1335 +
1336 + if filename is None and not allocate_new:
1337 + try:
1338 + instance_key = self.dbapi._instance_key(cpv,
1339 + support_string=True)
1340 + except KeyError:
1341 + pass
1342 + else:
1343 + filename = self._pkg_paths.get(instance_key)
1344 + if filename is not None:
1345 + filename = os.path.join(self.pkgdir, filename)
1346 +
1347 + if filename is None:
1348 + if self._multi_instance:
1349 + pf = catsplit(cpv)[1]
1350 + filename = "%s-%s.xpak" % (
1351 + os.path.join(self.pkgdir, cpv.cp, pf), "1")
1352 + else:
1353 + filename = os.path.join(self.pkgdir, cpv + ".tbz2")
1354 +
1355 + return filename
1356 +
1357 + def _is_specific_instance(self, cpv):
1358 + specific = True
1359 + try:
1360 + build_time = cpv.build_time
1361 + build_id = cpv.build_id
1362 + except AttributeError:
1363 + specific = False
1364 else:
1365 - mypath = os.path.join(mycat, mypkg + ".tbz2")
1366 - self._pkg_paths[mycpv] = mypath # cache for future lookups
1367 - return os.path.join(self.pkgdir, mypath)
1368 + if build_time is None or build_id is None:
1369 + specific = False
1370 + return specific
1371 +
1372 + def _max_build_id(self, cpv):
1373 + max_build_id = 0
1374 + for x in self.dbapi.cp_list(cpv.cp):
1375 + if (x == cpv and x.build_id is not None and
1376 + x.build_id > max_build_id):
1377 + max_build_id = x.build_id
1378 + return max_build_id
1379 +
1380 + def _allocate_filename(self, cpv):
1381 + return os.path.join(self.pkgdir, cpv + ".tbz2")
1382 +
1383 + def _allocate_filename_multi(self, cpv):
1384 +
1385 + # First, get the max build_id found when _populate was
1386 + # called.
1387 + max_build_id = self._max_build_id(cpv)
1388 +
1389 + # A new package may have been added concurrently since the
1390 + # last _populate call, so use increment build_id until
1391 + # we locate an unused id.
1392 + pf = catsplit(cpv)[1]
1393 + build_id = max_build_id + 1
1394 +
1395 + while True:
1396 + filename = "%s-%s.xpak" % (
1397 + os.path.join(self.pkgdir, cpv.cp, pf), build_id)
1398 + if os.path.exists(filename):
1399 + build_id += 1
1400 + else:
1401 + return filename
1402 +
1403 + @staticmethod
1404 + def _parse_build_id(filename):
1405 + build_id = -1
1406 + hyphen = filename.rfind("-", 0, -6)
1407 + if hyphen != -1:
1408 + build_id = filename[hyphen+1:-5]
1409 + try:
1410 + build_id = long(build_id)
1411 + except ValueError:
1412 + pass
1413 + return build_id
1414
1415 def isremote(self, pkgname):
1416 """Returns true if the package is kept remotely and it has not been
1417 downloaded (or it is only partially downloaded)."""
1418 - if self._remotepkgs is None or pkgname not in self._remotepkgs:
1419 + if (self._remotepkgs is None or
1420 + self.dbapi._instance_key(pkgname) not in self._remotepkgs):
1421 return False
1422 # Presence in self._remotepkgs implies that it's remote. When a
1423 # package is downloaded, state is updated by self.inject().
1424 return True
1425
1426 - def get_pkgindex_uri(self, pkgname):
1427 + def get_pkgindex_uri(self, cpv):
1428 """Returns the URI to the Packages file for a given package."""
1429 - return self._pkgindex_uri.get(pkgname)
1430 -
1431 -
1432 + uri = None
1433 + metadata = self._remotepkgs.get(self.dbapi._instance_key(cpv))
1434 + if metadata is not None:
1435 + uri = metadata["PKGINDEX_URI"]
1436 + return uri
1437
1438 def gettbz2(self, pkgname):
1439 """Fetches the package from a remote site, if necessary. Attempts to
1440 resume if the file appears to be partially downloaded."""
1441 + instance_key = self.dbapi._instance_key(pkgname)
1442 tbz2_path = self.getname(pkgname)
1443 tbz2name = os.path.basename(tbz2_path)
1444 resume = False
1445 @@ -1404,10 +1614,10 @@ class binarytree(object):
1446 self._ensure_dir(mydest)
1447 # urljoin doesn't work correctly with unrecognized protocols like sftp
1448 if self._remote_has_index:
1449 - rel_url = self._remotepkgs[pkgname].get("PATH")
1450 + rel_url = self._remotepkgs[instance_key].get("PATH")
1451 if not rel_url:
1452 rel_url = pkgname+".tbz2"
1453 - remote_base_uri = self._remotepkgs[pkgname]["BASE_URI"]
1454 + remote_base_uri = self._remotepkgs[instance_key]["BASE_URI"]
1455 url = remote_base_uri.rstrip("/") + "/" + rel_url.lstrip("/")
1456 else:
1457 url = self.settings["PORTAGE_BINHOST"].rstrip("/") + "/" + tbz2name
1458 @@ -1450,15 +1660,19 @@ class binarytree(object):
1459 except AttributeError:
1460 cpv = pkg
1461
1462 + _instance_key = self.dbapi._instance_key
1463 + instance_key = _instance_key(cpv)
1464 digests = {}
1465 - metadata = None
1466 - if self._remotepkgs is None or cpv not in self._remotepkgs:
1467 + metadata = (None if self._remotepkgs is None else
1468 + self._remotepkgs.get(instance_key))
1469 + if metadata is None:
1470 for d in self._load_pkgindex().packages:
1471 - if d["CPV"] == cpv:
1472 + if (d["CPV"] == cpv and
1473 + instance_key == _instance_key(_pkg_str(d["CPV"],
1474 + metadata=d, settings=self.settings))):
1475 metadata = d
1476 break
1477 - else:
1478 - metadata = self._remotepkgs[cpv]
1479 +
1480 if metadata is None:
1481 return digests
1482
1483 diff --git a/pym/portage/emaint/modules/binhost/binhost.py b/pym/portage/emaint/modules/binhost/binhost.py
1484 index 1138a8c..cf1213e 100644
1485 --- a/pym/portage/emaint/modules/binhost/binhost.py
1486 +++ b/pym/portage/emaint/modules/binhost/binhost.py
1487 @@ -7,6 +7,7 @@ import stat
1488 import portage
1489 from portage import os
1490 from portage.util import writemsg
1491 +from portage.versions import _pkg_str
1492
1493 import sys
1494
1495 @@ -38,7 +39,7 @@ class BinhostHandler(object):
1496 if size is None:
1497 return True
1498
1499 - mtime = data.get("MTIME")
1500 + mtime = data.get("_mtime_")
1501 if mtime is None:
1502 return True
1503
1504 @@ -90,6 +91,7 @@ class BinhostHandler(object):
1505 def fix(self, **kwargs):
1506 onProgress = kwargs.get('onProgress', None)
1507 bintree = self._bintree
1508 + _instance_key = bintree.dbapi._instance_key
1509 cpv_all = self._bintree.dbapi.cpv_all()
1510 cpv_all.sort()
1511 missing = []
1512 @@ -98,16 +100,21 @@ class BinhostHandler(object):
1513 onProgress(maxval, 0)
1514 pkgindex = self._pkgindex
1515 missing = []
1516 + stale = []
1517 metadata = {}
1518 for d in pkgindex.packages:
1519 - metadata[d["CPV"]] = d
1520 -
1521 - for i, cpv in enumerate(cpv_all):
1522 - d = metadata.get(cpv)
1523 + cpv = _pkg_str(d["CPV"], metadata=d,
1524 + settings=bintree.settings)
1525 + d["CPV"] = cpv
1526 + metadata[_instance_key(cpv)] = d
1527 + if not bintree.dbapi.cpv_exists(cpv):
1528 + stale.append(cpv)
1529 +
1530 + for cpv in cpv_all:
1531 + d = metadata.get(_instance_key(cpv))
1532 if not d or self._need_update(cpv, d):
1533 missing.append(cpv)
1534
1535 - stale = set(metadata).difference(cpv_all)
1536 if missing or stale:
1537 from portage import locks
1538 pkgindex_lock = locks.lockfile(
1539 @@ -121,31 +128,39 @@ class BinhostHandler(object):
1540 pkgindex = bintree._load_pkgindex()
1541 self._pkgindex = pkgindex
1542
1543 + # Recount stale/missing packages, with lock held.
1544 + missing = []
1545 + stale = []
1546 metadata = {}
1547 for d in pkgindex.packages:
1548 - metadata[d["CPV"]] = d
1549 -
1550 - # Recount missing packages, with lock held.
1551 - del missing[:]
1552 - for i, cpv in enumerate(cpv_all):
1553 - d = metadata.get(cpv)
1554 + cpv = _pkg_str(d["CPV"], metadata=d,
1555 + settings=bintree.settings)
1556 + d["CPV"] = cpv
1557 + metadata[_instance_key(cpv)] = d
1558 + if not bintree.dbapi.cpv_exists(cpv):
1559 + stale.append(cpv)
1560 +
1561 + for cpv in cpv_all:
1562 + d = metadata.get(_instance_key(cpv))
1563 if not d or self._need_update(cpv, d):
1564 missing.append(cpv)
1565
1566 maxval = len(missing)
1567 for i, cpv in enumerate(missing):
1568 + d = bintree._pkgindex_entry(cpv)
1569 try:
1570 - metadata[cpv] = bintree._pkgindex_entry(cpv)
1571 + bintree._eval_use_flags(cpv, d)
1572 except portage.exception.InvalidDependString:
1573 writemsg("!!! Invalid binary package: '%s'\n" % \
1574 bintree.getname(cpv), noiselevel=-1)
1575 + else:
1576 + metadata[_instance_key(cpv)] = d
1577
1578 if onProgress:
1579 onProgress(maxval, i+1)
1580
1581 - for cpv in set(metadata).difference(
1582 - self._bintree.dbapi.cpv_all()):
1583 - del metadata[cpv]
1584 + for cpv in stale:
1585 + del metadata[_instance_key(cpv)]
1586
1587 # We've updated the pkgindex, so set it to
1588 # repopulate when necessary.
1589 --
1590 2.0.5

Replies