Gentoo Archives: gentoo-portage-dev

From: "Manuel RĂ¼ger" <mrueg@g.o>
To: gentoo-portage-dev@l.g.o
Subject: [gentoo-portage-dev] [PATCH] Support different (de)compressors for binary packages
Date: Fri, 30 Jun 2017 09:49:25
Message-Id: 677e1f94-7513-224d-1545-df8a260c6002@gentoo.org
1 This patch allows to set the compressor for binary packages via a
2 BINPKG_COMPRESSION variable. BINPKG_COMPRESSION_ARGS allows to specify
3 command-line arguments for that compressor.
4
5 ---
6 bin/binpkg-helper.py | 91
7 +++++++++++++++++++++++++++++++++++
8 bin/misc-functions.sh | 9 +++-
9 bin/quickpkg | 67 +++++++++++++++++++-------
10 man/make.conf.5 | 25 ++++++++++
11 pym/_emerge/BinpkgExtractorAsync.py | 43 +++++++++++++++--
12 pym/portage/dbapi/bintree.py | 8 +--
13 pym/portage/util/compression_probe.py | 45 ++++++++++++++---
14 7 files changed, 253 insertions(+), 35 deletions(-)
15 create mode 100755 bin/binpkg-helper.py
16
17 diff --git a/bin/binpkg-helper.py b/bin/binpkg-helper.py
18 new file mode 100755
19 index 000000000..b603747cf
20 --- /dev/null
21 +++ b/bin/binpkg-helper.py
22 @@ -0,0 +1,91 @@
23 +#!/usr/bin/python -b
24 +# Copyright 2009-2017 Gentoo Foundation
25 +# Distributed under the terms of the GNU General Public License v2
26 +
27 +import argparse
28 +import sys
29 +
30 +import portage
31 +portage._internal_caller = True
32 +from portage import os
33 +from portage import cpv_getkey
34 +from portage.process import find_binary
35 +from portage.util import (
36 + shlex_split,
37 + varexpand,
38 + )
39 +from portage.util.compression_probe import _compressors
40 +
41 +def command_compressioncmd(args):
42 + settings = portage.settings
43 + usage = "usage: compressioncmd ${CATEGORY}/${P}\n"
44 +
45 + if len(args) != 1:
46 + sys.stderr.write(usage)
47 + sys.stderr.write("One argument is required, got %s\n" % len(args))
48 + return 1
49 +
50 + cpv = args[0]
51 + binpkg_compression = settings.get("BINPKG_COMPRESSION", "bzip2")
52 + try:
53 + compression = _compressors[binpkg_compression]
54 + except KeyError as e:
55 + sys.stderr.write("Invalid or unsupported compression method: %s" %
56 e.args[0])
57 + return 1
58 + # Fallback to bzip2 for the package providing the decompressor
59 + # This solves bootstrapping a client without the decompressor
60 +
61 + if cpv_getkey(cpv) is None:
62 + sys.stderr.write("The argument must be in the ${CATEGORY}/${PN}-${PV}
63 format. Provided argument was: %s\n" % cpv)
64 + return 1
65 +
66 + if cpv_getkey(cpv) == compression["package"]:
67 + compression = _compressors["bzip2"]
68 + try:
69 + compression_binary = shlex_split(varexpand(compression["compress"],
70 mydict=settings))[0]
71 + except IndexError as e:
72 + sys.stderr.write("Invalid or unsupported compression method: %s" %
73 e.args[0])
74 + return 1
75 + if find_binary(compression_binary) is None:
76 + missing_package = compression["package"]
77 + sys.stderr.write("File compression unsupported %s. Missing package:
78 %s" % (binpkg_compression, missing_package))
79 + return 1
80 + cmd = [varexpand(x, mydict=settings) for x in
81 shlex_split(compression["compress"])]
82 + # Filter empty elements
83 + cmd = [x for x in cmd if x != ""]
84 +
85 + print(' '.join(cmd))
86 + return os.EX_OK
87 +
88 +def main(argv):
89 +
90 + if argv and isinstance(argv[0], bytes):
91 + for i, x in enumerate(argv):
92 + argv[i] = portage._unicode_decode(x, errors='strict')
93 +
94 + valid_commands = ('compressioncmd',)
95 + description = "Returns the compression command"
96 + usage = "usage: %s COMMAND [args]" % \
97 + os.path.basename(argv[0])
98 +
99 + parser = argparse.ArgumentParser(description=description, usage=usage)
100 + options, args = parser.parse_known_args(argv[1:])
101 +
102 + if not args:
103 + parser.error("missing command argument")
104 +
105 + command = args[0]
106 +
107 + if command not in valid_commands:
108 + parser.error("invalid command: '%s'" % command)
109 +
110 + if command == 'compressioncmd':
111 + rval = command_compressioncmd(args[1:])
112 + else:
113 + raise AssertionError("invalid command: '%s'" % command)
114 +
115 + return rval
116 +
117 +if __name__ == "__main__":
118 + rval = main(sys.argv[:])
119 + sys.exit(rval)
120 diff --git a/bin/misc-functions.sh b/bin/misc-functions.sh
121 index 58755a1e1..0ba0db226 100755
122 --- a/bin/misc-functions.sh
123 +++ b/bin/misc-functions.sh
124 @@ -453,7 +453,7 @@ __dyn_package() {
125 # Make sure $PWD is not ${D} so that we don't leave gmon.out files
126 # in there in case any tools were built with -pg in CFLAGS.
127
128 - cd "${T}"
129 + cd "${T}" || die
130
131 if [[ -n ${PKG_INSTALL_MASK} ]] ; then
132 PROOT=${T}/packaging/
133 @@ -478,8 +478,13 @@ __dyn_package() {
134 [ -z "${PORTAGE_BINPKG_TMPFILE}" ] && \
135 die "PORTAGE_BINPKG_TMPFILE is unset"
136 mkdir -p "${PORTAGE_BINPKG_TMPFILE%/*}" || die "mkdir failed"
137 +
138 COMPRESSION_COMMAND=$(PYTHONPATH=${PORTAGE_PYTHONPATH:-${PORTAGE_PYM_PATH}}
139 \
140 + "${PORTAGE_PYTHON:-/usr/bin/python}"
141 "$PORTAGE_BIN_PATH"/binpkg-helper.py \
142 + compressioncmd ${CATEGORY}/${P})
143 + [ -z "${COMPRESSION_COMMAND}" ] && \
144 + die "Failed to get COMPRESSION_COMMAND"
145 tar $tar_options -cf - $PORTAGE_BINPKG_TAR_OPTS -C "${PROOT}" . | \
146 - $PORTAGE_BZIP2_COMMAND -c > "$PORTAGE_BINPKG_TMPFILE"
147 + $COMPRESSION_COMMAND -c > "$PORTAGE_BINPKG_TMPFILE"
148 assert "failed to pack binary package: '$PORTAGE_BINPKG_TMPFILE'"
149 PYTHONPATH=${PORTAGE_PYTHONPATH:-${PORTAGE_PYM_PATH}} \
150 "${PORTAGE_PYTHON:-/usr/bin/python}"
151 "$PORTAGE_BIN_PATH"/xpak-helper.py recompose \
152 diff --git a/bin/quickpkg b/bin/quickpkg
153 index 4f26ee912..a7f7ec74f 100755
154 --- a/bin/quickpkg
155 +++ b/bin/quickpkg
156 @@ -8,6 +8,7 @@ import argparse
157 import errno
158 import math
159 import signal
160 +import subprocess
161 import sys
162 import tarfile
163
164 @@ -16,17 +17,20 @@ if
165 osp.isfile(osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))),
166 ".porta
167 sys.path.insert(0,
168 osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
169 import portage
170 portage._internal_caller = True
171 +from portage import cpv_getkey
172 from portage import os
173 from portage import xpak
174 from portage.dbapi.dep_expand import dep_expand
175 from portage.dep import Atom, use_reduce
176 from portage.exception import (AmbiguousPackageName, InvalidAtom,
177 InvalidData,
178 InvalidDependString, PackageSetNotFound, PermissionDenied)
179 -from portage.util import ConfigProtect, ensure_dirs, shlex_split, _xattr
180 +from portage.util import ConfigProtect, ensure_dirs, shlex_split,
181 varexpand, _xattr
182 xattr = _xattr.xattr
183 from portage.dbapi.vartree import dblink, tar_contents
184 from portage.checksum import perform_md5
185 from portage._sets import load_default_config, SETPREFIX
186 +from portage.process import find_binary
187 +from portage.util.compression_probe import _compressors
188
189 def quickpkg_atom(options, infos, arg, eout):
190 settings = portage.settings
191 @@ -50,16 +54,16 @@ def quickpkg_atom(options, infos, arg, eout):
192 " ".join(e.args[0]))
193 del e
194 infos["missing"].append(arg)
195 - return
196 + return 1
197 except (InvalidAtom, InvalidData):
198 eout.eerror("Invalid atom: %s" % (arg,))
199 infos["missing"].append(arg)
200 - return
201 + return 1
202 if atom[:1] == '=' and arg[:1] != '=':
203 # dep_expand() allows missing '=' but it's really invalid
204 eout.eerror("Invalid atom: %s" % (arg,))
205 infos["missing"].append(arg)
206 - return
207 + return 1
208
209 matches = vardb.match(atom)
210 pkgs_for_arg = 0
211 @@ -108,16 +112,16 @@ def quickpkg_atom(options, infos, arg, eout):
212 in settings.features))
213 def protect(filename):
214 if not confprot.isprotected(filename):
215 - return False
216 + return 1
217 if include_unmodified_config:
218 file_data = contents[filename]
219 if file_data[0] == "obj":
220 orig_md5 = file_data[2].lower()
221 cur_md5 = perform_md5(filename, calc_prelink=1)
222 if orig_md5 == cur_md5:
223 - return False
224 + return 1
225 excluded_config_files.append(filename)
226 - return True
227 + return os.EX_OK
228 existing_metadata = dict(zip(fix_metadata_keys,
229 vardb.aux_get(cpv, fix_metadata_keys)))
230 category, pf = portage.catsplit(cpv)
231 @@ -134,12 +138,36 @@ def quickpkg_atom(options, infos, arg, eout):
232 binpkg_tmpfile = os.path.join(bintree.pkgdir,
233 cpv + ".tbz2." + str(os.getpid()))
234 ensure_dirs(os.path.dirname(binpkg_tmpfile))
235 - # The tarfile module will write pax headers holding the
236 - # xattrs only if PAX_FORMAT is specified here.
237 - tar = tarfile.open(binpkg_tmpfile, "w:bz2",
238 - format=tarfile.PAX_FORMAT if xattrs else tarfile.DEFAULT_FORMAT)
239 - tar_contents(contents, root, tar, protect=protect, xattrs=xattrs)
240 - tar.close()
241 + binpkg_compression = settings.get("BINPKG_COMPRESSION", "bzip2")
242 + try:
243 + compression = _compressors[binpkg_compression]
244 + except KeyError as e:
245 + eout.eerror("Invalid or unsupported compression method: %s" %
246 e.args[0])
247 + return 1
248 + # Fallback to bzip2 for the package providing the decompressor
249 + # This solves bootstrapping a client without the decompressor
250 + if cpv_getkey(cpv) == compression["package"]:
251 + compression = _compressors["bzip2"]
252 + try:
253 + compression_binary = shlex_split(varexpand(compression["compress"],
254 mydict=settings))[0]
255 + except IndexError as e:
256 + eout.eerror("Invalid or unsupported compression method: %s" %
257 e.args[0])
258 + return 1
259 + if find_binary(compression_binary) is None:
260 + missing_package = compression["package"]
261 + eout.eerror("File compression unsupported %s. Missing package: %s"
262 % (binpkg_compression, missing_package))
263 + return 1
264 + cmd = [varexpand(x, mydict=settings) for x in
265 shlex_split(compression["compress"])]
266 + # Filter empty elements that make Popen fail
267 + cmd = [x for x in cmd if x != ""]
268 + with open(binpkg_tmpfile, "wb") as fobj:
269 + proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=fobj)
270 + # The tarfile module will write pax headers holding the
271 + # xattrs only if PAX_FORMAT is specified here.
272 + with tarfile.open(mode="w|",format=tarfile.PAX_FORMAT if xattrs
273 else tarfile.DEFAULT_FORMAT, fileobj=proc.stdin) as tar:
274 + tar_contents(contents, root, tar, protect=protect, xattrs=xattrs)
275 + proc.stdin.close()
276 + proc.wait()
277 xpak.tbz2(binpkg_tmpfile).recompose_mem(xpdata)
278 finally:
279 if have_lock:
280 @@ -154,16 +182,20 @@ def quickpkg_atom(options, infos, arg, eout):
281 eout.eerror(str(e))
282 del e
283 eout.eerror("Failed to create package: '%s'" % binpkg_path)
284 + return 1
285 else:
286 eout.eend(0)
287 infos["successes"].append((cpv, s.st_size))
288 infos["config_files_excluded"] += len(excluded_config_files)
289 for filename in excluded_config_files:
290 eout.ewarn("Excluded config: '%s'" % filename)
291 + return os.EX_OK
292 if not pkgs_for_arg:
293 eout.eerror("Could not find anything " + \
294 "to match '%s'; skipping" % arg)
295 infos["missing"].append(arg)
296 + return 1
297 + return os.EX_OK
298
299 def quickpkg_set(options, infos, arg, eout):
300 eroot = portage.settings['EROOT']
301 @@ -179,7 +211,7 @@ def quickpkg_set(options, infos, arg, eout):
302 if not set in sets:
303 eout.eerror("Package set not found: '%s'; skipping" % (arg,))
304 infos["missing"].append(arg)
305 - return
306 + return 1
307
308 try:
309 atoms = setconfig.getSetAtoms(set)
310 @@ -187,10 +219,11 @@ def quickpkg_set(options, infos, arg, eout):
311 eout.eerror("Failed to process package set '%s' because " % set +
312 "it contains the non-existent package set '%s'; skipping" % e)
313 infos["missing"].append(arg)
314 - return
315 -
316 + return 1
317 + retval = os.EX_OK
318 for atom in atoms:
319 - quickpkg_atom(options, infos, atom, eout)
320 + retval |= quickpkg_atom(options, infos, atom, eout)
321 + return retval
322
323
324 def quickpkg_extended_atom(options, infos, atom, eout):
325 diff --git a/man/make.conf.5 b/man/make.conf.5
326 index aea189e4a..8e0d04967 100644
327 --- a/man/make.conf.5
328 +++ b/man/make.conf.5
329 @@ -110,6 +110,31 @@ ACCEPT_RESTRICT="*"
330 ACCEPT_RESTRICT="* -bindist"
331 .fi
332 .TP
333 +\fBBINPKG_COMPRESSION\fR = \fI"compression"\fR
334 +This variable is used to determine the compression used for \fIbinary
335 +packages\fR. Supported settings and compression algorithms are: bzip2,
336 gzip,
337 +lz4, lzip, lzop, xz, zstd.
338 +.br
339 +Defaults to "bzip2".
340 +.br
341 +.I Example:
342 +.nf
343 +# Set it to use lz4:
344 +BINPKG_COMPRESSION="lz4"
345 +.fi
346 +.TP
347 +\fBBINPKG_COMPRESSION_ARGS\fR = \fI"arguments for compression command"\fR
348 +This variable is used to add additional arguments to the compression
349 command
350 +selected by \fBBINPKG_COMPRESSION\fR.
351 +.br
352 +Defaults to "".
353 +.br
354 +.I Example:
355 +.nf
356 +# Set it to use compression level 9:
357 +BINPKG_COMPRESSION_ARGS="-9"
358 +.fi
359 +.TP
360 .B CBUILD
361 This variable is passed by the \fIebuild scripts\fR to the \fIconfigure\fR
362 as \fI\-\-build=${CBUILD}\fR only if it is defined. Do not set this
363 yourself
364 diff --git a/pym/_emerge/BinpkgExtractorAsync.py
365 b/pym/_emerge/BinpkgExtractorAsync.py
366 index 0bf3c74c9..e85f4ecac 100644
367 --- a/pym/_emerge/BinpkgExtractorAsync.py
368 +++ b/pym/_emerge/BinpkgExtractorAsync.py
369 @@ -6,8 +6,15 @@
370 from _emerge.SpawnProcess import SpawnProcess
371 import portage
372 from portage.localization import _
373 -from portage.util.compression_probe import (compression_probe,
374 - _decompressors)
375 +from portage.util.compression_probe import (
376 + compression_probe,
377 + _compressors,
378 +)
379 +from portage.process import find_binary
380 +from portage.util import (
381 + shlex_split,
382 + varexpand,
383 +)
384 import signal
385 import subprocess
386
387 @@ -28,8 +35,11 @@ def _start(self):
388 tar_options.append(portage._shell_quote("--xattrs-exclude=%s" % x))
389 tar_options = " ".join(tar_options)
390
391 - decomp_cmd = _decompressors.get(
392 - compression_probe(self.pkg_path))
393 + decomp = _compressors.get(compression_probe(self.pkg_path))
394 + if decomp is not None:
395 + decomp_cmd = decomp.get("decompress")
396 + else:
397 + decomp_cmd = None
398 if decomp_cmd is None:
399 self.scheduler.output("!!! %s\n" %
400 _("File compression header unrecognized: %s") %
401 @@ -39,6 +49,31 @@ def _start(self):
402 self._async_wait()
403 return
404
405 + try:
406 + decompression_binary = shlex_split(varexpand(decomp_cmd,
407 mydict=self.env))[0]
408 + except IndexError:
409 + decompression_binary = ""
410 +
411 + if find_binary(decompression_binary) is None:
412 + # Try alternative command if it exists
413 + if
414 _compressors.get(compression_probe(self.pkg_path)).get("decompress_alt"):
415 + decomp_cmd = _compressors.get(
416 + compression_probe(self.pkg_path)).get("decompress_alt")
417 + try:
418 + decompression_binary = shlex_split(varexpand(decomp_cmd,
419 mydict=self.env))[0]
420 + except IndexError:
421 + decompression_binary = ""
422 +
423 + if find_binary(decompression_binary) is None:
424 + missing_package =
425 _compressors.get(compression_probe(self.pkg_path)).get("package")
426 + self.scheduler.output("!!! %s\n" %
427 + _("File compression unsupported %s.\n Command was: %s.\n Maybe
428 missing package: %s") %
429 + (self.pkg_path, varexpand(decomp_cmd, mydict=self.env),
430 missing_package), log_path=self.logfile,
431 + background=self.background, level=logging.ERROR)
432 + self.returncode = 1
433 + self._async_wait()
434 + return
435 +
436 # Add -q to decomp_cmd opts, in order to avoid "trailing garbage
437 # after EOF ignored" warning messages due to xpak trailer.
438 # SIGPIPE handling (128 + SIGPIPE) should be compatible with
439 diff --git a/pym/portage/dbapi/bintree.py b/pym/portage/dbapi/bintree.py
440 index ca90ba8f9..c833968c2 100644
441 --- a/pym/portage/dbapi/bintree.py
442 +++ b/pym/portage/dbapi/bintree.py
443 @@ -141,7 +141,6 @@ def aux_get(self, mycpv, wants, myrepo=None):
444 return [aux_cache.get(x, "") for x in wants]
445 mysplit = mycpv.split("/")
446 mylist = []
447 - tbz2name = mysplit[1]+".tbz2"
448 if not self.bintree._remotepkgs or \
449 not self.bintree.isremote(mycpv):
450 try:
451 @@ -1448,9 +1447,10 @@ def _allocate_filename_multi(self, cpv):
452 @staticmethod
453 def _parse_build_id(filename):
454 build_id = -1
455 - hyphen = filename.rfind("-", 0, -6)
456 + suffixlen = len(".xpak")
457 + hyphen = filename.rfind("-", 0, -(suffixlen + 1))
458 if hyphen != -1:
459 - build_id = filename[hyphen+1:-5]
460 + build_id = filename[hyphen+1:-suffixlen]
461 try:
462 build_id = long(build_id)
463 except ValueError:
464 @@ -1497,7 +1497,7 @@ def gettbz2(self, pkgname):
465 if self._remote_has_index:
466 rel_url = self._remotepkgs[instance_key].get("PATH")
467 if not rel_url:
468 - rel_url = pkgname+".tbz2"
469 + rel_url = pkgname + ".tbz2"
470 remote_base_uri = self._remotepkgs[instance_key]["BASE_URI"]
471 url = remote_base_uri.rstrip("/") + "/" + rel_url.lstrip("/")
472 else:
473 diff --git a/pym/portage/util/compression_probe.py
474 b/pym/portage/util/compression_probe.py
475 index 754621016..b15200044 100644
476 --- a/pym/portage/util/compression_probe.py
477 +++ b/pym/portage/util/compression_probe.py
478 @@ -11,14 +11,43 @@
479 from portage import _encodings, _unicode_encode
480 from portage.exception import FileNotFound, PermissionDenied
481
482 -_decompressors = {
483 - "bzip2": "${PORTAGE_BUNZIP2_COMMAND:-${PORTAGE_BZIP2_COMMAND} -d}",
484 - "gzip": "gzip -d",
485 - "lz4": "lz4 -d",
486 - "lzip": "lzip -d",
487 - "lzop": "lzop -d",
488 - "xz": "xz -d",
489 - "zstd": "zstd -d",
490 +_compressors = {
491 + "bzip2": {
492 + "compress": "${PORTAGE_BZIP2_COMMAND} ${BINPKG_COMPRESSION_ARGS}",
493 + "decompress": "${PORTAGE_BUNZIP2_COMMAND}",
494 + "decompress_alt": "${PORTAGE_BZIP2_COMMAND} -d",
495 + "package": "app-arch/bzip2",
496 + },
497 + "gzip": {
498 + "compress": "gzip ${BINPKG_COMPRESSION_ARGS}",
499 + "decompress": "gzip -d",
500 + "package": "app-arch/gzip",
501 + },
502 + "lz4": {
503 + "compress": "lz4 ${BINPKG_COMPRESSION_ARGS}",
504 + "decompress": "lz4 -d",
505 + "package": "app-arch/lz4",
506 + },
507 + "lzip": {
508 + "compress": "lzip ${BINPKG_COMPRESSION_ARGS}",
509 + "decompress": "lzip -d",
510 + "package": "app-arch/lzip",
511 + },
512 + "lzop": {
513 + "compress": "lzop ${BINPKG_COMPRESSION_ARGS}",
514 + "decompress": "lzop -d",
515 + "package": "app-arch/lzop",
516 + },
517 + "xz": {
518 + "compress": "xz ${BINPKG_COMPRESSION_ARGS}",
519 + "decompress": "xz -d",
520 + "package": "app-arch/xz-utils",
521 + },
522 + "zstd": {
523 + "compress": "zstd ${BINPKG_COMPRESSION_ARGS}",
524 + "decompress": "zstd -d",
525 + "package": "app-arch/zstd",
526 + },
527 }
528
529 _compression_re = re.compile(b'^(' +

Replies