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. |