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