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] Generate soname dependency metadata (282639)
Date: Tue, 27 Jan 2015 03:17:36
Message-Id: 1422328588-17820-1-git-send-email-zmedico@gentoo.org
1 Generate soname dependency metadata for binary and installed packages,
2 in the form of PROVIDES and REQUIRES metadata. It is useful to generate
3 PROVIDES and REQUIRES metadata now, so that it will be available
4 when dependency resolver support is added in the future. Note that
5 slot-operator dependencies will not be able to serve as a substitute
6 for soname dependencies for the forseeable future, because system
7 dependencies are frequently unspecified (according to Gentoo policy).
8
9 The PROVIDES/REQUIRES system is very similar to the automatic Requires
10 and Provides system which is supported by RPM. The PROVIDES/REQUIRES
11 metadata is generated automatically from the ELF files that are
12 installed by a package. The PROVIDES/REQUIRES syntax is described in
13 the /var/db/pkg section of the portage(5) man page. REQUIRES_EXCLUDE
14 and PROVIDES_EXCLUDE ebuild variables allow for filtering of the
15 sonames that are saved in REQUIRES and PROVIDES (see the ebuild(5) man
16 page for details).
17
18 The /var/db/pkg NEEDED.ELF.2 format now includes an additional field
19 which indicates the multilib category, as discussed in bug #534206. The
20 multilib category is used to categorize the sonames that are listed in
21 PROVIDES/REQUIRES metadata, since sonames need to be resolved
22 separately for each multilib category. The complete list of supported
23 multilib categories is documented in the comments of the
24 portage.dep.soname.multilib_category module.
25
26 X-Gentoo-Bug: 282639
27 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=282639
28 ---
29 bin/ebuild.sh | 2 +-
30 bin/phase-functions.sh | 2 +-
31 man/ebuild.5 | 12 +++
32 man/portage.5 | 25 +++++
33 pym/_emerge/Package.py | 3 +-
34 pym/portage/dbapi/bintree.py | 5 +-
35 pym/portage/dbapi/vartree.py | 1 +
36 pym/portage/dep/soname/__init__.py | 2 +
37 pym/portage/dep/soname/multilib_category.py | 112 +++++++++++++++++++++++
38 pym/portage/package/ebuild/doebuild.py | 86 ++++++++++++++++--
39 pym/portage/util/_dyn_libs/LinkageMapELF.py | 61 ++++++++++---
40 pym/portage/util/_dyn_libs/NeededEntry.py | 83 +++++++++++++++++
41 pym/portage/util/_dyn_libs/soname_deps.py | 136 ++++++++++++++++++++++++++++
42 pym/portage/util/elf/__init__.py | 2 +
43 pym/portage/util/elf/constants.py | 36 ++++++++
44 pym/portage/util/elf/header.py | 62 +++++++++++++
45 pym/portage/util/endian/__init__.py | 2 +
46 pym/portage/util/endian/decode.py | 56 ++++++++++++
47 18 files changed, 661 insertions(+), 27 deletions(-)
48 create mode 100644 pym/portage/dep/soname/__init__.py
49 create mode 100644 pym/portage/dep/soname/multilib_category.py
50 create mode 100644 pym/portage/util/_dyn_libs/NeededEntry.py
51 create mode 100644 pym/portage/util/_dyn_libs/soname_deps.py
52 create mode 100644 pym/portage/util/elf/__init__.py
53 create mode 100644 pym/portage/util/elf/constants.py
54 create mode 100644 pym/portage/util/elf/header.py
55 create mode 100644 pym/portage/util/endian/__init__.py
56 create mode 100644 pym/portage/util/endian/decode.py
57
58 diff --git a/bin/ebuild.sh b/bin/ebuild.sh
59 index e6f9cb9..b6b3723 100755
60 --- a/bin/ebuild.sh
61 +++ b/bin/ebuild.sh
62 @@ -578,7 +578,7 @@ if ! has "$EBUILD_PHASE" clean cleanrm ; then
63 # interaction begins.
64 unset EAPI DEPEND RDEPEND PDEPEND HDEPEND INHERITED IUSE REQUIRED_USE \
65 ECLASS E_IUSE E_REQUIRED_USE E_DEPEND E_RDEPEND E_PDEPEND \
66 - E_HDEPEND
67 + E_HDEPEND PROVIDES_EXCLUDE REQUIRES_EXCLUDE
68
69 if [[ $PORTAGE_DEBUG != 1 || ${-/x/} != $- ]] ; then
70 source "$EBUILD" || die "error sourcing ebuild"
71 diff --git a/bin/phase-functions.sh b/bin/phase-functions.sh
72 index aec86fd..def2080 100644
73 --- a/bin/phase-functions.sh
74 +++ b/bin/phase-functions.sh
75 @@ -580,7 +580,7 @@ __dyn_install() {
76 for f in ASFLAGS CBUILD CC CFLAGS CHOST CTARGET CXX \
77 CXXFLAGS EXTRA_ECONF EXTRA_EINSTALL EXTRA_MAKE \
78 LDFLAGS LIBCFLAGS LIBCXXFLAGS QA_CONFIGURE_OPTIONS \
79 - QA_DESKTOP_FILE ; do
80 + QA_DESKTOP_FILE PROVIDES_EXCLUDE REQUIRES_EXCLUDE ; do
81 x=$(echo -n ${!f})
82 [[ -n $x ]] && echo "$x" > $f
83 done
84 diff --git a/man/ebuild.5 b/man/ebuild.5
85 index b587264..c2cbe4b 100644
86 --- a/man/ebuild.5
87 +++ b/man/ebuild.5
88 @@ -480,6 +480,12 @@ source source\-build which is scheduled for merge
89 .TE
90 .RE
91 .TP
92 +.B PROVIDES_EXCLUDE\fR = \fI[space delimited list of fnmatch patterns]\fR
93 +Sonames and file paths matched by these fnmatch patterns will be
94 +excluded during genertion of \fBPROVIDES\fR metadata (see
95 +\fBportage\fR(5)). Patterns are delimited by whitespace, and it is
96 +possible to create patterns containing quoted whitespace.
97 +.TP
98 .B PORTAGE_LOG_FILE
99 Contains the path of the build log. If \fBPORT_LOGDIR\fR variable is unset then
100 PORTAGE_LOG_FILE=\fI"${T}/build.log"\fR.
101 @@ -501,6 +507,12 @@ to the package version(s) being replaced. Typically, this variable will
102 not contain more than one version, but according to PMS it can contain
103 more.
104 .TP
105 +.B REQUIRES_EXCLUDE\fR = \fI[space delimited list of fnmatch patterns]\fR
106 +Sonames and file paths matched by these fnmatch patterns will be
107 +excluded during generation of \fBREQUIRES\fR metadata (see
108 +\fBportage\fR(5)). Patterns are delimited by whitespace, and it is
109 +possible to create patterns containing quoted whitespace.
110 +.TP
111 .B ROOT\fR = \fI"/"
112 Contains the path that portage should use as the root of the live filesystem.
113 When packages wish to make changes to the live filesystem, they should do so in
114 diff --git a/man/portage.5 b/man/portage.5
115 index 189561c..bf159fd 100644
116 --- a/man/portage.5
117 +++ b/man/portage.5
118 @@ -1443,6 +1443,31 @@ can be changed quickly. Generally though there is one file per environment
119 variable that "matters" (like CFLAGS) with the contents stored inside of it.
120 Another common file is the CONTENTS file which lists the path and hashes of
121 all objects that the package installed onto your system.
122 +.TP
123 +.BR PROVIDES
124 +Contains information about the sonames that a package provides, which is
125 +automatically generated from the files that it installs. The sonames
126 +may have been filtered by the \fBPROVIDES_EXCLUDE\fR \fBebuild\fR(5)
127 +variable. A multilib category, followed by a colon, always preceeds a
128 +list of one or more sonames.
129 +
130 +.I Example:
131 +.nf
132 +x86_32: libcom_err.so.2 libss.so.2 x86_64: libcom_err.so.2 libss.so.2
133 +.fi
134 +.TP
135 +.BR REQUIRES
136 +Contains information about the sonames that a package requires, which is
137 +automatically generated from the files that it installs. The sonames
138 +may have been filtered by the \fBREQUIRES_EXCLUDE\fR \fBebuild\fR(5)
139 +variable. Any sonames that a package provides are automatically excluded
140 +from \fBREQUIRES\fR. A multilib category, followed by a colon, always
141 +preceeds a list of one or more sonames.
142 +
143 +.I Example:
144 +.nf
145 +x86_32: ld-linux.so.2 libc.so.6 x86_64: ld-linux-x86-64.so.2 libc.so.6
146 +.fi
147 .RE
148 .TP
149 .BR /var/lib/portage/
150 diff --git a/pym/_emerge/Package.py b/pym/_emerge/Package.py
151 index 8612e8b..518dbf6 100644
152 --- a/pym/_emerge/Package.py
153 +++ b/pym/_emerge/Package.py
154 @@ -43,7 +43,8 @@ class Package(Task):
155 "HDEPEND", "INHERITED", "IUSE", "KEYWORDS",
156 "LICENSE", "PDEPEND", "PROVIDE", "RDEPEND",
157 "repository", "PROPERTIES", "RESTRICT", "SLOT", "USE",
158 - "_mtime_", "DEFINED_PHASES", "REQUIRED_USE"]
159 + "_mtime_", "DEFINED_PHASES", "REQUIRED_USE", "PROVIDES",
160 + "REQUIRES"]
161
162 _dep_keys = ('DEPEND', 'HDEPEND', 'PDEPEND', 'RDEPEND')
163 _buildtime_keys = ('DEPEND', 'HDEPEND')
164 diff --git a/pym/portage/dbapi/bintree.py b/pym/portage/dbapi/bintree.py
165 index 1156b66..583e208 100644
166 --- a/pym/portage/dbapi/bintree.py
167 +++ b/pym/portage/dbapi/bintree.py
168 @@ -81,7 +81,8 @@ class bindbapi(fakedbapi):
169 ["BUILD_TIME", "CHOST", "DEPEND", "EAPI",
170 "HDEPEND", "IUSE", "KEYWORDS",
171 "LICENSE", "PDEPEND", "PROPERTIES", "PROVIDE",
172 - "RDEPEND", "repository", "RESTRICT", "SLOT", "USE", "DEFINED_PHASES"
173 + "RDEPEND", "repository", "RESTRICT", "SLOT", "USE",
174 + "DEFINED_PHASES", "PROVIDES", "REQUIRES"
175 ])
176 self._aux_cache_slot_dict = slot_dict_class(self._aux_cache_keys)
177 self._aux_cache = {}
178 @@ -322,7 +323,7 @@ class binarytree(object):
179 ["BUILD_TIME", "CHOST", "DEPEND", "DESCRIPTION", "EAPI",
180 "HDEPEND", "IUSE", "KEYWORDS", "LICENSE", "PDEPEND", "PROPERTIES",
181 "PROVIDE", "RESTRICT", "RDEPEND", "repository", "SLOT", "USE", "DEFINED_PHASES",
182 - "BASE_URI"]
183 + "BASE_URI", "PROVIDES", "REQUIRES"]
184 self._pkgindex_aux_keys = list(self._pkgindex_aux_keys)
185 self._pkgindex_use_evaluated_keys = \
186 ("DEPEND", "HDEPEND", "LICENSE", "RDEPEND",
187 diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py
188 index 2d4d32d..cf31c8e 100644
189 --- a/pym/portage/dbapi/vartree.py
190 +++ b/pym/portage/dbapi/vartree.py
191 @@ -176,6 +176,7 @@ class vardbapi(dbapi):
192 "EAPI", "HDEPEND", "HOMEPAGE", "IUSE", "KEYWORDS",
193 "LICENSE", "PDEPEND", "PROPERTIES", "PROVIDE", "RDEPEND",
194 "repository", "RESTRICT" , "SLOT", "USE", "DEFINED_PHASES",
195 + "PROVIDES", "REQUIRES"
196 ])
197 self._aux_cache_obj = None
198 self._aux_cache_filename = os.path.join(self._eroot,
199 diff --git a/pym/portage/dep/soname/__init__.py b/pym/portage/dep/soname/__init__.py
200 new file mode 100644
201 index 0000000..4725d33
202 --- /dev/null
203 +++ b/pym/portage/dep/soname/__init__.py
204 @@ -0,0 +1,2 @@
205 +# Copyright 2015 Gentoo Foundation
206 +# Distributed under the terms of the GNU General Public License v2
207 diff --git a/pym/portage/dep/soname/multilib_category.py b/pym/portage/dep/soname/multilib_category.py
208 new file mode 100644
209 index 0000000..8cc8fd3
210 --- /dev/null
211 +++ b/pym/portage/dep/soname/multilib_category.py
212 @@ -0,0 +1,112 @@
213 +# Copyright 2015 Gentoo Foundation
214 +# Distributed under the terms of the GNU General Public License v2
215 +#
216 +# Compute a multilib category, as discussed here:
217 +#
218 +# https://bugs.gentoo.org/show_bug.cgi?id=534206
219 +#
220 +# Supported categories:
221 +#
222 +# alpha_{32,64}
223 +# arm_{32,64}
224 +# hppa_{32,64}
225 +# ia_{32,64}
226 +# m68k_{32,64}
227 +# mips_{eabi32,eabi64,n32,n64,o32,o64}
228 +# ppc_{32,64}
229 +# s390_{32,64}
230 +# sh_{32,64}
231 +# sparc_{32,64}
232 +# x86_{32,64,x32}
233 +#
234 +# NOTES:
235 +#
236 +# * The ABIs referenced by some of the above *_32 and *_64 categories
237 +# may be imaginary, but they are listed anyway, since the goal is to
238 +# establish a naming convention that is as consistent and uniform as
239 +# possible.
240 +#
241 +# * The Elf header's e_ident[EI_OSABI] byte is completely ignored,
242 +# since OS-independence is one of the goals. The assumption is that,
243 +# for given installation, we are only interested in tracking multilib
244 +# ABIs for a single OS.
245 +
246 +from ...util.elf.constants import (
247 + EF_MIPS_ABI, EF_MIPS_ABI2, ELFCLASS32, ELFCLASS64,
248 + EM_386, EM_68K, EM_AARCH64, EM_ALPHA, EM_ARM, EM_IA_64, EM_MIPS,
249 + EM_PARISC, EM_PPC, EM_PPC64, EM_S390, EM_SH, EM_SPARC,
250 + EM_SPARC32PLUS, EM_SPARCV9, EM_X86_64, E_MIPS_ABI_EABI32,
251 + E_MIPS_ABI_EABI64, E_MIPS_ABI_O32, E_MIPS_ABI_O64)
252 +
253 +_machine_prefix_map = {
254 + EM_386: "x86",
255 + EM_68K: "m68k",
256 + EM_AARCH64: "arm",
257 + EM_ALPHA: "alpha",
258 + EM_ARM: "arm",
259 + EM_IA_64: "ia",
260 + EM_MIPS: "mips",
261 + EM_PARISC: "hppa",
262 + EM_PPC: "ppc",
263 + EM_PPC64: "ppc",
264 + EM_S390: "s390",
265 + EM_SH: "sh",
266 + EM_SPARC: "sparc",
267 + EM_SPARC32PLUS: "sparc",
268 + EM_SPARCV9: "sparc",
269 + EM_X86_64: "x86",
270 +}
271 +
272 +_mips_abi_map = {
273 + E_MIPS_ABI_EABI32: "eabi32",
274 + E_MIPS_ABI_EABI64: "eabi64",
275 + E_MIPS_ABI_O32: "o32",
276 + E_MIPS_ABI_O64: "o64",
277 +}
278 +
279 +def _compute_suffix_mips(elf_header):
280 +
281 + name = None
282 + mips_abi = elf_header.e_flags & EF_MIPS_ABI
283 +
284 + if mips_abi:
285 + name = _mips_abi_map.get(mips_abi)
286 + elif elf_header.e_flags & EF_MIPS_ABI2:
287 + name = "n32"
288 + elif elf_header.ei_class == ELFCLASS64:
289 + name = "n64"
290 +
291 + return name
292 +
293 +def compute_multilib_category(elf_header):
294 + """
295 + Compute a multilib category from an ELF header.
296 +
297 + @param elf_header: an ELFHeader instance
298 + @type elf_header: ELFHeader
299 + @rtype: str
300 + @return: A multilib category, or None if elf_header does not fit
301 + into a recognized category
302 + """
303 + category = None
304 + if elf_header.e_machine is not None:
305 +
306 + prefix = _machine_prefix_map.get(elf_header.e_machine)
307 + suffix = None
308 +
309 + if prefix == "mips":
310 + suffix = _compute_suffix_mips(elf_header)
311 + elif elf_header.ei_class == ELFCLASS64:
312 + suffix = "64"
313 + elif elf_header.ei_class == ELFCLASS32:
314 + if elf_header.e_machine == EM_X86_64:
315 + suffix = "x32"
316 + else:
317 + suffix = "32"
318 +
319 + if prefix is None or suffix is None:
320 + category = None
321 + else:
322 + category = "%s_%s" % (prefix, suffix)
323 +
324 + return category
325 diff --git a/pym/portage/package/ebuild/doebuild.py b/pym/portage/package/ebuild/doebuild.py
326 index 791b5c3..8bc2009 100644
327 --- a/pym/portage/package/ebuild/doebuild.py
328 +++ b/pym/portage/package/ebuild/doebuild.py
329 @@ -33,7 +33,11 @@ portage.proxy.lazyimport.lazyimport(globals(),
330 'portage.package.ebuild._ipc.QueryCommand:QueryCommand',
331 'portage.dep._slot_operator:evaluate_slot_operator_equal_deps',
332 'portage.package.ebuild._spawn_nofetch:spawn_nofetch',
333 + 'portage.util.elf.header:ELFHeader',
334 + 'portage.dep.soname.multilib_category:compute_multilib_category',
335 'portage.util._desktop_entry:validate_desktop_entry',
336 + 'portage.util._dyn_libs.NeededEntry:NeededEntry',
337 + 'portage.util._dyn_libs.soname_deps:SonameDepsProcessor',
338 'portage.util._async.SchedulerInterface:SchedulerInterface',
339 'portage.util._eventloop.EventLoop:EventLoop',
340 'portage.util._eventloop.global_event_loop:global_event_loop',
341 @@ -57,9 +61,9 @@ from portage.eapi import eapi_exports_KV, eapi_exports_merge_type, \
342 eapi_has_pkg_pretend, _get_eapi_attrs
343 from portage.elog import elog_process, _preload_elog_modules
344 from portage.elog.messages import eerror, eqawarn
345 -from portage.exception import DigestException, FileNotFound, \
346 - IncorrectParameter, InvalidDependString, PermissionDenied, \
347 - UnsupportedAPIException
348 +from portage.exception import (DigestException, FileNotFound,
349 + IncorrectParameter, InvalidData, InvalidDependString,
350 + PermissionDenied, UnsupportedAPIException)
351 from portage.localization import _
352 from portage.output import colormap
353 from portage.package.ebuild.prepare_build_dirs import prepare_build_dirs
354 @@ -76,6 +80,11 @@ from _emerge.EbuildSpawnProcess import EbuildSpawnProcess
355 from _emerge.Package import Package
356 from _emerge.RootConfig import RootConfig
357
358 +if sys.hexversion >= 0x3000000:
359 + _unicode = str
360 +else:
361 + _unicode = unicode
362 +
363 _unsandboxed_phases = frozenset([
364 "clean", "cleanrm", "config",
365 "help", "info", "postinst",
366 @@ -2250,21 +2259,64 @@ def _post_src_install_soname_symlinks(mysettings, out):
367 is_libdir_cache[obj_parent] = rval
368 return rval
369
370 + build_info_dir = os.path.join(
371 + mysettings['PORTAGE_BUILDDIR'], 'build-info')
372 + try:
373 + with io.open(_unicode_encode(os.path.join(build_info_dir,
374 + "PROVIDES_EXCLUDE"), encoding=_encodings['fs'],
375 + errors='strict'), mode='r', encoding=_encodings['repo.content'],
376 + errors='replace') as f:
377 + provides_exclude = f.read()
378 + except IOError as e:
379 + if e.errno not in (errno.ENOENT, errno.ESTALE):
380 + raise
381 + provides_exclude = ""
382 +
383 + try:
384 + with io.open(_unicode_encode(os.path.join(build_info_dir,
385 + "REQUIRES_EXCLUDE"), encoding=_encodings['fs'],
386 + errors='strict'), mode='r', encoding=_encodings['repo.content'],
387 + errors='replace') as f:
388 + requires_exclude = f.read()
389 + except IOError as e:
390 + if e.errno not in (errno.ENOENT, errno.ESTALE):
391 + raise
392 + requires_exclude = ""
393 +
394 missing_symlinks = []
395 + soname_deps = SonameDepsProcessor(
396 + provides_exclude, requires_exclude)
397 +
398 + # Parse NEEDED.ELF.2 like LinkageMapELF.rebuild() does, and
399 + # rewrite it to include multilib categories.
400 + needed_file = portage.util.atomic_ofstream(needed_filename,
401 + encoding=_encodings["repo.content"], errors="strict")
402
403 - # Parse NEEDED.ELF.2 like LinkageMapELF.rebuild() does.
404 for l in lines:
405 l = l.rstrip("\n")
406 if not l:
407 continue
408 - fields = l.split(";")
409 - if len(fields) < 5:
410 - portage.util.writemsg_level(_("\nWrong number of fields " \
411 - "in %s: %s\n\n") % (needed_filename, l),
412 + try:
413 + entry = NeededEntry.parse(needed_filename, l)
414 + except InvalidData as e:
415 + portage.util.writemsg_level("\n%s\n\n" % (e,),
416 level=logging.ERROR, noiselevel=-1)
417 continue
418
419 - obj, soname = fields[1:3]
420 + filename = os.path.join(image_dir,
421 + entry.filename.lstrip(os.sep))
422 + with open(_unicode_encode(filename, encoding=_encodings['fs'],
423 + errors='strict'), 'rb') as f:
424 + elf_header = ELFHeader.read(f)
425 +
426 + # Compute the multilib category and write it back to the file.
427 + entry.multilib_category = compute_multilib_category(elf_header)
428 + needed_file.write(_unicode(entry))
429 +
430 + soname_deps.add(entry)
431 + obj = entry.filename
432 + soname = entry.soname
433 +
434 if not soname:
435 continue
436 if not is_libdir(os.path.dirname(obj)):
437 @@ -2284,6 +2336,22 @@ def _post_src_install_soname_symlinks(mysettings, out):
438
439 missing_symlinks.append((obj, soname))
440
441 + needed_file.close()
442 +
443 + if soname_deps.requires is not None:
444 + with io.open(_unicode_encode(os.path.join(build_info_dir,
445 + 'REQUIRES'), encoding=_encodings['fs'], errors='strict'),
446 + mode='w', encoding=_encodings['repo.content'],
447 + errors='strict') as f:
448 + f.write(soname_deps.requires)
449 +
450 + if soname_deps.provides is not None:
451 + with io.open(_unicode_encode(os.path.join(build_info_dir,
452 + 'PROVIDES'), encoding=_encodings['fs'], errors='strict'),
453 + mode='w', encoding=_encodings['repo.content'],
454 + errors='strict') as f:
455 + f.write(soname_deps.provides)
456 +
457 if not missing_symlinks:
458 return
459
460 diff --git a/pym/portage/util/_dyn_libs/LinkageMapELF.py b/pym/portage/util/_dyn_libs/LinkageMapELF.py
461 index 3920f94..c44666a 100644
462 --- a/pym/portage/util/_dyn_libs/LinkageMapELF.py
463 +++ b/pym/portage/util/_dyn_libs/LinkageMapELF.py
464 @@ -11,12 +11,37 @@ from portage import _os_merge
465 from portage import _unicode_decode
466 from portage import _unicode_encode
467 from portage.cache.mappings import slot_dict_class
468 -from portage.exception import CommandNotFound
469 +from portage.exception import CommandNotFound, InvalidData
470 from portage.localization import _
471 from portage.util import getlibpaths
472 from portage.util import grabfile
473 from portage.util import normalize_path
474 +from portage.util import varexpand
475 from portage.util import writemsg_level
476 +from portage.util._dyn_libs.NeededEntry import NeededEntry
477 +
478 +# Map ELF e_machine values from NEEDED.ELF.2 to approximate multilib
479 +# categories. This approximation will produce incorrect results on x32
480 +# and mips systems, but the result is not worse than using the raw
481 +# e_machine value which was used by earlier versions of portage.
482 +_approx_multilib_categories = {
483 + "386": "x86_32",
484 + "68K": "m68k_32",
485 + "AARCH64": "arm_64",
486 + "ALPHA": "alpha_64",
487 + "ARM": "arm_32",
488 + "IA_64": "ia_64",
489 + "MIPS": "mips_o32",
490 + "PARISC": "hppa_64",
491 + "PPC": "ppc_32",
492 + "PPC64": "ppc_64",
493 + "S390": "s390_64",
494 + "SH": "sh_32",
495 + "SPARC": "sparc_32",
496 + "SPARC32PLUS": "sparc_32",
497 + "SPARCV9": "sparc_64",
498 + "X86_64": "x86_64",
499 +}
500
501 class LinkageMapELF(object):
502
503 @@ -294,21 +319,31 @@ class LinkageMapELF(object):
504 "in %s: %s\n\n") % (location, l),
505 level=logging.ERROR, noiselevel=-1)
506 continue
507 - fields = l.split(";")
508 - if len(fields) < 5:
509 - writemsg_level(_("\nWrong number of fields " \
510 - "in %s: %s\n\n") % (location, l),
511 + try:
512 + entry = NeededEntry.parse(location, l)
513 + except InvalidData as e:
514 + writemsg_level("\n%s\n\n" % (e,),
515 level=logging.ERROR, noiselevel=-1)
516 continue
517 - arch = fields[0]
518 - obj = fields[1]
519 - soname = fields[2]
520 - path = frozenset(normalize_path(x) \
521 - for x in filter(None, fields[3].replace(
522 - "${ORIGIN}", os.path.dirname(obj)).replace(
523 - "$ORIGIN", os.path.dirname(obj)).split(":")))
524 +
525 + # If NEEDED.ELF.2 contains the new multilib category field,
526 + # then use that for categorization. Otherwise, if a mapping
527 + # exists, map e_machine (entry.arch) to an approximate
528 + # multilib category. If all else fails, use e_machine, just
529 + # as older versions of portage did.
530 + arch = entry.multilib_category
531 + if arch is None:
532 + arch = _approx_multilib_categories.get(
533 + entry.arch, entry.arch)
534 +
535 + obj = entry.filename
536 + soname = entry.soname
537 + expand = {"ORIGIN": os.path.dirname(entry.filename)}
538 + path = frozenset(normalize_path(varexpand(x, expand))
539 + for x in entry.runpaths)
540 path = frozensets.setdefault(path, path)
541 - needed = frozenset(x for x in fields[4].split(",") if x)
542 + needed = frozenset(entry.needed)
543 +
544 needed = frozensets.setdefault(needed, needed)
545
546 obj_key = self._obj_key(obj)
547 diff --git a/pym/portage/util/_dyn_libs/NeededEntry.py b/pym/portage/util/_dyn_libs/NeededEntry.py
548 new file mode 100644
549 index 0000000..5de59a0
550 --- /dev/null
551 +++ b/pym/portage/util/_dyn_libs/NeededEntry.py
552 @@ -0,0 +1,83 @@
553 +# Copyright 2015 Gentoo Foundation
554 +# Distributed under the terms of the GNU General Public License v2
555 +
556 +from __future__ import unicode_literals
557 +
558 +import sys
559 +
560 +from portage import _encodings, _unicode_encode
561 +from portage.exception import InvalidData
562 +from portage.localization import _
563 +
564 +class NeededEntry(object):
565 + """
566 + Represents one entry (line) from a NEEDED.ELF.2 file. The entry
567 + must have 5 or more semicolon-delimited fields in order to be
568 + considered valid. The sixth field is optional, corresponding
569 + to the multilib category. The multilib_category attribute is
570 + None if the corresponding field is either empty or missing.
571 + """
572 +
573 + __slots__ = ("arch", "filename", "multilib_category", "needed",
574 + "runpaths", "soname")
575 +
576 + _MIN_FIELDS = 5
577 + _MULTILIB_CAT_INDEX = 5
578 +
579 + @classmethod
580 + def parse(cls, filename, line):
581 + """
582 + Parse a NEEDED.ELF.2 entry. Raises InvalidData if necessary.
583 +
584 + @param filename: file name for use in exception messages
585 + @type filename: str
586 + @param line: a single line of text from a NEEDED.ELF.2 file,
587 + without a trailing newline
588 + @type line: str
589 + @rtype: NeededEntry
590 + @return: A new NeededEntry instance containing data from line
591 + """
592 + fields = line.split(";")
593 + if len(fields) < cls._MIN_FIELDS:
594 + raise InvalidData(_("Wrong number of fields "
595 + "in %s: %s\n\n") % (filename, line))
596 +
597 + obj = cls()
598 + # Extra fields may exist (for future extensions).
599 + if (len(fields) > cls._MULTILIB_CAT_INDEX and
600 + fields[cls._MULTILIB_CAT_INDEX]):
601 + obj.multilib_category = fields[cls._MULTILIB_CAT_INDEX]
602 + else:
603 + obj.multilib_category = None
604 +
605 + del fields[cls._MIN_FIELDS:]
606 + obj.arch, obj.filename, obj.soname, rpaths, needed = fields
607 + obj.runpaths = tuple(filter(None, rpaths.split(":")))
608 + obj.needed = tuple(filter(None, needed.split(",")))
609 +
610 + return obj
611 +
612 + def __str__(self):
613 + """
614 + Format this entry for writing to a NEEDED.ELF.2 file.
615 + """
616 + return (
617 + self.arch + ";" +
618 + self.filename + ";" +
619 + self.soname + ";" +
620 + ":".join(self.runpaths) + ";" +
621 + ",".join(self.needed) +
622 + (";" + self.multilib_category if self.multilib_category
623 + is not None else "") +
624 + "\n"
625 + )
626 +
627 + if sys.hexversion < 0x3000000:
628 +
629 + __unicode__ = __str__
630 +
631 + def __str__(self):
632 + return _unicode_encode(self.__unicode__(),
633 + encoding=_encodings['content'])
634 +
635 + __str__.__doc__ = __unicode__.__doc__
636 diff --git a/pym/portage/util/_dyn_libs/soname_deps.py b/pym/portage/util/_dyn_libs/soname_deps.py
637 new file mode 100644
638 index 0000000..b01c3d2
639 --- /dev/null
640 +++ b/pym/portage/util/_dyn_libs/soname_deps.py
641 @@ -0,0 +1,136 @@
642 +# Copyright 2015 Gentoo Foundation
643 +# Distributed under the terms of the GNU General Public License v2
644 +
645 +import fnmatch
646 +from itertools import chain
647 +import os
648 +import re
649 +
650 +from portage.util import shlex_split
651 +
652 +class SonameDepsProcessor(object):
653 + """
654 + Processes NEEDED.ELF.2 entries for one package, in order to generate
655 + REQUIRES and PROVIDES data.
656 +
657 + Any sonames provided by the package will automatically be filtered
658 + from the generated REQUIRES and PROVIDES values.
659 + """
660 +
661 + def __init__(self, provides_exclude, requires_exclude):
662 + """
663 + @param provides_exclude: PROVIDES_EXCLUDE value
664 + @type provides_exclude: str
665 + @param requires_exclude: REQUIRES_EXCLUDE value
666 + @type requires_exclude: str
667 + """
668 + self._provides_exclude = self._exclude_pattern(provides_exclude)
669 + self._requires_exclude = self._exclude_pattern(requires_exclude)
670 + self._requires_map = {}
671 + self._provides_map = {}
672 + self._provides_unfiltered = {}
673 + self._provides = None
674 + self._requires = None
675 + self._intersected = False
676 +
677 + @staticmethod
678 + def _exclude_pattern(s):
679 + # shlex_split enables quoted whitespace inside patterns
680 + if s:
681 + pat = re.compile("|".join(
682 + fnmatch.translate(x.lstrip(os.sep))
683 + for x in shlex_split(s)))
684 + else:
685 + pat = None
686 + return pat
687 +
688 + def add(self, entry):
689 + """
690 + Add one NEEDED.ELF.2 entry, for inclusion in the generated
691 + REQUIRES and PROVIDES values.
692 +
693 + @param entry: NEEDED.ELF.2 entry
694 + @type entry: NeededEntry
695 + """
696 +
697 + multilib_cat = entry.multilib_category
698 + if multilib_cat is None:
699 + # This usage is invalid. The caller must ensure that
700 + # the multilib category data is supplied here.
701 + raise AssertionError(
702 + "Missing multilib category data: %s" % entry.filename)
703 +
704 + if entry.needed and (
705 + self._requires_exclude is None or
706 + self._requires_exclude.match(
707 + entry.filename.lstrip(os.sep)) is None):
708 + for x in entry.needed:
709 + if (self._requires_exclude is None or
710 + self._requires_exclude.match(x) is None):
711 + self._requires_map.setdefault(
712 + multilib_cat, set()).add(x)
713 +
714 + if entry.soname:
715 + self._provides_unfiltered.setdefault(
716 + multilib_cat, set()).add(entry.soname)
717 +
718 + if entry.soname and (
719 + self._provides_exclude is None or
720 + (self._provides_exclude.match(
721 + entry.filename.lstrip(os.sep)) is None and
722 + self._provides_exclude.match(entry.soname) is None)):
723 + self._provides_map.setdefault(
724 + multilib_cat, set()).add(entry.soname)
725 +
726 + def _intersect(self):
727 + requires_map = self._requires_map
728 + provides_map = self._provides_map
729 + provides_unfiltered = self._provides_unfiltered
730 +
731 + for multilib_cat in set(chain(requires_map, provides_map)):
732 + requires_map.setdefault(multilib_cat, set())
733 + provides_map.setdefault(multilib_cat, set())
734 + provides_unfiltered.setdefault(multilib_cat, set())
735 + for soname in list(requires_map[multilib_cat]):
736 + if soname in provides_unfiltered[multilib_cat]:
737 + requires_map[multilib_cat].remove(soname)
738 +
739 + provides_data = []
740 + for multilib_cat in sorted(provides_map):
741 + if provides_map[multilib_cat]:
742 + provides_data.append(multilib_cat + ":")
743 + provides_data.extend(sorted(provides_map[multilib_cat]))
744 +
745 + if provides_data:
746 + self._provides = " ".join(provides_data) + "\n"
747 +
748 + requires_data = []
749 + for multilib_cat in sorted(requires_map):
750 + if requires_map[multilib_cat]:
751 + requires_data.append(multilib_cat + ":")
752 + requires_data.extend(sorted(requires_map[multilib_cat]))
753 +
754 + if requires_data:
755 + self._requires = " ".join(requires_data) + "\n"
756 +
757 + self._intersected = True
758 +
759 + @property
760 + def provides(self):
761 + """
762 + @rtype: str
763 + @return: PROVIDES value generated from NEEDED.ELF.2 entries
764 + """
765 + if not self._intersected:
766 + self._intersect()
767 + return self._provides
768 +
769 + @property
770 + def requires(self):
771 + """
772 + @rtype: str
773 + @return: REQUIRES value generated from NEEDED.ELF.2 entries
774 + """
775 + if not self._intersected:
776 + self._intersect()
777 + return self._requires
778 diff --git a/pym/portage/util/elf/__init__.py b/pym/portage/util/elf/__init__.py
779 new file mode 100644
780 index 0000000..4725d33
781 --- /dev/null
782 +++ b/pym/portage/util/elf/__init__.py
783 @@ -0,0 +1,2 @@
784 +# Copyright 2015 Gentoo Foundation
785 +# Distributed under the terms of the GNU General Public License v2
786 diff --git a/pym/portage/util/elf/constants.py b/pym/portage/util/elf/constants.py
787 new file mode 100644
788 index 0000000..3857b71
789 --- /dev/null
790 +++ b/pym/portage/util/elf/constants.py
791 @@ -0,0 +1,36 @@
792 +# Copyright 2015 Gentoo Foundation
793 +# Distributed under the terms of the GNU General Public License v2
794 +
795 +EI_CLASS = 4
796 +ELFCLASS32 = 1
797 +ELFCLASS64 = 2
798 +
799 +EI_DATA = 5
800 +ELFDATA2LSB = 1
801 +ELFDATA2MSB = 2
802 +
803 +E_MACHINE = 18
804 +EM_SPARC = 2
805 +EM_386 = 3
806 +EM_68K = 4
807 +EM_MIPS = 8
808 +EM_PARISC = 15
809 +EM_SPARC32PLUS = 18
810 +EM_PPC = 20
811 +EM_PPC64 = 21
812 +EM_S390 = 22
813 +EM_ARM = 40
814 +EM_ALPHA = 41
815 +EM_SH = 42
816 +EM_SPARCV9 = 43
817 +EM_IA_64 = 50
818 +EM_X86_64 = 62
819 +EM_AARCH64 = 183
820 +
821 +E_ENTRY = 24
822 +EF_MIPS_ABI = 0x0000F000
823 +EF_MIPS_ABI2 = 0x00000020
824 +E_MIPS_ABI_O32 = 0x00001000
825 +E_MIPS_ABI_O64 = 0x00002000
826 +E_MIPS_ABI_EABI32 = 0x00003000
827 +E_MIPS_ABI_EABI64 = 0x00004000
828 diff --git a/pym/portage/util/elf/header.py b/pym/portage/util/elf/header.py
829 new file mode 100644
830 index 0000000..3310eeb
831 --- /dev/null
832 +++ b/pym/portage/util/elf/header.py
833 @@ -0,0 +1,62 @@
834 +# Copyright 2015 Gentoo Foundation
835 +# Distributed under the terms of the GNU General Public License v2
836 +
837 +import collections
838 +
839 +from ..endian.decode import (decode_uint16_le, decode_uint32_le,
840 + decode_uint16_be, decode_uint32_be)
841 +from .constants import (E_ENTRY, E_MACHINE, EI_CLASS, ELFCLASS32,
842 + ELFCLASS64, ELFDATA2LSB, ELFDATA2MSB)
843 +
844 +class ELFHeader(object):
845 +
846 + __slots__ = ('e_flags', 'e_machine', 'ei_class', 'ei_data')
847 +
848 + @classmethod
849 + def read(cls, f):
850 + """
851 + @param f: an open ELF file
852 + @type f: file
853 + @rtype: ELFHeader
854 + @return: A new ELFHeader instance containing data from f
855 + """
856 + f.seek(EI_CLASS)
857 + ei_class = ord(f.read(1))
858 + ei_data = ord(f.read(1))
859 +
860 + if ei_class == ELFCLASS32:
861 + width = 32
862 + elif ei_class == ELFCLASS64:
863 + width = 64
864 + else:
865 + width = None
866 +
867 + if ei_data == ELFDATA2LSB:
868 + uint16 = decode_uint16_le
869 + uint32 = decode_uint32_le
870 + elif ei_data == ELFDATA2MSB:
871 + uint16 = decode_uint16_be
872 + uint32 = decode_uint32_be
873 + else:
874 + uint16 = None
875 + uint32 = None
876 +
877 + if width is None or uint16 is None:
878 + e_machine = None
879 + e_flags = None
880 + else:
881 + f.seek(E_MACHINE)
882 + e_machine = uint16(f.read(2))
883 +
884 + # E_ENTRY + 3 * sizeof(uintN)
885 + e_flags_offset = E_ENTRY + 3 * width // 8
886 + f.seek(e_flags_offset)
887 + e_flags = uint32(f.read(4))
888 +
889 + obj = cls()
890 + obj.e_flags = e_flags
891 + obj.e_machine = e_machine
892 + obj.ei_class = ei_class
893 + obj.ei_data = ei_data
894 +
895 + return obj
896 diff --git a/pym/portage/util/endian/__init__.py b/pym/portage/util/endian/__init__.py
897 new file mode 100644
898 index 0000000..4725d33
899 --- /dev/null
900 +++ b/pym/portage/util/endian/__init__.py
901 @@ -0,0 +1,2 @@
902 +# Copyright 2015 Gentoo Foundation
903 +# Distributed under the terms of the GNU General Public License v2
904 diff --git a/pym/portage/util/endian/decode.py b/pym/portage/util/endian/decode.py
905 new file mode 100644
906 index 0000000..ec0dcec
907 --- /dev/null
908 +++ b/pym/portage/util/endian/decode.py
909 @@ -0,0 +1,56 @@
910 +# Copyright 2015 Gentoo Foundation
911 +# Distributed under the terms of the GNU General Public License v2
912 +
913 +def decode_uint16_be(data):
914 + """
915 + Decode an unsigned 16-bit integer with big-endian encoding.
916 +
917 + @param data: string of bytes of length 2
918 + @type data: bytes
919 + @rtype: int
920 + @return: unsigned integer value of the decoded data
921 + """
922 + return (ord(data[0:1]) << 8) + ord(data[1:2])
923 +
924 +def decode_uint16_le(data):
925 + """
926 + Decode an unsigned 16-bit integer with little-endian encoding.
927 +
928 + @param data: string of bytes of length 2
929 + @type data: bytes
930 + @rtype: int
931 + @return: unsigned integer value of the decoded data
932 + """
933 + return ord(data[0:1]) + (ord(data[1:2]) << 8)
934 +
935 +def decode_uint32_be(data):
936 + """
937 + Decode an unsigned 32-bit integer with big-endian encoding.
938 +
939 + @param data: string of bytes of length 4
940 + @type data: bytes
941 + @rtype: int
942 + @return: unsigned integer value of the decoded data
943 + """
944 + return (
945 + (ord(data[0:1]) << 24) +
946 + (ord(data[1:2]) << 16) +
947 + (ord(data[2:3]) << 8) +
948 + ord(data[3:4])
949 + )
950 +
951 +def decode_uint32_le(data):
952 + """
953 + Decode an unsigned 32-bit integer with little-endian encoding.
954 +
955 + @param data: string of bytes of length 4
956 + @type data: bytes
957 + @rtype: int
958 + @return: unsigned integer value of the decoded data
959 + """
960 + return (
961 + ord(data[0:1]) +
962 + (ord(data[1:2]) << 8) +
963 + (ord(data[2:3]) << 16) +
964 + (ord(data[3:4]) << 24)
965 + )
966 --
967 2.0.5

Replies