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 1/2] Generate soname dependency metadata (282639)
Date: Mon, 09 Feb 2015 05:42:50
Message-Id: 1423460519-10610-2-git-send-email-zmedico@gentoo.org
In Reply to: [gentoo-portage-dev] [PATCH 0/2] Implement soname dependencies 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 This is mostly the same as "[PATCH v2] Generate soname dependency
30 metadata (282639)" sent to the list earlier. The only changes are:
31 * Added e_type attribute to the ELFHeader class, so that it can be used
32 to distinguish ET_REL, ET_EXEC, ET_DYN, and ET_CORE types.
33 * Enabled unicode_literals in more files.
34
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 | 114 +++++++++++++++++++++++
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 | 138 ++++++++++++++++++++++++++++
49 pym/portage/util/elf/__init__.py | 2 +
50 pym/portage/util/elf/constants.py | 45 +++++++++
51 pym/portage/util/elf/header.py | 65 +++++++++++++
52 pym/portage/util/endian/__init__.py | 2 +
53 pym/portage/util/endian/decode.py | 48 ++++++++++
54 18 files changed, 668 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 3f35180..95d0453 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 @@ -506,6 +512,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 8bebd32..cec4e2f 100644
123 --- a/man/portage.5
124 +++ b/man/portage.5
125 @@ -1479,6 +1479,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..30dbe1d
217 --- /dev/null
218 +++ b/pym/portage/dep/soname/multilib_category.py
219 @@ -0,0 +1,114 @@
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 __future__ import unicode_literals
254 +
255 +from portage.util.elf.constants import (
256 + EF_MIPS_ABI, EF_MIPS_ABI2, ELFCLASS32, ELFCLASS64,
257 + EM_386, EM_68K, EM_AARCH64, EM_ALPHA, EM_ARM, EM_IA_64, EM_MIPS,
258 + EM_PARISC, EM_PPC, EM_PPC64, EM_S390, EM_SH, EM_SPARC,
259 + EM_SPARC32PLUS, EM_SPARCV9, EM_X86_64, E_MIPS_ABI_EABI32,
260 + E_MIPS_ABI_EABI64, E_MIPS_ABI_O32, E_MIPS_ABI_O64)
261 +
262 +_machine_prefix_map = {
263 + EM_386: "x86",
264 + EM_68K: "m68k",
265 + EM_AARCH64: "arm",
266 + EM_ALPHA: "alpha",
267 + EM_ARM: "arm",
268 + EM_IA_64: "ia",
269 + EM_MIPS: "mips",
270 + EM_PARISC: "hppa",
271 + EM_PPC: "ppc",
272 + EM_PPC64: "ppc",
273 + EM_S390: "s390",
274 + EM_SH: "sh",
275 + EM_SPARC: "sparc",
276 + EM_SPARC32PLUS: "sparc",
277 + EM_SPARCV9: "sparc",
278 + EM_X86_64: "x86",
279 +}
280 +
281 +_mips_abi_map = {
282 + E_MIPS_ABI_EABI32: "eabi32",
283 + E_MIPS_ABI_EABI64: "eabi64",
284 + E_MIPS_ABI_O32: "o32",
285 + E_MIPS_ABI_O64: "o64",
286 +}
287 +
288 +def _compute_suffix_mips(elf_header):
289 +
290 + name = None
291 + mips_abi = elf_header.e_flags & EF_MIPS_ABI
292 +
293 + if mips_abi:
294 + name = _mips_abi_map.get(mips_abi)
295 + elif elf_header.e_flags & EF_MIPS_ABI2:
296 + name = "n32"
297 + elif elf_header.ei_class == ELFCLASS64:
298 + name = "n64"
299 +
300 + return name
301 +
302 +def compute_multilib_category(elf_header):
303 + """
304 + Compute a multilib category from an ELF header.
305 +
306 + @param elf_header: an ELFHeader instance
307 + @type elf_header: ELFHeader
308 + @rtype: str
309 + @return: A multilib category, or None if elf_header does not fit
310 + into a recognized category
311 + """
312 + category = None
313 + if elf_header.e_machine is not None:
314 +
315 + prefix = _machine_prefix_map.get(elf_header.e_machine)
316 + suffix = None
317 +
318 + if prefix == "mips":
319 + suffix = _compute_suffix_mips(elf_header)
320 + elif elf_header.ei_class == ELFCLASS64:
321 + suffix = "64"
322 + elif elf_header.ei_class == ELFCLASS32:
323 + if elf_header.e_machine == EM_X86_64:
324 + suffix = "x32"
325 + else:
326 + suffix = "32"
327 +
328 + if prefix is None or suffix is None:
329 + category = None
330 + else:
331 + category = "%s_%s" % (prefix, suffix)
332 +
333 + return category
334 diff --git a/pym/portage/package/ebuild/doebuild.py b/pym/portage/package/ebuild/doebuild.py
335 index 0ac0166..a5970d5 100644
336 --- a/pym/portage/package/ebuild/doebuild.py
337 +++ b/pym/portage/package/ebuild/doebuild.py
338 @@ -33,7 +33,11 @@ portage.proxy.lazyimport.lazyimport(globals(),
339 'portage.package.ebuild._ipc.QueryCommand:QueryCommand',
340 'portage.dep._slot_operator:evaluate_slot_operator_equal_deps',
341 'portage.package.ebuild._spawn_nofetch:spawn_nofetch',
342 + 'portage.util.elf.header:ELFHeader',
343 + 'portage.dep.soname.multilib_category:compute_multilib_category',
344 'portage.util._desktop_entry:validate_desktop_entry',
345 + 'portage.util._dyn_libs.NeededEntry:NeededEntry',
346 + 'portage.util._dyn_libs.soname_deps:SonameDepsProcessor',
347 'portage.util._async.SchedulerInterface:SchedulerInterface',
348 'portage.util._eventloop.EventLoop:EventLoop',
349 'portage.util._eventloop.global_event_loop:global_event_loop',
350 @@ -57,9 +61,9 @@ from portage.eapi import eapi_exports_KV, eapi_exports_merge_type, \
351 eapi_has_pkg_pretend, _get_eapi_attrs
352 from portage.elog import elog_process, _preload_elog_modules
353 from portage.elog.messages import eerror, eqawarn
354 -from portage.exception import DigestException, FileNotFound, \
355 - IncorrectParameter, InvalidDependString, PermissionDenied, \
356 - UnsupportedAPIException
357 +from portage.exception import (DigestException, FileNotFound,
358 + IncorrectParameter, InvalidData, InvalidDependString,
359 + PermissionDenied, UnsupportedAPIException)
360 from portage.localization import _
361 from portage.output import colormap
362 from portage.package.ebuild.prepare_build_dirs import prepare_build_dirs
363 @@ -77,6 +81,11 @@ from _emerge.EbuildSpawnProcess import EbuildSpawnProcess
364 from _emerge.Package import Package
365 from _emerge.RootConfig import RootConfig
366
367 +if sys.hexversion >= 0x3000000:
368 + _unicode = str
369 +else:
370 + _unicode = unicode
371 +
372 _unsandboxed_phases = frozenset([
373 "clean", "cleanrm", "config",
374 "help", "info", "postinst",
375 @@ -2261,21 +2270,64 @@ def _post_src_install_soname_symlinks(mysettings, out):
376 is_libdir_cache[obj_parent] = rval
377 return rval
378
379 + build_info_dir = os.path.join(
380 + mysettings['PORTAGE_BUILDDIR'], 'build-info')
381 + try:
382 + with io.open(_unicode_encode(os.path.join(build_info_dir,
383 + "PROVIDES_EXCLUDE"), encoding=_encodings['fs'],
384 + errors='strict'), mode='r', encoding=_encodings['repo.content'],
385 + errors='replace') as f:
386 + provides_exclude = f.read()
387 + except IOError as e:
388 + if e.errno not in (errno.ENOENT, errno.ESTALE):
389 + raise
390 + provides_exclude = ""
391 +
392 + try:
393 + with io.open(_unicode_encode(os.path.join(build_info_dir,
394 + "REQUIRES_EXCLUDE"), encoding=_encodings['fs'],
395 + errors='strict'), mode='r', encoding=_encodings['repo.content'],
396 + errors='replace') as f:
397 + requires_exclude = f.read()
398 + except IOError as e:
399 + if e.errno not in (errno.ENOENT, errno.ESTALE):
400 + raise
401 + requires_exclude = ""
402 +
403 missing_symlinks = []
404 + soname_deps = SonameDepsProcessor(
405 + provides_exclude, requires_exclude)
406 +
407 + # Parse NEEDED.ELF.2 like LinkageMapELF.rebuild() does, and
408 + # rewrite it to include multilib categories.
409 + needed_file = portage.util.atomic_ofstream(needed_filename,
410 + encoding=_encodings["repo.content"], errors="strict")
411
412 - # Parse NEEDED.ELF.2 like LinkageMapELF.rebuild() does.
413 for l in lines:
414 l = l.rstrip("\n")
415 if not l:
416 continue
417 - fields = l.split(";")
418 - if len(fields) < 5:
419 - portage.util.writemsg_level(_("\nWrong number of fields " \
420 - "in %s: %s\n\n") % (needed_filename, l),
421 + try:
422 + entry = NeededEntry.parse(needed_filename, l)
423 + except InvalidData as e:
424 + portage.util.writemsg_level("\n%s\n\n" % (e,),
425 level=logging.ERROR, noiselevel=-1)
426 continue
427
428 - obj, soname = fields[1:3]
429 + filename = os.path.join(image_dir,
430 + entry.filename.lstrip(os.sep))
431 + with open(_unicode_encode(filename, encoding=_encodings['fs'],
432 + errors='strict'), 'rb') as f:
433 + elf_header = ELFHeader.read(f)
434 +
435 + # Compute the multilib category and write it back to the file.
436 + entry.multilib_category = compute_multilib_category(elf_header)
437 + needed_file.write(_unicode(entry))
438 +
439 + soname_deps.add(entry)
440 + obj = entry.filename
441 + soname = entry.soname
442 +
443 if not soname:
444 continue
445 if not is_libdir(os.path.dirname(obj)):
446 @@ -2295,6 +2347,22 @@ def _post_src_install_soname_symlinks(mysettings, out):
447
448 missing_symlinks.append((obj, soname))
449
450 + needed_file.close()
451 +
452 + if soname_deps.requires is not None:
453 + with io.open(_unicode_encode(os.path.join(build_info_dir,
454 + 'REQUIRES'), encoding=_encodings['fs'], errors='strict'),
455 + mode='w', encoding=_encodings['repo.content'],
456 + errors='strict') as f:
457 + f.write(soname_deps.requires)
458 +
459 + if soname_deps.provides is not None:
460 + with io.open(_unicode_encode(os.path.join(build_info_dir,
461 + 'PROVIDES'), encoding=_encodings['fs'], errors='strict'),
462 + mode='w', encoding=_encodings['repo.content'],
463 + errors='strict') as f:
464 + f.write(soname_deps.provides)
465 +
466 if not missing_symlinks:
467 return
468
469 diff --git a/pym/portage/util/_dyn_libs/LinkageMapELF.py b/pym/portage/util/_dyn_libs/LinkageMapELF.py
470 index 3920f94..c44666a 100644
471 --- a/pym/portage/util/_dyn_libs/LinkageMapELF.py
472 +++ b/pym/portage/util/_dyn_libs/LinkageMapELF.py
473 @@ -11,12 +11,37 @@ from portage import _os_merge
474 from portage import _unicode_decode
475 from portage import _unicode_encode
476 from portage.cache.mappings import slot_dict_class
477 -from portage.exception import CommandNotFound
478 +from portage.exception import CommandNotFound, InvalidData
479 from portage.localization import _
480 from portage.util import getlibpaths
481 from portage.util import grabfile
482 from portage.util import normalize_path
483 +from portage.util import varexpand
484 from portage.util import writemsg_level
485 +from portage.util._dyn_libs.NeededEntry import NeededEntry
486 +
487 +# Map ELF e_machine values from NEEDED.ELF.2 to approximate multilib
488 +# categories. This approximation will produce incorrect results on x32
489 +# and mips systems, but the result is not worse than using the raw
490 +# e_machine value which was used by earlier versions of portage.
491 +_approx_multilib_categories = {
492 + "386": "x86_32",
493 + "68K": "m68k_32",
494 + "AARCH64": "arm_64",
495 + "ALPHA": "alpha_64",
496 + "ARM": "arm_32",
497 + "IA_64": "ia_64",
498 + "MIPS": "mips_o32",
499 + "PARISC": "hppa_64",
500 + "PPC": "ppc_32",
501 + "PPC64": "ppc_64",
502 + "S390": "s390_64",
503 + "SH": "sh_32",
504 + "SPARC": "sparc_32",
505 + "SPARC32PLUS": "sparc_32",
506 + "SPARCV9": "sparc_64",
507 + "X86_64": "x86_64",
508 +}
509
510 class LinkageMapELF(object):
511
512 @@ -294,21 +319,31 @@ class LinkageMapELF(object):
513 "in %s: %s\n\n") % (location, l),
514 level=logging.ERROR, noiselevel=-1)
515 continue
516 - fields = l.split(";")
517 - if len(fields) < 5:
518 - writemsg_level(_("\nWrong number of fields " \
519 - "in %s: %s\n\n") % (location, l),
520 + try:
521 + entry = NeededEntry.parse(location, l)
522 + except InvalidData as e:
523 + writemsg_level("\n%s\n\n" % (e,),
524 level=logging.ERROR, noiselevel=-1)
525 continue
526 - arch = fields[0]
527 - obj = fields[1]
528 - soname = fields[2]
529 - path = frozenset(normalize_path(x) \
530 - for x in filter(None, fields[3].replace(
531 - "${ORIGIN}", os.path.dirname(obj)).replace(
532 - "$ORIGIN", os.path.dirname(obj)).split(":")))
533 +
534 + # If NEEDED.ELF.2 contains the new multilib category field,
535 + # then use that for categorization. Otherwise, if a mapping
536 + # exists, map e_machine (entry.arch) to an approximate
537 + # multilib category. If all else fails, use e_machine, just
538 + # as older versions of portage did.
539 + arch = entry.multilib_category
540 + if arch is None:
541 + arch = _approx_multilib_categories.get(
542 + entry.arch, entry.arch)
543 +
544 + obj = entry.filename
545 + soname = entry.soname
546 + expand = {"ORIGIN": os.path.dirname(entry.filename)}
547 + path = frozenset(normalize_path(varexpand(x, expand))
548 + for x in entry.runpaths)
549 path = frozensets.setdefault(path, path)
550 - needed = frozenset(x for x in fields[4].split(",") if x)
551 + needed = frozenset(entry.needed)
552 +
553 needed = frozensets.setdefault(needed, needed)
554
555 obj_key = self._obj_key(obj)
556 diff --git a/pym/portage/util/_dyn_libs/NeededEntry.py b/pym/portage/util/_dyn_libs/NeededEntry.py
557 new file mode 100644
558 index 0000000..c52cfce
559 --- /dev/null
560 +++ b/pym/portage/util/_dyn_libs/NeededEntry.py
561 @@ -0,0 +1,82 @@
562 +# Copyright 2015 Gentoo Foundation
563 +# Distributed under the terms of the GNU General Public License v2
564 +
565 +from __future__ import unicode_literals
566 +
567 +import sys
568 +
569 +from portage import _encodings, _unicode_encode
570 +from portage.exception import InvalidData
571 +from portage.localization import _
572 +
573 +class NeededEntry(object):
574 + """
575 + Represents one entry (line) from a NEEDED.ELF.2 file. The entry
576 + must have 5 or more semicolon-delimited fields in order to be
577 + considered valid. The sixth field is optional, corresponding
578 + to the multilib category. The multilib_category attribute is
579 + None if the corresponding field is either empty or missing.
580 + """
581 +
582 + __slots__ = ("arch", "filename", "multilib_category", "needed",
583 + "runpaths", "soname")
584 +
585 + _MIN_FIELDS = 5
586 + _MULTILIB_CAT_INDEX = 5
587 +
588 + @classmethod
589 + def parse(cls, filename, line):
590 + """
591 + Parse a NEEDED.ELF.2 entry. Raises InvalidData if necessary.
592 +
593 + @param filename: file name for use in exception messages
594 + @type filename: str
595 + @param line: a single line of text from a NEEDED.ELF.2 file,
596 + without a trailing newline
597 + @type line: str
598 + @rtype: NeededEntry
599 + @return: A new NeededEntry instance containing data from line
600 + """
601 + fields = line.split(";")
602 + if len(fields) < cls._MIN_FIELDS:
603 + raise InvalidData(_("Wrong number of fields "
604 + "in %s: %s\n\n") % (filename, line))
605 +
606 + obj = cls()
607 + # Extra fields may exist (for future extensions).
608 + if (len(fields) > cls._MULTILIB_CAT_INDEX and
609 + fields[cls._MULTILIB_CAT_INDEX]):
610 + obj.multilib_category = fields[cls._MULTILIB_CAT_INDEX]
611 + else:
612 + obj.multilib_category = None
613 +
614 + del fields[cls._MIN_FIELDS:]
615 + obj.arch, obj.filename, obj.soname, rpaths, needed = fields
616 + obj.runpaths = tuple(filter(None, rpaths.split(":")))
617 + obj.needed = tuple(filter(None, needed.split(",")))
618 +
619 + return obj
620 +
621 + def __str__(self):
622 + """
623 + Format this entry for writing to a NEEDED.ELF.2 file.
624 + """
625 + return ";".join([
626 + self.arch,
627 + self.filename,
628 + self.soname,
629 + ":".join(self.runpaths),
630 + ",".join(self.needed),
631 + (self.multilib_category if self.multilib_category
632 + is not None else "")
633 + ]) + "\n"
634 +
635 + if sys.hexversion < 0x3000000:
636 +
637 + __unicode__ = __str__
638 +
639 + def __str__(self):
640 + return _unicode_encode(self.__unicode__(),
641 + encoding=_encodings['content'])
642 +
643 + __str__.__doc__ = __unicode__.__doc__
644 diff --git a/pym/portage/util/_dyn_libs/soname_deps.py b/pym/portage/util/_dyn_libs/soname_deps.py
645 new file mode 100644
646 index 0000000..a7d5954
647 --- /dev/null
648 +++ b/pym/portage/util/_dyn_libs/soname_deps.py
649 @@ -0,0 +1,138 @@
650 +# Copyright 2015 Gentoo Foundation
651 +# Distributed under the terms of the GNU General Public License v2
652 +
653 +from __future__ import unicode_literals
654 +
655 +import fnmatch
656 +from itertools import chain
657 +import os
658 +import re
659 +
660 +from portage.util import shlex_split
661 +
662 +class SonameDepsProcessor(object):
663 + """
664 + Processes NEEDED.ELF.2 entries for one package, in order to generate
665 + REQUIRES and PROVIDES metadata.
666 +
667 + Any sonames provided by the package will automatically be filtered
668 + from the generated REQUIRES values.
669 + """
670 +
671 + def __init__(self, provides_exclude, requires_exclude):
672 + """
673 + @param provides_exclude: PROVIDES_EXCLUDE value
674 + @type provides_exclude: str
675 + @param requires_exclude: REQUIRES_EXCLUDE value
676 + @type requires_exclude: str
677 + """
678 + self._provides_exclude = self._exclude_pattern(provides_exclude)
679 + self._requires_exclude = self._exclude_pattern(requires_exclude)
680 + self._requires_map = {}
681 + self._provides_map = {}
682 + self._provides_unfiltered = {}
683 + self._provides = None
684 + self._requires = None
685 + self._intersected = False
686 +
687 + @staticmethod
688 + def _exclude_pattern(s):
689 + # shlex_split enables quoted whitespace inside patterns
690 + if s:
691 + pat = re.compile("|".join(
692 + fnmatch.translate(x.lstrip(os.sep))
693 + for x in shlex_split(s)))
694 + else:
695 + pat = None
696 + return pat
697 +
698 + def add(self, entry):
699 + """
700 + Add one NEEDED.ELF.2 entry, for inclusion in the generated
701 + REQUIRES and PROVIDES values.
702 +
703 + @param entry: NEEDED.ELF.2 entry
704 + @type entry: NeededEntry
705 + """
706 +
707 + multilib_cat = entry.multilib_category
708 + if multilib_cat is None:
709 + # This usage is invalid. The caller must ensure that
710 + # the multilib category data is supplied here.
711 + raise AssertionError(
712 + "Missing multilib category data: %s" % entry.filename)
713 +
714 + if entry.needed and (
715 + self._requires_exclude is None or
716 + self._requires_exclude.match(
717 + entry.filename.lstrip(os.sep)) is None):
718 + for x in entry.needed:
719 + if (self._requires_exclude is None or
720 + self._requires_exclude.match(x) is None):
721 + self._requires_map.setdefault(
722 + multilib_cat, set()).add(x)
723 +
724 + if entry.soname:
725 + self._provides_unfiltered.setdefault(
726 + multilib_cat, set()).add(entry.soname)
727 +
728 + if entry.soname and (
729 + self._provides_exclude is None or
730 + (self._provides_exclude.match(
731 + entry.filename.lstrip(os.sep)) is None and
732 + self._provides_exclude.match(entry.soname) is None)):
733 + self._provides_map.setdefault(
734 + multilib_cat, set()).add(entry.soname)
735 +
736 + def _intersect(self):
737 + requires_map = self._requires_map
738 + provides_map = self._provides_map
739 + provides_unfiltered = self._provides_unfiltered
740 +
741 + for multilib_cat in set(chain(requires_map, provides_map)):
742 + requires_map.setdefault(multilib_cat, set())
743 + provides_map.setdefault(multilib_cat, set())
744 + provides_unfiltered.setdefault(multilib_cat, set())
745 + for soname in list(requires_map[multilib_cat]):
746 + if soname in provides_unfiltered[multilib_cat]:
747 + requires_map[multilib_cat].remove(soname)
748 +
749 + provides_data = []
750 + for multilib_cat in sorted(provides_map):
751 + if provides_map[multilib_cat]:
752 + provides_data.append(multilib_cat + ":")
753 + provides_data.extend(sorted(provides_map[multilib_cat]))
754 +
755 + if provides_data:
756 + self._provides = " ".join(provides_data) + "\n"
757 +
758 + requires_data = []
759 + for multilib_cat in sorted(requires_map):
760 + if requires_map[multilib_cat]:
761 + requires_data.append(multilib_cat + ":")
762 + requires_data.extend(sorted(requires_map[multilib_cat]))
763 +
764 + if requires_data:
765 + self._requires = " ".join(requires_data) + "\n"
766 +
767 + self._intersected = True
768 +
769 + @property
770 + def provides(self):
771 + """
772 + @rtype: str
773 + @return: PROVIDES value generated from NEEDED.ELF.2 entries
774 + """
775 + if not self._intersected:
776 + self._intersect()
777 + return self._provides
778 +
779 + @property
780 + def requires(self):
781 + """
782 + @rtype: str
783 + @return: REQUIRES value generated from NEEDED.ELF.2 entries
784 + """
785 + if not self._intersected:
786 + self._intersect()
787 + return self._requires
788 diff --git a/pym/portage/util/elf/__init__.py b/pym/portage/util/elf/__init__.py
789 new file mode 100644
790 index 0000000..4725d33
791 --- /dev/null
792 +++ b/pym/portage/util/elf/__init__.py
793 @@ -0,0 +1,2 @@
794 +# Copyright 2015 Gentoo Foundation
795 +# Distributed under the terms of the GNU General Public License v2
796 diff --git a/pym/portage/util/elf/constants.py b/pym/portage/util/elf/constants.py
797 new file mode 100644
798 index 0000000..f687fdb
799 --- /dev/null
800 +++ b/pym/portage/util/elf/constants.py
801 @@ -0,0 +1,45 @@
802 +# Copyright 2015 Gentoo Foundation
803 +# Distributed under the terms of the GNU General Public License v2
804 +#
805 +# These constants are available from elfutils:
806 +# https://git.fedorahosted.org/cgit/elfutils.git/tree/libelf/elf.h
807 +
808 +EI_CLASS = 4
809 +ELFCLASS32 = 1
810 +ELFCLASS64 = 2
811 +
812 +EI_DATA = 5
813 +ELFDATA2LSB = 1
814 +ELFDATA2MSB = 2
815 +
816 +E_TYPE = 16
817 +ET_REL = 1
818 +ET_EXEC = 2
819 +ET_DYN = 3
820 +ET_CORE = 4
821 +
822 +E_MACHINE = 18
823 +EM_SPARC = 2
824 +EM_386 = 3
825 +EM_68K = 4
826 +EM_MIPS = 8
827 +EM_PARISC = 15
828 +EM_SPARC32PLUS = 18
829 +EM_PPC = 20
830 +EM_PPC64 = 21
831 +EM_S390 = 22
832 +EM_ARM = 40
833 +EM_SH = 42
834 +EM_SPARCV9 = 43
835 +EM_IA_64 = 50
836 +EM_X86_64 = 62
837 +EM_AARCH64 = 183
838 +EM_ALPHA = 0x9026
839 +
840 +E_ENTRY = 24
841 +EF_MIPS_ABI = 0x0000f000
842 +EF_MIPS_ABI2 = 0x00000020
843 +E_MIPS_ABI_O32 = 0x00001000
844 +E_MIPS_ABI_O64 = 0x00002000
845 +E_MIPS_ABI_EABI32 = 0x00003000
846 +E_MIPS_ABI_EABI64 = 0x00004000
847 diff --git a/pym/portage/util/elf/header.py b/pym/portage/util/elf/header.py
848 new file mode 100644
849 index 0000000..3d23074
850 --- /dev/null
851 +++ b/pym/portage/util/elf/header.py
852 @@ -0,0 +1,65 @@
853 +# Copyright 2015 Gentoo Foundation
854 +# Distributed under the terms of the GNU General Public License v2
855 +
856 +from portage.util.endian.decode import (decode_uint16_le,
857 + decode_uint32_le, decode_uint16_be, decode_uint32_be)
858 +from portage.util.elf.constants import (E_ENTRY, E_MACHINE, E_TYPE,
859 + EI_CLASS, ELFCLASS32, ELFCLASS64, ELFDATA2LSB, ELFDATA2MSB)
860 +
861 +class ELFHeader(object):
862 +
863 + __slots__ = ('e_flags', 'e_machine', 'e_type', 'ei_class',
864 + 'ei_data')
865 +
866 + @classmethod
867 + def read(cls, f):
868 + """
869 + @param f: an open ELF file
870 + @type f: file
871 + @rtype: ELFHeader
872 + @return: A new ELFHeader instance containing data from f
873 + """
874 + f.seek(EI_CLASS)
875 + ei_class = ord(f.read(1))
876 + ei_data = ord(f.read(1))
877 +
878 + if ei_class == ELFCLASS32:
879 + width = 32
880 + elif ei_class == ELFCLASS64:
881 + width = 64
882 + else:
883 + width = None
884 +
885 + if ei_data == ELFDATA2LSB:
886 + uint16 = decode_uint16_le
887 + uint32 = decode_uint32_le
888 + elif ei_data == ELFDATA2MSB:
889 + uint16 = decode_uint16_be
890 + uint32 = decode_uint32_be
891 + else:
892 + uint16 = None
893 + uint32 = None
894 +
895 + if width is None or uint16 is None:
896 + e_flags = None
897 + e_machine = None
898 + e_type = None
899 + else:
900 + f.seek(E_TYPE)
901 + e_type = uint16(f.read(2))
902 + f.seek(E_MACHINE)
903 + e_machine = uint16(f.read(2))
904 +
905 + # E_ENTRY + 3 * sizeof(uintN)
906 + e_flags_offset = E_ENTRY + 3 * width // 8
907 + f.seek(e_flags_offset)
908 + e_flags = uint32(f.read(4))
909 +
910 + obj = cls()
911 + obj.e_flags = e_flags
912 + obj.e_machine = e_machine
913 + obj.e_type = e_type
914 + obj.ei_class = ei_class
915 + obj.ei_data = ei_data
916 +
917 + return obj
918 diff --git a/pym/portage/util/endian/__init__.py b/pym/portage/util/endian/__init__.py
919 new file mode 100644
920 index 0000000..4725d33
921 --- /dev/null
922 +++ b/pym/portage/util/endian/__init__.py
923 @@ -0,0 +1,2 @@
924 +# Copyright 2015 Gentoo Foundation
925 +# Distributed under the terms of the GNU General Public License v2
926 diff --git a/pym/portage/util/endian/decode.py b/pym/portage/util/endian/decode.py
927 new file mode 100644
928 index 0000000..9833b53
929 --- /dev/null
930 +++ b/pym/portage/util/endian/decode.py
931 @@ -0,0 +1,48 @@
932 +# Copyright 2015 Gentoo Foundation
933 +# Distributed under the terms of the GNU General Public License v2
934 +
935 +import struct
936 +
937 +def decode_uint16_be(data):
938 + """
939 + Decode an unsigned 16-bit integer with big-endian encoding.
940 +
941 + @param data: string of bytes of length 2
942 + @type data: bytes
943 + @rtype: int
944 + @return: unsigned integer value of the decoded data
945 + """
946 + return struct.unpack_from(">H", data)[0]
947 +
948 +def decode_uint16_le(data):
949 + """
950 + Decode an unsigned 16-bit integer with little-endian encoding.
951 +
952 + @param data: string of bytes of length 2
953 + @type data: bytes
954 + @rtype: int
955 + @return: unsigned integer value of the decoded data
956 + """
957 + return struct.unpack_from("<H", data)[0]
958 +
959 +def decode_uint32_be(data):
960 + """
961 + Decode an unsigned 32-bit integer with big-endian encoding.
962 +
963 + @param data: string of bytes of length 4
964 + @type data: bytes
965 + @rtype: int
966 + @return: unsigned integer value of the decoded data
967 + """
968 + return struct.unpack_from(">I", data)[0]
969 +
970 +def decode_uint32_le(data):
971 + """
972 + Decode an unsigned 32-bit integer with little-endian encoding.
973 +
974 + @param data: string of bytes of length 4
975 + @type data: bytes
976 + @rtype: int
977 + @return: unsigned integer value of the decoded data
978 + """
979 + return struct.unpack_from("<I", data)[0]
980 --
981 2.0.5