Gentoo Archives: gentoo-commits

From: Zac Medico <zmedico@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: man/, lib/_emerge/, lib/portage/dbapi/, lib/portage/tests/emerge/
Date: Wed, 27 Nov 2019 03:22:05
Message-Id: 1574824760.8faad11a18fcc33329931a75002f293e8fa462eb.zmedico@gentoo
1 commit: 8faad11a18fcc33329931a75002f293e8fa462eb
2 Author: Zac Medico <zmedico <AT> gentoo <DOT> org>
3 AuthorDate: Mon Nov 25 05:08:14 2019 +0000
4 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org>
5 CommitDate: Wed Nov 27 03:19:20 2019 +0000
6 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=8faad11a
7
8 emerge: add --quickpkg-direct option
9
10 Enable use of installed packages directly as binary
11 packages. This is similar to using binary packages produced by
12 quickpkg(1), but installed packages are used directly as though
13 they are binary packages. This option only works in combination
14 with the --root=DIR option, and it comes with the caveat that
15 packages are only allowed to be installed into the root that
16 is specified by the --root=DIR option. The other root which
17 serves as a source of packages is assumed to be immutable
18 during the entire operation (similar to --buildpkgonly mode).
19
20 Default behavior for handling of protected configuration files
21 is controlled by the QUICKPKG_DEFAULT_OPTS variable. When a
22 configuration file is not included because it is protected, an
23 ewarn message is logged.
24
25 Suggested use cases:
26
27 * Install packages from a buildtime container into an empty root,
28 in order to create a minimal runtime container (which need not
29 include a package manager). In a multi-stage Dockerfile, install
30 runtime files to an empty directory in the build stage, and in
31 the final stage use COPY to populate a container with the
32 contents of that directory. For greater efficiency, use buildah
33 to install directly into a mounted container, avoiding the COPY
34 step. Use the emerge --usepkgonly and --ignore-soname-deps=n
35 options to account for soname dependencies, allowing implicit
36 system dependencies such as glibc to be automatically pulled
37 into the runtime image.
38
39 * Enable a live usb, iso, or pxe image to act as a binary
40 installer that uses packages installed in the live image as a
41 source of binary packages.
42
43 Bug: https://bugs.gentoo.org/699986
44 Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>
45
46 lib/_emerge/Binpkg.py | 59 +++++++++-------
47 lib/_emerge/Scheduler.py | 7 +-
48 lib/_emerge/actions.py | 37 ++++++++--
49 lib/_emerge/depgraph.py | 19 ++++++
50 lib/_emerge/main.py | 7 +-
51 lib/portage/dbapi/__init__.py | 5 +-
52 lib/portage/dbapi/bintree.py | 112 ++++++++++++++++++++++++++++--
53 lib/portage/dbapi/vartree.py | 117 ++++++++++++++++++++++++++++++--
54 lib/portage/tests/emerge/test_simple.py | 3 +-
55 man/emerge.1 | 19 +++++-
56 10 files changed, 338 insertions(+), 47 deletions(-)
57
58 diff --git a/lib/_emerge/Binpkg.py b/lib/_emerge/Binpkg.py
59 index f9cffa26d..b5a69f8e7 100644
60 --- a/lib/_emerge/Binpkg.py
61 +++ b/lib/_emerge/Binpkg.py
62 @@ -7,7 +7,6 @@ import _emerge.emergelog
63 from _emerge.EbuildPhase import EbuildPhase
64 from _emerge.BinpkgFetcher import BinpkgFetcher
65 from _emerge.BinpkgEnvExtractor import BinpkgEnvExtractor
66 -from _emerge.BinpkgExtractorAsync import BinpkgExtractorAsync
67 from _emerge.CompositeTask import CompositeTask
68 from _emerge.BinpkgVerifier import BinpkgVerifier
69 from _emerge.EbuildMerge import EbuildMerge
70 @@ -16,6 +15,7 @@ from _emerge.SpawnProcess import SpawnProcess
71 from portage.eapi import eapi_exports_replace_vars
72 from portage.util import ensure_dirs
73 from portage.util._async.AsyncTaskFuture import AsyncTaskFuture
74 +from portage.util.futures.compat_coroutine import coroutine
75 import portage
76 from portage import os
77 from portage import shutil
78 @@ -135,11 +135,14 @@ class Binpkg(CompositeTask):
79
80 pkg = self.pkg
81 pkg_count = self.pkg_count
82 - fetcher = BinpkgFetcher(background=self.background,
83 - logfile=self.settings.get("PORTAGE_LOG_FILE"), pkg=self.pkg,
84 - pretend=self.opts.pretend, scheduler=self.scheduler)
85 + fetcher = None
86
87 if self.opts.getbinpkg and self._bintree.isremote(pkg.cpv):
88 +
89 + fetcher = BinpkgFetcher(background=self.background,
90 + logfile=self.settings.get("PORTAGE_LOG_FILE"), pkg=self.pkg,
91 + pretend=self.opts.pretend, scheduler=self.scheduler)
92 +
93 msg = " --- (%s of %s) Fetching Binary (%s::%s)" %\
94 (pkg_count.curval, pkg_count.maxval, pkg.cpv,
95 fetcher.pkg_path)
96 @@ -160,7 +163,7 @@ class Binpkg(CompositeTask):
97
98 # The fetcher only has a returncode when
99 # --getbinpkg is enabled.
100 - if fetcher.returncode is not None:
101 + if fetcher is not None:
102 self._fetched_pkg = fetcher.pkg_path
103 if self._default_exit(fetcher) != os.EX_OK:
104 self._async_unlock_builddir(returncode=self.returncode)
105 @@ -209,7 +212,8 @@ class Binpkg(CompositeTask):
106
107 # This gives bashrc users an opportunity to do various things
108 # such as remove binary packages after they're installed.
109 - self.settings["PORTAGE_BINPKG_FILE"] = pkg_path
110 + if pkg_path is not None:
111 + self.settings["PORTAGE_BINPKG_FILE"] = pkg_path
112 self._pkg_path = pkg_path
113
114 logfile = self.settings.get("PORTAGE_LOG_FILE")
115 @@ -245,6 +249,13 @@ class Binpkg(CompositeTask):
116 self._async_unlock_builddir(returncode=self.returncode)
117 return
118
119 + self._start_task(
120 + AsyncTaskFuture(future=self._unpack_metadata()),
121 + self._unpack_metadata_exit)
122 +
123 + @coroutine
124 + def _unpack_metadata(self):
125 +
126 dir_path = self.settings['PORTAGE_BUILDDIR']
127
128 infloc = self._infloc
129 @@ -260,8 +271,7 @@ class Binpkg(CompositeTask):
130 portage.prepare_build_dirs(self.settings["ROOT"], self.settings, 1)
131 self._writemsg_level(">>> Extracting info\n")
132
133 - pkg_xpak = portage.xpak.tbz2(self._pkg_path)
134 - pkg_xpak.unpackinfo(infloc)
135 + yield self._bintree.dbapi.unpack_metadata(self.settings, infloc)
136 check_missing_metadata = ("CATEGORY", "PF")
137 for k, v in zip(check_missing_metadata,
138 self._bintree.dbapi.aux_get(self.pkg.cpv, check_missing_metadata)):
139 @@ -295,11 +305,14 @@ class Binpkg(CompositeTask):
140
141 env_extractor = BinpkgEnvExtractor(background=self.background,
142 scheduler=self.scheduler, settings=self.settings)
143 -
144 - self._start_task(env_extractor, self._env_extractor_exit)
145 -
146 - def _env_extractor_exit(self, env_extractor):
147 - if self._default_exit(env_extractor) != os.EX_OK:
148 + env_extractor.start()
149 + yield env_extractor.async_wait()
150 + if env_extractor.returncode != os.EX_OK:
151 + raise portage.exception.PortageException('failed to extract environment for {}'.format(self.pkg.cpv))
152 +
153 + def _unpack_metadata_exit(self, unpack_metadata):
154 + if self._default_exit(unpack_metadata) != os.EX_OK:
155 + unpack_metadata.future.result()
156 self._async_unlock_builddir(returncode=self.returncode)
157 return
158
159 @@ -316,18 +329,16 @@ class Binpkg(CompositeTask):
160 self._async_unlock_builddir(returncode=self.returncode)
161 return
162
163 - extractor = BinpkgExtractorAsync(background=self.background,
164 - env=self.settings.environ(),
165 - features=self.settings.features,
166 - image_dir=self._image_dir,
167 - pkg=self.pkg, pkg_path=self._pkg_path,
168 - logfile=self.settings.get("PORTAGE_LOG_FILE"),
169 - scheduler=self.scheduler)
170 self._writemsg_level(">>> Extracting %s\n" % self.pkg.cpv)
171 - self._start_task(extractor, self._extractor_exit)
172 -
173 - def _extractor_exit(self, extractor):
174 - if self._default_exit(extractor) != os.EX_OK:
175 + self._start_task(
176 + AsyncTaskFuture(future=self._bintree.dbapi.unpack_contents(
177 + self.settings,
178 + self._image_dir)),
179 + self._unpack_contents_exit)
180 +
181 + def _unpack_contents_exit(self, unpack_contents):
182 + if self._default_exit(unpack_contents) != os.EX_OK:
183 + unpack_contents.future.result()
184 self._writemsg_level("!!! Error Extracting '%s'\n" % \
185 self._pkg_path, noiselevel=-1, level=logging.ERROR)
186 self._async_unlock_builddir(returncode=self.returncode)
187
188 diff --git a/lib/_emerge/Scheduler.py b/lib/_emerge/Scheduler.py
189 index af43a2e24..7fa3992e7 100644
190 --- a/lib/_emerge/Scheduler.py
191 +++ b/lib/_emerge/Scheduler.py
192 @@ -1,4 +1,4 @@
193 -# Copyright 1999-2014 Gentoo Foundation
194 +# Copyright 1999-2019 Gentoo Authors
195 # Distributed under the terms of the GNU General Public License v2
196
197 from __future__ import division, print_function, unicode_literals
198 @@ -868,10 +868,11 @@ class Scheduler(PollScheduler):
199
200 if fetched:
201 bintree.inject(x.cpv, filename=fetched)
202 - tbz2_file = bintree.getname(x.cpv)
203 +
204 infloc = os.path.join(build_dir_path, "build-info")
205 ensure_dirs(infloc)
206 - portage.xpak.tbz2(tbz2_file).unpackinfo(infloc)
207 + self._sched_iface.run_until_complete(
208 + bintree.dbapi.unpack_metadata(settings, infloc))
209 ebuild_path = os.path.join(infloc, x.pf + ".ebuild")
210 settings.configdict["pkg"]["EMERGE_FROM"] = "binary"
211 settings.configdict["pkg"]["MERGE_TYPE"] = "binary"
212
213 diff --git a/lib/_emerge/actions.py b/lib/_emerge/actions.py
214 index 705a3ff1c..6f815bff2 100644
215 --- a/lib/_emerge/actions.py
216 +++ b/lib/_emerge/actions.py
217 @@ -1,4 +1,4 @@
218 -# Copyright 1999-2018 Gentoo Foundation
219 +# Copyright 1999-2019 Gentoo Authors
220 # Distributed under the terms of the GNU General Public License v2
221
222 from __future__ import division, print_function, unicode_literals
223 @@ -122,6 +122,23 @@ def action_build(emerge_config, trees=DeprecationWarning,
224 # before we get here, so warn if they're not (bug #267103).
225 chk_updated_cfg_files(settings['EROOT'], ['/etc/portage'])
226
227 + quickpkg_direct = ("--usepkg" in emerge_config.opts and
228 + emerge_config.opts.get('--quickpkg-direct', 'n') == 'y' and
229 + emerge_config.target_config is not emerge_config.running_config)
230 + if '--getbinpkg' in emerge_config.opts or quickpkg_direct:
231 + kwargs = {}
232 + if quickpkg_direct:
233 + kwargs['add_repos'] = (emerge_config.running_config.trees['vartree'].dbapi,)
234 +
235 + try:
236 + emerge_config.target_config.trees['bintree'].populate(
237 + getbinpkgs='--getbinpkg' in emerge_config.opts,
238 + **kwargs)
239 + except ParseError as e:
240 + writemsg("\n\n!!!%s.\nSee make.conf(5) for more info.\n"
241 + % e, noiselevel=-1)
242 + return 1
243 +
244 # validate the state of the resume data
245 # so that we can make assumptions later.
246 for k in ("resume", "resume_backup"):
247 @@ -352,12 +369,17 @@ def action_build(emerge_config, trees=DeprecationWarning,
248 # instances need to load remote metadata if --getbinpkg
249 # is enabled. Use getbinpkg_refresh=False to use cached
250 # metadata, since the cache is already fresh.
251 - if "--getbinpkg" in emerge_config.opts:
252 + if "--getbinpkg" in emerge_config.opts or quickpkg_direct:
253 for root_trees in emerge_config.trees.values():
254 + kwargs = {}
255 + if quickpkg_direct:
256 + kwargs['add_repos'] = (emerge_config.running_config.trees['vartree'].dbapi,)
257 +
258 try:
259 root_trees["bintree"].populate(
260 getbinpkgs=True,
261 - getbinpkg_refresh=False)
262 + getbinpkg_refresh=False,
263 + **kwargs)
264 except ParseError as e:
265 writemsg("\n\n!!!%s.\nSee make.conf(5) for more info.\n"
266 % e, noiselevel=-1)
267 @@ -2898,9 +2920,16 @@ def run_action(emerge_config):
268 if (emerge_config.action in ('search', None) and
269 '--usepkg' in emerge_config.opts):
270 for mytrees in emerge_config.trees.values():
271 + kwargs = {}
272 + if (mytrees is emerge_config.target_config.trees and
273 + emerge_config.target_config is not emerge_config.running_config and
274 + emerge_config.opts.get('--quickpkg-direct', 'n') == 'y'):
275 + kwargs['add_repos'] = (emerge_config.running_config.trees['vartree'].dbapi,)
276 +
277 try:
278 mytrees['bintree'].populate(
279 - getbinpkgs='--getbinpkg' in emerge_config.opts)
280 + getbinpkgs='--getbinpkg' in emerge_config.opts,
281 + **kwargs)
282 except ParseError as e:
283 writemsg('\n\n!!!%s.\nSee make.conf(5) for more info.\n'
284 % (e,), noiselevel=-1)
285
286 diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
287 index 1127a6234..6d8e73172 100644
288 --- a/lib/_emerge/depgraph.py
289 +++ b/lib/_emerge/depgraph.py
290 @@ -495,6 +495,7 @@ class _dynamic_depgraph_config(object):
291 self._backtrack_infos = {}
292
293 self._buildpkgonly_deps_unsatisfied = False
294 + self._quickpkg_direct_deps_unsatisfied = False
295 self._autounmask = self.myparams['autounmask']
296 self._displayed_autounmask = False
297 self._success_without_autounmask = False
298 @@ -4526,6 +4527,16 @@ class depgraph(object):
299 self._dynamic_config._skip_restart = True
300 return False, myfavorites
301
302 + if (self._frozen_config.myopts.get('--quickpkg-direct', 'n') == 'y' and
303 + self._frozen_config.target_root is not self._frozen_config._running_root):
304 + running_root = self._frozen_config._running_root.root
305 + for node in self._dynamic_config.digraph:
306 + if (isinstance(node, Package) and node.operation in ('merge', 'uninstall') and
307 + node.root == running_root):
308 + self._dynamic_config._quickpkg_direct_deps_unsatisfied = True
309 + self._dynamic_config._skip_restart = True
310 + return False, myfavorites
311 +
312 if (not self._dynamic_config._prune_rebuilds and
313 self._ignored_binaries_autounmask_backtrack()):
314 config = self._dynamic_config._backtrack_infos.setdefault("config", {})
315 @@ -9062,6 +9073,14 @@ class depgraph(object):
316 writemsg("!!! Cannot merge requested packages. "
317 "Merge deps and try again.\n\n", noiselevel=-1)
318
319 + if self._dynamic_config._quickpkg_direct_deps_unsatisfied:
320 + self._show_merge_list()
321 + writemsg("\n!!! --quickpkg-direct requires all "
322 + "dependencies to be merged for root '{}'.\n".format(
323 + self._frozen_config._running_root.root), noiselevel=-1)
324 + writemsg("!!! Cannot merge requested packages. "
325 + "Merge deps and try again.\n\n", noiselevel=-1)
326 +
327 def saveNomergeFavorites(self):
328 """Find atoms in favorites that are not in the mergelist and add them
329 to the world file if necessary."""
330
331 diff --git a/lib/_emerge/main.py b/lib/_emerge/main.py
332 index 0d2c45a4f..8c72cdf9c 100644
333 --- a/lib/_emerge/main.py
334 +++ b/lib/_emerge/main.py
335 @@ -1,4 +1,4 @@
336 -# Copyright 1999-2018 Gentoo Foundation
337 +# Copyright 1999-2019 Gentoo Authors
338 # Distributed under the terms of the GNU General Public License v2
339
340 from __future__ import print_function
341 @@ -637,6 +637,11 @@ def parse_opts(tmpcmdline, silent=False):
342 "action" : "store",
343 },
344
345 + "--quickpkg-direct": {
346 + "help": "Enable use of installed packages directly as binary packages",
347 + "choices": y_or_n
348 + },
349 +
350 "--quiet": {
351 "shortopt" : "-q",
352 "help" : "reduced or condensed output",
353
354 diff --git a/lib/portage/dbapi/__init__.py b/lib/portage/dbapi/__init__.py
355 index 80f8a689f..37728714e 100644
356 --- a/lib/portage/dbapi/__init__.py
357 +++ b/lib/portage/dbapi/__init__.py
358 @@ -1,4 +1,4 @@
359 -# Copyright 1998-2018 Gentoo Foundation
360 +# Copyright 1998-2019 Gentoo Authors
361 # Distributed under the terms of the GNU General Public License v2
362
363 from __future__ import unicode_literals
364 @@ -32,8 +32,7 @@ class dbapi(object):
365 _use_mutable = False
366 _known_keys = frozenset(x for x in auxdbkeys
367 if not x.startswith("UNUSED_0"))
368 - _pkg_str_aux_keys = ("BUILD_TIME", "EAPI", "BUILD_ID",
369 - "KEYWORDS", "SLOT", "repository")
370 + _pkg_str_aux_keys = ("EAPI", "KEYWORDS", "SLOT", "repository")
371
372 def __init__(self):
373 pass
374
375 diff --git a/lib/portage/dbapi/bintree.py b/lib/portage/dbapi/bintree.py
376 index ba21e6d23..9d3ea039b 100644
377 --- a/lib/portage/dbapi/bintree.py
378 +++ b/lib/portage/dbapi/bintree.py
379 @@ -1,4 +1,4 @@
380 -# Copyright 1998-2018 Gentoo Foundation
381 +# Copyright 1998-2019 Gentoo Authors
382 # Distributed under the terms of the GNU General Public License v2
383
384 from __future__ import unicode_literals
385 @@ -7,6 +7,7 @@ __all__ = ["bindbapi", "binarytree"]
386
387 import portage
388 portage.proxy.lazyimport.lazyimport(globals(),
389 + '_emerge.BinpkgExtractorAsync:BinpkgExtractorAsync',
390 'portage.checksum:get_valid_checksum_keys,perform_multiple_checksums,' + \
391 'verify_all,_apply_hash_filter,_hash_filter',
392 'portage.dbapi.dep_expand:dep_expand',
393 @@ -18,6 +19,7 @@ portage.proxy.lazyimport.lazyimport(globals(),
394 'portage.util:atomic_ofstream,ensure_dirs,normalize_path,' + \
395 'writemsg,writemsg_stdout',
396 'portage.util.path:first_existing',
397 + 'portage.util._async.SchedulerInterface:SchedulerInterface',
398 'portage.util._urlopen:urlopen@_urlopen,have_pep_476@_have_pep_476',
399 'portage.versions:best,catpkgsplit,catsplit,_pkg_str',
400 )
401 @@ -30,6 +32,9 @@ from portage.exception import AlarmSignal, InvalidData, InvalidPackageName, \
402 ParseError, PermissionDenied, PortageException
403 from portage.localization import _
404 from portage.package.ebuild.profile_iuse import iter_iuse_vars
405 +from portage.util.futures import asyncio
406 +from portage.util.futures.compat_coroutine import coroutine
407 +from portage.util.futures.executor.fork import ForkExecutor
408 from portage import _movefile
409 from portage import os
410 from portage import _encodings
411 @@ -70,6 +75,8 @@ class UseCachedCopyOfRemoteIndex(Exception):
412 class bindbapi(fakedbapi):
413 _known_keys = frozenset(list(fakedbapi._known_keys) + \
414 ["CHOST", "repository", "USE"])
415 + _pkg_str_aux_keys = fakedbapi._pkg_str_aux_keys + ("BUILD_ID", "BUILD_TIME", "_mtime_")
416 +
417 def __init__(self, mybintree=None, **kwargs):
418 # Always enable multi_instance mode for bindbapi indexing. This
419 # does not affect the local PKGDIR file layout, since that is
420 @@ -142,7 +149,10 @@ class bindbapi(fakedbapi):
421 return [aux_cache.get(x, "") for x in wants]
422 mysplit = mycpv.split("/")
423 mylist = []
424 - if not self.bintree._remotepkgs or \
425 + add_pkg = self.bintree._additional_pkgs.get(instance_key)
426 + if add_pkg is not None:
427 + return add_pkg._db.aux_get(add_pkg, wants)
428 + elif not self.bintree._remotepkgs or \
429 not self.bintree.isremote(mycpv):
430 try:
431 tbz2_path = self.bintree._pkg_paths[instance_key]
432 @@ -218,6 +228,73 @@ class bindbapi(fakedbapi):
433 # inject will clear stale caches via cpv_inject.
434 self.bintree.inject(cpv, filename=tbz2path)
435
436 +
437 + @coroutine
438 + def unpack_metadata(self, pkg, dest_dir):
439 + """
440 + Unpack package metadata to a directory. This method is a coroutine.
441 +
442 + @param pkg: package to unpack
443 + @type pkg: _pkg_str or portage.config
444 + @param dest_dir: destination directory
445 + @type dest_dir: str
446 + """
447 + loop = asyncio._wrap_loop()
448 + if isinstance(pkg, _pkg_str):
449 + cpv = pkg
450 + else:
451 + cpv = pkg.mycpv
452 + key = self._instance_key(cpv)
453 + add_pkg = self.bintree._additional_pkgs.get(key)
454 + if add_pkg is not None:
455 + yield add_pkg._db.unpack_metadata(pkg, dest_dir)
456 + else:
457 + tbz2_file = self.bintree.getname(cpv)
458 + yield loop.run_in_executor(ForkExecutor(loop=loop),
459 + portage.xpak.tbz2(tbz2_file).unpackinfo, dest_dir)
460 +
461 + @coroutine
462 + def unpack_contents(self, pkg, dest_dir):
463 + """
464 + Unpack package contents to a directory. This method is a coroutine.
465 +
466 + @param pkg: package to unpack
467 + @type pkg: _pkg_str or portage.config
468 + @param dest_dir: destination directory
469 + @type dest_dir: str
470 + """
471 + loop = asyncio._wrap_loop()
472 + if isinstance(pkg, _pkg_str):
473 + settings = self.settings
474 + cpv = pkg
475 + else:
476 + settings = pkg
477 + cpv = settings.mycpv
478 +
479 + pkg_path = self.bintree.getname(cpv)
480 + if pkg_path is not None:
481 +
482 + extractor = BinpkgExtractorAsync(
483 + background=settings.get('PORTAGE_BACKGROUND') == '1',
484 + env=settings.environ(),
485 + features=settings.features,
486 + image_dir=dest_dir,
487 + pkg=cpv, pkg_path=pkg_path,
488 + logfile=settings.get('PORTAGE_LOG_FILE'),
489 + scheduler=SchedulerInterface(loop))
490 +
491 + extractor.start()
492 + yield extractor.async_wait()
493 + if extractor.returncode != os.EX_OK:
494 + raise PortageException("Error Extracting '{}'".format(pkg_path))
495 +
496 + else:
497 + instance_key = self._instance_key(cpv)
498 + add_pkg = self.bintree._additional_pkgs.get(instance_key)
499 + if add_pkg is None:
500 + raise portage.exception.PackageNotFound(cpv)
501 + yield add_pkg._db.unpack_contents(pkg, dest_dir)
502 +
503 def cp_list(self, *pargs, **kwargs):
504 if not self.bintree.populated:
505 self.bintree.populate()
506 @@ -261,6 +338,7 @@ class bindbapi(fakedbapi):
507
508 return filesdict
509
510 +
511 class binarytree(object):
512 "this tree scans for a list of all packages available in PKGDIR"
513 def __init__(self, _unused=DeprecationWarning, pkgdir=None,
514 @@ -301,6 +379,7 @@ class binarytree(object):
515 self.tree = {}
516 self._remote_has_index = False
517 self._remotepkgs = None # remote metadata indexed by cpv
518 + self._additional_pkgs = {}
519 self.invalids = []
520 self.settings = settings
521 self._pkg_paths = {}
522 @@ -511,7 +590,7 @@ class binarytree(object):
523 except PortageException:
524 pass
525
526 - def populate(self, getbinpkgs=False, getbinpkg_refresh=True):
527 + def populate(self, getbinpkgs=False, getbinpkg_refresh=True, add_repos=()):
528 """
529 Populates the binarytree with package metadata.
530
531 @@ -520,12 +599,14 @@ class binarytree(object):
532 @param getbinpkg_refresh: attempt to refresh the cache
533 of remote package metadata if getbinpkgs is also True
534 @type getbinpkg_refresh: bool
535 + @param add_repos: additional binary package repositories
536 + @type add_repos: sequence
537 """
538
539 if self._populating:
540 return
541
542 - if not os.path.isdir(self.pkgdir) and not getbinpkgs:
543 + if not os.path.isdir(self.pkgdir) and not (getbinpkgs or add_repos):
544 self.populated = True
545 return
546
547 @@ -557,6 +638,9 @@ class binarytree(object):
548 if pkgindex_lock:
549 unlockfile(pkgindex_lock)
550
551 + if add_repos:
552 + self._populate_additional(add_repos)
553 +
554 if getbinpkgs:
555 if not self.settings.get("PORTAGE_BINHOST"):
556 writemsg(_("!!! PORTAGE_BINHOST unset, but use is requested.\n"),
557 @@ -1066,6 +1150,16 @@ class binarytree(object):
558 self._merge_pkgindex_header(pkgindex.header,
559 self._pkgindex_header)
560
561 + def _populate_additional(self, repos):
562 + for repo in repos:
563 + aux_keys = list(set(chain(repo._aux_cache_keys, repo._pkg_str_aux_keys)))
564 + for cpv in repo.cpv_all():
565 + metadata = dict(zip(aux_keys, repo.aux_get(cpv, aux_keys)))
566 + pkg = _pkg_str(cpv, metadata=metadata, settings=repo.settings, db=repo)
567 + instance_key = self.dbapi._instance_key(pkg)
568 + self._additional_pkgs[instance_key] = pkg
569 + self.dbapi.cpv_inject(pkg)
570 +
571 def inject(self, cpv, filename=None):
572 """Add a freshly built package to the database. This updates
573 $PKGDIR/Packages with the new package metadata (including MD5).
574 @@ -1500,6 +1594,8 @@ class binarytree(object):
575 filename = self._pkg_paths.get(instance_key)
576 if filename is not None:
577 filename = os.path.join(self.pkgdir, filename)
578 + elif instance_key in self._additional_pkgs:
579 + return None
580
581 if filename is None:
582 if self._multi_instance:
583 @@ -1570,8 +1666,12 @@ class binarytree(object):
584 def isremote(self, pkgname):
585 """Returns true if the package is kept remotely and it has not been
586 downloaded (or it is only partially downloaded)."""
587 - if (self._remotepkgs is None or
588 - self.dbapi._instance_key(pkgname) not in self._remotepkgs):
589 + if self._remotepkgs is None:
590 + return False
591 + instance_key = self.dbapi._instance_key(pkgname)
592 + if instance_key not in self._remotepkgs:
593 + return False
594 + elif instance_key in self._additional_pkgs:
595 return False
596 # Presence in self._remotepkgs implies that it's remote. When a
597 # package is downloaded, state is updated by self.inject().
598
599 diff --git a/lib/portage/dbapi/vartree.py b/lib/portage/dbapi/vartree.py
600 index 603d58015..039e520d5 100644
601 --- a/lib/portage/dbapi/vartree.py
602 +++ b/lib/portage/dbapi/vartree.py
603 @@ -33,7 +33,7 @@ portage.proxy.lazyimport.lazyimport(globals(),
604 'portage.util._compare_files:compare_files',
605 'portage.util.digraph:digraph',
606 'portage.util.env_update:env_update',
607 - 'portage.util.install_mask:install_mask_dir,InstallMask',
608 + 'portage.util.install_mask:install_mask_dir,InstallMask,_raise_exc',
609 'portage.util.listdir:dircache,listdir',
610 'portage.util.movefile:movefile',
611 'portage.util.monotonic:monotonic',
612 @@ -71,6 +71,8 @@ from portage import _os_merge
613 from portage import _selinux_merge
614 from portage import _unicode_decode
615 from portage import _unicode_encode
616 +from portage.util.futures.compat_coroutine import coroutine
617 +from portage.util.futures.executor.fork import ForkExecutor
618 from ._VdbMetadataDelta import VdbMetadataDelta
619
620 from _emerge.EbuildBuildDir import EbuildBuildDir
621 @@ -80,8 +82,10 @@ from _emerge.MiscFunctionsProcess import MiscFunctionsProcess
622 from _emerge.SpawnProcess import SpawnProcess
623 from ._ContentsCaseSensitivityManager import ContentsCaseSensitivityManager
624
625 +import argparse
626 import errno
627 import fnmatch
628 +import functools
629 import gc
630 import grp
631 import io
632 @@ -128,6 +132,7 @@ class vardbapi(dbapi):
633
634 _aux_cache_keys_re = re.compile(r'^NEEDED\..*$')
635 _aux_multi_line_re = re.compile(r'^(CONTENTS|NEEDED\..*)$')
636 + _pkg_str_aux_keys = dbapi._pkg_str_aux_keys + ("BUILD_ID", "BUILD_TIME", "_mtime_")
637
638 def __init__(self, _unused_param=DeprecationWarning,
639 categories=None, settings=None, vartree=None):
640 @@ -953,6 +958,110 @@ class vardbapi(dbapi):
641 pass
642 self._bump_mtime(cpv)
643
644 + @coroutine
645 + def unpack_metadata(self, pkg, dest_dir):
646 + """
647 + Unpack package metadata to a directory. This method is a coroutine.
648 +
649 + @param pkg: package to unpack
650 + @type pkg: _pkg_str or portage.config
651 + @param dest_dir: destination directory
652 + @type dest_dir: str
653 + """
654 + loop = asyncio._wrap_loop()
655 + if not isinstance(pkg, portage.config):
656 + cpv = pkg
657 + else:
658 + cpv = pkg.mycpv
659 + dbdir = self.getpath(cpv)
660 + def async_copy():
661 + for parent, dirs, files in os.walk(dbdir, onerror=_raise_exc):
662 + for key in files:
663 + shutil.copy(os.path.join(parent, key),
664 + os.path.join(dest_dir, key))
665 + break
666 + yield loop.run_in_executor(ForkExecutor(loop=loop), async_copy)
667 +
668 + @coroutine
669 + def unpack_contents(self, pkg, dest_dir,
670 + include_config=None, include_unmodified_config=None):
671 + """
672 + Unpack package contents to a directory. This method is a coroutine.
673 +
674 + This copies files from the installed system, in the same way
675 + as the quickpkg(1) command. Default behavior for handling
676 + of protected configuration files is controlled by the
677 + QUICKPKG_DEFAULT_OPTS variable. The relevant quickpkg options
678 + are --include-config and --include-unmodified-config. When
679 + a configuration file is not included because it is protected,
680 + an ewarn message is logged.
681 +
682 + @param pkg: package to unpack
683 + @type pkg: _pkg_str or portage.config
684 + @param dest_dir: destination directory
685 + @type dest_dir: str
686 + @param include_config: Include all files protected by
687 + CONFIG_PROTECT (as a security precaution, default is False
688 + unless modified by QUICKPKG_DEFAULT_OPTS).
689 + @type include_config: bool
690 + @param include_unmodified_config: Include files protected by
691 + CONFIG_PROTECT that have not been modified since installation
692 + (as a security precaution, default is False unless modified
693 + by QUICKPKG_DEFAULT_OPTS).
694 + @type include_unmodified_config: bool
695 + """
696 + loop = asyncio._wrap_loop()
697 + if not isinstance(pkg, portage.config):
698 + settings = self.settings
699 + cpv = pkg
700 + else:
701 + settings = pkg
702 + cpv = settings.mycpv
703 +
704 + scheduler = SchedulerInterface(loop)
705 + parser = argparse.ArgumentParser()
706 + parser.add_argument('--include-config',
707 + choices=('y', 'n'),
708 + default='n')
709 + parser.add_argument('--include-unmodified-config',
710 + choices=('y', 'n'),
711 + default='n')
712 +
713 + # Method parameters may override QUICKPKG_DEFAULT_OPTS.
714 + opts_list = portage.util.shlex_split(settings.get('QUICKPKG_DEFAULT_OPTS', ''))
715 + if include_config is not None:
716 + opts_list.append('--include-config={}'.format(
717 + 'y' if include_config else 'n'))
718 + if include_unmodified_config is not None:
719 + opts_list.append('--include-unmodified-config={}'.format(
720 + 'y' if include_unmodified_config else 'n'))
721 +
722 + opts, args = parser.parse_known_args(opts_list)
723 +
724 + tar_cmd = ('tar', '-x', '--xattrs', '--xattrs-include=*', '-C', dest_dir)
725 + pr, pw = os.pipe()
726 + proc = (yield asyncio.create_subprocess_exec(*tar_cmd, stdin=pr))
727 + os.close(pr)
728 + with os.fdopen(pw, 'wb', 0) as pw_file:
729 + excluded_config_files = (yield loop.run_in_executor(ForkExecutor(loop=loop),
730 + functools.partial(self._dblink(cpv).quickpkg,
731 + pw_file,
732 + include_config=opts.include_config == 'y',
733 + include_unmodified_config=opts.include_unmodified_config == 'y')))
734 + yield proc.wait()
735 + if proc.returncode != os.EX_OK:
736 + raise PortageException('command failed: {}'.format(tar_cmd))
737 +
738 + if excluded_config_files:
739 + log_lines = ([_("Config files excluded by QUICKPKG_DEFAULT_OPTS (see quickpkg(1) man page):")] +
740 + ['\t{}'.format(name) for name in excluded_config_files])
741 + out = io.StringIO()
742 + for line in log_lines:
743 + portage.elog.messages.ewarn(line, phase='install', key=cpv, out=out)
744 + scheduler.output(out.getvalue(),
745 + background=self.settings.get("PORTAGE_BACKGROUND") == "1",
746 + log_path=settings.get("PORTAGE_LOG_FILE"))
747 +
748 def counter_tick(self, myroot=None, mycpv=None):
749 """
750 @param myroot: ignored, self._eroot is used instead
751 @@ -1892,10 +2001,10 @@ class dblink(object):
752 @param include_config: Include all files protected by CONFIG_PROTECT
753 (as a security precaution, default is False).
754 @type include_config: bool
755 - @param include_config: Include files protected by CONFIG_PROTECT that
756 - have not been modified since installation (as a security precaution,
757 + @param include_unmodified_config: Include files protected by CONFIG_PROTECT
758 + that have not been modified since installation (as a security precaution,
759 default is False).
760 - @type include_config: bool
761 + @type include_unmodified_config: bool
762 @rtype: list
763 @return: Paths of protected configuration files which have been omitted.
764 """
765
766 diff --git a/lib/portage/tests/emerge/test_simple.py b/lib/portage/tests/emerge/test_simple.py
767 index 866521488..f5cd6f3d2 100644
768 --- a/lib/portage/tests/emerge/test_simple.py
769 +++ b/lib/portage/tests/emerge/test_simple.py
770 @@ -1,4 +1,4 @@
771 -# Copyright 2011-2018 Gentoo Foundation
772 +# Copyright 2011-2019 Gentoo Authors
773 # Distributed under the terms of the GNU General Public License v2
774
775 import subprocess
776 @@ -254,6 +254,7 @@ call_has_and_best_version() {
777 cross_eroot = os.path.join(cross_root, eprefix.lstrip(os.sep))
778
779 test_commands = (
780 + emerge_cmd + ("--usepkgonly", "--root", cross_root, "--quickpkg-direct=y", "dev-libs/A"),
781 env_update_cmd,
782 portageq_cmd + ("envvar", "-v", "CONFIG_PROTECT", "EROOT",
783 "PORTAGE_CONFIGROOT", "PORTAGE_TMPDIR", "USERLAND"),
784
785 diff --git a/man/emerge.1 b/man/emerge.1
786 index 8d3e74074..8238515a6 100644
787 --- a/man/emerge.1
788 +++ b/man/emerge.1
789 @@ -1,4 +1,4 @@
790 -.TH "EMERGE" "1" "Jun 2019" "Portage VERSION" "Portage"
791 +.TH "EMERGE" "1" "Nov 2019" "Portage VERSION" "Portage"
792 .SH "NAME"
793 emerge \- Command\-line interface to the Portage system
794 .SH "SYNOPSIS"
795 @@ -829,6 +829,23 @@ B blocked by another package (unresolved conflict)
796 b blocked by another package (automatically resolved conflict)
797 .TE
798 .TP
799 +.BR "\-\-quickpkg\-direct < y | n >"
800 +Enable use of installed packages directly as binary packages. This is
801 +similar to using binary packages produced by \fBquickpkg\fR(1), but
802 +installed packages are used directly as though they are binary packages.
803 +This option only works in combination with the \fB\-\-root=DIR\fR option,
804 +and it comes with the caveat that packages are only allowed to be
805 +installed into the root that is specified by the \fB\-\-root=DIR\fR
806 +option (the other root which serves as a source of packages is
807 +assumed to be immutable during the entire operation).
808 +
809 +Default behavior for handling of protected configuration files is
810 +controlled by the \fBQUICKPKG_DEFAULT_OPTS\fR variable. The relevant
811 +quickpkg options are \fI\-\-include\-config\fR and
812 +\fI\-\-include\-unmodified\-config\fR (refer to the \fBquickpkg\fR(1)
813 +man page). When a configuration file is not included because it is
814 +protected, an ewarn message is logged.
815 +.TP
816 .BR "\-\-quiet [ y | n ]" ", " \-q
817 Results may vary, but the general outcome is a reduced or condensed
818 output from portage's displays.