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